APUE学习笔记——第十章 信号

1、信号

信号是软件中断,很多比较重要的应用程序都需要处理信号,信号提供了一种处理异步事件的方法,例如:终端用户键入中断键,则会通过信号机制
停止一个程序。
每个信号都有一个名字,这些名字都是以SIG开头,在头文件<signal.h>中,信号都被定义为正整数。
很多条件可以产生信号:
(1)当用户按某些终端键时,引发终端产生的信号。按Ctrl+C键,通常产生SIGINT中断信号
(2)硬件异常产生信号。例如除数为0、无效内存引用等。
(3)进程调用kill函数将信号发送给另外一个进程或进程组。
(4)用户用kill命令将信号发送给其他进程。
(5)检测到某种软件条件已经发送,并将其通知有关进程时候产生信号。
内核在信号出现时候处理方式有:(1)忽略此信号,(2)铺捉信号,(3)执行系统默认动作。
SIGKILL和SIGSTOP这两个信号不能被忽略。

信号类型:
SIGABRT:由调用abort函数产生,进程非正常退出
SIGALRM:用alarm函数设置的timer超时或setitimer函数设置的interval timer超时
SIGBUS:某种特定的硬件异常,通常由内存访问引起
SIGCANCEL:由Solaris Thread Library内部使用,通常不会使用
SIGCHLD:(子)进程Terminate或Stop的时候,SIGCHLD会发送给它的父进程。缺省情况下该Signal会被忽略
SIGCONT:当被stop的进程恢复运行的时候,自动发送
SIGEMT:和实现相关的硬件异常
SIGFPE:数学相关的异常,如被0除,浮点溢出
SIGFREEZE:Solaris专用,Hiberate或者Suspended时候发送
SIGHUP:发送给具有Terminal的Controlling Process,当terminal被disconnect时候发送
SIGILL:非法指令异常
SIGINFO:BSD signal。由Status Key产生,通常是CTRL+T。发送给所有Foreground Group的进程
SIGINT:由Interrupt Key产生,通常是CTRL+C或者DELETE。发送给所有ForeGround Group的进程
SIGIO:异步IO事件
SIGIOT:实现相关的硬件异常,一般对应SIGABRT
SIGKILL:无法处理和忽略。中止某个进程
SIGLWP:由Solaris Thread Libray内部使用
SIGPIPE:在reader中止之后写Pipe的时候发送
SIGPOLL:当某个事件发送给Pollable Device的时候发送
SIGPROF:Setitimer指定的Profiling Interval Timer所产生
SIGPWR:和系统相关。和UPS相关。
SIGQUIT:输入Quit Key的时候(CTRL+/)发送给所有Foreground Group的进程
SIGSEGV:非法内存访问
SIGSTKFLT:Linux专用,数学协处理器的栈异常
SIGSTOP:中止进程。无法处理和忽略。
SIGSYS:非法系统调用
SIGTERM:请求中止进程,kill命令缺省发送
SIGTHAW:Solaris专用,从Suspend恢复时候发送
SIGTRAP:实现相关的硬件异常。一般是调试异常
SIGTSTP:Suspend Key,一般是Ctrl+Z。发送给所有Foreground Group的进程
SIGTTIN:当Background Group的进程尝试读取Terminal的时候发送
SIGTTOU:当Background Group的进程尝试写Terminal的时候发送
SIGURG:当out-of-band data接收的时候可能发送
SIGUSR1:用户自定义signal 1
SIGUSR2:用户自定义signal 2
SIGVTALRM:setitimer函数设置的Virtual Interval Timer超时的时候
SIGWAITING:Solaris Thread Library内部实现专用
SIGWINCH:当Terminal的窗口大小改变的时候,发送给Foreground Group的所有进程
SIGXCPU:当CPU时间限制超时的时候
SIGXFSZ:进程超过文件大小限制
SIGXRES:Solaris专用,进程超过资源限制的时候发送

2、signal函数

#include <signal.h>
void (*signal(int signo, void (*func)(int)))(int);
若成功则返回信号之前的处理配置,出错返回SIG_ERR
signo参数是上述的信号名,func的值是常量SIG_IGN、常量SIG_DFL或当接到此信号后要调用的函数的地址,如果是SIG_IGN,则向内核表示忽略此信号;
如果是SIG_DFL则表示接到此信号后的动作是系统默认动作;当指定函数地址时,则在信号发生时调用该函数,这种处理为“捕捉”该信号。此函数为信号处理函数。


在#include<signal.h>中有下列申明
#define SIG_ERR (void(*)())-1
#define SIG_DFL (void(*)())0
#define SIG_IGN (void(*)())1

一个简单的信号处理例子:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>
#include <error.h>

void sig_chld(int signo){
    printf("child process\n");
}

int main(){
    pid_t pid;
    int status;
    signal(SIGCHLD,sig_chld);
    pid = fork();
    if(pid == 0){
        printf("Child pid: %d\n",getpid());
    }else if(pid > 0){
        waitpid(pid,&status,0);
        printf("Parent pid: %d, status: %d\n",getpid(),status);
    }
    exit(0);
}
3、kill和raise函数

kill函数将信号发送给进程或进程组,raise函数则允许进程向自身发送信号。
#include <signal.h>
int kill(pid_t pid, int signo)
int raise(int signo) //成功返回0,出错返回-1

raise(signo)等价于kill(getpid(),signo)

4、alarm和pause函数

使用alarm函数可以设置一个计时器,在将来某个指定的时间该计时器会超时,当计时器超时时,产生SIGALRM信号。如果不忽略或者不捕获此信号,则其默认动作
是终止调用该alarm函数的进程。
#include <unistd.h>
unsigned int alarm(unsigned int seconds)//返回值:0或以前设置的闹钟时间的余留秒数
经过了指定的seconds秒后会产生信号SIGALRM,需要了解的是:经过了指定秒数后,信号由内核产生,由于进程调度的延迟,所以进程得到控制从而能够处理该信号还需要一些时间。
每个进程只能有一个闹钟时钟,如果在调用alarm时,以前已为该进程设置过闹钟时钟,而且它还没有超时,则将该闹钟时钟的余留值作为本次alarm函数调用的值返回。以前登记的闹钟时钟则被新值代替。如果有以前为进程登记的尚未超过的闹钟时钟,而且本次调用的seconds值为0,则取消以前的闹钟时钟,其余留值仍作为alarm函数的返回值。
puase函数使调用进程挂起直到捕捉到一个信号。只有执行了一个信号处理程序并从其返回时,pause才返回-1,并将errno设置为EINTR。
int pause(void)

利用alarm和pause可以实现简单的sleep函数

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

void sig_alarm(int signo){
    puts("alarm");
}

int main(){
    int a,b,c;
    a = alarm(10);
    if(signal(SIGALRM,sig_alarm) == SIG_ERR){
        printf("error");
    }
    b = alarm(5);
    pause();
    printf("%d, %d, %d\n",a,b,alarm(0));
    exit(0);
}
输出结果为:

alarm(5秒后输出)
0, 10, 0
此函数缺陷很多,具体见书本。

5、信号集和相关函数

通过信号集(signal set)表示多个信号,这样方便操作多个信号。信号集的数据类型为sigset_t,信号集操作函数如下: 
#include <signal.h> 
int sigemptyset(sigset_t *set);  //初始化由set指向的信号集,清除其中所有信号
int sigfillset(sigset_t *set);    //初始化由set指向的信号集,使其包括所有信号
int sigaddset(sigset_t *set, int signum);   //添加一个指定的信号
int sigdelset(sigset_t *set, int signum);   //删除一个指定信号
int sigismember(const sigset_t *set, int signum);   //判断signum是否在set所指向的信号集中
一个进程的信号屏蔽字规定了当前阻塞而不能递送给该进程的信号集,调用sigprocmask函数可以检测或更改其信号屏蔽字
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
若oldset是非空指针,那么进程的当前信号屏蔽字通过oldset返回
若set是非空指针,那么参数how指示如何修改当前信号屏蔽字
how的取值为:SIG_BLOCK、SIG_UNBLOCK、SIG_SETMASK
SIG_BLOCK: 该进程新的信号屏蔽字是其当前信号屏蔽字和set指向信号集的并集。set包含了我们希望阻塞的附加信号。
SIG_UNBLOCK: 该进程新的信号屏蔽字是其当前信号屏蔽字和set指向信号集的补集的交集。set包含了我们希望解除阻塞的信号。
SIG_SERMASK:该进程新的信号屏蔽字将被set指向的信号集值代替
int sigpending(sigset_t *set)
sigpending函数返回信号集,其中的各个信号对于调用进程是阻塞的而不能传递,简单的说就是获取被阻塞的信号集

信号屏蔽例程:

#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

void sig_int(int signo){
    if(signo == SIGINT) puts("catch SIGINT");
    else puts("catch SIGQUIT");
}

int main(){

    sigset_t newmask,pendmask,oldmask;
    signal(SIGINT,sig_int);
    signal(SIGQUIT,sig_int);
    sigemptyset(&newmask);
    sigaddset(&newmask,SIGINT);
	//将newmask信号集阻塞,并且将其保存在oldmask中
    sigprocmask(SIG_BLOCK,&newmask,&oldmask);
    sleep(5);
	//获取阻塞的信号集
    sigpending(&pendmask);
    if(sigismember(&pendmask,SIGINT))
        printf("SIGINT is pending\n");
    if(sigismember(&pendmask,SIGQUIT))
        printf("SIGQUIT is pending\n");
	//恢复原来被阻塞的信号集
    sigprocmask(SIG_SETMASK,&oldmask,NULL);
    sleep(4);
    exit(0);
}
运行样例:

^C
^C
^\catch SIGQUIT
SIGINT is pending
catch SIGINT
^Ccatch SIGINT

6、sigaction函数

sigaction函数的功能是检查或修改与指定信号相关联的处理动作或同时执行这两种操作。
可以用sigaction函数实现signal函数。函数原型及结构参数如下:
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

struct sigaction {
      void     (*sa_handler)(int);/*addr of signal handler or SIG_IGN, or SIG_DFL*/
      sigset_t   sa_mask;
     int        sa_flags;
       void     (*sa_sigaction)(int, siginfo_t *, void *);
      void     (*sa_restorer)(void);
};


7、sigsuspend函数

该函数在一个原子操作中先恢复信号屏蔽字,然后使进程休眠。函数原型如下:
#include <signal.h>
int sigsuspend(const sigset_t *sigmask); //返回-1,并将errno设置为EINTR
将信号屏蔽字设置为mask指向的值,在捕捉到一个信号或发生一个会终止该进程的信号之前,该进程被挂起。
如果捕捉到一个信号而且从该信号程序返回,则sigsuspend返回,并且该进程的信号屏蔽字设置为调用sigsuspend之前的值。
该函数可以保护不希望由信号中断的代码临界区。

除了sigmask中的信号值,其他任何信号值都能使它终止挂起状态。

发布了101 篇原创文章 · 获赞 26 · 访问量 46万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览