目录

1. Linux中的各种接口

1.1 LSB标准

1.2 Linux API

1.2.1 概述

1.2.2 Linux内核系统调用接口

1.2.3 C标准库

1.3 Linux ABI

1.4 内核API

1.5 系统调用与各种接口的关系

1.5.1 系统调用与API的关系

1.5.2 系统调用与系统命令的关系

1.5.3 系统调用与内核函数的关系

2. 中断、异常和系统调用的比较

2.1 源头不同

2.2 服务响应方式不同

2.3 处理机制不同

3. 系统调用的基本概念

3.1 系统调用号

3.2 系统调用表

3.3 系统调用封装例程和服务例程

4. 系统调用处理流程

4.1 设置异常处理函数

4.2 触发软中断进入内核态

4.3 调用系统调用服务例程

4.4 系统调用返回

5. 添加新的系统调用

5.1 添加系统调用号

5.1 添加系统调用表项

5.3 实现系统调用服务例程

5.4 重新编译内核

5.5 编写用户态程序


1. Linux中的各种接口

1.1 LSB标准

① LSB即Linux Standards Base,是Linux标准化领域中事实上的标准

② 由于Linux的发行版众多,为了促进Linux不同发行版之间的兼容性,LSB开发了一系列标准,使各种软件可以在兼容LSB标准的系统上运行

1.2 Linux API

1.2.1 概述

Linux API是Linux内核与用户空间的API,也就是让用户空间的程序能够通过这个接口访问系统资源和内核提供的服务

Linux API由两部分组成:Linux内核系统调用接口和GNU C库(glibc库)中的例程

1.2.2 Linux内核系统调用接口

系统调用接口是内核中所有已实现的可用系统调用的集合

1.2.3 C标准库

GNU C库是Linux内核系统调用接口的封装,其中包括POSIX兼容的应用函数调用和Linux专用的应用函数调用

目前最新的Linux内核5.0版本中系统调用大约有380个,GNU C库大约有2000个函数

说明:POSIX标准

POSIX表示可移植操作系统接口(Portable Operating System Interface of UNIX),POISX标准是针对API而不是针对系统调用的,他定义了操作系统应该为应用程序提供的接口标准,并不涉及对应的函数如何实现

1.3 Linux ABI

ABI是一些列约定的集合,可以说调用惯例(calling convention)就是ABI。ABI是和具体的CPU架构和OS相关的,具体而言,ABI包括以下内容,

① 一个特定的处理器指令集

② 函数调用惯例

③ 系统调用方式

④ 可执行文件的格式(e.g. ELF、PE)

说明:什么是函数调用惯例和系统调用方式

Linux提供了一个syscall函数,用来根据系统调用号直接调用系统调用,在该函数的man手册中说明了不同体系结构触发软中断的命令以及系统调用的传参方式,其实这就是所谓的ABI

1.4 内核API

① 内核API主要是内核中标记为EXPORT_SYMBOL的函数,这些函数主要是为了内核模块的编写而提供的

② 收到内核版本更迭的影响,内核API并不稳定,3.x版本内核的模块可能在4.x版本上就无法使用

1.5 系统调用与各种接口的关系

1.5.1 系统调用与API的关系

由于API是对系统调用的封装,所以API和系统调用之间可能存在如下几种关系,

① API和系统调用形式一致

e.g. read函数和read系统调用

② 几个不同的API内部使用了同一个系统调用

e.g. malloc / calloc和free函数的实现都调用了brk系统调用

③ 一个API内部使用了多个系统调用

e.g. malloc函数的实现根据要分配内存的大小,可能使用brk系统调用,也可能使用mmap系统调用

④ API内部不使用系统调用

e.g. string.h中声明的各种字符串处理函数

1.5.2 系统调用与系统命令的关系

系统命令相对API接口更高一层,每个系统命令都是一个可执行程序,使用strace命令可以查看系统命令使用的系统调用

说明:下图列出了一些系统命令与Linux各模块之间的关系

1.5.3 系统调用与内核函数的关系

系统调用进入内核后,会找到各自对应的内核函数,这些内核函数被称为系统调用的服务例程

e.g. 系统调用getpid对应的服务例程为sys_getpid

2. 中断、异常和系统调用的比较

根据中断和异常章节的学习可知,中断、异常和系统调用本质上属于一类,在处理方式上也类似,他们的差异体现在如下方面

2.1 源头不同

① 中断是外设发出的请求

② 异常是应用程序意想不到的行为

③ 系统调用是应用程序请求操作系统提供服务

2.2 服务响应方式不同

① 中断是异步的

② 异常是同步的

③ 系统调用既可以是异步的(e.g. 异步IO),也可以是同步的

2.3 处理机制不同

① 中断服务程序在内核态运行,对用户是透明的

② 异常出现时,或者杀死进程,或者重新执行引起异常的指令

③ 系统调用是用户发出请求后等待操作系统的服务

3. 系统调用的基本概念

3.1 系统调用号

① 定义在各体系结构的unistd.h中,在Linux 2.6.11 + 80386版本中为include/asm-i386/unistd.h

② 用来唯一的表示每个系统调用

③ 作为系统调用表的下标,当用户空间的进程执行一个系统调用时,该系统调用号作为参数传递,用来指明要执行的系统调用服务例程

3.2 系统调用表

① 在Linux 2.6.11 + 80386版本中,系统调用表sys_call_table定义在arch/i386/kernel/entry.S中

② 系统调用表是一个函数指针数组,使用系统调用号索引

说明:系统调用号和系统调用表一旦分配好就不能有改变,否则编译好的应用程序会因为调用到错误的系统调用而导致程序异常

3.3 系统调用封装例程和服务例程

① 系统调用服务例程就是内核态最终处理系统调用请求的函数

② 引入系统调用封装例程是因为用户空间的程序无法直接调用内核代码,因此在需要执行一个系统调用时,是通过软中断引发一个异常进入内核态,封装例程就是对这个过程的封装

4. 系统调用处理流程

4.1 设置异常处理函数

在内核初始化的trap_init函数中,将系统调用异常处理函数设置为system_call

说明:将系统调用的IDT描述符设置为系统门,使得用户态可以穿过该门进入内核态

4.2 触发软中断进入内核态

根据不同的体系结构,使用int $0x80 / syscall / svc指令即可触发系统调用对应的异常,并跳转到system_call函数运行

system_call函数在调用系统调用服务例程之前完成如下工作,

① 将系统调用号压栈(根据ABI,系统调用号通过eax寄存器传递)

② 调用SAVE_ALL将异常处理程序可以用到的所有寄存器保存到相应的栈中

此处注意3点,

a. 由于系统调用一定是从用户态切换到内核态,所以在进入异常处理时,硬件会进行栈的切换,并自动保存相关寄存器,如下图所示(系统调用中没有ERROR CODE)

b. SAVE_ALL中同时将ds和es装入内核数据段的段选择符

c. 根据之前的系统调用ABI说明,SAVE_ALL保存的寄存器中就包含了系统调用参数

③ 调用GET_THREAD_INFO,将当前进程thread_info的地址存放在ebp中

④ 判断系统调用号的合法性,如果合法则查找系统调用表并调用系统调用服务例程

4.3 调用系统调用服务例程

① 系统调用服务例程根据系统调用号在sys_call_table中查表得到

② 所有系统调用服务例程的参数为struct pt_regs类型,该类型对应的就是寄存器在栈上的布局

这里就有一个问题了,我们知道在X86体系结构中,函数参数是优先通过寄存器传递的,那么给系统调用服务例程的参数是如何传递的呢 ? 这里的玄机就在于asmlinkage宏

__attribute__((regparm(n)))用于指定最多可以使用n个寄存器传递参数,超过n的参数将使用栈传递

对于系统调用,使用regparm(0),也就是所有参数均通过栈传递,内核中所有系统调用的实现都使用了这个修饰符

说明:由于ARM体系结构中定义了ATPCS标准,函数的前4个参数使用r0 ~ r4寄存器传递,所以asmlinkage宏实际上就是extern "C",并未使用regparm修饰

在调用实际的系统调用服务例程之前,会先将sp指针传递给r0

4.4 系统调用返回

当服务例程执行结束时,system_call从eax获得他的返回值,并把这个返回值存放在栈中,让其位于用户态eax寄存器曾存放的位置,然后执行syscall_exit终止系统调用处理程序

当进程恢复到用户态执行时,就可以从eax中找到系统调用的返回值

说明:系统调用机制优化简介

在2.6的早期版本中,系统调用的实现使用的是int 0x80和iret命令,因为需要从用户态切换到内核态执行服务例程,然后再返回用户态,所以开销很大

为了加快系统调用的速度,随后引入了vsyscalls和vDSO机制,这两种机制都是从机制上对系统调用的速度进行了优化,但是使用软中断来进行系统调用需要进行特权级切换这一根本问题并没有解决

目前X86_64体系结构使用syscall / sysret指令实现系统调用,细节就不介绍了,因为我目前也不懂

说明2:定义系统调用服务例程

在后续的Linux内核版本中,提供了一组宏,用于定义系统调用服务例程

其中宏名中的数字表示服务例程的参数个数,下面举例说明

该宏可定义sys_write函数

5. 添加新的系统调用

说明:如上文所述,根据不同的体系结构与内核版本,要修改的文件位置会有所不同,甚至要修改的文件就不同,但是思路是一致的

5.1 添加系统调用号

修改体系结构目录中的unistd.h文件,增加系统调用号

注意同步修改NR_syscalls宏,该宏表示系统调用个数,会用于判断系统调用号的合法性

5.1 添加系统调用表项

修改体系结构目录的entry.S文件,添加系统调用表项

5.3 实现系统调用服务例程

可以新建文件,也可以在原有文件中添加系统调用服务例程。如果新建文件,注意修改Makefile

此处我们选择在kernel/sys.c中新增服务例程,

注意使用asmlinage修饰符

5.4 重新编译内核

由于修改了内核源码,要使其生效,必须重新编译并布署内核

5.5 编写用户态程序

#include <unistd.h>
#include <sys/syscall.h>#define __NR_mysyscall 289int main(void)
{syscall(__NR_mysyscall);return 0;
}

此处使用syscall + 系统调用号的方式调用系统调用,并未提供系统封装例程。在Linux 2.6.18版本之前,unistd.h中提供了一组__syscall宏用于定义系统调用封装例程,下面以定义3个参数的封装例程为例加以说明

其中type & name为封装例程的返回值与函数名,之后的type & arg对用于定义函数形参

在封装例程的实现中,就是按照ABI的约定将参数通过寄存器传输,并调用int $0x80触发软中断

这里特别说明下红框中的"0",他表示输入部分仍使用和输出部分相同的寄存器,次数就是用eax传输系统调用号(系统调用号由__NR_##name构成)

说明:增加有套路,定义需谨慎

增加一个系统调用并不难,他有一套比较规范的方法,难点是在实际应用中如何增加合适的系统调用。在绝大多数情况下,我们不会新增系统调用

Linux操作系统原理与应用06:系统调用相关推荐

  1. Linux 操作系统原理 — 内存 — 内存分配算法

    目录 文章目录 目录 前文列表 内存碎片 伙伴(Buddy)分配算法 Slab 算法 虚拟内存的分配 内核态内存分配 vmalloc 函数 kmalloc 用户态内存分配 malloc 申请内存 用户 ...

  2. Linux 操作系统原理 — 内存 — 基于 MMU 硬件单元的虚/实地址映射技术

    目录 文章目录 目录 前文列表 物理地址与虚拟地址 内存空间的组织方式 虚拟地址空间的编址 内核态地址空间 用户态地址空间 内-外存空间的交换与虚拟存储空间之间的映射关系 缺页异常 前文列表 < ...

  3. Linux 操作系统原理 — 系统结构

    目录 文章目录 目录 Linux 系统架构 Linux 内核 内存管理 进程管理 文件系统 设备驱动程序 网络接口 Shell Linux 系统架构 Linux 系统一般有 4 个主要部分:内核.Sh ...

  4. Linux 操作系统原理 — 内存 — 基于局部性原理实现的内/外存交换技术

    目录 文章目录 目录 前文列表 基于局部性原理实现的内-外存交换技术 局部性原理 Swap 交换分区 前文列表 <Linux 操作系统原理 - 内存 - 物理存储器与虚拟存储器> < ...

  5. Linux 操作系统原理 — 内存 — 页式管理、段式管理与段页式管理

    目录 文章目录 目录 前文列表 页式管理 快表 多级页表 基于页表的虚实地址转换原理 应用 TLB 快表提升虚实地址转换速度 页式虚拟存储器工作的全过程 缺页中断 为什么 Linux 默认页大小是 4 ...

  6. linux的原理和运用,Linux操作系统原理与应用_内存寻址

    原标题:Linux操作系统原理与应用_内存寻址 第五讲今天上线啦. 在本次课程中,陈老师详细的讲解了有关于内存寻址的演变的相关知识. 第一部分中,介绍了关于内存寻址的相关背景知识.内存寻址-操作系统设 ...

  7. linux操作系统原理_Linux内核分析-操作系统是如何工作的(二)

    linux操作系统的主要构架如图1所示,我们知道,操作系统是通过管理CPU进程.存储器.文件系统.设备驱动.以及网络接口等相关部分来工作的,我们这里主要是通过分析关于CPU的操作即进程的管理执行来分析 ...

  8. Linux操作系统原理与应用01:概述

    目录 1. Linux内核的技术特点 1.1 单内核结构 1.1.1 单内核特性 1.1.2 微内核特性 1.2 抢占式内核 1.2.1 非抢占式内核特性 1.2.2 抢占式内核特性 1.3 支持动态 ...

  9. linux操作系统原理_Linux后台开发C++学习路线技能加点,已拿鹅厂offer

    大家好我是lemon,最近在知乎经常被邀请回答类似如何学习C++和C++后台开发应该具体储备哪些基础技能的问题,围观原文链接: 非常详细的 Linux C/C++ 学习路线总结!助我拿下腾讯offer ...

最新文章

  1. 汇编和python-Python入门你要懂哪些?这篇文章总算讲清楚了
  2. 【计算机网络】计算机网络概述 : 总结 ( 概念 | 组成 | 功能 | 分类 | 性能指标 | OSI 七层参考模型 | TCP/IP 模型 | 五层参考模型 )★★★
  3. 必会重构技巧(三):提取接口
  4. 解读MD07中可供货天数的计算
  5. AAAI 2021 《Regularizing Attention Networks for Anomaly Detection in Visual Question Answering》论文笔记
  6. Java 调用接口工具类并设置请求和传输超时时间
  7. java1234小峰推荐书籍_java1234 webservice 第2 课 cfx实现
  8. python列表嵌套字典取值_我的 python 学习历程-Day05 字典/字典的嵌套
  9. zookeeper conceptual
  10. 关于DOM操作的几个类型
  11. python将空格变成换行_Python基础之PEP8规范(代码写作规范)
  12. 使用计算机计算一个多边形,多边形面积计算器
  13. WCDMA中3.84M码片速率和5M带宽的由来
  14. elastix中NAT穿越问题解决办法
  15. 黑客用社会工程学做渗透测试的广泛应用介绍
  16. 校外全局使用校园网,校园网免费下载知网资料
  17. NLTK-006:分类文本(性别鉴定)
  18. 计算机二级office知识大纲,2017计算机二级考试MS office 考试内容大纲
  19. 定时播放音频、定时播放视频解决方案 —— 定时执行专家
  20. 高速数据采集卡之FMC子板丨FMC接口AD/DA子卡丨坤驰科技

热门文章

  1. 学校计算机社团都干些什么,计算机社团管理制度
  2. mvc ajax helpers,ASP.NET MVC 实践系列4-Ajax应用
  3. C - Watchmen
  4. JDK8新特性(九)之Stream流的find()、max()、min()、reduce()方法
  5. Spring Data Jpa的@Temporal注解
  6. Layui--颜色选择器layui.colorpicker
  7. 步进电机 高速光耦_干货!伺服电机和步进电机的31个技术问答
  8. java switch case怎么判断范围_java小白从入门到精通(基础二)
  9. java计算两个时间段的重合天数
  10. Win10错误代码0x80070541是怎么回事