linux c编程:信号(一)

信号是软件中断,很多比较重要的应用程序都需要处理信号。并且信号提供了一种处理异步事件的方法。如终端用户键入中断键,会通过信号机制停止一个程序,或及早终止管道中的下一个程序

很多条件都可以产生信号,比如用户键入某些终端键,CTRL+C或者delete,进程调用kill(2)函数可将任意信号发送给另一个进程或进程组硬件检测到异常产生的信号,软件某种条件导致的异常

使用kill -l就可以看有多少种信号类型

kill -L

 1) SIGHUP  2) SIGINT  3) SIGQUIT  4) SIGILL  5) SIGTRAP

 6) SIGABRT  7) SIGBUS  8) SIGFPE  9) SIGKILL 10) SIGUSR1

11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM

16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP

21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ

26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR

31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3

38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8

43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13

48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12

53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7

58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2

63) SIGRTMAX-1 64) SIGRTMAX

从上面的结果会发现一个规律,前32种信号会有各种不同的名称,后32种会以“SIGRTMIN”或者“SIGRTMAX”开头,前者是从unix继承下来的信号,称为不可靠信号(也称为非实时信号),后者为了解决不可靠信号的问题进行了更改和扩充的信号形成了可靠信号(也称为实时信号)

如果想要了解可靠与不可靠信号,需要了解信号的生命周期:

一个完整的信号周期可以分为三个重要阶段,三个重要阶段有四个重要事件刻画的:信号产生,信号在进程中注册,信号在进程中注销,执行信号处理函数。

相邻的两个事件的时间间隔构成了生命周期的一个阶段,这里的信号处理有多种方式,一般由内核完成,也可以由用户进程完成

可靠信号与不可靠信号的区别:

不可靠信号如果发现信号已经在进程中注册,就会忽略该信号,因此若前一个信号还没有注销又产生了新的信号就是导致信号丢失

可靠信号发送给一个进程时,不管该信号是否已经在进程中注册,都会被再注册一次,因此信号不会丢失,所有可靠信号都支持排队,所有不可靠信号都不支持排队。

这里信号的产生,注册,注销等是指信号的内部的实现机制,而不是调用信号的函数实现,所以信号注册与否,与后面讲到的发送信号函数(kill)以及信号安装函数(signal())无关只与信号值有关

下表是这些信号的说明:

Signal

Description

SIGABRT

由调用abort函数产生,进程非正常退出

SIGALRM

alarm函数设置的timer超时或setitimer函数设置的interval timer超时

SIGBUS

某种特定的硬件异常,通常由内存访问引起

SIGCANCEL

Solaris Thread Library内部使用,通常不会使用

SIGCHLD

进程TerminateStop的时候,SIGCHLD会发送给它的父进程。缺省情况下该Signal会被忽略

SIGCONT

当被stop的进程恢复运行的时候,自动发送

SIGEMT

和实现相关的硬件异常

SIGFPE

数学相关的异常,如被0除,浮点溢出,等等

SIGFREEZE

Solaris专用,Hiberate或者Suspended时候发送

SIGHUP

发送给具有TerminalControlling Process,当terminaldisconnect时候发送

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专用,进程超过资源限制的时候发送

在某个信号出现时,可以告诉内核按下列3种方式进行处理

忽略信号:除了SIGKILLSIGSTOP。其他信号都可以忽略。这两种信号不能被忽略的原因是它们向内核和超级用户提供了使进程终止或停止的可靠方法。

捕捉信号:通知内核在某种信号发生的时候,调用一个用户函数。比如前面的进程函数中,如果捕捉到SIGCHLD信号,则表示一个子进程已经终止,所以此信号的捕捉函数可以调用waitpid以取得该子进程的进程ID以及它的终止状态

执行系统默认动作。对于大多数信号的系统默认动作就是终止该进程。

下面来看下信号的具体用法,一般有三种方式进行操作:

1 signal(SIGINT,SIG_ING); SIGINT是信号,SIG_ING代表忽略SIGINT信号

下面是一个死循环的函数,按下CTRL+C是没有反应的,只有按下CTRL+来结束

void signal_func(){

    signal(SIGINT,SIG_IGN);

    for(;;);

}

2 signal(SIGINT,SIG_DFL) SIG_DFL表示接到此信号后的动作是系统默认动作

void signal_func(){

    signal(SIGINT,SIG_DFL);

    for(;;);

}

3 void ( *signal( int sig, void (* handler)( int )))( int );

这是一个函数指针handler所指向的函数是一个不带任何参数并且返回值为int的一个函数.

signal是一个函数它返回一个函数指针后者所指向的函数接受一个整型参数 且没有返回值仔细看是不是siganal( int signo, void (*handler)(int) )的第2个参数了,对了,其实他所返回的就是 signal的第2个信号处理函数,指向信号处理函数,就可以执行函数了( signal内部时, signal把信号做为参数传递给handler信号处理函数,接着 signal函数返回指针并且又指向信号处理函数就开始执行它)

使用方法如下:

typedef void (*signal_handler)(int);

void signal_handler_fun(int signal_num){

    printf("Catch the signal %d ",signal_num);

}

void signal_func(){

    signal_handler p_signal=signal_handler_fun;

    signal(SIGINT,p_signal);

    for(;;);

}

当检测到CTRL+C的时候,则会跳转去执行signal_handler_fun函数。

发送信号的函数主要有kill(),raise(),alarm(),pause()

1)kill()raise()

kill()函数和熟知的kill系统命令一样,可以发送信号给信号和进程组(实际上kill系统命令只是kill函数的一个用户接口),需要注意的是他不仅可以终止进程(发送SIGKILL信号),也可以向进程发送其他信号。

kill函数不同的是raise()函数允许进程向自身发送信号。

#include<signal.h>

#include<sys/types.h>

int kill(pid_t pid,int sig)

pid >0 将该信号发送给进程IDPID的进程

pid == 0 将该信号发送给与发送进程属于同一进程组的所有进程。

pid < 0 信号发送给进程组号为-pid的进程

pid == -1 信号发给所有的进程表中的进程

 

raise函数原型:

#include<signal.h>

#include<sys/types.h>

int raise(int sig)

下面的例子使子进程不在父进程调用kill之前不退出,然后父进程调用kill使子进程退出:

void signal_func(){

    pid_t pid;

    int ret;

    pid=fork();

    if(pid == 0){

        printf("child(pid:%d) is waiting for any signal ",getpid());

        raise(SIGSTOP);

        exit(0);

    }

    else{

        sleep(2);

        if((waitpid(pid,NULL,WNOHANG)) == 0){

            if((ret=kill(pid,SIGKILL)) == 0){

                printf("parent kill %d ",pid);

            }

        }

//        waitpid(pid,NULL,0);

        exit(0);

    }

}

运行结果:

原文地址:https://www.cnblogs.com/zhanghongfeng/p/9108299.html