信号的概念
信号就是一个中断 : 它通知一个事件的发生(打断当前的操作(选择合适的时机),去处理信号)
而进程的信号就是一个软件(应用程序)的中断
信号的种类
在Linux下,使用 kill -l 命令查看信号的种类
- 一共有62种信号
- 1~31号信号是Linux继承Unix而来的31种事件信号,当系统发生对应的时间的时候,就会发送对应的信号
- 34~64号信号是没有指定具体事件的,当我们用户需要用到自定义事件的时候,我们可以去对这些信号进行定义
注意 :
1~31号信号为非可靠事件, 意味着当有多个相同的信号到来时, 只会对一个信号进行处理,其他的信号都会被丢弃 , 同时也称为非实时信号 .
34~64号信号为可靠信号, 意味着当有多个相同的信号到来时, 每一个都会被处理 , 同时也称为实时信号
信号的生命周期
信号的产生
信号的产生方式有软件产生于硬件产生
硬件产生 : 指直接在终端中输入一些指令产生信号
例如: ctrl+c ctrl+z ctrl+\ 等
软件产生 : 指在程序中产生的信号
- int kill(pid_t pid, int sig);
在指定的pid进程发送指定的sig信号 - int raise(int sig);
给调用进程发送sig信号 - void abort(void);
给调用进程发送SIGABRT - unsigned int alarm(unsigned int seconds);
在指定的seconds秒之后向调用进程发送一个SIGALRM信号----定时器
若seconds为0, 则作为为取消上一个定时器
信号在进程中的注册
信号在继承中的注册是通过位图来注册的, 这个位图在内核中所对应的就是一个 struct sigset_t
它的大小是128位,用来对信号是否到来做标记.
# define _SIGSET_NWORDS (1024/(8*sizeof(unsigned long int)))
typedef struct{
unsigned long int _val[_SIGSET_NWORDS];
}_sigset_t;
注意 :
- 在PCB中用来描述信号注册的结构不仅只有一个sigset_t结构体, 还有一个sigqueue链表, 他的作用是在信号到来的时候组织一个对应信号的结点,然后添加到这个链表中
- 我们前面说了信号的种类有可靠信号与非可靠信号,而非可靠信号就是因为, 该信号的到来只能在位于中进行一次标记,当有其他相同的信号到来时,将什么也不做, (每一个信号只添加一个节点)
- 而可靠信号不管位图是否已经标记, 都会地每一个道到来的信号组织一个结点添加到列表中
信号在进程中的注销
信号的注销指的就是在PCB中删除信号的sig’queue结点, 修改位图
可靠信号的注销
删除节点,判断链表中是否还有相同的信号, 如果没有,则将位图置0.否则,位图依然为1.
非可靠信号的注销
删除节点, 修改位图为0.
信号在进程中的处理
- 默认处理方式
默认处理方式是有操作系统已将规定好了的处理法方式 例如:SIGQUIT信号的默认处理为使当前进程退出 - 忽略处理方式
僵尸进程的产生原因为父进程先于子进程退出, 父进程没有关心子进程的退出返回原因, 而子进程在退出时会向父进程发送一个SIGCHLD信号,而父进程对SIGCHLD的默认处理方式就是忽略处理, 因此,父进程没有及时获取待子进程的退出原因, 所以才造成了僵尸进程 - 自定义处理方式
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
自定义处理方式为一个signal接口,在这个接口中有两个参数, 第一个signum为要自定义处理方式的信号, 而第二个参数是一个函数指针, 在这个函数指针内部就是,这个信号定义的处理方式,在这个参数中有两个宏 SIG_IGN(忽略处理) 与SIG_DFL(默认处理)
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
//signum : 信号的编号---指定要处理的信号
//act : 自定义的信号处理方式
//oldact : 信号的原有处理方式
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
在PCB中有一个pending位图, 这个位图标记的就是未处理的信号, 而在PCB中也同样拥有一个action结构体数组, 它里面保存着着各个信号的原始处理方式, 而未决信号是通过pending位图, 来拿到action中的未处理信号的处理方式.
在sigaction结构体中, sa_handler就是信号的处理方式, 我们的sigaction接口就是将,新的sa_gandler进行修改, sa_mask是为了避免在进行信号处理的时候, 其他的信号造成影响,因为设定sa_mask, 添加进sa_mask中的信号都将别忽略.
自定义信号的捕捉流程
默认信号的处理方式是在内核中完成的, 因此当我们的程序被信号中断时, 我们的程序就有用户态切换到内核态, 在内核态中完成信号的处理
而自定义信号的流程是:
一开始程序运行在我们的用户态中, 当发生系统调用 / 中断 / 异常的时候, 我们的程序会重用户态转到内核态, 完成内核功能从内核态返回的时候回判断是否有信号等待处理, 如果有, 并且这个信号处理方式为自定义处理方式, 则会有内核态转向用户态, 执行sigcb()函数, 在执行完信号处理之后, 会再一次返回内核态, 之后在判断是否有未处理的信号, 直到没有信号未处理, 才会返回用户态继续执行主流程.
信号的阻塞 : 阻止信号被递达
递达的意思就是信号的处理, 而阻塞就是不处理信号,信号依然可以注册.
信号的阻塞也是在PCB中添加标志, 在PCB有一个blocked位图,其中标志的就是用户需要阻塞的信号, 当有信号在pending位图中被注册时, 会将blocked取反之后与pending位图进行与操作, 之后留下俩的就是需要进行处理的信号.
//设置信号阻塞集合
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
//how : 即将处理的动作
//SIG_BLOCK : 将set集合中的信号添加到阻塞集合当中
//SIG_UNBLOCK : 将set集合当中的数据在阻塞集合当中移除
//SIG_SETMASK : 使用set集合集合替换阻塞集合
//清空set集合当中的数据
int sigemptyset(sigset_t *set);
//将所有信号添加到set集合中
int sigfillset(sigset_t *set);
//将指定的信号添加到set集合中
int sigaddset(sigset_t *set, int signum);
注意: 在所有的信号中SIGKILL(9号), SIGSTOP(19号) 这两个信号无法被自定义处理, 无法被阻塞, 无法被忽略
//获取未决信号的集合
int sigpending(sigset_t *set);
//判断一个信号是否在集合中
int sigismember(const sigset_t *set, int signum);