Linux之信号

信号集类型sigset_t

是描述信号的集合的结构体

定义

typedef struct
{
    unsigned long sig[_NSIG_WORDS];
}sigset_t

相关函数

#include <signal.h>

sigemptyset(sigset_t *set)		//初始化由set指定的信号集,信号集里面的所有信号被清空
sigfillset(sigset_t *set)		//调用该函数后,set指向的信号集中将包含linux支持的64种信号
sigaddset(sigset_t *set, int signum)	//在set指向的信号集中加入signum信号;
sigdelset(sigset_t *set, int signum)	//在set指向的信号集中删除signum信号;
sigismember(const sigset_t *set, int signum)	//判定信号signum是否在set指向的信号集中。

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:函数指针,指向信号处理函数。默认使用此函数处理信号

  • sa_sigaction:同样是信号处理函数,有三个参数,可以获得关于信号更详细的信息。可以通过参数四使用此函数处理信号。即参数一和参数二是二选一的。

  • sa_mask:信号处理函数执行期间需要被屏蔽的信号。

  • sa_flags:指定信号处理的行为

    • SA_RESTART,使被信号打断的系统调用自动重新发起
    • SA_NOCLDSTOP,使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号
    • SA_NOCLDWAIT,使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程
    • SA_NODEFER,使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号
    • SA_RESETHAND,信号处理之后重新设置为默认的处理方式
    • SA_SIGINFO,使用 sa_sigaction 而不是 sa_handler 作为信号处理函数
  • sa_restorer:废弃不使用

sigaction函数

用于设置对某种信号的处理方式

#include <signal.h>

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
  • signum:操作的信号

  • act:设置新的处理方式。

  • oldact:原来的处理方式

  • 返回值:0 表示成功,-1 表示有错误发生

Linux下信号处理机制

Linux下的信号采用的异步处理机制,当进程收到信号时,操作系统会中断进程当前的正常流程,转而进入信号处理函数执行操作,完成后再返回中断的地方继续执行,且信号处理期间系统不会再次触发它,即处理期间如果有同类型的信号发来会被屏蔽。所以我们希望信号处理函数执行快、尽可能少错过信号。
一般的解决方案是,信号处理函数用于通知主循环发生信号,具体的处理逻辑放在主循环中。

统一事件源

信号处理函数使用管道将信号传递给主循环,信号处理函数往管道的写端写入信号值,主循环则从管道的读端读出信号值。使用I/O复用(select、poll、epoll)系统调用来监听管道读端的可读事件,这样信号事件与其他文件描述符都可以通过epoll来监测,从而实现统一处理

使用管道通信

在linux下,使用socketpair函数能够创建一对套接字进行通信

#include <sys/types.h>
#include <sys/socket.h>
int socketpair(int domain, int type, int protocol, int sv[2]);
  • domain:协议族

  • type:协议。SOCK_STREAM(基于TCP)或者SOCK_DGRAM(基于UDP)

  • protocol:类型,只能为0

  • sv[2]:即管道两端,均可进行读写操作

  • 返回值:成功为0,失败为-1

以下为具体案例 (省略头文件)

//自定义信号处理函数,仅通知主循环发生信号
void sig_handler(int sig)
{
    //为保证函数的可重入性,保留原来的errno
    //可重入性表示中断后再次进入该函数,环境变量与之前相同,不会丢失数据
    int save_errno = errno;
    int msg = sig;

    //将信号值从管道写端写入,传输字符类型,而非整型
    send(pipefd[1], (char *)&msg, 1, 0);

    //将原来的errno赋值为当前的errno
    errno = save_errno;
}

//通过sigaction函数设置用自定义信号处理函数处理信号
void addsig(int sig, void (handler)(int), bool restart = true)
{
	//创建sigaction结构体变量
	struct sigaction sa;
	memset(&sa, '\0', sizeof(sa));

	//信号处理函数中仅仅发送信号值,不做对应逻辑处理
	sa.sa_handler = handler;
	//可以设置被信号打断的系统调用自动发起
	if (restart)
		sa.sa_flags |= SA_RESTART;
	//将所有信号添加到信号集中,即处理信号期间屏蔽所有信号
	sigfillset(&sa.sa_mask);

	//执行sigaction函数给信号设置处理方式,assert只用于检测错误
	assert(sigaction(sig, &sa, NULL) != -1);
}

//创建管道套接字
ret = socketpair(PF_UNIX, SOCK_STREAM, 0, pipefd);
assert(ret != -1);

//管道通信时如果缓冲区满了,则会阻塞,进一步增加信号处理函数执行时间
//为了避免,设置为非阻塞
//未对非阻塞返回值处理是因为定时事件是非必须立即处理的事件,可以允许这样的情况发生
setnonblocking(pipefd[1]);

//设置管道读端为ET非阻塞,即检测到事件触发时通知,必须立即处理
addfd(epollfd, pipefd[0], false);

//传递给主循环的信号值,这里只关注 SIGALRM 和 SIGTERM
addsig(SIGALRM, sig_handler, false);
addsig(SIGTERM, sig_handler, false);

//循环条件
bool stop_server = false;

//超时标志
bool timeout = false;

//每隔TIMESLOT时间触发SIGALRM信号
alarm(TIMESLOT);

while (!stop_server)
{
    //监测发生事件的文件描述符
    int number = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
    if (number < 0 && errno != EINTR)
    {
        break;
    }

    //轮询文件描述符
    for (int i = 0; i < number; i++)
    {
        int sockfd = events[i].data.fd;

        //管道读端对应文件描述符发生读事件
        if ((sockfd == pipefd[0]) && (events[i].events & EPOLLIN))
        {
            int sig;
            char signals[1024];

            //从管道读端读出信号值,成功返回字节数,失败返回-1
            //正常情况下,这里的ret返回值总是1,只有14和15两个ASCII码对应的字符
            ret = recv(pipefd[0], signals, sizeof(signals), 0);
            if (ret == -1)
            {
                //handle the error
                continue;
            }
            else if (ret == 0)
            {
                continue;
            }
            else
            {
               //真正处理信号值对应的逻辑
                for (int i = 0; i < ret; ++i)
                {
                    switch (signals[i])
					{
                    	case SIGALRM:
                    	{
                        	timeout = true;
                        	break;
                    	}
                    	case SIGTERM:
                    	{
                        	stop_server = true;
                    	}
					}
            	}
        }
    }
}
posted @ 2023-08-15 17:48  悲伤鳄鱼吃面包  阅读(13)  评论(0编辑  收藏  举报