请问在程序中调用signal()进行注册的时候,注册的过程是怎样实现的

xinxiakk 2007-01-30 02:47:01
最近在看linux0.11,有这么个问题,希望大家解答。
我想问的是在程序中出现signal()函数的时候,是不是仅仅只是注册这个函数,而当程序往下执行而发出信号的时候,这个时候才发生int0x80中断从而进行调用sys_signal和do_signal函数?另外,注册这个过程是怎样实现的?

为了表述的更清晰一些,我在网上找了个例子来说明,如下:

int main()
{
/* 进程号 */
pid_t pid;
/* 想用信号?那先对信号处理函数进行注册,对应起来 */
signal(SIGCHLD, handler);
/* 程序在这里“分叉”,新的进程创建了 */
pid = fork();
/* 通过fork()的返回值来判断是父进程还是子进程 */
switch(pid){
/* 返回 -1,很不幸,创建进程失败了。可能是没有足够的内存空间,也可能已经开起了太多的进程。 */
case -1:
perror("fork\n");
break;
/* 返回 0,现在是在子进程里运行,那就调用子进程的操作函数。 */
case 0:
/* 一个运行65535次的循环,如果你的机子太快,不能看清楚两个进程同时运行的效果,那就再加大循环次数。或用sleep()函数 */
big_loop(FAC_N);
/* 取得子进程的PID,你可以看清楚子进程和父进程的PID是不同的(子进程的PID比父进程的要大,因为是在父进程运行后才创建的)。*/
printf("PID:%d\n", getpid());
/* 子进程执行完了,发送信号 SIGCHLD。由于前面对这个信号进行了注册,这时就会转入信号处理操作。 */
break;
/* 哈哈,返回的即不是错误,又不是子进程,那就是父进程喽。*/
default:
/* 这里让用户输入了4个数 */
input_information();
/* 取得子进程的PID。*/
printf("PID:%d\n", getpid());
break;
}
exit(0);
}


请问是不是在

/* 想用信号?那先对信号处理函数进行注册,对应起来 */
signal(SIGCHLD, handler);

这里的时候还没有进行int0x80的系统调用,这里仅仅是注册?那注册如何实现和完成的?


而在

/* 子进程执行完了,发送信号 SIGCHLD。由于前面对这个信号进行了注册,这时就会转入信号处理操作。 */

这个地方才触发了真正的signal的系统调用的?

非常希望大家能回答我的问题,不甚感激
...全文
995 16 打赏 收藏 转发到动态 举报
写回复
用AI写文章
16 条回复
切换为时间正序
请发表友善的回复…
发表回复
braveboy1983 2008-12-13
  • 打赏
  • 举报
回复
signal()函数是系统调用sys_signal()的用户调用接口函数。当用户使用signal()函数的时候,由于system_call2的宏定义,使得signal()与sys_signal()函数对应起来,当int 80中断发生时,_system_call函数会根据sys_signal()函数的调用号,在一个系统函数调用函数指针表fn_ptr sys_call_table[]查找sys_signal()函数,最后执行sys_signal()函数。在执行了sys_sinal()函数,也即执行了signal()函数了,把signal()函数中的handler插入到current->sigaction[signum - 1] = tmp;这时,如果要求的信号没有到,是不会去执行handler指向的函数的,system_call会中断返回。直到信号来了,就是在此发生系统调用过程,检测到信号来了,就会去执行do_signal函数,把handler函数调用地址放到iret返回的位置,并进行进程的切换工作,最后执行iret的时候,就会执行handler指向的函数(用户空间),最后再返回到system_call中断的地方,也就是用户空间。
braveboy1983 2008-12-13
  • 打赏
  • 举报
回复
signal()函数是系统调用sys_signal()的用户调用接口函数。当用户使用signal()函数的时候,由于system_call2的宏定义,使得signal()与sys_signal()函数对应起来,当int 80中断发生时,_system_call函数会根据sys_signal()函数的调用号,在一个系统函数调用函数指针表fn_ptr sys_call_table[]查找sys_signal()函数,最后执行sys_signal()函数。在执行了sys_sinal()函数,也即执行了signal()函数了,把signal()函数中的handler插入到current->sigaction[signum - 1] = tmp;这时,如果要求的信号没有到,是不会去执行handler指向的函数的,system_call会中断返回。直到信号来了,就是在此发生系统调用过程,检测到信号来了,就会去执行do_signal函数,把handler函数调用地址放到iret返回的位置,并进行进程的切换工作,最后执行iret的时候,就会执行handler指向的函数(用户空间),最后再返回到system_call中断的地方,也就是用户空间。
xinxiakk 2007-02-07
  • 打赏
  • 举报
回复
我看过代码了,我总结一下,exit(0)会调用sys_exit(0)从而调用do_exit(0),从而调用tell_father,这个tell_father函数于是发出了SIGCHID信号。于是在这个exit系统调用返回的时候,会调用sys_call.s中的汇编片段ret_from_sys_call从而调用do_signal对信号进行处理。

ps:要说明的是任何系统调用的返回都会对信号进行处理。如果有未处理的就处理然后返回,没有则直接返回。

另外,特别感谢aria(*nix learner)能够和我讨论这个问题,在此结帖。
aria 2007-02-06
  • 打赏
  • 举报
回复
是。

不过具体从exit(0)如何与内核结合起来倒是没有研究过,估计得请其他看过的兄台说明了。
xinxiakk 2007-02-05
  • 打赏
  • 举报
回复
接着问

还是按照上面的那个程序
子进程结束,是不是通过break后接着执行的exit(0); 实现的?
aria 2007-02-03
  • 打赏
  • 举报
回复
to xinxiakk(特亚),

并非对parent的schedule()系统调用,只是由于核态中调用了schedule(),因此parent会有机会运行回到用户态(当然不管怎么样parent总是会获得时间片运行的)

建议参考代码理解一下,0.11的代码相对来说较小,易理解些。信号处理和其他部分还是有很多关系的,自己看理解会容易些。
xinxiakk 2007-02-02
  • 打赏
  • 举报
回复
我接着上面扩展一下,那是不是对于所有发信号,都是由调用schedule()而进入系统调用的呢?
xinxiakk 2007-02-02
  • 打赏
  • 举报
回复
aria(*nix learner) ,
按照你的意思,是不是说
这个例子发信号是由于进行了对parent的schedule()系统调用从而进入了系统调用?
lauxp 2007-02-01
  • 打赏
  • 举报
回复
1). 首先signal就是int 0x80实现的,没有更多的功能,就是添加进程的signal_handler
2). Linux属于内核非抢占调度,也就是说内核态不主动让出CPU,schedule没有办法切换进程的,只能在用户态抢占(通过中断/异常进入内核态并完成重新调度的)
3). 信号的处理是在从内核态到用户态切换之前完成的,比如中断/系统调用处理完毕之前:此时为了返回用户态的寄存器(这是由int 0x80导致的并保存在内核态堆栈的),会调用ret_from_intr/ret_from_syscall/ret_from_exception, 这三个函数都会检查有没有pending的signal,如果有,就调用do_signal(),当然这比较复杂,因为signal_handler属于用户态的空间,所以内核要在user->stack上面建立frame,然后再回到内核态,最终返回系统调用/异常。
4). pending signal是由发送方调用kill(),这个系统调用同样进入内核态,找到信号的收者,并把这个信号添加到接受者进程的signals,并且如果不被blocked,设置pending signal标志。这样接受者进程如果有机会得到执行,从内核态回来之前肯定会发现这个信号的,转到3执行就好了。

==========================
aria(*nix learner)说得对

up
mhmdanger 2007-02-01
  • 打赏
  • 举报
回复
分三步
第一步,注册信号处理函数,这个过程要在内核中记录处理函数的地址
第二步,其它进程或内核或自己递送信号,其实也是在内核中作个标记,意思是说发生了某个信号
第三步,从系统返回用户空间之前执行信号处理程序
xmoon1983 2007-02-01
  • 打赏
  • 举报
回复
mark 学习。
aria 2007-01-31
  • 打赏
  • 举报
回复
之前贴子里得第二次的系统调用并非特指。

之前的说法没有专门针对SIGCHLD信号做说明,只是说明了一下一般信号处理的过程。SIGCHLD信号的处理基本符合这个过程。下面试做详细些的说明。

如果针对SIGCHLD专门考虑,可以分几部分看,一般设计父子两个进程,假设名称是parent和child.
1. parent设置了SIGCHLD的处理函数为handler,此时会调用sys_signal(),将信号处理函数信息放到parent的task_struct中
2. parent fork出child,此处和signal关系不大
3. child退出,最终会调用到核态的do_exit() [kernel/exit.c],过程中会调用tell_father() [kernel/exit.c],此时会设置parent的task_struct中的signal的SIGCHLD位,之后会主动引起一次调度,以便parent能有机会处理
4. parent正常肯定会得到机会被调度(可以参考kernel/sched.c中的schedule(),调度时会检查是否有未被block的信号,如果有则将对应进程状态设置为TASK_RUNNING)。一般情况下进程检测信号有几个地方:a)从系统调用(应还有其他情况)回到用户态前,这里的系统调用是泛指;b)进程从睡眠中被唤醒,此时一定是在系统调用中。a),b)情况下回到用户态时都必定要经过ret_from_sys_call [kernel/system_call.s],在ret_from_sys_call处处理时会检查是否有信号。
a) 情况比如一般的read/write系统调用被阻塞情况;属于b) 情况的包括进程时间片用完,切换到其他进程等(可参考_timer_interrupt [kernel/system_call.s],在最后仍然都会跳转到ret_from_sys_call)
你的例子的情况下可能是a)也可能是b),不过这并不重要,因为都会运行到ret_from_sys_call,parent被调度后必定要回到用户态,此时会发现有SIGCHLD信号,接着就会调用do_signal() [kernel/signal.c],之后会将handler插入到用户堆栈中,在返回到用户态后会马上执行handler。

另:赵炯的《Linux内核0.11完全注释》里解释得更加清楚和详细,建议可以参考一下。
rogerfhl 2007-01-31
  • 打赏
  • 举报
回复
最近正看到这里,关注一下
xinxiakk 2007-01-31
  • 打赏
  • 举报
回复
那按照你的说法,这个过程发生了2次系统调用了?
第一次系统调用是由内嵌汇编通过3个参数 int 0x80,signal,SIGCHLD ,从而调用 sys_signal进行注册,是这样吗?
显然这第一个系统调用返回了,然后继续执行下面的代码。

第二次系统调用你没说清楚,只是说返回时调do_signal()

那你能说说第二次的系统调用是什么吗?是通过什么流程走到system_call.s的ret_from_sys_call里的?而这个第二次的系统调用我知道是通过发SIGCHLD触发的,但是是如何通过代码的执行进行系统调用的呢?


aria 2007-01-30
  • 打赏
  • 举报
回复
按我的理解,用户态的signal()执行后会调用核态的sys_signal(),并把信号处理函数以及其他信息添加到当前进程的struct task_struct的sigaction[]中,此时不会触发实际的信号处理函数。

信号的处理的一个很重要的入口在system_call.s的ret_from_sys_call(系统调用返回时)中,在发现有信号pending(且未被block)时,此时会调用do_signal(),将信号处理函数插入到用户态堆栈中,在系统调用返回后会立即执行信号处理函数。

4,436

社区成员

发帖
与我相关
我的任务
社区描述
Linux/Unix社区 内核源代码研究区
社区管理员
  • 内核源代码研究区社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧