聽完了 shell 今天的分享又學了不少東西. 關於 syscall 方面, google 了一下, 目前瞭解
到的是
- syscall 成本很高
- syscall 在 x86 上是以 int 0x80 讓 kernel trap 從而切換 user space 到 kernel space
- 新的 CPU 提供了更高效的 syscall 執行方式, 但不向前兼容
- 部份 syscall 的執行, 可在 user space 完成
關於第 4 點很有意思. 一些被執行的頻率非常的高又不需要特別權限的簡單 syscall, 在 linux 上以 vDSO (Virtual Dynamic Shared Object) 的方式被實現. 這玩意是由 kernel 提供的, 在 build kernel source tree 時會一併產生, 會跟我們的程序 link 在一塊, 可在 build 過的 kernel source tree 下執行以下命令找到相應的 vDSO$ find arch/$ARCH/ -name '*vdso*.so*' -o -name '*gate*.so*'
通常是由如 glibc 來包裝并調用. 執行 ldd 時能看到程序是有跟它 link 在一塊的 (不同 HW platform 名字會有差異), 例如
$ ldd /bin/ls
linux-vdso.so.1 (0x00007fff205fc000)
libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007fd891751000)
libacl.so.1 => /lib/x86_64-linux-gnu/libacl.so.1 (0x00007fd891548000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd89119e000)
libpcre.so.3 => /lib/x86_64-linux-gnu/libpcre.so.3 (0x00007fd890f30000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fd890d2c000)
/lib64/ld-linux-x86-64.so.2 (0x00007fd89199e000)
libattr.so.1 => /lib/x86_64-linux-gnu/libattr.so.1 (0x00007fd890b26000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fd890909000)
程序在啟動時, 有部份 kernel page 會以 read only 的方式被 map 到 user space, 這樣 vDSO 就可以不做 context switch 的情況下讀取到 kernel 中的資料完成如 gettimeofday(), getpid() 等 syscall.
實驗 1
$ cat test.c
int main()
{
int i = 0;
for(; i < 1000000000; i ++) {}
return 0;
}
Compile & run
$ gcc -o test test.c && time ./test
real 0m2.127s
user 0m2.104s
sys 0m0.000s
實驗 2
$ cat test2.c
#include <sys/types.h>
#include <unistd.h>
int main()
{
int i = 0;
for(; i < 1000000000; i ++) {
getpid();
}
return 0;
}
Compile & run
$ gcc -o test2 test2.c && time ./test2
real 0m3.499s
user 0m3.464s
sys 0m0.000s
$ perf record -F 371 ./test
$ perf report
60.41% test test [.] main
9.01% test test [.] getpid@plt
調用了 getpid() 這個 syscall 但時間并沒有花在 kernel space 中.
實驗 3
$ cat test3.c
#include <sys/types.h>
#include <unistd.h>
int main()
{
int i = 0;
for(; i < 100000000; i ++) {
close(0);
}
return 0;
}
Compile & run
$ gcc -o test3 test3.c && time ./test3
real 0m59.033s
user 0m19.532s
sys 0m39.524s
close() 想必會更新 kernel 中的 data structure, 用 vDSO 實現應該會很危險, 必須在 kernel space 中完成.
主要 CPU 時間花用的部份
$ perf record -F 371 ./test3
$ perf report
21.32% test [kernel.kallsyms] [k] system_call
16.61% test [kernel.kallsyms] [k] system_call_after_swapgs
14.83% test [kernel.kallsyms] [k] _raw_spin_lock
10.00% test [kernel.kallsyms] [k] __close_fd
3.56% test [kernel.kallsyms] [k] sysret_check
2.94% test [kernel.kallsyms] [k] sys_close
0.61% test test [.] close@plt
0.60% test [kernel.kallsyms] [k] ret_from_sys_call
0.59% test test [.] main
28.27+0.61+0.59=29.47% 是花在 user space 中, 21.32+16.61+14.83+10+3.56+2.94=69.26% 花在 kernel space 中.
參考資料