Linux0.12源码阅读 —— 一个进程收到多个被用户捕获信号的处理

参考资料

1.Linux内核完全注释 v5.0修正版

实验环境

bochs模拟x86硬件平台下的Linux0.12操作系统
实验环境地址:http://www.oldlinux.org/Linux.old/bochs/ ,该路径下选择一个Linux0.12版本就可进行实验。

问题描述

当一个进程被切回执行时,这时发现有多个被用户捕获的信号需要处理,这多个信号的处理是怎么进行的?
例如,进程切回时,此时的信号位图signal有信号值为10,11,12三个被用户捕获的信号(这三个信号都是可以被用户捕获的)。这三个信号是怎么被处理的,什么时候被处理的?

要点说明

根据Linux0.12的内核源码,信号的处理有两个途径,一个是进程时间片用完被切出去,当切回时会进行信号处理。二是每次系统调用都会进行信号处理。这里我们需要测试的是在执行一次信号处理的情况下信号的处理情况。我们的期望是在执行一次信号处理的情况下能够把所有的信号都处理完成,这样才能保证信号的及时性。

注意这里的信号处理不是指用户指定的处理函数的执行,而是Linux内核对信号的处理。这里我们做出区分:
1.“”信号处理“”指的是内核对信号的处理
2.“”用户处理函数执行”或其他类似带上函数这个词的都是指用户指定的信号处理函数的执行。

代码测试

/* @file : test.c */

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

int i = 1;
char order[4] = {'0','0','0',0};

/* 信号值10的处理函数 */
void small()
{
	/*
	   这里不要用printf等函数查看信号处理情况,
	   因为printf函数是会去调用系统调用的,而系统调用会再次引发一次信号处理,
	   下面两个同理
	*/
	order[0] += i++;
}

/* 信号值11的处理函数 */
void mid()
{
	order[1] += i++;
}

/* 信号值12的处理函数 */
void big()
{
	order[2] += i++;
}

/* 当前进程醒来了 */
void wakeup()
{
	/* 
	   这里可以用printf是因为这个信号不是我们关心的,
	   并且也不会干扰10,11,12的信号处理,
	   因为这个时候 10,11.12还是被阻塞的。
	*/
	printf("process %d wakeup\n",getpid());
}

void main()
{
    /* 屏蔽信号10,11,12,信号值=位图偏移+1 */
	int ret,set_mask=(1<<9)|(1<<10)|(1<<11);
	/*
	   加上SA_NOMASK标志避免使用___masksig_restore信号恢复函数,
	   因为该恢复函数会调用系统调用,系统调用会引发一次信号处理,下面两个同理
	*/
	struct sigaction small_action={small,0,SA_NOMASK};
	struct sigaction mid_action={mid,0,SA_NOMASK};
	struct sigaction big_action={big,0,SA_NOMASK};
	/* 我们不关心,所以可以不要SA_NOMASK标志 */
	struct sigaction wakeup_action={wakeup,};
	
	struct sigaction old;
	
	/* 先打印下当前进程的进程号,
	   没有实现ps命令 :) ,有点忧伤! */
	printf("pid : %d\n",getpid());
	
	/* 我们不关心原来的sigaction,所以随他去吧! */
	sigaction(10,&small_action,&old);	/* 更改信号值10的sigaction */
	sigaction(11,&mid_action,&old);		/* 更改信号值11的sigaction */
	sigaction(12,&big_action,&old);	/* 更改信号值12的sigaction */
	sigaction(1,&wakeup_action,&old);	/* 更改信号值1的sigaction */
	
	while(1) /* 搁这等信号吧 */
	{
		/* 
			linux0.12的条件有点简陋了 。
		 	下面这条汇编,相当于是调用了sigsuspend函数,屏蔽set_mask所指定的信号。
		 	在Linux0.12下,我有尝试使用sigsuspend,然而好像有点问题,所以
		 	只能自己用汇编调用系统调用sys_sigsuspend了 :) 。
		 	这样之后,当前进程就会屏蔽10,11,12信号值,并会把自己置为
		 	TASK_INTERRUPTIBLE状态。当我们向该进程发送1信号值后,该进程
		 	就会解除TASK_INTERRUPTIBLE状态,并回到TASK_RUNNING状态,
		 	同时会解除对信号10,11,12的屏蔽。
		 	之所以这样做,是为了保证信号值10,11,12是被进程 同时 处理的。
		*/
		asm(
			"\tint $0x80\n"
			:"=a" (ret):"0" (72),"b" (0),"c" (0),"d" (set_mask)
		);
		/* 打印一下信号的处理顺序和返回值,返回值应该是-EINTR即-4*/
		/* 打印多次 */
		printf("ret:%d , order:small-mid-big %s\n",ret,order);
		printf("ret:%d , order:small-mid-big %s\n",ret,order);
		printf("ret:%d , order:small-mid-big %s\n",ret,order);
		
		/* 重置全局变量状态 */
		i = 1;
		order[0]=order[1]=order[2]='0';
	}
}

执行结果

编译链接上面代码生成可执行文件test
gcc -o test test.c

现代Linux下的执行情况:
在这里插入图片描述

linux0.12下的执行情况:
在这里插入图片描述

结果分析

从现代Linux下的执行情况来看,信号的处理顺序:
1.信号值12被处理
2.信号值11被处理
3.信号值10被处理
4.回到用户程序原点继续执行

Linux0.12下信号的处理顺序:
1.信号值10被处理
2.回到用户原点继续执行
3.有系统调用发生(printf函数内部可能多次调用了系统调用,所以第一个printf函数执行后,11,12信号值都被处理了),处理11,12信号值。

也就是说在Linux0.12里,信号的处理是不及时的,10信号值被处理后,并没有马上处理11,12信号值,而是等到下次的系统调用或是进程时间片被切出时才进行其余信号的处理。

Linux0.12源码分析

系统调用或进程切回后回到
+ret_from_sys_call:
++获得需要处理的信号中信号值最小的信号作为do_signal的参数入栈
++调用do_siganl()
+++do_signal在判定到当前需处理的信号是被用户捕获的后,把该信号用户指定的处理函数、恢复函数以及相关寄存器存入相应的内核或用户栈并返回0。
++do_signal的返回值为0,iret返回用户程序。
+用户信号处理函数执行和恢复
+回到用户程序执行原点继续执行。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值