Linux系统编程(14)——进程信号(全知识)-捕捉信号/流程、阻塞信号、volatile、可重入函数

信号:类似生活中,看到红绿灯就知道走还是不走

信号具有的特点:

  • 信号有很多种
  • 不同的信号有不同的处理方式
  • 在信号发生之前,每种信号如何处理,我们都知道

kill

kill -l(小写L):查看Linux所有的信号

一共 62 种信号。1-31;34-64;

函数:

int kill(pid_t pid, int sig);  

参数:

pid_t pid:进程的id, int sig:进程的信号

返回值:

成功:0   失败:-1

 作用:

信号的作用,就相当于神经系统,用来处理进程运行过程中遇到的各种“意外”情况。

【重点】信号产生方式:

  • 键盘按键产生:ctrl + c (2信号) ;ctrl + \ (3号信号);ctrl + z(19号信号)
  • 硬件产生:

MMU 硬件产生的信号 ->产生 11 号信号段错误信号

CPU产生的信号 ->产生 8 号信号 

  • 软件条件产生的信号

PIPE 读端关闭,尝试写,此时会触发SIGPIPE 信号,管道破裂

TCP 一定条件下也会触发 SIGPIPE 信号

  • 系统调用产生

kill 命令

 信号处理方式:

  • 忽略
  • 默认处理:大部分信号的默认行为都是终止进程
  • 捕捉信号:通过自定义的行为来处理信号

捕捉信号函数:

typedef void(*sighandler_t)(int);     //创建函数指针

sighandler_t signal(int signum, sighandler_t handler);

头文件:

#include<signal.h>

参数:

signum:对几号信号进行修改

sighandler_t handler:捕捉之后,进行什么处理

//自定义信号处理
//signal 函数  :捕获信号
typedef void(*sighandler_t)(int);//创建函数指针
sighandler_t signal(int signum, sighandler_t handler);
//signum   对几号信号进行修改
//sighandler_t handler     捕捉之后,进行什么处理

void myhandle(int signo) {   //自定义方式
	printf("signo= %d\n", signo);
}

int main() {
	//signal   替换了原来的处理方式,就会通过返回值来返回
	signal(2, myhandle);  //2号信号  ctrl c  变成  打印signo= 2
	signal(3, myhandle);  //3号信号  ctrl \ 

	while (1) {  //测试 会一直sleep
		sleep(1);
	}
	return 0;
}

 阻塞信号


可重入函数:

一个函数可重入,就意味着多个执行流中调用没问题。

一个函数不可重入,就意味着多个执行流中调用有问题。

可重入:函数运行一半,又调用函数,再次调用就是再次进入函数。

是否可重入判断标准:

如果一个函数使用了全局变量/静态变量——不可重入。

如果函数使用了 malloc ,不可重入。

如果一个函数调用了不可重入函数,也是不可重入的。

注意:如果一个函数虽然用了全局变量,但是使用了互斥锁,就是可重入函数——这个说法是错误的。

volatile关键字:

功能:保持内存可见性

//自定义信号处理
//signal 函数  :捕获信号
typedef void(*sighandler_t)(int);//创建函数指针
sighandler_t signal(int signum, sighandler_t handler);
//signum   对几号信号进行修改
//sighandler_t handler     捕捉之后,进行什么处理


#include<signal.h>
int flag = 1;  

//volatile
void myhandle(int sig) {
	(void)sig;
	flag = 0;
}

int main() {
	//signal   替换了原来的处理方式,就会通过返回值来返回
	signal(2, myhandle);  //2号信号  ctrl c  变成  函数功能
	while (flag);
	return 0;
}

注意:这个代码 flag 没有用关键字 volatile,代码的结果是:你按 ctrl c,结果无效。

原因:编译器的代码有关。

由于 while 循环频繁读取 flag 到寄存器中,编译器认为这是一个比较高的开销,另外代码没有检测到哪个代码要修改flag(myhandle 函数内核调用,编译器不知道),编译器就会有错误的判断,把 flag 这个值优化到寄存器中。

#include<stdio.h>
#include<unistd.h>
#include<signal.h>

volatile int flag = 1;  
//告诉编译器每次都从内存中读取,不优化到寄存器中
//功能:保持内存可见性

//volatile
void myhandle(int sig) {
	(void)sig;
	flag = 0;
}

int main() {
	//signal   替换了原来的处理方式,就会通过返回值来返回
	signal(2, myhandle);  //2号信号  ctrl c  变成  函数功能
	while (flag);
	return 0;
}

加上volatile,就可以了。

另外:

volatile:经常使用在多线程程序中,编译器对于对多执行流的情况不太会判断。

信号其他知识:

wait 阻塞等待

waitpid 阻塞/非阻塞等待

阻塞等待:代码简单,效率低

非阻塞等待:代码复杂,效率高

如要满足代码简单,效率高:利用17号信号 SIGCHLD

void myhandle(int sig) {
	(void)sig;
	printf("child exit\n");
	wait(NULL);
}

int main() {
	signal(17, myhandle);  //17号信号SIGCHLD  ctrl c  变成  函数功能
	pid_t ret = fork();
	if (ret > 0) {
		//father
		while (1) {
			sleep(1);
		}
	}
	else if (ret == 0) {
		//child
		exit(0);
	}
	else {
		perror("fork");
		return 1;
	}
	return 0;
}

子进程正常退出,不会僵尸进程,各干各的。 结果显示:child exit ,父进程会继续sleep。这是一种方法。


#include<stdio.h>
#include<unistd.h>
#include<signal.h>

int main() {
	signal(17, SIG_IGN);  //17号信号SIGCHLD  子进程直接忽略(SIG_IGN)
	for (int i = 0; i<20; i++) {
		pid_t ret = fork();
	 if (ret == 0) {
			//child
		 printf("child pid %d\n", getpid());
		}
	 while (1) {
		 sleep(1);
	 }
	}
	
	return 0;
}

这是避免僵尸进程最好的方法:子进程直接忽略(SIG_IGN),即:signal(SIGCHLD  , SIG_IGN)。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值