Skip to main content

《Linux是怎样工作的》系统调用

Published 2 min readMarkdown

执行创建线程和操作硬件的时候依赖于系统调用,想要操作需要提前调用内核发送请求。

系统调用可以分为以下几类

  • 进程控制(创建和删除)

  • 内存管理(分配和释放)

  • 进程间通信

  • 网络管理

  • 文件访问(访问设备)

CPU 模式切换

系统会发送特殊的 CPU 命令发起。在进程中的用户模式下,当通过系统调用向内核发送请求,CPU 会发生一个中断事件,这个时候切换为内核状态,根据请求的内容进行处理,当内核请求完之后就会重新回到用户模式。并且这个 CPU 信号不是所有的系统都一样的

注意:用户模式不可能直接绕过内核进行系统调用

image.png

测试 Hello World 程序发生的系统调用

#include <stdio.h>
int main(void)
{
    puts("hello world");
    return 0;
}

使用 strace 命令的输出结果

execve("./hello", ["./hello"], 0x7ffc9e25b5f0 /* 23 vars */) = 0
brk(NULL)                               = 0x24f6000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9de0d1d000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=30274, ...}) = 0
mmap(NULL, 30274, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f9de0d15000
close(3)                                = 0
open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0`&\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2156592, ...}) = 0
mmap(NULL, 3985920, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f9de072f000
mprotect(0x7f9de08f3000, 2093056, PROT_NONE) = 0
mmap(0x7f9de0af2000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c3000) = 0x7f9de0af2000
mmap(0x7f9de0af8000, 16896, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f9de0af8000
close(3)                                = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9de0d14000
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9de0d12000
arch_prctl(ARCH_SET_FS, 0x7f9de0d12740) = 0
access("/etc/sysconfig/strcasecmp-nonascii", F_OK) = -1 ENOENT (No such file or directory)
access("/etc/sysconfig/strcasecmp-nonascii", F_OK) = -1 ENOENT (No such file or directory)
mprotect(0x7f9de0af2000, 16384, PROT_READ) = 0
mprotect(0x600000, 4096, PROT_READ)     = 0
mprotect(0x7f9de0d1e000, 4096, PROT_READ) = 0
munmap(0x7f9de0d15000, 30274)           = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9de0d1c000
write(1, "hello world\n", 12)           = 12
exit_group(0)                           = ?
+++ exited with 0 +++

sar 命令,可以查看用户模式和内核模式下运行的时间比例

完整用法:man sar

image.png

只有用户模式程序

我们测试一个只有用户模式的程序

loop.c

int main(void)
{
    for(;;)
        ;
}

进行编译

cc -o loop loop.c

运行 loop

./loop &

image.png

查看调用情况

sar -P ALL 1

可以看到几乎没有系统调用而是一直是用户模式

image.png

调用示意图

image.png

既有用户模式也有系统模式

下面测试一个 ppidloop.c

#include <sys/types.h>
#include <unistd.h>

int main(void)
{
    for(;;)
        getppid();
}

注释:getppid() 是获取父线程的进程 ID

使用下面的命令可以获取到 1s 时间内运行的运行时间比例

sar -P ALL 1

image.png

  • 在 CPU1 核心上面运行运行 getppid 占用了 59% 的时间

  • 根据 ppidloop 程序发出的请求来获取父进程的进程 ID 这一内核处理占用了 41% 的运行时间

为什么 %system 不是 100% ? 因为用于循环执行 main() 函数内的 getppid() 的循环处理,是属于进程自身的处理

流程示意图

下面是直接用的书上面的示意图,根据我的实际情况需要改一下 用户模式:内核模式 = 59:41

image.png

一般 %system 的值高达好几十的时候一般是因为陷入系统调用,或者是服务器负载过高

如何确认系统调用的确时间?

可以使用 strace 命令后面加上一个 -T 的选项实现

比如我们测试一下 helloworld.c

strace -T -o hello.log ./hello

这个后面的 <0.000040> 就是耗时,单位是微秒

image.png

No comments yet. Be the first to comment.

More from this blog