Linux信号(Understanding Linux Kernel 3rd)

Linux信号(Understanding Linux Kernel 3rd)

  1. 总览

    • 产生

      内核或应用程序产生,一份简短的信息。

    • 传递

      • 挂起状态
      • 非挂起状态
    • 信号类型

      • 发给进程的信号(所有线程共享)
      • 发给线程的信号
    • 处理者

      • 进程信号是其中一个没有屏蔽这个信号的线程处理。
      • 线程就是指定线程处理。
    • 处理方式

      • do_signal处理
      • 创建对应的特定栈帧来处理。
    • 信号处理函数

      • 整个进程中的线程共享。
      • 有默认也有自定义。
      • 需要的信息也可以自定义。
  2. 信号的作用

    • 简讯

      • 一份简短的信息。
      • 生产者是内核或进程。
      • 处理者是具体的线程或者是进程组中符合处理条件的线程。
    • 内容

      • 标准的信号就只有一个数字。
      • 32位的整数,每一位对应一个信号位。
      • 支持实时信号的系统是64位,即两个long.
    • 信号定义

      trap -l
      
      • 用上面的指令罗列出支持的信号。

      • 在编程中一般是宏定义,然后每个宏对应一个数字。

        #define SIGHUP		 1
        #define SIGINT		 2
        #define SIGQUIT		 3
        #define SIGILL		 4
        #define SIGTRAP		 5
        #define SIGABRT		 6
        #define SIGIOT		 6
        #define SIGBUS		 7
        #define SIGFPE		 8
        #define SIGKILL		 9
        #define SIGUSR1		10
        #define SIGSEGV		11
        #define SIGUSR2		12
        #define SIGPIPE		13
        #define SIGALRM		14
        #define SIGTERM		15
        #define SIGSTKFLT	16
        #define SIGCHLD		17
        #define SIGCONT		18
        #define SIGSTOP		19
        #define SIGTSTP		20
        #define SIGTTIN		21
        #define SIGTTOU		22
        #define SIGURG		23
        #define SIGXCPU		24
        #define SIGXFSZ		25
        #define SIGVTALRM	26
        #define SIGPROF		27
        #define SIGWINCH	28
        #define SIGIO		29
        #define SIGPOLL		SIGIO
        /*
        #define SIGLOST		29
        */
        #define SIGPWR		30
        #define SIGSYS		31
        #define	SIGUNUSED	31
        
        /* These should not be considered constants from userland.  */
        #define SIGRTMIN	32
        
    • 目的

      • 让某个进程或线程知道发生了什么特殊的事。

        每个signal对应的有特定的事件。

      • 让这个进程或线程处理这件事。

        调用对应的信号处理函数。

    • 信号对应的默认处理类型

      • 终止

        进程挂掉

      • 产生core文件

        用于调试

      • 忽略

        不处理

      • 继续执行

        如果进程处于停止则继续执行。

      • 停止

        可再执行

    • 信号与处理器架构有关

      • 常规信号1-31

        挂起的时候不允许信号重复。

        对于多次产生的信号,内核发现如果已经存在有对应的信号在挂起,则只接受第一个。

      • 实时信号32-64

        不常用。

        挂起的时候允许信号重复。

    • 信号发送和接收

      • 内核提供了许多的系统调用来产生信号。

      • rt_ == realtime

      • 实时信号和普通信号的操作函数基本相同。

      • 分类

        • 发送给进程
        • 发送给线程
        • 发送给在某个特定线程组的进程。
        • 修改信号处理函数
        • 检测是否有挂起信号。
        • 修改进程阻塞信号集合。
        • 等待某个信号。
      • 发送

        • 任何时候都可以发送。

          但是这个时候的进程的状态不确定。可能在任意状态。

        • 状态对应处理方式
          • 睡眠状态内核保留等待重新执行再发送。
          • 信号阻塞将会被挂起,直到取消阻塞。
    • 信号产生

      • 内核直接修改目标进程结构体来表示信号已经发送。
    • 信号交付

      • 内核强制要求内核执行信号响应函数来响应这个信号。
      • 如果处于睡眠可唤醒状态,则直接唤醒来响应。
      • 交付了之后对对应位进行置位为0。对于多个相同的信号只递交一次。
    • 挂起信号

      • 生成但是还未交付给进程的信号叫做挂起信号。挂在进程中,挂起的信号分为私有和公有。
      • 在添加信号时会检测新生成的信号是否已经存在,如果存在则丢掉。如果是实时信号则没有这种限制。
      • 挂起的时长不确定。
    • 交付信号可能出现的情况

      • 信号交付只交付给正在执行的进程。

        current宏所指向的进程就是执行进程。

      • 交付的信号可能被进程阻塞挂起,因此内核保存,等到执行并没有阻塞的时候交付。

      • 执行某个信号的处理函数时,这个信号会被阻塞,直到处理完。

    • 内核处理信号

      • 哪些进程阻塞了哪些信号。

      • 内核到用户态前,检测是否有信号已经到达,还没有处理。每次时钟中断都会检测。也就是说花费一次时间片可能会检测多次。因为权重可大可小。

      • 信号是否忽略需要满足下面的所有条件。

        • 目标进程不是出于调试状态。

        • 目标进程没有阻塞信号。

        • 发送的信号被目标进程显示或者隐式的忽略。

        回调函数默认是ignore或者修改为了ignore.

      • 处理信号将会强制的从进程执行代码转到执行信号处理函数。函数执行完了之后恢复执行。

    • 进程响应信号的三种情况

      • 显示忽略

      • 执行默认操作

        • Terminate

          挂掉进程

        • dump

          挂掉并生成core

        • ignore

          隐式忽略

        • stop

          进程被调试,修改执行状态为TASK_STOPPED状态。

        • continue

          进程处于TASK_STOPPED状态,改为执行状态。TASK_RUNNING

      • 捕获信号并调用对应的信号处理函数。

    • 特殊信号

      • 特殊的信号,SIGKILL|SIGSTOP不能被忽略,总是执行默认处理及杀死进程,也不能被捕获,不能被阻塞。拥有权限的用户可以用这两个信号执行杀死进程的操作,不管进程做了任务的防御都没有用。

      • 执行成功的前提是有这个权限。而且这种方式对于进程是致命的。不友好的。

    • POSIX标准的信号和多线程进程

      • 信号处理器线程间共享。
      • 各个线程拥有自己的挂起和阻塞信号集合。
      • kill | sigqueue函数发送的信号是给进程的,而不是某个特定线程。包括其他的信号。
      • 信号传递给进程之后,进程任意选择一个没有阻塞该信号的线程处理。
      • 致命信号发送给多线程进城后,内核会杀死所有的线程。而不是接收到这个信号的线程。
    • 私有信号和共享信号

      • 内核支持给某个线程发送信号,发送给特定线程的信号是该线程的特有信号也叫私有信号,有一个私有信号队列。
      • 共享信号则存放在这个线程的共享队列。
    • 特殊进程

      • 0号进程不接受任何信号。
      • 1号进程不会接受接收自己以外的信号。即自己造成的信号接受,其他的忽略。
      • 0号永不死亡,1号在init程序挂了以后死亡。
    • 相关结构体

      • 系统内的进程,内核必须要追中,哪些信号被屏蔽,哪些被挂起。还要知道这些什么时候执行这些信号。内核将这些信号都放在了对应的结构体里面了。

      • 进程描述符内

        struct task_struct {
            ...
            // 共享的信号
            struct signal_struct *signal;
            // 私有的信号
            struct sigpending pending;
            // 信号处理函数
            struct sighand_struct *sighand;
            // 阻塞的信号集合
            sigset_t blocked;
            // 临时挂起信号
            sigset_t real_blocked;
            sigset_t saved_sigmask; /* restored if set_restore_sigmask() was used */
            ...
        };
        
      • 共享信号

        struct signal_struct {
            ...
            // 共享的
        	struct sigpending	shared_pending;
        		/*
        	 * We don"t bother to synchronize most readers of this at all,
        	 * because there is no reader checking a limit that actually needs
        	 * to get both rlim_cur and rlim_max atomically, and either one
        	 * alone is a single word that can safely be read normally.
        	 * getrlimit/setrlimit use task_lock(current->group_leader) to
        	 * protect this instead of the siglock, because they really
        	 * have no need to disable irqs.
        	 */
        	struct rlimit rlim[RLIM_NLIMITS];
            ...
        };
        
        • 结构体包含的不仅仅是和信号相关的,还有其他的比如说每个处理器的资源限制。
        • 还有pgrp存放的是线程组第一线程的进程号。
        • session存放着第一session的进程号。
      • 共有和私有挂起信号队列

        struct sigpending {
        	struct list_head list;
        	sigset_t signal;
        };
        
        struct sigqueue {
        	struct list_head list;
        	int flags;
        	siginfo_t info;
        	struct user_struct *user;
        };
        
        • 发送信号的同时可能有多个。发送的对象可能有多个。需要追踪挂起的信号。

        • 信号有两种,多进程共享的共有挂起信号,每个线程独有的私有挂起信号。

        • list

          双向循环链表

        • lock

          指向sighandlerlock.

        • flags

          表示sigqueue的数据机构

        • siginfo_t

          信号原因和一些必要信息。

        • user

          进程所有者的用户信息。

      • 各个信号对应的处理函数

        struct sighand_struct {
            // 用了几个
        	atomic_t		count;
            // 响应信号的函数
        	struct k_sigaction	action[_NSIG];
            // 保护信号描述符和信号处理函数
        	spinlock_t		siglock;
        	wait_queue_head_t	signalfd_wqh;
        };
        
        struct k_sigaction {
        	struct sigaction sa;
        };
        
        struct sigaction {
        	__sighandler_t sa_handler;
        	unsigned long sa_flags;
        	__sigrestore_t sa_restorer;
        	sigset_t sa_mask;		/* mask last for extensibility */
        };
        
        • sa_handler

          • SIGDFL=0 默认处理
          • SIGIGN=1 忽略
          • 指针 处理函数
        • sa_flags应该处理信号

          • SA_NOCLDSTOP

            应用于信号SIGCHLD,子进程停止的时候不要发送信号给父进程。

          • SA_NOCHLDWAIT

            应用于信号SIGCHLD,子进程停止的时候不要创建僵尸进程,直接死亡。表示父进程对子进程的死亡没有兴趣。

          • SA_SIGINFO

            • 提供详细信息给信号处理函数。常用的。
          • SA_ONSTACK

            提供一个可选的栈给信号处理器。

          • SA_RESART

            被中断的系统调用会重启。

          • SA_NODEFER|SA_NOMASK

            执行处理函数的时候也不屏蔽信号。

            丢掉还是直接替换?

          • SA_RESETHAND|SA_ONESHOT

            信号处理函数执行一次后就恢复为默认的。

        • sa_mask

          执行处理器的时候应该屏蔽那些信号。

      • 信号集结构体

        
        #ifdef __KERNEL__
        /* Digital Unix defines 64 signals.  Most things should be clean enough
           to redefine this at will, if care is taken to make libc match.  */
        
        #define _NSIG		64
        #define _NSIG_BPW	64
        #define _NSIG_WORDS	(_NSIG / _NSIG_BPW)
        
        typedef unsigned long old_sigset_t;		/* at least 32 bits */
        
        typedef struct {
        	unsigned long sig[_NSIG_WORDS];
        } sigset_t;
        
        #else
        /* Here we must cater to libcs that poke about in kernel headers.  */
        
        #define NSIG		32
        typedef unsigned long sigset_t;
        
        #endif /* __KERNEL__ */
        
    • 信号发起者信息

      typedef struct siginfo {
      	int si_signo;
      	int si_errno;
      	int si_code;
      	int __pad0;
      
      	union {
      		int _pad[SI_PAD_SIZE];
      
      		/* kill() */
      		struct {
      			pid_t _pid;		/* sender"s pid */
      			uid_t _uid;		/* sender"s uid */
      		} _kill;
      
      		/* POSIX.1b timers */
      		struct {
      			timer_t _tid;		/* timer id */
      			int _overrun;		/* overrun count */
      			char _pad[sizeof(__ARCH_SI_UID_T) - sizeof(int)];
      			sigval_t _sigval;	/* must overlay ._rt._sigval! */
      			int _sys_private;	/* not to be passed to user */
      		} _timer;
      
      		/* POSIX.1b signals */
      		struct {
      			pid_t _pid;		/* sender"s pid */
      			uid_t _uid;		/* sender"s uid */
      			sigval_t _sigval;
      		} _rt;
      
      		/* SIGCHLD */
      		struct {
      			pid_t _pid;		/* which child */
      			uid_t _uid;		/* sender"s uid */
      			int _status;		/* exit code */
      			clock_t _utime;
      			clock_t _stime;
      		} _sigchld;
      
      		/* SIGILL, SIGFPE, SIGSEGV, SIGBUS */
      		struct {
      			void __user *_addr;	/* faulting insn/memory ref. */
      			int _imm;		/* immediate value for "break" */
      			unsigned int _flags;	/* see below */
      			unsigned long _isr;	/* isr */
      			short _addr_lsb;	/* lsb of faulting address */
      		} _sigfault;
      
      		/* SIGPOLL */
      		struct {
      			long _band;	/* POLL_IN, POLL_OUT, POLL_MSG (XPG requires a "long") */
      			int _fd;
      		} _sigpoll;
      	} _sifields;
      } siginfo_t;
      
      • si_signo

      中断编号

      • si_errno

      指令导致信号发起的错误代码。0表示没有错误。

      • si_code

      造成的原因,和信号值有关。

      /* Values for `si_code".  Positive values are reserved for kernel-generated
         signals.  */
      enum
      {
        SI_ASYNCNL = -60,             /* Sent by asynch name lookup completion.  */
      # define SI_ASYNCNL     SI_ASYNCNL
        SI_TKILL = -6,                /* Sent by tkill.  */
      # define SI_TKILL       SI_TKILL
        SI_SIGIO,                     /* Sent by queued SIGIO. */
      # define SI_SIGIO       SI_SIGIO
        SI_ASYNCIO,                   /* Sent by AIO completion.  */
      # define SI_ASYNCIO     SI_ASYNCIO
        SI_MESGQ,                     /* Sent by real time mesq state change.  */
      # define SI_MESGQ       SI_MESGQ
        SI_TIMER,                     /* Sent by timer expiration.  */
      # define SI_TIMER       SI_TIMER
        SI_QUEUE,                     /* Sent by sigqueue.  */
      # define SI_QUEUE       SI_QUEUE
        SI_USER,                      /* Sent by kill, sigsend.  */
      # define SI_USER        SI_USER
        SI_KERNEL = 0x80              /* Send by kernel.  */
      #define SI_KERNEL       SI_KERNEL
      };
      
      
      /* `si_code" values for SIGILL signal.  */
      enum
      {
        ILL_ILLOPC = 1,               /* Illegal opcode.  */
      # define ILL_ILLOPC     ILL_ILLOPC
        ILL_ILLOPN,                   /* Illegal operand.  */
      # define ILL_ILLOPN     ILL_ILLOPN
        ILL_ILLADR,                   /* Illegal addressing mode.  */
      # define ILL_ILLADR     ILL_ILLADR
        ILL_ILLTRP,                   /* Illegal trap. */
      # define ILL_ILLTRP     ILL_ILLTRP
        ILL_PRVOPC,                   /* Privileged opcode.  */
      # define ILL_PRVOPC     ILL_PRVOPC
        ILL_PRVREG,                   /* Privileged register.  */
      # define ILL_PRVREG     ILL_PRVREG
        ILL_COPROC,                   /* Coprocessor error.  */
      # define ILL_COPROC     ILL_COPROC
        ILL_BADSTK                    /* Internal stack error.  */
      # define ILL_BADSTK     ILL_BADSTK
      };
      
      /* `si_code" values for SIGFPE signal.  */
      enum
      {
        FPE_INTDIV = 1,               /* Integer divide by zero.  */
      # define FPE_INTDIV     FPE_INTDIV
        FPE_INTOVF,                   /* Integer overflow.  */
      # define FPE_INTOVF     FPE_INTOVF
        FPE_FLTDIV,                   /* Floating point divide by zero.  */
      # define FPE_FLTDIV     FPE_FLTDIV
        FPE_FLTOVF,                   /* Floating point overflow.  */
      # define FPE_FLTOVF     FPE_FLTOVF
        FPE_FLTUND,                   /* Floating point underflow.  */
      # define FPE_FLTUND     FPE_FLTUND
        FPE_FLTRES,                   /* Floating point inexact result.  */
      # define FPE_FLTRES     FPE_FLTRES
        FPE_FLTINV,                   /* Floating point invalid operation.  */
      # define FPE_FLTINV     FPE_FLTINV
        FPE_FLTSUB                    /* Subscript out of range.  */
      # define FPE_FLTSUB     FPE_FLTSUB
      };
      
      /* `si_code" values for SIGSEGV signal.  */
      enum
      {
        SEGV_MAPERR = 1,              /* Address not mapped to object.  */
      # define SEGV_MAPERR    SEGV_MAPERR
        SEGV_ACCERR                   /* Invalid permissions for mapped object.  */
      # define SEGV_ACCERR    SEGV_ACCERR
      };
      
      /* `si_code" values for SIGBUS signal.  */
      enum
      {
        BUS_ADRALN = 1,               /* Invalid address alignment.  */
      # define BUS_ADRALN     BUS_ADRALN
        BUS_ADRERR,                   /* Non-existant physical address.  */
      # define BUS_ADRERR     BUS_ADRERR
        BUS_OBJERR                    /* Object specific hardware error.  */
      # define BUS_OBJERR     BUS_OBJERR
      };
      
      /* `si_code" values for SIGTRAP signal.  */
      enum
      {
        TRAP_BRKPT = 1,               /* Process breakpoint.  */
      # define TRAP_BRKPT     TRAP_BRKPT
        TRAP_TRACE                    /* Process trace trap.  */
      # define TRAP_TRACE     TRAP_TRACE
      };
      
      /* `si_code" values for SIGCHLD signal.  */
      enum
      {
        CLD_EXITED = 1,               /* Child has exited.  */
      # define CLD_EXITED     CLD_EXITED
        CLD_KILLED,                   /* Child was killed.  */
      # define CLD_KILLED     CLD_KILLED
        CLD_DUMPED,                   /* Child terminated abnormally.  */
      # define CLD_DUMPED     CLD_DUMPED
        CLD_TRAPPED,                  /* Traced child has trapped.  */
      # define CLD_TRAPPED    CLD_TRAPPED
        CLD_STOPPED,                  /* Child has stopped.  */
      # define CLD_STOPPED    CLD_STOPPED
        CLD_CONTINUED                 /* Stopped child has continued.  */
      # define CLD_CONTINUED  CLD_CONTINUED
      };
      
      /* `si_code" values for SIGPOLL signal.  */
      enum
      {
        POLL_IN = 1,                  /* Data input available.  */
      # define POLL_IN        POLL_IN
        POLL_OUT,                     /* Output buffers available.  */
      # define POLL_OUT       POLL_OUT
        POLL_MSG,                     /* Input message available.   */
      # define POLL_MSG       POLL_MSG
        POLL_ERR,                     /* I/O error.  */
      # define POLL_ERR       POLL_ERR
        POLL_PRI,                     /* High priority input available.  */
      # define POLL_PRI       POLL_PRI
        POLL_HUP                      /* Device disconnected.  */
      # define POLL_HUP       POLL_HUP
      }; 
      
      • _sifields

      一个共享体,根据信号的不同,使用变量不同。

  3. 生成信号

    • 生成的两个步骤

      • 第一步是更新目标进程的值。

      • 第二步传递,一般不直接执行,而是根据目标的状态和信号代码来决定。可能会唤醒,可能会强制进程接受信号。

    • 内核或函数生成信号都是通过下面的几种函数生成的

      send_sig():
      	Sends a signal to a single process
      send_sig_info():
      	Like send_sig(), with extended information in a siginfo_t structure
      force_sig():
      	Sends a signal that cannot be explicitly ignored or blocked by the process
      force_sig_info():
      	Like force_sig(), with extended information in a siginfo_t structure
      force_sig_specific():
      	Like force_sig(), but optimized for SIGSTOP and SIGKILL signals
      sys_tkill():
      	System call handler of tkill() (see the later section “System Calls Related to Signal Handling”)
      sys_tgkill():
      	System call handler of tgkill()
      
      • 上面所有的函数都会调用一个函数specific_send_sig_info

    • 内核或者应用产生的信号发送给进程都是通过下面几个函数发送的

      send_group_sig_info():
      	Sends a signal to a single thread group identified by the process descriptor of one of
      its members
      kill_pg():
      	Sends a signal to all thread groups in a process group (see the section “Process
      Management” in Chapter 1)
      kill_pg_info():
      	Like kill_pg(), with extended information in a siginfo_t structure
      kill_proc():
      	Sends a signal to a single thread group identified by the PID of one of its members
      kill_proc_info():
      	Like kill_proc(), with extended information in a siginfo_t structure
      sys_kill():
      	System call handler of kill() (see the later section “System Calls Related to Signal Handling”)
      sys_rt_sigqueueinfo():
      	System call handler of rt_sigqueueinfo()
      
      • 所有的函数都会调用函数group_send_sig_info

    • specific_send_sig_info

      static int specific_send_sig_info(int sig, struct siginfo *info, struct task_struct *t)
      {
      	return send_signal(sig, info, t, 0);
      }
      
      static int send_signal(int sig, struct siginfo *info, struct task_struct *t,
      			int group)
      {
      	int from_ancestor_ns = 0;
      
      #ifdef CONFIG_PID_NS
      	from_ancestor_ns = si_fromuser(info) &&
      			   !task_pid_nr_ns(current, task_active_pid_ns(t));
      #endif
      
      	return __send_signal(sig, info, t, group, from_ancestor_ns);
      }
      static int __send_signal(int sig, struct siginfo *info, struct task_struct *t,
      			int group, int from_ancestor_ns)
      {
      	struct sigpending *pending;
      	struct sigqueue *q;
      	int override_rlimit;
      
      	trace_signal_generate(sig, info, t);
      
      	assert_spin_locked(&t->sighand->siglock);
      
      	if (!prepare_signal(sig, t, from_ancestor_ns))
      		return 0;
      
      	pending = group ? &t->signal->shared_pending : &t->pending;
      	/*
      	 * Short-circuit ignored signals and support queuing
      	 * exactly one non-rt signal, so that we can get more
      	 * detailed information about the cause of the signal.
      	 */
      	if (legacy_queue(pending, sig))
      		return 0;
      	/*
      	 * fast-pathed signals for kernel-internal things like SIGSTOP
      	 * or SIGKILL.
      	 */
      	if (info == SEND_SIG_FORCED)
      		goto out_set;
      
      	/*
      	 * Real-time signals must be queued if sent by sigqueue, or
      	 * some other real-time mechanism.  It is implementation
      	 * defined whether kill() does so.  We attempt to do so, on
      	 * the principle of least surprise, but since kill is not
      	 * allowed to fail with EAGAIN when low on memory we just
      	 * make sure at least one signal gets delivered and don"t
      	 * pass on the info struct.
      	 */
      	if (sig < SIGRTMIN)
      		override_rlimit = (is_si_special(info) || info->si_code >= 0);
      	else
      		override_rlimit = 0;
      
      	q = __sigqueue_alloc(sig, t, GFP_ATOMIC | __GFP_NOTRACK_FALSE_POSITIVE,
      		override_rlimit);
      	if (q) {
      		list_add_tail(&q->list, &pending->list);
      		switch ((unsigned long) info) {
      		case (unsigned long) SEND_SIG_NOINFO:
      			q->info.si_signo = sig;
      			q->info.si_errno = 0;
      			q->info.si_code = SI_USER;
      			q->info.si_pid = task_tgid_nr_ns(current,
      							task_active_pid_ns(t));
      			q->info.si_uid = current_uid();
      			break;
      		case (unsigned long) SEND_SIG_PRIV:
      			q->info.si_signo = sig;
      			q->info.si_errno = 0;
      			q->info.si_code = SI_KERNEL;
      			q->info.si_pid = 0;
      			q->info.si_uid = 0;
      			break;
      		default:
      			copy_siginfo(&q->info, info);
      			if (from_ancestor_ns)
      				q->info.si_pid = 0;
      			break;
      		}
      	} else if (!is_si_special(info)) {
      		if (sig >= SIGRTMIN && info->si_code != SI_USER) {
      			/*
      			 * Queue overflow, abort.  We may abort if the
      			 * signal was rt and sent by user using something
      			 * other than kill().
      			 */
      			trace_signal_overflow_fail(sig, group, info);
      			return -EAGAIN;
      		} else {
      			/*
      			 * This is a silent loss of information.  We still
      			 * send the signal, but the *info bits are lost.
      			 */
      			trace_signal_lose_info(sig, group, info);
      		}
      	}
      
      out_set:
      	signalfd_notify(t, sig);
      	sigaddset(&pending->signal, sig);
      	complete_signal(sig, t, group);
      	return 0;
      }
      
      • specific_send_sig_info参数

        • sig

          信号编号

        • info

          • 0表示由用户态用户发送
          • 1表示由内核发送。
          • 2表示由内核发送,而且信号还是SIGSTOP|SIGKILL
          • 指针
        • t

          目标进程

      • 调用前后

        /*
         * Force a signal that the process can"t ignore: if necessary
         * we unblock the signal and change any SIG_IGN to SIG_DFL.
         *
         * Note: If we unblock the signal, we always reset it to SIG_DFL,
         * since we do not want to have a signal handler that was blocked
         * be invoked when user space had explicitly blocked it.
         *
         * We don"t want to have recursive SIGSEGV"s etc, for example,
         * that is why we also clear SIGNAL_UNKILLABLE.
         */
        int
        force_sig_info(int sig, struct siginfo *info, struct task_struct *t)
        {
        	unsigned long int flags;
        	int ret, blocked, ignored;
        	struct k_sigaction *action;
        	//获取自旋锁并禁用中断
        	spin_lock_irqsave(&t->sighand->siglock, flags);
        	action = &t->sighand->action[sig-1];
        	ignored = action->sa.sa_handler == SIG_IGN;
        	blocked = sigismember(&t->blocked, sig);
        	if (blocked || ignored) {
        		action->sa.sa_handler = SIG_DFL;
        		if (blocked) {
        			sigdelset(&t->blocked, sig);
        			recalc_sigpending_and_wake(t);
        		}
        	}
        	if (action->sa.sa_handler == SIG_DFL)
        		t->signal->flags &= ~SIGNAL_UNKILLABLE;
        	ret = specific_send_sig_info(sig, info, t);
            //释放自旋锁并回复中断
        	spin_unlock_irqrestore(&t->sighand->siglock, flags);
        
        	return ret;
        }
        
        • 调用前需要禁用本地的中断并且还获取到了自旋锁。调用完后释放自旋锁并恢复之前的中断状态。
      • 大致步骤

        • 检测目标进程是否忽略了这个信号。忽略返回0。满足下面所有条件会忽略。

          • 没有被调试。

            调试了gdb会获取

          • 没有被阻塞。

            阻塞了则会挂起

          • 信号被显式或隐式的忽略。

            没有被忽略则会处理。

        • 检测信号是否是实时信号,或者是已经挂起则返回0.

        • send_signal添加信号到挂起信号集中。

        • 如果不是阻塞信号则调用signal_wake_up函数告诉进程信号的到来。

          • t->thread_info->flags |= TIF_SIGPENDING表示有信号需要处理。

          • 如果是SIGKILL,并且目标进程处于TASK_ INTERRUPTIBLE 或者是TASK_STOPPED调用try_to_wake_up唤醒进程。

          • 如果try_to_wake_up返回值为0,表示进程正在执行。

            • 如果进程在其他的处理器执行,则发起处理器间中断,
            • 然后切换该进程到另一个处理器。
            • 进程切换后的后检测是否有信号挂起,如果有就处理信号。
        • 成功在目标进程描述符挂起信号后返回1.

    • send_signal

      • 作用

        插入新的信号到挂起队列。

      • 参数

        • 信号值sig

        • 信号信息info

        值或者指针,同前。

        • 目标进程t

        • 挂起信号队列。signals

      • 执行步骤

        1. info参数为2表示内核强制杀死,信号为SIGKILL|SIGSTOP。则直接跳转到9。
        2. 挂起信号已达到资源上限t->user->sigpendding,用户进程都有资源上限,则跳转到9。
        3. 从内存分配一个sig_queue结构体,存放信息。
        4. 增长资源计数t->user->sigpendding,增长t->user的引用计数。
        5. 添加信号结构体到挂起队列中。
        6. 填充信号信息结构体。
        7. 添加对应的信号位到信号集合中。
        8. 成功完成返回0.
        9. 前面步骤可能出现,挂起太多,内存不够,信号是强制执行信号。
        10. 添加信号到信号集。
        11. 返回0.即使挂起到队列失败,对应的位也要设置在mask中。
      • 即使没有内存存储信息也要保证目标进程获取到信号。这样有助于恢复系统,保护系统的稳定性。

    • group_send_sig_info

      • 作用

      • 发送信号给一个进程组。
      • 参数

      • 信号值sig
      • 信号信息info012如前。
      • 目标进程p
      • 步骤

      1. 信号值合法检测。

      2. 检测信号发送者是否有权限。

      3. 信号值不能为0,且进程没有正在被杀死。

      4. 获取自旋锁并禁用当前处理器中断。

      5. 调用handle_stop_signal函数,检查新来的信号是否会让某些信号无效。

        • 如果死进程,则返回。
        • 如果是STOP|TSTP|TTIN|TTOUT,则会调用函数rm_from_queue删除共享和私有队列中挂起的SIGCONT
        • 如果是SIGCONT则会调用rm_from_queue删除共享和私有队列中挂起的STOP|TSTP|TTIN|TTOUT.并且唤醒进程。
      6. 如果忽略信号返回0。忽略信号同前的步骤1。

      7. 如果是非实时信号且已经被挂起,则返回0.

      8. 调用send_signal添加信号到队列中。如果返回是个非0,则同样返回。

      9. 调用__group_complete_signal函数唤醒其中一个线程来处理。

      10. 释放自旋锁恢复当前处理器之前的中断状态。

      11. 返回0表示成功处理。

    • _ _group_complete_signal选择处理函数

      • 作用

        • 唤醒合适的线程来处理新的信号。
      • 线程满足条件

        • 线程没有阻塞这个信号。

        • 线程没有状态不是EXIT_ZOMBIE, EXIT_DEAD, TASK_TRACED, or TASK_STOPPED

          如果信号是SIGKILL线程可以是TASK_TRACED or TASK_STOPPED

        • 线程没有被杀死PF_EXITING

        • 要么是正在执行的线程,要么是没有被设置TIF_SIGPENDING.

          • 唤醒有挂起信号的进程毫无意义。
          • 正在执行的进程也可以处理这个挂起信号。
      • 多个满足条件的线程中选择

        • 如果传入的目标进程也满足,则用这个传入的p
        • 不然就搜索线程组,从接受到上一个共享信号的线程开始。
      • 成功找到处理线程就可以开始传递信号

        • 信号检测是否为指明信号,如果是则杀死所有。
        • 不是则唤醒合适的线程通知其有新的信号需要处理。
  4. 传递信号

    • 内核处理信号

      • 通过系统调用得知有信号传递,通过传递的信号值和目标进程中的目标进程pid来获取对应进程的进程描述符。
      • 因为信号可能在任意时刻发送,所以目标进程处于什么状态是未知的。
      • 在多处理器主机中,如果目标进程没有执行内核将会延迟递交信号。
    • 中断上下文切换与信号处理

      • 从内核中断返回用户进程之前,会检测目标进程是否有信号未处理。即通过成员变量的值是否为SIGPENDING来确认。
      • 内核每次处理完中断或者异常(处理器产生的软中断)都会检测是否有
      • 中断常见的是时钟中断,当然也有其他的中断,而异常就是非法操作,导致处理器产生的异常。比如异常访问地址,除0操作。
    • 非阻塞信号处理

      • 使用do_signal函数处理非阻塞信号。

      • 参数

        • regs

          寄存器信息

        • oldset

          非空用于返回阻塞信号掩码。

      • 主要目的

        • 简单的讲讲机制,实现很复杂。
    • do_signal调用场景

      • 从内核中断返回用户进程。
      • 中断函数调用,则是简单的返回。
    • do_signal核心

      • 循环,不断的调用dequeue_signal函数,直到公有信号队列和私有信号队列都没有挂起信号为止。

      • dequeue_signal返回值是刚刚删除的挂起信号值。返回值为0表示处理完毕。

      • 先处理私有再处理公共。返回的信号值会清理对应的掩码。还会调用recalc_sigpending();调整当前的状态。

    • 检测目标进程

      • 是否被调试

        是则调用do_notify_parent_cldstop and schedule函数,通知调试进程。

    • dequeue_signal返回值signr

      • 根据返回值确认目标进程的处理方式。
      k_sigaction ka = &current->sig->action[signr-1];
      
      • 动作分三类,前面有介绍:忽略,默认,执行处理函数。
      if (ka->sa.sa_handler == SIG_IGN)
      	continue;
      
      • 默认操作

      // 处理默认
      if(ka->sa.sa_handler == SIG_DFL)
          do_default_action();
      // 1号进程忽略信号
      if (current->pid == 1)
      	continue;
      // 忽略操作
      if (signr==SIGCONT || signr==SIGCHLD ||
      signr==SIGWINCH || signr==SIGURG)
      	continue;
      // 停止操作停止进程,设置进程为`TASK_STOPPED`。如果是其他三个,且是孤儿进程则继续。
      // SIGSTOP总是停止,其他的三个不在孤儿进程再回执行退出。
      if (signr==SIGSTOP || signr==SIGTSTP ||
      	signr==SIGTTIN || signr==SIGTTOU) 
      {
          if (signr != SIGSTOP &&
      	    is_orphaned_pgrp(current->signal->pgrp))
          	continue;
          do_signal_stop(signr);
      }
      // 检测当前进程是否是第一个关闭线程,是则发起组关闭。
      // 唤醒迭代然后修改状态杀死进程,向父进程发起通知。
      do_signal_stop();
      
      • dump

        创建一个core.pid文件在进程执行路径。

        文件包含当时的及才能拿地址空间和处理器。

        以及一些必要的信息。

        然后执行杀死进程操作。

      • 自定义函数

      • 调用执行。
      handle_signal(signr, &info, &ka, oldset, regs);
      if (ka->sa.sa_flags & SA_ONESHOT)
      	ka->sa.sa_handler = SIG_DFL;
      return 1;
      
      • 如果是一次性的,改回默认。
      • 一次处理一个信号,这样可以保证实时信号能够正确的处理。
    • 信号处理函数与上下文切换

      • 中断是在内核执行,信号处理函数是用户代码,在用户空间执行。

      • 切换涉及到了进程上下文切换。而且还是频繁的交错切换。

      • 每次切换到内核态的时候,内核栈都会被清空。

      • 如果是还涉及到了系统调用,就更加复杂,因为还要切换回执行的调用函数而不是应用程序。

      • 解决方案

        • 将内核的上下文,以及内核栈拷贝到函数调用的用户进程栈中。执行完了之后执行相反的操作恢复之前的上下文。
        kenerl mode            user Mode
                               continue_work_flow
        do_signal()
        handle_signal()
        setup_frame()
                               signal_handler()
        system_call()
        sys_sigreturn()
        restore_sigcontext()
                               continue_work_flow
        
        • 中断返回当前执行进程执行do_signal

          • 如果是可以唤醒就强制唤醒立即执行。
          • 不可以唤醒则是在唤醒之后第一时间执行这个函数。
        • 非阻塞信号挂起到进程后。这个进程当从中断或者异常中返回时,就会检测这个do_signal函数来处理信号。通过setup_frame or setup_rt_frame创建一个用户栈帧,一般有多个栈帧(类比gdb,主要是函数嵌套)

        • 修改了指针计数器到处理函数地址,修改了处理函数代码。

        • 执行完了处理函数,再执行setup_frame or setup_rt_frame添加的额外代码。

        • 这些代码调用了sigreturn or rt_sigreturn,这个函数调用restore_sigcontext拷贝之前存在用户进程栈的上下文,栈信息到内核上。

        • 执行完了系统调用就返回常规的进程,继续执行。(这里之前的进程就在执行或者是马上就要执行。)

hmoban主题是根据ripro二开的主题,极致后台体验,无插件,集成会员系统
自学咖网 » Linux信号(Understanding Linux Kernel 3rd)