Linux网络编程笔记6 | 信号

十一、信号

1.基本概念

1)什么是信号?

                事件(信号)
过程(进程)1---------v--------->
                  /  \
                 /事件 \
过程(进程)2      /---v---\
                   /  \
                  /    \
过程(进程)3      /--- ---\

信号是提供异步事件处理机制的软件中断。这些异步事件可能来自硬件设备,也可能来自系统内核,甚至可能来自用户程序。进程之间可以相互发送信号,这使信号成为一种进程间通信(Inter-Process Communication, IPC)的基本手段。信号的异步特性不仅表现为它的产生是异步的,对它的处理同样也是异步的。程序设计者不可能也不需要精确地预见什么时候触发什么信号,也同样无法预见该信号究竟在什么时候会被处理。一切尽在内核操控下异步地发生。

2)什么是信号处理?
每一个信号都有其生命周期:
产生:信号被生成,并被发送至系统内核
未决:信号被内核缓存,而后被递送至目标进程
递送:内核已将信号发送至目标进程
忽略 - 什么也不做。
捕获 - 暂定当前的执行过程,转而调用一个事先写好的信号处理函数,待该函数完成并返回后,再继续之前被中断的过程。
默认 - 既不忽略该信号,也不用自己定义处理方式,而是按照系统默认的方式予以响应。
激励(信号)->响应(信号处理)

3)信号的名称和编号
信号名称:形如SIGXXX的字符串或宏定义,提高可读性。
信号编号:整数
通过kill -l命令查看当前系统所支持的全部信号名称及其编号。
1~31,31个不可靠信号,也叫非实时信号。
34~64, 31个可靠信号,也叫实时信号。
共62个信号,注意没有32和33信号。
SIGHUP(1),控制终端关闭,终止
SIGINT(2),用户产生中断符(Ctrl+C),终止
SIGQUIT(3),用户产生退出符(Ctrl+),终止+转储
SIGBUS(7),硬件或内存对齐错误,终止+转储
SIGKILL(9),不能被捕获和忽略,终止
SIGSEGV(11),无效内存访问,终止+转储
SIGPIPE(13),向读端已关闭的管道写入,终止
SIGALRM(14),alarm函数设置的闹钟到期,终止
SIGTERM(15),可被捕获和忽略,终止
SIGCHLD(17),子进程终止,忽略
SIGIO(29),异步I/O事件,终止
举例:写一个死循环

#include <stdio.h>
#include <unistd.h>
int main(void) {
   
    printf("PID: %d\n", getpid());
    for (;;);
    return 0;
}

kill -SIGQUIT 进程pid 进程pid …
kill -SIGQUIT -1 给所有进程发QUIT信号

2.捕获信号

#include <signal.h>
typedef void (*sighandler_t) (int);
|
函数指针
指向一个接受整型参数且无返回值的函数
设置针对特定信号的处理方式,即捕获特定的信号:
sighandler_t signal(int signum, sighandler_t handler);
成功返回原信号处理方式,失败返回SIG_ERR(sighandler_t类型的-1)。
signum - 信号编号
handler - 信号处理函数指针,也可以取以下值:
SIG_IGN - 忽略信号
SIG_DFL - 默认操作

// 定义信号处理函数
void sigint (int signum) {
SIGINT(2)信号的处理代码
}

// 捕获SIGINT(2)信号
if (signal(SIGINT, sigint) == SIG_ERR) {
perror(“signal”);
return -1;
}

                        SIGINT(2)
                          v
SIGINT(2)/PID/sigint->系统内核
                          v
                   目标进程中的sigint函数

signal(SIGINT, SIG_DFL); // 按默认方式处理
signal(SIGINT, SIG_IGN); // 忽略信号
当一个信号正在被处理的过程中,相同的信号再次产生,该信号会被阻塞,直到前一个信号处理完成,即从信号处理函数中返回,后一个被阻塞的信号才被递送,进而再次执行信号处理函数。当一个不可靠信号正在被处理的过程中,多个相同的信号再次产生,只有第一个信号会被阻塞,其它信号直接丢弃,如果是可靠信号,都会被阻塞,并按照产生的顺序依次被递送。
信号处理函数及被其调用的函数都有可能发生重入,由此可能引发无可预知的风险。
global = 0;

++global;

所有标准I/O函数都是不可重入函数。在信号处理的过程中要慎用。

A信号->A信号处理函数 \ 打印AAAAAA
                                 > printf(调试信息);
B信号->B信号处理函数 / 打印BBBBBB
AAABBBBBBAAA

代码:signal.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
void sigint(int signum) {
   
    signal(SIGINT, SIG_DFL);//一次性问题
    printf("%d进程:收到%d信号!\n",
        getpid(), signum);
    signal(SIGINT, sigint);//不让系统出现一次性问题
}
void sigterm(int signum) {
   
    printf("%d进程:收到%d信号!\n",
        getpid(), signum);
    printf("妥当善后...\n");
    exit(0);
}
int main(void) {
   
    if (signal(/*2*/SIGINT, sigint) == SIG_ERR) {
   
        perror("signal");
        return -1;
    }
    if (signal(/*3*/SIGQUIT, SIG_IGN) ==
        SIG_ERR) {
   
        perror("signal");
        return -1;
    }
    if (signal(/*9*//*SIGKILL*//*15*/SIGTERM,
        /*SIG_IGN*/sigterm/*SIG_DFL*/)==SIG_ERR){
   //9信号不能被忽略,也不能捕获
        perror("signal");
        return -1;
    }
    for (;;);
    return 0;
}

3.信号捕获流程

                      中断
主控制流程---------      v     ----------->
                 /             \
信号处理函数    / ------>        \       用户空间
             /          \     /  \     ---------
            /            \   /     \    内核空间
内核处理流程-----------> ---------->
           do_signal      system_call
              V                V
           handle_signal  sys_sigreturn
               V               V
           setup_frame  restore_sigcontext

信号的本质是一个中断的处理过程,而非多线程的并发过程。

线程安全的函数未必是可重入函数。
    V                    V
 锁机制                 局部化

4.信号捕获的一次性问题

在某些非Linux操作系统上,存在信号捕获的一次性问题:
即使设置了对某个信号的捕获,只有设置后的第一个该信号被递送时,信号处理函数会被执行,以后再来相同的信号,均按默认方式处理。如果希望对信号的捕获具有持久性,可以在信号处理函数返回前再次设置对该信号的捕获。

5.太平间信号

通过SIGCHLD(17)信号高效地回收子进程僵尸。
高效:及时性,适时性。
代码:sigchld.c

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
void sigchld(int signum) {
   
    for (;;) {
   //为了收集所有僵尸,故循环	
        pid_t pid = waitpid(-1, NULL, WNOHANG);//非阻塞
        if (pid == -1) {
   
            if (errno != ECHILD) {
   
                perror("wait");
                exit(-1);
            }
            printf("父进程:所有子进程"
                "都已终止。\n");
            break;
        }
        if (!pid) {
   
            printf("父进程:暂时没有可回收的"
                "子进程。\n");
            break;
        }
        printf("父进程:%d子进程终止。\n", pid);
    }
    usleep(100000);//为了观察现象
}
int main(void) {
   
    if (signal(SIGCHLD, sigchld) == SIG_ERR) {
   
        perror("signal");
        return -1;
    }
    for (int i = 0; i < 5; ++i) {
   
        pid_t pid = fork();
        if (pid == -1) {
   
            perror("fork");
            return -1;
        }
        if (pid == 0) {
   
            printf("子进程:我是%d进程。"
                "我要退出了。\n", getpid());
            return 0;
        }
    }
    getchar();
    return 0;
}

6.信号处理的继承与恢复

1)fork/vfork函数创建的子进程会继承父进程的信号处理方式,直到子进程调用exec函数创建新进程替代其自身为止。
2)exec函数创建的新进程会将原进程中被设置为捕获的信号还原为默认处理。在原进程中被忽略的信号于新进程中继续被忽略。
代码:fork.c

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void sigint(int signum) {
   
    printf("%d进程:收到%d信号!\n",
        getpid(), signum);
}
int main(void) {
   
    if (signal(SIGINT, sigint) == SIG_ERR) {
   
        perror("signal");
        return -1;
    }
    if (signal(SIGQUIT, SIG_IGN) == SIG_ERR) {
   
        perror("signal");
        return -1;
    }
    pid_t pid = fork();
    if (pid == -1) {
   
        perror("fork");
        return -1;
    }
    if (pid == 0) {
   
        printf("%d进程:我是子进程,"
            "正在运行中...\n", getpid());
        for (;;);
        return 0;
    }
    sleep(1);
    printf("%d进程:我是父进程,马上要退出。\n",
        getpid());
    return 0;
}

代码:exec.c

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void sigint(int signum) {
   
    printf("%d进程:收到%d信号!\n",
        getpid(), signum);
}
int main(void) {
   
    if (signal(SIGINT, sigint) == SIG_ERR) {
   
        perror("signal"
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值