以下内容转载自 https://mp.weixin.qq.com/s/CXhc71UZtbdbAo15xbJ6_Q
[我的博客地址](https://shankusu2017.github.io/)
原创 爱写程序的阿波张 源码游记 2019-04-24
我们将在最后一章讨论有关系统调用方面的抢占调度,所以这里有必要对系统调用有个基本的了解。
系统调用是指使用类似函数调用的方式调用操作系统提供的API。
虽然从概念上来说系统调用和函数调用差不多,但本质上它们有很大的不同,操作系统的代码位于内核地址空间,而CPU在执行用户代码时特权等级很低,无权访问需要最高优先级才能访问的内核地址空间的代码和数据,所以不能通过简单的call指令直接调用操作系统提供的函数,而需要使用特殊的指令进入操作系统内核完成指定的功能。
另外,用户代码调用操作系统API也不是根据函数名直接调用,而是需要根据操作系统为每个API提供的一个整型编号来调用,AMD64 Linux平台约定在进行系统调用时使用rax寄存器存放系统调用编号,同时约定使用rdi, rsi, rdx, r10, r8和r9来传递前6个系统调用参数。
可能有读者会说,我们平时编程也没有用到系统调用啊?!其实并不是没有用到,而是我们没有感觉到它的存在,比如最简单的向屏幕输出字符串,打开文件,读写文件以及网络编程中的创建socket等等都使用了系统调用,我们没有感觉到系统调用的存在主要是因为我们使用的函数库或package把它们封装成了函数,我们只需要直接调用这些函数就可以了。比如有下面一段go代码:
package main
import (
"os"
)
func main() {
fd, err := os.Open("./syscall.go") // 将会使用系统调用打开文件
......
fd.Close() // 将会使用系统调用关闭文件
}
这里的os.Open()和fd.Close()函数最终都会通过系统调用进入操作系统内核完成相应的功能。以os.Open为例,它最终会执行下面这段汇编代码来通过openat系统调用打开文件:
mov 0x10(%rsp),%rdi #第1个参数
mov 0x18(%rsp),%rsi #第2个参数
mov 0x20(%rsp),%rdx #第3个参数
mov 0x28(%rsp),%r10 #第4个参数
mov 0x30(%rsp),%r8 #第5个参数
mov 0x38(%rsp),%r9 #第6个参数
mov 0x8(%rsp),%rax #系统调用编号 rax = 267,表示调用openat系统调用
syscall #系统调用指令,进入Linux内核
这里,代码首先把6个参数以及openat这个系统调用的编号267保存在了对应的寄存器中,然后使用syscall指令进入内核执行打开文件的功能。
最后,如果你觉得本文对你有帮助的话,麻烦帮忙点一下文末右下角的 在看 或转发到朋友圈,非常感谢!