- A+
进程信号介绍:
操作系统通过信号来通知进程系统中发生了某种预先规定好的事件(一组事件中的一个),它也是用户进程之间通信和同步的一种原始机制。一个键盘中断或者一个错误条件(比如进程试图访问它的虚拟内存中不存在的位置等)都有可能产生一个信号。Shell也使用信号向它的子进程发送作业控制信号
简易来说,信号即是信号与操作系统的一种的沟通方式
信号的概念
信号是进程之间事件异步通知的一种方式,属于软中断。
信号
kill -l命令可以查看信号
可以看到信号都有属于自己的编号和宏定义
- 1~31的信号为普通信号
- 34~64的信号为实时信号
注意:没有 32、33信号
信号产生
- 1.通过终端按键产生信号
- 2.调用系统函数向进程发信号
- 3.由软件条件产生信号
- 4.硬件异常产生信号
信号的处理不是立即执行的
假设你现在在做一件事情,这时候你的朋友叫你过去帮他做一件事情,这时候你去不了,因为你还在做自己手头上的事情,优先级更高,所以会选择先做好这件事情再去帮忙。
异步:即信号的发送不是同时的,有时候需要发送,而不是统一同时发送的。
因为信号产生是异步的,当信号产生的时候,对应的进程,可能正在做更重要的事情,进程暂时不处理这个信号。
信号处理有三种方式:
- 1. 忽略此信号。
- 2. 执行该信号的默认处理动作。
- 3. 提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉 (Catch)一个信号。 自定义处理,由用户提供
忽略信号是处理信号的方式,处理的方式是忽略
而阻塞信号,是不处理信号
忽略和阻塞要分清
进程信号的术语
- 递达:进程执行信号的处理动作
- 信号未决:信号从产生到递达之间的状态
- 阻塞:进程可以对信号阻塞,被阻塞的信号处于未决状态,直到进程解除信号的阻塞状态,才可以递达
signal函数
捕捉指定函数,并执行自定义函数
void handler(int signo) { cout<<"我是一个进程,刚刚获取了一个信号:"<<signo<<"cnt: "<<endl; } int main() { signal(2,handler); //捕捉2号信号,并执行handler函数 return 0; }
注意:9号信号不允许被自定义捕捉
kill函数
给指定进程发送指定信号
kill(2,指定进程pid);
失败时返回-1
raise函数(信号)
给自己(该进程)发送指定信号
raise(2)
abort函数()
abort
给该进程发送6号信号,并终止进程
6号信号可以被捕捉,但捕捉后,进程依旧结束
sigset_t
是用来表示信号集类型的
sigset_t bsig,obsig
创建两个信号集
阻塞信号集也叫做当 前进程的信号屏蔽字(Signal Mask)
sigemptyset函数(&信号集)
初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含 任何有 效信号。
sigfillset函数 (&信号集)
初始化set所指向的信号集,使其中所有信号的对应bit置位,表示 该信号集的有效信号包括系 统支持的所有信号。
sigaddset函数(&信号集,信号)
将指定信号添加到指定信号集中
sigpending函数(&信号集)
获取正在送往进程中被阻塞的信号合集 并放入指定信号集中
sigismember(&信号集,信号)
sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含 某种 信号,若包含则返回1,不包含则返回0,出错返回-1。
注意,在使用sigset_ t类型的变量之前,一定要调 用sigemptyset或sigfillset做初始化,使信号集处于确定的 状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信
sigprocmask函数(操作,set,oset)
把set的信号集修改obset
检查或修改指定信号相关联的处理动作 可同时两种操作
有三种操作
- SIG_BLOCK set包含了我们希望添加到当前信号屏蔽字的信号
- SIG_UNBLOCK set包含了我们希望从当前信号屏蔽字中解除阻塞的信号
- SIG_SETMASK 设置当前信号屏蔽字为set所指向的值
进程崩溃的本质是该进程收到了异常信号
崩溃了不一定导致进程结束,可以捕获
小结:
- 所有的信号产生,最终都要由OS来执行
- 信号处理不一定是立即处理,而是在合适的时候
- 信号不是被立即处理,是会被暂时记录下来,记录在进程PCB内
- 一个进程在没有收到信号时,它知道遇到什么信号,该做什么反应
- OS向进程发信号,实际上向进程控制块中,写入信号数据。
信号在内核表示的示意图
先看nending是否接收到信号,再看block是否要拦截,最后看handler的处理方式
刚才我们提到,进程信号,不是立即处理的,而是会被暂时记录下来,存储在进程PCB中,处理是在合适的时候
那这个合适的时候是什么?:当前进程从内核态切换回用户态时,进行信号的检测与处理
用户态切换到内核态 原因
- 1系统调用
- 2进程切换
内核级页表 所有进程共享,只有一份
用户级页表 每一个进程都要一份 并且相互不同
无论进程怎么切换,都可以找到内核的代码和数据,前提要有权限,当前进程如何具备权限,访问这个内核页表,乃至访问内核数据。
要进行身份切换
进程是用户态,只能访问用户级页表
进程是内核态,能访问用户级页表和内核级页表
如何区分进程是用户太和进程态
CPU内部都有对应的状态寄存器CR3,有比特位表示当前进程的状态。
0:内核态 3:用户态
内核态具有更高权限,可以访问所以代码
用户态只能访问自己的代码
我们的程序,会无数次直接或间接的访问系统级软硬件资源(管理者是OS)本质上,你并没有自己去操作这些软硬件资源,而是必须通过OS->无数次陷入内核(1.切换身份 2.切换页表)-》调用内核的代码-》完成访问的动作-》结果放回给用户(1.切换身份 2.切换页表)->得到结果
这里我们会发现,当有自定义捕捉时,为什么操作系统还要把内核态切换回用户态,执行完后,再返回内核态呢?内核态还有更高权限,可以访问所以代码,这样做岂不是多此一举?首先我们要知道,自定义捕捉,是用户提供的,你能保证用户提供的代码是安全吗?如果是恶意代码呢?并且内核态权限高,用内核态的身份去执行这段不知道安全性的代码,风险会不会无限大呢?所以这样做其实是为了保护操作系统。
sigaction(信号,新动作,旧动作)捕获信号,执行动作
struct sigaction act,oact 创建动作
act.sa_handler=handler:自定义动作
SIG_IGN:忽略
SIG_DFL:默认
act.sa_flays=0;默认为0;
当一个被捕捉的信号,在执行处理动作时,会自动屏蔽该信号,直到执行动作结束,才会把该信号从信号屏蔽字解开,若在执行动作时,还要把屏蔽其他时,可以 sigaddset(&act.sa_mask,信号) 会在执行这个信号递达自动屏蔽指定信号
子进程退出时,暂停,继续会自动发送SIGCHLD 17号信号