系统调用的过程
来自APUE和TLPI读书笔记
一个系统调用是怎么发生的呢?典型步骤如下:
调用标准库中的函数,例如C语言中,申请内存时,会使用如下代码:
#include <stdlib.h> #include <string.h> #include <stdio.h> int main() { char *s = malloc(sizeof("hello")); printf("s -> %s\n", s); memcpy(s, "world", 5); printf("s -> %s\n", s); free(s); }
其中 malloc
就是一个标准库中的函数,它其实是一个系统调用的wrapper。
malloc
所包裹的系统调用函数(Linux中一般是sbrk
)不一定每一次都会被调用,因为通常来说,为了提高内存分配的性能, 标准库里的wrapper函数会对所申请的内存进行缓存。此外,内核所提供的系统调用,也会规定好以一种什么样的方式进行调用, 哪个参数该放哪个寄存器上等等。标准库里的函数负责把这些准备好。由于所有的系统调用都是以同一种方式进入内核,而每个系统调用都有一个编号,这个编号是内核用来分辨是哪个系统调用用的, wrapper函数把系统调用的编号copy到寄存器
%eax
。wrapper函数执行一个
trap(一般翻译为中断)
进入内核模式,比如以前的Linux中,一般是执行int 0x80
,当然现在的CPU 有一个更高效的方式。当进入内核态之后,操作系统完成这么几件事情:
- 保存当前的寄存器里的值
- 检查系统调用编号是否正确
- 执行系统调用的代码
- 执行完成之后恢复寄存器的值
- 返回到用户态的wrapper函数中,所执行系统调用的地方的代码指令处
wrapper函数对系统调用返回的结果进行检查,如果出错了,就设置好
errno
这个值。
这就是一个典型的C程序进行系统调用的过程,其他语言也是类似,只不过所使用的标准库函数不一定同名,系统调用返回之后的
处理方式也未必相同(比如未必是使用 errno
而是抛异常等)。
更多文章
本站热门
- socks5 协议详解
- zerotier简明教程
- 搞定面试中的系统设计题
- frp 源码阅读与分析(一):流程和概念
- 用peewee代替SQLAlchemy
- Golang(Go语言)中实现典型的fork调用
- DNSCrypt简明教程
- 一个Gunicorn worker数量引发的血案
- Golang validator使用教程
- Docker组件介绍(二):shim, docker-init和docker-proxy
- Docker组件介绍(一):runc和containerd
- 使用Go语言实现一个异步任务框架
- 协程(coroutine)简介 - 什么是协程?
- SQLAlchemy简明教程
- Go Module 简明教程