IPC方法-信号

信号通过软件方法实现(软中断), 会导致延时性. 每个进程接收到的信号都是由内核发送处理的, 内核作为中转.

未决: 产生和递达之间的状态, 主要由于阻塞(屏蔽)导致.

未决信号集和阻塞信号集(信号屏蔽字)都存放在PCB中, 数据结构是集合, 不重复但无序, 内部存放0/1值. 正常传递时, 未决信号集中从0变到1(未决)再从1变到0(被处理)的过程可以看作是瞬时的. 但当阻塞信号集某编号因为某种原因为1时, 当内核发送信号后, 未决信号集中对应的该编号也会变为1(不发送就仍为0), 所以说阻塞信号集影响未决信号集.

信号的处理方式:
  1. 执行默认动作
  2. 忽略(丢弃), 也是一种处理
  3. 捕捉, 调用户处理函数.
信号四要素:
  1. 名字 2. 编号 3. 默认动作 4. 对应触发事件
默认动作有5种:
  1. Term,终止进程
  2. Core,终止进程并产生core文件
  3. Ign,忽略
  4. Stop,暂停
  5. Cont,继续

man 7 signal查看信号, 当一个名字对应多个编号时, 取中间那列的, 左右两列为其他平台的编号.
9)SIGKILL和19)SIGSTOP比较特殊, 不允许忽略和捕捉, 甚至不允许阻塞, 只能执行默认动作

信号的产生

1. 终端按键产生信号

ctrl + c 2)SIGINT 中断信号interrupt, 终止进程(Term)
ctrl + z 20)SIGTSTP 暂停与终端交互进程, 放到后台(Stop). 注意与19)SIGSTOP区分, SIGSTOP可以暂停任何进程(Stop).
ctrl + \ 3)SIGQUIT 退出进程(Core)

2. 硬件异常产生信号

除0操作 8)SIGFPE(浮点数例外)
非法访问内存 11)SIGSEGV(段错误)
总线错误 7)SIGBUS

3. kill命令/函数产生信号

int kill(pid_t pid, int sig)可以调用kill函数
对于管道连接的进程, 当写端进程终止后, 写端都关闭, 读端也会跟着关闭, 所以只kill写端进程就会把管道两端的进程都杀死.

4. 自身调用raise()和abort()函数产生信号

5. 软件条件产生信号:alarm()和setitimer()

alarm()传新的时间进去后会覆盖旧的, 所以返回的上一次剩下的秒数没什么用, 如果alarm(0)就是关闭定时器, 直接终止进程. 注意终止进程只是默认行为, 可以设置一个handler函数如signal(SIGALRM, handler)来注册信号的捕捉函数(真正捕捉信号的是内核), 这样就可以在时间到了之后不终止进程. 信号捕捉函数是一个典型的回调函数(封装了一个函数, 但是没有直接调用).

可以使用time命令查看程序运行的时间, 总时间=用户空间时间+内核空间时间+等待时间, 等待时间I/O占了大部分.

new_value是传入参数, 表示要定时的时间; old_value是传出参数, 表示上次剩的时间, 相当于alarm()函数的返回值. setitimer成功返回0, 失败返回-1.
需要先把结构体成员赋初值, 再把结构体指针传到函数里, 分别指定val和interval的秒值/毫秒值:

signal(SIGALRM, signalHandler);

struct itimerval new_value, old_value;
new_value.it_value.tv_sec = 5;
new_value.it_value.tv_usec = 0;
new_value.it_interval.tv_sec = 3;
new_value.it_interval.tv_usec = 0;
setitimer(ITIMER_REAL, &new_value, &old_value);

只有第一次设定闹钟后5秒会接收到SIGALRM信号, 之后每3秒接收到一个SIGALRM信号.

信号集操作函数

sigset_t myset;自己的信号集, 即"位图"

影响阻塞信号集3号位的方法:
1.先sigemptyset(&myset)sigaddset(&myset, 3)把自己的信号集3号位设置为1(注意信号集编号从1开始, 长度为64位unsigned long int, 对应kill -l列出来的信号宏, 一般操作前31个).
2.如果是设置阻塞, 调用sigprocmask(SIG_BLOCK, &myset, &oldset)把阻塞集mask的3号位置为1, 其中sigset_t oldset是传出参数, 用来保存原来的阻塞集状态, 可以传NULL. 之后当内核发送3号信号(SIGQUIT)时, 未决信号集的第3位也根据阻塞集变为1.
3.如果是解除阻塞, 调用sigprocmask(SIG_UNBLOCK, &myset, &oldset)把阻塞集mask的3号位置为0, 则未决集的3号位也会从1变0. 也就是说无论是设置阻塞还是解除阻塞, 位图myset的对应号位都要置为1.

类似sigaddset()的的还有sigfillset(&myset)全置1, sigdelset(&myset, 3)把3号位改为0(若已为0则无动作), sigismember(&myset, 5)判断5号位是否为1.

阻塞信号集不可读, 但可以使用int sigpending(sigset_t *set)来读取未决信号集, set是传出参数. 打印信号集要使用sigismember函数.

sigaction()捕捉信号

信号捕捉函数由内核调用, 即所谓的回调模式, 只有在确认信号已抵达后才会被调用, 在用户空间执行, 执行完通过sigreturn返回内核进行报道, 再从上次被中断的地方继续执行.

注册信号捕捉函数除了signal(signum, handler)之外, 还可以使用int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)函数, 用结构体的sigset_t sa_mask成员设置信号处理期间要被屏蔽的信号集合(使用sigemptyset()和sigaddset()设置), 防止调用sa_handler(函数指针型成员变量)期间被打断, sa_mask会覆盖内核的阻塞集mask. 另外sa_flags也要设置为0, 表示sa_handler处理某信号期间忽略相同信号.
捕获完信号后最好恢复成信号的默认动作sigaction(signum, &oldact, NULL), 且sigaction()调用前后都要对要接收的信号进行屏蔽(设置mask), 以防止注册信号捕捉函数的过程中信号到来.

时序竞态(竞态条件)

pause()函数可以让进程主动被挂起, 当捕获并处理完信号后返回-1, 设置errno为EINTR. 需和信号捕捉函数配合使用
如果alarm()后失去CPU, 且过了定时的时间, 当重获CPU时会先处理已发送并阻塞的SIGALRM信号, 这样pause()后就无法接收到本应等待的SIGALRM, 从而一直挂起, 导致时序竞态问题. 使用sigsuspend可解决该问题.

sigsuspend()函数实质上就是在内核阻塞信号集mask上把SIGALRM屏蔽, 当执行sigsuspend()时再使用临时阻塞集解除屏蔽. 这样即使alarm后失去CPU, SIGALRM信号被阻塞, 等恢复执行后也是先调用sigsuspend()并用捕捉函数处理信号, 而不是先处理SIGALRM再pause()导致一直挂起.

全局变量异步I/O

对于进程间通信, 全局变量存在异步I/O的情况, 应尽量少用全局变量(使用锁的机制可以避免这种情况). 即可能主函数信号发出后失去CPU, 当恢复CPU后, 另一端已发送的信号会优先被处理(信号捕捉函数里可以改变全局变量), 使得对全局变量的修改顺序出现错误, 导致进程挂起.

使用全局变量的函数容易变成不可重入函数, 一旦被信号打断容易发生错误, 执行结果会和预期的不同(如递归操作). 所以信号捕捉函数要设计成可重入函数, 避免使用全局变量和static变量, 避免使用malloc/free.

捕捉SIGCHLD回收子进程

当多个子进程同时死亡时, 虽然信号捕捉函数一次只能处理一个信号, 但如果waitpid()第一个参数设为回收全部子进程, 在这一次调用的过程中waitpid()就会把当前剩下死亡的子进程都回收掉, 此处信号的作用只是激活waitpid()函数, 回收几个子进程由waitpid自己决定.

中断慢速系统调用

read在读文件时不会阻塞, 在读网络/设备/管道时可能发生阻塞, 此时收到信号就会被中断.

中断的慢速系统调用要么重启要么执行默认动作, 如果重启需要again和goto参数, 同时判断EINTR信号

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 信号(signal)是一种软件中断,它提供了一种处理异步事件的方法,也是进程间惟一的异步通信方式。在Linux系统...
    夏大王2019阅读 1,074评论 0 1
  • 一、信号及信号来源 信号本质 信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一...
    丶Em1tu0F阅读 1,511评论 0 1
  • 对于 Linux来说,实际信号是软中断,许多重要的程序都需要处理信号。信号,为 Linux 提供了一种处理异步事件...
    故事狗阅读 86,302评论 2 63
  • 使用场景:1、为了并发,中断处理其它事件,1、进程间通信1、中断中止(注意不是终止)当前正在执行的程序,转而执行其...
    奥斯特洛司机阅读 771评论 0 0
  • 转自:http://www.dbafree.net/?p=870 我们可以使用kill -l查看所有的信号量解释,...
    AndreaArlex阅读 6,319评论 0 4

友情链接更多精彩内容