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

    文章

    1309

    浏览量

    39862
  • Linux
    +关注

    关注

    87

    文章

    10992

    浏览量

    206745
收藏 人收藏

    评论

    相关推荐

    Linux内核系统调用

    )。系统提供了两个方法来完成内核空间与用户空间的数据拷贝:copy_to_user()和copy_from_user()。7. 内核执行系统
    发表于 02-21 10:49

    ARM关于的简单问题,请教

    本帖最后由 yingsun2013 于 2013-12-6 16:28 编辑 ARM里面7种工作模式,除了用户系统外,其它每种模式都有各自独立的空间。C语言工作时,也需要堆和
    发表于 12-06 16:19

    协议介绍

    环境还有待进一步的升级开发。8)本协议目前在ARM LPC2210开发,测试。使用RL8019网络芯片。9)本协议开发在多任务的操作系统UCOS-II下,全部采用事件驱动机制设计
    发表于 09-03 15:02

    协议介绍--TCP/IP

    环境还有待进一步的升级开发。8)本协议目前在ARM LPC2210开发,测试。使用RL8019网络芯片。9)本协议开发在多任务的操作系统UCOS-II下,全部采用事件驱动机制设计
    发表于 09-03 15:03

    协议介绍

    协议介绍本协议的联系方式:qq:292942278附件代码是在ARM7(LPC2210)下开发,用于用户参考。本协议已经移植到ARM9(ATMEL AT91RM9200),使用操
    发表于 09-14 08:44

    汇编调用c函数为什么需要设置

    ,关于系统初始化,也看到指针初始化,即正确给指针sp赋值,却从来没看到有人解释,为何要这样做。接下来,我试图解释这个问题。 首先了解的作用。关于这个,详细讲解要很长的篇幅,故此处
    发表于 07-31 11:11

    Hexagon的软件

    着程序中的一个子程序。 框架包含着如下的元素本地变量以及被子程序使用的数据子程序调用的返回地址(从连接寄存器LR中压入)为以前的框架
    发表于 09-19 17:41

    FreeRTOS任务系统的关系?

    configTOTAL_HEAP_SIZE((size_t)(55*1024))的作用?三个设置大小的地方之间又有何种联系?4、任务系统的关系?多谢指点讨论!!
    发表于 07-15 00:17

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

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

    ARM汇编基础出操作

    ARM 汇编基础出就要对堆栈进行操作,所谓的堆栈其实就是一段内存,这段内存比较特殊,由 SP 指针访问, SP 指针指向顶。芯片一电 SP 指针还没有初始化,所以 C 语言没
    发表于 12-13 07:43

    GD32VF103多任务应用中的重用

    。RISC-V内核还提供了mscratchcswl 寄存器,用于在多个中断等级间切换时,交换目的寄存器与 mscratch的值来加速中断处理,将中断处理程序与应用程序任务的堆栈空间分离。对于SEGGER
    发表于 12-17 15:59

    什么是堆?什么是

    在嵌入式编程中,是一个很重要的概念,不管是裸机编程还是基于RTOS编程。函数形参、局部变量、函数调用现场的保护及返回地址、中断函数执行前线程保护及中断嵌套的现场的保护都依赖于空间。
    发表于 12-22 06:09

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

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

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

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

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

    接下来:call do_syscall_64,进入do_syscall_64函数: __visible void do_syscall_64( struct pt_regs *regs){ struct thread_info *ti = current_thread_info() ; unsigned long nr = regs- >orig_ax ; enter_from_user_mode() ; local_irq_enable() ; if ( READ_ONCE ( ti- >flags) /* * NB: Native and x32 syscalls are dispatched from the same * table. The only functional difference is the x32 bit in * regs- >orig_ax, which changes the behavior of some syscalls. */ if ( likely (( nr __SYSCALL_MASK]( regs- >di, regs- >si, regs- >dx, regs- >r10, regs- >r8, regs- >r9) ; } syscall_return_slowpath( regs ) ; } 上
    的头像 发表于 07-31 11:29 527次阅读