【操作系统---16】进程信号


信号:

是一个软中断;通知进程发生了某件事情,中断进程当前操作;让进程处理这个事件

信号有很多种—Linux下有62种,非可靠信号1-31,可靠信号34-64;进程还必须识别这些信号

kill杀死进程的原理:

向进程发送信号通知事件

信号生命周期:

信号的产生–>在进程中注册–>在进程中注销–>信号的处理


信号的产生:

		硬件产生:ctrl+c(中断信号)	ctrl+l(退出信号)	ctrl+z(停止信号)

		软件产生:kill -signum -p pid		int kill(pid_t pid, int sig);			
	
				raise(signm)			abort()			alarm(nsec)

信号在进程中的注册:

在进程pcb中做标记(task struct中的struct pending),标记进程收到了哪些信号

		非可靠信号如果未注册,位图修改为1,向sigqueue添加一个节点,如果位图已经从0变为1,

		再来一个不做任何操作,只注册一次(事件丢失)

		可靠信号位图已经为1,再来一个同样的信号,会在sigqueue中在添加一个节点(不管信号

		是否已经注册)---(事件不会丢失)

信号在进程中的注销:

		非可靠信号:节点只有一个,删除节点位图置0

		可靠信号:节点可能有多个,删除一个节点之后,判断连表中是否还有相同信号的节点

			  	若没有,位图置0,否则位图不变,依然需要标记有信号处理

未决:是一种状态—信号从产生到处理之前的状态


信号的处理:

信号的处理并不是立即被处理;而是选择一个合适的时机去处理(进程的运行从内核态返回用户态的时候)

		Q:进程如何从用户态切换到内核态?

		A:发起系统调用、系统异常、中断

处理方式:

		默认处理方式:即定义好的处理方式

		忽略处理方式:处理动作中什么都不做(依然会注册,会处理)

		自定义处理方式:用户自己确定信号如何处理---自定义信号的处理函数替换原有的处理函数

接口:

		sighandler_t signal(int signum, sighandler_t handler);

		signum:信号编号---替换signum这个信号的处理函数

		handler:函数指针,用户传入的处理函数		SIG_DFL:信号的默认处理动作

												SIG_IGN:信号的忽略处理动作

		typedef void(*_signal_handler)(int);

		int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

休眠状态中如果有信号到来,会打断休眠的操作

自定义处理方式的捕捉流程:

		发起系统调用--->切换到内核态(调用功能完成)--->do signal()处理未处理的信号--->sigcb()

		信号自定义回调完毕--->sigreturn返回内核态--->(如果有信号还没处理继续调用do signal();)

		没有信号待处理就sys_sigreturn返回用户态

代码示例:

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

void sigcb(int signo)
{
	    printf("signo is : %d\n",signo);
}

int  main()
{
	    //signal(SIGINT,SIG_IGN);
	
	    //int sigaction(int signum, const struct sigaction *act,
                    	 //struct sigaction *oldact);
		     
	    struct sigaction new_act,old_act;

	    new_act.sa_flags=0;
	    
		//处理方式替换为sigcb的方式,每次输入要替换的信号,都会执行sigcb函数
	    new_act.sa_handler=sigcb;

	    __sigemptyset(&new_act.sa_mask);

	    //将2号信号的动作用new_act替换掉,old_act用来保存原来的信号集合
	    sigaction(2,&new_act,&old_act);

    
	    while(1)
	    {
			printf("i love luzihan ~\n");
			sleep(3);
	    }
	    return 0;
}

信号的阻塞:

阻止信号被递达—信号依然可以被注册,只是暂时不处理

递达:一个动作—信号的处理

在pcb中还有一个集合—阻塞信号集合—标记哪些信号暂时不被处理

pending位图 block位图 handler位图

接口:

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

		参数第二个传地址&set;

		SIG_BLOCK:向阻塞集合中加入set集合中的信号	block | set

		SIG_UNBLOCK:从阻塞集合中移除set集合中的信号	block &(~set)

		SIG_SETMASK:将set集合中的信号设置为阻塞集合

		oldset:保存原先的集合

		int sigfillset(sigset_t *set);

		int sigaddset(sigset_t *set, int signum);

代码示例:

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

void sigcb(int signo)
{
	    printf("signo is : %d\n",signo);
}

int main()
{
	    //#include <signal.h>

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

	    sigset_t set,old_set;

		//创建好set集合之后必须清空,否则信号是不能放入set集合中的
	    sigemptyset(&set);

	    //可靠信号与不可靠信号的对比
	    signal(SIGINT,sigcb);
	    signal(34,sigcb);

	    //把所有信号都添加到set集合中
	    sigfillset(&set);

	    //把set集合中的所有信号都阻塞
	    sigprocmask(SIG_BLOCK,&set,&old_set);

	    getchar();

	    sigprocmask(SIG_UNBLOCK,&set,NULL);

	    return 0;
}

在这里插入图片描述
在这里插入图片描述
kill -9 和 kill -19 是无法被阻塞的无法被忽略无法被自定义 SIGKILL和SIGSTOP


函数的重入:

多个执行流程同时执行进入相同的函数

		可重入:不会造成数据二义性以及代码逻辑混乱

		不可重入:有可能造成数据二义性以及代码逻辑混乱

		当用户设计一个函数或者使用一个函数的时候,在多个执行流中,这时候需要考虑函数是否可重入

是否重入的关键点:

		函数是否对临界资源(全局数据)进行了非原子操作

		examp:a++分三步,从内存读a,累加,返回到内存---cpu时间片切换,这个操作就可以被打断

代码示例:

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

int x=1;
int y=1;

int sum(int* a,int* b)
{
	    (*a)++;
	    sleep(3);//给足够的时间输入信号
	    (*b)++;

	    return (*a)+(*b);
}

void sigcb(signo)
{
	    printf("signo is : %d\n",sum(&x,&y));
}

int main()
{
	    signal(SIGINT,sigcb);
	    printf("sum is : %d\n",sum(&x,&y));

	    return 0;
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值