linux 内核信号处理概述(do_signal)

本文深入探讨Linux内核的信号处理,包括do_signal和get_signal函数的作用。重点讲解了get_signal函数的四个关键步骤,如处理特殊信号、调度信号处理、保存用户态上下文及设置返回地址。同时,介绍了系统调用重启的过程,如何处理信号中断系统调用的情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

分析do_signal之前我想先要分析下get_signal这个函数。

get_signal

1  如果信号是SIGNAL_CLD_STOPPED或者SIGNAL_CLD_CONTINUED,则调用do_notify_parent_cldstop发送信息给调试进程

2 进程已经处于将要退出状态也就是可能要进入coredump(被设置了SIGNAL_GROUP_EXIT),那么直接设置信号为SIGKILL且进行coredump操作在返回

3  循环调用dequeue_signal从tsk->pending和tsk->signal->shared_pending取下挂起的信号量然后获取sighand->action,如果信号是SIG_IGN则继续循环处理下一个挂起的信号,如果设置了SA_ONESHOT标志则重置处理函数ka->sa.sa_handler = SIG_DFL;跳出循环,返回。

4 如果对应的处理函数是SIG_DFL则判断信号是否是默认无动作信号如果是则继续处理下一个 信号。在判断是否是stop类信号,如果是则调用do_signal_stop(去激活组停止,并且唤醒进程组其他进程)处理。

其实各位想过没信号处理函数是用户太的函数不能在内核太直接引用也就是说执行用户指定的信号处理函数还需要切回用户太并且能够等程序执行完后能够正常恢复到原来的用户程序执行流。

int get_sigframe(struct rt_sigframe_user_layout *user,struct ksignal *ksig, struct pt_regs *regs)

{

       1  获取用户太堆栈sp的两个位置是存放user->next_frame (struct frame_record)和user->sigframe(sigframe_size(user)),栈是向下增长的,此处用的都是减法所以原有的栈不会被破坏

       2 调用int __save_altstack(stack_t __user *uss, unsigned long sp)存储t->sas_ss_sp、t->sas_ss_flags、t->sas_ss_size都存储到user.sigframe->uc.uc_stack里

       3 调用setup_sigframe将用户态的寄存器保存在user->next_frame和user->sigframe->uc.uc_mcontext里。

       4  调用setup_return修改用户太的寄存器的值

             regs->regs[0] = usig;
            regs->sp = (unsigned long)user->sigframe;
            regs->regs[29] = (unsigned long)&user->next_frame->fp;
            regs->pc = (unsigned long)ka->sa.sa_handler;     

             还有个最重要的 

                 regs->regs[30] = (unsigned long)sigtramp ; // __sigrestore_t sigtramp, regs[30]存储的是函数返回地址 ,也就是信号处理函数执行完成之后接着执行的指令地址。 这个地址指令其实也就是调用  sigreturn系统调用(eax int0x80),这个系统调用完成的主要任务就是恢复用户太寄存器。        

}

current->sas_ss_sp 和 current->sas_ss_size信号处理程序备用堆栈地址和大小

当设置了SA_ONSTACK标志会使用备用堆栈,当没有设置这个标志的时候

handle_signal :

        1 主要就是调用setup_rt_frame进行栈构造以及regs修改

        2  检查寄存器是否合法(暂时不关心)

do_signal:

        1 调用get_signal获取待处理的信号

         2 调用handle_signal去处理信号

其实还有个有意思的地方是系统调用重启:

                1 首先判断是否在系统调用中通过in_syscall(regs->syscallno != NO_SYSCALL)

                2 如果在则设置restart_addr地址为regs->pc - (compat_thumb_mode(regs) ? 2 : 4);

                3 获取上个函数返回值retval = regs->regs[0];

                switch (retval)

                case -ERESTARTNOHAND:
                case -ERESTARTSYS:
                case -ERESTARTNOINTR:
                case -ERESTART_RESTARTBLOCK:
                    regs->regs[0] = regs->orig_x0;//保存系统调用号
                    regs->pc = restart_addr;//重新赋值pc值为上条指令也就是系统调用的指令,因为肯定是当前pc的上条指令导致陷入内核执行后面一系列操作的。也就是上条指令,先不管内核指令跑到那个了,只关心用户态的指令是不是执行了某条指令陷入了内核执行了某个系统调用,然后pc会自动指向下一条用户态的指令。这里将指令后退也就是所重新执行一边刚刚触发陷入内核的执行系统调用的指令。所以等信号处理函数执行完成后会重新执行这条指令那么就会重新陷入内核重新执行系统调用上层应用层不会存在感知。
                    break;

                

                 if (regs->pc == restart_addr &&
                            (retval == -ERESTARTNOHAND ||
                             retval == -ERESTART_RESTARTBLOCK ||
                             (retval == -ERESTARTSYS &&
                              !(ksig.ka.sa.sa_flags & SA_RESTART)))) {
                            regs->regs[0] = -EINTR;//当没有指定SA_RESTART直接设置返回值为 -EINTR表示系统调用被信号中断
                            regs->pc = continue_addr;//恢复pc为原来的位置也就是触发系统调用指令的下一条指令。主要do_signal函数都是在系统调用返回用户态前执行。这个流程很简单自己去跟一下即可。
                }

到这里基本上内核关于信号处理这块主要原理说完了。

                        

### Linux 内核信息概述 Linux 内核作为操作系统的核心部分,负责管理和协调硬件资源以及提供应用程序运行所需的环境。对于深入了解 Linux 内核的工作机制及其开发细节,可以从多个方面入手。 #### 创建内核线程的方法 创建内核线程的功能由 `kernel_thread` 函数实现,该函数位于 `kernel/fork.c` 文件中。此函数用于启动一个新的内核线程,并返回新进程的 PID 值[^2]: ```c pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags) { struct kernel_clone_args args = { .flags = ((lower_32_bits(flags) | CLONE_VM | CLONE_UNTRACED) & ~CSIGNAL), .exit_signal = (lower_32_bits(flags) & CSIGNAL), .stack = (unsigned long)fn, .stack_size = (unsigned long)arg, }; return _do_fork(&args); } ``` 这段代码展示了如何利用特定标志位和参数配置来初始化新的内核线程实例。 #### 内核指针的安全打印方式 为了保护敏感信息不被未授权访问者获取,在打印内核地址时会采用特殊格式 `%pK` 来代替普通的十六进制表示法。这种做法取决于系统控制变量 `kptr_restrict` 的设置情况,详情可参见官方文档说明[^3]。 #### 查询已加载模块的信息工具 当需要查看当前系统上已经安装或者正在使用的驱动程序及其他组件的状态时,可以借助命令行实用程序 `modinfo` 完成这项工作。它能够解析 `/lib/modules/<version>/modules.dep` 和其他关联文件中的元数据记录,从而提取出关于各个模块的关键属性,比如名称、版本号、许可证声明等[^4]。 ```bash modinfo -F description ext4 ``` 上述指令将会输出 Ext4 文件系统的简短介绍文字。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值