信号(p161)
- 每个进程收到的所有信号,都是由内核负责发送,内核处理。
- 信号的响应方式:
1.忽略信号:信号处理完成后忽略/丢弃。
2.执行系统默认动作:1.终止进程,2.终止进程且core文件,3.忽略,4.暂停stop,5.继续。
3.捕捉信号:不让信号执行默认动作,按自己规定的方式执行。 - 信号屏蔽字(阻塞信号集set):将某些信号加入集合,对他们设置屏蔽,当屏蔽该信号后,再接收到该信号,该信号的处理将推后。
- 未决信号集(信号未处理,产生的抵达之间的状态):
1.信号产生,未决信号集中描述该信号的位立刻翻转为1,表信号处于未决状态。当信号被处理时,对应位翻转回为0。这一时刻往往非常短暂。
2.信号产生后由于某些原因(主要是阻塞)不能抵达。这类信号的集合称之为未决信号集。在屏蔽解除前,信号一直处于未决状态。 - 信号屏蔽字影响未决信号集
Linux常规信号一览表
1)SIGHUP:当用户退出shell时,由该shell启动的所有进程将收到这个信号,默认动作为终止进程
2)SIGINT:当用户按下了<Ctrl+C>组合键时,用户终端向正在运行中的由该终端启动的程序发出此信号。默认动作为终止进程。
3)SIGQUIT:当用户按下<ctrl+>组合键时产生该信号,用户终端向正在运行中的由该终端启动的程序发出些信,默认动作为终止进程。
4)SIGlLL:CPU检测到某进程执行了非法指令。默认动作为终止进程并产生core文件
5)SIGTRAP:该信号由断点指令或其他trap指令产生。默认动作为终止里程并产生core文件。
6)SIGABRT:调用abort函数时产生该信号。默认动作为终止进程并产生core文件。
7)SlGBUS:非法访问内存地址,包括内存对齐出错,默认动作为终止进程并产生core文件。
8)SIGFPE:在发生致命的运算错误时发出。不仅包括浮点运算错误,还包括溢出及除数为0等所有的算法错误。默认动作为终止进程并产生core文件。
9)SIGKlLL:无条件终止进程。本信号不能被忽略,处理和阻塞。默认动作为终止进程。它向系统管理员提供了可以杀死任何进程的方法。
10)SIGUSR1:用户定义的信号。即程序员可以在程序中定义并使用该信号。默认动作为终止进程。
11)SIGSEGV:指示进程进行了无效内存访问。默认动作为终止进程并产生core文件。
金.
12)SIGUSR2:另外一个用户自定义信号,程序员可以在程序中定义并使用该信号。默认动作为终止进程。
13)SIGPIPE:Broken pipe向一个没有读端的管道写数据。默认动作为终止进程。
14)SIGALRM:定时器超时,超时的时间由系统调用alarm设置。默认动作为终止进程。
15)SIGTERM:程序结束信号,与SGKIL不同的是,该信号可以被阻塞和终止。通常用来要示程序正常退出。执行shell命令Kil时,缺省产生这个个信号。默认动作为终止进程。
16)SIGSTKFLT:Linux早期版本出现的信号,现仍保留向后兼容。默认动作为终止进程。
17)SIGCHLD:子进程结束时,父进程会收到这个信号。默认动作为忽略这个信号。子进程的运行状态发生变化的时候,会向他的父进程发送SIGCHLD信号
18)SIGCONT:如果进程已停止,则使其继续运行。默认动作为继续/忽略。
19)SIGSTOP:停止进程的执行。信号不能被忽略,处理和阻塞。默认动作为暂停进程。
20)51GTSTP:停止终端交互进程的运行。按下<ctrl+z>组合键时发出这个信号。默认动作为暂停进程。
21)SIGTTIN:后台进程读终端控制台。默认动作为暂停进程。
22)SIGTTOU:该信号类似于SIGTTIN,在后台进程要向终端输出数据时发生。默认动作为暂停进程。
23)SIGURG:套接字上有紧急数据时,向当前正在运行的进程发出些信号,报告有紧急数据到达。如网络带外数据到达,默认动作为忽略该信号。
24)SlGXCPU:进程执行时间超过了分配给该进程的CPU时间,系统产生该信号并发送给该进程。默认动作为终止进程。
25)SIGXFSZ:超过文件的最大长度设置。默认动作为终止进程。
26)SIGVTALRM:虚拟时钟超时时产生该信号。类似于SIGALRM,但是该信号只计算该进程占用CPU的使用时间。默认动作为终止进程。
27)SGlPROF:类似于SIGVTALRM,它不公包括该进程占用CPU时间还包括执行系统调用时间。默认动作为终止进程。
28)SIGWINCH:窗口变化大小时发出。默认动作为忽略该信号。
AEANE4
29)SIGIO:此信号向进程指示发出一个异步IO事件。默认动作为忽略。
30)SIGPWR:关机,默认动作为终止进程
31)SIGSYS:无效的系统调用。默认动作为终止进程并产生core文件。
34)SIGRTMIN ~(64)SIGRTMAX:LINUX的实时信号,它们没有固定的含义(可以由用户自定义)。所有的实时信号的默认动作都为终止进程。
- 9号SIGKILL和19号SIGSTOP信号,不允许忽略和捕捉,不能阻塞。
按键终止信号
- ctrl+c:(2)SIGINT,中断,interrupt
- ctrl+z:(20)SIGTSTP,暂停,停止,terminal
- ctrl+\:(3)SIGQUIT:退出
kill系统调用允许进程将信号发送给某个进程或进程组
#include<sys/types.h>
#include<signal.h>
int kill(pid_t pid,int sig);
pid == 0:将sig发送给调用进程同组的进程,包括进程自己。
pid == -1:进程有发送信号权限时,把sig发送给处init进程之外的所有进程(权限:CAP_KILL)
pid< -1:sig发送给进程组号为pid绝对值的进程。
sig:最好写英文宏名称
成功:返回0,失败:返回-1
- raise函数
给当前进程发信号
#include<signal.h>
int raise(int sig);
成功:0,失败:非0
- abort函数
使进程异常终止
#include<signal.h>
void abort(void);
返回值:无
调用此函数使内核向调用进程发送SIGABRT信号并使进程异常结束
- alarm函数
设置定时器,当指定seconds后,内核会给当前进程发送SIGALRM信号,终止当前进程。
每个进程都有定时器,且只有一个
#include<unistd.h>
unsigned int alarm(unsigned int seconds);
返回上一个定时器的剩余时间,如果没有则返回0。
取消定时器:alarm(0);返回旧闹钟余下的秒数
- 例子:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>
typedef void (*sighandler_t)(int);
void caught()
{
printf("the signal is caught\n");
}
void caught_sigaction()
{
printf("the signal is caught\n");
}
void mysleep_signal(unsigned int seconds) 方法1
{
sighandler_t sig = signal(SIGALRM,caught);
alarm(seconds);
int pau = pause();
if(pau == -1)
{
printf("pause successful\n");
}
alarm(0);
}
void mysleep_sigaction(unsigned int seconds) 方法2
{
struct sigaction *act;
act = (struct sigaction *)malloc(sizeof(struct sigaction));
struct sigaction *oldact;
oldact = (struct sigaction *)malloc(sizeof(struct sigaction));
act->sa_handler = caught_sigaction;
act->sa_flags = 0;
sigemptyset(&act->sa_mask);
int sig = sigaction(SIGALRM, act,oldact);
if(sig == -1)
{
perror("sigaction error:");
exit(1);
}
alarm(seconds);
pause();
alarm(0);
sigaction(SIGALRM,oldact,oldact);
}
int main()
{
while(1)
{
mysleep_signal(3);
printf("sleep_signal......\n");
mysleep_sigaction(3);
printf("sleep_sigaction......\n");
}
return 0;
}
- setitimer函数(实现微秒级定时,周期定时)
int setitimer(int which,const struct itimerval *new_value,struct itimerval *old_value);
- signal允许进程设置信号的响应形式(信号捕捉)
#include<signal.h>
sighandler_t signal(int signum,sighandler_t handler);
signum:信号值
handler:信号指定的响应方式(执行操作或处理函数)
成功:返回前一个sighandler_t 的值,失败:返回SIG_ERR
- 例子1:
#include<stdio.h>
#include<signal.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
typedef void (*sighandler_t)(int);
int num = 0;
void catch()
{
num++;
printf("the signal of SIGINT is being [%d]caught!!!\n",num);
}
int main()
{
sighandler_t sig = signal(SIGINT,catch);
if(sig == SIG_ERR)
{
perror("signal error");
exit(1);
}
while(1);
return 0;
}
- 例子2:
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<sys/time.h>
#include<stdlib.h>
/*
struct itimerval {
struct timeval it_interval;
struct timeval it_value;
};
struct timeval {
time_t tv_sec;
suseconds_t tv_usec;
*/
typedef void (*sighandler_t)(int);
void sigfunc()
{
printf("the signal of SIGALRM is coming!!!\n");
}
int main()
{
struct itimerval time1,time2;
sighandler_t sig = signal(SIGALRM,sigfunc);
if(sig == SIG_ERR)
{
perror("signal error:");
exit(1);
}
time1.it_interval.tv_sec = 1;两次任务之间的时间间隔
time1.it_interval.tv_usec = 0;
time1.it_value.tv_sec = 1;定时时间
time1.it_value.tv_usec = 0;
int setitime = setitimer(ITIMER_REAL,&time1,&time2);
if(setitime == -1)
{
perror("setitimer error:");
exit(1);
}
while(1);
return 0;
}
信号集设定
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);
将某个信号加入信号集,成功:0;失败:-1
int sigdelset(sigset t*set,int signum);
将某个信号清出信号集,成功:0;失败:-1
int sigismember(const sigset t*set,int signum);
判断某个信号是否在信号集中,返回值:在集合:1;不在:0;出错:-1
sigsett类型的本质是位图。但不应该直接使用位操作,
而应该使用上述函数,保证跨系统操作有效。对比认知select函数
sigprocmask (信号屏蔽字)
- 用来屏蔽信号、解除屏蔽也使用该函数。其本质,读取或修改进程的信号屏蔽字(PCB中)
- 严格注意,屏蔽信号:只是将信号处理延后执行(延至解除屏蔽);而忽略表示将信号丢处理。
设置或者获取信号屏蔽字
int sigprocmask(int how,const sigset_t*set,sigset_t*oldset);
成功:0;失败:-1,设置errno
set:传入参数,是一个位图,set中哪位置1,就表示当前进程屏蔽哪个信号。
oldset:传出参数,保存旧的信号屏蔽集。
how参数取值:假设当前的信号屏蔽字为mask
1.SIG_BLOCK:当how设置为此值,set 表示需要屏蔽的信号。相当于mask=mask|set
2.SIG_UNBLOCK:当how设置为此,set 表示需要解除屏蔽的信号。相当于mask=mask &~set
3.SIG_SETMASK:当how设置为此,set表示用于替代原始屏蔽及的新屏蔽集。相当于mask=set
若,调用 sigprocmask解除了对当前若干个信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。
sigpending(未决信号)
获取未决信号
#include<signal.h>
int sigpending(sigset_t *set);
set:信号集,传出参数(获取的信号包含在set信号集中)
成功:0,失败:-1
- 例子:
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
void print_signal(sigset_t *set)
{
for(int i = 1;i < 32;i++)
{
if(sigismember(set,i) == 1)
{
putchar('1');
}
else
{
putchar('0');
}
}
printf("\n");
}
int main()
{
sigset_t newset,oldset,outset;
sigemptyset(&newset);
for(int i = 1;i<32;i++)
{
sigaddset(&newset,i);
}
sigprocmask(SIG_BLOCK,&newset,&oldset);
while(1)
{
sigpending(&outset);
print_signal(&outset);
sleep(1);
}
return 0;
}
sigaction(测试或设置信号的处理方式)
#include<signal.h>
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldset);
struct sigaction {
void(*sa_handler)(int); 函数指针,传函数名,回调函数,被内核调用
void(*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask; 指定信号捕捉期间要屏蔽的信号集,仅在执行期间有用
int sa_flags; 0时默认屏蔽本信号
void(*sa_restorer)(void); 忽略
};
act为传入参数
oldset为传出参数;返回-1
成功:返回0,失败
pause函数(时序竟态:程序先后执行顺序不一致)
#include<unistd.h>
int pause(void);
功能:进程调用pause会挂起,直到进程捕捉到一个信号,等待信号唤醒
当捕捉到信号时,返回-1,并且设置errno为EINTR
sigsuspend函数(解除对信号的阻塞&挂起等待同时执行,保证挂起的进程能收到该信号,原子操作p177)
#include<signal.h>
int sigsuspend(const sigset_t *mask);
功能:设置要被阻塞的信号,在对时序要求严格的场合下都应该使sigsuspend.替换pause。
成功:返回0,失败:返回-1
***解决时序问题***
可以通过设置屏蔽 SIGALRM的方法来控制程序执行逻辑,但无论如何设置,程序都有可能在“解除信号屏蔽”与“挂起等待信号”这个两个操作间隙失去cpu资源。除非将这两步骤合并成一个“原子操作”。sigsuspend.函数具备这个功能。
sigsuspend 函数调用期间,进程信号屏蔽字由其参数mask指定。
可将某个信号(如SlGALRM)从临时信号屏蔽字mask中删除,这样在调用sigsuspend时将解除对该信号的屏蔽,然后挂起等待,当sigsuspend返回时,进程的信号屏蔽字恢复为原来的值。如果原来对该信号是屏蔽态,sigsuspend函数返回后仍然屏蔽该信号。
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<unistd.h>
void caught()
{
printf("the signal is caught\n");
}
void signal1()
{
//1.设置信号的捕捉方式
struct sigaction *act;
struct sigaction *oldact;
act = (struct sigaction *)malloc(sizeof(struct sigaction));
oldact = (struct sigaction *)malloc(sizeof(struct sigaction));
act->sa_flags = 0;
sigemptyset(&act->sa_mask);
act->sa_handler = caught;
int sig = sigaction(SIGTSTP,act,oldact);
if(sig == -1)
{
perror("sigaction error:");
exit(1);
}
//2.阻塞SIGALRM信号
sigset_t *newmask;
sigemptyset(newmask);
sigaddset(newmask,SIGTSTP);
sigsuspend(newmask);
//恢复
alarm(0);
sigaction(SIGTSTP,oldact,oldact);
}
int main()
{
while(1)
{
signal1();
}
return 0;
}
- 练习:父子进程间交替数数:
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<unistd.h>
int flags = 0,num = 0;
void father()
{
num +=2;
printf("I am father,mypid = %d,counting...[%d]\n",getpid(),num);
flags = 1;
sleep(1);
}
void child()
{
num +=2;
printf("I am child,mypid = %d,counting...[%d]\n",getpid(),num);
flags = 1;
sleep(1);
}
struct sigaction sigact;
int main()
{
pid_t pid = fork();
if(pid > 0)
{
num = 0;
sleep(1);
//signal(SIGUSR1,father);
sigact.sa_sigaction = father;
sigact.sa_flags = 0;
sigemptyset(&sigact.sa_mask);
sigaction(SIGUSR1,&sigact,NULL);
father();
while(1)
{
if(flags == 1)
{
kill(pid,SIGUSR2);
flags = 0;
}
}
}
else if(pid == 0)
{
num = 1;
//signal(SIGUSR2,child);
sigact.sa_sigaction = child;
sigact.sa_flags = 0;
sigemptyset(&sigact.sa_mask);
sigaction(SIGUSR2,&sigact,NULL);
while(1)
{
if(flags == 1)
{
kill(getppid(),SIGUSR1);
flags = 0;
}
}
}
return 0;
}