信号

一个典型的信号处理与响应

《信号》
《信号》 程序中的信号处理过程

摘要

1.信号基本概念
2.信号产生的一般方式
3.信号递达盒阻塞的概念和原理
4.信号捕捉方式
5.可重入函数概念
6.竞态条件的情景和处理方式
7.SIGCHILD信号,编写信号处理函数的一般机制

信号的基本概念

一个熟悉场景

1用户输入命令,在shell下启动一个进程
2用户按下Ctrl-C,产生一个硬件中断
3如果CPU当前正在执行这个程序的代码,则用户空间代码暂停执行,CPU从用户态切换到内核态处理硬件中断
4终端驱动程序将Ctrl-C解释成一个SINGINT信号,记录在PCB中(进程接受信号)
5当某个时刻要从内核返回进程用户空间代码运行之前,首先处理PCB中的信号,发现有一个SIGINT信号待处理,其默认动作为终止进程,所以直接终止进程而不返回其用户空间继续执行

注:Ctrl-C信号只发送给前台进程,不能发送给后台进程

linux系统定义的信号列表
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX

注:每个信号都有编号和宏定义名称,在文件signal.h中
每个信号的默认处理动作在signal(7)中.    man 7 signal可以查看

信号产生的主要条件:
1用户在终端下按键Ctrl-C SIGINT   Ctrl-\  SIGQUIT  Ctrl-Z SIGTSP
2硬件异常产生信号  eg    /0  产生SIGFPE  非法地址访问产生SIGSEGV
3一个进程调用kill(2)函数发送信号给另一个进程

信号处理动作:
1忽略信号
2执行默认处理动作
3提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数(捕捉)

产生信号

A通过终端产生信号
B通过系统调用向进程发送信号 raise函数可以给当前进程发送指定的信号(自己给自己发送)
int kill(pid_t pid, int signo);
int raise(int signo);
abort 函数使当前进程接收到信号而异常终止
C由软件条件产生信号
unsigned int alarm(unsigned int seconds);

阻塞信号

A信号在内核中的表示

实际执行信号的处理动作称为信号递达
信号从产生到递达之间的状态称为信号未决

《信号》 信号在内核中的状态

注:每个信号都有两个标志位表示阻塞(block)和未决(pending),还有一个函数指针表示处理东西.
信号产生时,内核在进程控制块中设置未决状态,直到信号递达才清除该标志

常规信号产生多次只记录一次,而实时信号抵达之前产生多次放在队列中
阻塞信号集也叫当前进程的信号屏蔽字(Signal Mask)

B信号集操作函数

int sigemptyset(sigset_t * set);
int sigfillset(sigset_t* set);
int sigaddset(sigset_t* set,int signo);
int sigdelset(sigset_t* set,int signo);
int sigismember(const sigset_t * set,int signo);

C.sigprocmask

调用函数sigprocmask可以读取或者更改进程的信号屏蔽字(阻塞信号集).
int sigprocmask(int how,const sigset_t * set,sigset_t *oset);
如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达

D.sigpending

int sigpending(sigset_t * set);
读取当前进程的未决信号集,通过set参数传出.

捕捉信号

A内核实现信号捕捉
1用户注册信号处理函数
2当前main发生中断或者异常切换到内核态
3在中断处理完毕之后返回用户之前检查是否有信号递达
4内核决定返回用户态后是不是恢复main上下文执行,而是执行handler函数
注:main和handler函数使用不同的堆栈空间,他们之间不存在调用和被调用的关系,是两个独立的控制流程
5handler函数返回后自动执行特殊系统调用sigreturn再次返回内核态
6如果没有新的信号递达,这次再返回用户态恢复main函数上下文继续执行

B.sigaction

int sigaction(int signo, const struct sigaction* act,struct sigaction* oact);
该函数可以读取和修改与指定信号相关联的处理动作(类似C++handler,修改同时返回old handler)

C.pause

int pause(void)
使得调用进程挂起等待信号递达.

D可重入函数
当一个函数被不同的控制流程调用,有可能第一次调用还没有返回时,再次进入该函数,者称为重入.
当函数因为重入可能造成错乱时,称为不可重入函数.否则称为可重入函数

不可重入规则:
调用了malloc或free,因为malloc也是全局链表管理堆的
调用了标准IO哭喊树,标准IO库的很多实现都以不可重入的方式使用全局数据结构

E.sig_atomic_t类型与volatile限定符

对于程序中存在多个执行流程访问同一变量的情况,volatile限定符是有必要的
此外,虽然程序只有单一的执行流程,但是变量属于以下情况,也需要volatile:
1变量在内存单元的数据不需要写操作就可以自己发生改变.每次读取的值都可能不一样

竞态条件与sigsuspend函数

eg当前进程设置闹钟(闹钟的计时是为当前进程做的),但是在当前进程等待过程中,优先级更高的进程获取执行权限,导致当前进程被挂起.而在闹钟结束之时进程仍然没有获取执行权限.那么就会导致程序出错.这就叫做静态条件

….

点赞