目录
一、信号的概念
1 与信号相关的事件和状态
2 信号编号
3 Linux常规信号一览表
二、发送信号
1 kill函数和kill命令
2 alarm函数
3 setitimer/getitimer函数
三、信号集操作函数
1 信号集设定
2 sigprocmask函数
3 sigpending函数
四、信号捕获
1 signal函数
2 sigaction函数
3 信号捕获过程
五、信号引起的时序竞态
1 pause函数
2 时序竞态问题
3 异步I/O引发的时序竞态
4 可重入和不可重入
六、SIGCHLD信号
1 产生条件
2 借助SIGCHLD信号回收子进程
七、信号传参
1 发送信号传参
2 捕获信号传参
八、中断系统调用
A给B发送信号,B收到信号之前执行自己的代码,收到信号后,不管执行到程序的什么位置,都要暂停运行,去处理信号,处理完毕再继续执行。与硬件中断类似—―异步模式。但信号是软件层面上实现的中断,早期常被称为“软中断”。
信号的特点:由于信号是通过软件方法实现,其实现手段导致信号有很强的延时性。但对于用户来说,这个延迟时间非常短,不易察觉。·
每个进程收到的所有信号,都是由内核负责发送的,内核处理。
(1) 产生信号
(2)抵达:递送并且到达进程。
(3)未决:产生和抵达之间的状态。主要由于阻塞(或屏蔽)导致该状态。
(4)信号处理方式:
Linux内核的进程控制块PCB是一个结构体,task_struct,除了包含进程id,状态,工作目录,用户 id,组 id,文件描述符表,还包含了信号相关的信息,主要指阻塞信号集和未决信号集。
阻塞信号集(信号屏蔽字):将某些信号加入集合,对他们设置屏蔽,当屏蔽x信号后,再收到该信号,该信号的处理将推后(解除屏蔽后),此时信号处于未决状态。
未决信号集:
信号产生,未决信号集中描述该信号的位立刻翻转为1,表信号处于未决状态。当信号被处理对应位翻转回为o。这一时刻往往非常短暂。
信号产生后由于某些原因(主要是阻塞)不能抵达。这类信号的集合称之为未决信号集。在屏蔽解除前,信号一直处于未决状态。
不存在编号为0的信号。其中 1-31号信号称之为常规信号(也叫普通信号或标准信号),34-64称之为实时信号,驱动编程与硬件相关,名字上区别不大。而前32个名字各不相同。
信号4要素:
1.编号
2.名称
3.事件
4.默认处理动作·
在不同的操作系统下,信号的值可能不相同,但是信号的宏定义对应的内容是相同的。
Term表示终止当前进程.
Core表示终止当前进程,生成core文件Core Dump(Core Dump 用于gdb调试).
Ign表示忽略该信号.
Stop表示停止当前进程.
Cont表示继续执行先前停止的进程.
Linux常规信号一览:
这里特别强调9)SIGkill和19)SIGSTOP信号,不允许忽略和捕捉,只能执行默认动作。设置不能将其设置为阻塞。
另外要清楚,只有每一个信号所对应的事件发生了,该信号才会被递送(但不一定抵达),不应该乱发信号!
1)SIGHUP:当用户退出shell时,由该shell启动的所有进程将收到这个信号,默认动作为终止进程
2)SIGINT:当用户按下了
作为终止里程。
3)SIGQUIT:当用户按下
4)SIGILL:CPU检测到某进程执行了非法指令。默认动作为终止进程并产生core文件
5)SIGTRAP:该信号由断点指令或其他trap指令产生。默认动作为终止里程并产生core文件。
6 ) SIGABRT:调用abort函数时产生该信号。默认动作为终止进程并产生core文件。
7)SIGBUS:非法访问内存地址,包括内存对齐出错,默认动作为终止进程并产生core文件。
8)SIGFPE:在发生致命的运算错误时发出。不仅包括浮点运算错误,还包括溢出及除数为0等所有的算法错误。默认动作为终止进程并产生core文件。
9)SIGKILL:无条件终止进程。本信号不能被忽略,处理和阻塞。默认动作为终止进程。它向系统管理员提供了可以杀死任何进程的方法。
10)SIGUSE1:用户定义的信号。即程序员可以在程序中定义并使用该信号。默认动作为终止进程。
11)SIGSEGV:指示进程进行了无效内存访问。默认动作为终止进程并产生core文件。
12)SIGUSR2:这是另外一个用户自定义信号,程序员可以在程序中定义并使用该信号。默认动作为终止进程。
13)SIGPIPE:Broken pipe向一个没有读端的管道写数据。默认动作为终止进程。
14) SIGALRM:定时器超时,超时的时间由系统调用alarm设置。默认动作为终止进程。
15)SIGTERM:程序结束信号,与SIGKILL不同的是,该信号可以被阻塞和终止。通常用来要示程序正常退出。执行shell命令Kill时,缺省产生这个信号。默认动作为终止进程。
16)SIGCHLD:子进程结束时,父进程会收到这个信号。默认动作为忽略这个信号(唯一一个默认为忽略动作的信号)。
17)SIGSKFLT:linux早起版本出现的信号,现在仍然保留向后先下兼容。默认动作为终止进程。
18)SIGCONT:如果进程已经停止,则使其继续运行。默认动作为继续/忽略
19)SIGSTOP:停止进程的执行。信号不能被忽略,处理和阻塞。默认动作为暂停进程。
20)SIGTTIN:后台进程读终端控制台。默认动作为暂停进程。
21)SIGTSTP:停止进程的运行。按下
22)SIGTTOU:该信号类似于SIGTTIN,在后台进程要向终端输出数据时发生。默认动作为暂停进程。
23)SIGURG:套接字上有紧急数据时,向当前正在运行的进程发出些信号,报告有紧急数据到达。如网络带外数据到达,默认动作为忽略该信号。
24)SIGXFSZ:进程执行时间超过了分配给该进程的CPU时间,系统产生该信号并发送给该进程。默认动作为终止进程。
25)SIGXFSZ:超过文件的最大长度设置。默认动作为终止进程。
26)SIGVTALRM:虚拟时钟超时时产生该信号。类似于SIGALRM,但是该信号只计算该进程占用CPU的使用时间。默认动作为终止进程。
27)SGIPROF:类似于SIGVTALRM,它不公包括该进程占用CPU时间还包括执行系统调用时间。默认动作为终止进程。
28)SIGWINCH:窗口变化大小时发出。默认动作为忽略该信号。
29)SIGIO:此信号向进程指示发出了一个异步IO事件。默认动作为忽略。
30)SIGPWR:关机。默认动作为终止进程。
31)SIGSYS:无效的系统调用。默认动作为终止进程并产生core文件。
32)SIGRTMIN~ 64)SIGRTMAX:LINUX的实时信号,它们没有固定的含义(可以由用户自定义)。所有的实时信
号的默认动作都为终止进程。
#include
int kill(pid_t pid, int sig);
参数:
pid:
> 0,sig发送给ID为pid的进程
== 0,sig发送给与发送进程同组的所有进程
< 0,sig发送给组ID为|-pid|的进程,并且发送进程具有向其发送信号的权限
== -1,sig发送给发送进程有权限向他们发送信号的系统上的所有进程
sig:
待发送的信号,sig为0时,用于检测,特定为pid进程是否存在,如不存在,返回-1。
通过ps ajx可以看到进程id(PID)和进程的组(PGID)等
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
2 19840 0 0 ? -1 S 0 0:00 [kworker/u2:2]
2 20802 0 0 ? -1 S 0 0:00 [kworker/u2:0]
1105 21228 21228 21228 ? -1 Ss 0 0:00 sshd: [accepted]
3728 21230 3720 3720 ? -1 S 0 0:00 /bin/sh -c ntpdate -b -d ntpupdate.tencentyun.com 2>&1 | grep 'step time server' | sed -r '
21230 21231 3720 3720 ? -1 S< 0 0:00 ntpdate -b -d ntpupdate.tencentyun.com
21230 21232 3720 3720 ? -1 S 0 0:00 grep step time server
21230 21233 3720 3720 ? -1 S 0 0:00 sed -r s/.*offset(.*)sec.*/\1/
代码示例:
#include
#include //perror
#include //fork/getpid/getppid/sleep
#include //kill
int main(int argc, char *argv[])
{
pid_t pid = fork();
if (pid > 0) {
printf( "parent,pid = %d\n",getpid());
while(1);
}else if (pid == 0) {
printf( "child pid = %d,ppid = %d\n",getpid(),getppid());
sleep(2);
kill(getppid(),SIGSEGV);
}
return 0;
}
#include
unsigned int alarm(unsigned int seconds);
描述:
设置定时器(闹钟)。在指定seconds 后,内核会给当前进程发送14)SIGALRM信号。进程收到该信号,默认动作终止。每个进程都有且只有唯一一个定时器。
返回值:
返回0或剩余的秒数,无失败。常用:取消定时器 alarm(0),返回旧闹钟余下秒数。
例: alarm(5)→ 3sec → alarm(4) → 5sec → alarm(5) → alarm(0)
定时,与进程状态无关(自然定时法)就绪、运行、挂起(阻塞、暂停)、终止、僵尸...无论进程处于何种状态,alarm都计时。·
代码示例:
///
// 统计一秒能进行多少次i++
///
#include //alarm
#include
int main()
{
int i=0;
alarm(1);
while(1) {
i++;
printf("%d\n",i);
}
}
实际程序执行时间 = 系统时间+用户时间+等待时间
time ./alarm
#include
int getitimer(int which, struct itimerval *curr_value);
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value)
参数:
which 指定定时方式,有如下三种。
(1)自然定时:ITIMER_REAL -> 14)SIGLARM 计算自然时间
(2)虚拟空间计时(用户空间):ITIMER_VIRTUAL -> 26)SIGVTALARM 只计算进程占用CPU的时间
(3)运行时计时(用户+内核):ITIMER_PROF -> 27)SIGPROF 计算占用CPU及系统调用的时间
new_value 传入参数,定时时间
old_ value 传出参数,上次定时剩余时间
struct itimerval {
struct timeval it_interval; /* 用来设定两次定时任务之间的间隔时间 */
struct timeval it_value; /* 定时的时长 */
};struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
代码示例:
#include
#include //setitimer
#include //signal
#include //itimerval
void func(int signo)
{
printf("hello %d\n",signo);
}
int main()
{
struct itimerval it,oldit;
signal(SIGALRM,func);//注册SIGALARM信号的捕捉函数
it.it_value.tv_sec = 2;
it.it_value.tv_usec = 0;
it.it_interval.tv_sec = 5;
it.it_interval.tv_usec = 0;
if(setitimer(ITIMER_REAL,&it,&oldit)==-1)
{
perror("setitimer error");
return -1;
}
while(1);
return 0;
}
sigset_t set;//信号集类型
#include
int sigemptyset(sigset_t *set);//清空信号集set,所有二进制位全设置0
int sigfillset(sigset_t *set);//填充信号集set,所有二进制位设置为1
int sigaddset(sigset_t *set, int signum);//向集和set中添加一个信号
int sigdelset(sigset_t *set, int signum); //向集和set中删除一个信号
int sigismember(const sigset_t *set, int signum);//判断信号signum是否在信号集set中,存在返回1,不在返回 0,出错返回-1
#include
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
描述:
调用函数sigprocmask可以读取或更改进程的信号屏蔽字。 如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。
#include
int sigpending(sigset_t *set);
读取当前进程的未决信号集
#include
#include
#include
#include
//打印信号集
void print_set(const sigset_t set)
{
int i;
for (i=1;i<32;i++)
{
if(sigismember(&set,i))
{
putchar('1');
}
else
{
putchar('0');
}
}
printf("\n");
}
int main()
{
sigset_t set,oldset,pdset;
sigemptyset(&set);
sigaddset(&set,SIGINT);
int ret = sigprocmask(SIG_BLOCK,&set,&oldset);//设置信号屏蔽字
if(ret==-1)
{
perror("sigprocmask err");
exit(1);
}
while(1)
{
ret = sigpending(&pdset);//获取未决信号集
if(ret == -1)
{
perror("sigpending err");
exit(1);
}
print_set(pdset); //打印信号屏蔽字
}
return 0;
}
运行程序之后,可以看到当键入Ctrl+C(SIGINT)之后,由于2号信号(SIGINT)被屏蔽,将一直保留在未决信号集中而不被处理。
需要注意的是:9)号信号SIGKILL和19)号信号不能被屏蔽,即使设置了信号屏蔽字也无法屏蔽。只能执行默认动作。
#include
typedef void (*sighandler_t)(int); //定义一个函数指针,一个int类型参数,void类型返回值
sighandler_t signal(int signum, sighandler_t handler);
描述:
注册一个信号捕捉函数
代码示例:
#include
#include
#include
void sig_cat(int signo)
{
printf("catch you:%d\n",signo);
}
int main()
{
signal(SIGINT,sig_cat);//注册信号捕获函数
while(1);
return 0;
}
运行程序之后,按下Ctrl+C,将会被捕获
#include
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);//由于有void*参数,可以携带复杂的数据
sigset_t sa_mask;//工作于信号捕捉函数执行期间屏蔽sa_mask对应的信号
int sa_flags;//设置为0时,默认屏蔽当前信号捕获函数对应的信号。例如,注册了SIGINT的捕获函数fun,当正在执行fun时,SIGINT再次到来时会被屏蔽
void (*sa_restorer)(void);
};
代码示例:
#include
#include
#include
void sig_cat(int signo)
{
printf("catch you:%d\n",signo);
}
int main()
{
struct sigaction act,oldact;
act.sa_handler = sig_cat;//设置信号捕获函数
sigemptyset(&act.sa_mask);//设置信号屏蔽字,只在sig_cat函数执行中有效
act.sa_flags = 0; //设置默认属性
int ret = sigaction(SIGINT,&act,&oldact);
if(ret==-1)
{
perror("sigaction err");
exit(1);
}
while(1);
return 0;
}
信号捕捉特性:
1.进程正常运行时,默认 PCB中有一个信号屏蔽字,假定为☆,它决定了进程自动屏蔽哪些信号。当注册了某个信号捕捉函数,捕捉到该信号以后,要调用该函数。而该函数有可能执行很长时间,在这期间所屏蔽的信号不由☆来指定。而是用sa_mask 来指定。调用完信号处理函数,再恢复为☆。
2. xXx信号捕捉函数执行期间,xXx信号自动被屏蔽。
3.阻塞的常规信号不支持排队,产生多次只记录一次。(在处理信号捕获函数期间,多次发送信号,当函数返回后,该信号只会被处理一次。后32个实时信号支持排队)。
#include
int pause(void);
操作系统内唯一一个主动造成进程挂起的系统调用。调用该系统调用的进程将处于阻塞状态(主动放弃cpu)直到有信号递达将其唤醒。
返回值:
代码示例:
// 实现sleep函数工程
#include
#include
#include
void sig_alrm(int signo)
{
}
unsigned mysleep(unsigned int nsecs)
{
struct sigaction newact,oldact;
unsigned int unslept;
newact.sa_handler = sig_alrm;
sigemptyset(&newact.sa_mask);
newact.sa_flags = 0;
sigaction(SIGALRM,&newact,&oldact);
alarm(nsecs);//定时nsecs秒
pause();
unslept = alarm(0);//取消闹钟,防止pause在alarm超时之前被其他信号唤醒,导致sig_alrm错误调用
sigaction(SIGALRM,&oldact,NULL);//将调用mysleep函数的程序中的信号行为恢复,避免调用之后修改调用者的信号逻辑
return unslept;
}
int main()
{
while(1)
{
printf("定时两秒\n");
mysleep(2);
}
}
【思考】
当alarm执行之后,如果此时因某种情况失去CPU,时间超过设定定时的时间,那么在得到CPU之前,操作系统会根据执行alarm的行为发送SIGALRM信号。再次得到CPU时,该程序已经不会受到SIGALRM信号了,那么进程将会阻塞在pause函数,这就是时序竞态。
那么如何解决这个问题呢?
可以通过设置屏蔽SIGALRM的方法来控制程序执行逻辑,但无论如何设置,程序都有可能在“解除信号屏蔽”与“挂起等待信号”这个两个操作间隙失去 cpu资源。除非将这两步骤合并成一个“原子操作"”。sigsuspend函数具备这个功能。在对时序要求严格的场合下都应该使用sigsuspend替换pause。
#include
int sigsuspend(const sigset_t *mask);
描述:
暂时用mask给出的掩码替换调用进程的信号掩码,然后挂起进程,直到传递信号,其操作是调用信号处理程序或终止进程
改进的sleep代码如下:
// 实现sleep函数工程
#include
#include
#include
void sig_alrm(int signo)
{
}
unsigned mysleep(unsigned int nsecs)
{
struct sigaction newact,oldact;
unsigned int unslept;
newact.sa_handler = sig_alrm;
sigemptyset(&newact.sa_mask);
newact.sa_flags = 0;
sigaction(SIGALRM,&newact,&oldact);
//屏蔽SIGALRM信号
sigemptyset(&newmask);
sigaddset(&newmask, SIGALRM);
sigprocmask(SIG_BLOCK, &newmask, &oldmask);//当向信号屏蔽字中添加SIGALRM,此时若出现时序竞态的情况,SIGALRM会被屏蔽而进入未决信号集
alarm(nsecs);
suspmask = oldmask;
sigdelset(&suspmask, SIGALRM);//为了不解除其他信号而影响程序逻辑,这里将系统屏蔽字拿出来之后,删除SIGALRM信号,即可解除对SIGALRM的屏蔽
//解除屏蔽和等待作为一个原子操作
sigsuspend(&suspmask);
unslept = alarm(0);//取消闹钟,防止pause在alarm超时之前被其他信号唤醒,导致sig_alrm错误调用
sigaction(SIGALRM,&oldact,NULL);//将调用mysleep函数的程序中的信号行为恢复,避免调用之后修改调用者的信号逻辑
sigprocmask(SIG_SETMASK,&oldmask,NULL);//恢复原先的信号屏蔽字
return unslept;
}
int main()
{
while(1)
{
printf("定时两秒\n");
mysleep(2);
}
}
使用SIGUSE1和SIGUSE2实现父子进程交替数数。
代码示例:
#include //kill
#include //fork/getpid
#include //perror
#include
int n = 0;
int flag = 0;//flag用来标志当前进程的状态,1表示数完数字,0表示发完信号。初始为0表示等待数数
void sys_err(const char * str)
{
perror(str);
exit(1);
}
void do_sig_child(int num)
{
printf("I'm child %d \t %d\n",getpid(),n);
n += 2;
flag = 1;
}
void do_sig_parent(int num)
{
printf("I'm parent %d \t %d\n",getpid(),n);
n += 2;
flag = 1;
}
int main()
{
pid_t pid = fork();
struct sigaction act;
if(pid<0) sys_err("fork error");
else if(pid == 0)//子进程
{
n = 1; //子进程数基数
act.sa_handler = do_sig_child;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGUSR1,&act,NULL);
while(1)
{
if(flag)
{
kill(getppid(),SIGUSR2);
flag = 0;
}
}
}
else if (pid > 0)//父进程,从父进程开始数数
{
sleep(1);//让子进程有充分之间注册信号
act.sa_handler = do_sig_parent;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGUSR2,&act,NULL);
do_sig_parent(0);//父进程开始数数,父进程数偶数
while(1)
{
if(flag)
{
kill(pid,SIGUSR1);
flag = 0;
}
}
}
return 0;
}
执行程序后发现,程序会卡在数到5027的位置(这个是随机的位置,可能会卡在其他位置)。
示例中,通过flag变量标记程序实行进度。flag置1表示数数完成。flag置0表示给对方发送信号完成。问题出现的位置,在父子进程 kill函数之后需要紧接着调用flag,将其置0,标记信号已经发送。回顾之前的时序竞态,在父进程执行kill发送SIGUSE1和设置flag标志位这期间很有可能被kernel调度,失去执行权利,而子进程获取了执行时间,通过发送信号回调捕捉函数,子进程的信号不会再次发送到父进程。当父进程获得CPU后,flag被设置为0。至此以后,父进程不会再收到子进程的信号来修改flag位,从而导致阻塞。
如何解决该问题呢?可以使用后续课程讲到的“锁”机制。当操作外部作用域的变量的时候,通过加锁、解锁来解决该问题。也就是说,在发送信号和修改flag位的时候加锁。现阶段,我们在编程期间如若使用全局变量,应在主观上注意全局变量的异步lO可能造成的问题。
上图中,inser函数为不可重入,由于内部使用了全局变量,当第一次插入insert函数时,在执行head==p和p->next=head之间因为一个信号打断被插入了node2,最终结果为第4步的结果,程序的逻辑发生了改变。
注意事项
a)使用静态数据结构。
b)调用了malloc或free
c)是标准l/O函数
例如:strtok就是一个不可重入函数,因为strtok内部维护了一个内部静态指针,保存上一次切割到的位置,如果信号的捕捉函数中也去调用strtok函数,则会造成切割字符串混乱,应用strtok_r版本,r表示可重入。
#include
#include
#include
#include
#include
void catch_child(int signo)
{
pid_t wpid;
while((wpid = wait(NULL))!=-1) //防止多个子进程发送信号排队而只执行一次
{
printf("catch child id:%d\n",wpid);
};
return;
}
int main()
{
pid_t pid;
int i;
for(i=0;i<5;i++)
{
if((pid=fork())==0)
{
break;
}
}
if(5==i){
struct sigaction act;
act.sa_handler = catch_child;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGCHLD, &act, NULL); //父进程注册SIGCHLD信号捕获函数
printf("I'm parent, pid=%d\n",getpid());
while(1);
}
else
{
printf("I'm child pid=%d\n",getpid());
//子进程退出时会向父进程发送SIGCHLD信号
}
return 0;
}
#include
int sigqueue(pid_t pid, int sig, const union sigval value);union sigval {
int sival_int;
void *sival_ptr;//由于进程之间数据不共享,一般用来给自己发送该数据。
};
#include
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);//由于有void*参数,可以携带复杂的数据
sigset_t sa_mask;//工作于信号捕捉函数执行期间屏蔽sa_mask对应的信号
int sa_flags;//设置为0时,默认屏蔽当前信号捕获函数对应的信号。例如,注册了SIGINT的捕获函数fun,当正在执行fun时,SIGINT再次到来时会被屏蔽
void (*sa_restorer)(void);
};siginfo_t {
int si_signo; /* Signal number */
int si_errno; /* An errno value */
int si_code; /* Signal code */
int si_trapno; /* Trap number that caused
hardware-generated signal
(unused on most architectures) */
pid_t si_pid; /* Sending process ID */
uid_t si_uid; /* Real user ID of sending process */
int si_status; /* Exit value or signal */
clock_t si_utime; /* User time consumed */
clock_t si_stime; /* System time consumed */
sigval_t si_value; /* Signal value */
int si_int; /* POSIX.1b signal */
void *si_ptr; /* POSIX.1b signal */
int si_overrun; /* Timer overrun count;
POSIX.1b timers */
int si_timerid; /* Timer ID; POSIX.1b timers */
void *si_addr; /* Memory location which caused fault */
long si_band; /* Band event (was int in
glibc 2.3.2 and earlier) */
int si_fd; /* File descriptor */
short si_addr_lsb; /* Least significant bit of address
(since Linux 2.6.32) */
void *si_call_addr; /* Address of system call instruction
(since Linux 3.5) */
int si_syscall; /* Number of attempted system call
(since Linux 3.5) */
unsigned int si_arch; /* Architecture of attempted system call
(since Linux 3.5) */}
当注册信号捕捉函数,希望获取更多信号相关信息,不应使用sa_handler而应该使用sa_sigaction。但此时的sa_flags必须指定为SA_SIGINFO。siginfo_t是一个成员十分丰富的结构体类型,可以携带各种与信号相关的数据。
系统调用可分为两类:慢速系统调用和其他系统调用。·
1.慢速系统调用:可能会使进程永远阻塞的一类。如果在阻塞期间收到一个信号,该系统调用就被中断,不再继续执行(早期);也可以设定系统调用是否重启。如,read、write、pause、wait...
2.其他系统调用:getpid、getppid、fork...
结合pause,回顾慢速系统调用:慢速系统调用被中断的相关行为,实际上就是pause的行为。如,read
可修改 sa_flags 参数来设置被信号中断后系统调用是否重启。SA_INTERRURT不重启。SA_RESTART重启。
扩展了解:sa_flags还有很多可选参数,适用于不同情况。如:捕捉到信号后,在执行捕捉函数期间,不希望自动阻塞该信号,可将sa_flags设置为SA_NODEFER(捕捉函数执行期间信号不会被屏蔽),除非sa_mask 中包含该信号。