栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

第10章 信号

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

第10章 信号

        信号是由用户、系统或者进程发送给目标进程的信息,以通知目标进程某个状态的改变或系统异常。

10.1 Linux信号概述 10.1.1 发送信号

        Linux下,一个进程给其他进程或进程组发送信号的API是kill函数。

#include 
#include 

int kill(pid_t pid, int sig);
参数解释:
pid:接收信号的目标进程ID
sig:准备发送的信号代码,假如其值为0则没有任何信号送出,但是系统会执行错误检查,
通常会利用sig值为零来检验某个进程是否仍在执行。
返回值:成功时返回0,失败则返回-1并设置errno。

        参数pid的取值及含义如下表所示:

        Linux定义的信号值都大于0,如果sig取值为0,则kill函数不发送任何信号。参数sig的取值及其含义如下表所示:

        kill函数返回的errno取值及含义如下表所示:

#include 
#include 
#include 
#include 
#include 
#include 
#include 


void waitChild(int signo)
{
    int status;
    if (signo == SIGCHLD)
    {
        pid_t pid = wait(&status);
        printf("回收子进程, pid: %dn", pid);
    }
}

//输入ctrl + c,会发送SIGINT信号
void testInt(int signo)
{
    printf("键入: ctrl + c, pid: %dn", getpid());
}

//输入ctrl + z,会发送SIGTSTP信号
void testSTP(int signo)
{
    printf("键入: ctrl + z, pid: %dn", getpid());
}

int main()
{
    pid_t pid;
    if (0 > (pid = fork())) //创建子进程
    {
        printf("fork errno is: %dn", errno);
    }
    else if (pid == 0)  //子进程
    {
        sleep(2);   //睡眠2s,让父进程为SIGCHLD信号绑定好函数
        printf("I am is child! pid is %dn", getpid());

        // 利用kill函数 发信号给父进程
        if (-1 == kill(getppid(), SIGINT))
        {
            printf("send SIGINT to parent process fail, errno is: %dn", errno);
        }

        //结束进程,返回123,同时发送SIGCHLD信号
        exit(123);
    }
    else    //父进程
    {
        printf("I am is parent! pid is %dn", getpid());
        // 让SIGCHLD信号绑定waitChild函数
        if (SIG_ERR == signal(SIGCHLD, waitChild))
        {
            printf("bind SIGCHLD signal errno is: %dn", errno);
            exit(-1);
        }

        // 让SIGINT 信号绑定testInt
        if (SIG_ERR == signal(SIGINT, testInt))
        {
            printf("bind SIGINT signal errno is: %dn", errno);
            exit(-1);
        }

        // 让SIGTSTP 信号绑定testSTP
        if (SIG_ERR == signal(SIGTSTP, testSTP))
        {
            printf("bind SIGTSTP signal errno is: %dn", errno);
            exit(-1);
        }
        while(1); // 死循环,等待信号
    }
    return 0;
}
10.1.2 中断系统调用

        如果程序在执行执行处于阻塞状态的系统调用(select、poll)时接收到信号,并且我们为该信号设置了信号处理函数,则默认情况下系统调用将被中断,并且errno被设置为EINTR。我们可以使用sigaction函数为信号设置SA_RESTART标志以重启被该信号中断的系统调用。

        对于默认行为是暂停进程的信号(SIGSTOP、SIGTTIN),如果没有为它们设置信号处理函数,则它们也可以中断某些系统调用(connect、epoll_wait)。

10.2 信号注册函数

        对于捕获到的信号,要使用信号注册函数为信号设置处理函数。

10.2.1 signal函数
#include 

void (*signal(int signo, void (*func)(int)))(int);
参数解释:
signo:信号。见10.1.1的表
void (*func)(int):返回值为void,参数为int的函数指针
返回值:
signal系统调用出错时返回SIG_ERR,并设置errno

实际上,信号处理函数的原型如下:
tyepdef void (*__sighandler_t)(int);
可知__sighandler_t是函数指针(void (*)(int))的一个别名。
此时再看:void (*signal(int signo, void (*func)(int)))(int);
signal(int signo, void (*func)(int))就是一个函数指针,指向void (*)(int)型函数。


        代码示例: 

#include 
#include 
#include 

void procFun(int sig)
{
    if(sig == SIGINT)
    {
        puts("CTRL + C pressed");
    }
}

int main()
{
    //注册信号处理函数
    signal(SIGINT, procFun);
    
    sleep(10);  
    return 0;
}
10.2.2 sigaction函数

        由于signal函数在不同系统中存在兼容性问题,所以实际中很少使用。取而代之的是sigaction函数。

#include 

int sigaction(int signo, const struct sigaction* act, struct sigaction* oldact);
参数解释:
signo:信号
act:信号处理函数信息
oldact:通过此参数获取之前注册的信号处理函数指针,若不需要则传递0
返回值:
成功时返回0,失败时返回-1并设置errno

struct sigaction
{
    void (*sa_handler)(int);    //信号处理函数指针
    sigset_t sa_mask;           //在进程原有信号掩码的基础上增加信号掩码,以指定哪些信号不能发送给本进程
    int sa_flags;               //设置程序收到信号时的行为 
};

        sigaction结构体的sa_flags成员的取值及含义如下表:

        使用代码示例:

#include 
#include 
#include 

void procFun(int sig)
{
    if(sig == SIGINT)
    {
        puts("CTRL + C pressed");
    }
}

int main()
{

    struct sigaction act;
    act.sa_handler = procFun;
    sigemptyset(&act.sa_mask);   //初始化sa_mask的所有成员为0
    act.sa_flags = 0;
    sigaction(SIGINT, &act, 0);
    sleep(10);  
    return 0;
}
10.3 信号集

        信号从产生到抵达目的地,叫作信号递达。而信号从产生到递达的中间状态,叫作信号的未决状态。产生未决状态的原因有可能是信号受到阻塞了,也就是信号屏蔽字(或称阻塞信号集,mask)的对应位被置1(非0即1)。

        信号集可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞;而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。

10.3.1 信号集函数

        Linux使用sigset_t来表示一组信号。sigaction结构体的sa_mask成员的类型就是sigset_t。其定义如下:

#include 
#define _SIGSET_NWORDS (1024 / (8 * sizeof(unsigned long int)))
typedef struct
{
    unsigned long int __val[_SIGSET_NWORDS];
}sigset_t;

sigset_t实质上是一个数组,每个元素的每个bit位表示一个信号,这种定义方式类似于fd_set。

        下面一组函数用来设置、修改、删除、查询信号集:

#include 
int sigemptyset(sigset_t* _set);    //将信号集清0
int sigfillset(sigset_t* _set);     //将信号集置为1
int sigaddset(sigset_t* _set, int signo);      //将信号signo加入信号集
int sigdelset(sigset_t* _set, int signo);      //将信号signo从信号集中删除
int sigismember(_const sigset_t* _set, int signo); //判断signo是否在信号集中。在:1;不在0;错误:-1

        sigprocmask函数可以读取或更改进程的信号屏蔽字(阻塞信号集)。能用来可以用来屏蔽信号,也可以用来解除屏蔽信号。

#include 
int sigprocmask(int how, const sigset_t* set, sigset_t* oldset);
参数解释:
how:指定操作方式
set:传入参数。set中哪位置1,就表示当前进程屏蔽哪个信号
oldset:传出参数,保存旧的信号屏蔽集。
返回值:
成功时返回0,失败返回-1并设置errno

        该函数的_how参数可取值及对应的含义如下表:

how参数表示意义
SIG_BLOCKset包含了我们希望添加到当前信号屏蔽字的信号(往里加)
SIG_BLOCKset包含了我们希望从当前信号屏蔽字中解除屏蔽的信号(往外减)
SIG_SETMASK设置当前信号屏蔽字为set(重新设置)

        信号被屏蔽后将无法被进程接收。如果给进程发送一个被屏蔽的信号,则操作系统将该信号设置位进程的一个挂起的信号(未决信号)。如果取消对被挂起信号的屏蔽,则它能立即被进程接收到。 sigpending函数能获取当前进程的未决信号集,通过set参数保存。

#include 
int sigpending(sigset_t *set);
返回值:调用成功则返回0,出错则返回-1并设置errno

        使用代码示例:

#include 
#include 
#include 
void handler(int signo)     //信号2的处理函数
{
    printf("get a signo is:%dn",signo);
}
void show(sigset_t *pending)    //输出pending信号集
{
    int i=1;
    for(; i<=31; i++)
    {
        if(sigismember(pending, i)) //判断信号i是否在pending信号集中
        {
            printf("1");    //在则输出1
        }
        else
        {
            printf("0");    //不在则输出0
        }
    }
    printf("n");
}
int main()
{
    sigset_t set, oldset;     //定义两个阻塞信号集
    sigemptyset(&set);      //初始化这两个信号集
    sigemptyset(&oldset);
    sigaddset(&set, 2);     //将信号2添加到阻塞信号集
    sigprocmask(SIG_SETMASK, &set, &oldset);  //将当前阻塞信号集设为set
    printf("show set begin!n");
    show(&set);
    signal(2, handler);     //设置信号2的处理函数为handler
 
    sigset_t pending;       //定义未决信号集pending
    int i=3;
    while(1)
    {
        sigpending(&pending);   //获取当前进程的未决信号集
        printf("show pending begin!n");
        show(&pending);         //打印未决信号集
        sleep(1);
        if(i-- ==0)
        {
            //3秒之后将阻塞信号集设为oldset
            sigprocmask(SIG_SETMASK, &oldset, NULL);
        }
    }
    return 0;
}

        执行结果分析: 

10.4 统一事件源

        事件源:定时器的超时事件、信号、数据读写、网络异常。

        信号是一种异步事件:信号处理函数和程序的主循环是两条不同的执行路线。当信号来临时,主逻辑会被打断去执行信号处理函数。而信号到来的时机是不确定,如果此时信号处理函数会去访问一个已经被锁住的资源,那么这个线程就会被阻塞。这一点可通过下面一个例子看出:

#include 
#include 
#include 
 
void procFun(int sig)
{
    if(sig == SIGINT)
    {
        puts("CTRL + C pressed");
        sleep(6);
        printf("procFun has already sleep 6sn");
    }
}
 
int main()
{
 
    struct sigaction act;
    act.sa_handler = procFun;    //为信号设置处理函数
    sigemptyset(&act.sa_mask);   //初始化sa_mask的所有成员为0
    act.sa_flags = 0;
    sigaction(SIGINT, &act, 0);

    //在这3s内按ctrl+c,发送信号,信号处理函数会被立即执行,
    //信号处理函数执行结束后,继续执行main函数。
    //可见当信号来临时,主逻辑会被打断去执行信号处理函数
    for(int i=1; i<=3; i++)      
    {
        sleep(1);
        printf("main functon sleep %dsn", i);
    }
    printf("main function finishedn");
    return 0;
}

程序执行结果:
GF@GF:~/VscodeProject/test1$ ./signal 
main functon sleep 1s
main functon sleep 2s
^CCTRL + C pressed
procFun has already sleep 6s
main functon sleep 3s
main function finished

        此外,一般信号处理时会将一些信号屏蔽。为了不屏蔽这些信号太久,同时也不至于主逻辑被冲散,一种解决方案是:

用一种简单的信号处理函数,这种函数只是简单的通知主循环并告诉信号值,而真正的信号处理函数才会被主循环调用,根据信号值做出相应的处理。这种简单的信号处理函数和主循环之间通常用管道做通信,这种函数从管道的写端写入信号值,主循环从管道的读端读取信号值。因为主循环本来就要使用I/O复用函数监听连接socket和监听socket,所以,不妨将这个管道一并注册到I/O复用函数,这样就能在主循环中及时收到信号并作出处理。这样一来,只要有信号发出,就可以被及时接收,同时主逻辑也不会被中断。这种方法可以简写为如下示例:

int pipefd[2];
... 
void sig_handler(int sig)  //简单的信号处理函数
{   
    int msg = sig;  
    send(pipefd[1], (char*)&msg, 1, 0);//将信号按字节写入管道,以通知主循环 
} 

void addsig(int sig)  //为信号设置处理函数
{  
    struct sigaction sa;  
    memset(&sa, '', sizeof(sa));  
    sa.sa_handler = sig_handler;  
    sa.sa_flags |= SA_RESTART;  //信号如果打断了慢速系统调用,中断处理完成之后继续恢复系统调用
    sigfillset(&sa.sa_mask);    //在信号处理函数中屏蔽所有信号
    assert(sigaction(sig, &sa, NULL) != -1);  
}  

void handle_sig(int sig)    //真正的信号处理函数
{
    switch(sig)
    {
        case SIGCHLD:
            ...
        case SIGHUP:
            ...
        case SIGTERM:
            ...
        case SIGINT:
            ...
        ...
    }
}
...

int main(int argc, char **argv)
{
    ...
    //创建管道
    ret=socketpair(PF_UNIX,SOCK_STREAM,0,pipefd);   
    setnonblocking(pipefd[1]);  
    addfd(epollfd,pipefd[0]);  
    while(true)
    {
        int ret = epoll_wait(epollfd, events, MAX_EVENTS, -1);
        for (int i = 0; i < ret; i++)
        {
            int sockfd = events[i].fd;
            if (sockfd == listenfd)
            {
                int connfd = accept(sockfd, ...);
                ...
            }
            else if(sockfd  == pipefd[0] && events[i].events & EPOLLIN) //接收到信号
            {
                char signals[1024];  
                int num = recv(pipefd[0], signals, sizeof(signals), 0);  
                //每个信号值占1字节,所以按字节来逐个接收信号  
                //可能处理的时候收到了多个信号
                for (int i = 0; i < num; i++) 
                {
                    handle_sig(signals[i]);
                } 
            }
            else
            {
                 //读写socket   
            }
        }
    }
    ...
}

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/842291.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号