在system_call.s系统调用处理程序中会调用信号量对应的c函数,do_signa函数是系统调用中断处理程序中的信号处理程序。
call _do_signal
我们来查看一下_do_signal函数的工作流程,它会把信号的处理函数插入到用户程序堆栈中,然后修改中断返回的环境,直接返回到用户态的信号处理函数中先,再从用户态中跳转到原先执行系统调用的后一条语句中。如下图所示
void do_signal(long signr,long eax, long ebx, long ecx, long edx, long fs, long es, long ds, long eip, long cs, long eflags, unsigned long * esp, long ss) { unsigned long sa_handler; long old_eip=eip; /*eip相当于pc*/ struct sigaction * sa = current->sigaction + signr - 1;/*指到当前的信号上*/ int longs; unsigned long * tmp_esp; sa_handler = (unsigned long) sa->sa_handler; /*取出sa指向的中断服务函数*/ if (sa_handler==SIG_IGN) /*信号可忽略则返回*/ return; if (sa_handler==SIG_DFL) { /*default signal handling 为其他信号量时*/ if (signr==SIGCHLD) /*若子进程进程终止或者停止时,将SIGCHLD信号发送给其父进程*/ return; else do_exit(1<<(signr-1)); /*终止进程执行*/ } /*sa_flags:位掩码,指定用于控制信号处理过程的各种选项。sa_flags取值: SA_ONESHOT:当用户注册的信号处理函数sa_handler被执行过一次后, 该信号的处理函数被设为系统默认的处理函数。*/ if (sa->sa_flags & SA_ONESHOT) /*也就是说,执行过了这个函数,就将这个函数清空*/ sa->sa_handler = NULL; /*句柄函数置空*/ *(&eip) = sa_handler; /*eip相当于pc,表示将对应信号处理程序的句柄压入PC中*/ longs = (sa->sa_flags & SA_NOMASK)?7:8; *(&esp) -= longs; /*移动栈指针,移动long这么长*/ verify_area(esp,longs*4); /*验证该区域是否可用*/ tmp_esp=esp; /*sa_restorer 恢复函数的指针。恢复函数用于在 handler 完成后将控制返回给原始程序*/ put_fs_long((long) sa->sa_restorer,tmp_esp++); /*恢复函数的指针压栈*/ put_fs_long(signr,tmp_esp++); /*中断号压栈*/ if (!(sa->sa_flags & SA_NOMASK)) /*SA_NODEFER:当信号处理函数正在进行时,不堵塞对于信号处理函数自身信号功能。*/ put_fs_long(current->blocked,tmp_esp++);/*如果堵塞信号处理函数自身,则将当前的阻塞码压栈*/ put_fs_long(eax,tmp_esp++); /*各种寄存器入栈*/ put_fs_long(ecx,tmp_esp++); put_fs_long(edx,tmp_esp++); put_fs_long(eflags,tmp_esp++); put_fs_long(old_eip,tmp_esp++); /*原来的用户程序被打断的地方压入栈*/ current->blocked |= sa->sa_mask; }
然后再system_call.s中出栈
call _do_signal popl %eax popl %eax popl %ebx popl %ecx popl %edx pop %fs pop %es pop %ds iret
在执行系统调用进入内核态的时候,CPU会在该进程的内核态堆栈上面压入用户程序的SS和ESP,EFLAGS,还有下一条指令所在的CS和EIP。要想让这个中断返回到信号处理函数去执行,就可以修改这个返回的EIP,但这个旧的EIP一定要保存下来,这样才能返回到原程序中,这里的做法是将用户堆栈向下延生,来保存old_eip,再保存些信号处理函数可能改变的寄存器的值和恢复函数sa_restoreer。我觉得这里一定要清楚是在保存在用户态堆栈上面,由于EIP和ESP都变了,所以要在内核态堆栈里面修改这些值。这样在系统调用要返回的时候执行一条iret就会把内核堆栈中的内容弹出,跳转到信号处理程序里面,因为eip已经被设置为指向信号处理程序了。在来看用户态堆栈的栈顶为sa_restorer的地址,所以在信号处理程序执行完,执行ret指令的时候就会返回到栈顶的地址中也就是sa_restorer的地址,在这个程序里面就需要把信号处理程序中改变的寄存器的值恢复成执行系统调用后一条语句的环境,其实就是一直调用pop,之后再执行ret,跳转到old_eip的地址去,也就是原先保存的下一条语句。
Linux0.11内核源码分析——内核信号量
最新推荐文章于 2024-10-16 10:13:45 发布