系统编程概念
系统调用
CPU状态切换
- 系统调用从用户态切换到核心态,使CPU能够访问受保护的内核内存
- 每个系统调用都由唯一一个数字来标识(man syscall 查看详细)
调用过程
- 调用C语言函数库的外壳函数(wrapper)函数,外壳函数必须保证所有的参数可用
- 内核希望将放入特定的寄存器,因此外壳函数会将参数复制到到寄存器
- 外壳函数将系统调用对应的编号复制到一个特殊的CPU寄存器(%eax)中
- 外壳函数执行一条中断机器指令(int 0x80),CPU从用户态切换到核心态,执行系统中断0x80的中断矢量所指向的代码
- 内核调用system_call()来处理本次中断
- 在内核栈中保存寄存器的值
- 检查系统调用编号的有效性
- 通过变量sys_call_table找到系统调用程序,检查参数的有效性,执行,结果返回给system_call
- 从内核中恢复各个寄存器的值,并将系统调用返回值放在栈中
- 返回到外壳函数,并同时将处理器切换回用户态
- 如果调用有错误,errno会设置一个错误码(linux下调用错误通常会返回一个负数,然后wrapper函数取反,将结果设置到errno,所以errno是正的)
errno
- 系统调用成功,errno绝对不会将其设置成0(假如前一个函数调用失败,后一个函数调用成功errno不会被设置成0)
- errno实际是和每一个线程关联的,各个线程的errno互相独立
系统调用的消耗比较大(一般不会是瓶颈,是逻辑代码写得很差)
调用一亿次,getppid时间消耗是普通返回数值的27倍数(所以一般调用得多可以第一次初始化后面直接用数值就行)
#include <unistd.h>
const int kMaxRepeatCount = 100000000;
int c_call() {
return 1;
}
int main() {
for (int i = 0; i < kMaxRepeatCount; ++i) {
//getppid();
c_call();
}
return 0;
}
[noexcept@fedora linux api]$ gcc system_call_vs_c_call.c -o sys_call
[noexcept@fedora linux api]$ gcc system_call_vs_c_call.c -o c_call
[noexcept@fedora linux api]$ time ./sys_call
real 0m5.003s
user 0m2.033s
sys 0m2.940s
[noexcept@fedora linux api]$ time ./c_call
real 0m0.181s
user 0m0.178s
sys 0m0.002s
[noexcept@fedora linux api]$
查看glibc版本(试了一下其他的so貌似不能直接执行的)
[noexcept@fedora linux api]$ /lib64/libc.so.6
GNU C Library (GNU libc) release release version 2.33.
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 11.1.1 20210531 (Red Hat 11.1.1-3).
libc ABIs: UNIQUE IFUNC ABSOLUTE
For bug reporting instructions, please see:
<https://www.gnu.org/software/libc/bugs.html>.