目录
操作系统篇
1 Linux中查看进程运行状态的指令、查看内存使用情况的指令、 tar解压文件的参数。
2 文件权限怎么修改
3 说说常用的Linux命令
4 说说如何以root权限运行某个程序。
5 说说软链接和硬链接的区别。
6 说说静态库和动态库怎么制作及如何使用,区别是什么。
7 简述GDB常见的调试命令,什么是条件断点,多进程下如何调试。
8 说说什么是大端小端,如何判断大端小端?
9 说说进程调度算法有哪些?
10 简述操作系统如何申请以及管理内存的?
11 简述Linux系统态与用户态,什么时候会进入系统态?
12 简述LRU算法及其实现方式。
13 一个线程占多大内存?
14 什么是页表,为什么要有?
15 简述操作系统中的缺页中断。
16 说说虚拟内存分布,什么时候会由用户态陷入内核态?
17 简述一下虚拟内存和物理内存,为什么要用虚拟内存,好处是什么?
18 虚拟地址到物理地址怎么映射的?
19 说说堆栈溢出是什么,会怎么样?
20 简述操作系统中malloc的实现原理
22 32位系统能访问4GB以上的内存吗?
23 请你说说并发和并行
24 说说进程、线程、协程是什么,区别是什么?
25 请你说说Linux的fork的作用
26 请你说说什么是孤儿进程,什么是僵尸进程,如何解决僵尸进程
28 说说进程通信的方式有哪些?
29 说说进程同步的方式?
30 说说Linux进程调度算法及策略有哪些?
31 说说进程有多少种状态?
33 简述mmap的原理和使用场景
34 互斥量能不能在进程中使用?
35 协程是轻量级线程,轻量级表现在哪里?
36 说说常见信号有哪些,表示什么含义?
37 说说线程间通信的方式有哪些?
41 单核机器上写多线程程序,是否要考虑加锁,为什么?
42 说说多线程和多进程的不同?
43 简述互斥锁的机制,互斥锁与读写的区别?
44 说说什么是信号量,有什么作用?
46 简述自旋锁和互斥锁的使用场景
48 多线程和单线程有什么区别,多线程编程要注意什么,多线程加锁
49 说说sleep和wait的区别?
51 进程和线程相比,为什么慢?
52 简述Linux零拷贝的原理?
操作系统篇
1 Linux中查看进程运行状态的指令、查看内存使用情况的指令、 tar解压文件的参数。
查看进程运行状态的指令
:
ps
命令。
“
ps -aux | grep PID
”
,用来查看某
PID
进程状态
查看内存使用情况的指令
:
free
命令。
“
free -m
”
,命令查看内存使用情况。
tar
解压文件的参数
:
五个命令中必选一个–
c
:
建立压缩档案–
x
:解压–
t
:查看内容–
r
:向压缩归档文件末尾追加文件–
u
:更新原压缩包中的文件这几个参数是可选的–
z
:有
gzip
属性的–
j
:有
bz2
属性的–
Z
:有
compress
属性的–
v
:显示所有过程–
O
:将文件解开到标准输出
答案解析
//ps
使用示例//
显示当前所有进程ps
–
A//
与
grep
联用查找某进程ps
–
aux
|
grep apache//
查看进程运行状态、查看内存使用情况的指令均可使用
top
指令。top
2 文件权限怎么修改
文件的基本权限就有九个,分别是
owner/group/others
三种身份各有自己的
read/write/execute
chmod
-rwxrwxrwx
时,这九个权限是三个三个一组。其中,我们可以使用数字来代表各个权限。
每种身份
(owner/group/others)
各自的三个权限
(r/w/x)
分数是需要累加的,例如当权限为:
[-rwxrwx—]
,则分数是:owner = rwx = 4+2+1 = 7group = rwx = 4+2+1 = 7others= — = 0+0+0 = 0所以我们设定权限的变更时,该文件的权限数字就是
770
!变更权限的指令
chmod
的语法是这样的:
[
root@www ~
]
# chmod
[
–
R
]
xyz
文件或目录选项与参数:xyz
:
就是刚刚提到的数字类型的权限属性,为
rwx
属性数值的相加。–
R
:
进行递归
(
recursive
)
的持续变更,亦即连同次目录下的所有文件都会变更# chmod 770 douya.c
//
即修改
douya.c
文件的权限为
770
3 说说常用的Linux命令
命令:用于切换当前目录
命令:查看当前文件与目录
命令:该命令常用于分析一行的信息,若当中有我们所需要的信息,就将该行显示出来,该命令通常与管道命令一起使用,用于对一些命令的输出进行筛选加工。
命令:复制命令
命令:移动文件或文件夹命令
6. rm
命令:删除文件或文件夹命令
命令:查看进程情况
命令:向进程发送终止信号
命令:对文件进行打包,调用
gzip
或
bzip
对文件进行压缩或解压
命令:查看文件内容,与
less
、
more
功能相似
命令:可以查看操作系统的信息,如进程、
CPU
占用率、内存信息等
命令:命令用于显示工作目录。
4 说说如何以root权限运行某个程序。
sudo chown root app
(文件名)sudo chmod u
+
s app
(文件名)
5 说说软链接和硬链接的区别。
定义不同
限制不同
创建方式不同
影响不同
inode
号的文件。
,若被指向路径文件被重新创建,死链接可恢复为正常的软链接
6 说说静态库和动态库怎么制作及如何使用,区别是什么。
gcc hello.c -c //这样就生成hello.o目标文件
ar rcs libhello.a hello.o//生成libhello.a静态库
.
c
–
lhello
–
o staticLibrary
//main.c
和
hello
静态库链接,生成
staticLibrary
执行文
: 是指
main
主函数
: 是我们生成的
.a
文件砍头去尾(
lib
不要
.a
也不要)前面加
-l
: 是指告诉
gcc
编译器先从
-L
指定的路径去找静态库,默认是从
/usr/lib/
或者
去找。
: 是指当前路径的意思
: 是最后想生成的文件名(这里可随意起名字)
gcc
–
shared
–
fpic hello
.
c
–
o libhello
.
so–
shared
指定生成动态库–
fpic
:
fPIC
选项作用于编译阶段,在生成目标文件时就得使用该选项,以生成位置无关的代码。
动态库的使用:
gcc main
.
c
–
lhello
–
L
.
/ –
o dynamicDepot/*main.c
: 是指
main
主函数-lhello
: 是我们生成的
.so
文件砍头去尾(
lib
不要
.so
也不要)前面加
-l-L
: 是指告诉
gcc
编译器先从
-L
指定的路径去找静态库,默认是从
/usr/lib/
或者/usr/local/lib/
去找。./
: 是指当前路径的意思dynamicDepot
: 是最后想生成的文件名(这里可随意起名字)*/
静态库代码装载的速度快,执行速度略比动态库快。
动态库更加节省内存,可执行文件体积比静态库小很多。
静态库是在编译时加载,动态库是在运行时加载。
生成的静态链接库,
Windows
下以
.lib
为后缀,
Linux
下以
.a
为后缀。生成的动态链接库,
Windows下以.dll
为后缀,
Linux
下以
.so
为后缀。
7 简述GDB常见的调试命令,什么是条件断点,多进程下如何调试。
调试
:
gdb
调试的是可执行文件,在
gcc
编译时加入
-g
,告诉
gcc
在编译时加入调试信息,这样
gdb才能调试这个被编译的文件 gcc -g tesst.c -o test。
命令格式:
:退出
gdb
,结束调试
:查看程序源代码
,
10
:显示
5
到
10
行的代码
显示源文件
5
到
10
行的代码,在调试多个文件时使用
显示
get_sum
函数周围的代码
显示源文件
get_sum
函数周围的代码,在调试多个文件时使用
:字符串用来从当前行向前查找第一个匹配的字符串
:程序开始执行
:查看帮助信息
:设置断点
:在第七行设置断点
:以函数名设置断点
行号或者函数名
if
条件:以条件表达式设置断点
条件表达式:条件表达式发生改变时程序就会停下来
:继续执行下一条语句 ,会把函数当作一条语句执行
:继续执行下一条语句,会跟踪进入函数,一次一条的执行函数内的代码
break if
条件 以条件表达式设置断点
用
set follow-fork-mode child
调试子进程
set follow-fork-mode parent
调试父进程
8 说说什么是大端小端,如何判断大端小端?
:
低
的有效字节存储在
低的
存储器地址。小端一般为主机字节序;常用的
X86
结构是小端模式。很多的ARM
,
DSP
都为小端模式。
:
高
的有效字节存储在
低的
存储器地址。大端为网络字节序;
KEIL C51
则为大端模式。
ARM
处理器还可以由硬件来选择是大端模式还是小端模式。
联合体
来判断系统是大端还是小端。因为联合体变量总是从
低地址
存储
。
int fun1(){ union test{ char c; int i; }; test t; t.i = 1; //如果是大端,则t.c为0x00,则t.c != 1,反之是小端 return (t.c == 1); }
在进行网络通信时是否需要进行字节序转换?
跨平台进行网络数据通信时必须
不
,字节序转换函数会根据当前平台的存储模式做出相应正确的转换,如
网络字节序
字节流
,
对于一个多字节数值
,
在进行网络传输的时候
,
先传递哪个字节
?
也就是
,
当接收端收到第一个字节的时候
,
它将这个字节作为高位字节还是低位字节处理
,
是一个比较有意
; UDP/TCP/IP
协议规定
:
把接收到的第一个字节当作高位字节看待
,
这就要求
发送端发送的
;
而在发送端发送数据时
,
发送的第一个字节是该数值在内存中的起始地址处
,
也就是说
,
该数值在内存中的起始地址处对应的那个字节就是要发送的第一个高位
(
即
:
高位字节存放在低地址处
);
由此可见
,
多字节数值在发送之前
,
在内存中因该是以大端法存放
;
所以说
,
网络字节序是大端字节序
;
比如
,
我们经过网络发送整型数值
0x12345678
时
,
在
80X86
平
,
它是以小端发存放的
,
在发送之前需要使用系统提供的字节序转换函数
htonl()
将其转换成大端
;
9 说说进程调度算法有哪些?
先来先服务调度算法
短作业
(
进程
)
优先调度算法
高优先级优先调度算法
时间片轮转法
多级反馈队列调度算法
先来先服务调度算法:每次调度都是从后备作业(进程)队列中选择一个或多个
最先进入该队列的
作业
(进程),将它们调入内存,为它们分配资源、创建进程,然后放入就绪队列。
短作业
(
进程
)
优先调度算法:短作业优先
(SJF)
的调度算法是从后备队列中选择一个或若干个
估计运
行时间最短的作业(进程)
,将它们调入内存运行。
高优先级优先调度算法:当把该算法用于作业调度时,系统将从后备队列中选择若干个
优先权最高的作业装入内存。当用于进程调度时,该算法是把处理机分配给就绪队列中优先权最高的进程
时间片轮转法:每次调度时,
把
CPU
分配给队首进程,并令其执行一个时间片。时间片的大小从几
ms
到几百
ms
。当执行的时间片用完时,由一个计时器发出时钟中断请求,调度程序便据此信号来
停止该进程的执行,并将它送往就绪队列的末尾
;然后,再把处理机分配给就绪队列中新的队首进程,同时也让它执行一个时间片。
多级反馈队列调度算法:综合前面多种调度算法。
非抢占式优先权算法
抢占式优先权调度算法
(
原优先权最高的进程
)
的
i
时,就将其优先权
Pi
与正在执行的进程
j
的优先权
Pj
进行比较。如果
≤
Pj
,原进程
Pj
便继续执行;但如果是
Pi>Pj
,则立即停止
Pj
的执行,做进程切换,使
i
进程投入执
非抢占式(
Nonpreemptive
):
让进程运行直到结束或阻塞的调度方式,容易实现,适合专用系统
,不适合通用系统。抢占式(Preemptive):
允许将逻辑上可继续运行的在运行过程暂停的调度方式可防止单一进程长时间
独占,
CPU
系统开销大
(降低途径:硬件实现进程切换,或扩充主存以贮存大部分程序)。
10 简述操作系统如何申请以及管理内存的?
物理内存
:物理内存有四个层次,分别是寄存器、高速缓存、主存、磁盘。
内存管理器
(memory manager)
,它的主要工
虚拟内存
:
操作系统为每一个进程分配一个独立的地址空间
,但是虚拟内存。
虚拟内存与物理内存存在映射关系,通过页表寻址完成虚拟地址和物理地址的转换
。
*brk
和
mmap。
11 简述Linux系统态与用户态,什么时候会进入系统态?
内核态与用户态
:
内核态
(系统态)与
用户态
是操作系统的两种运行级别。内核态拥有最高权限,可以访问所有系统指令;用户态则只能访问一部分指令。
什么时候进入内核态
:共有三种方式:
a
、
系统调用
。
b
、
异常
。
c
、
设备中断
。
其中,系统调用是主动的,另外两种是被动的。
为什么区分内核态与用户态
:在
CPU
的所有指令中,
有一些指令是非常危险的,如果错用,将导致整个系统崩溃。
比如:清内存、设置时钟等。所以区分内核态与用户态主要是出于安全的考虑。
12 简述LRU算法及其实现方式。
LRU
算法
:
LRU
算法用于
缓存淘汰
。思路是
将缓存中最近最少使用的对象删除掉
实现方式
:利用
链表和
hashmap
。
存在
(一般称为命中),则
把该节点移到链
,如果
不存在
,则
新建一个节点,放到链表头部
,若
缓存满了,则把链表最后一个节点删除
。
在访问数据的时候,如果数据项在链表中存在,则把该节点移到链表头部,否则返回
-1
。这样一来在链表尾部的节点就是最近最久未访问的数据项。
class LRUCache {
list> cache;//创建双向链表
unordered_map>::iterator> map;//创建哈希表
int cap;
public:
LRUCache(int capacity) {
cap = capacity;
}
int get(int key) {
if (map.count(key) > 0){
auto temp = *map[key];
cache.erase(map[key]);
map.erase(key);
cache.push_front(temp);
map[key] = cache.begin();//映射头部
return temp.second;
}
return -1;
}
void put(int key, int value) {
if (map.count(key) > 0){
cache.erase(map[key]);
map.erase(key);
}
else if (cap == cache.size()){
auto temp = cache.back();
map.erase(temp.first);
cache.pop_back();
}
cache.push_front(pair(key, value));
map[key] = cache.begin();//映射头部
}
};
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache* obj = new LRUCache(capacity);
* int param_1 = obj->get(key);
* obj->put(key,value);
*/
13 一个线程占多大内存?
linux
的线程大概占
8M
内存
。
的栈是通过缺页来分配内存的,不是所有栈地址空间都分配了内存。因此,
8M
是最大消耗,实际的内存消耗只会略大于实际需要的内存(
内部损耗,每个在
4k
以内
)
。
14 什么是页表,为什么要有?
操作系统虚拟内存到物理内存的映射表,就被称为页表。
:不可能每一个虚拟内存的
Byte
都对应到物理内存的地址。这张表将大得真正的物理地址也放不下,于是操作系统引入了页(Page
)的概念。进行分页,这样可以
减小虚拟内存页对应物理内存页的映射表大小
。
Byte
都对应到物理内存的地址,每个条目最少需要
8
字节(
32
位虚拟地址
->32位物理地址),在 4G
内存的情况下,就需要
32GB
的空间来存放对照表,那么这张表就大得真正的物理地址也放不下了,于是操作系统引入了页(Page
)的概念。 在系统启动时,操作系统将整个物理内存以 4K
为单位,划分为各个页。之后进行内存分配时,都以页为单位,那么虚拟内存页对应物理内存页的映射表就大大减小了,4G
内存,只需要
8M
的映射表即可,一些进程没有使用到的虚拟内存,也并不需要保存映射关系,而且Linux
还为大内存设计了多级页表,可以进一页减少了内存消耗。
15 简述操作系统中的缺页中断。
缺页异常
:
malloc
和
mmap
函数在分配内存时只是建立了进程虚拟地址空间,并没有分配虚拟内存对应的物理内存。当进程访问这些没有建立映射关系的虚拟内存时,处理器自动触发一个缺页异
。
缺页中断
:缺页异常后将产生一个缺页中断,此时操作系统会根据页表中的
外存地址
在外存中找到所缺的一页,将其调入内存
。
保护
CPU
现场、分析中断原因、转入缺页中断处理程序、恢复CPU
现场,继续执行。
缺页中断与一般中断区别:
16 说说虚拟内存分布,什么时候会由用户态陷入内核态?
虚拟内存分布
:
:
代码段
.text
:
存放程序执行代码的一块内存区域。只读,代码段的头部还会包含一些只读的常数变量。
数据段
.data
:
存放程序中
已初始化
的
全局变量和静态变量
的一块内存区域。
BSS
段
.bss
:存放程序中
未初始化的全局变量和静态变量的一块内存区域。
可执行程序在运行时又会多出两个区域:
堆区和栈区
。
文件映射区
,位于堆和栈之间。
DMA
区、常规区、高位区。
什么时候进入内核态
:共有三种方式:
a
、
系统调用
。
b
、
异常
。
c
、
设备中断
。其中,系统调用是主动的,另外两种是被动的。
17 简述一下虚拟内存和物理内存,为什么要用虚拟内存,好处是什么?
物理内存
:物理内存有四个层次,分别是寄存器、高速缓存、主存、磁盘。
内存管理器
(memory manager)
,它的主要工
虚拟内存
:操作系统为
每一个进程分配一个独立的地址空间
,但是虚拟内存。虚拟内存与物理内存存在映射关系,通过页表寻址完成虚拟地址和物理地址的转换
。
为什么要用虚拟内存
:因为早期的内存分配方法存在以下问题:
进程地址空间不隔离。会导致数据被随意修改
。
内存使用效率低。
程序运行的地址不确定。操作系统随机为进程分配内存空间
,所以程序运行的地址是不确定
使用虚拟内存的
好处
:
扩大地址空间
。每个进程独占一个
4G
空间,虽然
真实物理内存没那么多
。
内存保护:防止不同进程对物理内存的争夺和践踏,可以对特定内存地址提供写保护,防止
实现内存共享,方便进程通信
。
避免内存碎片,虽然物理内存可能不连续,但映射到虚拟内存上可以连续
。
使用虚拟内存的
缺点
:
需要额外构建数据结构,占用空间
。
增加了执行时间
。
页面换入换出耗时
。
18 虚拟地址到物理地址怎么映射的?
页表中的每一项都记录了这个页的基地址。
逻辑地址转线性地址
:
段起始地址
+
段内偏移地址
=
线性地址
线性地址转物理地址
:
32
位的线性地址被划分为三部分:页目录索引(
DIRECTORY
,
10
位)、页表索引
TABLE
,
10
位)、页内偏移(
OFFSET
,
12
位)
cr3
中取出进程的页目录地址(操作系统调用进程时,这个地址被装入寄存器中)
+
页目录索引
=
页表地址
+
页表索引
=
页地址
+
页内偏移
=
物理地址
按照以上两步法,就完成了一个三级页表从虚拟地址到物理地址的转换。
19 说说堆栈溢出是什么,会怎么样?
向该数据块写入了过多的数据,导致数据越界
。常指调用堆栈溢出,本质上一种数据结构的满溢情况。堆栈溢出可以理解为两个方面:堆溢出和栈溢出。
堆溢出:比如不断的
new
一个对象,一直创建新的对象,而不进行释放,最终导致内存不足。将会报错:OutOfMemory Error
。
栈溢出:一次函数调用中,栈中将被依次压入:参数,返回地址等,而方法如果递归比较深或进去死循环,就会导致栈溢出。将会报错:StackOverflow Error
。
20 简述操作系统中malloc的实现原理
底层实现:
当开辟的空间小于
128K
时,调用
brk
()函数;当开辟的空间大于
128K
时,调用mmap()。
malloc
采用的是内存池的管理方式,以减少内存碎片。先申请大块内存作为堆区,然后将堆区分为多个内存块。当用户申请内存时,直接从堆区分配一块合适的空闲快。采用隐式链表将所有空闲块,每一个空闲块记录了一个未分配的、连续的内存地址。
说说进程空间从高位到低位都有些什么?
从高地址到低地址,一个程序由命令行参数和环境变量、栈、文件映射区、堆、
BSS
段、数据
段、代码段组成。
命令行参数和环境变量
栈区:
存储局部变量、函数参数值。栈从高地址向低地址增长。是一块连续的空间。
文件映射区
,位于堆和栈之间。
堆区:
动态申请内存用。堆从低地址向高地址增长。
BSS
段
:存放程序中未初始化的全局变量和静态变量的一块内存区域。
数据段
:存放程序中已初始化的全局变量和静态变量的一块内存区域。
代码段:
存放程序执行代码的一块内存区域。只读,代码段的头部还会包含一些只读的常数变量。
22 32位系统能访问4GB以上的内存吗?
。原因是计算机使用二进制,每位数只有
0
或
1
两个状态,
32
位正好是
2
的
32
次方,正好是4G
,所以大于
4G
就没办法表示了,而在
32
位的系统中,因其它原因还需要占用一部分空间,所以内存只能识别3G
多。要使用
4G
以上就只能换
64
位的操作系统了。但是使用PAE
技术
就可以实现
32
位系统能访问
4GB
以上的内存。
(
PAE
)技术最初是
为了弥补
32
位地址在
PC
服务器应用上的不足而推出的
。我们知道,传统的
IA32
架构只有
32
位地址总线,只能让系统容纳不超过
4GB
的内存,这么大的内存,对于普通的桌面应用应该说是足够用了。可是,对于服务器应用来说,还是显得不足,因为服务器上可能承载了很多同时运行的应用。
PAE
技术将地址扩展到了
36
位,这样,系统就能够容纳
2^36=64GB的内存。
23 请你说说并发和并行
并发
:对于单个
CPU
,在一个时刻只有一个进程在运行,但是线程的切换时间则减少到纳秒数量级,多个任务不停来回快速切换。
并行
:对于多个
CPU
,多个进程同时运行。
区别
。通俗来讲,它们虽然都说是
“
多个进程同时运行
“
,但是它们的
“
同时
“
不是一个概念。并行
“
同时
“
是同一时刻可以多个任务在运行
(
处于
running)
,并发的
“
同时
“
是经过不同线程快速切换,
24 说说进程、线程、协程是什么,区别是什么?
进程
:程序是指令、数据及其组织形式的描述,而进程则是程序的运行实例,包括程序计数器、寄存器和变量的当前值。
线程
:微进程,一个进程里更小粒度的执行单元。一个进程里包含多个线程并发执行任务。
协程
:协程是微线程,在子程序内部执行,可在子程序内部中断,转而执行别的子程序,在适当的时候再返回来接着执行。
:
线程与进程的区别
:
一个线程挂掉,对应的进程挂掉
;一个进程挂掉,不会影响其他进程。
进程是系统资源调度的最小单位
;
线程
CPU
调度的最小单位
。
进程在执行时拥有独立的内存单元,多个线程共享进程的内存
,如代码段、数据段、扩展
线程与协程的区别:
协程执行效率极高
。协程
直接操作栈基本没有内核切换的开销
,所以上下文的切换非常快,
协程不需要多线程的锁机制,因为多个协程从属于一个线程,不存在同时写变量冲突,效率
25 请你说说Linux的fork的作用
函数用来创建一个子进程。对于父进程,
fork()
函数返回新创建的子进程的
PID
。对于子进程,
fork() 函数调用成功会返回0
。如果创建出错,
fork()
函数返回
-1
。
函数,其原型如下:
#includepid_t
fork
(
void
);
函数不需要参数,返回值是一个进程标识符
PID
。返回值有以下三种情况:
fork()
函数返回新创建的子进程的
PID
。
fork()
函数调用成功会返回
0
。
fork()
函数返回
-1
。
函数创建一个新进程后,会为这个新
进程分配进程空间,将父进程的进程空间中的内容复制到子进程的进程空间中
,包括父进程的数据段和堆栈段,并且和父进程共享代码段。这时候,子进程和父进程一模一样,都接受系统的调度。因为两个进程都停留在fork()
函数中,最后
fork()
函数会返回两次,一次在父进程中返回,一次在子进程中返回,两次返回的值不一样,如上面的三种情况。
26 请你说说什么是孤儿进程,什么是僵尸进程,如何解决僵尸进程
孤儿进程
:是指一个父进程退出后,而它的一个或多个子进程还在运行,那么这些子进程将成为孤 儿进程。孤儿进程将被
init
进程(进程号为
1
)所收养
,并且由
init
进程对它们完整状态收集工作。
僵尸进程
:是指一个进程使用
fork
函数创建子进程,如果子进程退出,而父进程并没有调用
wait() 或者waitpid()
系统调用取得子进程的终止状态
,那么
子进程的进程描述符仍然保存在系统中,占用系统资源
,这种进程称为僵尸进程。
如何解决僵尸进程
:
fork
子进程之后我们都要及时使用
wait
系统调用
;同时,
SIGCHLD
信号,所以我们可以建立一个
捕获SIGCHLD信号的信号处理函数,在函数体中调用
wait
(或
waitpid
),就可以清理退出的子进程以
使用
kill
命令
。
:
ps aux
|
grep Z
kill
–
s SIGCHLD
pid
(
父进程
pid
)
请你说说什么是守护进程,如何实现?
守护进程
:守护进程是
运行在后台的一种生存期长的特殊进程。它独立于控制终端,处理一些系统级别任务。
如何实现
:
fork()
产生一个子进程,然后使父进程退出。
setsid()
创建一个新会话。
将当前目录更改为根目录
。使用
fork()
创建的子进程也继承了父进程的当前工作目录。
#include
#include
#include
#include
#include
#include
#include
#include
#define MAXFILE 65535
int main(){
//第一步:创建进程
int pid = fork();
if (pid > 0)
exit(0);//结束父进程
else if (pid
28 说说进程通信的方式有哪些?
管道
、
系统
IPC
(包括消息队列、信号量、信号、共享内存)、
套接字
socket
。
管道
:包括无名管道和命名管道,无名管道半双工,只能用于具有亲缘关系的进程直接的通信(父子进程或者兄弟进程),可以看作一种特殊的文件;命名管道可以允许无亲缘关系进程间的通信。
系统
IPC
:消息的链接表,放在内核中。消息队列独立于发送与接收进程,进程终止时,消息队列
semaphore
:是一个计数器,可以用来控制多个进程对共享资源的访问。信号量用于实现
:用于通知接收进程某个事件的发生。
:使多个进程访问同一块内存空间。
29 说说进程同步的方式?
信号量
semaphore
:是一个计数器,可以用来控制多个进程对共享资源的访问。信号量用于实现 进程间的互斥与同步。P
操作
(
递减操作
)
可以用于阻塞一个进程,
V
操作
(
增加操作
)
可以用于解除阻 塞一个进程。
管道
:一个进程通过调用管程的一个过程进入管程。在任何时候,只能有一个进程在管程中执行, 调用管程的任何其他进程都被阻塞,以等待管程可用。
消息队列
:消息的链接表,放在内核中。消息队列独立于发送与接收进程,进程终止时,消息队列及其内容并不会被删除;消息队列可以实现消息的随机查询,可以按照消息的类型读取。
30 说说Linux进程调度算法及策略有哪些?
先来先服务调度算法
短作业
(
进程
)
优先调度算法
高优先级优先调度算法
时间片轮转法
多级反馈队列调度算法
先来先服务调度算法:每次调度都是从后备作业(进程)队列中选择一个或多个最先进入该队列的 作业(进程),将它们调入内存,为它们分配资源、创建进程,然后放入就绪队列。
短作业
(
进程
)
优先调度算法:短作业优先
(SJF)
的调度算法是从后备队列中选择一个或若干个估计运 行时间最短的作业(进程),将它们调入内存运行。
高优先级优先调度算法:当把该算法用于作业调度时,系统将从后备队列中选择若干个优先权最高 的作业装入内存。当用于进程调度时,该算法是把处理机分配给就绪队列中优先权最高的进程
时间片轮转法:每次调度时,把
CPU
分配给队首进程,并令其执行一个时间片。时间片的大小从几 ms 到几百
ms
。当执行的时间片用完时,由一个计时器发出时钟中断请求,调度程序便据此信号来
多级反馈队列调度算法:综合前面多种调度算法。
非抢占式优先权算法
抢占式优先权调度算法
(
原优先权最高的进程
)
的
i
时,就将其优先权
Pi
与正在执行的进程
j
的优先权
Pj
进行比较。如果
≤
Pj
,原进程
Pj
便继续执行;但如果是
Pi>Pj
,则立即停止
Pj
的执行,做进程切换,使
i
进程投入执
Nonpreemptive
):让进程运行直到结束或阻塞的调度方式,容易实现,适合专用系统,不 适合通用系统。
):允许将逻辑上可继续运行的在运行过程暂停的调度方式可防止单一进程长时间 独占,CPU
系统开销大(降低途径:硬件实现进程切换,或扩充主存以贮存大部分程序)
31 说说进程有多少种状态?
创建、就绪、执行、阻塞、终止
。一个进程创建后,被放入队列处于就绪状态,等待 操作系统调度执行,执行过程中可能切换到阻塞状态(并发),任务完成后,进程销毁终止。
创建状态
,需要获取系统资源创建进程管理块(
PCB
:
)完成资源分配。
创建状态
完成之后,进程已经准备好,处于
就绪状态
,但是还未获得处理器资源,无法运行。
当具有时间片
开始进入
运行状态
。如果进程的时间片用完了就进入
就绪
状态
。
运行状态
期间,如果进行了阻塞的操作,如耗时的
I/O
操作,此时进程暂时无法操作就进入到了
阻塞状
态
,在这些操作完成后就进入
就绪状态
。等待再次获取处理器资源,被系统调度,
当具有时间片
就进入
运行状态
。
终止状态
进程通信中的管道实现原理是什么?
缓冲区
(称为
管道
)用于通信。
管道
是一种两个进程间进行
单向通信
的机制。因为这种单向性,管道又称为半双工管道,所以其使用是有一定的局限性的。半双工是指数据只能由一个进程流向另一个进程(一个管道负责读,一个管道负责写);如果是全双工通信,需要建立两个管道。管道分为无名管道和命名管道,无名管道只能用于具有亲缘关系的进程直接的通信(父子进程或者兄弟进程),可以看作一种特殊的文件,管道本质是一种文件
;命名管道可以允许无亲缘关系进程间的通信。
#
include
unistd
.
h
>
int
pipe
(
int
fd
[
2
]);
函数创建的管道处于一个进程中间,因此一个进程在由
pipe()
创建管道后,一般再使用
fork()
建立一个子进程,然后通过管道实现父子进程间的通信。管道两端可分别用描述字fd[0]
以及
fd[1]
来描述。注意管道的两端的任务是固定的,即一端只能用于读,由描述字fd[0]
表示,称其为管道读端;另 一端则只能用于写,由描述字fd[1]
来表示,称其为管道写端。如果试图从管道写端读取数据,或者向管道读端写入数据都将发生错误。一般文件的 I/O
函数都可以用于管道,如
close()
、
read()
、
write()
等。
如下:
父进程调用
pipe
开辟管道
,
得到两个文件描述符指向管道的两端。
父进程调用
fork
创建子进程
,
那么子进程也有两个文件描述符指向同一管道。
父进程关闭管道读端
,
子进程关闭管道写端。父进程可以往管道里写
,
子进程可以从管道里读
,
管道是 用环形队列实现的,
数据从写端流入从读端流出
,
这样就实现了进程间通信。
#include
#include
#include
#include
#define INPUT 0
#define OUTPUT 1
int main(){
//创建管道
int fd[2];
pipe(fd);
//创建子进程
pid_t pid = fork();
if (pid
33 简述mmap的原理和使用场景
:
mmap
是一种内存映射文件的方法
,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read, write
等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。如下图:
:
对同一块区域频繁读写操作;
可用于实现用户空间和内核空间的高效交互
可提供进程间共享内存及相互通信
可实现高效的大规模数据传输。
34 互斥量能不能在进程中使用?
。
存在资源竞争或并发使用的问题
,所以需要
互斥量
。
互斥量
,因为一个进程中可以包含多个线程,线程与线程之间需要通过互斥的手段进行同步,避免导致共享数据修改引起冲突。可以使用互斥锁
,属于互斥量的一种。
35 协程是轻量级线程,轻量级表现在哪里?
协程调用跟切换比线程效率高
:协程执行效率极高。协程不需要多线程的锁机制,可以不加锁的访问全局变量,所以上下文的切换非常快。
协程占用内存少
:执行协程只需要极少的栈内存(大概是
4
~
5KB
),而默认情况下,线程栈的大小为1MB
。
切换开销更少
:协程直接操作栈基本没有内核切换的开销,所以切换开销比线程少。
36 说说常见信号有哪些,表示什么含义?
的信号为传统
UNIX
支持的信号,是不可靠信号
(
非实时的
)
。不可靠信号和可靠信号的区别在于前者不支持排队,可能会造成信号丢失,而后者不会。编号为1 ~ 31
的信号如下:
:
其中最重要的就是 “1”、“9”、“15”、“17”这几个信号。
37 说说线程间通信的方式有哪些?
临界区、互斥量、信号量、条件变量、读写锁
:
临界区:每个线程中访问临界资源的那段代码称为临界区(
Critical Section
)(临界资源是一次仅允许一个线程使用的共享资源)。每次只准许一个线程进入临界区,进入后不允许其他线程进入。
互斥量:采用互斥对象机制,只有拥有互斥对象的线程才可以访问。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问。
信号量:计数器,允许多个线程同时访问同一个资源。
条件变量:通过条件变量通知操作的方式来保持多线程同步。
读写锁:读写锁与互斥量类似。但互斥量要么是锁住状态,要么就是不加锁状态。读写锁一次只允许一个线程写,但允许一次多个线程读,这样效率就比互斥锁要高。
说说线程同步方式有哪些?
互斥锁、信号量、条件变量、读写锁
:
互斥锁
:采用互斥对象机制,只有拥有互斥对象的线程才可以访问。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问。
信号量
:计数器,允许多个线程同时访问同一个资源。
条件变量
:通过条件变量通知操作的方式来保持多线程同步。
读写锁
:读写锁与互斥量类似。但互斥量要么是锁住状态,要么就是不加锁状态。读写锁一次只允许一个线程写,但允许一次多个线程读,这样效率就比互斥锁要高。
说说什么是死锁,产生的条件,如何解决?
死锁
:
是指多个进程在执行过程中,
因争夺资源而造成了互相等待
。此时系统产生了死锁。比如两只羊过独木桥,若两只羊互不相让,争着过桥,就产生死锁。
产生的条件
:死锁发生有
四个必要条件
:
互斥条件
:进程对所分配到的资源不允许其他进程访问,若其他进程访问,只能等待,直到
请求保持条件
:进程获得一定资源后,又对其他资源发出请求,但该资源被其他进程占有,
不可剥夺条件
:进程已获得的资源,只能自己释放,不可剥夺;
环路等待条件
:若干进程之间形成一种头尾相接的循环等待资源关系。
如何解决
:
T1
和
T2
,它们分别占有
R1
和
R2
资源
T1
请求
R2
资源的同时,
T2
请求
R1
资源。
T2
说:你把
R1
给我,我就给你
R2。
说:不行,你要先给我
R2
,我才能给你
R1
有了进程,为什么还要有线程?
原因
执行单元
。每次进程切换,都要先保存进程资源然后再恢
但是进程频繁切换将引起额外开销,从而严重影响系统的性能。
为了减少
粒度
的执行单元来实现并
线程
。
线程与进程对比
进程间的信息难以共享。
由于除去只读代码段外,父子进程并未共享内存,因此必须采用一
多个线程共享
进程的内存,如代码段、数据段、扩展段,线程间进行信息交换十分方便。
fork()
来创建进程的代价相对较高,即便利用写时复制技术,仍然需要复制诸如内存页
fork()
调用在时间上的开销依然不菲。
10
倍甚至更多。
线程间是共享虚拟地址空间的,无需采用写时复
41 单核机器上写多线程程序,是否要考虑加锁,为什么?
:因为线程锁通常用来实现线程的同步和通信。在单核机器上的多线程程序,仍然存在线程同步的问题。因为在抢占式操作系统中,通常为每个线程分配一个时间片,当某个线程时间片耗尽时,操作系统会将其挂起,然后运行另一个线程。如果这两个线程共享某些数据,不使用线程锁的前提下,可能会
导致共享数据修改引起冲突。
42 说说多线程和多进程的不同?
TLB
并获取新的地址空间,然后切换硬件上下文和内核栈;多线程切换时只
43 简述互斥锁的机制,互斥锁与读写的区别?
互斥锁机制
:
mutex
,用于保证在任何时刻,都只能有一个线程访问该对象。当获取锁操作失败
互斥锁和读写锁
:
:
bool
型变量,为
true
时表示锁可获取,为
false
时表示已上锁。这里说的是
互斥锁
, 其实是泛指linux
中所有的锁机制。 我们采用互斥锁保护临界区,从而防止竞争条件。也就是说,一个线程在进入临界区时应得到锁;它在退出临界区时释放锁。函数 acquire()
获取锁,而函数
release()
释放锁,如图 :
available
,它的值表示锁是否可用。如果锁是可用的,那么调用
acquire() 会成功,并且锁不再可用。当一个线程试图获取不可用的锁时,它会阻塞,直到锁被释放。
acquire()
:
acquire() {
while (!available);
/* busy wait */
available = false;
}
release()
:
release
() {available
=
true
;}
44 说说什么是信号量,有什么作用?
概念
:
信号量本质上是一个计数器,用于多进程对共享数据对象的读取,它主要是用来保护共享资源(
信号量也属于临界资源),使得资源在一个时刻只有一个进程独享。
原理
:由于信号量只能进行两种操作等待和发送信号,即
P(sv)
和
V(sv)
,具体的行为如下:
)
P(sv)
操作:如果
sv
的值大于零,就给它减
1
;如果它的值为零,就挂起该进程的执行(信号量
1
,表示它使用了一个资源单位)。
)
V(sv)
操作:如果有其他进程因等待
sv
而被挂起,就让它恢复运行,如果没有进程因等待
sv
而
1
(若此时信号量的值为
0
,则进程进入挂起状态,直到信号量的值大于
0
,若进程
作用
:用于多进程对共享数据对象的读取,它主要是用来保护共享资源(信号量也属于临界资
进程、线程的中断切换的过程是怎样的?
CPU
上对进程或者线程进行切换。
进程上下文切换
线程上下文切换
46 简述自旋锁和互斥锁的使用场景
互斥锁
用于临界区持锁时间比较长的操作,比如下面这些情况都可以考虑
IO
操作
自旋锁就
主要用在临界区持锁时间非常短且
CPU
资源不紧张的情况下。
请你说说线程有哪些状态,相互之间怎么转换?
新建状态
(New)
就绪状态
(Runnable)
运行状态
(Running)
阻塞状态
(Blocked)
死亡状态
(Dead)
48 多线程和单线程有什么区别,多线程编程要注意什么,多线程加锁
多线程编程需要考虑
同步
的问题。线程间的同步方式包括
互斥锁、信号量、条件变量、读写锁
。
多线程加锁,主要需要注意
死锁
的问题。破坏死锁的必要条件从而避免死锁。
49 说说sleep和wait的区别?
sleep
是一个
延时函数
,
让进程或线程进入休眠。休眠完毕后继续运行
。
linux
下面,
sleep
函数的参数是秒,而
windows
下面
sleep
的函数参数是毫秒。
下面
sleep
的函数参数是毫秒。
#include
//
首先应该先导入头文件Sleep
(
500
) ;
//
注意第一个字母是大写。//
就是到这里停半秒,然后继续向下执行。
wait
是父进程回收子进程
PCB
资源的一个系统调用。进程一旦调用了
wait
函数,就立即阻塞自己
,然后由
wait
函数自动分析当前进程的某个子进程是否已经退出,当找到一个已经变成僵尸的
wait
就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进
wait
就会一直阻塞,直到有一个出现为止。函数原型如下:
status
返回,而子进程的进程识别码也会一起返回。如果不需要结束
status
可以设成
NULL
。
区别
:
sleep
是一个延时函数,让进程或线程进入休眠。休眠完毕后继续运行。
wait
是父进程回收子进程
PCB
(
Process Control Block
)资源的一个系统调用。
说说线程池的设计思路,线程池中线程的数量由什么确定?
设计思路
:
n
个线程,并让其运行起来,加锁去队列里取任务运行
线程池中线程数量
:
CPU
,
IO
、并行、并发
如果是
CPU
密集型应用,则线程池大小设置为:
CPU
数目
+
1如果是
IO
密集型应用,则线程池大小设置为:
2
*
CPU
数目
+
1最佳线程数目
=
(线程等待时间与线程
CPU
时间之比
+
1
)
*
CPU
数目
CPU
时间所占比例越高,需要越少线程。
为什么要创建线程池
:
同时线
线程池的核心线程与普通线程:
100
个任务,此时为空,线程池里有
10
个核心线程,若突然来了
10
个任务,那么
10
个核心线程直接处理;若又来了
90
个任务,此时核心线程来不及处理,那么有
80
个任务先
120
个任务,此时任务队列已满,不得已,就得创建
个普通线程来处理多余的任务。
51 进程和线程相比,为什么慢?
进程系统开销显著大于线程开销;线程需要的系统资源更少。
进程切换开销比线程大。多进程切换时需要刷新
TLB
并获取新的地址空间,然后切换硬件上下文和 内核栈;多线程切换时只需要切换硬件上下文和内核栈。
进程通信比线程通信开销大。进程通信需要借助管道、队列、共享内存,需要额外申请空间,通信 繁琐;而线程共享进程的内存,如代码段、数据段、扩展段,通信快捷简单,同步开销更小。
52 简述Linux零拷贝的原理?
什么是零拷贝
:
CPU
不执行将数据从一个内存区域,拷贝到另外一
CPU
周期和内存带宽。
零拷贝的好处
:
CPU
周期,空出的
CPU
可以完成更多其他的任务
零拷贝原理
:
IO
中,用户态空间与内核态空间之间的复制是完全不必要的,因为用户态空间仅仅起到了
1
)
Linux
提供了
sendfile()
用来减少我们的数据拷贝和上下文切换次数。
发起
sendfile()
系统调用,操作系统由用户态空间切换到内核态空间(第一次上下文切换)
通过
DMA
引擎将数据从磁盘拷贝到内核态空间的输入的
socket
缓冲区中(第一次拷贝)
将数据从内核空间拷贝到与之关联的
socket
缓冲区(第二次拷贝)
将
socket
缓冲区的数据拷贝到协议引擎中(第三次拷贝)
系统调用结束,操作系统由用户态空间切换到内核态空间(第二次上下文切换)
2
次的上下文切换,
3
次的
I/O
拷贝。我们看到从用户空间到内核空间并没
从操作系统角度来看,这个就是零拷贝
。内核空间出现了复制的原因
:
通常的硬
DMA
访问时期望的是连续的内存空间。
mmap
数据零拷贝原理
Linux
提供了
mmap
零拷贝来实现。
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net