信号与信号是两个不相干的技术点. 信号是一种事件通知机制, 通知某个进程打断当前操作去处理某个这个事件. 信号量是进程通信处理同步与互斥的机制,本质是一个计数器, 记录了临界资源的数目.
kill -l ,指令查看所有62种信号
前31种(1~31号)是非可靠/非实时信号, 后31种(34~64号)是可靠信号/实时信号 两者的区别就是在信号注册时, 非可靠信号: 非可靠信号如果已注册则什么都不做(信号丢弃,不会注册第二次), 没注册过则注册. 可靠信号:不论是否注册过都使该信号指定的位图(未绝信号集合)置1, 且会使记录信号的一个sigqueue链表添加一个节点, 当多个相同的信号都处理或删除了位图该位置才置0.
1.指令: kill -signum 进程pid , 给指定进程发送指定的信号,
之前常用的kill pid指令就是给进程发一个15号信号(默认)使进程退出.
2.在某进程内用kill接口: int kill(pid_t pid , int sig) 也是给指定进程发送指定信号.
3.使用 int raise(int sig) 接口, 给自己发送指定信号.
4.void abort(void) 给自己发送SIGABRT信号(程序异常信号),调用这个接口进程直接退出.
5.unsigned int alarm(unsigned int s) , s秒后给自己发送SIGALRM信号,调用这个接口s秒后退出.
信号处理方式: 默认处理方式,系统对每个信号定义的处理方式. 忽略处理方式, 收到信号则忽略该信号啥都不干. 自定义处理方式, 自己定义一个信号处理函数然后使用signal()接口替换指定的信号.
sighandler_t signal(int signum, sighandler_t handler) : 第一个参数,要修改的信号,可以传名字或者号码, 第二个参数是自己定义的处理函数的函数名.
系统从用户态切换到内核态的情况: 系统调用接口, 异常, 中断.
10秒内按下ctrl+c收到ctrl+c信号则不再直接退出进程, 替换为自己的sigcb处理函数. 没有ctrl+c信号则静静等待10秒后退出.
阻止未决信号的处理, 有的信号注册了但当前也不处理, 等到解除阻塞后再处理.
int sigprocmask(int how, sigset_t* new, sigset_t* old) 接口,功能是对传入的new信号集合进行阻塞或解除阻塞操作.
how:SIG_BOLCK, 向block阻塞集合中添加new信号. SIG_UNBLOCK, 从block集合中删除new信号. SIG_SETMASK, 将new设置为block.
new: 自己定义的要添加或者删除的信号集合.
old: 用于接收修改前block中的信号(多数用于还原, 不需要时传NULL)
还有些其他的接口在下边的代码种给出用法
实现:
在后台输入如下指令给指定进程发送指定信号:
敲回车之后解除阻塞后可以看到40号和2号信号才开始处理(进行我们替换之后的处理函数), 可以发现可靠信号40是收到几次就处理了几次, 非可靠信号是只处理了一次, 验证了上面说的可靠信号和非可靠信号的区别.
值得注意的是: 在进程中, SIGKILL 和 SIGSTOP 信号不可阻塞且不可被修改处理方式.
一个进程无法被KILL指令处理的原因:
1.僵尸进程(不可阻塞不可修改的sigkill和sigstop对僵尸进程也是没用的).
2.信号被修改了处理方式.
3.信号被阻塞.
4.进程是停止状态(可以被kill -9即kill sigkill杀死).
应用:
1. 在前边说的命名管道中, 我们说所有读端关闭, 写端触发异常, 进程退出, 这时我们来个下图的操作修改了这个异常.则进程不退出了, 运行到下面的write写入之后接到的返回值-1, write失败进入到if(ret==-1)之后才退出.
2. 僵尸进程产生是因为父进程在子进程退出后没有处理子进程退出后给父进程发送的SIGCHILD信号, 因为SIGCHLD的默认处理方式就是忽略.
此时我们使用 signal(SIGCHLD, SIG_IGN) 把sigchld改成我们用户自己来设置的显示忽略, 之后子进程再退出就不会成为僵尸进程了. 或者使用我们之前说的while(waitpid(-1,NULL,WNOHANG)>0) 进程等待处理子进程, 这时我们可以写成:
void sigcb(){
while(waitpid(-1,NULL,WNOHANG)>0);
}
signal(SIGCHLD,sigcb);
作用: 修饰变量时用, 保持内存可见性. 防止编译器过度优化, 被修饰的变量不被优化.
没加volatile关键字且没使用优化时: 收到ctrl+c信号就跳出死循环打印?了.
没加volatile但使用了编译器优化(-O2): cpu处理数据是从内存中加载数据到cpu寄存器, 优化后不需要频繁地从内存加载数据, 虽然内存中a=0了, 但优化后用的之前cpu寄存器中a=1的值.无法跳出循环.
使用编译器优化且加了volatile关键字: 单独不优化a变量.可跳出循环.
下面的程序, main主控流程运行了a++(a=1)之后在3秒钟之前收到ctrl+c信号, 然后开始执行信号的执行流程,又进行了一次a++(a=2), 且3秒后进行了b++(b=1), 然后返回了3给sigcb. 然后主控流程里从a++后继续向下运行b++(b=2),所以返回4给main.
对于上面这种一个函数在不同执行流程中同时运行就叫函数重入了.
这种函数重入之后造成不想要的意料之外结果我们应设置为不可重入函数(通过同步与互斥).
一个函数内部如果对全局数据是受保护的原子操作(一次性就完成,一步作完的操作), 不会造成意料之外的结果.就是可重入函数.