Linux--信号

来源:互联网 发布:吾生有涯,而知无涯全文 编辑:程序博客网 时间:2024/05/18 20:07

信号:

 

一.什么是信号:

用过Windows的都知道,当我们无法正常结束一个程序时,可以用任务管理器强制结束这个进程,但这其实是怎么实现的呢?同样的功能在Linux上是通过生成信号和捕获信号来实现的,运行中的进程捕获到这个信号然后作出一定的操作并最终被终止。

 

信号是Linux系统响应某些条件而产生的一个事件,接收到该信号的进程会相应地采取一些行动。通常信号是由一个错误产生的。但它们还可以作为进程间通信或修改行为的一种方式,明确地由一个进程发送给另一个进程。一个信号的产生叫生成,接收到一个信号叫捕获。

 

举一个例子:

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

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

3. 如果CPU当前正在执行这个进程的代码,则该进程的用户空间代码暂停执行,CPU从用户态切换到内核态处理硬件中断。

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

5. 当某个时刻要从内核返回到该进程的用户空间代码继续执行之前,首先处理PCB中记录的信号,发现有一个SIGINT信号待处理,而这个信号的默认处理动作是终止进程,所以直接终止进程而不再返回它的用户空间代码执行。

 

用kill –l命令查看系统定义的信号列表


二.产生信号的条件

1.用户在终端按下某些按键时,终端会发送信号给前台进程

         Ctrl-c: SIGINT, Ctrl-\ : SIGQUIT, Ctrl-z:SIGSTP

2.硬件异常

3.调用kill终止信号

 

三.信号的处理方式

1.忽略该信号

2.执行该信号的默认处理动作

3.提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这叫做捕捉一个信号(自定义动作)

信号捕捉函数:

程序可用使用signal函数来处理指定的信号,主要通过忽略和恢复其默认行为来工作。signal函数的原型如下:

#include <signal.h> void (*signal(int sig, void(*func)(int)))(int); 

signal是一个带有sig和func两个参数的函数,func是一个类型为void(*)(int)的函数指针。该函数返回一个与func相同类型的指针,指向先前指定信号处理函数的函数指针。准备捕获的信号的参数由sig给出,接收到的指定信号后要调用的函数由参数func给出。

SIG_IGN:忽略信号

SIG_DFL:恢复信号的默认行为

 

举一个例子:

void handler(int sig){  printf("geta sig:%d\n",sig);} int main(){     signal(2,handler);     signal(3,handler);     while(1){       printf("I am a proc\n");       sleep(1);     }}


结果:我们会发现当我们键入Ctrl-c以及Ctrl-\的时候他会捕捉信号并且执行自定义动作。其余的会执行默认动作。

 

四.信号处理

sigaction函数:

#include <signal.h> int sigaction(int sig, const structsigaction *act, struct sigaction *oact); 

sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回- 1。signo是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非空,则通过oact传出该信号原来的处理动作。act和oact指向sigaction结构体。

sigset_t sa_mask   指定一个信号集,在调用sa_handler所指向的信号处理函数之前,该信号集将被加入到进程的信号屏蔽字中。信号屏蔽字是指当前被阻塞的一组信号,它们不能被当前进程接收到。

int sa_flags  信号处理修改器;

sa_mask 的值通常是通过使用信号集函数来设置的。

sa_flags 通常可以取以下的值

 

五.发送信号

上面说到的函数都是一些进程接收到一个信号之后怎么对这个信号作出反应,即信号的处理的问题,有没有什么函数可以向一个进程主动地发出一个信号呢?我们可以通过两个函数kill和alarm来发送一个信号。

 

1、kill函数

进程可以通过kill函数向包括它本身在内的其他进程发送一个信号,如果程序没有发送这个信号的权限,对kill函数的调用就将失败,而失败的常见原因是目标进程由另一个用户所拥有。想一想也是容易明白的,你总不能控制别人的程序吧,当然超级用户root,这种上帝般的存在就除外了。

 

kill函数的原型为:

#include <sys/types.h> #include <signal.h> int kill(pid_t pid, int sig); 

它的作用把信号sig发送给进程号为pid的进程,成功时返回0。

 

kill调用失败返回-1,调用失败通常有三大原因:

1、给定的信号无效(errno =EINVAL)

2、发送权限不够( errno =EPERM )

3、目标进程不存在( errno =ESRCH )

 

2、alarm函数

这个函数跟它的名字一样,给我们提供了一个闹钟的功能,进程可以调用alarm函数在经过预定时间后向发送一个SIGALRM信号。

alarm函数的型如下:

#include <unistd.h> unsigned int alarm(unsigned intseconds); 

alarm函数用来在seconds秒之后安排发送一个SIGALRM信号,如果seconds为0,将取消所有已设置的闹钟请求。alarm函数的返回值是以前设置的闹钟时间的余留秒数,如果返回失败返回-1。


我们用上述所学实现一个自己的mysleep函数

#include<stdio.h>#include<signal.h> void handler(int signo){ } int mysleep(int timeout){       struct sigaction act,oact;       sigset_t newmask,oldmask,suspmask;       act.sa_handler = handler;       sigemptyset(&act.sa_mask);       act.sa_flags = 0;       sigaction(SIGALRM,&act,&oact);        sigemptyset(&newmask);       sigaddset(&newmask,SIGALRM);       sigprocmask(SIG_BLOCK,&newmask,&oldmask);        alarm(timeout);        suspmask = oldmask;       sigdelset(&suspmask,SIGALRM);       sigsuspend(&suspmask);       int ret = alarm(0); sigaction(SIGALRM,&oact,NULL);       sigprocmask(SIG_SETMASK,&oldmask,NULL);       return ret;} int main(){       while(1)       {         mysleep(1);         printf("I am waking proc\n");       }} 

每休眠一秒调用一次printf函数。

 

六.可重入函数与线程安全

 

可重入函数:如果一个函数只访问自己的局部变量或参数,则称为可重入函数。

不可重入函数:函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入,insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为不可重入函数。

 

如果一个函数符合以下条件之一则是不可重入的:

1.调用了malloc或free,因为malloc也是用全局链表来管理堆的。

2.调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。

3.可重入体内使用了静态的数据结构。

 

想一个问题,当进程接收到一个信号时,转到你关联的函数中执行,但是在执行的时候,进程又接收到同一个信号或另一个信号,又要执行相关联的函数时,程序会怎么执行?

也就是说,信号处理函数可以在其执行期间被中断并被再次调用。当返回到第一次调用时,它能否继续正确操作是很关键的。这不仅仅是递归的问题,而是可重入的(即可以完全地进入和再次执行)的问题。而反观Linux,其内核在同一时期负责处理多个设备的中断服务例程就需要可重入的,因为优先级更高的中断可能会在同一段代码的执行期间“插入”进来。这就导致了线程不安全问题。

简言之,就是说,我们的信号处理函数要是可重入的,即离开后可再次安全地进入和再次执行,要使信号处理函数是可重入的,则在信息处理函数中不能调用不可重入的函数。

 

可重入函数与线程安全的区别与联系:

(1)线程安全是在多个线程情况下引发的,而可重入函数可以在只有一个线程的情况下来说。

(2)线程安全不一定是可重入的,而可重入函数则一定是线程安全的。

(3)如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的。

(4)如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生死锁,因此是不可重入的。

(5)线程安全函数能够使不同的线程访问同一块地址空间,而可重入函数要求不同的执行流对数据的操作互不影响使结果是相同的。

1 0
原创粉丝点击