天天看点

第13章 信号及信号处理

一、信号的基础。

  1、信号的基本概念。

@可以用kill -l来查看系统中的信号,这些信号的定义在signal.h文件中。

@信号的编号没有0信号,是从1开始的。

2、产生信号。

  @产生进程有5种方式。

  用户按下按键;硬件异常产生信号(被0除);调用kill(2)函数可以发送信号给另一个进程;kill(1)命令发送信号;内核检测到某种软件条件发生。如alarm

@当就绪或者是阻塞的进程,遇到信号都会被激活来处理信号。

3、处理信号。

@有三种处理方法。忽略。注册一个处理函数。系统默认执行。

4、常用信号的使用方法。

@Ctrl +c向当前进程发送一个SIGINT信号。

@Ctrl +/ 使程序结束运行,而产生core文件。可以利用gdb来调试core文件。

二、信号的影响。

1、重入。

    一个函数符合以下条件之一都是不可重入的。

@使用全局变量。例如全局变量或静态变量。

@调用了动态方法得到内存。

@使用了标准的IO库。标准的IO库的很多实现都以不可重入的方式使用全局数据结构。

总结:使用全局的东西的函数都不行。称为非纯代码。

2、原子操作。

如果多个流程访问一个全局资源,那么这个全局要定义为:

 如: volatile sig_atomic_t a=0;

3、中断系统调用。

@使用sigactions可以设置信号处理程序返回时是否重新启动被中断的系统调用。

三、信号处理函数。

1、设置信号处理函数。

@使用signal函数来处理函数加载。

  #include<signal.h>

    void (*signal (int signo,void(*fuc)(int))) (int);

  第一个参数,表示要加载处理的信号的编号。例如:SIGKILL其本质是整数。

第二个参数,表示一个函数的指针。也可以是以下三个之一。

SIG_IGN:表示忽略该信号。(STGKILL SIGSTOP信号不够忽略。)

SIG_DFL表示使用默认的信号处理方式。

用定义好的函数指针。

一般用法:

void signal_handle(int signo)

{

    switch(signo){

   case SIG :

。。。。。

}

@有两个信号没有特殊的意义的:SIGUSR1,SIGUSR2

2、发送信号。

@使用kill向进程或进程组发送信号。

#include<signal.h>

int kill(pid_t pid, int signo);

pid有4种不同的写法。

大于0:信号发给pid 的进程。

等于0:发送给进程组ID和该进程相同的进程。

小于0:发送给进程组内进程ID为pid的进程。

等于-1:信号发送给所有的进程。

注意事项:

@系统进程不能接收信号。

进程之间发送信号需要权限的检查。

(1)uid=uid:可以,同一用户的进程之间的信号通信。

 (2)uid=eid:接收者得到了发送者的授权,暂时成为与发送者同一个用户的进程。

 (3)eid=uid:发送者得到接收者的暂时成为与接收者同一个用户的进程。

   (4)eid=eid:发送者的接收者都暂时属于同一用户,等同地第一种情况。

根用户可以向任何用户发送信号。如果成功则返回0,不成功返回-1,

@可以向自己发送信号。如kill(getpid(),signo);

3、向进程本身发送信号。

int raise(int signo);

@可以使进程退出。但是只是没有做善后的工作。如关闭文件,冲洗流。

4、设置Linux定时器。

@#include<unistd.h>

uinsigned int alarm(unsigned int seconds);

@当系统时间超过该时间后,就会调用alarm函数的进程发送一个SIGALRM的函数的进程发送一个信号SIGALRM的信号。终止调用alarm函数的进程。

@当之前没有设置过定时器,或者设置过的定时器已过时,则返回0;

@当之前设置过,还没有过时,则返回剩余的秒数。

@当alarm的参数为0时,取消一个定时器,返回剩余的秒数。

5、定时等待IO。

 @当在等待io时可以用alarm用设置定时阻塞。

 6、挂起进程。

@进程有三个状态:运行;就绪和阻塞。

@当一个进程 在就绪态,但是我想他进入阻塞态时。可以调用。pause函数。

#include<unistd.h>

int pause(void);

直到有一个信号到来,并且执行了一个信号处理程序从其返回后,pause函数返回-1,表示执行正确。挂起后不再响应SIGTERM等很多信号。唯一能够保证用kill.

7、进程休眠。

@pause使进程无时间限制的挂起,若想在一定时间恢复运行则使用sleep.

    unsigned int sleep(unsigned int nsec);

 返回值有两种:当挂起的时间超过了指定时间,返回0;当时间还没有到就被信号唤醒,还返回挂起来的时间。

四、信号集与屏蔽信号。

1、信号集和信号处理函数。

@进程能够捕捉并且处理的信号集合成为信号集。

@#include<signal.h>

 int sigemptyset(sigset_t *set):清空set,设置为0;

int sigfillset(sigset_t *set);设置为1;

int sigaddset(sigset_t* set, int signo);指定信号编号设置为1

int sigdelset(sigset_t * set ,int signo);指定信号编号设置为0

@使用sigismember函数测某个信号所对应的位是否被设置,

int sigismember(sigset_t *set,int signo);

被设置返回1,未设置返回0,失败则返回-1.

@信号的编号是由1开始的,但是信号集是从0开始的。signo的值应该是信号编码减去1;

2、屏蔽信号。

@信号的屏蔽就是阻塞一个信号 。信号屏蔽字的本质同信号集一样,是一个位向量。信号编码对应的位为1表示屏蔽该信号,对应的位为0表示处理该信号。

@Linux环境sigprocmask函数设置信号屏蔽字,其函数原型如下:

#include <signal.h>

int sigprocask(int how,cosnt sigset_t *restrict set, sigset_t* restrict oset)

第二个参数set是一个信号集。

第三个参数oset被设置为原来的信号屏蔽字。

第一个参数how有三种取值情况。

SIG_BLOCK:set包含了希望添加到当前屏蔽信号集的信号。相当于mask=mask | set;

SIG_UNBLOCK:set包含了希望从当前屏蔽信号中解除阻塞的信号,相当于mask=mask & ~ set;

SIG_SETMASK:设置当前屏蔽信号集为set所指向的值,相当于mask=set; 

如果set的值是NULL,则无论how是何值都不会更改信号屏蔽字。这种方法用于得到当前进程的信号屏蔽字。

如: sigset_t oset;

             sigprocmask(0,NULL,&oset);//信号屏蔽字保存在oset中。如oset为NULL,则表示忽略原来信号屏蔽字。成功就返回0,否则返回-1;

@注意SIGKILL和SIGSTOP这两个信号不能被屏蔽。

3、处理未决信号。

@当屏蔽一个信号,但是进程在某处还是接收到此信号。这种信号叫做未决信号。

int sigpending (sigset_t *set);

参数表示为当前进程所有未决的信号 。

4、高级信号注册函数。

  int sigaction(int signo,const struct sigaction *restrict act,struct sigaction *restict oact);

第一人参数表示注册 的信号编号。

后面的参数表示act指向的结构设置信号处理函数,将原来的值保存在oact中。

struct sigation{

      void (*sa_handler) (int);//信号处理函数

      sigset_t sa_mask;屏蔽信号集

    int sa_flags;信号选项

   void (*sa_sigaction) (int siginfo_t *,void *);替代sa_hanler的信号处理函数。

继续阅读