系统调用

  • 1 API、POSIX和C库
  • 2 系统调用
    • 系统调用号
  • 3 系统调用处理程序
    • 指定恰当的系统调用
    • 参数传递
  • 4 系统调用的实现
    • 参数验证
  • 5 系统调用上下文
    • 绑定一个系统调用的最后步骤
    • 从用户空间访问系统调用
    • 为什么不通过系统调用的方式实现

1 API、POSIX和C库

API:应用编程接口。一个API定义了一组应用程序使用的编程接口。它们可以实现成一个系统调用,也可以通过调用多个系统调用来实现,而完全不使用任何系统调用也不存在问题。在Unix世界中,最流行的应用编程接口是基于POSIX标准的。Linux是与POSIX兼容的。

Linux的系统调用像大多数Unix一样,作为C库的一部分。C库实现了Unix系统的主要API,包括标准C库函数和系统调用。

Unix的系统调用抽象出了完成某种确定目的的函数。至于这些函数怎么用完全不需要内核去关心,提供机制(需要提供什么功能)而不是策略(怎样实现这些功能)。

2 系统调用

系统调用通常通过函数进行调用,在Linux中常称作syscalls。系统调用还会通过一个long类型的返回值来表示成功或者失败,使用long类型是为了与64位的硬件体系结构保持兼容。

函数声明中如果用asmlinkage限定词,表示通知编译器仅从栈中提取函数的参数,所有的系统调用都需要这个限定词。Linux所有的系统调用都应该遵守的命名规则:系统调用getpid()在内核中被定义为sys_getpid(),要在系统调用加上sys_。

系统调用号

Linux中,每个系统调用都被赋予一个系统调用号。这样,通过这个独一无二的号就可以关联系统调用。当用户空间的进程执行一个系统调用的时候,这个系统调用号就被用来指明到底是要执行那个系统调用,进程不会提及系统调用的名称。

系统调用号一旦分配就不能再有任何变更,否则编译好的应用程序就会崩溃。此外,如果一个系统调用被删除,它所占的系统调用号也不允许被回收利用,否则,以前编译过的代码再调用这个系统调用,实际上调用的就是另一个系统调用了。Linux有一个系统调用sys_ni_syscall(),它除了返回ENOSYS外不做任何其他的工作,这个错误号就是专门针对无效的系统调用而设的。

内核记录了系统调用表中的所有已被注册过的系统调用的列表,存储在sys_call_table中。它与结构体系有关,需要将系统调用分别注册到每个需要支持的体系结构去,一般定义在entry.s中。2.6.10版本的位置在arch/结构体系/kernel/entry.S。这个表中为每一个有效的系统调用指定了唯一的系统调用号。
m32r体系结构:

3 系统调用处理程序

用户空间的程序无法直接执行内核代码,他们不能直接调用内核空间中的函数,所以,应用程序应该以某种方式通知系统,告诉内核自己需要执行一个系统调用,希望系统切换到内核态,通知内核的机制是靠软中断实现的:通过一个异常来促使系统切换到内核态去执行异常处理程序。此时的异常处理程序就是系统调用程序。x86系统上的软中断是int 0x80指令产生的。这条指令会触发一个异常导致系统切换到内核态并执行第128号异常处理程序,而该程序正是系统调用处理程序,叫system_call()。x86处理器增加了一条叫做sysenter的指令。与int指令相比,这条指令提供了更快、更专业的陷入内核执行系统调用的方式。

指定恰当的系统调用

因为所有的系统调用陷入内核方式都一样,因此必须把系统调用号一并传给内核,告诉内核去执行什么系统调用。在x86上,系统调用号是通过eax寄存器传递给内核的。在陷入内核之前,用户空间就把相应系统调用所对应的号放入eax中。这样系统调用处理程序一旦运行,就可以从eax中得到数据。

system_call()函数通过将给定的系统调用号与NR_syscalls做比较来检查其有效性。如果大于等于NR_syscalls,该函数返回ENOSYS。否则,就执行相应的系统调用。

call *sys_call_table(,%eax,4)

由于系统调用表中的表项是32位(4字节)类型存放的,所以内核需要将给定的系统调用号乘以4,然后用所得的结构在该表中查询其位置。

参数传递

除了系统调用号外,大部分系统调用都还需要一些外部的参数输入。所以,在发送异常的时候,应该把这些参数从用户空间传给内核。最简单的办法就是像传递系统调用号一样:把这些参数存放到寄存器里。在x86系统上,ebx、ecx、edx、edi和esi按照顺序存放前五个参数。需要6个或6个以上参数的,应该用一个单独的寄存器存放指向所有这些参数在用户空间地址的指针。

用户空间返回值也通过寄存器传递,在x86系统上,它存放在eax寄存器中。

4 系统调用的实现

实现一个新的系统调用的第一步是决定它的用途。它要做什么?每个系统调用都应该有一个明确的用途。在Linux中不提倡采用多用途的系统调用(一个系统调用根据参数来选择完成不同的工作)。

新系统调用的参数、返回值和错误码又该是什么?系统调用的接口应该简洁,参数尽可能的少。

设计接口的时候要尽量为将来多做考虑。

当写一个系统调用的时候,要时刻注意可移植性和健壮性,不但要考虑当前,还要为将来做打算。

参数验证

系统调用必须仔细检查它们所有的参数是否合法有效。系统调用在内核空间执行,如果任由用户将不合法的输入传递给内核,那么系统的安全和稳定将面临巨大的考验。

内核提供了两个方法来完成必须的检查和内核空间与用户空间之间数据的来回拷贝。为了向用户空间写入数据,内核提供了copy_to_uesr(),它需要三个参数。第一个参数是进程空间中的目的内存地址。第二个是内核空间内的源地址。最后一个参数是需要拷贝的数据长度。为了从用户空间读取数据,内核提供了copy_from_user()。该函数把第二个参数指定的位置上的数据拷贝到第一个参数指定的位置上,拷贝的数据长度由第三个参数决定。

注意,内核无论何时都不能轻率地接受来自用户空间的指针!

5 系统调用上下文

在执行系统调用的时候处于进程上下文。current指针指向当前任务,即引发系统调用的那个进程。
在进程上下文中,内核可以休眠并且可以被抢占。当系统调用返回的时候,控制权仍然在system_call()中,它最终负责切换到用户空间并让用户进程继续执行下去。

绑定一个系统调用的最后步骤

当编写完一个系统调用后,把它注册成一个正式的系统调用是件琐碎的工作:

  • 首先,在系统调用表的最后加入一个表项。每种支持该系统调用的硬件体系都必须做这样的工作。从0开始算起,系统调用在该表中的位置就是它的系统调用号。如第10个系统调用分配到的系统调用号为9。
  • 对于所支持的各种体系结构,系统调用号都必须定义于include/asm/unistd.h中。
  • 系统调用必须被编译进内核映像(不能编译成模块)。这只要把它放进kernel/下的一个相关文件就行了。

我们通过一个虚构的系统调用foo()来仔细观察这些步骤。首先,我们把sys_foo加入到系统调用表中去。对于大多数体系结构中,该表位于entry.s中,形式如下:

我们把新的系统调用加到这个表的末尾:

.long sys_foo

虽然没有明确地指定编号,但我们加入这个系统调用被次序分配给了285这个系统调用号。对于每种需要支持的体系结构,我们都必须将自己的系统调用加入到其系统调用表中去。每种体系结构不需要对应相同的系统调用号。系统调用号是专属体系结构ABI(应用程序二进制结构)的部分。

接下来,我们把系统调用号加入到include/asm/unistd.h中,它的格式如下

然后,我们在该列表中加入下面这行:

#define _NR_foo          285

同时#define NR_syscalls 285这个要改成#define NR_syscalls 286。因为系统调用处理程序system_call()函数通过将给定的系统调用号与NR_syscalls做比较来检查其有效性。如果大于等于NR_syscalls,该函数返回ENOSYS。否则,就执行相应的系统调用。

最后,我们来实现foo()系统调用。无论何种配置,该系统调用都必须编译到核心的内核映像中去,所以我们把它放在kernel/sys.c文件中。再次编译内核就可以了。

从用户空间访问系统调用

Linux提供了一组宏,用于直接对系统调用进行访问。它会设置好寄存器并调用陷入指令。这些宏是_syscalln(),其中n的范围是0到6,代表需要传递给系统调用的参数个数。举个例子,open系统调用的 定义为:

long open(const char *filename,int flags,int mode);

直接调用该系统调用的宏形式为:

_syscall3(long,open,const char *,filename,int flags,int mode)

这样程序就可直接使用open。对于每个宏,都有2+2*n个参数,第一个参数是返回值类型。第二个参数是系统调用的名称。

为什么不通过系统调用的方式实现

建立一个新的系统调用非常容易,但却绝不倡导。

建立一个新的系统调用的好处:

  • 系统调用创建容易且使用方便
  • Linux系统调用的高性能显而易见

问题是:

  • 你需要一个系统调用号,而这需要在一个内核处于开发版本的时候由官方分配给你
  • 系统调用被加入稳定内核后就被固化了
  • 需要将系统调用分别注册的每个需要的体系结构去
  • 在脚本中不容易调用系统调用,也不能从文件系统直接访问系统调用

Linux内核设计与实现---系统调用相关推荐

  1. Linux内核设计第四周——扒开系统调用三层皮

    Linux内核设计第四周 --扒开系统调用三层皮 一.知识点总结 (一).系统调用基础知识 1.用户态和内核态 内核态:在高级别的状态下,代码可以执行特权指令,访问任意的物理地址:  用户态:在相应的 ...

  2. 《Linux内核设计与实现》读书笔记 - 目录 (完结)

    读完这本书回过头才发现, 第一篇笔记居然是 2012年8月发的, 将近一年半的时间才看完这本书(汗!!!). 为了方便以后查看, 做个<Linux内核设计与实现>读书笔记 的目录: < ...

  3. 读《Linux内核设计与实现》我想到了这些书

          从题目中可以看到,这篇文章是以我读<Linux内核设计与实现>而想到的其他我读过的书,所以,这篇文章的主要支撑点是<Linux内核>.       开始读这本书已经 ...

  4. 读 Linux内核设计与实现 我想到了这些书

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴!     ...

  5. Linux内核设计与实现学习笔记目录

    **注:**这是别人的笔记,我只是把目录抄过来 <Linux内核设计与实现学习笔记> 1.<Linux内核设计与实现>读书笔记(一)-内核简介 2.<Linux内核设计与 ...

  6. 《linux内核设计与实现》读书笔记第一、二章

    第一章 Linux内核简介 1.1 Unix的历史 1971年,Unix被移植到PDP-11型机中.  1973年,Unix操作系统用C语言改写--为Unix系统的广泛移植铺平了道路.  1977年, ...

  7. Linux内核设计的艺术

    Linux内核设计的艺术这本书是我认为对Linux内核描述非常优秀的书籍.书籍中描述了内核启动的流程,内核运行的机理,内存管理,进程管理等等. #书籍目录 第1章 从开机加电到执行 main函数之前的 ...

  8. 初探内核之《Linux内核设计与实现》笔记下

    定时器和时间管理 系统中有很多与时间相关的程序(比如定期执行的任务,某一时间执行的任务,推迟一段时间执行的任务),因此,时间的管理对于linux来说非常重要. 主要内容: 系统时间 定时器 定时器相关 ...

  9. 初探内核之《Linux内核设计与实现》笔记上

    内核简介  本篇简单介绍内核相关的基本概念. 主要内容: 单内核和微内核 内核版本号 1. 单内核和微内核   原理 优势 劣势 单内核 整个内核都在一个大内核地址空间上运行. 1. 简单. 2. 高 ...

最新文章

  1. 如何在Windows7上安装Hyper-v manager
  2. 图像检索:几类基于内容的图像分类技术
  3. 神策数据受邀参加全国 APP 个人信息保护监管会
  4. win32api.sendmessage模拟鼠标点击_安卓模拟器一键宏设置教程
  5. [渝粤教育] 中国地质大学 大学英语(6) 复习题
  6. Redis--Windos下的安装和使用
  7. 【SSH网上商城项目实战03】使用EasyUI搭建后台页面框架
  8. 解决Eclipse10配置Pydev不成功的问题
  9. Java 替换PDF中的字体
  10. 晶振PCB layout注意事项
  11. PHP + Apache + Mysql集成环境部署及简要教程
  12. 如何把m4a转换成mp3?音频格式转换步骤
  13. 彩云之南,难忘的地方
  14. No.44-VulnHub-Pegasus: 1-Walkthrough渗透学习
  15. Oracle实现递归查询
  16. java与python两个小人动图_CSS Sprite小图片自动合并工具(NodeJS,Python,Java,Ruby)
  17. 预训练模型(PTMs)发展史
  18. 专家建议加速2G3G退网、5G取代4G,你感受到网速快了吗?
  19. postgis+geoserver最短路径
  20. 遗传算法GA求解非连续函数最值问题

热门文章

  1. 使用prismjs为网站添加代码高亮功能
  2. 字符串、对象、数组操作方法、json方法
  3. 【HTML基础】表格和表单
  4. style对象的cssText方法
  5. java面向对象中的抽象,类与对象
  6. Unicode与JavaScript详解 [很好的文章转]
  7. OSPF路由器建立全毗邻关系的状态转换过程
  8. 林子大了,什么鸟都有----.NET运用String的十八层境界
  9. WCF中的方法重载 实现
  10. linux mint 18.3 内核,Linux Mint 18.3 “Sylvia” Cinnamon正式发布上线