Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

系统调用

复习

  • 引导程序与内核加载:引导程序把内核装入内存,内核初始化后接手管理
  • 操作系统是什么:操作系统是资源的管理者和抽象提供者
  • 内核态与用户态:为什么普通程序不能随意执行危险操作

TL;DR

  • 系统调用是用户程序请求内核服务的正式接口
  • 它触发一次从用户态到内核态的切换,由内核代为完成
  • 内核按“编号”分发,做完再把结果返回用户程序
  • 它看起来像函数调用,但开销要大得多

正文

  程序想读一个文件、申请一块内存、往网络发一个包,这些事它自己都做不了——权限不够。它只能求内核帮忙。这个“求”的正式方式,就是系统调用(system call)。

一次系统调用长什么样

  用户程序想请内核办事,大致按这个流程:

  1. 把“要办哪件事”的编号,和需要的参数,放到约定的位置(通常是几个寄存器)
  2. 执行一条特殊的“陷入”指令,主动从用户态进入内核态
  3. 内核根据编号,在表里查到对应的处理程序
  4. 内核完成工作,把结果放回约定位置,返回用户态

  这很像去银行办事:先填一张单子(参数),取一个号(编号),柜员按号办理,办完把回执给你。你进不了金库,但通过柜台,事情照样办成了。

用户程序:放编号和参数 → 陷入 →(内核态)内核处理 → 返回

它像函数调用,但不一样

  从写法上看,系统调用和普通函数调用差不多:都是“把参数传进去,拿个结果回来”。可两者有本质区别:

  • 普通函数调用,仍在用户态内执行,跳进跳出的只是代码
  • 系统调用会切换特权级,进入内核态再回来

  特权级切换不便宜:要保存现场、切换栈、做权限检查。所以能少调就少调——这也是为什么读文件常常一次读一大块,而不是一个字节一个字节地调。

常见的系统调用

  系统调用名目很多,但常用的也就那几类:

  • 文件:打开、读、写、关闭
  • 进程:创建、结束、等待
  • 内存:申请、释放
  • 通信:网络收发、管道读写

  它们可以说是“操作系统功能的正式出口”。程序想用操作系统的任何能力,基本都要经过它们。

库函数与系统调用

  你可能会有个疑问:平时写代码,好像从没“手动陷入内核”过,只是调了个函数就完事了。

  这是因为库函数替你包了一层。比如打印用的 printf,它自己并不直接操作硬件;它把内容整理好,最终去调用内核的“写”系统调用,由内核把数据送到屏幕或文件。类似地,很多库函数只是“包装”,真正陷入内核的是它内部那一次系统调用。

  一层套一层,人写起来简单,底层却老老实实地走了正规流程——这正是我们一路见到的加层思路。

  到这里,“程序如何请求内核”就清楚了。可内核要管理的是一个又一个正在运行的程序。它怎样把一个静静躺在磁盘上的程序,变成一个正在占用 CPU 的“活物”?下一章,我们认识一个关键概念:进程

思考题

  既然系统调用和普通函数调用长得差不多,为什么不让用户程序直接执行内核代码,省掉切换?这样会带来什么问题?

小结

知识点

  • 系统调用是用户程序请求内核服务的接口
  • 一次系统调用:放编号与参数、陷入内核、内核处理、返回
  • 系统调用会切换特权级,开销比普通函数大
  • 库函数常常封装了系统调用

参考资料

  1. Wikipedia(zh):系统调用:系统调用的机制
  2. Wikipedia(zh):POSIX:常见的系统调用接口标准
  3. Wikipedia(zh):C 标准库:库函数与系统调用的关系

思考题答案(仅供参考)

  如果让用户程序直接执行内核代码,就等于取消了那两种模式:任何程序都能以最高权限为所欲为,改内存、动设备、绕过保护,系统立刻失去安全和稳定。系统调用虽然多了一次切换的开销,却换来两道保险:入口是固定的、可控的,内核可以在“放行”之前检查参数是否合法。用一点性能换取安全与秩序,非常划算。

协议

  本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

封面图

设计师 | 南国微雪