signal函数_Linux IPC通信方式之Signal

3d4e583a41c3cb8cceee35b9836f5548.png

1、信号的介绍

    信号是事件发生时对进程的通知机制。一个进程可以向另一个进程发送信号。在Linux内核中,使用一个唯一的整数来区分不同的信号。

    可以在终端输入“kill -l”命令来查看所有的信号及其对应的编号。

8ff01d110fd71ac032d3403111274465.png

(1)信号的四要素:编号、信号名称、对应事件和默认动作

    在终端执行“man 7 signal”可以查看到所有信号的相关信息

    例如:SIGINT信号的编号为2,名称为SIGINT,对应事件为在键盘输入ctrl+c,默认动作为终止进程。

2、用户进程对信号的响应方式

    (1)执行默认操作:遵循信号原有的含义。相当于用户进程对信号没有任何处理。通常情况下会导致用户进程的退出;

    (2)忽略信号:忽略信号的原有含义。对信号不做任何处理;

    (3)捕捉信号:实现对信号的重定义。用户定义与该信号对应的信号处理函数,当收到该信号时,执行相应的处理函数。

    注意:信号SIGKILL和SIGSTOP不能被忽略,也不能被捕捉,只能进行默认处理结束当前进程。

3、信号在内核中的表示

    信号的处理动作称为信号递达(Delivery),信号从产生到递达之间的状态,称为信号未决(Pending)。

    进程可以选择阻塞(Block)某个信号(SIGKILL信号和SIGSTOP信号不能被阻塞)。被阻塞的信号产生之后将一直处于未决状态,直到被阻塞的信号的解除阻塞,该信号才能被递达。

    注意:阻塞和忽略是两个不同的动作,阻塞说明这个信号不会递达。而忽略说明这个信号递达之后的处理动作是忽略。

    未决信号集:保存没有被当前进程处理的信号

    阻塞信号集:保存需要阻塞的信号

    在进程的PCB中使用未决信号集和阻塞信号集的来处理信号。信号产生时,内核在进程控制块中的未决信号集设置该信号对应的标志,直到信号递达之后才清除该标志。如果该信号的阻塞信号集对应标志为有效,则这个信号被阻塞,不能被递达,同时该信号在未决信号集的标志会被保留,直到解除该信号的阻塞。

    如图所示,信号的处理过程如下:

6e38becd7d69761cebb1e844523a6f4e.png

(1)SIGHUP信号没有产生也没有阻塞,递达之后的处理方式为默认处理(SIG_DFL)

(2)SIGINT信号已经产生并处于未决状态,但是阻塞信号集中相应位置为1,信号被阻塞

(3)SIGQUIT信号已经产生,没有被阻塞,会调用用户定义的signal_handler信号处理函数,函数返回之后清除未决信号集中的SIGQUIT信号

4、常用函数

改变信号的处理方式:signal函数

函数名称:sighandler_t signal(int signum, sighandler_t handler);

函数功能:指定信号的处理方式

头文件:#include

输入参数:

    signum:要修改处理方式的信号编号

    handler:

        SIG_DFL:将该信号的处理方式恢复为默认值,适用于将之前调用signal函数所改变的信号处理方式还原

        SIG_IGN:忽略该信号,用户进程在收到该信号时直接丢弃

        自定义函数名称:有用户自定义信号的处理方式

返回值:

    输入参数handler传递的是用户自定义的函数,返回值为该函数的地址

    输入参数handler传递的是SIG_DFL,返回值也是SIG_DFL

    输入参数handler传递的是SIG_IGN,返回值也是SIG_IGN

    调用失败返回SIG_ERR,并设置error

其他:

typedef void (*__sighandler_t) (int);

#define  SIG_ERR   ((__sighandler_t) -1)  /* Error return.  */

#define  SIG_DFL   ((__sighandler_t)  0)  /* Default action.  */

#define  SIG_IGN   ((__sighandler_t)  1)  /* Ignore signal.  */

signal函数的示例代码:#include #include #include void signal_func(int sig){    printf("signal handler(%d)\n", sig);    /* 修改信号处理方式为默认处理 */    signal(sig, SIG_DFL);}int main(int argc, char *argv[]){    static unsigned int cnt = 0;    /* 修改SIGINT信号的处理方式,在接收到SIGINT信号时,将调用signal_func函数 */    if (signal(SIGINT, signal_func) == SIG_ERR)    {        perror("signal handler set failed!\n");        return -1;    }        while(1)    {        printf("cnt = %d\n", cnt++);        sleep(1);    }    return 0;}

745c45217a2b85d1c736de457bb0c1e7.png

等待信号:pause函数

function

int pause(void);

head file

#include

description

阻塞进程的执行,直到收到信号,并且信号处理函数成功返回

input

void

ret value

收到信号后,返回-1,并将error设置为EINTR

示例代码:

#include #include #include void signal_function(int sig){    static unsigned int sigint_cnt = 0;    static unsigned int sigquit_cnt = 0;    switch (sig)    {    case SIGINT:        printf("Recv SIGINT signal cnt is %d\n", ++sigint_cnt);        break;    case SIGQUIT:        printf("Recv SIGQUIT signal cnt is %d\n", ++sigquit_cnt);        break;    default: break;    }}int main(int argc, char *argv[]){    static unsigned int recv_signal_cnt = 0;    int ret = 0;    /* 修改SIGINT信号的处理方式 */    if (signal(SIGINT, signal_function) == SIG_ERR)    {        perror("signal handler set failed!\n");        return -1;    }    /* 修改SIGQUIT信号的处理方式 */    if (signal(SIGQUIT, signal_function) == SIG_ERR)    {        perror("signal handler set failed!\n");        return -1;    }    while(1)    {        /* 只有在捕获到信号,并且对应的信号处理函数以及返回时,pause函数才会返回         * 在这种情况下,pause函数会返回-1,并且errno设置为EINTR         */        ret = pause();        printf("pause() return is %d\n", ret);        perror("pause() return");        printf("Recved signal cnt = %d\n", ++recv_signal_cnt);    }    return 0;}

4e537cfa27e08604dcc2381e422d8e61.png

闹钟函数:alarm函数

function

unsigned int alarm(unsigned int seconds);

head file

#include

description

为进程提供一个闹钟的功能,进程传入一个定时时间,在经过定时时间之后,内核会向该进程发送一个SIGALRM的信号。

如果传入的时间为0,则将取消之前设置的定时时间

input

定时时间,单位为秒

ret value

以前设置的定时时间的余留秒数

ret = alarm(10);  /* ret = 0; */

sleep(2);

ret = alarm(5);   /* ret = 8 */

示例代码:

#include #include #include void sig_alarm_func(int sig){    printf("sig_alarm_func %d\n", sig);}int main(int argc, char *argv[]){    unsigned int cnt = 0;    if (signal(SIGALRM, sig_alarm_func) == SIG_ERR)    {        perror("signal handler set failed!\n");        return -1;    }    alarm(10);    while(1)    {        printf("run cnt = %d\n", cnt++);        sleep(1);    }    return 0;}

b9aa655bc40690482bc5ef653f1f14dc.png

信号发送函数1:raise函数

function

int raise(int sig);

head file

#include

description

进程或线程为自己发送一个signal

对于单线程的程序,等同于kill(getpid(), sig)

对于多线程的程序,等同于pthread_kill(pthread_self(), sig)

input

sig:要发送的信号编号

ret value

返回0成功,返回非0值失败

other

如果sig设置了信号处理函数,则在信号处理函数返回之后,raise函数才会返回

示例代码:

#include #include #include void signal_func(int sig){    printf("signal handler\n");}int main(int argc, char *argv[]){    unsigned int cnt = 0;    signal(SIGINT, signal_func);    while(1)    {        sleep(1);        printf("run cnt = %d\n", cnt++);        if (!(cnt % 10))        {            raise(SIGINT);        }    }    return 0;}

45a52279ef0fa01036ef0b9dc1dba39c.png

信号发送函数2:kill函数

function

int kill(pid_t pid, int sig);

head file

#include

#include

description

向指定进程发送信号

input

pid:目标进程的pid

sig:要发送的信号

ret value

成功返回0,失败返回-1,并设置errno

other

kill函数返回-1的三大原因

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

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

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

示例代码:

#include #include #include #include void signal_func(int sig){    printf("child process recv sig %d\n", sig);    _exit(0);}int main(int argc, char *argv[]){    pid_t pid = 0;    pid = fork();    if (pid > 0)    {        /* parent process */        sleep(5);        kill(pid, SIGINT);        while(1);    }    else if (pid == 0)    {        /* child process */        signal(SIGINT, signal_func);        while(1)        {            sleep(1);            printf("child process\n");        }    }    else    {    }    return 0;}

a6b2c82583b9f25745bba5a706522ee5.png

定时函数:setitimer函数

function

int setitimer(int which, const struct itimerval *new_value,

                         struct itimerval *old_value);

head file

#include

description

为系统提供一个两次定时的功能。第一次定时时间到达之后,周期的设置第二次定时时间

input

which

ITIMER_REAL:以系统真是时间来计算,定时时间到达时,发送SIGALRM信号

ITIMER_VIRTUAL:以该进程在用户态下花费的时间来计算,定时时间到达时,发送SIGVTALRM信号

ITIMER_PROF:以该进程在用户态和内核态花费的时间来计算,定时时间到达时,发送SIGPROF信号

new_value

传入定时的时间参数

old_value

传出上一次定时的时间参数

ret value

设置成功返回0

设置失败返回-1

other

struct itimerval {

    struct timeval it_interval; /* 第一次定时之后的周期定时时间 */

    struct timeval it_value;    /* 第一次定时时间              */

};

struct timeval {

    time_t      tv_sec;         /* 单位:秒   */

    suseconds_t tv_usec;        /* 单位:微秒 */

};

示例代码:

#include #include #include #include #include void sig_alarm_func(int sig){    printf("Recv signal: %d\n", sig);}int main(int argc, char *argv[]){    unsigned int cnt = 0;    struct itimerval time;    signal(SIGALRM, sig_alarm_func);    memset(&time, 0, sizeof(time));    /* 第一次定时时间10s */    time.it_value.tv_sec  = 10;    time.it_value.tv_usec = 0;    /* 第二次定时时间2s */    time.it_interval.tv_sec  = 2;    time.it_interval.tv_usec = 0;    setitimer(ITIMER_REAL, &time, NULL);    while (1)    {        printf("run cnt = %d\n", ++cnt);        sleep(1);    }    return 0;}

ebed267d6b600c1b250d598c58a811dd.png

信号集相关处理函数

头文件:  #include

函数名称:int sigemptyset(sigset_t *set);

函数功能:初始化set所指向的信号集,使其中所有bit都为0

函数名称:int sigfillset(sigset_t *set);

函数功能:初始化set所指向的信号集,使其中所有bit都为1

函数名称:int sigaddset(sigset_t *set, int signo);

函数功能:向信号集中添加一个有效信号

函数名称:int sigdelset(sigset_t *set, int signo);

函数功能:从信号集中删除一个有效信号

输入参数:set:信号集的存储空间首地址

         signo:具体信号的编号

返回值:成功返回0,失败返回-1,并设置errno。

函数名称:int sigismember(const sigset_t *set, int signo);

头文件:  #include

函数功能:判断一个信号是否在信号集中

输入参数:set:信号集的存储空间首地址

         signo:具体信号的编号

返回值:如果这个信号在信号集中返回1;不在这个信号集中返回0;出错返回-1,并设置errno

头文件:  #include

函数名称:int sigprocmask(int how, const sigset_t *set, sigset_t *oset);

头文件:  #include

函数功能:修改PCB中的阻塞信号集

输入参数:how:

            SIG_BLOCK  :设置当前进程的阻塞信号集中的数据 mask = mask | set

            SIG_UNBLOCK:清除当前进程的阻塞信号集中的数据 mask = mask & ~set

            SIG_SETMASK:设置当前进程的阻塞信号集为set所指向的值,mask = set

         set:阻塞信号集

输出参数:oset:返回当前进程的阻塞信号集

返回值:成功返回0;失败返回-1,并设置errno

头文件:  #include

函数名称:int sigpending(sigset_t *set);

函数功能:获取当前进程的未决信号集

输出参数:set:信号集的存储空间首地址

返回值:成功返回0;失败返回-1,并设置errno

示例代码:

#include #include #include int main(int argc, char *argv[]){    int cnt = 0;    int ret = 0;    sigset_t sigset;    sigset_t sigset_tmp;    /* 初始化一个信号集,初值为0 */    sigemptyset(&sigset);    /* 向信号集中添加一个SIGINT信号 */    sigaddset(&sigset, SIGINT);    /* 判断SIGINT信号是否在sigset信号集中 */    ret = sigismember(&sigset, SIGINT);    if (ret)        printf("SIGINT set success!\n");    else        printf("SIGINT set failed!\n");    /* 设置进程的阻塞信号集 */    ret = sigprocmask(SIG_SETMASK, &sigset, NULL);    if (ret != 0)    {        perror("sigset set failed!\n");        return -1;    }    signal(SIGINT, SIG_IGN);    while(1)    {        cnt++;        /* 读取当前进程的阻塞信号集 */        ret = sigpending(&sigset_tmp);        if (ret != 0)        {            perror("get pending signal failed!\n");            return -1;        }            /* 打印当前进程的阻塞信号的数据 */        for (int i = 1; i < 32; i++)        {            ret = sigismember(&sigset_tmp, i);            printf("%d", ret ? 1 : 0);        }        printf("\n");        if (cnt > 10)        {            cnt = 0;                        /* 清除进程的阻塞信号集中的数据 */            sigprocmask(SIG_UNBLOCK, &sigset, NULL);        }        sleep(1);    }    return 0;}

84bab9b8f7507d097a9f0a274499fc88.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值