Linux系统调用原理

操作系统通过系统调用为运营于其上的经过提供服务。

当用户态进度发起八个种类调用, CPU 将切换成 内核态 并起先进行二个 内核函数 。
内核函数负责响应应用程序的渴求,例如操作文件、进行互连网通信或然申请内部存储器能源等。

初稿地址:https://learn-linux.readthedocs.io
玩转Linux旧群已满,请加新群:278378501
迎接关心我们的大众号:小菜学编制程序 (coding-fan)

举二个最简易的例证,应用进程必要输出1行文字,供给调用 write 这些系统调用:

#include <string.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    char *msg = "Hello, world!\n";
    write(1, msg, strlen(msg));

    return 0;
}

注解

读者或者会稍稍难点——输出文本不是用 printf 等函数吗?

确实是。 printf 是越来越高层次的库函数,建立在系统调用之上,达成多少格式化等效果。
因而,本质上依旧系统调用起决定性效用。

调用流程

那么,在应用程序内,调用3个种类调用的流水生产线是怎么着的吗?

我们以三个若是的种类调用 xyz 为例,介绍二次系统调用的具备环节。

图片 1

如上海教室,系统调用执行的流程如下:

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

履行态切换

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

Linux 通过 软中断 实现从 用户态 到 内核态 的切换。 用户态 与 内核态 是单身的执行流,因而在切换时,必要常备不懈 执行栈 并保存 寄存器 。

基础完成了众多见仁见智的体系调用(提供不相同功能),而 系统调用处理函数 唯有3个。
因而,用户进程必须传递七个参数用于区分,那正是 系统调用号 ( system
call number
 )。
在 Linux 中, 系统调用号 一般经过 eax 寄存器 来传递。

总结起来, 实践态切换 进程如下:

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

编制程序实践

上边,通过三个简便的次第,看看应用程序怎么着在 用户态 准备参数并因而 int 指令触发 软中断 以陷入 内核态 执行 系统调用 :

.section .rodata

msg:
    .ascii "Hello, world!\n"

.section .text

.global _start

_start:
    # call SYS_WRITE
    movl $4, %eax
    # push arguments
    movl $1, %ebx
    movl $msg, %ecx
    movl $14, %edx
    int $0x80

    # Call SYS_EXIT
    movl $1, %eax
    # push arguments
    movl $0, %ebx
    # initiate
    int $0x80

那是八个汇编语言程序,程序入口在 *_start* 标签之后。

第 12 行,准备 系统调用号 :将常数 4 放进 寄存器 eax 。 系统调用号 4 代表 系统调用
SYS_write , 大家将经过该连串调用向专业输出写入3个字符串。

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

write 系统调用须求 3 个参数:

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

第 17 行,执行 int 指令触发软中断 0x80 ,程序将深陷内核态并由基础执行系统调用。
系统调用执行实现后,内核将承受切换回用户态,应用程序继续执行之后的授命(
从 20 行开始 )。

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

注解

瞩目到,那里不可不显式调用 exit 系统调用退出程序。
不然,程序将继承往下执行,最终遭遇 段错误segmentation
fault
 )!

读者可能很愕然——在写 C 语言或许别的程序时,这些调用并不是必须的!

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

接下去,大家编译并推行那些汇编语言程序:

$ ls
hello_world-int.S
$ as -o hello_world-int.o hello_world-int.S
$ ls
hello_world-int.o  hello_world-int.S
$ ld -o hello_world-int hello_world-int.o
$ ls
hello_world-int  hello_world-int.o  hello_world-int.S
$ ./hello_world-int
Hello, world!

其实,将 系统调用号 和 调用参数 放进正确的 寄存器 并触及正确的 软中断 是个再一次的闲事。 C 库已经把那脏累活给干了——试试 syscall 函数吧!

#include <string.h>
#include <sys/syscall.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    char *msg = "Hello, world!\n";
    syscall(SYS_write, 1, msg, strlen(msg));

    return 0;
}

下一步

订阅更新,获取越多读书资料,请关怀大家的 微信公众号 :

图片 2

参考文献

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

图片 3

相关文章