您的位置:首页 > 运维架构 > Linux

Linux-信号机制详解(一)

2016-07-17 00:29 423 查看
      之前有写过SystemV的信号量机制,现在是信号。这里的信号和前面的信号量是不同的。这里的信号是进程给操作系统或进程的某种信息,让操作系统或者其他进程做出某种反应。

      信号是进程间通信机制中唯一的异步通信机制,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。进程之间可以互相通过系统调用kill发送软中断信号。内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件。信号机制除了基本通知功能外,还可以传递附加信息。

在这里举几个常见的信号的例子:

1. 用户输入命令,在Shell下启动一个前台进程。

2. 用户按下Ctrl-C,这个键盘输入产生一个硬件中断。

3. 如果CPU当前正在执行这个进程的代码,则该进程的用户空间代码暂停执行,CPU从用

户态 切换到内核态处理硬件中断。

4. 终端驱动程序将Ctrl-C解释成一个SIGINT信号,记在该进程的PCB中(也可以说发送了

一 个SIGINT信号给该进程)。

5. 当某个时刻要从内核返回到该进程的用户空间代码继续执行之前,需先处理PCB中记

录的信号,发现有一个SIGINT信号待处理,用这个信号的默认处理动作是终止进程,所

以直接终止进程而不再返回它的用户空间代码执行。

    前台进程在运行过程中用户随时可能按下Ctrl-C键产生一个信号,也就是说该进 程的用户空间代码执行到任何地值都有可能收到SIGINT信号而终止,所以信号相对于进程的控制流 程来说是异步(Asynchronous)的。

一。信号种类:

Linux的signal.h中定义了很多信号,使用命令kill -l  可以查看系统定义的信号列表:



可以发现其实没有32,33号信号。1-31号信号叫做普通信号,34-64号信号叫做实时信号。

通过指令      kill  信号序号  进程号   可以向一个进程发送信号

二。信号产生的条件:

1. 用户在终端按下某些键时,终端驱动程序会发送信号给前台进程,例如Ctrl-C产生SIGINT信号,Ctrl-\产生SIGQUIT信号,Ctrl-Z产生SIGTSTP信号(可使前台进程停止)

      SIGINT的默认处理动作是终止进程,SIGQUIT的默认处理动作是终止进程并且Core Dump,所以我们可以写一个死循环,让后通过对这个进程发送SIGINT(ctrl-C)SIGQUIT(ctrl-\)信号来终止这进程。





可以看到进程被终止了,ctrl-C好像不行。。。。

解释一下CoreDump:

当一个进程要异常终止时,可以选择把进程的用户空间内存数据全部 保存到磁盘上,文件名通常是core,这叫做Core Dump。进程异常终止通常是因为有Bug,例如非法内存访问致段错误,事后可以用调试器检查core文件以查清错误原因,这叫做Post-mortem Debug。这个进程允许产生多个的core问件取决于进程的Resource Limit(这个信息保存 在PCB中)。默认是不允许产生core问件的,因为core文件中可能包含用户密码等敏感信息,不安全。在开发调试阶段可以用ulimit命令改变这个限制,允许产生core文件。

2. 硬件异常产生信号,这些条件由硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除以0的令,CPU的运算单元会产生异常,内核将这个异常解释 为SIGFPE信号发送给进程。再如当前进程访问了非法内存地址,,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程。

       首先在后台执行死循环程序,然后用kill命令给它发SIGSEGV信号。通过在可执行文件后面加一个&,将进程放到后台运行:



可以看到进程被杀死。

kill命令是调⽤kill函数实现的。 kill函数可以给⼀个指定的进程发送指定的信号。

raise函数可 以给当前进程发送指定的信号(自己给自己发信号)。

<span style="font-family:Microsoft YaHei;font-size:14px;">#include <signal.h>
int kill(pid_t pid, int signo);
int raise(int signo);</span>

这两个函数都是成功返回0,错误返回-1。

abort函数使当前进程接收到SIGABRT信号时异常终止。
<span style="font-family:Microsoft YaHei;font-size:14px;">#include <stdlib.h>
void abort(void);</span>

就像exit函数一样,abort函数总是会成功的,所以没有返回值。

3. 一个进程调用kill(2)函数可以发送信号给另一个进程。 可以用kill(1)命令发送信号给某个进程,kill(1)命令也是调用kill(2)函数实现的,如果不明确指定信号则发送SIGTERM信号,该信号的默认处理动作是终止进程。 当内核检测到某种软件条件发生时也可以通过信号通知进程,例如闹钟超时产生SIGALRM信号,向读端已关闭的管道写数据时产生SIGPIPE信号。 如果不想按默认动作处理信号,用户程序可以调用sigaction(2)函数告诉内核如何处理某种信号

     可选的处理方式有三种:

          (一)忽略此信号。

           (二)执行该信号的默认处理动作。

            (三)提供一个自定义的信号处理函数要求内核在处理该信号时切换到用户态执行这个处理函数,这种方 式称为捕捉(Catch)一个信号。

例:

SIGPIPE是一种由软件条件产生的信号
<span style="font-family:Microsoft YaHei;font-size:14px;">#include <unistd.h>
unsigned int alarm(unsigned int seconds);</span>

      调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动作是终止当前进程。 这个函数的返回值是0或者是以前设定的闹钟时间还余下 的秒数。打个比方,某人要睡觉,设定闹钟为30分钟之后响,20分钟后被吵醒了,还想多睡 一会儿,于是重新设定闹钟为15分钟之后响,“以前设定的闹钟时间还余下的时间”就是10分钟。如果seconds值为0,表⽰取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数。 



      这个程序的作用是1秒钟之内不停地数数,1秒钟到了就被SIGALRM信号终止。

三。阻塞信号

       实际执⾏信号的处理动作称为信号递达(Delivery),信号从产⽣到递达之间的状态,称为信号未决(Pending)。进程可以选择阻塞(Block )某个信号。被阻塞的信号产⽣时将保持在未决状态,直到进程解除对此信号的阻塞,才 执⾏递达的动作。
注意,阻塞和忽略是不同的只要信号被阻塞就不会递达,⽽忽略是在递达之后 可选的⼀种处理动作。信号在内核中的表⽰可以看作是这样的:


结合处理信号的三种方式:

1. SIGHUP信号未阻塞也未产⽣过,当它递达时执⾏默认处理动作。

2. SIGINT信号产⽣过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没 有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。

3. SIGQUIT信号未产⽣过,⼀旦产⽣SIGQUIT信号将被阻塞,它的处理动作是⽤户⾃定义函数sighandler。

常规信号在递达之前产⽣多次只计⼀次,⽽实时信号在递达之前产⽣多次可以依次放在⼀个队列⾥。因此,未决和阻塞标志可以⽤相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表⽰每个信号的“有效”或“⽆效”状态,在阻塞信号集中“有效”和“⽆效”的含义是该信号是否被阻塞,⽽在未决信号集中“有效”和“⽆效”的含义是该信号是否处于未决状态。

(一)信号集操作函数

调⽤以下函数来操作sigset_t变量

#include <signal.h>
int sigemptyset(sigset_t *set);

int sigfillset(sigset_t *set);

int sigaddset(sigset_t *set, int signo);

int sigdelset(sigset_t *set, int signo);

int sigismember(const sigset_t *set, int signo);(二)信号屏蔽字
调⽤函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集)。
#include <signal.h>

int sigprocmask(int how, const sigset_t *set, sigset_t *oset);

返回值:若成功则为0,若出错则为-1

如果oset是⾮空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是⾮空指针,则 更改进程的信号屏蔽字,参数how指⽰如何更改。如果oset和set都是⾮空指针,则先将原来的信号 屏蔽字备份到oset⾥,然后根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。

如果调⽤sigprocmask解除了对当前若⼲个未决信号的阻塞,则在sigprocmask返回前,⾄少将其中 ⼀个信号递达

(三)未决信号集
#include <signal.h>
int sigpending(sigset_t *set);
sigpending读取当前进程的未决信号集,通过set参数传出。调⽤成功则返回0,出错则返回-1。 

  
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  信号 Linux