linux进程信号II
目录
信号保存
概念补充
sigset_t类型
信号集操作函数
sigprocmask(修改block)
sigpending(获取pending)
小结
信号处理
sigaction
内核态和用户态的切换
CPL
硬件中断
概念
意义
时钟中断
软中断(又叫陷阱)
软中断辅助实现系统调用
从内核角度看
小结
缺页中断
可重入函数(问题)
不可重入函数
volatile(易变的,不稳定的)
SIGCHLD(17号信号)
问题
小知识
信号保存
概念补充
1.信号递达:实际执行信号的处理动作
2.信号未决:信号在产生到递达之间的状态
3.进程可以阻塞某个信号(屏蔽某个信号)
4.进程的PCB中有两位图用来接受信号
pending 比特位位置:信号编号 比特位内容:是否收到
block 比特位位置:信号编号 比特位内容:是否阻塞(屏蔽)
用一个位图来处理信号
handler位图表 位置代表信号编号,内容是一个指向信号处理方法的指针
5.被阻塞的信号产生时将保持在未决状态,直到进程J解除对此信号的阻塞,才执行递达的动作
6.阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达的一种可选动作.
sigset_t类型
称为信号集 是一个位图结构类型 ,pending和block都是此类型
其中block(阻塞信号集)又叫做信号屏蔽字
信号集操作函数
1.sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit位清零,表示该信号集不包含任何有效信号。
2.sigfillset初始化set所指向的信号集,使其中所有信号的对应bit位置1,表示该信号集的有效信号,包括系统支持的所有信号。
3.sigaddset() 设置一个sigset_t类型变量的对应位置为1
4.sigisnumber()判断一个信号是否在集合里 条件为真返回值大于0,为假返回0
5.四个函数都是成功返回0,出错返回-1。
sigprocmask(修改block)
how表示如何更改
set:
oldset:oldset是输出型参数,是修改前的block
sigpending(获取pending)
set:输出型参数,获取当前的pending
成功返回0,出错返回-1
小结
1.是否收到和是否阻塞无关系,如果一个信号被block,即便收到,也不能被递达
2.9号信号不可被捕捉,不可被屏蔽(管理员信号)
3.信号递达前把pending置0,然后去递达(执行处理函数)
信号处理
注:当某个信号处理函数被调用时,内核将自动将信号屏蔽,等处理完解除屏蔽,防止多个信号到来造成死递归.
sigaction
使用时要构造一个struct sigaction 传到act位置
oldact表示上一个act
sa_mask表示如果用户想除了正在处理的信号之外的额外屏蔽其他信号,可以将其加进来
内核态和用户态的切换
代码执行时,只有内核态和用户态两种执行模式
内核态:操作系统运行时的状态,主要用来执行内核代码
用户态:用CPU执行用户自己的代码的状态
大体流程:
所以OS处理自定义捕捉的信号比较费劲,处理默认的信号时,不需返回用户态,执行自定义的时,要返回用户态去(因为自定义函数在用户空间的某块栈帧中.要由用户去执行),执行完自定义信号处理函数后再执行sigreturn返回内核态,之后再到用户态上次被中断的地方继续执行
CPL
CPU中有寄存器cs,寄存器内存着执行级别CPL(Current Privilege Level)
CPL两种级别 : 0表示内核 3表示用户 ,在内核和用户页表中也有CPL,寄存器cs和页表中CPL相同时,页表才能正确映射,才能正常执行,防止用户使用野指针修改内核区
硬件中断
概念
CPU会放弃正在执行的代码,去执行这个硬件的代码.
1.每种外设都可以向CPU发送硬件中断
2.会有一个中断向量表(OS提供的)存各种中断处理方法
意义
硬件中断的意义是让OS不需要周期性检测外设,只需要等待中断到来再查表执行,提高效率.
时钟中断
当代计算机,CPU中会集成时钟源,这个硬件可以定期向OS发送中断信号,来推动OS的执行,所以OS这时可以看做一个中断向量表,这样OS就在硬件的推动下"自动"调度了,OS就是一个死循环的代码
每个一段时间,触发一次时钟中断,在这个时间段内,执行进程代码
软中断(又叫陷阱)
CPU通过识别汇编指令来执行中断就是软中断
为了让操作系统支持系统调用,CPU设计了对应的汇编指令(int 或 syscall),可以让CPU内部触发中断
int 通过触发软中断进入内核态,执行操作系统的代码
syscall 专为64位系统设计的进入内核执行系统调用的指令
软中断辅助实现系统调用
1.系统调用: 先int/syscall 陷入内核,本质就是触发软中断,CPU会根据系统调用号自动查表,执行对应方法
2.用户层通过寄存器将系统调用号给操作系统
3.操作系统通过寄存器或者用户传入的缓冲区地址将返回值给用户
4.系统调用号的本质:数组下标
从内核角度看
1.所有的系统调用方法都在全局的系统调用表里,使用时,只需要对应的数组下标
2.系统调用号,就是这个数组下标
3.对于每个进程来说,都有自己的用户级页表,而所有进程共用一个内核级页表
小结
1.真正的系统调用不是C语言风格的,我们使用的是C语言封装好的接口.
2.各种编程语言想在linux上运行,就必须通过C语言系统调用,这就是 C生万物.
缺页中断
直接申请一块空间时,不会在物理内存中直接开,只是在虚拟内存中开,当访问时,触发缺页中断,执行对应中断方法,再真正在物理内存开辟空间.
可重入函数(问题)
进程运行中,任何时间都有可能进行信号的递达,因为时间片可能到了
假如有一个insert函数,用来给全局链表进行插入操作,insert函数在执行中进程时间片到了恰好执行时钟中断,就进入了内核态执行sighandler,如果内核态又调用insert函数,insert函数就被重入了,等返回用户态继续执行,这时插入会发生错误,图中node2结点丢失,造成内存泄漏问题,所以这种函数应为不可重入函数
不可重入函数
1.使用new或malloc,因为使用了全局链表来管理
2.调用标准I/O库函数,标准I/O库函数的很多实现都以不可重入的方式使用全局数据结构
3.使用全局变量的函数
volatile(易变的,不稳定的)
C语言C90标准32关键字之一,叫做易变关键字,用来处理编译器的优化导致的问题
这个代码如果加上编译器优化,当给进程发2号信号时,进程就不会结束了,为什么呢
1.优化前:代码执行,CPU对变量进行检测,先把变量放到寄存器中,然后对变量进行运算,最后将变量写回内存,这个过程不断执行.
2.优化后:直接将变量的值写进寄存器中,作为寄存器变量,只做检测,不进行变量的读取和写入,导致全局变量改变,CPU中的寄存器变量不变,进程不会结束
编译器的优化,屏蔽了内存数据.
所以volatile用来禁止编译器对变量(还有指针等)的优化,保持内存可见性.
SIGCHLD(17号信号)
子进程退出时,会给父进程发送17号信号,只不过默认递达方式是忽略
所以,可以直接在信号处理函数中进行子进程的waitpid,父进程就不需轮询,等子进程结束发信号就自动回收了
问题
1.如果多个子进程,同时发信号时,由于一个信号在执行时,其他的会被block,最终可能只回收1个子进程
while循环解决
2..如果多个子进程,一部分退了,一部分没退,循环只能一起回收一起退的,所以还要加判断
WNOHANG :waitpid阻塞时,会直接返回0,不阻塞.
小知识
1.OS在调度进程时,一直在进行内核到用户,用户到内核的转换,因为还有时钟中断,进程时间片,调度
2.register 建议关键字,建议将声明的变量放到寄存器中,只是建议
3.linux要想不产生僵尸进程还有一种方法,父进程调用sigaction将SIGCHLD的处理动作置为SIG_IGN,这样fork出来的子进程在终止时会自动清理,不产生僵尸进程,也不通知父进程.系统默认忽略动作和用户sigaction函数定义的忽略动作通常是没有区别的,但这是一个特例.