Linux信号

目录

  • Linux信号
    • 1、在程序中发送信号
    • 2、信号的处理方式
    • 3、信号的机制
    • 4、信号集
      • 进程信号掩码
    • 5、Linux常规信号介绍
    • 6、中断系统调用
    • 7、统一事件源
    • 8、网络编程相关信号
      • 8.1、SIGHUP
      • 8.2、SIGPIPE
      • 8.3、SIGALRM
      • 8.4、SIGCHLD

Linux信号

信号是由用户、系统或进程发送给目标进程的信息,以通知目标进程某个状态的改变或系统异常,Linux中有很多种不同的信号。可以在终端输入kill -l 来查看Linux支持的信号,如下图:

Linux信号_第1张图片

Linux信号可由如下条件产生:

1、对于前台进程,用户可以通过输入特殊的终端字符来发送,比如输入Ctrl+C通常会给正在运行的进程发送一个中断信号。

2、系统异常。比如浮点异常和非法内存段访问。

3、系统状态变化。比如alarm定时器到期时将引起SIGALRM信号。

4、在终端运行kill命令或在程序中调用kill函数,例如:如果要杀死一个进程,我们可以使用 kill -9 pid 来杀死进程,-9表示发送9号信号,也就是SIGKILL信号,pid为发送信号的目标进程的进程ID;9号信号无法被忽略以及改变默认处理方式,因此发送9号信号一定能杀死进程。

1、在程序中发送信号

Linux中,给进程发送信号的系统调用为kill,定义如下:

#include 
#include 
int kill(pid_t pid, int sig);   // 给进程ID为pid的进程发送sig信号
pid参数 含义
pid > 0 信号发送给进程ID为pid的进程。
pid = 0 信号发送给本进程组内的其它进程。
pid = -1 信号发送给除init进程外的所有进程,需要有权限
pid < -1 信号发送给ID为-pid的进程组中的所有成员

kill函数在成功时返回0, 失败时返回-1,并设置errno。

2、信号的处理方式

每个信号都有默认的处理方式,有的信号的默认处理方式是终止进程,有的是忽略信号以及结束进程并生成核心转储文件、暂停进程以及继续进程等。我们也可以在程序中修改信号的处理方式,注意:无法修改9号信号SIGKILL的默认处理方式,也无法忽略该信号。

信号处理函数的原型为:

#include 
typedef void (*sighandler_t)(int);

除了用户自定义信号处理函数外,bits/signum.h头文件还定义了信号的两种其它处理方式:

#include 
#define SIG_DFL ((sighandler_t) 0)            // 使用信号的默认处理方式
#define SIG_IGN ((sighandler_t) 1)			 // 忽略目标信号

要为一个信号设置处理函数,可以使用signal系统调用:

#include 
sighandler_t signal(int signum, sighandler_t handler);

signum为要捕获的信号类型,handler用于指定新的信号处理函数,也就是程序在收到signum类型信号后执行的回调函数。返回值为旧的信号处理函数。

例如下面代码:

代码中修改了2号信号SIGINT的处理函数,SIGINT信号可以由终端中按Ctrl+C来产生,因此当我们运行该程序后按Ctrl+C就会输出hello,world,为了结束该进程我们可以按Ctrl+\,这个按键组合会产生SIGQUIT信号,该信号的默认处理函数是结束进程并产生转储文件。如下图所示:

#include 
#include 
#include 

// 信号处理回调函数
void handleSignal(int signum)
{
    std::cout << "hello, world!" << std::endl;

}

int main()
{
    signal(SIGINT, handleSignal);    //修改SIGINT信号的处理方式

    while(1)
    {
        sleep(1);
    }
    return 0;
}

在下面的代码中修改9号信号SIGKILL的信号处理函数进行一个测试:

#include 
#include 
#include 
#include 
#include 

typedef void (*sighandler_t)(int);

void sig_handler(int signum)
{
    std::cout << "hello, world!" << std::endl;
}

int main()
{
    sighandler_t ret = signal(SIGKILL, sig_handler);
    if( ret == SIG_ERR)
    {
        std::cout << "ignore SIGKILL failed, reason: " << strerror(errno) << std::endl;
    }

    while(1)
    {
        sleep(1);

    }

    return 0;
}

然后编译运行该程序,如下图:
在这里插入图片描述

发现signal系统调用失败了,打开另一个终端查看该进程ID,并发送9号信号给此进程,发现进程还是被杀死了。说明9号信号SIGKILL的默认处理动作是无法被修改的。而且该信号也是不能被忽略的。

在这里插入图片描述
在这里插入图片描述

sigaction系统调用:

#include 

// act为新的处理方式,odlact为旧的处理方式	
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

struct sigaction {
    void     (*sa_handler)(int);                            // 信号处理函数
    void     (*sa_sigaction)(int, siginfo_t *, void *);     // 第二种形式的信号处理函数
    // 屏蔽信号集,调用信号处理函数时,所要屏蔽的信号集合(信号屏蔽字)。注意:仅在处理函数被调用期间屏蔽生效,是临时性设置。
    sigset_t   sa_mask;                                     
    int        sa_flags;                                    // 通常设置为0,表使用默认属性                              
    void     (*sa_restorer)(void);                          // 过时的元素,弃用
};

使用sigaction函数时可以指定在信号处理函数被调用的过程中要屏蔽的信号。

3、信号的机制

进程或用户A给一个进程B发送信号,B在收到信号之前执行自己的代码,当B进程收到信号后,不管程序执行到什么位置,都要暂停运行,去处理信号,也就是调用信号处理函数,处理完再继续执行。与硬件中断类似——异步模式。但信号是软件层面实现的中断,早期常被成为“软中断”。

信号的特质:由于信号是通过软件方法实现,其实现手段导致信号有很强的延时性。但对于用户来说,这个延迟时间非常短,不易察觉。

每个进程收到的所有信号,都是由内核负责发送的,内核处理。

内核实现信号捕捉过程:

Linux信号_第2张图片

4、信号集

Linux使用数据结构sigset_t来表示一组信号,定义如下:

#define _SIGSET_NWORDS (1024 / (8 * sizeof(unsigned long int)))

typedef struct
{
    unsigned long int _val[_SIGSET_NWORDS];
} sigset_t;

// sigset_t 实际上是一个长整型数组,数组中每个元素的每个位表示一个信号,Linux提供了如下一组函数来设置、修改、删除和查询信号集:
int sigemptyset(sigset_t *set);			            //将信号集清0		 成功:0;失败:-1
int sigfillset(sigset_t *set);				       //将信号集置1		  	成功:0;失败:-1
int sigaddset(sigset_t *set, int signum);		    //将信号加入信号集  	成功:0;失败:-1
int sigdelset(sigset_t *set, int signum);		    //将信号清出信号集   	成功:0;失败:-1
int sigismember(const sigset_t *set, int signum);   //判断某个信号是否在信号集中	返回值:在集合:1;不在:0;出错:-1  

进程信号掩码

​ 我们可以利用sigprocmask来设置进程的信号掩码。该函数可以用来设置进程要屏蔽的信号或者解除屏蔽的信号,其本质,读取或修改进程的信号掩码(也叫信号屏蔽集)(PCB中)。进程的PCB中保存了该进程的信号屏蔽集,该屏蔽集用来指示哪些信号在产生时会被屏蔽。

#include 
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

/* how参数:
	SIG_BLOCK: 新的进程信号掩码是其当前值和set指定信号集的并集,相当于 mask = mask|set
	SIG_UNBLOCK: 新的信号掩码是其当前值和~set信号集的交集,因此set指定的信号集将不被屏蔽,相当于 mask = mask & ~set
	SIG_SETMASK: 直接将进程信号掩码设置为set

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

设置信号掩码后,被屏蔽的信号将不能被进程接收。如果给进程发送一个被屏蔽的信号,则操作系统将该信号设置为进程的一个被挂起的信号。如果我们取消对被挂起信号的屏蔽,则它能立即被进程接收到。下面的函数可以获得进程当前被挂起的信号集。

#include 

int sigpending(sigset_t *set);	

在信号屏蔽期间如果该信号多次产生,在屏蔽结束后进程也只能接收到一次该信号。

例如下面代码:在main函数中分别注册了SIGINT和SIGQUIT信号的处理函数,并屏蔽了SIGINT信号,SIGINT信号可由终端按Ctrl+C产生,因此当我们按这个按键组合时,进程无法收到信号。当我们使用Ctrl+\来产生SIGQUIT信号时,由于该信号没有被屏蔽因此其处理函数会被调用,解除对SIGINT信号的屏蔽,之后进程便可以收到SIGINT信号,并输出hello,world。但是无论按了多少次Ctrl+C,hello,world只会输出一次。如下图:

在这里插入图片描述

#include 
#include 
#include 

// SIGINT 信号处理函数
void handle_sigint(int signum)
{
    std::cout << "hello,world!" << std::endl;
}

// SIGQUIT 信号处理函数
void handle_sigquit(int signum)
{
    // 解除SIGINT信号的屏蔽
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, SIGINT);

    sigprocmask(SIG_UNBLOCK, &set, nullptr);

}

int main()
{
    // 注册SIGINT和SIGQUIT的信号处理函数
    signal(SIGINT, handle_sigint);
    signal(SIGQUIT, handle_sigquit);

    // 屏蔽SIGINT信号
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, SIGINT);
    sigprocmask(SIG_BLOCK, &set, nullptr);

    while(1)
    {
        sleep(1);
    }
    return 0;
}

但是上面的代码会出现一个让人奇怪的情况,当按下Ctrl+\解除了信号屏蔽之后,进程会立即收到SIGINT信号,但是再按Ctrl+C,将会发现SIGINT信号又被屏蔽了。但是如果在main函数中解除信号屏蔽,那么之后再发送SIGINT信号,进程都将立刻收到,如下图:

Linux信号_第3张图片

代码如下:

#include 
#include 
#include 

bool flag = false;

void handle_sigint(int signum)
{
    std::cout << "hello,world!" << std::endl;
}

void handle_sigquit(int signum)
{
    flag = true;
}

int main()
{
    signal(SIGINT, handle_sigint);
    signal(SIGQUIT, handle_sigquit);

    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, SIGINT);

    sigprocmask(SIG_BLOCK, &set, nullptr);

    while(1)
    {
        sleep(1);
        if(flag)
        {
            sigset_t set;
            sigemptyset(&set);
            sigaddset(&set, SIGINT);

            sigprocmask(SIG_UNBLOCK, &set, nullptr);
            flag = false;
        }
    }
    return 0;
}

关于为什么会出现这样的情况,可能是进入信号处理函数后,修改的信号掩码是临时的,在信号处理函数调用完毕后,又恢复了原来的mask。

5、Linux常规信号介绍

  1. SIGHUP: 当用户退出shell时,由该shell启动的所有进程将收到这个信号,默认动作为终止进程

  2. SIGINT:当用户按下了组合键时,用户终端向正在运行中的由该终端启动的程序发出此信号。默认动作为终止进程。

  3. SIGQUIT:当用户按下组合键时产生该信号,用户终端向正在运行中的由该终端启动的程序发出些信号。默认动作为终止进程。

  4. SIGILL:CPU检测到某进程执行了非法指令。默认动作为终止进程并产生core文件

  5. SIGTRAP:该信号由断点指令或其他 trap指令产生。默认动作为终止里程 并产生core文件。

  6. SIGABRT: 调用abort函数时产生该信号。默认动作为终止进程并产生core文件。

  7. SIGBUS:非法访问内存地址,包括内存对齐出错,默认动作为终止进程并产生core文件。

  8. SIGFPE:在发生致命的运算错误时发出。不仅包括浮点运算错误,还包括溢出及除数为0等所有的算法错误。默认动作为终止进程并产生core文件。

  9. SIGKILL:无条件终止进程。本信号不能被忽略,处理和阻塞。默认动作为终止进程。它向系统管理员提供了可以杀死任何进程的方法。

  10. SIGUSE1:用户定义 的信号。即程序员可以在程序中定义并使用该信号。默认动作为终止进程。

  11. SIGSEGV:指示进程进行了无效内存访问。默认动作为终止进程并产生core文件。

  12. SIGUSR2:另外一个用户自定义信号,程序员可以在程序中定义并使用该信号。默认动作为终止进程。

  13. SIGPIPE:Broken pipe向一个没有读端的管道写数据。默认动作为终止进程。

  14. SIGALRM: 定时器超时,超时的时间 由系统调用alarm设置。默认动作为终止进程。

  15. SIGTERM:程序结束信号,与SIGKILL不同的是,该信号可以被阻塞和终止。通常用来要示程序正常退出。执行shell命令Kill时,缺省产生这个信号。默认动作为终止进程。

  16. SIGSTKFLT:Linux早期版本出现的信号,现仍保留向后兼容。默认动作为终止进程。

  17. SIGCHLD:子进程结束时,父进程会收到这个信号。默认动作为忽略这个信号。

  18. SIGCONT:如果进程已停止,则使其继续运行。默认动作为继续/忽略。

  19. SIGSTOP:停止进程的执行。信号不能被忽略,处理和阻塞。默认动作为暂停进程。

  20. SIGTSTP:停止终端交互进程的运行。按下组合键时发出这个信号。默认动作为暂停进程。

  21. SIGTTIN:后台进程读终端控制台。默认动作为暂停进程。

  22. SIGTTOU: 该信号类似于SIGTTIN,在后台进程要向终端输出数据时发生。默认动作为暂停进程。

  23. SIGURG:套接字上有紧急数据时,向当前正在运行的进程发出些信号,报告有紧急数据到达。如网络带外数据到达,默认动作为忽略该信号。

  24. SIGXCPU:进程执行时间超过了分配给该进程的CPU时间 ,系统产生该信号并发送给该进程。默认动作为终止进程。

  25. SIGXFSZ:超过文件的最大长度设置。默认动作为终止进程。

  26. SIGVTALRM:虚拟时钟超时时产生该信号。类似于SIGALRM,但是该信号只计算该进程占用CPU的使用时间。默认动作为终止进程。

  27. SGIPROF:类似于SIGVTALRM,它不公包括该进程占用CPU时间还包括执行系统调用时间。默认动作为终止进程。

  28. SIGWINCH:窗口变化大小时发出。默认动作为忽略该信号。

  29. SIGIO:此信号向进程指示发出了一个异步IO事件。默认动作为忽略。

  30. SIGPWR:关机。默认动作为终止进程。

  31. SIGSYS:无效的系统调用。默认动作为终止进程并产生core文件。

  32. SIGRTMIN ~ 64 SIGRTMAX:LINUX的实时信号,它们没有固定的含义(可以由用户自定义)。所有的实时信号的默认动作都为终止进程。

6、中断系统调用

系统调用可以分为两类:

  1. 慢速系统调用:可能会使进程永远阻塞的一类。如果在阻塞期间收到一个信号,该系统调用就被中断,不再继续执行(早期);也可以设定系统调用是否重启。如,read、write、pause、wait…

  2. 其他系统调用:getpid、getppid、fork…

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

7、统一事件源

信号是一种异步事件:信号处理函数和程序的主循环是两条不同的执行路线。很显然,信号处理函数需要尽可能快地执行完毕,以确保该信号不被屏蔽太久(为了避免一些竞态条件,在调用信号处理函数期间,该信号不会被再次触发)。一种解决方案是:将信号的主要处理逻辑放在程序的主循环中,当信号处理函数触发时,它只是简单地通知主循环接收到信号,并把信号值传递给主循环,主循环再根据接收到的信号值执行对应的处理逻辑。信号处理函数通常使用管道来将信号传递给主循环:信号处理函数往管道的写端写入信号值,主循环则从管道的读端读出该信号值。那么主循环怎么知道管道上何时有数据呢?我们只需使用I/O复用系统调用来监听管道的读端文件描述符上的可读事件即可。如此一来,信号事件就能和其它I/O事件一样被处理,即统一事件源。

​ ——《Linux高性能服务器编程》

下面实现的回射服务器可以将I/O事件以及信号事件统一在主循环中进行处理:

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

using std::cout;
using std::endl;

#define PORT 6666
#define MAX_EVENTS 1024
#define MAX_BUF_SIZE 1024

struct Event;

using readHandle = void(*)(Event *);
using writeHandle = void(*)(Event *);

// 自定义结构体,用来保存一个连接的相关数据
struct Event
{
    int fd;
    char ip[64];
    uint16_t port;
    epoll_event event; 

    char buf[MAX_BUF_SIZE];
    int buf_size;

    readHandle read_cb;
    writeHandle write_cb;
};

int epfd;
static int pipefd[2];

void err_exit(const char *reason)
{
    cout << reason << ":" << strerror(errno) << endl;
    exit(1);
}

// 设置非阻塞
int setNonblcoking(int fd)
{
    int old_option = fcntl(fd, F_GETFL);
    int new_option = old_option | O_NONBLOCK;
    fcntl(fd, F_SETFL, new_option);

    return old_option;
}

// 设置端口复用
void setReusedAddr(int fd)
{
    int reuse = 1;
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
}

// 信号处理函数
void sig_handler(int signum)
{
    // 保留原来的errno,在函数最后恢复,以保证函数的可重入性
    int save_errno = errno;
    int msg = signum;
    send(pipefd[1], (char *)&msg, 1, 0);   // 将信号写入管道
    errno = save_errno;
}

// 设置信号的处理函数
void addsig(int sig)
{
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = sig_handler;
    sa.sa_flags |= SA_RESTART;
    sigfillset(&sa.sa_mask);
    sigaction(sig, &sa, nullptr);

}

// 初始化server socket
int socket_init(unsigned short port, bool reuseAddr)
{
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if(fd < 0)
    {
        err_exit("socket error");
    }

    if(reuseAddr)
    {
        setReusedAddr(fd);
    }

    struct sockaddr_in addr;
    bzero(&addr, 0);
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);

    int ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
    if(ret < 0)
    {
        err_exit("bind error");
    }

    setNonblcoking(fd);

    ret = listen(fd, 128);
    if(ret < 0)
    {
        err_exit("listen error");
    }

    return fd;
}

void readData(Event *ev)
{
    ev->buf_size = read(ev->fd, ev->buf, MAX_BUF_SIZE - 1);

    ev->event.events = EPOLLOUT;
    epoll_ctl(epfd, EPOLL_CTL_MOD, ev->fd, &ev->event);
}

void writeData(Event *ev)
{
    write(ev->fd, ev->buf, ev->buf_size);

    
    ev->event.events = EPOLLIN;
    epoll_ctl(epfd, EPOLL_CTL_MOD, ev->fd, &ev->event);
}

// 接收连接回调函数
void acceptConn(Event *ev)
{
    Event *cli = new Event;
    struct sockaddr_in cli_addr;
    socklen_t sock_len = sizeof(cli_addr);
    int cfd = accept(ev->fd, (struct sockaddr *)&cli_addr, &sock_len);
    if(cfd < 0)
    {
        cout << "accept error, reason:" << strerror(errno) << endl;
        return;
    } 
    setNonblcoking(cfd);

    cli->fd = cfd;
    cli->port = ntohs(cli_addr.sin_port);
    inet_ntop(AF_INET, &cli_addr.sin_addr, cli->ip, sock_len);
    cli->read_cb = readData;
    cli->write_cb = writeData;

    cli->event.events = EPOLLIN;
    cli->event.data.ptr = (void *) cli;
    epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &cli->event);

    cout << "New Connection, ip:[" << cli->ip << ":" << cli->port << "]" << endl;
}

int main(int argc, char *argv[])
{
    int fd = socket_init(PORT, true);
    Event server;
    server.fd = fd;
    
    epfd = epoll_create(MAX_EVENTS);
    if(epfd < 0)
    {
        err_exit("epoll create error");
    }

    server.event.events = EPOLLIN;
    server.event.data.ptr = (void *)&server;

    epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &server.event);

    // 使用socketpair创建管道,注册pipefd[0]上的可读事件
    int ret = socketpair(PF_UNIX, SOCK_STREAM, 0, pipefd);
    if(ret == -1)
    {
        err_exit("socketpair error");
    }
    setNonblcoking(pipefd[1]);
    Event pipeEv;
    pipeEv.fd = pipefd[0];
    pipeEv.event.events = EPOLLIN;
    pipeEv.event.data.ptr = (void *)&pipeEv;
    epoll_ctl(epfd, EPOLL_CTL_ADD, pipefd[0], &pipeEv.event);

    // 设置一些信号的处理函数
    addsig(SIGINT);
    addsig(SIGCHLD);
    addsig(SIGTERM);
    addsig(SIGQUIT);
    bool stop_server =false;

    struct epoll_event events[MAX_EVENTS];
    int nready = 0;

    while(!stop_server)
    {
        // 将定时容器中定时时间最短的时长作为epoll_wait的最大等待时间

        nready = epoll_wait(epfd, events, MAX_EVENTS, 1000);
        if(nready < 0)
        {
            cout << "epoll wait error, reason:" << strerror(errno) << endl;
        } 
        else if(nready > 0)
        {
            for(int i = 0; i < nready; i++)
            {
                Event *ev =  (Event *) events[i].data.ptr;
                // 接受新的连接
                if(ev->fd == fd )
                {
                    acceptConn(ev);
                }
                else if(ev->fd == pipefd[0])             //处理信号
                {
                    int sig;
                    ret = recv(ev->fd, ev->buf, MAX_BUF_SIZE, 0);
                    if(ret <= 0)
                    {
                        continue;
                    }
                    else
                    {
                        // 每个信号占一个字节,所以按字节来逐个接收信号
                        for(int i = 0; i < ret; i++)
                        {
                            switch (ev->buf[i])
                            {
                            case SIGCHLD:
                                cout << "SIGCHLD\n";
                                break;
                            case SIGQUIT:
                                stop_server = true;
                                break;
                            case SIGTERM:
                                cout << "SIGTERM\n";
                            case SIGINT:
                                cout << "别按了,休想终止我!\n";
                            }
                        }
                    }
                }
                else if(ev->event.events & EPOLLIN)
                {
                    ev->read_cb(ev);
                }
                else if(ev->event.events & EPOLLOUT)
                {
                    ev->write_cb(ev);
                }
            }
        }

    }

    close(fd);
    close(pipefd[0]);
    close(pipefd[1]);

    return 0;
}

8、网络编程相关信号

8.1、SIGHUP

​ 当挂起进程的控制终端时,SIGHUP信号将被触发。对于没有控制终端的网络后台程序而言,它们常利用SIGHUP信号来强制服务器重新读取配置文件。

8.2、SIGPIPE

​ 默认情况下,往一个读端关闭的管道或socket连接中写入数据将引发SIGPIPE信号,程序收到SIGPIPE信号的默认处理方式是结束进程,而我们绝不希望因为错误的写操作而导致进程退出,因此我们需要在代码中捕获并处理该信号,或者至少忽略它。

​ 我们可以使用send函数的MSG_NOSIGNAL标志来禁止写操作触发SIGPIPE信号。在这种情况下我们应该使用send函数反馈的errno值来判断管道或者socket连接的读端是否已经关闭。

8.3、SIGALRM

由alarm和setitimer函数设置的定时器一旦超时,将触发SIGALRM信号,因此我们可以利用该信号的信号处理函数来处理定时任务。SIGALRM的默认处理是终止进程,而且每个进程都有且只有唯一个定时器。

相关函数如下:

#include 
unsigned int alarm(unsigned int seconds);         // 返回0或剩余的秒数,无失败

#include 
// us级别的定时,可以实现周期定时
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);  
struct itimerval {
	struct timeval it_interval; /* 用来设定两次定时任务之间间隔的时间。 */
	struct timeval it_value;    /* 定时的时长 */
};

 struct timeval {
	time_t      tv_sec;         /* seconds */
	suseconds_t tv_usec;        /* microseconds */
};

8.4、SIGCHLD

子进程结束运行,其父进程会收到SIGCHLD信号。该信号的默认处理动作是忽略。可以捕捉该信号,在捕捉函数中完成子进程状态的回收。

你可能感兴趣的:(C++,linux,linux,c++,后端)