加入收藏 | 设为首页 | 会员中心 | 我要投稿 应用网_丽江站长网 (http://www.0888zz.com/)- 科技、建站、数据工具、云上网络、机器学习!
当前位置: 首页 > 服务器 > 搭建环境 > Windows > 正文

攻克Linux系统编程,细说系统调用规范,入行要先熟悉套路

发布时间:2019-03-17 16:47:46 所属栏目:Windows 来源:佚名
导读:副标题#e# 本文主要带大家深入研究 Linux 系统编程。系统编程的任务,可以定义为使用系统提供的功能解决我们面对的实际问题,而系统调用,则是系统开放给应用执行特定功能的接口。本文首先从 Linux 系统调用讲起,主要包括以下内容: 系统调用概述 系统调用
副标题[/!--empirenews.page--]

本文主要带大家深入研究 Linux 系统编程。系统编程的任务,可以定义为使用系统提供的功能解决我们面对的实际问题,而系统调用,则是系统开放给应用执行特定功能的接口。本文首先从 Linux 系统调用讲起,主要包括以下内容:

  • 系统调用概述
  • 系统调用的两种调用方式
  • 系统调用的两种执行过程
  • 系统调用的标准使用方法

另外,还会扩展两个知识点:

  • 与早期 Linux 相比,2.6 以后版本的内核,是如何实现更高效的系统调用的?
  • 全局 errno 是如何解决多线程冲突问题的?
  • 攻克Linux系统编程,细说系统调用规范,入行要先熟悉套路

1.1 系统调用概述

系统调用是操作系统内核提供给应用程序的基础接口,需要运行在操作系统的核心模式下,以确保有权限执行某些 CPU 特权指令。

Linux 系统提供了功能非常丰富的系统调用,涵盖了文件操作、进程控制、内存管理、网络管理、套接字操作、用户管理、进程间通信等各个方面。

执行如下命令,可列出系统中所有的系统调用名称。

  1. man syscalls 

Linux 自带的 man 手册对每个系统调用都进行了非常详细的说明,包括函数功能、传入的参数、返回值,以及可能产生的错误、使用注意事项,等等,其完善程度丝毫不亚于微软的 MSDN。虽然是英文版,但读起来比较通俗易懂,每位 Linux 系统开发者都应该习惯于查看这些文档。

另外,IBM 文档库里有一份质量非常高的《中文版系统调用列表》,阅读它会更轻松。

1.2 系统调用的两种调用方式

我们先看第一种方式。

系统调用由指派的编号来标识,通过 syscall 函数以编号为参数可直接被调用。

syscall 函数原型为:

  1. int syscall(int number, ...); 

完整的系统调用编号都定义在 sys/syscall.h 文件中。感兴趣的读者可以自行查看。

显然,记忆如此多的编号,对开发者很不友好。

于是,开发者多会选择第二种方式,即利用 glibc 提供的包装函数将这些系统调用包装成名字自解释的函数。

这个过程,包装函数并没有做太多额外工作,主要是检查参数,将它们拷贝到合适的寄存器中,接着调用指定标号的系统调用,之后再根据结果设置 errno,供应用程序检查执行结果,以及其他相关工作。

两种调用方式,在功能上可以认为是完全等价的,但在易读、易用性上,glibc 包装函数则更有优势。在之后的课程中,我提到某系统调用,若无特殊说明,指的便是 glibc 包装函数。

当然,如果包装函数无法满足某些特殊应用场景需求,还可以使用 syscall 函数直接执行系统调用。不过这种情况非常少见,到目前为止,我还没有遇到过。

1.3 系统调用的两种执行过程

1.3.1 基于中断方式

系统调用的实现代码是内核代码的一部分。执行系统调用代码,首先需要将系统从用户模式切换到核心模式。

早期的系统调用通过软中断实现模式的切换,而中断号属于系统稀缺资源,不可能为每个系统调用都分配一个中断号。

在 Linux 的实现中,所有的系统调用共用 128 号中断(也就是大名鼎鼎的 int 0x80 ),其对应的中断处理程序是 system_call,所有的系统调用都会转到这个中断处理程序中。

接着,system_call 会根据 EAX 传入的系统调用标号跳转并执行相应的系统调用程序。如果需要更多的参数,会依次用 EBX、ECX、EDX、EDI 进行传递。函数执行完成之后,会把结果放到 EAX 中返回给应用程序。

由此可知,一次系统调用便会触发一次完整的中断处理过程。在每次中断处理过程中,CPU 都会从系统启动时初始化好的中断描述表中,取出该中断对应的门描述符,并判断门描述符的种类。

在确认门描述符的级别(DPL)不比中断指令调用者的级别(CPL)低之后,再根据描述符的内容,将中断处理程序中可能用到的寄存器进行压栈保存。最后执行权限提升,设置 CS 和 EIP 寄存器,以使 CPU 跳转到指定的系统调用的代码地址,并执行目标系统调用。

1.3.2 基于 SYSENTER 指令

再仔细审视基于中断方式的系统调用的执行过程,不难发现,前面很多处理过程都是固定的,其实很没必要,如门描述符级别检查、查找中断处理程序入口,等等。

为了省去这些多余的检查,Intel 在 Pentium II CPU 中加入了新的 SYSENTER 指令,专门用来执行系统调用。

该指令会跳过前面检查步骤,直接将 CPU 切换到特权模式,继而执行系统调用,同时还增加了几个专用寄存器辅助完成参数传递和上下文保存工作。另外,还相应地增加了 SYSEXIT 指令,用来返回执行结果,并切回用户模式。

在 Linux 实现了 SYSENTER 方式的系统调用之后,就有人用 Pentium III 的机器对比测试了两种系统调用的效率。测试结果显示,与中断方式相比,SYSENTER 在用户模式下因省掉了级别检查类的操作,花费的时间大幅减少了 45% 左右;在核心模式下,因少了一个寄存器压栈保存动作,所花费的时间也减少了 2% 左右。

目前,基于中断方式的系统调用仍然保留着,Linux 启动时会自动检测 CPU 是否支持 SYSENTER 指令,从而根据情况选择相应的系统调用方式。

1.3.3 SYSENTER 指令诞生故事

介绍完了 SYSENTER 指令的优越之处,我们回过头再来聊聊它的由来。

从 Linux 2.5 内核开始,,在经历了多方测试、多次 Patch 之后,SYSENTER 指令才正式被 Linux 2.6 版本支持,且由 Linus Torvalds 大神亲自操刀实现。

上面提到过,其实早在 1998 年,SYSENTER 指令就已经引入到 Intel Pentium II CPU 中,直到 2002 年才在 Linux 2.5 版本的内核中出现。该指令一出现,Linux 社区就开始了激烈讨论。

后来 Intel Pentium 4 CPU 发布了,这款 CPU 在“设计上存在的问题,造成 Pentium 4 使用中断方式执行系统调用比 Pentium 3 以及 AMD Athlon 所耗费的 CPU 时钟周期多 5~10 倍”,Linus 对这个结果接受不了,于是在 Linux 2.6 内核中加入了 SYSENTER 指令,从而实现了更加高效的系统调用。

(编辑:应用网_丽江站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

热点阅读