0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

系统调用:用户栈与内核栈的切换(上)

麦辣鸡腿堡 来源:技术简说 作者:董旭 2023-07-31 11:27 次阅读

当发生系统调用、产生异常,外设发生中断等事件时,会发生用户栈和内核栈之间的切换,本文从系统调用角度分析用户栈与内核栈的切换。

系统调用的演变

x86 的系统调用经历了 int / iret 到 sysenter / sysexit 再到 syscall / sysret 实现方式的转变,关于具体的演化和区别、系统调用的其他细节等将在以后的系统调用专栏里分析。本文从系统调用最原始的int 0x80开始分析用户栈与内核栈的切换,重点看系统调用过程用户栈与内核栈切换的过程中的一些细节。

系统调用-分析从用户栈切换内核栈

内核SYSCALL 入口代码在entry_64.S中:

//arch/x86/entry/entry_64.S
ENTRY(entry_SYSCALL_64)
 UNWIND_HINT_EMPTY
 /* Interrupts are off on entry. */
 swapgs
 // 将用户栈偏移保存到 per-cpu 变量 rsp_scratch 中
 movq %rsp, PER_CPU_VAR(rsp_scratch)
 // 切换到进程内核栈
 movq PER_CPU_VAR(cpu_current_top_of_stack), %rsp

 /* 在栈中倒序构建 struct pt_regs */
 pushq $__USER_DS   /* pt_regs- >ss */
 pushq PER_CPU_VAR(rsp_scratch) /* pt_regs- >sp */
 pushq %r11    /* pt_regs- >flags */
 pushq $__USER_CS   /* pt_regs- >cs */
 pushq %rcx    /* pt_regs- >ip */
GLOBAL(entry_SYSCALL_64_after_hwframe)
 //rax 保存着系统调用号
 pushq %rax    /* pt_regs- >orig_ax */

 PUSH_AND_CLEAR_REGS rax=$-ENOSYS

 TRACE_IRQS_OFF

 /* 保存参数寄存器,调用do_syscall_64函数 */
 movq %rax, %rdi
 movq %rsp, %rsi
call do_syscall_64  /* returns with IRQs disabled */

上面的汇编指令中先将当前用户栈(用户空间栈顶)记录在CPU独占变量区域里(PER_CPU变量),如下所示:

movq %rsp, PER_CPU_VAR(rsp_scratch)

然后将CPU独占区域里记录的内核栈顶放入rsp/esp寄存器

movq PER_CPU_VAR(cpu_current_top_of_stack), %rsp

就是这么简洁,**上面两句汇编:就 将用户栈顶保存在了当前CPU的rsp_scratch这样一个PER_CPU变量里,完成了用户栈的保存 ,然后 将当前内核栈的地址存放到当前栈指针寄存器中 ,**那么此时栈寄存器指向的就是内核栈的栈顶,由此优雅、完美地完成了用户栈到内核栈的切换!

接下来所有的压栈操作都是在内核栈里操作了,依次将用户空间寄存器压栈,此时也是往内核栈push的, 内核使用struct pt_regs初始化内核栈,也就是通过push保存寄存器的值 (将用户栈信息:用户调用的系统调用号、参数、代码段地址、数据段地址等以struct pt_regs形式压入栈) ,形成一个pt_regs结构 ,如下图(源于上篇文章中的分析):

图片

在栈中顺序固定且倒序压栈(在x86_64中,内核栈rbx rbp r12 r13 r14 r15不是必须保存的项(为了访问不越界相应空间必须保留),根据需要保存,linux后续版本采取都保存方式),其中rax保存系统调用号

//rax 保存着系统调用号
 pushq %rax    /* pt_regs- >orig_ax */
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 内核
    +关注

    关注

    3

    文章

    1372

    浏览量

    40278
  • Linux
    +关注

    关注

    87

    文章

    11292

    浏览量

    209329
收藏 人收藏

    评论

    相关推荐

    C函数调用机制与帧原理详解

    当一个C函数被调用时,函数的参数如何传递、堆栈指针如何变化、帧是如何被建立以及如何被消除的,一直缺乏系统性的理解,因此决定花时间学习下函数调用时整个
    发表于 06-08 10:49 1290次阅读
    C函数<b class='flag-5'>调用</b>机制与<b class='flag-5'>栈</b>帧原理详解

    操作系统为什么分内核态和用户态?这两者如何切换

    操作系统为什么分内核态和用户态,这两者如何切换?进程在地址空间会划分为哪些区域?堆和有什么区别?
    发表于 07-23 09:01

    ARMv8的函数调用是什么意思?调用的内存管理是怎样的

    调用解析概念: 任意体系结构的CPU,都设计了一套通用寄存器、状态寄存器及其他控制寄存器,用以维系系统的正常运行。函数调用过程中,CPU一般都需要处理几件事情:保存母函数现场(寄存器
    发表于 05-13 10:36

    用一个实例展示一下Linux内核帧的入和退过程

    1、Linux内核调试方法总结之帧  帧  帧和指针可以说是C语言的精髓。帧是一种特殊的数据结构,在C语言函数
    发表于 11-04 15:47

    一文详解Linux内核回溯与妙用

    网上或多或少都能找到回溯的一些文章,但是讲的都并不完整,没有将内核回溯的功能用于实际的内核、应用程序调试,这是本篇文章的核心:尽可能引导读者将
    的头像 发表于 10-05 10:02 5376次阅读
    一文详解Linux<b class='flag-5'>内核</b>的<b class='flag-5'>栈</b>回溯与妙用

    对Linux的进程内核的认识

    在每一个进程的生命周期中,必然会通过到系统调用陷入内核。在执行系统调用陷入内核之后,这些
    发表于 05-12 08:53 623次阅读
    对Linux的进程<b class='flag-5'>内核</b><b class='flag-5'>栈</b>的认识

    带你了解嵌入式C语言函数调用

    大家都知道函数调用是通过来实现的,而且知道在中存放着该函数的局部变量。但是对于的实现细节可能不一定清楚。
    发表于 07-12 17:08 2109次阅读
    带你了解嵌入式C语言函数<b class='flag-5'>调用</b><b class='flag-5'>栈</b>

    浅谈鸿蒙内核源码的CPU四次换,寄存器改值

    本篇有相当的难度,涉及用户内核的两轮切换,CPU四次换,寄存器改值,将围绕下图来说明. 
    的头像 发表于 04-28 16:56 1604次阅读
    浅谈鸿蒙<b class='flag-5'>内核</b>源码的CPU四次换<b class='flag-5'>栈</b>,寄存器改值

    浅谈鸿蒙内核源码的

    上面的代码和鸿蒙内核方式一样,都采用了递减满的方式, 什么是递减满?
    的头像 发表于 04-24 11:21 1434次阅读
    浅谈鸿蒙<b class='flag-5'>内核</b>源码的<b class='flag-5'>栈</b>

    鸿蒙内核源码分析:用户内核的两次切换

    这是系统调用的总入口,所有的系统调用都要跑这里要统一处理.通过系统号(保存在R7),找到注册函数并回调.完成
    的头像 发表于 04-23 17:17 1894次阅读
    鸿蒙<b class='flag-5'>内核</b>源码分析:<b class='flag-5'>用户</b><b class='flag-5'>栈</b>和<b class='flag-5'>内核</b><b class='flag-5'>栈</b>的两次<b class='flag-5'>切换</b>

    是什么?有什么作用?

    大多数的处理器架构,都有实现硬件。有专门的指针寄存器,以及特定的硬件指令来完成 入/出 的操作。例如在 ARM 架构,R13 (S
    的头像 发表于 06-17 11:19 1.3w次阅读

    Linux中的进程、线程内核以及中断

    首先, (stack) 是一种串列形式的 数据结构。这种数据结构的特点是 后入先出 (LIFO, Last In First Out),数据只能在串列的一端 (称为:顶 top) 进行 推入
    的头像 发表于 05-14 09:30 698次阅读
    Linux中的进程<b class='flag-5'>栈</b>、线程<b class='flag-5'>栈</b>、<b class='flag-5'>内核</b><b class='flag-5'>栈</b>以及中断<b class='flag-5'>栈</b>

    系统调用用户内核切换(下)

    , regs- >dx, regs- >r10, regs- >r8, regs- >r9) ; } syscall_return_slowpath( regs ) ; }
    的头像 发表于 07-31 11:29 668次阅读

    linux中的进程,线程,内核的区别

    大多数的处理器架构,都有实现硬件。有专门的指针寄存器,以及特定的硬件指令来完成 入/出 的操作。例如在 ARM 架构,R13 (S
    发表于 08-18 10:57 499次阅读
    linux中的进程<b class='flag-5'>栈</b>,线程<b class='flag-5'>栈</b>,<b class='flag-5'>内核</b><b class='flag-5'>栈</b>的区别

    malloc在Linux执行的是哪个系统调用

    ()和mmap(),至于为什么是两个,这跟ptmalloc内存池的分配策略有关,稍后介绍。 既然是系统调用,那么就必须处于内核态去处理,而系统内核
    的头像 发表于 11-13 10:36 1006次阅读
    malloc在Linux<b class='flag-5'>上</b>执行的是哪个<b class='flag-5'>系统</b><b class='flag-5'>调用</b>