• 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为例,其代码在例如以下文件里。
    archx86kernelentry_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。


    见archx86includeasmunistd_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进行保存的。


  • 相关阅读:
    WDM驱动加载方式理解
    应用程序与设备对象交换数据的三种方法
    IRP完成例程返回值理解
    关于IoCallDriver使用的疑惑
    Ring0打开其他设备对象三种方式整理
    DPC和ISR的理解
    Windows驱动开发技术详解HelloWDM例子win7下无法安装
    wdk中ramdisk代码解读
    内核编程键盘过滤几种方法思路整理
    IOAPIC重定位中断处理函数思路整理
  • 原文地址:https://www.cnblogs.com/mengfanrong/p/5340785.html
Copyright © 2020-2023  润新知