arm平台下系统调用的一点疑问。
本文是原「蜗窝讨论区」的历史存档(2017-02-11),来自版块「Linux kernel技术问答」,共 8 帖。讨论区已停止服务,此处仅供查阅。
在学习arm平台下系统调用时遇见的疑问想不明白
大致理解是 通过swi指令触发软中断,从而cpu强制切换了模式(硬件行为),然后进入到软中断的中断处理函数,然后根据传递下来的参数和对应的系统调用号去找到相应的系统处理函数(这个处理函数的执行我的理解为在软中断的中断处理上下文中,但是该系统调用链下肯定会有很多引发调度的代码,这个不是和中断处理中不准调用引起调度的函数相矛盾吗?? ),执行完后在恢复堆栈返回用户态执行swi的下一条指令继续运行。 在网上找答案是看见一个帖子跟我疑惑差不多,也一同贴出来大家一起分析讨论下,被这个这个问题困惑了很久。
以下是我网上找的另一个人的帖子 想知道linux系统调用发生在哪个内核线程的上下文中。另外,针对kernel module的read和write操作发生在什么上下文中?当前的理解是: 以ARM为例,系统调用执行了swi指令,使得处理器进入SVC模式下的软中断处理函数中。现在的疑问是后续的处理函数仍然在中断处理函数中执行吗?还是调用tasklet后放到ksoftirqd中运行呢?个人感觉应该是后者,否则会导致中断处理长时间占用处理器,造成其他内核服务无法被调度。
你需要区分ARM SWI和linux内核中的soft irq,这两个术语是不一样的“软中断”。
感谢对问题的回答 softirq是真正运行在中断上下文 asm_do_IRQ( { ....... irq_exit(//执行软中断。如tasklet等等 }
假如分析从用户态利用svc进入触发该异常的方式
ENTRY(vector_swi)
/****将中断发生前的寄存器状态备份入栈***/
1: sub sp, sp, #S_FRAME_SIZE /在栈上开辟184的空间备份跳转之前的寄存器值/
2: stmia sp, {r0 - r12} / Calling r0 - r12 备份公用寄存器r0-r12/
3: ARM( add r8, sp, #S_PC /r8指向栈空间中应该存pc的位置/
4: ARM( stmdb r8, {sp, lr}^ / Calling sp, lr/
(r0-r12)和用户态的(r0-r12)是共享的,这也可以解释为什么进入svc后可以通过rxx寄存器 来传递参数。但是此时sp和用户sp是不一样的,理解成sp切换成了另外一个值。就叫做内核态的sp值吧。
关于这个sp值就是我最难理解的地方,此时这个sp我认为只是由于强制进入svc模式下的某个值,而并不能说明就是当前进程的内核态的sp,既然对于每个进程来说都有用户态和内核态的栈空间,那为什么此时sp就一定是当前进程的sp呢,我自己认为如果此时有个(sp= current->sp(仅仅表达这个意思,真正进程保它的内核态sp的值可能是更负责的实现,仅仅是想说要把sp切换成当前进程内核态的sp的值,这样后续的操作才能是在这个进程内核态的上下文中运行) 这种代码来显示下,我就能理解sp就是明确指向当前进程内核态的sp,这样后续的操作确实就在当前任务的内核态上下文,也能理一系列调用链的的合理性)
也许是因为SWI(Software Interrupt)容易和kernel中的soft irq混淆,在ARMv7之后,SWI指令修改为SVC指令(用于用户程序请求系统服务)。执行了SVC(Supervisor Call)指令后,CPU进入supervisor mode。 直接写sp会比较混乱,我倾向给它加一个后缀,例如sp_usr,sp_svc。你给出的vector_swi的代码其实就是把用户空间的现场保存在了该进程的内核栈上(由于cpu处于svc mode,因此代码中的sp就是sp_svc)。当然,你的核心问题是这时候的sp_svc为何就是当前进程的sp_svc呢?这需要看进程切换的代码和fork系统调用的代码,大概的sp_svc赋值情况如下: 1、在fork的时候,会创建进程的内核栈和thread info数据结构(union thread_union),并设定该进程的thread info的cpu_context(其中包括sp_svc) 2、在调度器调度该进程执行的时候,会从该进程的thread info的cpu_context中恢复硬件现场,也就是设定了sp_svc
再次感谢您的回答
我个人理解从您刚才的回答中还是我还是没能很好的理解为什么此时的sp_svc = curret->sp_svc
对于您给出的解释我是这样理解的,关于进程的创建和调度 进程1在被创建在fork的时候会 产生 struct_task1->hreadinfo->usr_sp1和struct_task1->hread info->kernel_sp1 进程2在被创建在fork的时候会 产生 struct_task2->hreadinfo->usr_sp2和struct_task2->hread info->kernel_sp2
在每个进程第一次创建的,(这里就以task1和task2来表示进程1和进程2,假设系统中此时就只有创建的2个进程,方便后续举例说明),在被创建后而没有被调度前,创建时应该会模拟一次被调度的压栈行为,即把hread info->kernel_sp1和hread info->kernel_sp2指向的内存空间模拟一次被调度的压栈的假象,同时把压栈后sp_kervel(内核态下sp寄存器的值)的值在赋值给struct_tasknumxx->hread info->kernel_spnumxx方便他们第一次被调度好弹栈。(struct_tasknumxx->hread info->kernel_spnumxx表示进程号为num的kerelsp的值,即指向该进程内核态栈底的地址)
这里以task1和task2相互调度来说明,假设此时task1在运行,然后遇见要sched了,此时就把当前task1的上下文(rxx,lr,pc等)保存在进程1的内核栈上,我认为的过程是先取出struct_task1->hread info->kernel_sp1赋值给sp_kervel(内核态下sp寄存器的值),然后压栈到sp_kervel(内核态下sp寄存器的值)上,随后把由于压栈更新后sp_kervel(内核态下sp寄存器的值)的值赋值给struct_task1->hread info->kernel_sp1这样就记录了task1进程的栈底值,这样每个进程就能通过 struct_tasknumxx->hread info->kernel_spxx 能找到该进程内核态的栈底,此时假如task2已经是ready状态,就可以拿到struct_task2->hread info->kernel_sp2的置赋值给sp_kervel(内核态下sp寄存器的值).然后把sp_kervel(内核态下sp寄存器的值)再弹栈,这样就可以接着在task2被调度出的断点运行。其他进程调度以此类推。所以我认为不管怎样 都是有一个从struct_tasknumxx->hread info->kernel_spnumxx赋值给sp_kervel(内核态下sp寄存器的值)的过程。
但是我的疑问是系统调svc指令只是是陷入了内核态,没有改变进入前current的值,但是此时sp_kervel不知道是个什么值,不知道它指向哪个空间。并没有看见如任务切换时把struct_tasknumxx->hread info->kernel_spnumxx 赋sp_kervel(内核态下sp寄存器的值)的过程来保证sp_kervel指向当前进程内核态的栈底。确实是真的没搞懂此时sp_kervel是指向了那个空间,我个人理解只是指向了内核空间而已。
你的描述中有一些错误的理解,请稍后,我会写一篇上下文相关的文档来解决你的疑问,请关注近期博客文章的更新。
期待拜读
进程1在被创建在fork的时候会 产生 struct_task1->hreadinfo->usr_sp1和struct_task1->hread info->kernel_sp1 进程2在被创建在fork的时候会 产生 struct_task2->hreadinfo->usr_sp2和struct_task2->hread info->kernel_sp2
这里表述错误,进程对应的用户栈(sp_usr)保存在内核栈上(struct pt_regs),没有tsk->thread_info->usr_sp这样的东西
所以我认为不管怎样 都是有一个从struct_tasknumxx->hread info->kernel_spnumxx赋值给sp_kervel(内核态下sp寄存器的值)的过程。
同意,这个过程发生在进程切换的过程中,具体可以参考__switch_to函数
但是我的疑问是系统调svc指令只是是陷入了内核态,没有改变进入前current的值,但是此时sp_kervel不知道是个什么值,不知道它指向哪个空间。
在进程切换的时候,在/process_management/context-switch-arch.html文章的最后有分析ARM64中的进程上下文切换的过程,你可以看看是如何给sp_svc赋值的。
