5.1信号一2015/7/28

1.信号的种类

$ 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个

信号产生的方式

终端特殊按键

ctl+c   SIGINT      //终止进程
ctl+z   SIGTSTP     //暂停进程并切换到后台
ctl+\   SIGQUIT     //退出(核心已转储)

终端接收到特殊按键后,终端发给内核,内核再给当前进程的PCB发送相应信号

硬件异常

除0操作         //SIGFPE    8   (核心已转储)
访问非法内存    //SIGSEGV   11  (段错误) 
/*访问MMU没有分配访问权限(例如3G~4G),但用户去访问就会产生非法访问*/

进程接收到信号后会有三类行为:默认行为,忽略行为,捕捉行为
忽略行为:无视信号
捕捉行为:要先设置一个信号处理函数(do_sig),当前进程收到该信号时,触发这个信号处理函数,执行该函数里面的内容(自行设计)
默认行为:包含5种动作,term(把当前进程终止掉),core(终止进程,并产生一个core文件,ulimit -c 1024设置core文件大小),stop(让当前进程暂停),continue(继续执行先前停止的进程),ignore(忽略)

   Term   Default action is to terminate the process.
   Ign    Default action is to ignore the signal.
   Core   Default action is to terminate the process and dump core (see core(5)).
   Stop   Default action is to stop the process.
   Cont   Default action is to continue the process if it is currently stopped.

当没有对信号进行特殊处理之前,32种信号里面的行为都是默认行为里面的默认动作

    Signal     Value     Action   Comment
   ──────────────────────────────────────────────────────────────────────
   SIGHUP        1       Term    Hangup detected on controlling terminal
                                 or death of controlling process
   SIGINT        2       Term    Interrupt from keyboard
   SIGQUIT       3       Core    Quit from keyboard
   SIGILL        4       Core    Illegal Instruction
   SIGABRT       6       Core    Abort signal from abort(3)
   SIGFPE        8       Core    Floating point exception
   SIGKILL       9       Term    Kill signal
   SIGSEGV      11       Core    Invalid memory reference
   SIGPIPE      13       Term    Broken pipe: write to pipe with no
                                 readers
   SIGALRM      14       Term    Timer signal from alarm(2)
   SIGTERM      15       Term    Termination signal
   SIGUSR1   30,10,16    Term    User-defined signal 1
   SIGUSR2   31,12,17    Term    User-defined signal 2

   SIGCHLD   20,17,18    Ign     Child stopped or terminated
   SIGCONT   19,18,25    Cont    Continue if stopped
   SIGSTOP   17,19,23    Stop    Stop process
   SIGTSTP   18,20,24    Stop    Stop typed at tty
   SIGTTIN   21,21,26    Stop    tty input for background process
   SIGTTOU   22,22,27    Stop    tty output for background process

函数产生信号

kill给特定进程或进程组发送信号

既是命令又是函数

int kill(pid_t, int sig)
    pid > 0     sig发送给ID为pid的进程
    pid == 0    sig发送给与发送进程同组的所有进程
    pid < 0     sig发送给组ID为|-pid|的进程,并且发送进程具有向其发送信号的权限
    pid == -1   sig发送给发送进程有权限向他们发送信号的系统上的所有进程(慎用!)
    sig为0时,用于检测,特定为pid进程是否存在,如不存在,返回-1
kill(geipid,sig)给自己发信号

例子:

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

int main(int argc, char *argv[])
{
    int signum;
    pid_t pid_id;

    if(argc < 3) {
        printf("./mykill signum pid_id\n");
        exit(-1);
    }   
    signum = atoi(argv[1]); //receive sig_id
    pid_id = atoi(argv[2]); //receive pid_id

    if(kill(pid_id, signum) < 0) {  //error is set appropriately
        perror("kill");
        exit(-1);
    }   
    return 0;
}

raise/abort给自己发信号

int raise(int sig)
给自己进程发送设置的信号
void abort(void)
给自己发送SIGABRT,默认行为core,自己把自己干掉,(核心已转储)

某种软件条件满足时产生信号

1.管道:读端关闭,写端写管道,那么该进程会收到SIGPIPE信号,默认动作core
2.alarm定时器,每个进程有且只有一个定时器,当时间到时,当前进程会收到SIGALRM信号,默认行为trem
3.子进程结束时,父进程会收到SIGCHLD这个信号。默认动作为忽略这个信号

unsigned int alarm(unsigned int seconds) 

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

int main(void)
{
    int count = 0;
    alarm(1);   //timing 1s,send SIGALARM,core

    while(1) {  
        printf("count = %d", count++);  //how many times a second print
    }   
    return 0;
}

进程处理信号行为

SIG_IGN     默认
        term
        core
            gcc -g file.c
            ulimit -c 1024
            gdb a.out core
            进程死之前的内存情况,死后验尸
        ign
        stop
        cont
SIG_DFL     忽略
a signal handling function  捕捉

PCB信号集

未决信号集,阻塞信号集,信号处理动作都在PCB中
未决态:信号产生了,还没有被进程响应,那么此信号处在未决态
递达态:信号产生了,并且被当前进程响应了,那么此信号称之为递达

信号集处理函数

sigset_t为信号集,可sizeof(sigset_t)查看大小
    int sigemptyset(sigset_t *set)  //set传出参数,清空set
    int sigfillset(sigset *set)     //把set全部置1
    int sigaddset(sigset_t *set, int signo)     //把信号集里的第signo这一位置1,添加一个信号
    int sigdelset(sigset_t *set, int signo)     //把信号集里的第signo这一位置0,删除一个信号
    int sigismember(const sigset_t *set, int signo)     //测试,在set这个信号集里面signo这个信号位是否为1,存在返回1,不存在返回0


调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集)
    #include <signal.h>
    int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
    return:成功返回0,出错返回-1
how参数的含义

    SIG_BLOCK       set包括了我们希望添加到当前信号屏蔽字的信号,相当于mask=mask|set
    SIG_UNBLOCK     set包含了我们希望从当前信号屏蔽字中解除阻塞的信号,相当于mask=mask&`set(set先取反,在进行位与操作)
    SIG_SETMASK     设置当前信号屏蔽字为set所指向的值,相当于mask=set

sigpending读取当前进程的未决信号集,通过set传出

    #include <signal.h>
    int sigpending(sigset_t *set);
    return:successful 0, error -1.

例子:

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

void printsigset(sigset_t *p) 
{
    int i = 0;
    for(i=0; i<32; i++) /* print 0~31 signal status */
        if(sigismember(p, i) == 1)  /* test p signal status */
            putchar('1');
        else
            putchar('0');
    puts("");   /* wrap */
}

int main(void)
{
    int i = 0;
    sigset_t set, p;    

    sigemptyset(&set);  /* empty set */
    sigaddset(&set, SIGINT);    /* add SIGINT to set */
    sigprocmask(SIG_SETMASK, &set, NULL);   /* replace block */
    while(1) {
        sigpending(&p); /* get pending */
        printsigset(&p);    /* print peding */
        sleep(1);
    }   
    retrun 0;   
}

信号捕捉设定

#include <signal.h>
int sigaction(int signum, const struct sigaction sigaction *act, struct sigaction *oldact);
struct sigaction 定义:
    struct sigaction {
        void    (*sa_handler)(int); 
        void    (*sa_sigaction)(int, siginfo_t *, void *)
        sigset_t    sa_mask;
        int     sa_flags;
        void    (*sa_restorer)(void);
    };

(*sa_handler)(int);有三种情况:SIG_DFL(默认),SIG_IGN(忽略),a pointer to a signal handling function
sa_mask;掩码,当运行signal handling function的时候还想阻塞哪些信号,临时的,只作用于signal handling function

例子:

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

void do_sig(int num)    /* signal handling function, num is signal number*/
{
    printf("I am in signal handling %d\n", num);
}

int main(void){
    int i = 0;
    struct sigaction set;
    set.sa_handler = do_sig;    /* set function address */
    sigemptyset(&set.sa_mask);  /* empty mask */
    set.sa_flags = 0;

    sigaction(SIGINT, &set, NULL);  /* handling SIGINT signal 2 */

    while(1) {
        printf("i = %d\n", i++);
        sleep(1);
    }   

    return 0;
}

do_sig是内核调用的,但是用户身份执行的
0.产生信号,打断当前进程,切换到内核空间,当前进程的处理器现场会保存到内核的栈空间里面去
1.内核接收到信号,去调do_sig函数,在代码段执行。
2.当do_sig执行完毕,返回内核去。
3.内核进行进程调度,把处理器现场重新加载到寄存器中,继续执行刚才被信号打断的进程。

C标准库信号处理函数

typedef void(*sighandler_t)(int)
sighandler_t signal(int signum,  sigbandler_t handler)

int system(const char *command)
集合了fork,exec,wait一体

例子:

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

void do_sig(int n)
{
    printf("here I am SIGINT handler\n");
}

int main(void)
{
    signal(SIGINT, do_sig);
    while(1) {
        printf("*******************\n");
        sleep(1);
    }   
    return 0;
}
------------------------------------------------
#include <stdlib.h>

int main(void)
{
    system("ls -l");
    return 0;
}

不可重入函数

含全局变量和静态变量是不可重入函数的一个要素
可重入函数可以通过man 7 signal查看
在信号捕捉函数里面应使用可重入函数

时序竞态

int pause(void)
    使调用进程挂起,知道有信号递达,如果递达信号是忽略,则继续挂起
int singsuspend(const sigset_t *mask)
    1.以通过指定mask来临时解除对某个信号的屏蔽
    2.然后等待挂起
    3.当被信号唤醒sigsuspend返回时,进程的信号屏蔽字恢复为原来的值 
#include <signal.h>
#include <unistd.h>
#include <stdio.h>

void do_sig(int num)
{
    /* nothing to do */
}

unsigned int mysleep(unsigned int nsecs)
{
    struct sigaction newact, oldact;
    unsigned int unslept;

    newact.sa_flags = 0;
    newact.sa_handler = do_sig;
    sigemptyset(&newact.sa_mask);
    sigaction(SIGALRM, &newact, &oldact);   /* set newmask, get oldmask to oldact */

    alarm(nsecs);   /* set SIGALRM */
    pause();

    unslept = alarm(0); /* if alarm clock is no finished, it will return the remanining number of seconds when alarm is again */
    sigaction(SIGALRM, &oldact, NULL);  /* set oldmask to block*/

    return unslept;
}

int main(void)
{
    while(1) {
        mysleep(2);
        printf("Two second passed\n");
    }
}

上述代码,当进程执行完alarm(nsecs)定时器之后,kernel又去调用其他进程,该进程处理器现场保存,但是定时器依旧在计时,当计时器订完之后,kernel在来执行该进程,恢复处理器现场,会立即出发sigaction里的do_sig函数,这个函数执行完之后,会再去执行pause(),那此进程这时候会被永久挂起。因为没有信号来触发pause了。

解决方式

unsigned int mysleep(unsigned int nsecs)
{
    struct sigaction newact, oldact;
    sigset_t newmask, oldmask, suspmask;
    unsigned int unslept;
    /* set our handler, save previous information */
    newact.sa_flags = 0;
    newact.sa_handler = do_sig;
    sigemptyset(&newact.sa_mask);
    sigaction(SIGALRM, &newact, &oldact);

    /* block SIGALRM and save current signal mask*/
    sigemptyset(&newmask);
    sigaddset(&newmask, SIGALRM);
    sigprocmask(SIG_BLOCK, &newmask, &oldmask);

    alarm(nsecs);

    suspmask = oldmask;
    sigdelset(&suspmask, SIGALRM); /* mask sure SIGALRM isn't blocked*/
    sigsuspend(&suspmask);      /* wait for any signal to be caught*/

    /* some signal has been caught, SIGALRM is now blocked*/
    unslept = alarm(0);
    sigaction(SIGALRM, &oldact, NULL); /* reset previous action */

    sigprocmask(SIG_SETMASK, &oldmask, NULL);
    return unslept;
}

说明,在设置好捕捉函数之后,在定义一个新的信号屏蔽字,把这个屏蔽字先用sigemptyset清空,然后在用sigaddset把SIGALRM信号加上,再通过 sigprocmask(SET_BLOOK,&newmask,&oldmask) 把原来的信号屏蔽字表替换掉,原来的保存到oldmask中,此时SIGALRM这个信号是屏蔽的,这时候去调用alarm(),下面接着把oldmask表里面的内容复制到suspmask中,把其中的SIGALRM删除,确认SIGALRM这个信号在suspmask里面是非阻塞的,然后执行sigsuspand(&suspmask),这个函数是先把当前进程的信号屏蔽字表临时替换成suspmask这个,然后挂起等待,等alarm定的时间到了,给进程发送SIGALRM信号,此时的进程的信号屏蔽字表是不对它屏蔽的,所以触发sigsuspmask这个函数,进程继续往下执行,信号屏蔽字重新变回到newmask,之后重新设置一个alarm通过返回值查看之前的闹钟是否执行完全,然后通过sigacton和siaprocmask把之前的捕捉动作和信号屏蔽字重新改回去。

你可能感兴趣的:(Linux编程)