Linux/Golang/glibC系統調用

Linux/Golang/glibC系統調用

本文主要通過分析Linux環境下Golang的系統調用,以此闡明整個流程

有時候涉略過多,反而遭到質疑~,寫點文章證明自己實力也好

Golang系統調用

找個函數來分析
https://pkg.go.dev/os/exec#Cmd.Wait

源碼文件在src/os目錄下的: exec.go -> exec_unix.go -> pidfd_linux.go
https://github.com/golang/go/blob/2f6426834c150c37cdb1330b48e9903963d4329c/src/os/exec.go#L134
go/src/os
/exec.go

go/src/os
/exec_unix.go

go/src/os
/pidfd_linux.go

往下是系統調用: src/syscall目錄的 syscall_linux.go -> ``
go/src/syscall
/syscall_linux.go

image

runtime層的:src/internal/runtime/syscall/syscall_linux.go,如下圖,可以看見Sysacll6只有聲明沒有函數體,是個外部聲明。
src/internal/runtime/syscall/syscall_linux.go

其函數體內容實際上位於同目錄下的 .s 彙編文件,與編譯時採用的架構工具鏈相關。

by the way: 這裏的語法是Golang彙編,屬於Plan9分支。
golang彙編參考資料:

  1. 官網資料 https://go.dev/doc/asm
  2. 簡潔概述 https://hopehook.com/post/golang_assembly/

總結:Golang直接了當地使用匯編實現了系統調用(軟中斷號),而不需要再通過 libc 去調用系統調用庫。這樣的好處是不需要考慮 glibc 繁雜沉重的兼容性方案。

Linux 定義的系統調用表

本地審計工具:ausyscall --dump

Linux系統調用

內核實現

通過軟中斷陷入內核態/特權模式
和STM32 ARM核心一樣,都是由一個異常向量表描述中斷對應的Handler地址,軟硬中斷也是一樣。
系統調用函數在 include/linux/syscalls.h中定義

我們拿asmlinkage long sys_openat(int dfd, const char __user *filename, int flags, umode_t mode); 來分析

這裏使用了彙編鏈接,它和上文提到的tbl系統調用表有關。我們拿x86/i386分析,arch/x86/entry/syscalls/syscall_32.tbl
32.tbl
中斷號 295 架構i386即傳統32位x86 sys_openat 是其回調函數/軟中斷Handler

linux/include/uapi/asm-generic/unistd.h

其實現位於 arch/處理器架構/include/之下
可以在 arch/x86 下搜索 openat

關於內核的系統調用這部分,本人會在再出一個文章。

Glibc 系統調用庫

注意:Glibc屬於庫,不屬於內核,是根文件系統的一部分。
我們在應用態陷入內核態,使用的c庫裏的open()等等函數,最後都是鏈式調用到了syscall()類的系統調用函數。

看glibc的源碼,就會發現彎彎繞繞,最後是調用到

作用是將參數寫入寄存器,讓SoC自己觸發軟中斷,根據Linux內核註冊的軟中斷號執行對應地址段的函數,也就是我們常在STM32裏註冊定義的中斷的handle函數。

Linux應用態到內核態例子

在線閱讀代碼:

  1. https://elixir.bootlin.com/glibc/glibc-2.29/source/include/errno.h#L37
  2. https://codebrowser.dev/glibc/glibc/io/read.c.html
  3. 帶了編譯產物的倉庫 https://github.com/bminor/glibc/tree/a81cdde1cb9d514fc8f014ddf21771c96ff2c182
    這些在線網站都不錯,但爲了高亮,所以我截圖放了github的

我們在應用層調用 系統庫的 fread()函數
其鏈接到glibc庫的 libio/iofread.c
image

其中第44行可見其爲 _IO_fread 聲明瞭weak弱鏈接別名 fread,有關別名表可見編譯產物如sysdeps/unix/syscalls.list
做了一些預操作之後,調用libio/libio.h 聲明的 libio/genops.c:_IO_sgetn
image
宏定義 libio/libioP.h
image
ps: JUMP2代表兩個參數

image
展開宏
image
展開宏
image

展開宏

image
展開宏
image

結構體
image

也就說調用了 FP.__xsgetn(FP, DATA, N) ,展開差不多是

struct _IO_FILE_plus *THIS;
THIS->vtable->__xsgetn; 即_IO_xsgetn_t類型函數指針

即THIS/file對象的函數地址 size_t __xsgetn (FILE *FP, void *DATA, size_t N);

初始化 _IO_jump_t 位於 JUMP_INIT
image

宏展開
image
函數定義
image

相關的計數器
image

再看另一個,我們常用的fopen
fopen

_IO_file_fopen

compat_symbol (libc, _IO_old_file_fopen, _IO_file_fopen, GLIBC_2_0);

_IO_old_file_fopen

#define __open open

這裏定向到了 open, 我們需要通過編譯產物 sysdeps/unix/syscalls.list 找到其鏈接段
image

可在 io/open.c 找到函數 __libc_open 位於
image
由於弱定義,所以被以下覆蓋 sysdeps/unix/sysv/linux/open.c

image
關鍵在於第43行的SYSCALL_CANCEL,其中的宏
image
展開宏INLINE_SYSCALL_CALL
image
展開宏
image
展開宏
image
展開宏
image

展開爲

//展開
__INLINE_SYSCALL4(openat, AT_FDCWD, file, oflag, mode)
//繼續展開爲
__INLINE_SYSCALL(openat, 4, AT_FDCWD, file, oflag, mode)

image

//x86
#define SYS_ify(syscall_name)	__NR_##syscall_name

#define INTERNAL_SYSCALL(name, nr, args...)				\
	internal_syscall##nr (SYS_ify (name), args)
//aarch64即 arm64
# define INTERNAL_SYSCALL_AARCH64(name, nr, args...)		\
	INTERNAL_SYSCALL_RAW(__ARM_NR_##name, nr, args)

x86的展開爲 internal_syscall4 (__NR_openat, AT_FDCWD, file, oflag, mode)
可在 sysdeps/unix/sysv/linux/sh/arch-syscall.h 找到,其中斷號爲 295
image

對應openat的x86/i386中斷號,剛好就是295,源碼分析完全正確!每種平臺的中斷號都不一樣,但是這樣分析是正確的。
體驗一下GNU宏地獄吧!

而ARM64的就高明得多,直接通過asm彙編指令寫寄存器跳轉執行__libc_do_syscall完成
image
glibc/sysdeps/unix/sysv/linux/arm/libc-do-syscall.S
image

總之是一個系統調用,等價於 openat(AT_FDCWD, file, oflag, mode);

總結:sysdeps是系統調用的實現,向上屏蔽細節,但是封裝的過程用於一堆條件宏,根本沒辦法用代碼分析工具,也難以調試。
對GNU LIBC代碼的個人拙見:
好處:節省空間,較好的運行速度。
壞處:作爲計算機世界的底層支持,這樣還不如在編譯器優化階段下功夫,過多的黑魔法必然寫出難以理解的代碼,牽一髮動全身,沒有人願意去改這堆瘋狂嵌套的代碼。作爲新興語言愛好者的我,始終認爲程序要少點黑魔法,簡潔直接纔是最優解,剩下的東西都應該交給編譯器,何況系統調用的耗時從來就不在這裏,主要性能影響都是在內核態用戶態切換的時候,並不在c庫本身。

GNU的代碼向來很難讀,glibc更是個寄吧,各種宏和硬鏈接亂飛,有再好的代碼閱讀工具也難找出來。
但要說來,說到底還是c語言/鏈接器的設計缺陷,沒辦法更好的實現動態表和靜態表。(多態組合、編譯期間的函數靜態段的多分支鏈接)

微軟的代碼是框架難以理解,因爲他們也不給出架構圖和代碼結構的...,,而GNU的代碼是宏和鏈接難以理解。

最後

請指正批評!感謝閱讀。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章