linux系统-信号

信号

基本概念及机制

信号的共性:

  • 简单
  • 不能携带大量信息
  • 满足特性条件才能发送

特质:A给B发送信号,B收到信号之前执行自己的代码,收到信号后,不管执行到程序的什么位置,都要暂停运行去处理信号,处理完毕之后再继续执行。与硬件中断类似——异步模式。但信号是软件层面上的实现的中断,早期被称为”软中断”。

信号的特质:由于信号通过软件方法实现,其实现手段导致信号有很强的延时性。但对于用户来说,这个延迟时间非常短,不易察觉。

所有信号的产生和处理, 都是由内核完成的

与信号相关的事件和状态

产生信号:

  • 按键产生:Ctrl+c, Ctrl+z, Ctrl+\

  • 系统调用产生:kill, raise, abort

  • 软件条件产生:定时器alarm

  • 硬件异常产生:非法访问内存(段错误), 除0(浮点数例外), 内存对齐错误(总线错误);

  • 命令产生:kill命令

递达: 内核发出的信号递送并且到达进程

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

信号的处理方式:

  • 执行默认动作

  • 丢弃(忽略)

  • 捕捉(调用户处理函数)

信号屏蔽字和未决信号集

Linux内核的进程控制块PCB是一个结构体,task_struct除了包含进程id,状态,工作目录,用户id,组id,文件描述符,还包含了信号相关的信息,主要指阻塞信号集和未决信号集。

阻塞态:用阻塞信号集(信号屏蔽字)来描述

PCB阻塞信号集影响未决信号集

阻塞信号集(信号屏蔽字):将某些信号加入集合,对他们设置屏蔽,当屏蔽x信号后,再收到该信号,该信号的处理将推后(解除屏蔽字后)

未决信号集:

  • 信号产生,未决信号集中描述该信号的位立刻翻转为1,表示信号处于未决状态,当信号被处理后,对应位翻转回为0,这一时刻往往非常短暂。
  • 信号产生后由于某些原因(主要是阻塞)不能抵达。这类信号的集合称为未决信号集。在屏蔽解除前,信号一直处于未决状态。

信号4要素

  • 编号:信号有自己的编号,不存在为0的信号。其中1-31号信号称之为常规信号(也叫普通信号或标准信号)。34-64为实时信号,驱动编程与硬件相关,名字上区别不大。而前32个名字各不相同。

  • 名称

  • 事件

  • 默认处理动作

    • Term:终止进程
    • Ign:忽略信号(默认即时对该种信号忽略操作)
    • Core:终止进程,生成Core文件(查验进程死亡原因,用于gdb调试)
    • Stop:停止(暂停)进程
    • Cont:继续运行进程

man 7 signal可以查看帮助文档

特别强调:9)SIGKILL和19)SIGSTOP信号,不允许忽略和捕捉,只能执行默认动作。甚至不能将其其设置为阻塞。

只有每个信号所对应的事件发生了, 该信号才会被递送(但不一定递达), 不应该乱发信号

kill函数

给指定进程发送指定信号(不一定杀死)

1
2
3
4
5
6
int kill(pid_t pid, int sig);//成功:0, 失败:-1(ID非法,普通用户杀init进程等权级问题),设置errno
//sig:不推荐使用数字,应使用宏名,因为不同操作系统信号编号可能不同,但名称一致
//pid >0; //发送信号给指定的进程
//pid = 0; //发送信号给与调用kill函数进程属于同一进程组的所有进程
//pid < 0;//取|pid|发给对应进程组:kill -9 -10698 :杀死10698进程组的所有进程;
//pid = -1;//发送给进程有权限发送的系统中所有进程

进程组:每个进程都属于一个进程组,进程组是一个或多个进程集合,它们互相关联,共同完成一个实体任务,每个进程组都有一个进程组长,默认进程组ID与进程组长ID相同。

权限保护:super用户(root)可以发送信号给任意用户,普通用户是不能向系统用户发送信号的。kill -9 (root用户的pid)是不可以的。同样,普通用户也不能向其他普通用户发送信号,终止其进程。只能向自己创建的进程发送信号,普通用户基本规则是:发送者实际或有效用户ID == 接收者实际或有效用户ID

raise和abort函数

raise函数:给当前进程发送指定信号(自己给自己发)

1
2
3
raise(signo) == kill(getpid(), signo);

int raise(int sig);//成功:0

abort函数:给自己发送异常终止信号。SIGABRT信号,终止并产生core文件。

1
void abort(void); //该函数无返回

软件条件产生信号(定时产生信号)

alarm函数:设置定时器(闹钟),在指定seconds后,内核会给当前进程发送14)SIGALRM信号。进程收到该信号,默认动作终止

每个进程都有且只有唯一个定时器。

1
unsigned int alarm(unsigned int seconds);//返回0或剩余的秒数,无失败

常用:取消定时器alarm(0),返回旧闹钟余下秒数

定时,与进程无关(自然定时法)!无论进程处于何种状态(就绪、运行、挂起、终止、僵尸…),alarm都计时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//测试一秒钟数多少个数
#include <stdio.h>
#include <unistd.h>

int main()
{
int i;
alarm(1);

for(i=0;;i++)
{
printf("%d\n",i);
}
return 0;
}

//使用time命令测试程序运行时间
//real 0m1.003s
//user 0m0.074s
//sys 0m0.217s

time ./alarm可以统计alarm的运算时间。

使用time命令查看程序执行的时间。程序运行的瓶颈在IO,优化程序,首先优化IO

实际执行时间 = 系统时间+用户时间+等待时间

setitimer函数:设置定时器(闹钟),可以替代alarm函数,精度微秒(us),可以实现周期定时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <sys/time.h>

int getitimer(int which, struct itimerval *curr_value);
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
//参数which:指定定时方式。
//自然定时:ITIMER_REAL:14) SIGLARM 计算自然定时
//虚拟空间计时(用户空间,只计算进程占用CPU的时间):ITIMER_VIRTUAL 26) SIGVIRTUAL 只计算进程占用cpu时间
//运行时计时(用户+内核): ITIMER_PROF 27)SIGPROF 计算cpu及执行系统调用的时间

/*精确到us的时间结构体*/
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};

提示:

  • it_interval :用来设定两次定时任务之间间隔的时间

  • it_value:定时的时长

  • 两个参数都设置为0,即清0操作

signal捕捉信号:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <stdio.h>
#include <sys/time.h>
#include <signal.h>

/*信号捕捉回调函数*/
void myfun(int signo)
{
printf("hello signal\n");
}

int main()
{
/*it为传入参数,进行初始化*/
struct itimerval it, oldit;
//信号捕捉函数是一个回调函数
signal(SIGALRM, myfun);//注册SIGALRM信号的捕捉处理函数,捕捉由内核完成

it.it_value.tv_sec = 5;
it.it_value.tv_usec = 0;
it.it_interval.tv_sec = 3;
it.it_interval.tv_usec = 0;

if(setitimer(ITIMER_REAL,&it, &oldit) == -1) //自然定时
{
perror("settimer error");
return -1;
}
/*手动让程序阻塞*/
while(1);
return 0;
}

信号集操作函数

内核通过读取未决信号集来判断信号是否应该被处理,信号屏蔽字mask可以影响未决信集。可以在应用程序中自定义set来改变mask以达到屏蔽指定信号的目的。

操作信号集的若干步骤

1
2
3
4
5
6
7
8
9
10
/*创建一个自定义信号集*/
sigset_t set;
/*清空自定义信号集*/
sigemptyset(&set);
/*向自定义信号集添加信号*/
sigaddset(&set,SIGINT);
/*用自定义信号集操作内核信号集*/
sigprocmask(SIG_BLOCK,&set);
/*查看未决信号集*/
sigpending(&myset);

信号集设定

sigset_t 类型的本质是位图。但不应该直接使用位操作,而应该使用下列函数,保证跨系统操作有效

1
2
3
4
5
6
sigset_t set; //typedef unsigned long sigset_t
int sigemptyset(sigset_t *set);//将某个信号集清0,成功0,失败-1
int sigfillset(sigset_t *set);//将某个信号集置1, 成功0,失败-1
int sigaddset(sigset_t *set, int signum);//将某个信号加入信号集
int sigdelset(sigset_t *set, int signum);//将某个信号清出信号集
int sigismember(const sigset_t *set, int signum);//判断某个信号是否在信号集中,返回值:在集合:1,不在集合:0

sigprocmask函数

用来屏蔽信号、解除屏蔽也使用该函数。其本质为读取或修改进程的信号屏蔽字(PCB中).

注意:屏蔽信号只是将信号处理延后执行(延至解除屏蔽),而忽略表示将信号丢弃处理

1
2
3
4
5
6
7
8
int sigprocmask(int how, const sigset *set, sigset_t *oldset);//成功,0,失败-1,设置errno
//参数
//how参数取值:假设当前的信号屏蔽字为mask
//1.SIG_BLOCK:当how设置为此值,set表示需要屏蔽的信号。相当于mask = mask|set(设置阻塞, set表示需要屏蔽的信号)
//2.SIG_UNBLOCK:当how设置为此值,set表示需要解除屏蔽的信号,相当于mask = mask&~set(设置非阻塞, set表示需要解除屏蔽的信号;)
//3.SIG_SETMASK:set表示用于替代原始屏蔽集的新屏蔽集:相当于mask = set。若调用sigprocmask解除了对当前若干个信号的阻塞。则在sigprocmask返回前,至少将其中一个信号递达。(用set替换原始屏蔽集)
//set:传入参数,是一个位图,set中哪个位置为1,就表示当前进程屏蔽哪个信号
//oldset:传出参数,保留旧的信号屏蔽集

sigpending函数

读取当前进程的未决信号集

1
2
int sigpending(sigset *set) ;//set传出参数。
//返回:成功:0,失败-1,设置errno

打印未决信号集:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>

void printped(sigset_t *ped)
{
int i;
for(i = 1;i<32; i++)
{
if(sigismember(ped,i) == 1)
{
putchar('1');
}
else
{
putchar('0');
}
}
printf("\n");
}

int main()
{
sigset_t myset, ped, oldset; //自定义集合类型
sigemptyset(&myset); /*清空自定义信号集*/
sigaddset(&myset,SIGQUIT);/*向自定义信号集添加信号*/
sigaddset(&myset,SIGINT); //程序终止信号,通常Ctrl+c
sigprocmask(SIG_BLOCK,&myset, &oldset);/*用自定义信号集操作内核信号集*/
while(1)
{
sigpending(&ped);
printped(&ped);
sleep(1);
}
return 0;
}

简易信号捕捉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <stdlib.h>

typedef void (*sighandler_t)(int);

void catchsigint(int signo)
{
printf("-----------catch\n");
}

int main()
{
sighandler_t handler;
handler = signal(SIGINT,catchsigint);
if(handler == SIG_ERR)
{
perror("signal error");
exit(1);
}
while(1);
return 0;
}

sigaction函数注册捕捉

sigaction函数的功能是检查或修改与指定信号相关联的处理动作(可同时两种操作)

1
2
3
4
5
6
7
8
9
int sigaction(int signum, const struct sigaction* act, struct sigaction* oldact);

struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *); //不用
sigset_t sa_mask; //只工作于信号捕捉函数执行期间,相当于中断屏蔽
int sa_flags; //本信号默认屏蔽
void (*sa_restorer)(void); //废弃
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>

void docatch(int signo)
{
printf("%d signal is catched\n",signo);
}

int main()
{
int ret;
struct sigaction act;
act.sa_handler = docatch;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask,SIGQUIT);
act.sa_flags = 0;//默认属性,信号捕捉函数执行期间自动屏蔽本信号

ret = sigaction(SIGINT,&act,NULL); //程序终止信号
if(ret < 0)
{
perror("sigaction error");
exit(1);
}
while(1);
return 0;
}

信号捕捉特性

  • 进程正常运行时,默认PCB中有一个信号屏蔽字,假定为x,它决定了进程自动屏蔽哪些信号。当注册了某个信号捕捉函数,捕捉到该信号之后,要调用该函数。而该函数有可能执行很长时间,在这期间所屏蔽的信号不能由x来指定。而是用sa_mask来指定。调用完信号处理函数,再次恢复为x(捕捉函数执行期间, 信号屏蔽字由mask变为sigaction结构体中的sa_mask, 捕捉函数执行结束后, 恢复回mask)。

  • xxx信号捕捉函数执行期间,xxx信号自动被屏蔽(捕捉函数执行期间, 本信号自动被屏蔽(sa_flags=0);)

  • 阻塞的常规信号不支持排队,产生多次只记录一次。(后32个实时信号支持排队)(捕捉函数执行期间, 若被屏蔽信号多次发送, 解除屏蔽后只响应一次)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>

void docatch(int signo)
{
printf("%d signal is catched\n",signo);
sleep(10); //模拟信号捕捉函数执行时间很长
printf("finish\n");
}

int main()
{
int ret;
struct sigaction act;
act.sa_handler = docatch;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask,SIGQUIT);
act.sa_flags = 0;//默认属性,信号捕捉函数执行期间自动屏蔽本信号

ret = sigaction(SIGINT,&act,NULL);
if(ret < 0)
{
perror("sigaction error");
exit(1);
}
while(1);
return 0;
}

内核实现信号捕捉过程:

signal_catch

为什么执行完信号处理函数后要再次进入内核?因为信号处理函数是内核调用的, 函数执行完毕后要返回给调用者。

竞态条件

pause函数

调用该函数可以造成进程主动挂起,等待信号唤醒调用该系统调用的进程将处于阻塞状态(主动放弃cpu)直到信号递达将其唤醒。

1
2
3
4
5
6
7
int pause(void) ;//返回值:-1并设置errno为EINTR
/*返回值:
如果信号的默认处理动作为终止进程,则进程终止,pause函数没有机会返回
如果信号的默认处理动作是忽略,进程继续处于挂起状态,pause函数不返回
如果信号的处理动作是捕捉,则调用完信号处理函数后,pause返回-1。errno设置为EINTR,表示信号被中断
pause收到的信号不能被屏蔽,如果被屏蔽,那么puase就不能被唤醒
*/

使用pausealarm来实现sleep函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <stdlib.h>

void catch_sigalrm(int signo)
{
printf("catched\n");
}

unsigned int mysleep(unsigned int seconds)
{
int ret;
struct sigaction act, oldact;
act.sa_handler = catch_sigalrm;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;

ret = sigaction(SIGALRM,&act,&oldact);
if(ret == -1)
{
perror("sigaction error");
exit(1);
}

alarm(seconds);
ret = pause();//主动挂起,等待信号
if(ret == -1 && errno == EINTR)
{
printf("pause success\n");
}
ret = alarm(0);//闹钟清0
sigaction(SIGALRM,&oldact,NULL);//恢复SIGALRM信号旧有的处理方式
return ret;
}

int main()
{
while(1)
{
mysleep(3);
printf("-----------\n");
}

return 0;
}

时序竞态

时序问题分析:

借助pausealarm实现的mysleep函数,设想如下时序:

  • 注册SIGALRM信号处理函数(sigaction...
  • 调用alarm(1)函数设定闹钟1秒
  • 函数调用刚结束,开始倒计时1秒,当前进程失去cpu,内核调度优先级高的进程(多个)取代当前进程,当前进程无法获得cpu,进入就绪态等待cpu
  • 1秒后,闹钟超时,内核向当前进程发送SIGALARM信号(自然定时法,与进程状态无关),高优先级进程尚未执行完,当前进程仍处于就绪态,信号无法处理(未决)
  • 优先级高的进程执行完,当前进程获得cpu资源,内核调度回当前进程执行。SIGALRM信号递达,信号设置捕捉,执行处理函数catch_sigalrm
  • 信号处理函数执行结束,返回当前进程主控流程,pause()被调用挂起等待。(欲等待alarm函数发送的SIGALRM信号将自己唤醒)
  • SIGALRM信号已经处理完毕,pause不会等到。

解决时序问题

可以通过设置屏蔽SIGALRM的方法来控制程序执行逻辑,但无论如何设置,程序都有可能在“解除信号屏蔽”与“挂起等待信号”这两个操作间隙失去cpu资源。除非将这两步骤合并成一个“原子操作”,sigsuspend函数具备这个功能。在对时序要求严格的场合下都应该使用sigsuspend替换pause

1
int sigsuspend(const sigset_t *mask); //挂起等待信号

sigsuspend函数调用期间,进程信号屏蔽字由其参数mask指定。

程序执行过程的信号屏蔽字由sigaction.sa_mask决定,但在执行sigsuspend期间由传入的mask决定。

可将某个信号(如SIGALRM)从临时屏蔽字mask中删除,这样在调用sigsuspend时将解除对该信号的屏蔽,然后挂起等待,当sigsubpend返回时,进程的信号屏蔽字恢复为原来的值。如果原来对该信号是屏蔽态,sigsuspend函数返回后仍然屏蔽该信号字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
unsigned int mysleep1(unsigned int seconds)
{
unsigned int unslept;
struct sigaction newact, oldact;
sigset_t newmask, oldmask, suspmask;

//为SIGALRM设置捕捉函数,一个空函数
newact.sa_handler = catch_sigalrm;
sigemptyset(&newact.sa_mask);
newact.sa_flags = 0;
sigaction(SIGALRM,&newact, &oldact);

//设置阻塞信号集,阻塞SIGALRM信号
sigemptyset(&newmask);
sigaddset(&newmask, SIGALRM);
sigprocmask(SIG_BLOCK,&newmask,&oldmask); //信号屏蔽字 mask

//定时n秒,到时可以产生SIGALRM信号
alarm(seconds);

//构造一个调用sigsuspend临时有效的阻塞信号集,
//在临时阻塞信号集里解除SIGALRM的阻塞
suspmask = oldmask; //SIGALRM没有被屏蔽
sigdelset(&suspmask, SIGALRM); //原来屏蔽字中可能有屏蔽

//sigsuspend调用期间,采用临时阻塞信号集suspmask替换原有阻塞信号集合
//这个信号集中不包含SIGALRM喜好,同时挂起等待
//当sigsuspend被信号唤醒返回时,恢复原来的阻塞信号集
sigsuspend(&suspmask);//原子操作
unslept = alarm(0);

//恢复SIGALRM原有的处理动作,呼应前面注释
sigaction(SIGALRM,&oldact,NULL);

//解除对SIGALRM的阻塞,呼应前面注释
sigprocmask(SIG_SETMASK, &oldmask, NULL);
return (unslept);
}

总结

竞态条件跟系统负载有很紧密的的关系,体现出信号的不可靠性。系统负载越严重,信号不可靠性越强。

不可靠由其实现原理导致。信号是通过软件方式实现的(与内核调度高度依赖,延时性强),每次系统调用结束后,或中断处理结束后,需要扫描PCB中的未决信号集来判断是否应该处理某个信号,当系统负载过重时,会出现时序混乱

这种意外情况只能出现在编写程序过程中,提早预见,主动规避,而无法通过gdb程序调试等其他手段弥补,且由于该错误不具规律性,后期捕捉和重现十分困难。

可重入函数,不可重入函数

一个函数在被调用执行期间(尚未调用结束),由于某种时序又被重复调用,称为“重入”,根据函数实现的方法可分为”可重入函数”和“不可重入函数”。

注意事项:

  • 定义可重入函数,函数内部不能含有全局变量及static变量,不能使用malloc,free
  • 信号捕捉函数应设计为可重入函数
  • 信号处理程序可以调用的可重入函数可参阅man 7 signal

SIGCHLD信号

产生条件

  • 子进程终止
  • 子进程收到SIGSTOP信号停止
  • 子进程处在停止态,接受到SIGCONT后唤醒

借助SIGCHLD信号回收子进程

子进程结束运行,其父进程会收到SIGCHLD信号,该信号的默认处理动作是忽略。可以捕捉该信号,在捕捉函数中完成子进程状态的回收。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>

void sys_err(char *str)
{
perror(str);
exit(1);
}

void do_sig_child(int signo)
{
int status;
pid_t pid;

while((pid = waitpid(0, &status,WNOHANG))>0)
{
if(WIFEXITED(status)) //WIFEXITED(status) 这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个非零值
{
printf("--------------child %d exit %d\n",pid,WEXITSTATUS(status)); //当WIFEXITED返回非零值时,我们可以用这个宏来提取子进程的返回值,如果子进程调用exit(5)退出,WEXITSTATUS(status)就会返回5;如果子进程调用exit(7),WEXITSTATUS(status)就会返回7。请注意,如果进程不是正常退出的,也就是说,WIFEXITED返回0,这个值就毫无意义
}
else if(WIFSIGNALED(status)) // WIFSIGNALED(status)为非0 表明进程异常终止
{
printf("child %d cancle signal %d\n",pid, WTERMSIG(status)); //通过WTERMSIG(status)获取使得进程退出的信号编号
}
}
}

int main()
{
pid_t pid;
int i;
//阻塞SIGCHLD
for(i=0; i<10; i++)
{
if((pid = fork()) == 0)
{
break;
}
else if(pid < 0)
{
sys_err("fork");
}
}

if(pid == 0) //10个子进程
{
int n = 1;
while(n--)
{
printf("child ID %d\n",getpid());
sleep(1);
}
return i+1;
}
else if(pid > 0)
{
//SIGCHLD阻塞
struct sigaction act;
act.sa_handler = do_sig_child;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGCHLD, &act, NULL);
//NULL解除对SIGCHLD的阻塞

while(1)
{
printf("Parent ID %d\n",getpid());
sleep(1);
}
}
return 0;
}

中断系统调用

系统调用可以分为两种:慢速系统调用和其他系统调用。

  • 慢速系统调用:可能会使进程永远阻塞的一类。如果在阻塞期间收到一个信号,该系统调用就会被中断,不再继续执行(早期)。也可以设定系统调用是否重启。如read、write、pause、wait...
  • 其他系统调用:getpid、getppid、fork

结合pause,回顾慢速系统调用:

慢速系统调用被中断的相关行为。实际上就是pause的行为,如read:

  • 想中断pause,信号不能被屏蔽
  • 信号的处理方式必须是捕捉(默认、忽略都不可以)
  • 中断后返回-1,设置errnoEINTR(表示被信号中断)

可以修改sa_flags参数来设置被信号中断后系统调用是否重启。SA_INTERRURT不重启,SA_RESTART重启。

sa_flags还有很多可选参数,适用于不同情况。如:捕捉到信号后,在执行捕捉信号期间,不希望自动阻塞该信号,可将sa_flags设置为SA_NODEFER,除非sa_mask中包含该信号。

终端

所有输入输出设备总称。

终端启动流程:init->fork->exec->getty->用户输入帐号->login->输入密码->exec->bash