资讯专栏INFORMATION COLUMN

Linux系统调用原理

Jonathan Shieber / 1762人阅读

摘要:操作系统通过系统调用为运行于其上的进程提供服务。是更高层次的库函数,建立在系统调用之上,实现数据格式化等功能。而库函数与系统调用处理函数之间,由于涉及用户态与内核态的切换,要复杂一些。

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

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

原文地址:https://learn-linux.readthedocs.io
玩转Linux旧群已满,请加新群:278378501。
欢迎关注我们的公众号:小菜学编程 (coding-fan)

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

#include 
#include 

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

    return 0;
}
注解

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

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

调用流程

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

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

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

应用程序 代码调用系统调用( xyz ),该函数是一个包装系统调用的 库函数 ;

库函数 ( xyz )负责准备向内核传递的参数,并触发 软中断 以切换到内核;

CPU 被 软中断 打断后,执行 中断处理函数 ,即 系统调用处理函数 ( system_call);

系统调用处理函数 调用 系统调用服务例程 ( sys_xyz ),真正开始处理该系统调用;

执行态切换

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

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

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

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

应用程序 在 用户态 准备好调用参数,执行 int 指令触发 软中断 ,中断号为 0x80 ;

CPU 被软中断打断后,执行对应的 中断处理函数 ,这时便已进入 内核态 ;

系统调用处理函数 准备 内核执行栈 ,并保存所有 寄存器 (一般用汇编语言实现);

系统调用处理函数 根据 系统调用号 调用对应的 C 函数—— 系统调用服务例程 ;

系统调用处理函数 准备 返回值 并从 内核栈 中恢复 寄存器 ;

系统调用处理函数 执行 ret 指令切换回 用户态 ;

编程实践

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

.section .rodata

msg:
    .ascii "Hello, world!
"

.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 , 我们将通过该系统调用向标准输出写入一个字符串。

第 14-16 行, 准备系统调用参数:第一个参数放进 寄存器 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 
#include 
#include 

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

    return 0;
}
下一步

订阅更新,获取更多学习资料,请关注我们的 微信公众号 :

参考文献

Serg Iakovlev

write(2) - Linux manual page

syscall(2) - Linux manual page

_exit(2) - Linux manual page

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/62956.html

相关文章

  • Linux再学习(一)-学习路线规划

    摘要:攻克了第一个困难了。因为你的角色再次变化。其中每个系统调用都要进行深入地学习读文档做实验。经过一段时间的学习,你攻克了这些东西。内核机制是我们重点学习部分,基于最新的内核。辅助学习,推荐深入理解内核。 1 抛弃旧文化,迎接Linux命令新文化 Linux第一步,从Windows思维,切换到Linux的命令行+文件模式 在Linux中,做什么都有相应命令。一般就在bin或者sbin目录...

    hoohack 评论0 收藏0
  • 技术分享 | Linux 入侵检测中的进程创建监控

    摘要:简介在入侵检测的过程中,进程创建监控是必不可少的一点,因为攻击者的绝大多数攻击行为都是以进程的方式呈现,所以及时获取到新进程创建的信息能帮助我们快速地定位攻击行为。 作者简介:张博,网易高级信息安全工程师。 0x00 简介 在入侵检测的过程中,进程创建监控是必不可少的一点,因为攻击者的绝大多数攻击行为都是以进程的方式呈现,所以及时获取到新进程创建的信息能帮助我们快速地定位攻击行为。 本...

    kevin 评论0 收藏0
  • linux : 锁实现原理

    摘要:当进程释放锁或者要离开互斥区的时候,对进行操作,即原子性的给同步变量加。如果同步变量由变成,则没有竞争发生,进程照常执行。这里的原子性加减通常是用完成的,与平台相关。的基本形式是当中存放的值等于时,用对其替换。 futex 转载链接 In computing, a futex (short for fast userspace mutex) is a Linux kernel syst...

    Youngs 评论0 收藏0
  • 研究一下 fork 的原理,并且还有 vfork 的一些使用场景

    摘要:也就是说,子进程对大多数变量的操作,都是不会影响父进程的。不过事实上,这也是父子进程使用互相通信的原理基础。 笔者一直试图从最基本的原理上去理解(甚至尝试原理性设计)一个服务器的架构,为此提出了一些问题。此外,笔者对 异步 I/O 也有不少学习。从几个方面学习了 vfork() 的用法。 本文纯粹记录一下。不过不同于其他资料的大段代码,本文更多地用文字和排版来尽可能清晰地说明。 本文地...

    Joyven 评论0 收藏0
  • 【转】Linux 守护进程的原理与实现

    摘要:守护进程是脱离于终端并且在后台运行的进程。守护进程常常在系统引导装入时启动,在系统关闭时终止。且该进程组不会因组长进程的退出而受到影响。因此,把文件权限掩码设置为,可以大大增强该守护进程的灵活性。 原文地址:http://blog.csdn.net/zsf8701/... 一、守护进程概述在Linux或者unix操作系统中在系统的引导的时候会开启很多服务,这些服务就叫做守护进程。为了增...

    BingqiChen 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<