Linux kernel 分析之十一:信号通信
2015-07-22 22:33
726 查看
信号是进程之间通信的一种方式。它包括3部分操作:
1.设置信号处理函数。系统调用signal。内核调用sys_signal(),设置当前进程对某信号的处理函数。
2.发送信号.系统调用kill。内核调用sys_kill()。向目标进程发送信号。3.接收并处理信号。目标进程调用do_signal()处理信号。
从用户态的角度看,目标进程在执行用户态的代码时突然“中断”,转而去执行对应的信号处理函数(同样在用户态)。等到信号处理函数执行完后,又从原来被中断的代码开始执行。
如何达到这样的效果呢?由前面的几种内核的伪装现场的手段,我们可以猜出它这次使用的手段。比如,要让目标进程执行信号处理函数,在内核态中当然不可能直接调用,但是可以通过设置pt_regs中的eip来达到这种效果。但是,要使目标进程在执行完信号处理函数后,又恢复到被中断的现场继续执行,那得花些技巧。不过,不外乎设置堆栈。这一次还包括了用户态堆栈。由于恢复的任务比较艰巨,系统干脆提供了一个系统调用sigreturn 。
既然内核希望用户在执行完信号处理函数后,调用sigreturn。接下去的思路就比较简单了。就是先把用户态的eip设置为signal_handler(通过修改pt_regs中的eip来实现),然后把堆栈中的返回地址改成调用sigreturn的一段代码的入口(当然原来的返回地址也还是要保存的)并且把相关参数“压入”用户态堆栈。
这样,在源进程发送信号后不久,目标进程被调度到,然后执行到do_signal。对信号一一作处理。调用顺序:
do_signal()->handle_signal()->setup_rt_frame()用来设置用户态堆栈。
我们看看这个函数做了些啥?
447 frame = get_sigframe(ka, regs, sizeof(*frame));
struct rt_sigframe __user *frame是内核在用户态的堆栈上新分配的一个数据结构。
011 struct rt_sigframe
012 {
013 char *pretcode;
014 int sig;
015 struct siginfo *pinfo;
016 void *puc;
017 struct siginfo info;
018 struct ucontext uc;
019 struct _fpstate fpstate;
020 char retcode[8];
021 };
图示:高地址
--------------用户进程原堆栈底部--------------用户进程原堆栈顶部
frame->retcode
frame底部
frame->pretcode返回地址:从signal handler返回后跳转的地址--------------frame顶部:用户进程新堆栈顶部
低地址
里面保存了大量用户态进程的上下文信息。尤其是 pretcode,现在位于用户进程的新堆栈的顶部。
接下去开始设置frame
458 err |= __put_user(usig, &frame->sig);
459 err |= __put_user(&frame->info, &frame->pinfo);
460 err |= __put_user(&frame->uc, &frame->puc);
461 err |= copy_siginfo_to_user(&frame->info, info);
462 if (err)
463 goto give_sigsegv;
464
465 /* Create the ucontext. */
466 err |= __put_user(0, &frame->uc.uc_flags);
467 err |= __put_user(0, &frame->uc.uc_link);
468 err |= __put_user(current->sas_ss_sp, &frame->uc.uc_stack.ss_sp);
469 err |= __put_user(sas_ss_flags(regs->esp),
470 &frame->uc.uc_stack.ss_flags);
471 err |= __put_user(current->sas_ss_size, &frame->uc.uc_stack.ss_size);
472 err |= setup_sigcontext(&frame->uc.uc_mcontext, &frame->fpstate,
473 regs, set->sig[0]);
474 err |= __copy_to_user(&frame->uc.uc_sigmask, set, sizeof(*set));
当用户进程根据pt_regs中设置好的eip执行signal handler。执行完毕后就会把frame->pretcode作为返回地址。(这一点2.6.13的内核与2.4的不同,后者是把指针指向retcode,其实仍然是调用sigreturn的代码。)
478 /* Set up to return from userspace. */
479 restorer = &__kernel_rt_sigreturn;
480 if (ka->sa.sa_flags & SA_RESTORER)
481 restorer = ka->sa.sa_restorer;
482 err |= __put_user(restorer, &frame->pretcode);
这里的&__kernel_rt_sigreturn就是内核设置的负责信号处理“善后”工作的代码入口。
定义在arch/i386/kernel/vsyscall-sigreturn.Snm /usr/src/linux/vmlinux|grep _kernel_rt_sigreturn
得,它的值是_kernel_rt_sigreturn&__kernel_rt_sigreturn的值应该是内核态的。
问题来了。
frame->pretcode是作为进程在用户态执行的代码(事实上,从执行signal handler开始,进程一直处于用户态)。它怎么能访问内核态的代码呢?
这不是会段错误么?
这里涉及到PIII中用sysenter来代替系统调用的int 0x80的问题。大概就是内核允许一部分代码给用户态进程访问。例如:
cat /proc/$pid/maps可以看到:
ffffe000-fffff000 ---p 00000000 00:00 0 [vdso]ldd 一个应用程序也可以看到:linux-gate.so.1 => (0xffffe000)
在arch/i386/kernel/中可以看到两个文件:vsyscall-sysenter.sovsyscall-int80.so
也就是说,__kernel_rt_sigreturn这段代码是链接在两个动态链接文件中。而不是vmlinux这个内核文件中。
具体是如何做到的,就不展开说了。
总之,在执行完signal handler后,进程将跳转到__kernel_rt_sigreturn。
021 __kernel_sigreturn:
022 .LSTART_sigreturn:
023 popl %eax /* XXX does this mean it needs unwind info? */
024 movl $__NR_sigreturn, %eax
025 int $0x80
实际上调用的是sigreturn系统调用。该系统调用会根据frame里的信息,把堆栈恢复到处理信号之前的状态。所以这段代码是不返回的。然后,用户进程就像什么事也没发生,继续照常运行。
这里,内核通过设置用户态堆栈的手段,达到了打断用户态进程的运行,转而调用signal handler的目的。手段不可谓不高明。
1.设置信号处理函数。系统调用signal。内核调用sys_signal(),设置当前进程对某信号的处理函数。
2.发送信号.系统调用kill。内核调用sys_kill()。向目标进程发送信号。3.接收并处理信号。目标进程调用do_signal()处理信号。
从用户态的角度看,目标进程在执行用户态的代码时突然“中断”,转而去执行对应的信号处理函数(同样在用户态)。等到信号处理函数执行完后,又从原来被中断的代码开始执行。
如何达到这样的效果呢?由前面的几种内核的伪装现场的手段,我们可以猜出它这次使用的手段。比如,要让目标进程执行信号处理函数,在内核态中当然不可能直接调用,但是可以通过设置pt_regs中的eip来达到这种效果。但是,要使目标进程在执行完信号处理函数后,又恢复到被中断的现场继续执行,那得花些技巧。不过,不外乎设置堆栈。这一次还包括了用户态堆栈。由于恢复的任务比较艰巨,系统干脆提供了一个系统调用sigreturn 。
既然内核希望用户在执行完信号处理函数后,调用sigreturn。接下去的思路就比较简单了。就是先把用户态的eip设置为signal_handler(通过修改pt_regs中的eip来实现),然后把堆栈中的返回地址改成调用sigreturn的一段代码的入口(当然原来的返回地址也还是要保存的)并且把相关参数“压入”用户态堆栈。
这样,在源进程发送信号后不久,目标进程被调度到,然后执行到do_signal。对信号一一作处理。调用顺序:
do_signal()->handle_signal()->setup_rt_frame()用来设置用户态堆栈。
我们看看这个函数做了些啥?
447 frame = get_sigframe(ka, regs, sizeof(*frame));
struct rt_sigframe __user *frame是内核在用户态的堆栈上新分配的一个数据结构。
011 struct rt_sigframe
012 {
013 char *pretcode;
014 int sig;
015 struct siginfo *pinfo;
016 void *puc;
017 struct siginfo info;
018 struct ucontext uc;
019 struct _fpstate fpstate;
020 char retcode[8];
021 };
图示:高地址
--------------用户进程原堆栈底部--------------用户进程原堆栈顶部
frame->retcode
frame底部
frame->pretcode返回地址:从signal handler返回后跳转的地址--------------frame顶部:用户进程新堆栈顶部
低地址
里面保存了大量用户态进程的上下文信息。尤其是 pretcode,现在位于用户进程的新堆栈的顶部。
接下去开始设置frame
458 err |= __put_user(usig, &frame->sig);
459 err |= __put_user(&frame->info, &frame->pinfo);
460 err |= __put_user(&frame->uc, &frame->puc);
461 err |= copy_siginfo_to_user(&frame->info, info);
462 if (err)
463 goto give_sigsegv;
464
465 /* Create the ucontext. */
466 err |= __put_user(0, &frame->uc.uc_flags);
467 err |= __put_user(0, &frame->uc.uc_link);
468 err |= __put_user(current->sas_ss_sp, &frame->uc.uc_stack.ss_sp);
469 err |= __put_user(sas_ss_flags(regs->esp),
470 &frame->uc.uc_stack.ss_flags);
471 err |= __put_user(current->sas_ss_size, &frame->uc.uc_stack.ss_size);
472 err |= setup_sigcontext(&frame->uc.uc_mcontext, &frame->fpstate,
473 regs, set->sig[0]);
474 err |= __copy_to_user(&frame->uc.uc_sigmask, set, sizeof(*set));
当用户进程根据pt_regs中设置好的eip执行signal handler。执行完毕后就会把frame->pretcode作为返回地址。(这一点2.6.13的内核与2.4的不同,后者是把指针指向retcode,其实仍然是调用sigreturn的代码。)
478 /* Set up to return from userspace. */
479 restorer = &__kernel_rt_sigreturn;
480 if (ka->sa.sa_flags & SA_RESTORER)
481 restorer = ka->sa.sa_restorer;
482 err |= __put_user(restorer, &frame->pretcode);
这里的&__kernel_rt_sigreturn就是内核设置的负责信号处理“善后”工作的代码入口。
定义在arch/i386/kernel/vsyscall-sigreturn.Snm /usr/src/linux/vmlinux|grep _kernel_rt_sigreturn
得,它的值是_kernel_rt_sigreturn&__kernel_rt_sigreturn的值应该是内核态的。
问题来了。
frame->pretcode是作为进程在用户态执行的代码(事实上,从执行signal handler开始,进程一直处于用户态)。它怎么能访问内核态的代码呢?
这不是会段错误么?
这里涉及到PIII中用sysenter来代替系统调用的int 0x80的问题。大概就是内核允许一部分代码给用户态进程访问。例如:
cat /proc/$pid/maps可以看到:
ffffe000-fffff000 ---p 00000000 00:00 0 [vdso]ldd 一个应用程序也可以看到:linux-gate.so.1 => (0xffffe000)
在arch/i386/kernel/中可以看到两个文件:vsyscall-sysenter.sovsyscall-int80.so
也就是说,__kernel_rt_sigreturn这段代码是链接在两个动态链接文件中。而不是vmlinux这个内核文件中。
具体是如何做到的,就不展开说了。
总之,在执行完signal handler后,进程将跳转到__kernel_rt_sigreturn。
021 __kernel_sigreturn:
022 .LSTART_sigreturn:
023 popl %eax /* XXX does this mean it needs unwind info? */
024 movl $__NR_sigreturn, %eax
025 int $0x80
实际上调用的是sigreturn系统调用。该系统调用会根据frame里的信息,把堆栈恢复到处理信号之前的状态。所以这段代码是不返回的。然后,用户进程就像什么事也没发生,继续照常运行。
这里,内核通过设置用户态堆栈的手段,达到了打断用户态进程的运行,转而调用signal handler的目的。手段不可谓不高明。
相关文章推荐
- 每天一个linux命令(8):cp 命令
- linux下动态库的编写和调用
- linux安装
- Linux下php5.3.6的安装:checking whether libxml build works... no
- linux 小记
- 嵌入式linux之定时器防抖
- linux 命令之 dmidecode
- Linux中的ulimit命令简介(某公司社招笔试试题)
- linux系统配置sftp服务器详解
- Linux kernel 分析之十:内核线程
- Linux kernel 分析之九:fork()系统调用
- linux中的lsof命令简介(某公司社招笔试试题)
- RHEL一(管理文件、通配符、特殊符号)
- Linux目录结构及文件基本操作
- linux patch 命令小结
- linux 日志服务器的构建
- linux文件压缩与解压
- linux中软件包管理
- linux中软件包管理
- 下载adt-bundle-linux-x86_64-20140702.zip 和 android sdk