进程信号:是一种软件中断,一种事件通知机制。通知进程发生了某个事件,打断进程当前操作,去处理这个事件。
种类:使用kill -l指令进行查看;62种信号
信号的生命周期:产生,注册,注销,处理。在生命周期外还会有阻塞
信号的产生:
(1)硬件产生:
ctrl+c:会使当前终端前台进程退出(SIGINT)
ctrl+\: (SIGQUIT)
ctrl+z : 把当前进程转到后台运行,使用’ fg ‘命令恢复。比如top -d1 然后ctrl+z ,到后台,然后fg,重新恢复
(2)系统调用
kill命令:杀死一个进程-给进程发送一个信号-默认是终止信号
kill -signum pid
int kill(pid_t pid, int sig)
pid:指定要将信号发送给哪个进程;
sig:指定要发送的信号
返回值:成功返回0;失败返回-1;
int raise(int sig);给调用进程自身发送指定信号
void abort(void):给进程发送一个SIGABRT信号
(3)软件产生
unsigned int alarm(unsigned int seconds);设置一个计时器
调用alarm函数设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发送
SIGALRM信号,该信号的默认动作是终止当前进程
下面这个程序的作用是一秒之内count不停的加加,1秒钟到了就被SIGALRM信号终止。
1 #include<iostream>
2 #include<signal.h>
3 #include<unistd.h>
4 using namespace std;
5 void handler(int signo)
6 {
7 cout<<"catch a signal"<<signo<<endl;
8 }
9 int main(int argc,char *argv[])
10 {
11 alarm(1);
12 int count = 0;
13 while(1)
14 {
15 cout<<count++<<endl;
16 }
34 return 0;
35 }
(4)硬件产生
硬件异常被硬件以某种方式被硬件检测(例如MMU/寄存器/运算器)到并通知内核,然后内核向当前进程发送适当的信号。
例如1/0 时CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号
(浮点型错误)发送给进程。再比如当前进程非法访问了内存地址,MMU会产
生异常,内核将这个异常解释为SIGSEGV信号(段错误)发送给进程。
信号在进程中注册:
在未决信号集合(pending位图)中标记信号+添加信号的信息节点
在pcb中有个未决信号集合—还没有被处理的信号的集合—位图
可靠信号的注册:不管信号是否已经被注册过,都会注册一次,添加一个信号节点。
非可靠信号的注册:如果信号已经被注册并且还未被处理,则什么都不做,否则将注册。
信号的注销:在pcb中删除信号信息节点,重置位图
非可靠信号:删除信息节点,直接位图重置
可靠信号:删除信息节点之后,确定没有相同节点才会重置位图
信号的处理:信号的递达—执行信号的处理回调函数
方式:默认,忽略,自定义捕捉
头文件:#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
signum:信号值; handler:信号要新指定的处理方式
handler:SIG_DFL-默认;SIG_IGN-忽略;自定义—typedef void(*sighandler_t)(int)
返回值:成功返回信号原来的处理方式;失败返回-1-SIG_ERR
信号的阻塞:
阻塞一个信号表示收到这个信号之后暂时不去处理,直到进程解除阻塞之后进行处理。
在pcb中有一个信号阻塞集合,在这个集合中标记哪个信号则表示阻塞哪个信号
block位图:用来记录信号是否被屏蔽或被阻塞
pending位图:记录进程是否收到特定信号。
handler:记录信号的处理方式
int sigprocmask(int how, sigset_t *set, sigset_t *oldset)
how:要对信号阻塞集合进行的操作类型
SIG_BLOCK:将set集合中的信号添加到阻塞集合中
SIG_UNBLOCK:从阻塞集合中移除set集合中的信号
SIG_SRTMASK:将set集合中的信号设置为阻塞集合的信号
oldset:用于保存修改前,阻塞集合中的信息,不需要保存时则置空
返回值:成功返回0;失败返回-1;
自定义修改两种信号(可靠,非可靠)signal
先阻塞一波信号 sigprocmask SIG_BLOCK
让程序运行暂停 getchar,暂停期间给程发送信号,让程序运行继续
解除信号阻塞 sigprocmask SIG_UNBLOCK
int sigemptyset(sigset_t *set); 清空set集合
int sigfillset(sigset_t *set); 将所有信号添加到set集合中
int sigaddset(sigset_t *set, int signum);将指定信号添加到集合中
int sigdelset(sigset_t *set, int signum);从集合中移除指定信号
int sigismember(const sigset_t *set,int signum) ;判断信号是否在set集合中
信号的捕捉:
递达的条件:信号是从内核态切换到用户态的时候进行相关检测(同时检测是否有新线程到来,是否进行进程切换)
陷入内核:用户态到内核态。
返回到用户:内核到用户态。
sigaction函数:
头文件:#include <signal.h>
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact)
具体使用方法如下:
#include<iostream>
#include<signal.h>
#include<unistd.h>
using namespace std;
void sigcb(int signo)
{
cout<<"signo:"<<signo<<endl;
}
int main()
{
struct sigaction act,oact;
act.sa_handler = sigcb;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);//处理一个信号时所要屏蔽的其他信号,不关心则清空。
sigaction(2,&act,&oact);
while(1);
sleep(5);
return 0;
}
测试结果如下:
2号信号被sigaction函数(系统调用)自定义为打印该信号的signo;
volatile关键字
作用:保存内存的可见性,被该关键字修饰的变量不允许被系统优化。
对该变量的任何操作,必须在真实的内存中进行操作,凡是被volatile修饰的
变量是不可覆盖的,在任何执行流当中,读取该数据时,必须从该数据真实
存储的位置进行读取,不能读取中间的临时缓存数据。
SIGCHILD:
父进程等待子进程的方式有三种:
1.wait(NULL);
2.waitpid(-1,&status,WNOHANG);
3.父进程可以自 定义SIGCHLD信号的处理函数,在信号处理函数中
调用wait清理子进程即可。
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void handler(int sig)
{
pid_t id;
while( (id = waitpid(-1, NULL, WNOHANG)) > 0){
printf("wait child success: %d\n", id);
}
printf("child is quit! %d\n", getpid());
}
int main()
{
signal(SIGCHLD, handler);
pid_t cid;
if((cid = fork()) == 0){//child
printf("child : %d\n", getpid());
sleep(3);
exit(1);
}
while(1){
printf("father proc is doing some thing!\n");
sleep(1);
}
return 0;
}