系统调用

本文是《go调度器源代码情景分析》系列 第一章 预备知识的第7小节。

 

我们将在最后一章讨论goroutine的有关系统调用方面的抢占调度,所以这里先对系统调用做个基本的介绍。

系统调用是指使用类似函数调用的方式调用操作系统提供的API。

虽然从概念上来说系统调用和函数调用差不多,但本质上它们有很大的不同,操作系统的代码位于内核地址空间,而CPU在执行用户代码时特权等级很低,无权访问需要最高优先级才能访问的内核地址空间的代码和数据,所以不能通过简单的call指令直接调用操作系统提供的函数,而需要使用特殊的指令进入操作系统内核完成指定的功能。

另外,用户代码调用操作系统API也不是根据函数名直接调用,而是需要根据操作系统为每个API提供的一个整型编号来调用,AMD64 Linux平台约定在进行系统调用时使用rax寄存器存放系统调用编号,同时约定使用rdi, rsi, rdx, r10, r8和r9来传递前6个系统调用参数。

可能有读者会说,我们平时编程也没有用到系统调用啊?!其实并不是没有用到,而是我们没有感觉到它的存在,比如最简单的向屏幕输出字符串,打开文件,读写文件以及网络编程中的创建socket等等都使用了系统调用,我们没有感觉到系统调用的存在主要是因为我们使用的函数库或package把它们封装成了函数,我们只需要直接调用这些函数就可以了。比如有下面一段go代码:

package main

import(
        "os"
)

funcmain() {
        fd, err:=os.Open("./syscall.go")  // 将会使用系统调用打开文件
        ......
        fd.Close()  // 将会使用系统调用关闭文件
}

 

这里的os.Open()和fd.Close()函数最终都会通过系统调用进入操作系统内核完成相应的功能。以os.Open为例,在Go语言的实现中它最终会执行下面这段汇编代码来通过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系统调用其实没有这么多参数,这是Go的底层实现,内核并不会使用多余无用的参数)以及openat这个系统调用的编号267保存在了对应的寄存器中,然后使用syscall指令进入内核执行打开文件的功能。

posted @ 2019-04-30 12:08  爱写程序的阿波张  阅读(1211)  评论(0编辑  收藏  举报