1. 什么是 vDSO
眾所周知,操作系統為我們管理硬件資源,并以系統調用的方式對用戶進程提供 API,但是syscall很慢,涉及陷入內核以及上下文切換。對于少量頻繁調用的系統調用(比如獲取當期系統時間)來說,是否可以某種安全的方式開放到用戶空間,讓用戶直接訪問而不需要經過syscall呢?
vDSO就是用來解決這個問題的。
vDSO全稱為virtual dynamic shared object,dynamic shared object 這個名詞大家應該有所耳聞,就是 Linux 下的動態庫的全稱,而 virtual 表明,這個動態庫是通過某種手段虛擬出來的,并不真正存在于 Linux 文件系統中。
要驗證這點也很簡單,只需要通過 ldd 命令,查看一些可執行文件所依賴的動態庫即可,
$ldd/bin/ls linux-vdso.so.1(0x00007ffe4e4ce000) libcap.so.2=>/usr/lib/libcap.so.2(0x00007f7bf818e000) libc.so.6=>/usr/lib/libc.so.6(0x00007f7bf7fc2000) /lib64/ld-linux-x86-64.so.2=>/usr/lib64/ld-linux-x86-64.so.2(0x00007f7bf81e8000)
可以明顯看出,在ls 這個可執行文件依賴的動態庫列表中,除了 linux-vdso.so.1 都有明確的路徑,同時還可以通過 proc 文件系統中進程的內存映射(memory map)情況來映射這一點:
$cat/proc/1/maps .... 7fd37e90f000-7fd37e911000rw-p0002f000103:0213244335/usr/lib/ld-2.33.so 7ffc2f7ce000-7ffc2f7ef000rw-p0000000000:000[stack] 7ffc2f7f7000-7ffc2f7fb000r--p0000000000:000[vvar] 7ffc2f7fb000-7ffc2f7fd000r-xp0000000000:000[vdso] ffffffffff600000-ffffffffff601000--xp0000000000:000[vsyscall]
可以看出,vDSO 確實是以共享庫的形式存在于每一個進程當中的。
通過 vDSO,進程訪問一些系統提供的 API,就可以直接在自己的地址空間訪問,而不需要進行用戶-內核態的狀態切換了
2. vDSO 實現原理
linux-vdso.so.1既然不是一個實實在在的文件,那其中的內容就應該直接保存在內存中,Linux 使用vdso_image來表示
2.1 vDSO image
在arch/x86/entyr/vdso/vdso-image-64.c文件中,定義了下面的vdso_image:
staticunsignedcharraw_data[8192]__ro_after_init__aligned(PAGE_SIZE)={ 0x7F,0x45,0x4C,0x46,0x02,0x01,0x01,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x3E,0x00, ... }; conststructvdso_imagevdso_image_64={ .data=raw_data, .size=8192, .alt=3013, .alt_len=91, .sym_vvar_start=-16384, .sym_vvar_page=-16384, .sym_pvclock_page=-12288, .sym_hvclock_page=-8192, .sym_timens_page=-4096, };
vdso_image.raw_data對應的就是 vDSO 提供的所有系統調用的二進制指令,一共有 8192 字節,相當于下面的結構:
staticstructpage*pages[2];
vdso_iamge_64自然需要保存到全局變量中才能發揮作用,這就涉及接下來要提到的 vDSO 初始化。
2.2 vDSO 初始化
vDSO 通過init_vdso()函數來初始化,通過條件編譯對 32/64 bit 的 image 進行選擇。同時也需要通過subsys_initcall(init_vdso)將init_vdso()放到initcall列表中。
init_vdso_image()這里不過多介紹,主要是用來優化指令,畢竟 vdso_image 中提供的二進制指令是手動放在一個數組中的,還有相當大的優化空間
staticint__initinit_vdso(void) { BUILD_BUG_ON(VDSO_CLOCKMODE_MAX>=32); init_vdso_image(&vdso_image_64); #ifdefCONFIG_X86_X32_ABI init_vdso_image(&vdso_image_x32); #endif return0; } subsys_initcall(init_vdso);
2.3 vDSO 和 可執行程序
如果你對 Linux 可執行程序的 加載-執行機制有所研究,就知道對于 elf 格式的可執行程序而言,最終調用了load_elf_binary()這個回調函數,在這個函數中,會根據 elf 文件頭中的描述,設置好新進程的各個段,并將 elf 文件中的內容拷貝到相應位置。
為什么好端端的,要提到可執行程序加載呢?這是因為,在系統初始化完成之后,vdso_image已經設置完畢,只需要在每次加載二進制可執行程序的時候,分配一塊內存空間,將vdso_image加載到該位置即可。
這就是arch_setup_additional_pages()函數所要完成的任務了:
intarch_setup_additional_pages(structlinux_binprm*bprm,intuses_interp) { if(!vdso64_enabled) return0; returnmap_vdso_randomized(&vdso_image_64); }
map_vdso_randomized()會通過stack protect機制,選擇一個隨機的加載地址,并調用map_vdso完成 mapping 工作,該函數內容較多,這里不贅述。
最終,vDSO 會向用戶提供四個系統調用:
__vdso_clock_gettime() __vdso_getcpu() __vdso_gettimeofday() __vdso_time()
你還別不信,可以自行驗證一下:
使用命令cat /proc/1/maps找到[vdso]對應的內存位置。
通過 dd 命令將內存的影像 dump 到文件中,如:dd if=/proc/1/mem of=/tmp/linux-vdso.so skip=140728627781632 ibs=1 count=4096,其中 skip 的值為 vdso 的內存起始地址,count 為這塊內存的大小。
使用objdump命令查看linux-vdso.so中所有符號objdump -T /tmp/linux-vdso.so,最終結果如下。
linux-vdso.so:fileformatelf64-x86-64 DYNAMICSYMBOLTABLE: 0000000000000740wDF.text000000000000015dLINUX_2.6clock_gettime 0000000000000600gDF.text0000000000000127LINUX_2.6__vdso_gettimeofday 00000000000008a0wDF.text0000000000000044LINUX_2.6clock_getres 00000000000008a0gDF.text0000000000000044LINUX_2.6__vdso_clock_getres 0000000000000600wDF.text0000000000000127LINUX_2.6gettimeofday 0000000000000730gDF.text0000000000000010LINUX_2.6__vdso_time 0000000000000730wDF.text0000000000000010LINUX_2.6time 0000000000000740gDF.text000000000000015dLINUX_2.6__vdso_clock_gettime 0000000000000000gDO*ABS*0000000000000000LINUX_2.6LINUX_2.6 00000000000008f0gDF.text0000000000000025LINUX_2.6__vdso_getcpu 00000000000008f0wDF.text0000000000000025LINUX_2.6getcpu
-
Linux
+關注
關注
87文章
11320瀏覽量
209845 -
操作系統
+關注
關注
37文章
6850瀏覽量
123429 -
API
+關注
關注
2文章
1505瀏覽量
62168 -
命令
+關注
關注
5文章
688瀏覽量
22056
原文標題:細說|vDSO機制原理
文章出處:【微信號:LinuxHub,微信公眾號:Linux愛好者】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論