目录
信号在进程的学习中是一个非常好用的存在,它是软件层次上对中断机制的一种模拟,是异步通信方式,同时也可以用来检测用户空间到底发生了什么情况,然后系统知道后就可以做出相应的对策。
在Linux系统中,信号(Signal)是一种进程间通信(IPC)的方式,它用于通知进程发生了某个特定的事件。信号机制允许操作系统或一个进程向另一个进程发送异步通知,以此来控制进程的行为。信号是软中断,可以打断进程的正常流程,迫使其提前处理信号所代表的事件。以下是关于Linux信号的一些基本概念和要点:
信号的基本特征:
- 信号的命名:Linux中的信号通常以“SIG”前缀开始,例如SIGINT(中断,通常由Ctrl+C触发)、SIGTERM(终止进程)、SIGHUP(挂起)、SIGKILL(强制终止进程)等。每个信号都有一个对应的数字编号。
- 信号处理:进程可以有三种方式处理信号:
- 默认处理:大多数信号有默认行为,比如SIGINT会使进程终止。
- 忽略:进程可以选择忽略某些信号,但某些关键信号如SIGKILL和SIGSTOP不能被忽略。
- 自定义处理:通过信号处理函数(signal handler)来指定信号到达时执行的特定操作。
- 信号发送:进程可以通过
kill
系统调用向自己或其他进程发送信号。内核也会在特定情况下自动发送信号,比如用户操作(如按下Ctrl+C)或硬件异常。
- 信号阻塞与未决:进程可以通过
sigprocmask
等函数临时阻塞对某些信号的接收,直到它解除阻塞。同时,未决信号是指已发送但尚未被进程处理的信号,这些信号会被排队等待处理。
- 信号与系统调用的关系:信号的处理通常与当前执行的系统调用无关,信号处理可以中断正常的程序执行流程,执行完信号处理函数后,可以选择恢复原系统调用(如果支持)或直接返回到用户态。
实际应用场景:
- 用户中断:用户通过键盘(如Ctrl+C)发送SIGINT来中断一个正在运行的命令或进程。
- 程序终止:系统管理员或程序本身使用SIGTERM来请求进程正常退出。
- 程序重启:SIGHUP信号常用于通知进程重新加载配置文件或重启。
- 调试:SIGTRAP等信号用于调试目的,可以捕获并分析程序状态。
使用信号的注意事项:
- 信号处理函数应当简短且安全,避免在其中执行长时间或阻塞的操作。
- 某些信号(如SIGKILL和SIGSTOP)不能被捕获或忽略,以确保系统能够控制进程。
- 合理设计信号处理逻辑,避免信号处理时的竞态条件和死锁。
理解信号及其机制对于编写健壮的Linux应用程序至关重要,尤其是在需要处理外部事件或异常情况的场景下。
信号的种类:
SIGINT:结束进程,对应快捷方式ctrl+c
SIGQUIT:退出信号,对应快捷方式ctrl+\
SIGKILL:结束进程,不能被忽略不能被捕捉
SIGTERM:结束终端进程,kill 使用时不加数字默认是此信号
SIGCHLD:子进程状态改变时给父进程发的信号
SIGSTOP:暂停进程,不能被忽略不能被捕捉
SIGTSTP:暂停信号,对应快捷方式ctrl+z
SIGALRM:闹钟信号,alarm函数设置定时,当到设定的时间时,内核会向进程发送此信号结束进程。
信号的三大类型:
1)忽略信号:对信号不做任何处理,但是有两个信号不能忽略:即SIGKILL及SIGSTOP。
2)捕捉信号:定义信号处理函数,当信号发生时,执行相应的处理函数。
3)执行缺省操作:Linux对每种信号都规定了默认操作 。
这个程序是运用了信号的一个顺序的简单小程序,我们只需要把逻辑顺清,谁应该接收什么信号,做什么处理,忽略什么信号就可以写出,但是有一点要注意的是如何获取父进程和子进程的ID号。
getPPID:获取父进程ID号
getPID:获取子进程ID号
值得一提的是虽然子进程复制了父进程几乎所有的东西,但是他们的ID号是不同的
用信号的知识实现司机和售票员问题。
1)售票员捕捉SIGINT(代表开车)信号,向司机发送SIGUSR1信号,司机打印(let's gogogo)
2)售票员捕捉SIGQUIT(代表停车)信号,向司机发送SIGUSR2信号,司机打印(stop the bus)
3)司机捕捉SIGTSTP(代表到达终点站)信号,向售票员发送SIGUSR1信号,售票员打印(please get off the bus)
4)司机等待售票员下车,之后司机再下车。
源码:
#include <sys/types.h> #include <signal.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/wait.h> pid_t pid; void hanlder1(int sig) { if(sig == SIGINT) { kill(getppid(), SIGUSR1);// getppid() 是父进程的号 } if(sig == SIGQUIT) { kill(getppid(), SIGUSR2); } if(sig == SIGUSR1) { printf("get off the bus\n"); exit(0); } } void hanlder2(int sig) { if(sig == SIGUSR1) { printf("let's gogogo\n"); } if(sig == SIGUSR2) { printf("stop the bus\n"); } if(sig == SIGTSTP) { kill(pid,SIGUSR1);// pid 是子进程的号 wait(NULL); exit(0); } } int main(int argc, char const *argv[]) { pid = fork(); if (pid < 0) { perror("pid open error"); return -1; } else if (pid == 0) { while (1) { signal(SIGTSTP,SIG_IGN);// 忽略SIGTSTP signal(SIGINT, hanlder1);// 捕获SIGINT signal(SIGQUIT, hanlder1);//捕获SIGQUIT signal(SIGUSR1, hanlder1);//捕获SIGUSR1 pause(); } } else { while (1) { signal(SIGINT, SIG_IGN);// 忽略SIGINT signal(SIGQUIT, SIG_IGN);// 忽略SIGQUIT signal(SIGUSR1, hanlder2);//捕获SIGUSR1 signal(SIGUSR2, hanlder2);//捕获SIGUSR2 signal(SIGTSTP, hanlder2);//捕获STGTSTP pause(); } } }