系统调用,是操作系统内核为应用提供的 API。今天继续来讲一个系统调用案例和 Go 中常见系统调用
arch | syscall NR | return | arg0 | arg1 | arg2 | arg3 | arg4 | arg5 |
---|---|---|---|---|---|---|---|---|
213 | epoll_create | man/ cs/ | 0xd5 | int size | - | - | - | - |
来源:https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md#x86_64-64_bit
这是 Linux 中的系统调用,编号是 213,我们可以从一下代码了解更多信息:
1# define SYS_epoll_create 213
2
3TEXT runtime.epoll_create(SB), NOSPLIT, $0
4 MOVL size+0(FP), DI
5 MOVL $SYS_epoll_create, AX
6 SYSCALL
7 MOVL AX, ret+8(FP)
8 RET
编号 213 按照调用规约会被存储在 rax 寄存器中,也就是这里的 AX 寄存器。epoll_create 只有一个阐述传递,也就是 int 类型的 size。SYSCALL 直接进入内核去了。
因此,我们需要知道 SYSCALL 之后发生了什么,即我们写好应用程序 Application program 执行了哪些 SYSCALL 逻辑。从下图可以得知:
当我们调用了 syscall.EpollCreate 之后进入了 syscall 包中的一段代码,然后根据一定的规则翻译成 runtime.RawSyscall,里面会有一堆准备参数和逻辑,完了之后就是执行 SYSCALL 指令。上期说的我们写的代码处于 CPU 分级保护域 ring-3,执行这个指令之后就能帮助我们从 ring-3 切换到 ring-0,然后去执行内核相关的代码了。ring-0 内核模式下什么操作都能够进行,没有特别的权限限制。内核也有相关的系统调用表(在最上面的链接中),会去调用具体的系统调用实现。
推荐一个可以查看层内核的在线工具,做 C/C++ 的同学都可以看:code.woboq.org
Go 常见系统调用
我想,大多数同学和我一样,想问一个问题:了解了系统调用对我们有什么用呢?按理来说,我们还要了解几个部分,Go 的实现,Go 怎么和操作系统交互。知道了 GC 就知道 Go 怎么和系统进行交互。知道了内存相关就知道内核 flag 或 syscall 被修改后为什么会导致应用层行为出这么大的问题。像这些业务问题我们大概知道一点,就不会在做应用的时候完全束手无策。
在 Go 代码中,也有支持系统调用的地方,比如:
Go 代码 | 系统调用 |
---|---|
os.GetPid() | getpid() |
println(“hello world”) | write(2, “hello world”, 11) |
startm -> newm -> newosproc | clone(child_stack=0xc43003c00, flags=……) |
系统调用 write(2, “hello world”, 11) 中,2 代表 stderr,11 代表字符串长度 strlen。我们在 Linux 中最常见的就是 stdin、stdout、stderr,如果不主动做修改,他们分别对应数字 0, 1, 2. 这里可能会问到,为什么会传递字符串长度,在 C 语言中传递字符串就需要传指针,因为指针不知道字符串的长度。而 Go 语言虽然看起来只是传递了数据结构,但底层还是会做展开。比如传入了一个数组,就会展开为:一个地址、一个长度 len、一个容量 cup,写的是一个参数,底层就展开为了三个参数。
启动线程代码 startm -> newm -> newosproc 中,newosproc 最终调用了系统调用 clone。
然后再看下不同操作系统下常见的系统调用
Types of System Calls | Windows | Linux |
---|---|---|
Process Control | CreateProcess()ExitProcess()WaitForSingleObject() | fork()exit()wait() |
File Management | CreateFile()ReadFile()WriteFile()CloseHandle() | open()read()write()close() |
Device Management | SetConsoleMode()ReadConsole()WriteConsole() | ioctl()read()write() |
Information Maintenance | GetCurrentProcessID()SetTimer()Sleep() | getpid()alarm()sleep() |
Communication | CreatePipe()CreateFileMapping()MapViewOfFile() | pipe()shmget()mmap() |
可以发现,不同操作系统下系统调用是不一样的,所以在不同操作系统上看到的也不一样。了解这些的意义也是在于,我们想要做超过自身能力意外的事情就必须求助于操作系统。
总结,了解系统调用后可以发现这个一个现象:我们的应用程序是被关在权限的监狱中,只能做很少的一些事情,其他的必须拜托操作系统来做。
下期预告:利用工具去观察系统调用。