当前位置: 首页 > news >正文

Linux kernel signal原理(下)- aarch64架构sigreturn流程

一、前言

       在上篇中写到了linux中signal的处理流程,在do_signal信号处理的流程最后,会通过sigreturn再次回到线程现场,上篇文章中介绍了在X86_64架构下的实现,本篇中介绍下在aarch64架构下的实现原理。

二、sigaction系统调用

#include <signal.h>
#include <stdio.h>
#include <string.h>void signal_handler(int signum, siginfo_t *siginfo, void *context)
{printf("Received signal %d\n", signum);printf("Send by PID: %d\n", siginfo->si_pid);
}int main()
{struct sigaction act;memset(&act, 0, sizeof(act));act.sa_sigaction = signal_handler;act.sa_flags = SA_SIGINFO;if (sigaction(SIGTERM, &act, NULL) < 0) {perror("sigaction");return 1;}while (1) {printf("perfect\n");sleep(10);}return 0;
}

如上是使用sigaction系统调用做的一个简单的测试。

1、放到环境上,并使用strace跟踪进程的系统调用。strace ./test_siginfo
2、向该进程发送SIGTERM信号

可以看到用户态进程在处理SIGTERM信号之后,通过特殊的rt_sigreturn系统调用到内核之后,又再次返回到用户态执行。具体这个rt_sigreturn从哪里来的,下面分析下。

首先看了下glibc的源码,看下在注册sigaction函数的时候是否会把sigreturn系统调用也注册进去,通过阅读源码发现x86_64是采用这个方式实现的,但是aarch64不是。

 

有事就找man,通过看了下man 2 sigreturn,看到了如下关键信息:

怀疑aarch64 架构下是通过vdso实现的。

三、sigreturn实现流程

通过查看上述测试进程在/proc下的内存映射,如下所示:

sh-5.0# cat /proc/974770/maps 
00400000-00401000 r-xp 00000000 b3:07 255                                /data/test_siginfo
00410000-00411000 r--p 00000000 b3:07 255                                /data/test_siginfo
00411000-00412000 rw-p 00001000 b3:07 255                                /data/test_siginfo
06801000-06822000 rw-p 00000000 00:00 0                                  [heap]
7f94c28000-7f94d81000 r-xp 00000000 b3:01 424154                         /usr/lib/aarch64-linux-gnu/libc-2.31.so
7f94d81000-7f94d90000 ---p 00159000 b3:01 424154                         /usr/lib/aarch64-linux-gnu/libc-2.31.so
7f94d90000-7f94d93000 r--p 00158000 b3:01 424154                         /usr/lib/aarch64-linux-gnu/libc-2.31.so
7f94d93000-7f94d96000 rw-p 0015b000 b3:01 424154                         /usr/lib/aarch64-linux-gnu/libc-2.31.so
7f94d96000-7f94d99000 rw-p 00000000 00:00 0 
7f94da7000-7f94dac000 r-xp 00000000 b3:01 42742                          /usr/lib64/libpsh_essence.so
7f94dac000-7f94dbb000 ---p 00005000 b3:01 42742                          /usr/lib64/libpsh_essence.so
7f94dbb000-7f94dbc000 r--p 00004000 b3:01 42742                          /usr/lib64/libpsh_essence.so
7f94dbc000-7f94dbd000 rw-p 00005000 b3:01 42742                          /usr/lib64/libpsh_essence.so
7f94dbd000-7f94dde000 r-xp 00000000 b3:01 423760                         /usr/lib/aarch64-linux-gnu/ld-2.31.so
7f94de6000-7f94dea000 rw-p 00000000 00:00 0 
7f94deb000-7f94ded000 r--p 00000000 00:00 0                              [vvar]
7f94ded000-7f94dee000 r-xp 00000000 00:00 0                              [vdso]
7f94dee000-7f94def000 r--p 00021000 b3:01 423760                         /usr/lib/aarch64-linux-gnu/ld-2.31.so
7f94def000-7f94df1000 rw-p 00022000 b3:01 423760                         /usr/lib/aarch64-linux-gnu/ld-2.31.so
7feceb1000-7feced2000 rw-p 00000000 00:00 0                              [stack]

 其中:

7f94ded000-7f94dee000 r-xp 00000000 00:00 0                              [vdso]

可以看到vdso内存大小:0x1000 = 4096,即一个page的大小。

vdso的起始虚拟地址在进程974770是: 7f94ded000,转化为十进制即547958476800,将这段内存dump到文件中:

sh-5.0# dd if=/proc/974770/mem of=/tmp/linus-vdso.so skip=547958476800 ibs=1 count=4096
dd: /proc/974770/mem: cannot skip to specified offset
4096+0 records in
8+0 records out
4096 bytes (4.1 kB, 4.0 KiB) copied, 0.0178463 s, 230 kB/s

 由于vdso是一个完整的ELF镜像,可以对其进行符号查找:

sh-5.0# objdump -T /tmp/linus-vdso.so /tmp/linus-vdso.so:     file format elf64-littleaarch64DYNAMIC SYMBOL TABLE:
0000000000000000 g    DO ABS  0000000000000000  LINUX_2.6.39 LINUX_2.6.39
0000000000000750 g    DF .text  0000000000000078  LINUX_2.6.39 __kernel_clock_getres
00000000000007cc g    D  .text  0000000000000008  LINUX_2.6.39 __kernel_rt_sigreturn
00000000000005a0 g    DF .text  00000000000001b0  LINUX_2.6.39 __kernel_gettimeofday
0000000000000300 g    DF .text  00000000000002a0  LINUX_2.6.39 __kernel_clock_gettime

从符号表中可以看出,确实是有__kernel_rt_sigreturn的实现。

下面看下内核是如何实现的:

 通过阅读内核源码,handle_signal的实现在构建用户态栈帧的时候可以看到如下关键流程:

static int setup_rt_frame(int usig, struct ksignal *ksig, sigset_t *set,struct pt_regs *regs)
{struct rt_sigframe_user_layout user;struct rt_sigframe __user *frame;int err = 0;fpsimd_signal_preserve_current_state();if (get_sigframe(&user, ksig, regs))return 1;frame = user.sigframe;__put_user_error(0, &frame->uc.uc_flags, err);__put_user_error(NULL, &frame->uc.uc_link, err);err |= __save_altstack(&frame->uc.uc_stack, regs->sp);err |= setup_sigframe(&user, regs, set);if (err == 0) {setup_return(regs, &ksig->ka, &user, usig);  //信号返回关键函数if (ksig->ka.sa.sa_flags & SA_SIGINFO) { //如果注册的时候传入了SA_SIGINFO标记,就会把X1,X2寄存器值传给用户态回调err |= copy_siginfo_to_user(&frame->info, &ksig->info);regs->regs[1] = (unsigned long)&frame->info; //X1regs->regs[2] = (unsigned long)&frame->uc;   //X2}}return err;
}
static void setup_return(struct pt_regs *regs, struct k_sigaction *ka,struct rt_sigframe_user_layout *user, int usig)
{__sigrestore_t sigtramp;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;/** Signal delivery is a (wacky) indirect function call in* userspace, so simulate the same setting of BTYPE as a BLR* <register containing the signal handler entry point>.* Signal delivery to a location in a PROT_BTI guarded page* that is not a function entry point will now trigger a* SIGILL in userspace.** If the signal handler entry point is not in a PROT_BTI* guarded page, this is harmless.*/if (system_supports_bti()) {regs->pstate &= ~PSR_BTYPE_MASK;regs->pstate |= PSR_BTYPE_C;}/* TCO (Tag Check Override) always cleared for signal handlers */regs->pstate &= ~PSR_TCO_BIT;if (ka->sa.sa_flags & SA_RESTORER)   //x86_64架构默认实现sigtramp = ka->sa.sa_restorer;elsesigtramp = VDSO_SYMBOL(current->mm->context.vdso, sigtramp); //aarch_64架构实现方式regs->regs[30] = (unsigned long)sigtramp; //将sigreturn系统调用地址保存在X30寄存器中
}

 通过以上代码可以很清晰的看出在aarch64架构下的实现,即首先会在VDSO的符号表中找到sigreturn的地址,然后保存在X30寄存器中,X30寄存器保存的是函数的返回地址,即在用户态handler执行完成之后要执行的函数地址。对arm寄存器不熟悉的可以参考之前的文章:

ARM64架构栈帧以及帧指针FP-CSDN博客

整个流程可以归结如下图所示:

1、用户程序注册了处理函数signal_handler来捕获SIGTERM信号。

2、当前正在执行main函数时,若发生中断或异常导致切换到内核态。

3、在中断处理完成后,在返回用户态执行main函数之前,检测到有SIGTERM信号pending。

4、内核决定在返回用户态后,不恢复main函数的上下文继续执行,而是调用signal_handler函数。signal_handler函数和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程。

5、signal_handler函数执行完毕后,会自动执行特殊的系统调用sigreturn,再次进入内核态。

6、如果没有新的信号pending,此次返回用户态将会恢复main函数的上下文,并继续执行。

相关文章:

  • XHTMLConverter把docx转换html报java.lang.NullPointerException异常
  • 基于SpringBoot的校园赛事直播管理系统-项目分享
  • 前端如何优雅地对接后端
  • 使用Selenium进行元素定位的全面指南
  • docker容器中uv的使用
  • 前端性能优化全攻略:JavaScript 优化、DOM 操作、内存管理、资源压缩与合并、构建工具及性能监控
  • 从代码学习深度学习 - 异步计算 PyTorch 版
  • 深入理解无监督学习:探索数据的潜在结构
  • Android Studio调试中的坑二
  • Idea创建项目的搭建方式
  • this._uid:Vue 内部为每个组件实例分配的唯一 ID
  • 数字化转型“变形记”:中钧科技经营帮如何让企业长出“智慧骨骼”
  • Python简介
  • 搜索策略的基本概念
  • 【音视频】AAC-ADTS分析
  • Ubuntu与Linux的关系
  • Redis之Java操作redis
  • 性能比拼: Go vs Java
  • 反素数c++
  • 云原生--基础篇-4--CNCF-1-云原生计算基金会(云原生生态发展和目标)
  • 税率飙至3500%!美国双反大棒重击东南亚光伏,中企如何应对
  • 周继红连任中国跳水协会主席
  • 特朗普“炮轰”美联储带崩美股!道指跌超900点,黄金再创新高
  • 商务部:支持外籍医生开设诊所,优化罕见病药品进口抽检模式
  • 言短意长|把水搅浑的京东和美团
  • 中汽协发布规范驾驶辅助宣传与应用倡议书