Linux信號(Understanding Linux Kernel 3rd)

  • 總覽

    • 產生

      內核或應用程序產生,一份簡短的信息。

    • 傳遞

      • 掛起狀態
      • 非掛起狀態
    • 信號類型

      • 發給進程的信號(所有線程共享)
      • 發給線程的信號
    • 處理者

      • 進程信號是其中一個沒有屏蔽這個信號的線程處理。
      • 線程就是指定線程處理。
    • 處理方式

      • do_signal處理
      • 創建對應的特定棧幀來處理。
    • 信號處理函數

      • 整個進程中的線程共享。
      • 有默認也有自定義。
      • 需要的信息也可以自定義。
  • 信號的作用

    • 簡訊

      • 一份簡短的信息。
      • 生產者是內核或進程。
      • 處理者是具體的線程或者是進程組中符合處理條件的線程。
    • 內容

      • 標準的信號就只有一個數字。
      • 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__ */
        
    • 信號發起者信息

      /*一般定義在  /usr/include/bits/siginfo.h */
      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

      一個共享體,根據信號的不同,使用變量不同。

  • 生成信號

    • 生成的兩個步驟

      • 第一步是更新目標進程的值。

      • 第二步傳遞,一般不直接執行,而是根據目標的狀態和信號代碼來決定。可能會喚醒,可能會強制進程接受信號。

    • 內核或函數生成信號都是通過下面的幾種函數生成的

      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
        • 不然就搜索線程組,從接受到上一個共享信號的線程開始。
      • 成功找到處理線程就可以開始傳遞信號

        • 信號檢測是否爲指明信號,如果是則殺死所有。
        • 不是則喚醒合適的線程通知其有新的信號需要處理。
  • 傳遞信號

    • 內核處理信號

      • 通過系統調用得知有信號傳遞,通過傳遞的信號值和目標進程中的目標進程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拷貝之前存在用戶進程棧的上下文,棧信息到內核上。

        • 執行完了系統調用就返回常規的進程,繼續執行。(這裏之前的進程就在執行或者是馬上就要執行。)

  • 發表評論
    所有評論
    還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
    相關文章