https://articles.manugarg.com/systemcallinlinux2_6.html

目录

1. 什么是系统调用?

2. 系统调用中发生了什么?

3. 很好的老方法

4. 新的闪亮方式

4.1. SYSENTER/SYSEXIT 指令:

4.2. linux 2.6 如何使用这些指令?

5. 一些代码

6. 参考文献


从 2.5 版开始,linux 内核在 Pentium II+ 处理器上引入了新的系统调用入口机制。由于采用现有软件中断方法的 Pentium IV 处理器存在性能问题,因此使用 Pentium II+ 处理器上可用的 SYSENTER/SYSEXIT 指令实现了替代系统调用入口机制。本文探讨了这种新机制。讨论仅限于 x86 架构,所有源代码列表均基于 linux 内核 2.6.15.6。

1. 什么是系统调用?

系统调用为用户态进程提供了一种从内核请求服务的方法。什么样的服务?由操作系统管理的服务,如存储、内存、网络、进程管理等。例如,如果用户进程要读取文件,则必须进行“打开”和“读取”系统调用。通常系统调用不会被进程直接调用。C 库为所有系统调用提供了一个接口。

2. 系统调用中发生了什么?

内核代码片段根据用户进程的请求运行。这段代码运行在 ring 0(当前权限级别为 -CPL-0),这是 x86 架构中的最高权限级别。所有用户进程都在环 3 (CPL 3) 中运行。因此,要实现系统调用机制,我们需要的是 1) 一种从环 3 调用环 0 代码的方法和 2) 一些内核代码来为请求提供服务。

3. 很好的老方法

直到很久以前,Linux 曾经使用软件中断在所有 x86 平台上实现系统调用。要执行系统调用,用户进程会将所需的系统调用号复制到 %eax 并执行“int 0x80”。这将产生中断 0x80 并调用中断服务程序。对于中断 0x80,该例程是“所有系统调用处理”例程。该例程将在 ring 0 中执行。该例程,如文件中所定义/usr/src/linux/arch/i386/kernel/entry.S,将保存当前状态并根据 %eax 中的值调用适当的系统调用处理程序。

4. 新的闪亮方式

它被发现 ,这种软件中断方法是在奔腾IV处理器要慢得多。为了解决这个问题,Linus 实现了一种替代系统调用机制,以利用所有 Pentium II+ 处理器提供的 SYSENTER/SYSEXIT 指令。在进一步采用这种新方法之前,让我们先熟悉一下这些说明。

4.1. SYSENTER/SYSEXIT 指令:

让我们看看授权来源,英特尔手册本身。英特尔手册说:

SYSENTER 指令是 Pentium® II 处理器上引入的“快速系统调用”工具的一部分。SYSENTER 指令经过优化,可为转换到保护环 0 (CPL = 0) 提供最大性能。SYSENTER 指令根据操作系统在某些特定型号的寄存器中指定的值设置以下寄存器。

  • CS 寄存器设置为 (SYSENTER_CS_MSR) 的值

  • EIP 寄存器设置为 (SYSENTER_EIP_MSR) 的值

  • SS 寄存器设置为(8 加上 SYSENTER_CS_MSR 中的值)的总和

  • ESP 寄存器设置为 (SYSENTER_ESP_MSR) 的值

看起来处理器正试图帮助我们。让我们也很快地看看 SYSEXIT:

SYSEXIT 指令是 Pentium® II 处理器上引入的“快速系统调用”工具的一部分。SYSEXIT 指令经过优化,可为从保护环 0 (CPL = 0) 转换到保护环 3 (CPL = 3) 提供最大性能。SYSEXIT 指令根据操作系统在特定模型或通用寄存器中指定的值设置以下寄存器。

  • CS 寄存器设置为(16 加上 SYSENTER_CS_MSR 中的值)的总和

  • EIP 寄存器设置为包含在 EDX 寄存器中的值

  • SS 寄存器设置为(24 加上 SYSENTER_CS_MSR 中的值)的总和

  • ESP 寄存器设置为 ECX 寄存器中包含的值

SYSENTER_CS_MSR、SYSENTER_ESP_MSR 和 SYSENTER_EIP_MSR 并不是真正的寄存器名称。Intel 只是将这些寄存器的地址定义为:

SYSENTER_CS_MSR 174h
SYSENTER_ESP_MSR 175h
SYSENTER_EIP_MSR 176h

在 linux 中,这些寄存器被命名为:

/usr/src/linux/include/asm/msr.h:101 #define MSR_IA32_SYSENTER_CS 0x174102 #define MSR_IA32_SYSENTER_ESP 0x175103 #define MSR_IA32_SYSENTER_EIP 0x176

4.2. linux 2.6 如何使用这些指令?

  1. Linux 在初始化期间设置这些寄存器。

    /usr/src/linux/arch/i386/kernel/sysenter.c:36 wmsr(MSR_IA32_SYSENTER_CS,__KERNEL_CS,0);37 wmsr(MSR_IA32_SYSENTER_ESP,tss->esp1,0);38 wmsr(MSR_IA32_SYSENTER_EIP, (unsigned long) sysenter_entry, 0);

    请注意,“tss”指的是任务状态段 (TSS),因此 tss->esp1 指向内核模式堆栈。[4]将TSS在linux中的使用解释为:

    x86 体系结构包括称为任务状态段 (TSS) 的特定段类型,用于存储硬件上下文。尽管 Linux 不使用硬件上下文切换,但它仍然被迫为系统中的每个不同 CPU 设置 TSS。这样做有两个主要原因:

    - 当 80 x 86 CPU 从用户模式切换到内核模式时,它会从 TSS 中获取内核模式堆栈的地址。

    - 当用户态进程试图通过 in 或 out 指令访问 I/O 端口时,CPU 可能需要访问存储在 TSS 中的 I/O 权限位图来验证是否允许进程访问该端口.

    所以在初始化内核时设置这些寄存器,这样在 SYSENTER 指令之后,ESP 被设置为内核模式堆栈,EIP 被设置为 sysenter_entry。

  2. 内核还为用户进程设置系统调用入口/出口点。内核在内存中创建单个页面,并在所有进程加载到内存时将其附加到所有进程的地址空间。该页面包含系统调用进入/退出机制的实际实现。这个页面的定义可以在文件中找到/usr/src/linux/arch/i386/kernel/vsyscall-sysenter.S。内核调用这个页面虚拟动态共享对象(vdso)。可以通过查看以下内容来确认此页面的存在:cat /proc/`pid`/maps

slax ~ # cat /proc/self/maps
08048000-0804c000 r-xp 00000000 07:00 13         /bin/cat
0804c000-0804d000 rwxp 00003000 07:00 13         /bin/cat
0804d000-0806e000 rwxp 0804d000 00:00 0          [heap]
b7ea0000-b7ea1000 rwxp b7ea0000 00:00 0
b7ea1000-b7fca000 r-xp 00000000 07:03 1840       /lib/tls/libc-2.3.6.so
b7fca000-b7fcb000 r-xp 00128000 07:03 1840       /lib/tls/libc-2.3.6.so
b7fcb000-b7fce000 rwxp 00129000 07:03 1840       /lib/tls/libc-2.3.6.so
b7fce000-b7fd1000 rwxp b7fce000 00:00 0
b7fe7000-b7ffd000 r-xp 00000000 07:03 1730       /lib/ld-2.3.6.so
b7ffd000-b7fff000 rwxp 00015000 07:03 1730       /lib/ld-2.3.6.so
bffe7000-bfffd000 rwxp bffe7000 00:00 0          [stack]
ffffe000-fffff000 ---p 00000000 00:00 0          [vdso]

对于使用共享库的二进制文件,也可以使用 ldd 来查看此页面:

slax ~ # ldd /bin/lslinux-gate.so.1 => (0xffffe000)librt.so.1 => /lib/tls/librt.so.1 (0xb7f5f000)...

观察 linux-gate.so.1。这不是物理文件。可以看到这个vdso的内容如下:

==> dd if=/proc/self/mem of=linux-gate.dso bs=4096 skip=1048574 count=1
1+0 records in
1+0 records out==> objdump -d --start-address=0xffffe400 --stop-address=0xffffe414 linux-gate.dso
ffffe400 <__kernel_vsyscall>:
ffffe400:       51                      push   %ecx
ffffe401:       52                      push   %edx
ffffe402:       55                      push   %ebp
ffffe403:       89 e5                   mov    %esp,%ebp
ffffe405:       0f 34                   sysenter
...
ffffe40d:       90                      nop
ffffe40e:       eb f3                   jmp    ffffe403 <__kernel_vsyscall+0x3>
ffffe410:       5d                      pop    %ebp
ffffe411:       5a                      pop    %edx
ffffe412:       59                      pop    %ecx
ffffe413:       c3                      ret 
  1. 在所有列表中,... 代表省略的不相关代码。

  2. 启动:用户态 进程(或代表它们的 C 库)调用 __kernel_vsyscall 来执行系统调用。__kernel_vsyscall 的地址不固定。内核使用 AT_SYSINFO elf 参数将此地址传递给用户态进程。AT_elf 参数,又名 elf 辅助向量,在启动时与进程参数和环境变量一起加载到进程堆栈中。有关 Elf 辅助向量的更多信息,请参见 [1]。

    移动到该地址后,寄存器 %ecx、%edx 和 %ebp 将保存在用户堆栈中,并且在执行 sysenter 之前将 %esp 复制到 %ebp。这个 %ebp 后来帮助内核恢复用户态堆栈。执行 sysenter 指令后,处理器在sysenter_entry处开始执行。sysenter_entry 定义/usr/src/linux/arch/i386/kernel/entry.S为:(见我在 [ ] 中的评论)

   179 ENTRY(sysenter_entry)180         movl TSS_sysenter_esp0(%esp),%esp181 sysenter_past_esp:182         sti183         pushl $(__USER_DS)184         pushl %ebp           [%ebp contains userland %esp]185         pushfl186         pushl $(__USER_CS)187         pushl $SYSENTER_RETURN     [%userland return addr]188....201         pushl %eax            202         SAVE_ALL            [pushes registers on to stack]203         GET_THREAD_INFO(%ebp)204205         /* Note, _TIF_SECCOMP is bit number 8, and so it needs testw and not testb */206         testw $(_TIF_SYSCALL_EMU|_TIF_SYSCALL_TRACE|_TIF_SECCOMP|_TIF_SYSCALL_AUDIT),TI_flags(%ebp)207         jnz syscall_trace_entry208         cmpl $(nr_syscalls), %eax209         jae syscall_badsys210         call *sys_call_table(,%eax,4)211         movl %eax,EAX(%esp)......
  1. 在 sysenter_entry 中:在第 183 行和第 202 行之间,内核通过将寄存器值压入堆栈来保存当前状态。

    观察 $SYSENTER_RETURN 是内部定义的用户态返回地址/usr/src/linux/arch/i386/kernel/vsyscall-sysenter.S,%ebp 包含用户态 ESP,因为 %esp 在调用 sysenter 之前被复制到 %ebp。

  2. 保存状态后,内核验证存储在 %eax 中的系统调用号。最后使用指令调用适当的系统调用:

        210 call *sys_call_table(,%eax,4)

    这与旧方式非常相似。

  3. 系统调用完成后,处理器在第 211 行恢复执行。进一步查看 sysenter_entry 定义:

    210         call *sys_call_table(,%eax,4)211         movl %eax,EAX(%esp)212         cli213         movl TI_flags(%ebp), %ecx214         testw $_TIF_ALLWORK_MASK, %cx215         jne syscall_exit_work216 /* if something modifies registers it must also disable sysexit */217         movl EIP(%esp), %edx         (EIP is 0x28)218         movl OLDESP(%esp), %ecx            (OLD ESP is 0x34)219         xorl %ebp,%ebp220         sti221         sysexit
  1. 将 %eax 中的值复制到堆栈。Userland ESP 和返回地址(to-be EIP)分别从内核堆栈复制到 %edx 和 %ecx。观察用户态返回地址,$SYSENTER_RETURN 在第 187 行被压入堆栈。之后 0x28 字节被压入堆栈。这就是 0x28(%esp) 指向 $SYSENTER_RETURN 的原因。

  2. 之后执行 SYSEXIT 指令。正如我们在上一节中所知道的,sysexit 将 %edx 中的值复制到 EIP,将 %ecx 中的值复制到 ESP。sysexit 将处理器转移回环 3,处理器在用户态恢复执行。

5. 一些代码

#include <stdio.h>int pid;int main() {__asm__("movl $20, %eax    \n""call *%gs:0x10    \n"   /* offset 0x10 is not fixed across the systems */"movl %eax, pid    \n");printf("pid is %d\n", pid);return 0;
}

这使用 __kernel_vsyscall 而不是 int 0x80 执行 getpid() 系统调用(__NR_getpid 为 20)。为什么是 %gs:0x10?解析进程堆栈以找出 AT_SYSINFO 的值可能是一项繁琐的任务。因此,当加载 libc.so(C 库)时,它会将 AT_SYSINFO 的值从进程堆栈复制到 TCB(线程控制块)。段寄存器 %gs 指的是 TCB。

请注意,偏移量 0x10 在整个系统中不是固定的。我使用 GDB 为我的系统找到了它。[1] 中给出了一种与系统无关的方式来找出 AT_SYSINFO。

注意:这个例子取自http://www.win.tue.nl/~aeb/linux/lk/lk-4.html稍作修改后使其在我的系统上工作。

6. 参考文献

以下是一些帮助我理解这一点的参考资料。

  1. 关于 Elf 辅助向量作者:Manu Garg

  2. 什么是 linux-gate.so.1?作者:约翰·彼得森

  3. 这个 Linux 内核: Andries Brouwer 的系统调用

  4. 了解 Linux 内核,作者:Daniel P. Bovet、Marco Cesati

  5. Linux内核源代码

Linux 2.6中基于Sysenter的系统调用机制相关推荐

  1. linux内核3.4基于wakeup_source的autosleep机制分析

    点击打开链接 一:wakeup_source简介: linux 3.4内核PM使用了wakeup_source来保持唤醒状态,也就是keep awake.之前android一直是基于Linux加入了w ...

  2. linux进程看门狗使用方式,Linux系统中基于看门狗的精细化进程监控方法及系统的制作方法...

    Linux系统中基于看门狗的精细化进程监控方法及系统的制作方法 [技术领域] [0001] 本发明涉及Linux系统的进程监控技术领域,特别是涉及一种Linux系统中基于看 门狗的精细化进程监控方法及 ...

  3. 查看依赖树_如何在基于 Ubuntu 或 Debian 的 Linux 发行版中查看一个软件包的依赖...

    但如果你想在安装一个软件包之前或之后知晓这个软件包的依赖,那该怎么办呢? 来源:https://linux.cn/article-12987-1.html 作者:Abhishek Prakash 译者 ...

  4. linux 添加新的系统调用,如何在Linux中添加新的系统调用

    如何在Linux中添加新的系统调用 2010-01-29 eNet&Ciweek #define __NR_mycall 191 系统调用号为191,之所以系统调用号是191,是因为Linux ...

  5. Linux(Centos7.8)中conda虚拟环境搭建LSTM神经网络基于django3.1.2的api接口

    目录 1.准备工作 2.项目需求 2.1 根据需求下载LSTM依赖包 2.2 代码实现 3.启动服务 4.第三方应用调用webapi服务提供的api接口 1.准备工作 由上一博客Linux(Cento ...

  6. 操原上机(一) 在Linux系统中增加新的系统调用

    在LINUX中增加新的系统调用 编写新的系统调用函数(指函数实现部分) 注册新的系统调用(声明系统调用函数和编号) 编译新LINUX内核 编译和安装模块 启动新的LINUX内核 编写应用程序测试新的系 ...

  7. debian php-fpn_如何在基于 Ubuntu 或 Debian 的 Linux 发行版中查看一个软件包的依赖...

    在 Ubuntu 或 Debian 中通过命令行来安装应用是一件很简单的事,你只需要执行 apt install package_name 就可以了. 但如果你想在安装一个软件包之前或之后知晓这个软件 ...

  8. Linux 0.11内核分析03:系统调用

    目录 1 概述 1.1 什么是系统调用 1.2 为什么需要系统调用 2 系统调用基础设施 2.1 安装系统门 2.1.1 中断描述符 2.1.2 中断描述符安装函数 2.1.3 安装0x80系统门 2 ...

  9. linux性能监控工具perf,Linux性能分析中常用的工具perf介绍

    今天小编要跟大家分享的文章是关于Linux性能分析中常用的工具perf介绍.系统级性能优化通常包括两个阶段:性能剖析(performance profiling)和代码优化.性能剖析的目标是寻找性能瓶 ...

最新文章

  1. sql 树状结构中知道 父节点与孙节点_sqlserver树状结构表中,获取指定节点的所有父节点路径_MySQL...
  2. delphi7升级delphi2007可以互用马_奶爸带娃玩“升级版摇摇马”火了,像极了传说中的“甘为孺子牛”...
  3. Redis入门到精通只需要三篇博客
  4. 数据库开发——MySQL——数据的增删改查
  5. 大数据项目实战数仓4——常用脚本
  6. 什么是JBPM工作流
  7. Protel 99SE安装向导
  8. WPS制作甘特图实操(带图超详细)
  9. 欧氏空间距离和内积_欧氏空间的内积与线性变换
  10. 数据库在软件开发中的作用是什么?
  11. 使用css3制作正六面体
  12. 关闭英文拼写检查,关闭xml验证
  13. linux一分钟关机命令,Linux关机命令集合
  14. 中科大统计学习(刘东)作业1
  15. 五大领域总目标指南_五大领域总目标和各年龄段目标 -
  16. 如何安装husky_Husky robot 玩耍1
  17. php 防止爬虫,服务器反爬虫攻略:Apache/Nginx/PHP禁止某些User Agent抓取网站
  18. 前端开发:keep-alive的使用详解
  19. jQuery中.off(),on()详解集合
  20. 20190308搜索考试(水货)

热门文章

  1. javaScript调用函数失败
  2. StringUtils.isAlphanumeric(String)方法检查中文是通过的,需要注意。它不能用来检测字符串是否只包含英文和数字。
  3. MAX31855 热电偶至数字输出转换器
  4. Gateway与后端系统连接详细配置
  5. Ubuntu下安装setuptools
  6. Eclipse的vim插件viPlugin的安装
  7. srpm包的编译方式
  8. dotfuscator初步
  9. Google Test(Primer)(三)——断言
  10. C语言decompose函数,R语言使用decompose函数进行时间序列的波动趋势分解