Linux-9-信号

信号

  • 前言
  • 生活中的信号:
    • 红绿灯:
    • 电话铃:
    • 闹钟:
  • Linux下的信号:
    • 信号种类:
      • 查看所有信号:
      • 查看单个信号:
    • 信号存储:
      • task_struct:
      • 实时信号和普通信号区别:
    • 信号发生:
      • 1. 键盘组合键:
        • Ctrl + c:
        • Ctrl + \:
        • 其他常用组合键:
      • 2. 异常:
        • 硬件错误:
      • 3. 系统调用接口:
        • signal()自定义:
        • sigaction()自定义:
        • raise()自触发:
        • alarm()闹钟触发:
        • kill()发送信号:
        • abort()强制终止:
      • 4. 软件条件:
    • 信号处理:
      • 1. 信号生命周期:
      • 2. 信号处理流程:
      • 3. 内核态和用户态:
        • 进程内核地址空间:
        • 进程状态寄存器:
        • 进程状态切换:
        • 信号处理的时机:
      • 4. 调用接口设置屏蔽集:
        • sigset_t:
        • sigprocmask():
        • sigemptyset():
        • sigfillset():
        • sigaddset():
        • sigpending():
        • sigismember():
        • 阻塞前后未决位图的变化:
  • SIGCHLD信号:
    • 三种子进程回收策略:
      • 父进程阻塞等待:
      • 父进程偶尔轮询:
      • 子进程发送终止信号:
    • SIGCHLD处理方法:
      • 自定义处理函数:
      • 系统默认/忽略:
  • 执行流安全隐患:
    • 可重入函数:
    • volatile关键字:

前言

Vue框架:Vue驾校-从项目学Vue-1
算法系列博客友链:神机百炼

生活中的信号:

红绿灯:

  • 背景:过马路非绿灯时人类进行等待,绿灯出现时人类开始走斑马线
  • 原理:
    1. 人类可以识别信号:记住了信号特征
    2. 人类本身就知道收到信号后如何处理
  • 程序设计:
    1. 程序员设计进程时候,已经在OS内置了默认信号处理方案,且信号属于进程内部特有的特征

电话铃:

  • 背景:听到电话铃声,人类放下手头工作去接电话
  • 原理:
    1. 信号产生和进程正常运行处于异步关系
    2. 异步关系:两者只有竞争关系,相互独立
    3. 同步关系:两者有依赖关系,相互依赖
  • 程序设计:进程处理信号的三种方式
    1. 默认行为:如暂停/继续/终止
    2. 自定义行为:自写函数取而代之
    3. 忽略信号:

闹钟:

  • 背景:听到闹钟响起,人类没有关,继续睡觉
  • 原理:
    1. 时间窗口:信号产生但是未被处理,仅作一次时间窗口的记录
  • 程序设计:
    1. 信号可以暂时被进程保存起来

Linux下的信号:

信号种类:

查看所有信号:

  • 命令:
kill -l
  • 所有信号:注意信号下标从1开始
#1~31号属于普通信号,32~64号属于实时信号
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX	

查看单个信号:

  • 命令:查看某信号所属文件夹
grep -ER 'SIGQUIT' /usr/include/
  • 单个信号所属多个系统文件:
/usr/include/asm-generic/signal.h:#define SIGQUIT		 3
/usr/include/mysql/private/my_config_x86_64.h:/* #undef SIGQUIT */
/usr/include/mysql/my_config_x86_64.h:/* #undef SIGQUIT */
/usr/include/bits/signum.h:#define	SIGQUIT		3	/* Quit (POSIX).  */
/usr/include/asm/signal.h:#define SIGQUIT		 3
/usr/include/valgrind/vki/vki-ppc64-linux.h:#define VKI_SIGQUIT          3
/usr/include/valgrind/vki/vki-darwin.h:#define	VKI_SIGQUIT	SIGQUIT
/usr/include/valgrind/vki/vki-arm-linux.h:#define VKI_SIGQUIT		 3
  • 命令:查看某信号具体内容
vim /usr/include/asm-generic/signal.h
  • 单个信号详细信息:
#define SIGHUP     1
#define SIGINT     2
#define SIGQUIT    3
#define SIGILL     4
#define SIGTRAP    5
#define SIGABRT    6            
#define SIGIOT     6
#define SIGBUS     7
#define SIGFPE     8
#define SIGKILL    9
#define SIGUSR1   10
#define SIGSEGV   11
#define SIGUSR2   12
#define SIGPIPE   13
#define SIGALRM   14
//.......

信号存储:

  • 信号的本质:软中断

task_struct:

  • 进程独立性:为了维护进程的独立性,和文件/虚拟地址空间一样,每个进程需要在PCB中创建单独的数据结构去记录其相关的信号
  • 两大位图+函数指针数组:
    Linux-9-信号_第1张图片
  • 未决位图/Pending bitmap:
    1. 当第i位信号产生时,该进程的Pending bitmap第i位从0置为1
    2. 当第i位信号被处理完毕,该进程的Pending bitmap第i位从1置为0
    3. 未决位图的变化时间:信号产生/处理
  • 阻塞位图/Block bitmap:
    1. 当第i位信号产生后,准备处理前,先查看该进程的Block bitmap第i位,若为0,则该信号被处理
    2. 当第i为信号产生后,准备处理前,先查看该进程的Block bitmap第i为,若为1,则该信号被阻塞
    3. 阻塞位图的变化时间:手动设置
  • 函数指针数组:
    1. 当程序员未自定义第i号信号处理方法,则函数指针数组第i-1个元素为空,OS执行默认处理方法
    2. 当程序员未自定义第i号信号处理方法,则函数指针数组第i-1个元素为函数指针,进程去执行所指函数方法

实时信号和普通信号区别:

  • 背景:

    当一个信号发生,但是未被执行/被阻塞时,Pending bitmap为1

  • 同一信号又一次发生时:

    1. 实时信号:使用链表记录每次信号,遍历链表挨个执行
    2. 普通信号:只记录最新的那次信号,不论Pending bitmap为1期间又发送了几次,只执行一次

信号发生:

  • 四大信号发生来源:

1. 键盘组合键:

Ctrl + c:
  • 本质:向进程发送了一个2号信号 SIGINT
Ctrl + c

kill -2 pid
Ctrl + \:
  • 本质:向进程发送了一个3号信号 SIGQUIT
Ctrl + \

kill -3 pid
其他常用组合键:
  • Ctrl + d 退出当前会话。

  • Ctrl + z 将当前运行的程序放到后台运行。与运行时加&类似

  • Ctrl + s 暂停运行该终端

  • Ctrl + q 退出这种状态,让终端继续运行

2. 异常:

硬件错误:
  • 信号发生本质:硬件发生错误后,OS修改当前进程task_struct中的信号位图
  • 除0错误:状态寄存器中的Z被置为1
  • 野指针错误:
    1. 软件:页表地址映射
    2. 硬件:MMU

3. 系统调用接口:

signal()自定义:
  • 使用范围:OS只允许将部分信号处理方式重新自定义

  • signal():

    #include 
    sighandler_t signal(int signum, sighandler_t handler);
    
  • 参数:

    1. signum:信号宏定义编号
    2. handler:回调函数指针
  • 作用:当signum对应的信号到达时,不采用默认信号处理方法,而采用函数指针handler所指向的函数进行处理

  • 特殊点:被OS调用,而非被main()调用

  • 实例:

  1. 代码:
#include 
#include 
void handler(int signo){
    printf("signal + handler\n");
    return;
}
int main(){
    signal(2, handler);
    while(1){
		printf("running...\n");
        sleep(1);
    }
    return 0;
}
  1. Ctrl + c 运行效果:
    Linux-9-信号_第2张图片
  2. kill -2 pid 运行效果:
    Linux-9-信号_第3张图片
sigaction()自定义:
  • sigaction():

    #include 
    sigaction(int signo,struct sigaction *act, struct sigaction *oldact);
    
  • 作用:自定义修改指定signo号信号的处理方法,并保存原本的信号处理方法

  • 特殊点:被OS调用,而非被main()调用

  • 参数:

    1. 信号编号:针对signo号信号进行自定义函数处理

    2. struct sigaction:函数指针+变量,其实是个类

      struct sigaction{
          void (*sa_handler)(int);	//自定义处理函数
          void (*sa_sigaction)(int, siginfo_t *, void *);	//处理实时信号
          sigset_t sa_mask;	//阻塞集
          int sa_falgs;	//flag==0即可
          void (*sa_restorer)(void);
      };
      
  • 返回值:

    1. 修改成功信号处理方法:0
    2. 修改失败信号处理方法:-1
  • 实例:

  1. 代码:
#include 
#include 
#include 
#include 
struct sigaction act, oldact;
void handler(int signo){
	printf("捕获到%d号信号\n", signo);
    sigaction(SIGINT, &oldact, NULL);
    return ;
}
int main(){
    //1,准备好输入输出变量
    memset(&act, 0, sizeof(act));
    memset(&oldact, 0, sizeof(oldact));
    
    //2,自定义信号处理
	act.sa_handler = handler;	//自定义处理函数
    act.sa_flags = 0;	//flag为0即可
    sigemptyset(&act.sa_mask);	//不设阻塞集
    
    //3,落实对信号处理方法的修改
    sigaction(SIGINT, &act, &oldact);
    while(1){
		printf("进程pid:%d", getpid());
        sleep(1);
    }
    return 0;
}
  1. 运行结果:
    Linux-9-信号_第4张图片
  • 与signal()的区别:

    1. 参数不同:
      1. signal():signo + 新处理函数指针
      2. sigaction():signo + sigaction结构体 + sigaction结构体
    2. 作用不同:
      1. signal():仅自定义signo的处理函数
      2. sigaction():自定义signo的新型处理函数 + 返回signo的原本处理函数
raise()自触发:
  • raise():

    #include 
    int raise(int sig);
    
  • 作用:
    执行该函数时,进程向自己发送sig号信号
    常常搭配signal()自定义处理函数使用

  • 实例:

  1. 代码:
#include 
#include 
void handler(int signo){
	printf("进程id:%d\n", getid());
    return;
}
int main(){
    signal(2, handler);
    while(1){
		sleep(1);
        raise(2);
    }
	return 0;
}
  1. 运行效果 :
    Linux-9-信号_第5张图片
alarm()闹钟触发:
  • alarm():

    #include 
    alarm(int seconds);
    
  • 参数:seconds为秒数,过seconds秒后触发14号的闹钟信号SIGALRM

  • 作用:

    1. 直接使用alarm():过seconds秒后触发SIGALRM信号,终止进程
    2. 自定义使用:过seconds秒后执行自定义的SIGALRM处理行为
  • 实例:粗略查看计算机1s内做多少次加法

    #include 
    #include 
    #include 
    #include 
    int count;
    void handler(int signo){
        printf("count:%d\n", count);
        alarm(1);
        return;
    }
    int main(){
        signal(SIGALRM, handler);
        alarm(1);
        while(1){
            count++;
        }
    	return 0;
    }
    
kill()发送信号:
  • kill():

    #include 
    #Include <signal.h>
    int kill(pid, int signo);
    
  • 作用:

    向指定pid所属进程发送signo信号

  • 实例:

  1. 代码1:被kill的进程
#include 
#include <>
int main(){
    while(1){
        pid_t pid = getpid();
        printf("该进程pid:%d\n",pid);
        sleep(1);
    }
    return 0;
}
  1. 代码2:调用kill的进程
#include 
#include 
#incllude <sys/types.h>
#include 
int main(int argc, char* argv[]){
    if(argc != 3){
        printf("%s正确用法:kill pid signo", argv[0]);
        return -1;
    }
    pid_t pid = atoi(argv[1]);
    int signo = atoi(argv[2]);
    kill(pid, signo);
	return 0;
}
  1. 命令:
./killed	#打印该进程pid
./killtest pid 2 
  1. 运行效果:
    Linux-9-信号_第6张图片
abort()强制终止:
  • abort():

    #include 
    void abort(void)
    
  • 作用:

    终止进程,比exit()更为强制。

    即使通过signal() / sigaction()重写了信号处理方法,之后也会终止进程

  • 原理:

    向进程发送6号信号,SIGABRT

  • 实例:

  1. 代码:
#include 
#include 
#include 
#include 
void handler(int signo){
    printf("%d号信号被捕捉\n",signo);
    return;
}
int main(){
    signal(6, handler);
    while(1){
		printf("pid:%d\n",getpid());
        sleep(1);
        abort();
    }
	return 0;
}
  1. 运行结果:
    Linux-9-信号_第7张图片

  2. 结论:即使自定义捕捉了6号信号,abort()函数还是可以终止进程

4. 软件条件:

信号处理:

1. 信号生命周期:

  • 信号生命周期示意图:
    Linux-9-信号_第8张图片

  • 信号阻塞:被阻塞的信号保持在未决状态,pending位图中该信号为1

    解除阻塞:解除阻塞后,信号开始递达,递达之后pending位图中该信号为0

  • 非实时信号阻塞:

    一个处于阻塞中的非实时信号,未解除阻塞前,如果又继续收到了多次该信号,则解除阻塞后也只能去处理最新的那一个该信号

  • 实时信号阻塞:

    一个处于阻塞中的实时信号,未解除阻塞前,如果又继续收到了多次该信号,则信号陆续存储到队列中,解除阻塞后遍历队列陆续都被处理

2. 信号处理流程:

  • 处理流程示意图:
    Linux-9-信号_第9张图片

3. 内核态和用户态:

进程内核地址空间:
  • 进程的虚拟地址空间:

    1. 用户空间存储进程自己的数据
    2. 内核空间存储操作系统的部分代码
  • 内核页表:维护内核空间和内存中OS的地址映射关系
    Linux-9-信号_第10张图片

  • 所有进程统一由OS调度:所有进程内核空间中的代码和数据一样

  • 内核态和用户态差别:

    1. 用户态下的进程,只能执行其用户空间中的代码
    2. 内核态下的进程,只能执行其内核空间中的部分OS代码,相当于暂时将处理权交递给了OS,可以进行进程切换/信号发送等权限很高的操作
进程状态寄存器:
  • CPU中的寄存器:
    1. CR3寄存器:以标志位记录当前进程处于用户态还是内核态
    2. 页表寄存器:根据CR3寄存器内容切换指向用户级页表地址/内核页表地址
进程状态切换:
  • 本质:cpu中CR3寄存器和页表寄存器内容修改

  • 用户态切换为内核态的时机:

    1. 系统调用:如fopen(),raise(),alarm(),kill(),abort()
    2. 时间片到期,进程切换
    3. 异常/中断/陷阱
  • 内核态切换为用户态的时机:

    1. 系统调用返回
    2. 进程切换完毕
    3. 异常/中断/陷阱处理完毕
信号处理的时机:
  • 信号何时被处理:当进程从内核态切换回到用户态时

  • 信号参与的进程状态切换流程:

    1. 进入内核态:系统调用/中断/异常/陷阱
    2. 内核态下OS先处理系统调用/中断/异常/陷阱
    3. 处理完毕后,查看当前进程的pending bitmap,将默认处理方式的信号处理完毕,若还有信号为自定义处理,则进入用户态运行handler()自定义信号处理
    4. 用户态中的handler()执行完毕,调用sigreturn重返内核态
    5. 内核态下OS调用sys_sigreturn(),返回用户态,从上次中断处继续向下运行
  • 信号参与的进程状态切换流程图:
    Linux-9-信号_第11张图片

  • 问:为什么handler()函数不能直接由内核态执行?

    答:

    1. 物理上:OS属于内核页表映射结果,handler()函数属于用户级页表映射结果

    2. 逻辑上:OS不信任任何用户,防止handler()函数内部含有非法/越权操作

    3. 结论:内核态的进程可以读用户空间内的内容,但是不能执行用户空间内的程序

  • 信号处理流程图汇总:
    Linux-9-信号_第12张图片

4. 调用接口设置屏蔽集:

sigset_t:
  • 本质:类/结构体

    typedef struct {
    	unsigned long sig[_NSIG_WORDS]} sigset_t;	
    
  • 作用:对进程的信号block位图/pending位图某位置为0/1,其中的0/1就是sigset_t

sigprocmask():
  • sigprocmask():

    #include 
    int sigprocmask(int how, const sigset_t *newset, sigset_t *oldset);
    
  • 作用:将本进程的block位图/屏蔽集设置为newset,同时将本进程的原本block位图/屏蔽集输出到oldset中

  • 参数:

    1. how:
      1. SIG_BLOCK:添加阻塞信号
      2. SIG_UNBLOCK:解除阻塞信号
      3. SIG_SETMASK:重设屏蔽字
    2. newset:当前要设置阻塞信号集/屏蔽集的输入位置
    3. oldset:进程原本阻塞信号集/屏蔽集的保存位置,不准备保存原本阻塞信号集可以传参一个NULL
  • 本质:通过系统调用接口让OS修改当前进程task_struct中的block位图

sigemptyset():
  • sigemptyset():

    #include 
    sigemptyset(sigset_t *set);
    
  • 作用:将信号block位图/屏蔽集每一位设置为0,但是只是对变量的内容修改,只有通过sigprocmask()才能对进程生效

  • 用例:

    #include 
    #include 
    int main(){
     	sigset_t set;
        sigemptyset(&set);
        //后续继续对屏蔽集set处理,再通过sigprocmask()函数调用OS系统调用接口,修改本进程的信号屏蔽集
    	return 0;
    }
    
sigfillset():
  • sigfillset():

    #include 
    sigfillset(sigset_t *set);
    
  • 作用:将信号block位图/屏蔽集每一位设置为1,但是只是对sigset_t变量的设置,还未通过sigprocmask()正式修改进程

sigaddset():
  • sigaddset():

    #include 
    sigaddset(sigset_t *set, unsigned int x);
    
  • 作用:将现有的信号block位图/屏蔽集的第x位置为1,标识新增屏蔽x号信号

sigpending():
  • sigpending():

    #include 
    sigpending(sigset_t *set);
    
  • 作用:获取当前进程的pending位图/未决位图

  • 输出型参数:set,将当前进程的未决位图输出到set中(pending位图中0表示该信号未产生,1表示该信号已产生)

  • 使用前提:准备好全0的sigset_t *

    #include 
    #include 
    int main(){
        sigset_t set;
        sigemptyset(&set);
        sigpending(&set);
        //set中此时就是当前进程的未决信号集
    }
    
sigismember():
  • sigismember():

    #include 
    sigismember(sigset_t *pending, int x);
    
  • 作用:返回未决信号集/pending位图中第x位上的0/1

  • 引申:打印当前进程完整的未决信号集

    void printPending(sigset_t *pending){
    	int i = 1;	//-std=c99会带来很多麻烦
        for(i=1; i<32; i++){
    		if(sigismember(pending, i))
                printf("1");
           	else printf("0");
        }
        printf("\n");
    }
    
阻塞前后未决位图的变化:
  • 代码:
#include 
#include 
#include 
void printPending(sigset_t &pending){
    int i = 0;
	for(i=1; i<32; i++){
		if(sigismember(pending, i))	printf("1");
        else printf("0");
    }
    printf("\n");
}
void handler(int signo){
    printf("%d号信号脱离阻塞已经递达\n",signo);
	return;
}
int main(){
    //1.阻塞Ctrl + c信号
    sigset_t set, oldset;
    sigemptyset(&set);
    sigemptyset(&oldset);
    sigaddset(&set, 2);
    sigprocmask(SIG_SETMASK, &set, &oldset);
    
    //2.打印当前未决位图
    sigset_t pending;
    int count = 0;
    while(1){
		sigemptyset(&pending);
        sigpending(&pending);
        printPending(&pending);
        sleep(1);
        
    //3.解除对Ctrl + c信号的block
        if(count>=10){
			sigprocset(SIG_sETMASK, &oldset, NULL);
        }
    }
	return 0;
}
  • 运行结果:
    Linux-9-信号_第13张图片
  • 结论:block变化领先主导了pending变化
    Linux-9-信号_第14张图片

SIGCHLD信号:

  • SIGCHLD:
    1. 编号:17
    2. 作用:子进程退出时为父进程发送的信号

三种子进程回收策略:

父进程阻塞等待:

  • waitpid:

    int waitpid(pid_t pid, int *status, int options);
    //等待成功:返回子进程pid
    //等待失败:返回-1
    
  • options == 0时,父进程阻塞等待pid号子进程

父进程偶尔轮询:

  • waitpid:

    int waitpid(pid_t pid, int *status, int options);
    //等待成功:返回子进程pid
    //等待失败:返回-1
    
  • options == WNOHANG时,父进程大部分时间运行自己的程序,偶尔轮询所有子进程是否终止

子进程发送终止信号:

  • SIGCHLD:

    1. 子进程终止时给父进程发送SIGCHLD信号

    2. OS对该信号的默认处理函数为忽略

    3. 父进程也可自定义该信号的处理函数

  • 父进程自定义处理函数:

    由于要清理僵尸子进程,所以还是父进程的处理函数还是需要调用wait() / waitpid()

SIGCHLD处理方法:

自定义处理函数:

  • 代码:
#include 
#include 
#include 
#include 
#include 
void handler(int signo){
    int status;
    pid_t pid;
  while(pid = waitpid(-1, &status, WNOHANG) > 0){
      printf("%d号子进程退出\n", pid);
      printf("进程收到信号为:%d\n", status&0x7f);                     
      printf("进程coredump为:%d\n", status&0x80);
      printf("进程退出码为:%d\n", (status>>8)&0xff);
    }
    return ;
  }    
  int main(){
    signal(17, handler);          
    if(fork() == 0){
      printf("%d号子进程开始运行\n", getpid());
      sleep(5);
      exit(100);
    }
    while(1);
    return 0;
  }
  • 运行结果:
    Linux-9-信号_第15张图片

系统默认/忽略:

  • 系统默认处理方法:
signal(17, SIG_DEL);	//默认
signal(17, SIG_IGN);	//忽略
  • 作用:子进程终止后,立即由OS执行回收
    1. 不产生僵尸进程
    2. 不向父进程发送通知信号
  • 代码:
#include     
#include     
#include     
#include     
#include     
int main(){    
  signal(17, SIG_IGN);    
  if(fork() == 0){    
    printf("%d号子进程正在运行\n", getpid());    
    sleep(3);                                                                                   
    exit(100);    
  }    
  while(1);    
  return 0;    
} 
  • 运行结果:
  1. 不断观察进程状态:
while :; do ps axj | grep sigchld  | grep -v grep;echo "######################" sleep 1;done
  1. 子进程终止后未经历Z状态:
    Linux-9-信号_第16张图片

执行流安全隐患:

  • 执行流 != 进程:
    1. main()函数本身为一个执行流
    2. 每处理一次信号,新增一个执行流

可重入函数:

  • 先了解一下不可重入函数:
#include 
#include 
#include 
struct sigaction act, oldact;
struct ListNode{
    int val;
    struct ListNode* next;
};
struct ListNode *head = (struct ListNode*) malloc(sizeof(ListNode));
void handler(){
    struct ListNode *node = (struct ListNode*) malloc(sizeof(ListNode));
	insert(node);
    printf("信号处理函数插入一个新节点\n");
    return;
}
void insert(struct ListNode* node){
    node->next = head;
    head = node;
	return;
}
int main(){
    memset(&act, 0, sizeof(act));
    memset(&oldact, 0, sizeof(oldact));
    act.sa_handler = handler;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    sigact(2, &act, &oldact);
    struct ListNode *node = (struct ListNode*) malloc(sizeof(ListNode));
	insert(node);
    printf("main函数插入一个新节点\n");
	return 0;
}
  • 潜在危险:内存泄露
    Linux-9-信号_第17张图片

  • 可重入函数:

    1. 定义:一个函数被多个执行流调用时可能出现内存泄露等问题
    2. 范围:STL容器,系统调用接口
    3. 函数优秀指标:可否重入并非是否优秀的指标

volatile关键字:

  • 编译器优化级别:

    1. gcc -O3:编译器优化最高级
    2. gcc -OS:在-O2基础上又进行了很多优化
    3. gcc -O2:寄存器级的优化,指令级的优化
    4. gcc -O1:优化代码的分支,常量以及表达 式
    5. gcc -O0:默认的编译选项,不做任何优化
  • CPU寄存器级别优化实例:

  1. 代码:
#include 
#include 
#include 
int flag = 0;
void handler(int signo){
	printf("get %d signal!\n",signo);
    flag = 1;
    return;
}
int main(){
    signal(2, handler);
    //flag为0时打印,发送Ctrl + c号信号后,flag==1,停止打印
    while(!flag){
		printf("flag == %d\n", flag);
        sleep();
    }
	return 0;
}
  1. 运行结果:

    • 若编译器优化程度不高:
      Linux-9-信号_第18张图片

    • 若编译器优化程序很高:
      Linux-9-信号_第19张图片

  2. 原理分析:

    • 比较运算的执行:
      Linux-9-信号_第20张图片

    • 编译器优化程度低:

      每次都从内存中取出变量flag值,放入CPU比较器中,和0比较

    • 编译器优化程度高:

      main()函数执行流中不含flag值修改语句,所以flag值仅放入一次CPU的比较器中,之后用到flag值都直接从比较器的寄存器中取。而SIGINT信号属于另一执行流,修改的是内存中的flag值,不再输入CPU的比较器中了

  • volatile关键字:
  1. 作用:
    被volatile关键字修饰的变量,禁止直接进行CPU寄存器级别检测,每次使用前都必须从内存中重新加载入CPU的寄存器中
  2. 代码:
#include 
#include 
#include 
volatile int flag = 0;
void handler(int signo){
  printf("get a %d signal\n", signo);
  flag = 1;
  return;
}
int main(){
  signal(2, handler);
  while(!flag){
    printf("flag == %d\n", flag);
    sleep(1);
  }
  return 0;
}
  1. 运行结果:
    Linux-9-信号_第21张图片

你可能感兴趣的:(风后奇门,-,Linux,linux,运维,服务器,信号,执行流)