Chinaunix首页 | 论坛 | 博客
  • 博客访问: 300926
  • 博文数量: 33
  • 博客积分: 132
  • 博客等级: 入伍新兵
  • 技术积分: 1002
  • 用 户 组: 普通用户
  • 注册时间: 2012-09-16 22:24
个人简介

学习计算机科学与技术专业,喜欢计算机,喜欢linux,喜欢编程

文章存档

2014年(7)

2013年(12)

2012年(14)

分类: LINUX

2013-08-03 22:10:45


  1. #include <stdio.h>
  2. #include <signal.h>                                                                                                                         
  3. void haha()
  4. {
  5.     printf("你受骗了!就知道你不会终止。\n");
  6. }                                                                                                                                                                     
  7. int main()
  8. {
  9.     printf("你知道怎样终止这个程序吗?\n");            
  10.     while(1)
  11.     {
  12.         signal(SIGINT, haha)||signal(SIGQUIT, haha)||signal(SIGTSTP, haha);
  13.     }
  14.     return 0;
  15. }

在这个程序中我仅仅屏蔽改变了 SIGINT 、SIGQUIT 、SIGTSTP这三个信号的响应效果。


其中 SIGINT 是键盘中的 ctrl + c 组合键的信号。

SIGQUIT 是键盘中的 ctrl + \ 组合键的信号。

SIGTSTP 是键盘中的 ctrl + z 组合键的信号。

signal 函数是当接收第一个参数的信号时,执行第二个参数所指函数的代码。     

所以当你发送上面这三个信号给这个程序时,都会执行 haha 这个函数。


信号的分发和处理是在内核态进行
,但是从上面的例子程序可以看出,信号的处理函数可能是在用户态。在这种情况下,它们是怎样进行交互的呢?信号又是怎样控制进程的呢?


当内核给进程发送一个信号时,其实是在进程所在的进程表项的信号域设置对应于该信号的位。(这个进程表项就是 PCB ——进程控制块,linux的进程控制块为一个由 task_struct 这个结构体所定义的数据结构,这个结构体在 /include/linux/sched.h 中。)


这里需要说明的是,如果信号发送给一个正在睡眠的进程,那么要看进程进入睡眠的优先级。


如果进程睡眠在可被中断的优先级上,则唤醒进程;否则仅设置进程表中信号域相应的位,而不唤醒进程。这个很重要,因为进程是通过上下文来检测是否收到信号的。具体地讲:一个进程在即将从内核态返回到用户态时;或者在进程要进入或离开一个适当的地调度优先级睡眠状态时,它会检测自己的 PCB 表。这样进程就知道是否有给它的信号了。


当然正如上面说的当一个进程在内核态下运行时,软中断信号并不立即起作用,要等到将返回用户态时才会遍历 PCB表,才会处理信号。    


补充说一下用户态和内核态:当一个进程执行系统调用进入内核代码中执行时,我们就称这个进程处在内核运行态(简称内核态),每个进程都有自己的内核栈。当进程在执行用户自己的代码时,则称其所处的是用户态,当然进程也有自己的用户栈。


现在我们来分析一下上面的代码:                    

 

首先在调用 signal 系统调用时该进程处在内核态,而我们的 haha 函数则处在用户态空间,那么内核态就在用户空间为 haha 函数开辟一个临时的用户栈,首先将 signal 的返回地址进行压栈,然后在进行相应的进栈操作后执行 haha 函数,由于信号是异步处理的,所以临时开辟的用户栈空间和进程本身的用户栈空间并不冲突。           


内核处理一个进程收到的软中断信号是在该进程的上下文中,因此,进程必须处于运行状态。如果进程收到一个要捕捉的信号,那么进程从内核态返回用户态时执行用户定义的函数,内核是在用户栈上创建一个新的层,该层中将返回地址的值设置成用户定义的处理函数的地址,这样进程从内核返回弹出栈顶时就返回到用户定义的函数处,从函数返回再弹出栈顶时,才返回原先进入内核的地方,接着原来的地方继续运行。这样做的原因是用户定义的处理函数不能且不允许在内核态下执行(如果用户定义的函数在内核态下运行的话,用户就可以获得任何权限)。


在信号处理方法中有几点需要特别注意:(BSD系统除外


  • 在一些系统中,当一个进程处理完中断信号返回用户态之前,内核会清除用户区中设定的对该信号处理函数的地址,也即是当下一次进程对该信号的处理方法又改为默认值,这是为了消除在下一次信号到来之前再次使用 signal 系统调用会使得进程在调用 signal 之前又得到该信号而导致退出。                      
  • 如果要捕捉的信号发生于进程正在一个系统调用中时,并且该进程睡眠在可中断的优先级上(若系统调用未睡眠而是在运行,那么必须等该系统调用运行完毕后再处理信号),此时该信号的发生使进程通过 longjmp 函数跳出当前的睡眠状态,返回用户态执行该信号的处理函数。当从我们的信号处理函数返回时,进程就像从系统调用返回一样,但返回了一个错误标志用来指出该次系统调用曾经被中断过。
  • 若进程睡眠在可中断的优先级上,则当它处理一个要忽略的信号时,该进程被唤醒,但不进行 longjmp 跳转,通常情况下是继续处于睡眠状态,此时用户感觉不到进程曾经被唤醒过。因此当我们使用 pause、sleep 等函数从挂起状态返回的信号必须要有信号处理函数。
  • 内核终止子进程的信号处理方法和其他信号有所不同。当进程退出时,内核会向父进程发一个信号(SIGCLD,SIGCLD信号的作用是唤醒一个睡眠在可被中断优先级上的进程),缺省情况下,父进程忽略该信号,但如果父进程需要获取子进程终止的状态就应该用 signal 函数为该信号(SIGCLD)设置信号处理函数,在信号处理函数中调用 wait 函数进行等待。因为我们不知道子进程什么时候终止,但在信号处理函数中调用 wait 函数我们就能保证在子进程终止前,wait 函数会使父进程挂起休眠。        


阅读(2096) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~