Linux 信号的处理、含义、发送和定时信号
文章目录
1. 信号的基本概念
- 软中断
- 目的:
- 让进程知道发生了某种事件
- 根据该事件执行相应的动作,即执行它自己代码中的信号处理程序 。
- 来源:内核
- 请求方:
- 进程:通过系统调用 kill 给另一个进程发送信号。进程之间可通过信号通信。
- 内核:进程 执行出错 时,内核向进程发送一个信号,例如非法段访问、浮点数溢出等,也可通知进程特定事件的发生。
- 用户:通过输入 Ctrl-C 、 Ctrl-\ 等请求内核产生信号
- 信号的状态
- delivery:当进程对信号 采取动作(执行信号处理函数或忽略)时称为递送。
- pending: 信号产生和递送之间的时间间隔内称信号是未决的
- block: 进程可指定对某个信号采用递送阻塞。若此时信号处理为默认或者捕捉的,该信号就会处于未决的状态。
2. 信号的分类
-
根据来源
-
同步信号:由进程的某个操作产生的信号称为 同步信号 ,例如被零除。
-
异步信号:用户击键这样的进程 的事件引起的信号称为异步信号 ,该信号产生的事件进程是不可控。
-
-
根据处理情况:
- 不可靠信号
- 信号值小于 SIGRTMIN。
- 当同时有多个信号产生时,无法及时处理,造成信号的丢失
- 早期 Unix 系统中的信号机制比较简单和原始,把那些建立在早期机制上的信号叫做不可靠信号
- 收到信号的速度超过进程处理的速度的时候,不可靠信号将多余的丢弃掉
- 可靠信号:
- 在SIGRTMIN-SIGRTMAX之间
- 可靠信号将来不及处理的信号就会排入进程的队列
- 不可靠信号
-
是否支持排队:
- 实时信号:实时信号都支持排队,都是可靠信号。后 32 种为非实时信号
- 非实时信号:非实时信号都不支持排队,都是不可靠信号。前 32 种为实时信号。
3. 常见信号
信号名称 | 信号说明 | 默认处理 |
---|---|---|
SIGABRT | 调用abort 时产生该信号,程序异常结束 | 进程终止并且产生core 文件 |
SIGALRM | 由alarm 或者 setitimer 设置的 定时器 到期 | 进程终止 |
SIGBUS | 总线错误,地址没对齐等,取决于具体硬件 | 进程终止并产生core 文件 |
SIGCHLD | 子进程停止或者终止时, 父进程收到 该信号 | 忽略该信号 |
SIGCONT | 让停止的进程继续执行 | 进程终止并且产生core 文件 |
SIGFPE | 算术运算异常,除 0 等 | 进程终止 |
SIGHUP | 进程的控制终端关闭时产生这个信号 | 进程终止并且产生core 文件 |
SIGILL | 代码中有非法指 | 进程终止 |
SIGINT | 终端输入了CTRL+c 信号 下面用 ^c 表示 | 进程终止 |
SIGIO | 异步I/O ,跟 SIGPOLL 一样 | 进程终止 |
SIGIOT | 执行I/O 时产生 硬件错 | 进程终止并且产生core 文件 |
SIGKILL | 该信号用户不能去捕捉和忽略它 | 进程终止 |
4. 信号处理
基本信号处理 signal()
目标 | 简单的信号处理 |
---|---|
头文件 | signal.h |
函数原型 | int signal(int signum, void (*action)(int)); |
参数 | signum 需响应的信号 action 如何响应(特殊值:SIG_IGN 忽略信号;SIG_DFL 恢复为默认处理) |
返回值 | -1 遇到错误 prevaction 返回之前的处理函数指针 |
-
不可靠信号:这是早期不可靠信号处理机制造成的。当执行完一次信号处理函数之后,系统的信号处理就恢复为默认处理,如果想让信号处理函数继续有效,必须重新设置。
void sigHandler(int signalNum) { printf("The sign no is:%d n", signalNum signal(SIGINT, sigHandler); //重新设置 }
-
面临的问题:
-
信号处理函数正在执行,没结束时,又产生一个同类型的信号,这时该怎么处理;
-
信号处理函数正在执行,没结束时,又发生了一个不同类型的信号,这时该怎么处理;
-
进程执行一个 阻塞系统调用如 read() 时,发生了一个信号 ,这时是让该阻塞系统调用返回错误再接着进入信号处理函数,还是先跳转到信号处理函数,等信号处理完毕后,系统调用再返回。
- 例子
#include <unistd.h> #include <stdio.h> #include <sys/types.h> #include <signal.h> #define INPUTLEN 20 char input[INPUTLEN]; void inthandler(int s) { printf("I have Received signal %d .. waiting\n", s); sleep(2); printf("I am leaving inthandler \n"); signal(SIGINT, inthandler); } void quithandler(int s) { printf("I have Received signal %d .. waiting\n", s); sleep(3); printf("I am leaving quithandler \n"); signal(SIGQUIT, quithandler); } int main(){ signal(SIGINT, inthandler); signal(SIGQUIT, quithandler); do { printf("Please input a message\n"); int nchars = read(0, input, (INPUTLEN - 1)); if (nchars == -1) { perror("read returned an error"); } else { input[nchars] = '\0'; printf("You have inputed: %s\n", input); } }while(strncmp(input, "quit", 4) != 0); }
- 结果
-
信号处理函数正在执行,没结束时,又产生一个同类型的信号
-
信号处理函数正在执行,没结束时,又发生了一个不同类型的信号
-
进程执行一个 阻塞系统调用如 read() 时,发生了一个信号
-
指定信号处理 signaction()
目标 | 指定信号的处理函数 |
---|---|
头文件 | signal.h |
函数原型 | int signaction(int signum, const struct sigaction *action, struct sigaction *prevaction); |
参数 | signum 需处理的信号 action 指向描述操作的结构的指针 prevaction 指向描述被替换操作的结构指针 |
返回值 | -1 遇到错误 0 成功 |
-
sigation 结构体
struct sigaction{ void (*sa_handler)(); void (*sa_sigaction)(int,siginfo_t *,void *); sigset_t sa_mask; int sa_flags; }
-
sa_flags的标志
标记 含义 SA_RESETHAND 当处理函数被调用时重置,即捕鼠器模式 SA_NODEFER 处理信号时关闭“信号自动阻塞”(sa_mask无效),因此 允许递归调用 信号处理函数 SA_RESTART 当阻塞于系统调时收到信号,如果本标志置位,则信号处理函数返回后,系统调用返回失败而需要重新开始,否则系统调用成功返回。主要用于低速设备相关的系统调用。 SA_SIGINFO 指明使用sa_sigaction 的处理函数值。如果它未设置,则使用旧处理机制, 若设置 ,则 传给处理函数 的包括 信号编号、信号产生的原因和条件等信息
进程的阻塞信号 sigprocmask()
目标 | 修改当前的信号挡板 |
---|---|
头文件 | signal.h |
函数原型 | int sigprocmask (int how, const sigset_t *sigs,sigset_t prev; |
参数 | how 如何修改信号挡板:SIG_BLOCK, SIG_UNBLOCK,SIG_SET sigs 指向使用的信号列表的指针 prev 指向之前的信号挡板列表的指针或者为 null |
返回值 | -1 遇到错误 0 成功 |
-
例子
#include <stdio.h> #include <signal.h> void sig_handler(int signum, siginfo_t *info, void *myact) { if (signum == SIGINT) printf("GOT a common signal.\n"); else printf("GOT a real time signal\n"); } int main() { struct sigaction act; sigset_t newmask, oldmask; int rc; sigemptyset(&newmask); sigaddset(&newmask, SIGINT); sigaddset(&newmask, SIGRTMIN); sigprocmask(SIG_BLOCK, &newmask, &oldmask); act.sa_sigaction = sig_handler; act.sa_flags = SA_SIGINFO; if (sigaction(SIGINT, &act, NULL) < 0) printf("install signal error\n"); if (sigaction(SIGRTMIN, &act, NULL) < 0) printf("install signal error\n"); printf("pid = %d\n", getpid()); sleep(60); sigprocmask(SIG_SETMASK, &oldmask, NULL); return 0; }
-
结果
5. 信号发送
-
kill
目标 向一个进程发送信号 头文件 signal.h
函数原型 int kill (pid_t pid , int);
参数 pid 目标进程
sig 要被发送的信号返回值 -1 遇到错误
0 成功 -
raise
目标 向自身进程发送信号 头文件 signal.h
函数原型 int raise(intsig);
参数 sig 要被发送的信号 返回值 -1 遇到错误
0 成功 -
sigqueue
目标 向进程发送信号 头文件 signal.h
函数原型 int sigqueue(pid_t pid,int sig,const union sigval value)
参数 pid 目标进程的 pid
sig 被发送信号
参数
value 为一整型与指针类型的联合体:
unionsigval{ int sival_int; void* sival_ptr;}返回值 -1 遇到错误
0 成功
6. 父子进程的信号处理
-
父进程创建子进程时, 子进程继承了父进程信号处理方式, 直到子进程调用 exec 函数
-
子进程调用 exec 函数后, exec 将父进程中设置为捕捉的信号变为默认处理方式 。
-
防止僵尸进程产生:
- 子进程在退出程序时,会向父进程发送 SIGCHLD 信号
- 父进程在该信号的处理函数中调用 wait 或者 waitpid 获取子进程的退出状态
- 默认情况下,父进程是忽略该信号的。
-
例子
#include <stdio.h> #include <signal.h> #include <unistd.h> #include <stdlib.h> void intsig_handler(int signum, siginfo_t *siginfo, void *empty) { printf("int_handler, my pid=%d\n", getpid()); } int main() { int pid; struct sigaction act; act.sa_sigaction = intsig_handler; act.sa_flags = SA_SIGINFO; if (sigaction(SIGINT, &act, NULL) < 0) printf("install signal error\n"); printf("The parent pid = %d\n", getpid()); pid = fork(); if (pid < 0) { perror("fork failed.\n"); exit(0); } printf("The return fork = %d\n", pid); if (pid == 0) execlp("ls", "ls", NULL); // if delete else // if delete while(1); }
-
结果
-
若删除上述代码倒数三、四行后
-
7. 系统定时信号
alarm()
- unsigned int alarm(unsigned int seconds);
- 函数说明 : 用来设置 信号 SIGALRM 在经过参数 seconds 指定的秒数后传送给目前的进程。如果参数 seconds 为 0 ,则之前设置的闹钟会被取消,并将剩下的时间返回。
- 返回值 : 返回之前闹钟的剩余秒数 ,如果之前未设闹钟则返回 0
- alarm() 执行后,进程将继续执行,在后期 (alarm 以后)的执行过程中将会在 seconds 秒后收到信号 SIGALRM 并执行其处理函数。
sleep()
- unsigned int sleep(unsigned int seconds);
- sleep() 是在库函数中实现的,它是通过调用 alarm() 来设定报警时间, 调用sigsuspend将进程挂起
usleep()
- unsigned int usleep (unsigned int useconds
- usleep 的时间单位为 us ,肯定不是由 alarm 实现的,但都是 linux用的,而 window 下不能用,因为都是 sleep 和 usleep 都是在unistd.h 下定义的。
- 可能被其他信号打断。
- return :若进程暂停到参数 seconds 所指定的时间,成功则返回 0;若有信号中断则返回剩余微秒数 。
setitimer()
int setitimer (int which, const struct itimerval *value, struct itimerval ovalue));
struct itimerval {
struct timeval it_interval ; // next value
struct timeval it_value ; // current value
};
struct timeval {
long tv_sec ; //seconds 时间的秒数
long tv_usec ; //micro seconds 时间的微秒数
}
-
which 可选项
- ITIMER_REAL : 以 系统真实的时间 来计算,它送出 SIGALRM 信号。
- ITIMER_VIRTUAL : 以该进程在 用户态下花费的时间 来计算,它送出SIGVTALRM 信号。
- ITIMER_PROF : 以该进程 在用户态下和内核态下所费的时间来计算,它送出 SIGPROF 信号。
-
setitimer 调用成功返回 0 ,否则返回 1
-
例子
#include <stdio.h> #include <unistd.h> #include <signal.h> #include <sys/time.h> int i = 0; void timeChange(int ms, struct timeval *ptVal) { ptVal->tv_sec = ms / 1000; ptVal->tv_usec = (ms % 1000) * 1000; } void alarmsign_handler(int SignNo){ printf("%d seconds\n", ++i); } int main(){ struct itimerval tval; signal(SIGALRM, alarmsign_handler); timeChange(1, &(tval.it_value)); timeChange(1000, &(tval.it_interval)); setitimer(ITIMER_REAL, &tval, NULL); while(getchar() != '#'); return 0; }
-
结果