Linux信号处理

由于在下能力相当有限,有不当之处,还望大家批评指正^_^


一、 信号概念


  • 信号(signal)是软中断。
  • 信号提供了一种处理异步事件的方式。
  • 当各种各样的事件发生时,程序相应的会收到各种各样的信号。
  • 对于大部分信号,程序可以自行决定如何处理他们。

程序在什么情况下会收到信号,下面是一些例子
执行某条指令,产生了异常
   (例如, 访存指令访问无效内存时收到SIGSEGV信号,执行非法指令收到SIGILL信号)
执行某些不合适的操作
  (例如,写破列的管道收到SIGPIPE信号,后台程序向控制台打印信息收到SIGTTOU信号)
发生了一些与当前程序有关的事情
  (例如,子进程退出后父进程收到SIGCHLD信号,定时器时间到了收到SIGALRM信号)
程序之间相互发送信号
  (例如, 程序之间通过信号进行协作)

通过man 7 signal命令可以查看Linux下的信号说明。
下面列出了此命令输出的一些信号,包括信号的编号,默认处理方式。
注意,同一信号,在不同CPU架构下的编号可能不一样。例如,像SIGUSR1, SIGCONT等。






二、 信号处理

对于一个信号如何处理,程序有如下3种选择
  • 忽略 
  • 捕获并处理 
  • 让系统按默认方式处理
注意:   SIGKILL与SIGSTOP信号,即无法被忽略,也无法被捕获。

Linux信号处理,有两种机制, 一种是传统的,一种是现代的。

总得来说,传统的信号处理机制有多种缺陷,因此还是不用为好。
另外,关于程序处理信号的具体方法,本文就不介绍了。《UNIX环境高级编程》对这些知识说得已经非常好了。

本文着重讲讲信号处理机制中的某些特殊之处。


三、 多线程程序中的信号处理

程序中对于某个信号如何处理的设置,对所有线程都是相同的。
换句话说,对任一信号设置的处理方式,以及相应的信号处理函数,对于所有线程都是共享的。
每个经程,通过pthread_sigmask设置自己要阻塞的信号。
问题是,这样的话,信号来了,具体由哪个线程谁处理呢???
这要分几种情况来讨论:

1. 外界向本进程发送的信号(通过sys_kill系统调用)
答案是,如果没有特殊情况,都是主线程处理。如果主线程不能处理,则选出一个线程来处理。
具体如何实现的,下面贴出内核中的相关代码

	/*
	 * Now find a thread we can wake up to take the signal off the queue.
	 *
	 * If the main thread wants the signal, it gets first crack.
	 * Probably the least surprising to the average bear.
	 */
	if (wants_signal(sig, p))
		t = p;
	else if (!group || thread_group_empty(p))
		/*
		 * There is just one thread and it does not need to be woken.
		 * It will dequeue unblocked signals before it runs again.
		 */
		return;
	else {
		/*
		 * Otherwise try to find a suitable thread.
		 */
		t = signal->curr_target;
		while (!wants_signal(sig, t)) {
			t = next_thread(t);
			if (t == signal->curr_target)
				/*
				 * No thread needs to be woken.
				 * Any eligible threads will see
				 * the signal in the queue soon.
				 */
				return;
		}
		signal->curr_target = t;
	}




2. 直接向某线程发送的信号(通过sys_tkill系统调用)
答案是,只会由此线程自己处理。

3.  线程执行相关指令产生的信号
某线程执行指令,导致产生了SIGSEGV或SIGILL,只会由产生此信号的线程自己处理。


最后,如何让线程专心做事,不被进程收到的信号打断呢。
同时,信号又能够得到妥善的处理。一种解决方法是:所有线程均阻塞所有process-directed信号,由一个线程轮询接收处理这些信号。



四、内核中的相关实现(引用内核版本2.6.32)
1. 内核何时处理信号
任务进入内核态后,在即将返回用户态之前,内核检查当前任务是否有信号需要处理。有的话,就处理信号。
任务一般会因为中断、系统调用、指令异常这几个原因,进入内核态。但不管是哪一种,在返回用户态之前,内核都会做信号方面的检查与处理。这个流程是一样的。
以i386为例,其代码在如下文件中。
arch\x86\kernel\entry_32.S


2.  信号处理代码简析

a). 信号处理总入口
static void do_signal(struct pt_regs *regs)
他由do_notify_resume调用


b). get_signal_to_deliver中处理被忽略的信号,以及按默认方式处理的信号


c). handle_signal中处理用户设定了要捕获处理的信号

3. 内核如何重启被信号打断的系统调用 


以ioctl为例。在x86_64下,其系统调用号为16。
见arch\x86\include\asm\unistd_64.h


ioctl的C库函数汇编代码如下:
000000000040af30 <__ioctl>:
40af30: b8 10 00 00 00      mov    $0x10,%eax
40af35: 0f 05               syscall 
40af37: 48 3d 01 f0 ff ff   cmp    $0xfffffffffffff001,%rax
40af3d: 0f 83 ad 23 00 00   jae    40d2f0 <__syscall_error>
40af43: c3                  retq   
40af44: 90                  nop    
40af45: 90                  nop 




handle_signal中的代码片断如下:
case -ERESTARTSYS:
if (!(ka->sa.sa_flags & SA_RESTART)) {
regs->ax = -EINTR;
break;
}
/* fallthrough */
case -ERESTARTNOINTR:
regs->ax = regs->orig_ax;
regs->ip -= 2;  //等于回到__ioctl中调用syscall之前的地方了
break;


4. 用户态信号处理函数的执行
当线程要从内核态回到用户态时,如果有信号要处理,那么就不是回到用户态原先因为进内核而被中断的地方执行了。而是回到用户设置的信号处理函数的地址处开始执行,就好像线程当初进入内核前正要执行信号处理函数一样。
以x86_32,传统信号处理方式为例。
代码在ia32_setup_frame中
内核将用户态程序现场修改为,回到用户态正好要执行的下一条指令,就是用户态信号处理函数的第一条指令。这样,回到用户态,就从用户态信号处理函数第一条指令开始执行了。

5. 用户态信号处理函数的返回
以x86_32,传统信号处理方式为例。
内核在ia32_setup_frame中为返回做好了准备

用户态信号处理函数的栈帧中,返回地址被内核赋了相应的值。即用户态信号处理函数在返回时,会返回到内核事先安排好的地方继续执行。其实就是调用__NR_ia32_sigreturn,对应sys_sigreturn。
sys_sigreturn中就作了现场环境的恢复。
主要是restore_sigcontext这个函数。
他从栈里面取出现场环境数据,进行恢复。
这些栈里面的数据,则是前面在ia32_setup_frame 处理中,通过调用函数ia32_setup_sigcontext进行保存的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
信号处理Linux系统中的一个重要概念,用于处理进程间通信和异常情况。信号是由操作系统或其他进程发送给进程的通知,用于通知进程发生了某个事件或异常情况。信号可以被进程捕获和处理,也可以被忽略或使用默认处理方式。 在Linux中,信号可以由多种情况触发,比如按下CTRL+C键产生的SIGINT信号,非法内存访问产生的信号,硬件故障产生的信号,以及环境切换等。进程可以通过调用signal函数来注册信号处理函数,以捕获和处理特定的信号。 signal函数的原型如下: ```c typedef void (*sighandler)(int); sighandler signal(int signum, sighandler handler); ``` 其中,signum是需要处理的信号编号,handler是信号的处理函数。处理函数可以是用户自定义的函数,也可以是预定义的常量SIG_IGN表示忽略该信号,或者SIG_DFL表示使用默认的信号处理方式。 在信号处理函数中,可以执行一些特定的操作来处理信号,比如打印日志、保存数据、发送信号给其他进程等。处理函数可以是空函数,表示仅仅捕获信号但不做任何处理。 需要注意的是,一个进程可以屏蔽掉大多数的信号,除了SIGSTOP和SIGKILL这两个信号是无法被屏蔽的。信号有优先级,当一个进程有多个未决信号时,内核将按照发送的顺序来递送信号。值越小的信号越先被递送。 在Linux中,可以通过编写信号处理程序来处理不同的信号,并根据需要执行特定的操作。通过信号处理,可以实现进程间通信、优雅地关闭进程或处理异常情况等功能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值