程序8-6 linux/kernel/signal.c


  1 /*

  2  *  linux/kernel/signal.c

  3  *

  4  *  (C) 1991  Linus Torvalds

  5  */

  6

  7 #include <linux/sched.h>  // 调度程序头文件,定义任务结构task_struct、初始任�0的数据,

                              // 还有一些有关描述符参数设置和获取的嵌入式汇编函数宏语句�

  8 #include <linux/kernel.h> // 内核头文件。含有一些内核常用函数的原形定义�

  9 #include <asm/segment.h>  // 段操作头文件。定义了有关段寄存器操作的嵌入式汇编函数�

 10

 11 #include <signal.h>       // 信号头文件。定义信号符号常量,信号结构及信号操作函数原型�

 12 #include <errno.h>        // 出错号头文件。定义出错号符号常量�

 13  

    // 获取当前任务信号屏蔽位图(屏蔽码或阻塞码)�sgetmask可分解为signal-get-mask。以下类似�

 14 int sys_sgetmask()

 15 {

 16         return current->blocked;

 17 }

 18

    // 设置新的信号屏蔽位图。信�SIGKILLSIGSTOP不能被屏蔽。返回值是原信号屏蔽位图�

 19 int sys_ssetmask(int newmask)

 20 {

 21         int old=current->blocked;

 22

 23         current->blocked = newmask & ~(1<<(SIGKILL-1)) & ~(1<<(SIGSTOP-1));

 24         return old;

 25 }

 26

    // 检测并取得进程收到的但被屏蔽(阻塞)的信号。还未处理信号的位图将被放入set中�

 27 int sys_sigpending(sigset_t *set)

 28 {

 29     /* fill in "set" with signals pending but blocked. */

        /* 用还未处理并且被阻塞信号的位图填�set指针所指位置处 */

    // 首先验证进程提供的用户存储空间应�4个字节。然后把还未处理并且被阻塞信号的位图填入

    // set指针所指位置处�

 30     verify_area(set,4);

 31     put_fs_long(current->blocked & current->signal, (unsigned long *)set);

 32     return 0;

 33 }

 34

 35 /* atomically swap in the new signal mask, and wait for a signal.

 36  *

 37  * we need to play some games with syscall restarting.  We get help

 38  * from the syscall library interface.  Note that we need to coordinate

 39  * the calling convention with the libc routine.

 40  *

 41  * "set" is just the sigmask as described in 1003.1-1988, 3.3.7.

 42  *      It is assumed that sigset_t can be passed as a 32 bit quantity.

 43  *

 44  * "restart" holds a restart indication.  If it's non-zero, then we

 45  *      install the old mask, and return normally.  If it's zero, we store

 46  *      the current mask in old_mask and block until a signal comes in.

 47  */

    /* 自动地更换成新的信号屏蔽码,并等待信号的到来�

     *

     * 我们需要对系统调用�syscall)做一些处理。我们会从系统调用库接口取得某些信息�

     * 注意,我们需要把调用规则�libc库中的子程序统一考虑�

     *

     * "set" 正是POSIX标准1003.1-19883.3.7节中所描述的信号屏蔽码sigmask

     *       其中认为类型sigset_t能够作为一�32位量传递�

     *

     * "restart"中保持有重启指示标志。如果为�0值,那么我们就设置原来的屏蔽码,

     *       并且正常返回。如果它�0,那么我们就把当前的屏蔽码保存在oldmask

     *       并且阻塞进程,直到收到任何一个信号为歀�

     */

    // 该系统调用临时把进程信号屏蔽码替换成参数中给定的set,然后挂起进程,直到收到一�

    // 信号为止�

    // restart是一个被中断的系统调用重新启动标志。当�1次调用该系统调用时,它是0。并�

    // 在该函数中会把进程原来的阻塞� blocked保存起来�old_mask),并设� restart为非0

    // 值。因此当进程�2次调用该系统调用时,它就会恢复进程原来保存在old_mask中的阻塞码�

 48 int sys_sigsuspend(int restart, unsigned long old_mask, unsigned long set)

 49 {

    // pause()系统调用将导致调用它的进程进入睡眠状态,直到收到一个信号。该信号或者会终止

    // 进程的执行,或者导致进程去执行相应的信号捕获函数�

 50     extern int sys_pause(void);

 51

    // 如果restart标志不为0,表示是重新让程序运行起来。于是恢复前面保存在old_mask中的

    // 原进程阻塞码。并返回�-EINTR(系统调用被信号中断)�

 52     if (restart) {

 53         /* we're restarting */       /* 我们正在重新启动系统调用 */

 54         current->blocked = old_mask;

 55         return -EINTR;

 56     }

    // 否则表示restart标志的值是0。表示第1次调用。于是首先设�restart标志(置�1),

    // 保存进程当前阻塞� blocked old_mask中,并把进程的阻塞码替换� set。然后调�

    // pause()让进程睡眠,等待信号的到来。当进程收到一个信号时�pause() 就会返回,并�

    // 进程会去执行信号处理函数,然后本调用返回 -ERESTARTNOINTR 码退出。这个返回码说明

    // 在处理完信号后要求返回到本系统调用中继续运行,即本系统调用不会被中断�

 57     /* we're not restarting.  do the work */

        /* 我们不是重新重新运行,那么就干活� */

 58     *(&restart) = 1;

 59     *(&old_mask) = current->blocked;

 60     current->blocked = set;

 61     (void) sys_pause();                 /* return after a signal arrives */

 62     return -ERESTARTNOINTR;             /* handle the signal, and come back */

 63 }

 64

    // 复制sigaction数据�fs数据�to处。即从内核空间复制到用户(任务)数据段中�

 65 static inline void save_old(char * from,char * to)

 66 {

 67         int i;

 68

    // 首先验证to处的内存空间是否足够大。然后把一�sigaction结构信息复制�fs段(用户�

    // 空间中。宏函数put_fs_byte()include/asm/segment.h中实现�

 69         verify_area(to, sizeof(struct sigaction));

 70         for (i=0 ; i< sizeof(struct sigaction) ; i++) {

 71                 put_fs_byte(*from,to);

 72                 from++;

 73                 to++;

 74         }

 75 }

 76

    // sigaction数据�fs数据�from位置复制�to处。即从用户数据空间取到内核数据段中�

 77 static inline void get_new(char * from,char * to)

 78 {

 79         int i;

 80

 81         for (i=0 ; i< sizeof(struct sigaction) ; i++)

 82                 *(to++) = get_fs_byte(from++);

 83 }

 84

    // signal()系统调用。类似于sigaction()。为指定的信号安装新的信号句�(信号处理程序)

    // 信号句柄可以是用户指定的函数,也可以�SIG_DFL(默认句柄)�SIG_IGN(忽略)�

    // 参数signum --指定的信号;handler -- 指定的句柄;restorer �恢复函数指针,该函数�

    // Libc 库提供。用于在信号处理程序结束后恢复系统调用返回时几个寄存器的原有值以及系�

    // 调用的返回值,就好象系统调用没有执行过信号处理程序而直接返回到用户程序一样� 函数

    // 返回原信号句柄�

 85 int sys_signal(int signum, long handler, long restorer)

 86 {

 87         struct sigaction tmp;

 88

    // 首先验证信号值在有效范围�1--32)内,并且不得是信号SIGKILL(和SIGSTOP)。因为这

    // 两个信号不能被进程捕获�

 89         if (signum<1 || signum>32 || signum==SIGKILL || signum==SIGSTOP)

 90                 return -EINVAL;

    // 然后根据提供的参数组�sigaction结构内容�sa_handler是指定的信号处理句柄(函数)�

    // sa_mask是执行信号处理句柄时的信号屏蔽码�sa_flags是执行时的一些标志组合。这里设�

    // 该信号处理句柄只使用1次后就恢复到默认值,并允许信号在自己的处理句柄中收到�

 91         tmp.sa_handler = (void (*)(int)) handler;

 92         tmp.sa_mask = 0;

 93         tmp.sa_flags = SA_ONESHOT | SA_NOMASK;

 94         tmp.sa_restorer = (void (*)(void)) restorer;      // 保存恢复处理函数指针�

    // 接着取该信号原来的处理句柄,并设置该信号�sigaction结构。最后返回原信号句柄�

 95         handler = (long) current->sigaction[signum-1].sa_handler;

 96         current->sigaction[signum-1] = tmp;

 97         return handler;

 98 }

 99

    // sigaction()系统调用。改变进程在收到一个信号时的操作�signum是除�SIGKILL以外�

    // 任何信号�[如果新操作(action)不为空 ]则新操作被安装。如� oldaction指针不为空,

    // 则原操作被保留到oldaction。成功则返回0,否则为-EINVAL

100 int sys_sigaction(int signum, const struct sigaction * action,

101         struct sigaction * oldaction)

102 {

103         struct sigaction tmp;

104

    // 首先验证信号值在有效范围�1--32)内,并且不得是信号SIGKILL(和SIGSTOP)。因为这

    // 两个信号不能被进程捕获�

105         if (signum<1 || signum>32 || signum==SIGKILL || signum==SIGSTOP)

106                 return -EINVAL;

    // 在信号的sigaction结构中设置新的操作(动作)。如�oldaction指针不为空的话,则将

    // 原操作指针保存到oldaction所指的位置�

107         tmp = current->sigaction[signum-1];

108         get_new((char *) action,

109                 (char *) (signum-1+current->sigaction));

110         if (oldaction)

111                 save_old((char *) &tmp,(char *) oldaction);

    // 如果允许信号在自己的信号句柄中收到,则令屏蔽码为0,否则设置屏蔽本信号�

112         if (current->sigaction[signum-1].sa_flags & SA_NOMASK)

113                 current->sigaction[signum-1].sa_mask = 0;

114         else

115                 current->sigaction[signum-1].sa_mask |= (1<<(signum-1));

116         return 0;

117 }

118

119 /*

120  * Routine writes a core dump image in the current directory.

121  * Currently not implemented.

122  */

    /*

     * 在当前目录中产生core dump映像文件的子程序。目前还没有实现�

     */

123 int core_dump(long signr)

124 {

125         return(0);      /* We didn't do a dump */

126 }

127

    // 系统调用的中断处理程序中真正的信号预处理程序(在kernel/sys_call.s,119行)。这�

    // 代码的主要作用是将信号处理句柄插入到用户程序堆栈中,并在本系统调用结束返回后立刻

    // 执行信号句柄程序,然后继续执行用户的程序�

    // 函数的参数是进入系统调用处理程序sys_call.s开始,直到调用本函数(sys_call.s

    // 125行)前逐步压入堆栈的值。这些值包括(�sys_call.s中的代码行)�

    // � CPU执行中断指令压入的用户栈地址ssesp、标志寄存器eflags和返回地址cseip

    // � 85--91行在刚进�system_call时压入栈的段寄存�dsesfs以及寄存�eax

    // orig_eax)�edxecxebx的值;

    // � 100行调�sys_call_table后压入栈中的相应系统调用处理函数的返回值(eax)�

    // � �124行压入栈中的当前处理的信号值(signr)�

128 int do_signal(long signr,long eax,long ebx, long ecx, long edx, long orig_eax,

129         long fs, long es, long ds,

130         long eip, long cs, long eflags,

131         unsigned long * esp, long ss)

132 {

133         unsigned long sa_handler;

134         long old_eip=eip;

135         struct sigaction * sa = current->sigaction + signr - 1;

136         int longs;                                // current->sigaction[signr-1]

137

138         unsigned long * tmp_esp;

139

    // 以下是调试语句。当定义�notdef时会打印相关信息�

140 #ifdef notdef

141         printk("pid: %d, signr: %x, eax=%d, oeax = %d, int=%d\n",

142                 current->pid, signr, eax, orig_eax,

143                 sa->sa_flags & SA_INTERRUPT);

144 #endif

    // 如果不是系统调用而是其它中断执行过程中调用到本函数时�  roig_eax 值为 -1� 参见

    // sys_call.s 144� 等语句。因此当 orig_eax不等� -1 时,说明是在某个系统调用�

    // 最后调用了本函数。在 kernel/exit.c waitpid() 函数中,如果收到�SIGCHLD 信号�

    // 或者在读管道函�fs/pipe.c中管道当前读数据但没有读到任何数据等情况下,进程收到

    // 了任何一个非阻塞的信号,则都会以 -ERESTARTSYS 返回值返回。它表示进程可以被中断,

    // 但是在继续执行后会重新启动系统调用。返回码-ERESTARTNOINTR说明在处理完信号后要�

    // 返回到原系统调用中继续运行,即系统调用不会被中断。参见前面第62行�

    // 因此下面语句说明如果是在系统调用中调用的本函数,并且相应系统调用的返回码 eax等于

    // -ERESTARTSYS -ERESTARTNOINTR时进行下面的处理(实际上还没有真正回到用户程序中)�

145         if ((orig_eax != -1) &&

146             ((eax == -ERESTARTSYS) || (eax == -ERESTARTNOINTR))) {

    // 如果系统调用返回码是 -ERESTARTSYS(重新启动系统调用),并� sigaction 中含有标�

    // SA_INTERRUPT(系统调用被信号中断后不重新启动系统调用)或者信号值小� SIGCONT或�

    // 信号值大�SIGTTOU(即信号不是SIGCONTSIGSTOPSIGTSTPSIGTTIN SIGTTOU),�

    // 修改系统调用的返回值为eax = -EINTR,即被信号中断的系统调用�

147                 if ((eax == -ERESTARTSYS) && ((sa->sa_flags & SA_INTERRUPT) ||

148                     signr < SIGCONT || signr > SIGTTOU))

149                         *(&eax) = -EINTR;

150                 else {

    // 否则就恢复进程寄存器eax在调用系统调用之前的值,并且把原程序指令指针回调2字节。即

    // 当返回用户程序时,让程序重新启动执行被信号中断的系统调用�

151                         *(&eax) = orig_eax;

152                         *(&eip) = old_eip -= 2;

153                 }

154         }

    // 如果信号句柄�SIG_IGN1,默认忽略句柄)则不对信号进行处理而直接返回�

155         sa_handler = (unsigned long) sa->sa_handler;

156         if (sa_handler==1)

157                 return(1);   /* Ignore, see if there are more signals... */

    // 如果句柄�SIG_DFL0,默认处理),则根据具体的信号进行分别处理�

158         if (!sa_handler) {

159                 switch (signr) {

    // 如果信号是以下两个则也忽略之,并返回�

160                 case SIGCONT:

161                 case SIGCHLD:

162                         return(1);  /* Ignore, ... */

163

    // 如果信号是以�4种信号之一,则把当前进程状态置为停止状�TASK_STOPPED。若当前进程

    // 父进程对SIGCHLD信号� sigaction处理标志 SA_NOCLDSTOP (即当子进程停止执行或又�

    // 续执行时不要产生SIGCHLD信号)没有置位,那么就给父进程发�SIGCHLD信号�

164                 case SIGSTOP:

165                 case SIGTSTP:

166                 case SIGTTIN:

167                 case SIGTTOU:

168                         current->state = TASK_STOPPED;

169                         current->exit_code = signr;

170                         if (!(current->p_pptr->sigaction[SIGCHLD-1].sa_flags &

171                                         SA_NOCLDSTOP))

172                                 current->p_pptr->signal |= (1<<(SIGCHLD-1));

173                         return(1);  /* Reschedule another event */

174

    // 如果信号是以�6种信号之一,那么若信号产生�core dump,则以退出码�signr|0x80

    // 调用do_exit()退出。否则退出码就是信号值�do_exit()的参数是返回码和程序提供的退�

    // 状态信息。可作为wait()waitpid()函数的状态信息。参�sys/wait.h文件�13-18行�

    // wait()waitpid()利用这些宏就可以取得子进程的退出状态码或子进程终止的原因(信号)�

175                 case SIGQUIT:

176                 case SIGILL:

177                 case SIGTRAP:

178                 case SIGIOT:

179                 case SIGFPE:

180                 case SIGSEGV:

181                         if (core_dump(signr))

182                                 do_exit(signr|0x80);

183                         /* fall through */

184                 default:

185                         do_exit(signr);

186                 }

187         }

188         /*

189          * OK, we're invoking a handler

190          */

            /*

             * OK,现在我们准备对信号句柄调用的设�

             */

    // 如果该信号句柄只需被调用一次,则将该句柄置空。注意,该信号句柄在前面已经保存�

    // sa_handler指针中�

    // 在系统调用进入内核时,用户程序返回地址�eipcs)被保存在内核态栈中。下面这段代

    // 码修改内核态堆栈上用户调用系统调用时的代码指针 eip 为指向信号处理句柄,同时也将

    // sa_restorersignr、进程屏蔽码(如果SA_NOMASK没置�)eaxecxedx作为参数以及

    // 原调用系统调用的程序返回指针及标志寄存器值压入用户堆栈� 因此在本次系统调用中�

    // 返回用户程序时会首先执行用户的信号句柄程序,然后再继续执行用户程序�

191         if (sa->sa_flags & SA_ONESHOT)

192                 sa->sa_handler = NULL;

    // 将内核态栈上用户调用系统调用下一条代码指令指�eip指向该信号处理句柄。由�C函数

    // 是传值函数,因此�eip赋值时需要使� "*(&eip)" 的形式。另外,如果允许信号自己�

    // 处理句柄收到信号自己,则也需要将进程的阻塞码压入堆栈�

    // 这里请注意,使用如下方式(第193行)对普�C函数参数进行修改是不起作用的。因为当

    // 函数返回时堆栈上的参数将会被调用者丢弃。这里之所以可以使用这种方式,是因为该函数

    // 是从汇编程序中被调用的,并且在函数返回后汇编程序并没有把调用do_signal()时的所�

    // 参数都丢弃�eip等仍然在堆栈中�

    // sigaction结构�sa_mask字段给出了在当前信号句柄(信号描述符)程序执行期间应该被

    // 屏蔽的信号集。同时,引起本信号句柄执行的信号也会被屏蔽� 不过�sa_flags中使用了

    // SA_NOMASK标志,那么引起本信号句柄执行的信号将不会被屏蔽掉。如果允许信号自己的�

    // 理句柄程序收到信号自己,则也需要将进程的信号阻塞码压入堆栈�

193         *(&eip) = sa_handler;

194         longs = (sa->sa_flags & SA_NOMASK)?7:8;

    // 将原调用程序的用户堆栈指针向下扩�7(或8)个长字(用来存放调用信号句柄的参数等)�

    // 并检查内存使用情况(例如如果内存超界则分配新页等)�

195         *(&esp) -= longs;

196         verify_area(esp,longs*4);

    // 在用户堆栈中从下到上存放sa_restorer、信�signr、屏蔽码blocked(如�SA_NOMASK

    // 置位)�eaxecxedxeflags和用户程序原代码指针�

197         tmp_esp=esp;

198         put_fs_long((long) sa->sa_restorer,tmp_esp++);

199         put_fs_long(signr,tmp_esp++);

200         if (!(sa->sa_flags & SA_NOMASK))

201                 put_fs_long(current->blocked,tmp_esp++);

202         put_fs_long(eax,tmp_esp++);

203         put_fs_long(ecx,tmp_esp++);

204         put_fs_long(edx,tmp_esp++);

205         put_fs_long(eflags,tmp_esp++);

206         put_fs_long(old_eip,tmp_esp++);

207         current->blocked |= sa->sa_mask;   // 进程阻塞�(屏蔽�)添上sa_mask中的码位�

208         return(0);              /* Continue, execute handler */

209 }

210