进程信号
- 信号量不是信号!
- 信号是一种中断机制,或者说是一种事件通知机制,本文讲述的是软件中断。
- 通过信号通知进程发生了某个事件,打断进程当前的操作,去处理事件。
- 一个信号对应一个事件,信号必须能够被识别。
1. Linux下信号种类
- 使用kill-l 命令进行查看,62种
- 1~31:非可靠信号,借鉴Unix而来,
- 33~64:可靠信号,扩充,
- 信号的生命周期:产生,注册,注销,处理
1.1 信号产生
- 硬件产生:Ctrl+c(中断当前操作), Ctrl+\, Ctrl+z,
- 软件产生:kill命令发送一个信号给进程;例如:9号信号(SIGKILL)强制杀死;
kill -sigid pid
- kill杀死进程的原理是给进程发送一个终止信号,进程处理信号的方式就是退出进程。
int kill(pid_t pid, int sig);
--给指定进程发生指定信号int raise(int fig);
--给调用进程发生指定信号void abort(void);
--引起异常进程终止
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
int main(int argc, char *argv[])
{
while(1)
{
printf("废寝忘食?\n");
sleep(1);
kill(getpid(), 9);//给指定进程发生指定信号
//raise(9);//给调用进程发生指定信号
//abort();//引起异常进程终止
}
}
unsigned int alarm(unsigned int seconds);
--设置一个定时器
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
void test(int no)
{
printf("该吃了\n");
alarm(3);
}
int main(int argc, char *argv[])
{
signal(SIGINT, sigcb);
alarm(3);
while(1)
{
printf("废寝忘食?\n");
sleep(1);
}
}
1.2 信号注册
- 让进程知道自己收到了哪个信号。
- 修改pcb中的未决信号集合位图,并添加信号信息节点
- pending:未决信号集合,没有被处理的信号集合,是一个位图,用于标记有哪些信号待处理。
- sigqueue:双向链表,用于添加信号信息节点,形态的节点有多少个,就表示有多少个相同的信号待处理。
- 非可靠:若信号没有注册,则注册,已经注册则什么都不做;
- 可靠:不管信号有没有注册,都会注册一次。
1.3 信号注销
- 删除信号痕迹(删除节点+修改位图)
- 非可靠信号:删除节点后,直接重置位图;
- 可靠信号:删除一个信息节点后,确定没有相同节点了才会重置位图。
1.4 信号处理
- 就是打断进程当前操作,然后执行信号的处理回调函数,执行完毕之后回到原来的主控流程继续运行
处理方式:
-
默认处理方式:执行默认的处理函数;
-
忽略处理方式:信号依然会注册只是处理方式变为空操作;
-
自定义处理方式:自己定义信号处理函数,修改信号的处理函数指针。
-
sighandler_t signal(int signum, sighandler_t handler);
-
signum:要修改的信号;
-
handler:传入新的处理方式;SIG_DFL/SIG_IGN/自定义函数。
-
typedef void (*sighandler_t)(int);
-
返回值:成功返回原来的处理方式;失败返回SIG_ERR。
使用signal自定义SIGINT信号的处理方式
- 自定义处理函数名称为“sigcb”, 在sigcb当中完成打印信号值
使用sigprocmask函数阻塞2号信号和40号信号
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
- 分别给进程发送5次2号信号和5次40号信号,观察结果
自定义处理方式的信号捕捉流程
程序运行:
- 当程序运行的都是我们自己写的代码和访问的都是自己的变量则程序运行在用户态;
- 若程序运行要访问内核空间或者说要完成内核中的功能就需要切换到内核态运行;
- 程序运行从用户态切换到内核态的方式:系统调用接口,中断,异常
- 可以使程序切换到内核态:
memcpy、fwrite、read、int a = 10/0;
- 默认和忽略在内核完成处理,自定义处理需返回用户态
1.5 阻塞
- 信号依然可以注册,只是暂时阻止信号被处理。
- 在pcb中还有一个信号集合–阻塞集合;哪个信号在这个集合中被标记,则表示这个信号要阻塞,收到了这个信号则暂时不去处理。
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
- how:操作类型–要对阻塞集合进行的操作;
- SIG_BLOCK:将set集合中的信号添加到阻塞集合;
block |= set
。 - SIG_UNBLOCK:从阻塞集合中移除set中的信号;
block &= ~block
。 - SIG_SETMASK:将set集合中的信号设置为阻塞集合;
block = set
。 - oldset :用于保存修改前阻塞集合中的数据,以便于能够还原。
- 修改指定信号的处理方式为自定义,能够感受到收到了某个信号;signal
- 阻塞所有信号;
sigprocmask,sigset_t;
- 让程序的运行停下来,向进程发送信号(非可靠、可靠);
getchar
- 让程序继续向下运行,解除阻塞,查看信号的处理结果;
sigprocmask
int sigemptyset(sigset_t *set);
清空set集合int sigfillset(sigset_t *set);
填充所有信号到set集合int sigaddset(sigset_t *set, int signum);
添加指定信号到set集合int sigdelset(sigset_t *set, int signum);
从set集合删除指定信号int sigismember(const sigset_t *set, int signum);
判断信号是否在集合中- 在所有信号中有两个信号比较特殊:
SIGKILL -9 / SIGSTOP -19
– 这两个信号不能被阻塞,不能被修改处理方式,不能被忽略 - 进程无法被杀死的情况:僵尸进程、信号被阻塞或者自定义忽略、进程使通知状态。
2. 信号的基本应用
2.1 SIGCHILD
- SIGCHILD :子进程退出之后给父进程发送的信号
· SIGCHLD信号的默认处理方式,就是什么都不做;之前使用waitpid避免出现僵尸进程。 - 自定义SIGCHLD信号的处理方式,在信号回调时调用waitpid。
- SIGCHLD信号是一个非可靠信号,有可能会丢失事件,
·while(waitpid(-1, NULL, WNOHANG) > 0;
` waitpid:返回值大于0表示有子进程退出;等于0表示没有子进程退出。 signal(SIGCHLD, SIG_IGN);
–显式的忽略处理.
2.2 SIGPIPE
- SIGPIPE:管道所有读端被关闭之后继续写入触发异常对应的信号
· SIGPIPE默认的处理方式式退出进程
· 若不想退出进程,则需要自定义处理