学习内容来自庖丁解牛,仅作为个人学习研究用途,如作者认为侵权请联系第一时间删除。

文章目录

  • 程序运行级别
  • int指令-中断隐指令
  • 触发系统调用及参数传递方式
  • 嵌入汇编修饰符
  • 系统调用在内核代码中的处理过程

程序运行级别

Intel x86 CPU 有 4 种不同的执行级别,分别是 0、1、2、3,数字越小,特权越高。

按照 Intel 的设想,操作系统内核运行在 Ring0 级别,驱动程序运行在 Ring1和Ring2级别,应用程序运行在 Ring3 级别,实际的操作系统(如 Linux、Windows)都没有用到图中的 4 级。

Linux 操作系统中只是采用了其中的 0 和 3 两个特权级别,分别对应内核态和用户态。

用户态和内核态很显著的区分方法就是 CS:EIP 的指向范围,在内核态时,CS:EIP的值可以是任意的地址,在 32 位的 x86 机器上有 4GB 的进程地址空间,内核态下的这 4GB的地址空间全都可以访问。但是在用户态时,只能访问 0x00000000~0xbfffffff 的地址空间,0xc0000000 以上的地址空间只能在内核态下访问。


int指令-中断隐指令

保存用户态 CS:EIP 的值,以及当前的堆栈段寄存器的栈顶,将 EFLAGS 寄存器的当前的值保存到内核堆栈里。
把当前的中断信号或者是系统调用的中断服务程序的入口加载到 CS:EIP 里,把当前的堆栈段 SS:ESP 也加载到 CPU 里完成后,当前 CPU 在执行下一条指令时就已经开始执行中断处理程序的入口了,这时对堆栈的操作已经是内核堆栈操作了。


触发系统调用及参数传递方式

使用 EAX 寄存器传递系统调用号。

普通函数调用是通过将参数压栈的方式传递的。系统调用从用户态切换到内核态,在两种执行模式下使用的是不同的堆栈,即进程的用户态和进程的内核态堆栈,传递参数方法无法通过参数压栈的方式,而是通过寄存器传递参数的方式。

除了 EAX 用于传递系统调用号外,参数按顺序赋值给 EBX、ECX、EDX、ESI、EDI、EBP,参数的个数不能超过 6个,即上述 6 个寄存器。如果超过 6 个就把某一个寄存器作为指针指向内存,这样就可以通过内存来传递更多的参数。


嵌入汇编修饰符


系统调用在内核代码中的处理过程

当执行 int 0x80 时,实际上 CPU 会自动跳转到system_call函数。


系统调用在内核源码的初始化

在start_kernel函数里调用的trap_init函数

asmlinkage __visible void __init start_kernel(void)
{...trap_init();    //初始化中断向量...
}

arch/x86/kernel/traps.cvoid __init trap_init(void)函数中有以下定义,其中调用了set_system_ trap_gate函数。

#ifdef CONFIG_X86_32set_system_trap_gate(SYSCALL_VECTOR, &system_call);set_bit(SYSCALL_VECTOR, used_vectors);
#endif

变量SYSCALL_VECTOR的定义在/linux-3.18.6/arch/x86/include/asm/irq_vectors.h

# define SYSCALL_VECTOR          0x80

可以看到通过 set_system_trap_gate函数绑定了中断向量 0x80 和 system_call 中断服务程序入口之后,一旦执行 int 0x80,CPU就直接跳转到 system_call 这个位置来执行。

3.6.18版本的内核源文件中system_call 的实现位于linux-3.18.6/arch/x86/kernel/entry_32.S的490行处,是汇编代码。gdb不能跟踪调试这段汇编代码,是因为这个“函数”内部没有严格遵守函数调用堆栈机制。


system_call的处理过程

需要注意int 0x80 和 system_call 是通过中断向量匹配起来的,而系统调用用户态接口和系统调用的内核处理函数是通过系统调用号匹配起来的。

system_call 这一段代码就是系统调用的处理过程,所有其他中断处理过程和这个 system_call 类似。比如中断过程中都有保护现场和恢复现场,这段代码里面一样也有保存现场 SAVE_ALL 和恢复现场restore_all 的过程。

经过简化后的system_call代码如下

ENTRY(system_call) RING0_INT_FRAME ASM_CLAC pushl_cfi %eax # 保存系统调用号SAVE_ALL # 保存现场,将用到的所有CPU寄存器保存到栈中GET_THREAD_INFO(%ebp) # ebp用于存放当前进程thread_info结构的地址testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp) jnz syscall_trace_entry cmpl $(nr_syscalls), %eax # 检查系统调用号(系统调用号应小于NR_syscalls)jae syscall_badsys # 不合法,跳入异常处理
syscall_call: call *sys_call_table(,%eax,4) # 通过系统调用号在系统调用表中找到相应的系统调用内核处理函数,比如sys_time movl %eax,PT_EAX(%esp) # 保存返回值到栈中
syscall_exit: testl $_TIF_ALLWORK_MASK, %ecx # 检查是否有任务需要处理jne syscall_exit_work # 需要,进入 syscall_exit_work,这里是最常见的进程调度时机
restore_all: TRACE_IRQS_IRET # 恢复现场
irq_return: INTERRUPT_RETURN # iret
ENDPROC(system_call)

在退出之前根据需要有一个 syscall_exit_work,如果没有,则恢复现场并 iret 返回用户态。值得注意的是,一旦进入 syscall_exit_work,里面就会有一个进程调度时机,这也是最常见的进程调度时机。

sys_call_table 分派表是由一段脚本根据 linux-3.18.6/ arch/x86/syscalls/syscall_32.tbl 来自动生成的,所以无法直接找到 sys_call_table 分派表初始化代码。


系统调用的内核处理过程总结

当一个系统调用发生时,进入内核处理这个系统调用,系统调用的内核服务程序在服务结束返回到用户态之前,可能会发生进程调度。在进程调度中会发生进程上下文的切换,这是一个连贯的过程。

从系统调用处理过程的入口开始,可以看到 SAVE_ALL 保存现场,然后找到 syscall_call 和 sys_call_table。

call *sys_call_table(,%eax,4)就是调用了系统调用的内核处理函数,之后 restore_all 和最后有一个 INTERRUPT_RETURN(iret)用于恢复现场并返回系统调用到用户态结束。

在这个过程当中可能会执行 syscall_exit_work,里面有work_pending,其中的 work_notifysig 是处理信号的。work_pending 里还有可能调用schedule,这是一个非常关键的部分,它是进程切换的代码。



从这里可以看出,int中断隐指令和执行了system_call中的SAVE_ALL并不是一回事。并且SAVE_ALL的寄存器压栈操作中的寄存器和刚才int中断隐指令中保存的寄存器的值没有关系,并且压的是不同的栈。(一个压在用户态栈空间,另一个压在内核态栈空间)。

[linux内核] 3.系统调用处理过程相关推荐

  1. linux 内核裁剪的具体过程和方法,Linux内核裁剪的具体过程和方法

    Linux内核裁剪的具体过程和方法 这是我前段时间自己整的一份,内核功能: 能够完成系统的基本功能,上网,收发邮件等,支持xwindows图形界面. 在menuconfig中配置: 详细介绍内核配置选 ...

  2. linux内核编译系统调用,linux编译内核及添加系统调用

    我们都知道系统功能调用是Unix/Linux操作系统向用户程序提供支持的接口,通过这些接口应用程序向操作系统请求服务,控制转向操作系统,而操作系统在完成服务后,将控制和结果返回给用户程序. 系统调用的 ...

  3. 第四十二期-ARM Linux内核的系统调用(2)

    作者:罗宇哲,中国科学院软件研究所智能软件研究中心 上一期中我们介绍了ARM Linux内核中的系统调用和定义系统调用的流程,这一期我们将介绍系统调用的执行过程. 一.ARM Linux内核中系统调用 ...

  4. linux内核添加系统调用(详细)

    linux内核添加系统调用(详细) 说在前面: 这是我第五次编译内核,分别踩了很多坑.中途问过wz佬,佬让我用qemu.我还是最后换ubuntu虚拟机跑了.现在已经有点emo了. 这篇博客是我第五次的 ...

  5. Linux内核Hook系统调用execve

    资源下载地址:linux内核hook系统调用execve函数-Linux文档类资源-CSDN下载 (已在内核为 4.19.0-amd64-desktop版本uos编译通过,并成功达到目的) 在Linu ...

  6. 使用NDB调试Linux内核的线程切换过程

    使用NDB调试Linux内核的线程切换过程 cpu_switch_to 因为要调试Linux内核的线程切换过程,所以需要在`cpu_switch_to`处设置断点. 注意:ARM无法设置软件断点,只能 ...

  7. Linux中断与进程切换,结合中断上下文切换和进程上下文切换分析Linux内核的一般执行过程...

    @ 实验环境 OS Linux cj-virtual-machine 5.3.0-51-generic 虚拟机 QEMU 内核版本 5.3.4 调式方法 GDB PS:调试环境安装请看上一篇博客汇编级 ...

  8. Linux内核有没有rootfs,Linux内核rootfs的初始化过程

    由于在下水平相当有限,不当之处,还望大家批评指正^_^ 在Linux  shell中执行mount命令,通常可以看到某个做了文件系统的磁盘分区或flash分区或内存文件系统做为所谓的根文件系统被mou ...

  9. 第四十一期-ARM Linux内核的系统调用(1)

    作者:罗宇哲,中国科学院软件研究所智能软件研究中心 上一期中我们介绍了工作队列相关的关键函数,这一期我们将介绍ARM Linux内核中的系统调用. 一.ARM Linux内核中的系统调用 在ARM L ...

  10. 如何来实现一个Linux内核的系统调用(基于tiny4412开发板)

    关于系统调用,相信学习过操作系统的同学应该都不陌生. 那么,什么是系统调用? 百度的权威解释如下: 点击打开链接 由操作系统实现提供的所有系统调用所构成的集合即程序接口或应用编程接口(Applicat ...

最新文章

  1. sizeof 是关键字不是函数!使用sizeof需要注意?
  2. 备份全网服务器数据生产架构方案案例模型
  3. python基础之循环与迭代器
  4. rand()函数100000随机数_利用随机函数Rand、Randbetween制作抽奖器应用技巧解读
  5. RxSwift之UI控件UIActivityIndicatorView与UIApplication扩展的使用
  6. python echo服务器_python常用框架 echo server 的测试
  7. 数据结构与算法--数组:二维数组中查找
  8. ubuntu下搭建nfs服务器
  9. 迁移TFS 2012服务至新的电脑硬件
  10. json里面的list数据取不出来_[工具]用kaggle API下载数据集
  11. python知识点总结
  12. r510服务器开机无显示,联智通达工业主板常见问题之工控电脑开机无显示
  13. eclipse 完全智能提示
  14. 返回结果乱码_Spring请求参数和响应结果全局加密和解密(1)
  15. Windows Phone 8.1 多媒体(2):视频
  16. 【C++】对象作为函数参数【原创技术】
  17. 服务端技术进阶(三)从架构到监控报警,支付系统的设计如何步步为营
  18. 对冲基金小镇 鬼城_未来系统,代码寿命和网络鬼城
  19. 如何用matlab求出矩阵简化阶梯形顺带算出主元所在的列
  20. 信息系统集成有以下几个显著特点

热门文章

  1. 用树莓派打拳皇游戏(运行 SWF 游戏文件)【Adobe Flash Player + Chromium】
  2. 青少年学习python有什么用_青少年为什么要学习Python
  3. 第三届CCF计算机职业资格认证考试题解(C++)
  4. 企业内IT部/信息部发展阶段和趋势(第一阶段)
  5. 伯努利分布、二项分布、多项分布、Beta分布、Dirichlet分布、连续分布(正态分布)、大数定理、中心极限定理、贝叶斯理论
  6. 如何将标准地图服务中的eps格式中国地图应用到论文中带审图号的地图制作?(二)
  7. JAVAWEB-NOTE03
  8. android4k分辨率,4k手机有哪些 4k分辨率是多少【图文】
  9. 常用的网络进行广告推广的落实措施都有哪些渠道呢
  10. mysql 游戏背包_01背包问题(完全背包,部分背包)golang实现