-
1.信号量概念
- 临界资源:供多个进程访问的资源(例如:管道,共享内存,消息队列)
- 临界区:访问临界资源时的代码
- 信号量:本质是资源计数器(资源计数器为1表示可以访问,为0表示不能访问)
- 互斥访问:同一个时刻,多个进程当中只有一个进程可以访问临界区资源
- 多个进程通过信号量保证互斥的时候需要先获取信号量,如果能够获取信号量,才能访问支援,如果获取不了,则阻塞等待.
- 如果不互斥访问导致的结果:复习:(程序计数器(保存下一步执行的指令),上下文信息(保存寄存器中的内容))会产生二义性:如果两个进程同时往一个临界资源里写时会发生:当一个进程A写a后,还没等读呢,时间片到了.另一个进程B写进去b,等到A读的时候b已经把a覆盖了,A进程读的和期望的就不相符了.
- 同步访问:临界资源允许一定数量的进程同时访问,如果有进程退出了.可以再加一个进程进行访问.
-
信号概念
- 信号是一个软中断,只是告诉进程有个信号,具体怎么执行,什么时候执行进程说了算.
- 信号的种类:kill -l 罗列信号(1~31是非实时信号,特点,信号可能会丢失)(34~64实时信号特点:信号不会丢失)
-
信号产生
- 硬件产生:例如:ctrl+c:是收到并执行了2号信号,ctrl+z 20号信号(这是停止进程,不是终止kill -9 pid强杀信号),进程还在后台运行),ctrl+| 3号信号...kill -2 pid(给进程发送信号)
- 软件产生:kill函数:给pid的进程发送sig信号
- raise函数:谁调用给谁发送:这个是调用kill封装的.
- 程序崩溃收到信号:
- 查看核心转储文件允许生成的大小
- 设置核心转储文件的大小
- 场景1:解引用空指针:结果如下,收到11号信号
- 场景2:内存访问越界:收到的也是11号信号
- 场景3:除0(收到的是8号信号)
- 多次free:(收到的是6号信号)
- 查看核心转储文件允许生成的大小
-
信号处理方式
- 1.默认处理方式也是操作系统对信号的处理方式:SIG_DFL(man 7 signal)
- 2.忽略处理方式:SIG_DFL(进程收到忽略处理方式的信息后是不进行处理的
- 举例:僵尸进程,子进程先于父进程退出,子进程会给父进程发送SIGCHLD(17号信号),父进程接受到这个信号后是忽略处理的,导致父进程没有回收子进程的状态信息,从而子进程变成僵尸进程.
- 3.自定义处理方式(下面有)
- 1.默认处理方式也是操作系统对信号的处理方式:SIG_DFL(man 7 signal)
-
信号的注册
- 一个进程收到一个信号,这个过程称为注册,操作系统给注册的.操作系统给内核的pcb里注册信号,进程处理信号称为信号注销.
- PCB里的的关系 sigset_t这个结构体里是位图
- 操作系统给进程注册的过程(位图进行标记的)
- 信号注册时会把对应的比特位置为1,表示当前进程收到这个信号,还会把sigqueue队列里添加一个节点,这个队列在操作系统内核本质上就是一个双向链表
- 实时信号和非实时信号注册的区别
- 非实时信号注册:第一次注册,修改位图,修改sigqueue.第二次注册相同的信号时位图时1~1,sigqueue节点并不会再添加
- 实时信号注册:第一次注册,修改位图,修改sigqueue.第二次注册相同的信号时位图是1~1变化,sigqueue节点还会添加
-
信号的注销
- 非可靠信号:位图1-0 ;sigqueue进行出队操作(非实时信号的节点在双向链表里只有一份,拿到节点后,找上面的信号处理方式)
- 可靠信号:在链表里找对应的信号,如果有就出队,出完队之后再检测是不是还有这个信号的节点,如果有,位图不改变.如果没有了,位图1-0;
- 信号的自定义处理方式(程序员自定义某个信号执行什么操作)函数也有信号捕捉的作用
- 函数1:函数第一个参数是信号值,例如:本来进程收到2号信号后会调用一个终止自己的函数,但是用了signal后,进程收到2号信号时,会调用程序员自定义的这个函数
- 并不是所有的信号都能被自定义处理:9号强杀信号不能被修改了.
- 函数2:
- struct sigaction结构体里存放的是信号值对应的具体操作.
- 函数的第一个参数是信号值,第二个参数是自定义的结构体,第三个参数(出参)原本信号值对应的结构体,如果,函数调用成功后,改掉了信号值对应的结构体,但是第三个参数会保存原本的信息,不至于丢失
- 结构体里只关系第一个成员和第三个成员,第一个成员是,当收到信号值后会执行哪个函数,我们要做的是定义一个结构体,自定义里面的第一成员,并把第三个成员全置为0,其他三个成员爱是啥是啥,第三个成员保存当进程在处理信号的时候,如果收到了其他信号,就放在这个位图里,后续再放在进程信号位图里,上面的PCB里的位图
- 函数1:函数第一个参数是信号值,例如:本来进程收到2号信号后会调用一个终止自己的函数,但是用了signal后,进程收到2号信号时,会调用程序员自定义的这个函数
-
信号的捕捉流程(如果信号的处理动作是用户自定义的函数,在信号递达时就调用这个函数称为信号的捕捉)
- 信号的处理时机:(信号是任何时候都能进入进程的)只有进程从内核态进入用户态的时候会调用do_signal函数(用来判断在出内核态之前有没有收到信号),如果函数发现收到了信号就处理,如果没有收到信号直接返回用户态
- 如果发现收到了信号,是怎么处理的
- 1.判断当前信号是否被阻塞,如果阻塞了就不处理了
- 2.执行信号的注销
- 3.调用信号的处理方式,如果是默认或者是忽略处理,直接在内核就处理了...如果是自定义处理的方式,要回到用户态执行自定义的函数,执行完后调用sigreturn函数回到内核态,再调用do_signal函数,看在上一次调用这个函数到现在有没有收到信号,如果有就重复这个过程,没有的话调用sys_signal函数回到用户态
- 核心:当进程从内核态进入用户态的时候会调用do_signal函数,检测是否收到信号
- 如果发现收到了信号,是怎么处理的
- 进入内核的方式(中断,异常,系统调用)
- 调用系统调用函数和库函数
- 内存访问越界和访问空指针
- 信号的处理时机:(信号是任何时候都能进入进程的)只有进程从内核态进入用户态的时候会调用do_signal函数(用来判断在出内核态之前有没有收到信号),如果函数发现收到了信号就处理,如果没有收到信号直接返回用户态
-
信号的阻塞
- 和信号注册一样,在PCB里有信号阻塞的位图,当需要阻塞一个信号的时候,将信号对应的比特位置为1,0是不阻塞........信号的阻塞和信号的注册没关系
- 信号阻塞不是不处理信号,而是等该信号不阻塞之后再进行处理
- 信号注册和信号阻塞的位图用的是一个结构体,只是对象不一样
- 加上阻塞之后的信号捕捉流程,上面有
- 阻塞接口:
- int sigprocmask(int how,const sigset_t *set,sigset_t *oldset);
- how:想让sigprocmask做什么事情
- SIG_BIOCK:设置某个信号为阻塞状态
- SIG_UNBLOCK :设置某个信号为非阻塞状态
- SIG_SETMASK :用第二个参数“set”,替换原来的阻塞位图。(替换的意思)
- set :新设置的阻塞位图
- oldset :原来老的阻塞位图
- 原理解析:当how为SIG_BLOCK时,函数会根据set,计算新的阻塞位图,方式为:block(new) = block(o1d) |set;当how为为SIG_UNBLOCK时,函数会根据set,计算新的阻塞位图,方式为:block(new) = block(o1d)& (~set) ;当how为为SIG_SETMASK时,函数会根据set,计算新的阻塞位图,方式为:block(new) = set;
- 9号信号和19号信号不能被阻塞
- 验证可靠信号和非可靠信号的阻塞:
- 1,自定义2号和40号的处理方式,以便我们观察
- 2.将2 40设置为阻塞状态
- 3.给进程疯狂发2 和40号信号
- 4.看看都执行了几次
- 解决wait等待子进程时,啥也不干.本来wait一直在等子进程的退出信号...父进程一直运行自己的程序,等子进程退出后会给父进程一个退出信号,当接收到这个退出信号后就会调用自定义的函数,
- 扩展内容:
- 可重入函数
- 可以被一个以上的任务调用的函数(),且不用担心数据被破坏,可重入函数任何时候都可以被中断,一段时间以后又可以运行,而相应的数据不会丢失。可重入函数或者只使用局部变量,即保存在CPU寄存器中或堆栈中;或者使用全局变量,则要对全局变量予以保护。
- 通俗理解,第一次调用还没返回呢,就调用第二次...如果包含全局变量()
- 如何写出可重入函数,不使用全局变量,不适用静态局部变量,如果必须使用全局变量,用互斥信号量进行保护
- volatile关键字的作用
- cpu每次拿值可能从内存里拿,也可能从寄存器里拿,这取决于gcc/g++编译时的优化等级,如果优化等级底,直接从内存拿了(就是想要的值).如果优化等级高从寄存器拿,可能和内存里的值不一样所以加volatile关键字,告诉cpu每次拿的时候都从内存拿
进程信号的深度剖析
最新推荐文章于 2022-11-03 17:31:18 发布