Linux进程信号

Linux进程信号

信号是一个软件中断。操作系统通过信号告诉进程发生了某个事件,打断进程当前的操作,去处理这个事件。

1.信号的查看
kill -l	//查看系统中的信号种类

在Linux操作系统中,一共有62中信号。

1~31号信号:从unix借鉴而来的,每个信号都有具体对应的系统事件------非可靠信号。

34~64号信号:后期扩充的,因为没有具体对应事件,因此命名比较草率------可靠信号。

信号的生命周期:产生->在进程中注册->在进程中注销->处理

2.信号的产生

硬件条件产生信号:

ctrl+c / ctrl+z / ctrl+|

软件条件产生信号:

kill -signum pid//命令

kill杀死一个进程的原理就是向进程发送一个信号,信号有对应的事件,进程放下手头工作去处理这个事件,然而事件的处理结果就是让进程退出。kill默认发送15号信号。

信号产生函数
int main()
{
	//kill(getpid(), SIGHUP);//给指定进程发送指定的信号
	//raise(SIGINT);//给进程自己发送指定的信号
	//abort();//给自己发送SIGABRT信号-通常用于异常通知
	alarm(3);//三秒钟之后给进程自己发送SIGALRM信号
	while(1)
	{
		printf("hello world\n");
		sleep(1);
	}
	return 0;
}
3.信号在进程中的注册

怎么让进程知道自己收到了某个信号。pcb->struct sigpending->struct sigset_t。

sigset_t这个结构体中有一个数组成员,这个数组用于实现一个位图。给进程发送一个信号,就会将这个位图中对应位置1,表示当前进程收到了这个信号。位图只能表示是否接收到了这个信号,不能表示收到了多少个这样的信号。

新号的注册不仅会修改位图,还会为信号组织一个sigqueue节点添加到pcb的sigqueue链表中。

1~31号非可靠信号的注册:若信号注册的时候位图为0,则会创建一个sigqueue节点并修改位图为1,但若是位图为1,则什么都不做。

34~64号可靠信号的注册:不管位图当前是多少,都会创建一个节点,添加到链表中,并修改位图为1。

4.信号在进程中的注销

为了保证一个信号不会被多次处理,因此信号是先注销再处理,在pcb中删除当前信号信息。

非可靠信号的注销:因为非可靠信号只会有一个节点,因此删除节点后,位图直接置0。

可靠信号的注销:因为可靠信号有可能多次注册,有多个节点,因此删除节点后,需要判断是否还有相同节点,若没有才会将位图置0。

5.信号的处理

信号表示一个事件的到来,处理事件就是完成功能,在c语言中完成一个功能的最小模块–函数。

其实每一个信号都对应有自己的事件处理函数,信号到来去处理这个事件就是去执行这个处理函数。

信号的处理方式

1.默认处理方式:操作系统中原定义好的每个信号的处理方式。

2.忽略处理方式:处理方式就是忽略,什么都不做。

3.自定义处理方式:自己定义一个事件函数,使用这个函数替换内核中默认的处理函数,信号到来就会调用我们所定义的函数了。

信号处理函数
void sigcb(int signo)
{
	printf("signal no:%d\n", signo);
}
int main()
{
    //signal(SIGINT, SIG_DFL);//当前信号到来的时候,采用默认处理方式
	//signal(SIGINT, SIG_IGN);//当前信号到来的时候,采用忽略处理方式
	signal(SIGINT, sigcb);//当前信号到来的时候调用sigcb这个函数,并且通过参数传入触发回调函数的信号值
	while(1)
	{
		printf("hello world\n");
		sleep(1);
	}
	return 0;
}
6.信号的阻塞

并不是不接收信号,信号依然可以注册,只是标识哪些信号暂时不处理。

在pcb中有一个位图,位图叫block位图—阻塞信号集合,这个集合中的信号如果来了则暂时不处理。

void sigcb(int signo)
{
	printf("signal no:%d\n", signo);
}
int main()
{
	signal(SIGINT, sigcb);
	signal(SIGRTMIN+4, sigcb);
	sigset_t set;
	sigemptyset(&set);//清空集合,防止位置数据造成影响
	sigfillset(&set);//向集合中添加所有信号
	sigprocmask(SIG_BLOCK, &set, NULL);//阻塞set集合中的所有信号
	printf("press enter continue");
	getchar();//等待一个回车,不按回车一直卡在这里	
	sigprocmask(SIG_UNBLOCK, &set, NULL);//解除set集合中的信号阻塞
	while(1)
	{
		sleep(1);
	}
	return 0;
}

在所有的信号中,有两个信号比较特殊:SIGKILL-9/SIGSTOP-19,这两个信号不可被阻塞,不可被忽略,不可被自定义。

7.SIGCHLD和SIGPIPE信号

子进程退出后会向父进程发送SIGCHLD信号通知父进程。

但是因为SIGCHLD信号默认的处理方式是忽略,因此之前的程序中若不进行进程等待则不知道子进程的退出。

学了信号之后,如果进行进程等待,而且还不想让父进程阻塞,就可以自定义SIGCHLD信号的处理方式。

void sigcb(int signo)
{
	int pid;
	while((pid = waitpid(-1, NULL, WNOHANG) > 0))
    //SIGCHLD信号是一个非可靠信号,如果有多个子进程同时退出,有可能造成信号丢失
    //非阻塞循环在一个回调中将所有的僵尸进程全部处理
	{
		printf("%d\n", pid);
	}
}
int main()
{
	signal(SIGCHLD, sigcb);
	pid_t pid = fork();
	if(pid == 0)
	{
		sleep(5);
		exit(66);
	}
	pid = fork();
	if(pid == 0)
	{
		sleep(10);
		exit(66);
	}
	while(1)
	{
		printf("忙碌\n");
		sleep(1);
	}
	return 0;
}

在进程间通信中,我们使用管道时,所有读端被关闭,则继续写入和套接字tcp断开连接会触发异常—SIGPIPE信号。也可以通过自定义SIGPIPE信号的处理方式来解决。

8.volatile关键字

用于修饰一个变量,保持变量的内存可见性,(cup在处理的时候每次都重新从内存中获取数据),防止编译器过度优化。

在编译程序的时候,如果使用了代码优化,发现使用某个变量的频率特别高,为了提高效率会将变量的值设置为某个寄存器的值,以后访问数据的时候直接从寄存器访问,减少了内存访问的过程,提高效率。

但是这会造成代码的逻辑混乱,因为读取变量的值时是从内存中读取,但是变量的值设置为寄存器的值,改变这个变量的值,只会让寄存器中的值改变,内存中的值并没有改变。

9.函数的重入

在多个执行流程中,同时进入一个函数运行。

函数可重入:指的是函数重入之后,不会造成数据二义或者逻辑混乱。

函数不可重入:指的是函数重入之后,有可能造成数据二义或逻辑混乱。

函数是否可重入的判断基准:这个函数是否对全局变量进行了非原子的操作,若有则不可重入。

操作的原子性:操作一次性完成,中间不会被打断。

原子操作:操作要么一次完成,要么就不做。

一个函数若根本没有操作全局数据,或者操作全局数据但是操作是原子性的,则是可以重入的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值