信号的基本概念
** 为啥要有信号**
信号概念:Linux系统响应某些状况而产生的事件,进程在接受到信号后采取相应的动作。
说白了,操作系统要管理进程
进程在受到某些信号的影响后,也会做出相应的动作,而信号有些是人为的,有些是系统为的。
一、信号产生的几种方法
方法 | 示例 |
---|---|
终端按键 产生 | Ctrl-C,Ctrl-Z等等组合键 |
硬件异常产生 | 进程执行除0,野指针等导致CPU异常 |
kill命令发送给某个进程 | kill -9 进程pid |
软件条件 | pipe读端不读了,并把它的文件描述符关了,而写端依然在写;alarm(3)闹钟 |
系统调用函数 | kill(pid,signo),raise(signo),abort()等系统调用 |
信号产生了,就要处理,咋处理呢?啥时候处理呢,合适的时候,啥是合适的时候呢?后边说
二、信号处理方法
1.忽略此信号
2.执行该信号的默认处理动作(大多数是终止该进程)
3.提供一个信号处理函数,要求内核在处理该信号的时候切换到用户态执行这个处理函数,这就叫捕捉(catch)信号
一些细节
1.SIGQUIT的默认处理动作是终止进程并且Core Dump,生成core文件;
2.系统默认不允许产生core文件,core文件中可能包含一些敏感信息。
3.开发调试阶段可用命令 ulimit 命令改变这个限制,生成core文件,方便时候调试
在内核中(非实时信号)1~31
1.信号都有两个标志位,分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作;
2.当一个信号产生时,内核在进程控制块中(PCB)中设置其pending 表,直到信号递达才会清除该标志位;
3.若该信号正在被阻塞,则暂缓递达,直到阻塞解除后,才能递达;
3.当信号被递达之前产生多次只记一次,而实时信号在递达之前产生多次会依次放在一个队列里。
捕捉信号
如果信号的处理动作是用户自定义的函数,在信号递达的时候就调用这个函数,这就叫捕捉信号
其流程如下
注意:当信号的处理函数被注册时,内核检测到有信号递达,所以内核返回用户态后不是恢复main()函数的上下文,而是执行自定义处理函数,所以sighandler和main函数使用不同的堆栈空间,因此它们不存在调用和被调用的关系,是两个独立的控制流程。
关键点:
1.从哪来到哪去;
2.当sighandler被调用时,内核将当前信号加入进程的信号屏蔽字,当处理函数返回时自动恢复为原来的信号屏蔽字,这就可以保证在处理某个信号的时候,如果这个信号再次产生,那么它会被阻塞到当前处理结束为止。
三、信号的操作
int kill (pid_t pid, int signo);
给指定进程发信号,成功返回0,失败返回-1
int raise(int signo);
给当前进程发信号,成功返回0,失败返回-1
#include <stdlib.h> void abort();
是当前进程收到信号而终止,没有返回值
1.信号集(sigset_t)
由于block和pending只记录有或没有,实质上是一个位图;因此可以用相同的数据类型来存储 ,所以 有了 sigset_t 类型,称为信号集。而阻塞信号集也称为信号屏蔽字。
2.信号集操作函数
#include <signal.h>
int sigemptyset(sigset_t* set);
让set指向的信号集所有bit位清零;
int sigfillset(sigset_t* set);
让set指向的信号集所有bit位置1;
int sigaddset(sigset_t* set, int signo);
在该信号集中添加一个signo信号
int sigdelset(sigset_t* set, int signo);
在该信号集中删除一个signo信号
这四个函数都是成功返回0,出错返回-1
int sigismember(const sigset_t* set, int signo);
判断set执向的信号集中是否包含signo信号,若包含,返回1,否则返回0,出错返回-1
int sigprocmask(int how, const sigset_t* set, sigset_t* oset);
作用:读取或更改进程中的信号屏蔽字
参数:
how:要做什么; 用法如下,set:指向新的信号屏蔽字,oset:输出型参数,当前的信号屏蔽字通过该参数传出;
返回值:成功返回0,失败返回-1
how参数用法:
SIG_BLOCK | set包含了我们希望添加到当前信号屏蔽字的信号 |
---|---|
SIG_UNBLOCK | set包含了我们希望从当前信号屏蔽字中解除阻塞的信号 |
SIG_SETMASK | 设置当前信号屏蔽字为set所指向的值 |
注意:如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmask返回前,至少要将其中一个信号递达
sigpending()函数
int sigpending(sigset_t* set);
读取当前进程的未决信号集,通过set参数传出,成功返回0,出错返回-1
sigaction()函数
int sigaction(int signo, const struct sigaction* act, struct sigaction* oact);
作用:读取和修改与指定信号相关联的处理动作,成功返回0,出错返回-1
参数:
signo是指定信号的编号。
若act指针非空,则根据act修改信号的处理动作。
若oact为非空,则通过oact传出该信号原来的处理动作。
struct sigaction {
void (*sa_handler)(int); //函数指针,自定义处理动作
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask; //设置信号屏蔽字
int sa_flags; //一般缺省为0
void (*sa_restorer)(void);
};
注意:sa_handler赋值为常数SIGIGN传给sigaction表示忽略信号,赋值为SIG_DFL表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉该信号,显然,这是一个回调函数。其不是被main函数调用,而是被系统调用。
3.等待函数
#include <unistd.h> int pause(void);
作用:pause函数是调用进程挂起知道有信号递达;
如果信号的处理动作是终止进程,则进程终止,pause没机会返回;
如果信号的处理动作是忽略,则进程继续挂起,pause不返回;
如果信号的处理动作是捕捉,则调用处理函数之后,pause返回-1,所以它只有出错返回
#include <signal.h> int sigsuspend(const sigset_t* sigmask);
作用:具有和pause一样的挂起等待功能。具体是临时解除对某个信号的屏蔽,然后挂起等待,当它返回时,进程的信号屏蔽字才会恢复。
mysleep实现
代码如下:
#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;
void handler(int signo) //啥也不做
{
;
}
unsigned int mysleep(size_t sec)
{
sigset_t newmask,oldmask,suspmask;
struct sigaction act, oact; //创建两个结构体
act.sa_handler = handler; //定义处理方式 sa_handler = void (*handler)(int)
sigemptyset(&act.sa_mask); //清空信号集
sigaction(SIGALRM,&act,&oact); //收到 SIGALRM 信号,执行handler动作 ,oact传出该信号原来的动作
//屏蔽14号
sigemptyset(&newmask);
sigaddset(&newmask,SIGALRM);
sigprocmask(SIG_BLOCK,&newmask,&oldmask);
alarm(sec); //设置闹钟
suspmask = oldmask;
//取消屏蔽
sigdelset(&suspmask,SIGALRM);
//挂起等待14号
sigsuspend(&suspmask); //原子操作,临时解除屏蔽,并挂起
unsigned int ret = alarm(0);
// pause(); //挂起
sigaction(SIGALRM,&oact,NULL); //恢复默认信号处理动作(以后收到14号信号,则默认退出)
sigprocmask(SIG_SETMASK,&oldmask,NULL); //恢复以前的信号屏蔽字
// cout << "alarm" << endl;
return ret;
}
int main()
{
while (1){
mysleep(1);
cout << "i am back!!! "<< getpid() << endl;
}
return 0;
}