unix信号处理
一、基本概念
-
中断
中止(注意不是终止)当前正在执行的程序,转而执行其它任务。 硬件中断:来自硬件设备的中断。 软件中断:来自其它程序的中断。
-
信号是一种软件中断
信号提供了一种以异步方式执行任务的机制。
3.常见信号
信号 | 产生条件 |
---|---|
SIGINT(2) | 用户按中断键(Ctrl+C),产生此信号,并送至前台进程组的所有进程 |
SIGINT(3) | 用户按退出键(Ctrl+\),产生此信号,并送至前台进程组的所有进程 |
SIGINT(9) | 不能被捕获或忽略。常用于杀死进程 |
4.不可靠信号(非实时信号)
(1)小于SIGRTMIN(34)的信号都是不可靠信号
(2)不支持排队,可能会丢失。同一个信号产生多次,进程可 能只收到一次该信号。
(3)进程每次处理完这些信号后,对相应信号的响应被自动恢复为默认动作,除非显示地通过signal函数重新设置一次信号处理程序。
5.可靠信号(实时信号)
(1)位于[SIGRTMIN(34),SIGRTMAX(64)]区间的信号都是可靠信号。
(2)支持排队,不会丢失。
(3)无论可靠信号还是不可靠信号,都可以通过sigqueue/sigaction函数发送/安装,以获得比其早期版本kill/signal函数更可靠的使用效果。
二、signal.c
(1) 在某些Unix系统上,通过signal函数注册的信号处理函数只一次有效。 即内核每次调用信号处理函数前,会将对该信号的处理自动恢复为默认方式。 为了获得持久有效的信号处理,可以在信号处理函数中再次调用signal函数,重新注册一次。
(2) SIGKILL/SIGSTOP信号不能被忽略,也不能被捕获。
(3) 普通用户只能给自己的进程发送信号,root用户可以给任何进程发送信号。
void sigtstp_proc(int sig){ //信号处理函数 当接收对应的信号时 执行的代码
printf("%d进程接收到一个信号:%d\n",getpid(),sig);
}
void sig_proc(int sig){
printf("%d进程接收到一个信号:%d\n",getpid(),sig);
}
int main(){
printf("进程%d",getpid());
//SIGINT 忽略 终止 终止+core 添加处理方式
if(signal(SIGINT,SIG_IGN)==SIG_ERR){ //忽略
perror("signal");
return -1;
}
if(signal(SIGQUIT,SIG_DFL)==SIG_ERR){//默认处理
perror("signal");
return -1;
}
if(signal(SIGTSTP,sigtstp_proc)==SIG_ERR){ //自定义处理方式
perror("signal"); //当进程接收到SIGTSTP时 无论在什么地方都会去执行sigtstp_proc
return -1;
}
if(signal(6,sig_proc)==SIG_ERR){
perror("signal");
return -1;
}
if(signal(9,SIG_IGN)==SIG_ERR){
perror("signal");
}
for(;;);
return 0;
}
三、子进程的信号处理
子进程会继承父进程的信号处理方式,直到子进程调用exec函数。
进程调用exec函数后, exec函数将被父进程设置为捕获的信号恢复至默认处理, 其余保持不变。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
void sigint_proc(int sig){
printf("%d进程接收到%d信号!\n",getpid(),sig);
}
int main(){
//父进程的注册的信号处理函数
if(signal(SIGINT,sigint_proc)==SIG_ERR){
perror("signal");
return -1;
}
pid_t id = fork();
if(id == -1){
perror("fork");
return -1;
}
if(id == 0){//子进程继承父进程的信号处理函数
printf("子进程:%d\n",getpid());
execve("rundead",NULL,NULL);
}else{
printf("父进程:%d\n",getpid());
}
for(;;);
return 0;
}
四、发送信号
Ctrl+C - SIGINT(2),终端中断
Ctrl+\ - SIGQUIT(3),终端退出
Ctrl+Z - SIGTSTP(20),终端暂停
命令
kill -信号 进程号
(1)kill
#include <signal.h>
int kill (pid_t pid, int sig);
成功返回0,失败返回-1。
pid > 0 - 向pid进程发送sig信号。
pid = 0 - 向同进程组的所有进程发送信号。
pid = -1 - 向所有进程发送信号,
前提是调用进程有向其发送信号的权限。
pid < -1 - 向绝对值等于pid的进程组发信号。
0信号为空信号。
若sig取0,则kill函数仍会执行错误检查,
但并不实际发送信号。这常被用来确定一个进程是否存在。
向一个不存在的进程发送信号,会返回-1,且errno为ESRCH。
kill.c
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
int main(int argc,char *argv[]){
if(argc < 3){
printf("%s -sig pid\n",argv[0]);
return -1;
}
int sig = -atoi(argv[1]);
pid_t pid = atoi(argv[2]);
int ret = kill(pid,sig);
if(ret == -1){
if(sig == 0 && errno == ESRCH){
printf("%d进程不存在!\n",pid);
}
perror("kill");
return -1;
}
return 0;
}
(2) raise
#include <signal.h>
int raise (int sig);
向调用进程自身发送sig信号。成功返回0,失败返回-1。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
void sigint_proc(int sig){
printf("%d进程收到%d信号!\n",getpid(),sig);
}
int main(){
if(signal(SIGINT,sigint_proc)==SIG_ERR){
perror("signal");
return -1;
}
raise(SIGINT);//向本进程发送一个信号
kill(getpid(),SIGINT);//向自己发送一个信号SIGINT
for(;;);
return 0;
}
五、pause
1.使调用进程进入睡眠状态,直到有信号终止该进程或被捕获。
2.只有调用了信号处理函数并从中返回以后,该函数才会返回。
3.相当于没有时间限制的sleep函数。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void sigint_proc(int sig){
}
int main(){
if(signal(SIGINT,sigint_proc)==SIG_ERR){
perror("signal");
return -1;
}
printf("begin...\n");
pause();//停住了 收到信号之后被唤醒
printf("end!\n");
return 0;
}
六、sleep
1.使调用进程睡眠seconds秒,除非有信号终止该进程或被捕获。
2.只有睡够seconds秒,或调用了信号处理函数并从中返回以后,该函数才会返回。
3.该函数要么返回0(睡够),要么返回剩余秒数(被信号中断)。
4.相当于有时间限制的pause函数。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void sigint_proc(int sig){
}
int main(){
if(signal(SIGINT,sigint_proc)==SIG_ERR){
perror("signal");
return -1;
}
unsigned int ret = sleep(10);//返回剩余的秒数 可能被信号唤醒
printf("ret = %u\n",ret);
return 0;
}
七、alarm
1.使内核在seconds秒之后,向调用进程发送SIGALRM(14)闹钟信号。
2.若之前已设过定时且尚未超时,则调用该函数会重新设置定时,并返回之前定时的剩余时间。
3.seconds取0表示取消之前设过且尚未超时的定时。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
void sigalarm_proc(int sig){
system("clear");
time_t t = time(NULL);
struct tm *lt = localtime(&t);
printf(" %2d:%2d:%2d \n",lt->tm_hour,lt->tm_min,lt->tm_sec);
alarm(1);//1秒之后又发出SIGALRM信号
}
int main(){
if(signal(SIGALRM,sigalarm_proc)==SIG_ERR){
perror("signal");
return -1;
}
alarm(3);
int ret = alarm(1);
printf("%d \n",ret);
for(;;);
return 0;
}
八、信号集与信号阻塞(信号屏蔽)
- 信号集
(1) 多个信号的集合类型: sigset_t,128个二进制位 每个位代表一个 信号。
#include <signal.h>
2.
3.// 将信号集set中的全部信号位置1
4.int sigfillset (sigset_t* set);
5.
6.// 将信号集set中的全部信号位清0
7.int sigemptyset (sigset_t* set);
8.
9.// 将信号集set中与signum对应的位置1
10.int sigaddset (sigset_t* set, int signum);
11.
12.// 将信号集set中与signum对应的位清0
13.int sigdelset (sigset_t* set, int signum);
14.
15.成功返回0,失败返回-1。
16.
17.// 判断信号集set中与signum对应的位是否为1
18.int sigismember (const sigset_t* set, int signum);
19.
20.若信号集set中与signum对应的位为1,则返回1,否则返回0。
-
信号屏蔽
(1)当信号产生时,系统内核会在其所维护的进程表中,为特定的进程设置一个与该信号相对应的标志位,这个过程称为递送(delivery)。
(2)信号从产生到完成递送之间存在一定的时间间隔。处于这段时间间隔中的信号状态,称为未决(pending)。
(3)每个进程都有一个信号掩码(signal mask)。 它实际上是一个信号集,其中包括了所有需要被屏蔽的信号。注意:对于不可靠信号,通过sigprocmask函数设置信号掩码以后,相同的被屏蔽信号只会屏蔽第一个,并在恢复信号掩码后被递送,其余的则直接忽略掉。
而对于可靠信号,则会在信号屏蔽时按其产生的先后顺序排队,一旦恢复信号掩码,这些信号会依次被信号处理函数处理。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
sigset_t set;
sigset_t oldset;
void sig_proc(int sig){
printf("收到了%d信号\n",sig);
}
void sig5_proc(int sigs){
//获取处于未决状态的信号集
sigset_t sigpset;
sigpending(&sigpset);
int sig = 1;
for(;sig<65;sig++){
if(sigismember(&sigpset,sig)==1){
printf("%d 信号处于未决!\n",sig);
}
}
//解除信号掩码
sigprocmask(SIG_SETMASK,&oldset,NULL);
//在屏蔽期间的信号将会被递送
}
int main(){
printf("进程:%d\n",getpid());
sigemptyset(&set);
//不可靠信号
sigaddset(&set,2);
sigaddset(&set,3);
sigaddset(&set,4);
//可靠信号
sigaddset(&set,40);
sigaddset(&set,41);
sigaddset(&set,42);
signal(2,sig_proc);
signal(3,sig_proc);
signal(4,sig_proc);
signal(40,sig_proc);
signal(41,sig_proc);
signal(42,sig_proc);
signal(5,sig5_proc);//收到5信号 解除信号掩码 解除之后,在屏蔽期间收到的信号就会被递送
//int ret = sigprocmask(SIG_SETMASK,&set,&oldset);
int ret = sigprocmask(SIG_BLOCK,&set,NULL);
if(ret == -1){
perror("sigprocmask");
}
for(;;);
//sigprocmask(SIG_SETMASK,&oldset,NULL);//恢复
sigprocmask(SIG_UNBLOCK,&set,NULL);//不太科学
return 0;
}
九、sigaction
#include <signal.h>
int sigaction (
int signum, // 信号码
const struct sigaction* act, // 信号处理方式
struct sigaction* oldact // 原信号处理方式
// (可为NULL)
);
struct sigaction {
void (*sa_handler) (int);
// 信号处理函数指针1
void (*sa_sigaction) (int, siginfo_t*, void*);
// 信号处理函数指针2
sigset_t sa_mask; // 信号掩码
int sa_flags; // 信号处理标志
void (*sa_restorer)(void);
// 保留,NULL
};
成功返回0,失败返回-1。
typedef struct siginfo {
pid_t si_pid; // 发送信号的PID
sigval_t si_value; // 信号附加值(需要配合sigqueue函数)
...
} siginfo_t;
typedef union sigval {
int sival_int;
void* sival_ptr;
} sigval_t;
sigaction::sa_flags可为以下值的位或:
SA_ONESHOT/SA_RESETHAND
执行完一次信号处理函数后,即将对此信号的处理恢复为
默认方式(这也是老版本signal函数的缺省行为).
SA_NODEFER/SA_NOMASK
在信号处理函数的执行过程中,不屏蔽这个正在被处理的信号。
SA_NOCLDSTOP
若signum参数取SIGCHLD,则当子进程暂停时,不通知父进程。
SA_RESTART
系统调用一旦被signum参数所表示的信号中断,会自行重启。
SA_SIGINFO
使用信号处理函数指针2,通过该函数的第二个参数,提供更多信息。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void sigint_proc(int sig){//在处理sig信号时 自动屏蔽该信号
printf("收到信号%d\n",sig);
sleep(10);
printf("proc end!\n");
}
int main(){
//signal(2,sigint_proc);
sigset_t set;
sigemptyset(&set);
struct sigaction act={};
act.sa_handler = sigint_proc;
act.sa_mask = set;//没有屏蔽任何信号
act.sa_flags = SA_NOMASK|SA_ONESHOT;
int ret = sigaction(2,&act,NULL);
if(ret == -1){
perror("sigaction");
return -1;
}
for(;;);
return 0;
}
十、sigqueue
#include <signal.h>
int sigqueue (pid_t pid, int sig,const union sigval value);
向pid进程发送sig信号,附加value值(整数或指针)。
成功返回0,失败返回-1。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
//sigq -sig pid data
int main(int argc,char *argv[]){
if(argc < 4){
printf("%s -sig pid data\n",argv[0]);
return -1;
}
printf("%d\n",getpid());
int sig = -atoi(argv[1]);
int pid = atoi(argv[2]);
int data = atoi(argv[3]);
union sigval val;
val.sival_int = data;
int ret = sigqueue(pid,sig,val);
if(ret == -1){
perror("sigqueue");
return -1;
}
return 0;
}
注意:sigqueue函数对不可靠信号不做排队,会丢失信号。
十一、计时器
1.系统为每个进程维护三个计时器
真实计时器:
程序运行的实际时间。
虚拟计时器:
程序运行在用户态所消耗的时间。
实用计时器:
程序运行在用户态和内核态所消耗的时间之和。
实际时间(真实计时器) = 用户时间(虚拟计时器) + 内核时间 + 睡眠时间
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <time.h>
#include <sys/time.h>
//看电影 打游戏 以一定的频率刷新
//定时任务 每隔一个时间间隔需要执行一次
//alarm
void sigalarm_proc(int sig){
system("clear");
time_t t = time(NULL);
struct tm *lt = localtime(&t);
printf("%2d:%2d:%2d\n",lt->tm_hour,lt->tm_min,lt->tm_sec);
//alarm(1);
}
int main(){
//定时器
if(signal(SIGALRM,sigalarm_proc)==SIG_ERR){
perror("signal");
return -1;
}
struct itimerval t = {{1,0},{3,0}};//每隔1秒发送一次 3秒之后发送第一个
int ret = setitimer(ITIMER_REAL,&t,NULL);
if(ret == -1){
perror("setitmer");
return -1;
}
for(;;);
return 0;
}