系统编程概念

系统调用

CPU状态切换
  • 系统调用从用户态切换到核心态,使CPU能够访问受保护的内核内存
  • 每个系统调用都由唯一一个数字来标识(man syscall 查看详细)
调用过程
  1. 调用C语言函数库的外壳函数(wrapper)函数,外壳函数必须保证所有的参数可用
  2. 内核希望将放入特定的寄存器,因此外壳函数会将参数复制到到寄存器
  3. 外壳函数将系统调用对应的编号复制到一个特殊的CPU寄存器(%eax)中
  4. 外壳函数执行一条中断机器指令(int 0x80),CPU从用户态切换到核心态,执行系统中断0x80的中断矢量所指向的代码
  5. 内核调用system_call()来处理本次中断
    • 在内核栈中保存寄存器的值
    • 检查系统调用编号的有效性
    • 通过变量sys_call_table找到系统调用程序,检查参数的有效性,执行,结果返回给system_call
    • 从内核中恢复各个寄存器的值,并将系统调用返回值放在栈中
    • 返回到外壳函数,并同时将处理器切换回用户态
  6. 如果调用有错误,errno会设置一个错误码(linux下调用错误通常会返回一个负数,然后wrapper函数取反,将结果设置到errno,所以errno是正的)
errno
  1. 系统调用成功,errno绝对不会将其设置成0(假如前一个函数调用失败,后一个函数调用成功errno不会被设置成0)
  2. 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>.
posted @ 2021-10-17 21:56  zhaowenwei  阅读(18)  评论(0)    收藏  举报