操作系统通过系统调用为运行于其上的进程提供服务。

当用户态进程发起一个系统调用, CPU 将切换到 内核态 并开始执行一个 内核函数 。 内核函数负责响应应用程序的要求,例如操作文件、进行网络通讯或者申请内存资源等。

原文地址:https://learn-linux.readthedocs.io

QQ交流群:278378501

微信公众号:小菜学编程 (coding-fan)

举一个最简单的例子,应用进程需要输出一行文字,需要调用 write 这个系统调用:

  1. #include <string.h>
  2. #include <unistd.h>
  3. int main(int argc, char *argv[])
  4. {
  5. char *msg = "Hello, world!\n";
  6. write(1, msg, strlen(msg));
  7. return 0;
  8. }

注解

读者可能会有些疑问——输出文本不是用 printf 等函数吗?

确实是。 printf 是更高层次的库函数,建立在系统调用之上,实现数据格式化等功能。 因此,本质上还是系统调用起决定性作用。

调用流程

那么,在应用程序内,调用一个系统调用的流程是怎样的呢?

我们以一个假设的系统调用 xyz 为例,介绍一次系统调用的所有环节。

如上图,系统调用执行的流程如下:

  1. 应用程序 代码调用系统调用( xyz ),该函数是一个包装系统调用的 库函数 ;
  2. 库函数 ( xyz )负责准备向内核传递的参数,并触发 软中断 以切换到内核;
  3. CPU 被 软中断 打断后,执行 中断处理函数 ,即 系统调用处理函数 ( system_call);
  4. 系统调用处理函数 调用 系统调用服务例程 ( sys_xyz ),真正开始处理该系统调用;

执行态切换

应用程序 ( application program )与 库函数 ( libc )之间, 系统调用处理函数 ( system call handler )与 系统调用服务例程 ( system call service routine )之间, 均是普通函数调用,应该不难理解。 而 库函数 与 系统调用处理函数 之间,由于涉及用户态与内核态的切换,要复杂一些。

Linux 通过 软中断 实现从 用户态 到 内核态 的切换。 用户态 与 内核态 是独立的执行流,因此在切换时,需要准备 执行栈 并保存 寄存器 。

内核实现了很多不同的系统调用(提供不同功能),而 系统调用处理函数 只有一个。 因此,用户进程必须传递一个参数用于区分,这便是 系统调用号 ( system call number )。 在 Linux 中, 系统调用号 一般通过 eax 寄存器 来传递。

总结起来, 执行态切换 过程如下:

  1. 应用程序 在 用户态 准备好调用参数,执行 int 指令触发 软中断 ,中断号为 0x80 ;
  2. CPU 被软中断打断后,执行对应的 中断处理函数 ,这时便已进入 内核态 ;
  3. 系统调用处理函数 准备 内核执行栈 ,并保存所有 寄存器 (一般用汇编语言实现);
  4. 系统调用处理函数 根据 系统调用号 调用对应的 C 函数—— 系统调用服务例程 ;
  5. 系统调用处理函数 准备 返回值 并从 内核栈 中恢复 寄存器 ;
  6. 系统调用处理函数 执行 ret 指令切换回 用户态 ;

编程实践

下面,通过一个简单的程序,看看应用程序如何在 用户态 准备参数并通过 int 指令触发 软中断 以陷入 内核态 执行 系统调用 :

  1. .section .rodata
  2. msg:
  3. .ascii "Hello, world!\n"
  4. .section .text
  5. .global _start
  6. _start:
  7. # call SYS_WRITE
  8. movl $4, %eax
  9. # push arguments
  10. movl $1, %ebx
  11. movl $msg, %ecx
  12. movl $14, %edx
  13. int $0x80
  14. # Call SYS_EXIT
  15. movl $1, %eax
  16. # push arguments
  17. movl $0, %ebx
  18. # initiate
  19. int $0x80

这是一个汇编语言程序,程序入口在 _start 标签之后。

第 12 行,准备 系统调用号 :将常数 4 放进 寄存器 eax 。 系统调用号 4 代表 系统调用 SYS_write , 我们将通过该系统调用向标准输出写入一个字符串。

第 14-16 行, 准备系统调用参数:第一个参数放进 寄存器 ebx ,第二个参数放进 ecx , 以此类推。

write 系统调用需要 3 个参数:

  • 文件描述符 ,标准输出文件描述符为 1 ;
  • 写入内容(缓冲区)地址;
  • 写入内容长度(字节数);

第 17 行,执行 int 指令触发软中断 0x80 ,程序将陷入内核态并由内核执行系统调用。 系统调用执行完毕后,内核将负责切换回用户态,应用程序继续执行之后的指令( 从 20 行开始 )。

第 20-24 行,调用 exit 系统调用,以便退出程序。

注解

注意到,这里必须显式调用 exit 系统调用退出程序。 否则,程序将继续往下执行,最终遇到 段错误segmentation fault )!

读者可能很好奇——在写 C 语言或者其他程序时,这个调用并不是必须的!

这是因为 C 库( libc )已经帮你把脏活累活都干了。

接下来,我们编译并执行这个汇编语言程序:

  1. $ ls
  2. hello_world-int.S
  3. $ as -o hello_world-int.o hello_world-int.S
  4. $ ls
  5. hello_world-int.o hello_world-int.S
  6. $ ld -o hello_world-int hello_world-int.o
  7. $ ls
  8. hello_world-int hello_world-int.o hello_world-int.S
  9. $ ./hello_world-int
  10. Hello, world!

其实,将 系统调用号 和 调用参数 放进正确的 寄存器 并触发正确的 软中断 是个重复的麻烦事。 C 库已经把这脏累活给干了——试试 syscall 函数吧!

  1. #include <string.h>
  2. #include <sys/syscall.h>
  3. #include <unistd.h>
  4. int main(int argc, char *argv[])
  5. {
  6. char *msg = "Hello, world!\n";
  7. syscall(SYS_write, 1, msg, strlen(msg));
  8. return 0;
  9. }

参考文献

  1. Serg Iakovlev
  2. write(2) - Linux manual page
  3. syscall(2) - Linux manual page
  4. _exit(2) - Linux manual page

Linux系统调用原理的更多相关文章

  1. Linux系统调用(转载)

    目录: 1. Linux系统调用原理 2. 系统调用的实现 3. Linux系统调用分类及列表 4.系统调用.用户编程接口(API).系统命令和内核函数的关系 5. Linux系统调用实例 6. Li ...

  2. ARM Linux系统调用的原理

    转载自:http://blog.csdn.net/hongjiujing/article/details/6831192 ARM Linux系统调用的原理 操作系统为在用户态运行的进程与硬件设备进行交 ...

  3. Linux系统调用(syscall)原理(转)

    引言:分析Android源码的过程中,要想从上至下完全明白一行代码,往往涉及app.framework.native一直到kernel,可能迷失到代码世界,明白了系统调用原理,或许能帮你峰回路转,找到 ...

  4. Linux内核学习笔记1——系统调用原理【转】

    1什么是系统调用 系统调用,顾名思义,说的是操作系统提供给用户程序调用的一组“特殊”接口.用户程序可以通过这组“特殊”接口来获得操作系统内核提供的服务,比如用户可以通过文件系统相关的调用请求系统打开文 ...

  5. [Linux]系统调用理解(1)

    本文是Linux系统调用专栏系列文章的第一篇,对Linux系统调用的定义.基本原理.使用方法和注意事项大概作了一个介绍,以便读者对Linux系统调用建立一个大致的印象. 什么是系统调用? Linux内 ...

  6. 20169212《Linux内核原理与分析》课程总结

    20169212<Linux内核原理与分析>课程总结 每周作业链接汇总 第一周作业:完成linux基础入门实验,了解一些基础的命令操作. 第二周作业:学习MOOC课程--计算机是如何工作的 ...

  7. 别出心裁的Linux系统调用学习法

    别出心裁的Linux系统调用学习法 操作系统与系统调用 操作系统(Operating System,简称OS)是计算机中最重要的系统软件,是这样的一组系统程序的集成:这些系统程序在用户对计算机的使用中 ...

  8. Linux进程调度原理

    Linux进程调度原理 Linux进程调度机制 Linux进程调度的目标 1.高效性:高效意味着在相同的时间下要完成更多的任务.调度程序会被频繁的执行,所以调度程序要尽可能的高效: 2.加强交互性能: ...

  9. linux内核剖析(六)Linux系统调用详解(实现机制分析)

    本文介绍了系统调用的一些实现细节.首先分析了系统调用的意义,它们与库函数和应用程序接口(API)有怎样的关系.然后,我们考察了Linux内核如何实现系统调用,以及执行系统调用的连锁反应:陷入内核,传递 ...

随机推荐

  1. BZOJ1492 [NOI2007]货币兑换

    Description 小Y最近在一家金券交易所工作.该金券交易所只发行交易两种金券:A纪念券(以下简称A券)和 B纪念券(以下 简称B券).每个持有金券的顾客都有一个自己的帐户.金券的数目可以是一个 ...

  2. 理解webpack4.splitChunks之cacheGroups

    cacheGroups其实是splitChunks里面最核心的配置,一开始我还认为cacheGroups是可有可无的,这是完全错误的,splitChunks就是根据cacheGroups去拆分模块的, ...

  3. redux、immutablejs和mobx性能对比(二)

    三.分析数据 1.前提说明 我对测试出的10个数据摘除最大值与最小值,然后求平均值 根据平均值我绘制了一个曲线图一个柱状图 曲线图用于查看1000-100000的性能趋势 柱状图用于比较在相同条数下r ...

  4. js 随机打乱数组

    假如有一个数组: var arr1=['a','b','c','d','e','f','g']; 需要将它进行随机打乱,网上好多都是用: arr1.sort(function(){ return 0. ...

  5. PHP 如何向关联数组指定的 Key 之前插入元素

    PHP 关联数组可以通过三种方式插入新元素: $array[$insert_key] = $insert_value; $array = array_merge($array, $insert_arr ...

  6. bootStrap下拉菜单 点击下拉列表某个元素,列表不隐藏

    html: <a class="dropdown-toggle bgImg-priceWran " id="dropdownMenu1" data-tog ...

  7. DOM操作表单

    <!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>& ...

  8. eclipse 断点调试快捷键

    (1)Ctrl+M --切换窗口的大小(2)Ctrl+Q --跳到最后一次的编辑处(3)F2 --当鼠标放在一个标记处出现Tooltip时候按F2则把鼠标移开时Tooltip还会显示即Show Too ...

  9. xfs参数简介

    age_buffer_centisecs age_buffer_centisecs:(Min: 100  Default: 1500  Max: 720000) 多长时间设置为脏数据 xfsbufd_ ...

  10. 《ArcGIS Runtime SDK for Android开发笔记》——(4)、基于Android Studio构建ArcGIS Android开发环境

    1.前言 2015年1月15日,发布ArcGIS Runtime SDK for Android v10.2.5版本.从该版本开始默认支持android studio开发环境,示例代码的默认开发环境也 ...