參考引用
- Unix和Linux操作系統有什么區別?
- 一文帶你徹底搞懂posix
Linux系統編程(文章鏈接匯總)
1. Unix 和 Linux
1.1 Unix
- Unix 操作系統誕生于 1969 年,貝爾實驗室發布了一個用 C 語言編寫的名為「Unix」的操作系統,該系統可以更快地修改、調整與移植適配
- Unix 操作系統主要在 CLI(命令行界面)上工作
- Unix 的個人用戶不多,主要使用集中在商業領域
1.2 Linux
- 1991 年,芬蘭大學生 Linus Torvalds 開發了一個自由的內核(Linux 這個名字來自于其使用的內核名稱)
- 1992 年,把 Linux 和 GNU 系統結合起來,就構成一個完整的操作系統:基于 Linux 的 GNU 系統(GNU/linux)
- Linux 并沒有使用 Unix 的源碼,而是按照公開的 POSIX 標準重新編寫
1.3 二者對比
- Linux 思想源于 Unix,以 Unix 為原型開發的
- Linux 完全開源,Unix 是版權專有
- Linux 狹義上是指 GNU/Linux 操作系統使用的內核,廣義上講,是一種「類Unix」操作系統;Unix 狹義上是指貝爾實驗室最初開發的一整套操作系統,廣義上講是屬于符合「Unix」標準的一類操作系統
- Linux 的默認 shell 是 BASH;而 Unix 用的是 Bourne shell
1.4 其他
- Linux(不包含 GNU 組件) 是 Android(2007.11) 的內核
- 蘋果使用 UNIX 作為 iOS(2007.1) 和 macOS 的內核
2. Ubuntu 和 Linux
- Linux 是一個開源的操作系統內核(一個 640KB文件,在 /Boot 目錄下),而 Ubuntu 是基于 Linux 內核開發的一個完整的操作系統,具體而言,Ubuntu 是基于 Debian 發行版的 Linux 操作系統
3. POSIX 標準
3.1 定義
- POSIX(Portable Operating System Interface,可移植操作系統接口)
- POSIX 是 IEEE 為了在各種 UNIX 操作系統上運行的軟件而定義的一系列 API 標準的總稱
- POSIX.1 已被國際標準化組織(International Standards Organization,ISO)接受,命名:ISO/IEC 9945-1:1990
3.2 歷史
- 為了提高兼容性和應用程序的可移植性,IEEE 開始標準化 Unix 的開發,由 Richard Stallman 命名為 POSIX,這套標準涵蓋了很多方面,比如 Unix 系統調用的 C 語言接口、shell 程序和工具、線程及網絡編程
- Unix 和 Linux 均遵循 POSIX 標準
- 有了這個規范,就可以調用通用的 API,Linux 提供的 POSIX 系統調用在 Unix 上也能執行
4. 系統調用和庫函數
- Linux 下對文件操作有兩種方式:系統調用(system call)和庫函數(Library functions)
4.1 系統調用
- 系統調用是 Linux 內核提供給應用層的應用編程接口(API),是 Linux 應用層進入內核的入口,內核提供了一系列服務、資源、支持等,應用程序通過系統調用 API 函數來使用內核提供的服務、資源以及各種各樣的功能
- 通過系統調用 API 函數,應用層可以實現與內核的交互,其關系可通過下圖簡單描述
Linux 應用編程(系統編程)與裸機編程、驅動編程有什么區別?
- 1. 裸機編程:一般把沒有操作系統支持的編程環境稱為裸機編程環境,如:單片機上的編程開發,編寫直接在硬件上運行的程序,沒有操作系統支持
- 2. 驅動編程:狹義上 Linux 驅動編程指的是基于內核驅動框架開發驅動程序,驅動開發工程師通過調用 Linux 內核提供的接口完成設備驅動的注冊,驅動程序負責底層硬件操作相關邏輯
- 3. 應用編程:基于 Linux 操作系統的應用編程,在應用程序中通過調用系統調用 API 完成應用程序的功能和邏輯,應用程序運行于操作系統之上。通常在操作系統下有兩種不同的狀態:內核態和用戶態,應用程序運行在用戶態,而內核則運行在內核態
4.2 庫函數
- 庫函數(Library function)是把函數放到庫里,供別人使用的一種方式,庫函數調用面向應用開發,可分為兩類
- 一類是 C 語言標準規定的庫函數
- 一類是編譯器特定的庫函數
- C 語言庫是應用層使用的一套函數庫,在 Linux 下通常以動態庫文件(.so)形式提供,存放在根文件系統 /lib/x86_64-linux-gnu 目錄,命名方式通常是 libc.so.6,不過這是一個軟鏈接文件,它會鏈接到真正的庫文件,C 語言庫函數構建于系統調用之上,也就是說大部分庫函數其實是由系統調用封裝而來
- 有些庫函數并不調用任何系統調用,如:字符串處理函數 strlen()、strcat()、memcpy()、memset()、strchr()等
- 有些庫函數會使用系統調用來完成實際操作,如:庫函數 fopen 內部調用了系統調用 open() 來幫它打開文件、庫函數 fread() 就利用了系統調用 read() 來完成讀文件操作、fwrite()就利用了系統調用 write()來完成寫文件操作
為什么需要庫函數?
- 有些系統調用使用起來并不是很方便,于是就出現了 C 語言庫,這些 C 語言庫函數的設計是為了提供比底層系統調用更為方便、更為好用、且更具有可移植性的調用接口
4.3 標準 C 語言函數庫
- 在 Linux 系統下,使用的 C 語言庫為 GNU C 語言函數庫(也叫 glibc),作為 Linux 下的標準 C 語言函數庫。glibc 為程序員提供豐富的 API,這些 API 的函數名、返回值和參數類型等都必須按照 POSIX 標準來定義
- 確定 Linux 系統的 glibc 版本
$ cd /lib/x86_64-linux-gnu $ ls -l libc.so.6 # libc.so.6 軟鏈接到 libc-2.27.so 庫文件,2.27 便是 Linux 系統的 glibc 版本 lrwxrwxrwx 1 root root 12 5月 3 2022 libc.so.6 -> libc-2.27.so
4.4 二者對比
- 庫函數屬于應用層,而系統調用是內核提供給應用層的編程接口,屬于系統內核的一部分
- 庫函數運行在用戶空間,調用系統調用會由用戶空間(用戶態)陷入到內核空間(內核態)
- 庫函數通常是有緩存的,而系統調用是無緩存的,所以在性能、效率上,庫函數通常要優于系統調用
- 庫函數調用屬于過程調用,運行時間為用戶時間,開銷較小
- 系統調用需要在用戶態和內核態之間切換,運行時間為系統時間,開銷較大
- 可移植性:庫函數相比于系統調用具有更好的可移植性,通常對于不同的操作系統,其內核向應用層提供的系統調用往往都是不同的,而對于 C 語言庫函數來說,由于很多操作系統都實現了 C 語言庫,C 語言庫在不同的操作系統之間其接口定義幾乎是一樣的,所以庫函數在不同操作系統之間相比于系統調用具有更好的可移植性
4.5 系統調用流程
- 當應用程序調用 printf() 函數時,printf() 函數會調用 C 庫中的 printf(),繼而調用 C 庫中的 write(),C 庫最后調用內核的 write()
- printf() 函數執行過程中,程序運行狀態切換如下
用戶態–>系統調用–>內核態–>返回用戶態
-
printf() 函數、glibc 庫和系統調用在系統中關系圖如下
-
實例
// test.c #include <stdio.h>int main(int argc, char **argv) {printf("Hello world!"); return 0; }
$ gcc test.c -o test $ strace test # 運行程序前加上 strace,可以追蹤庫函數調用過程 execve("./test3", ["./test3"], 0x7fffb2493520 /* 82 vars */) = 0 brk(NULL) = 0x560a8c38b000 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/opt/ros/melodic/lib/tls/x86_64/x86_64/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) stat("/opt/ros/melodic/lib/tls/x86_64/x86_64", 0x7ffff162afb0) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/opt/ros/melodic/lib/tls/x86_64/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) stat("/opt/ros/melodic/lib/tls/x86_64", 0x7ffff162afb0) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/opt/ros/melodic/lib/tls/x86_64/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) stat("/opt/ros/melodic/lib/tls/x86_64", 0x7ffff162afb0) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/opt/ros/melodic/lib/tls/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) stat("/opt/ros/melodic/lib/tls", 0x7ffff162afb0) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/opt/ros/melodic/lib/x86_64/x86_64/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) stat("/opt/ros/melodic/lib/x86_64/x86_64", 0x7ffff162afb0) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/opt/ros/melodic/lib/x86_64/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) stat("/opt/ros/melodic/lib/x86_64", 0x7ffff162afb0) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/opt/ros/melodic/lib/x86_64/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) stat("/opt/ros/melodic/lib/x86_64", 0x7ffff162afb0) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/opt/ros/melodic/lib/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) stat("/opt/ros/melodic/lib", {st_mode=S_IFDIR|0755, st_size=16384, ...}) = 0 openat(AT_FDCWD, "/home/yue/PX4-Autopilot/build/px4_sitl_default/build_gazebo/tls/x86_64/x86_64/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) stat("/home/yue/PX4-Autopilot/build/px4_sitl_default/build_gazebo/tls/x86_64/x86_64", 0x7ffff162afb0) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/home/yue/PX4-Autopilot/build/px4_sitl_default/build_gazebo/tls/x86_64/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) stat("/home/yue/PX4-Autopilot/build/px4_sitl_default/build_gazebo/tls/x86_64", 0x7ffff162afb0) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/home/yue/PX4-Autopilot/build/px4_sitl_default/build_gazebo/tls/x86_64/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) stat("/home/yue/PX4-Autopilot/build/px4_sitl_default/build_gazebo/tls/x86_64", 0x7ffff162afb0) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/home/yue/PX4-Autopilot/build/px4_sitl_default/build_gazebo/tls/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) stat("/home/yue/PX4-Autopilot/build/px4_sitl_default/build_gazebo/tls", 0x7ffff162afb0) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/home/yue/PX4-Autopilot/build/px4_sitl_default/build_gazebo/x86_64/x86_64/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) stat("/home/yue/PX4-Autopilot/build/px4_sitl_default/build_gazebo/x86_64/x86_64", 0x7ffff162afb0) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/home/yue/PX4-Autopilot/build/px4_sitl_default/build_gazebo/x86_64/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) stat("/home/yue/PX4-Autopilot/build/px4_sitl_default/build_gazebo/x86_64", 0x7ffff162afb0) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/home/yue/PX4-Autopilot/build/px4_sitl_default/build_gazebo/x86_64/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) stat("/home/yue/PX4-Autopilot/build/px4_sitl_default/build_gazebo/x86_64", 0x7ffff162afb0) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/home/yue/PX4-Autopilot/build/px4_sitl_default/build_gazebo/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) stat("/home/yue/PX4-Autopilot/build/px4_sitl_default/build_gazebo", {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0 openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=274620, ...}) = 0 mmap(NULL, 274620, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f2f5e388000 close(3) = 0 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/lib/x86_64-linux-gnu/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\240\35\2\0\0\0\0\0"..., 832) = 832 fstat(3, {st_mode=S_IFREG|0755, st_size=2030928, ...}) = 0 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f2f5e386000 mmap(NULL, 4131552, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f2f5ddb2000 mprotect(0x7f2f5df99000, 2097152, PROT_NONE) = 0 mmap(0x7f2f5e199000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7f2f5e199000 mmap(0x7f2f5e19f000, 15072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f2f5e19f000 close(3) = 0 arch_prctl(ARCH_SET_FS, 0x7f2f5e3874c0) = 0 mprotect(0x7f2f5e199000, 16384, PROT_READ) = 0 mprotect(0x560a8c066000, 4096, PROT_READ) = 0 mprotect(0x7f2f5e3cc000, 4096, PROT_READ) = 0 munmap(0x7f2f5e388000, 274620) = 0 fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0 brk(NULL) = 0x560a8c38b000 brk(0x560a8c3ac000) = 0x560a8c3ac000 write(1, "Hello world!", 12Hello world!) = 12 exit_group(0) = ? +++ exited with 0 +++
-
程序雖然只有一個printf函數,但在執行過程中前后調用了 execve、access、open、fstat、mmap、brk、write 等系統調用。其中 write 系統調用會把字符串 Hello world! 通過設備文件 1 發送到驅動,該設備節點對應終端 stdout
$ ls /dev/std* -l lrwxrwxrwx 1 root root 15 12月 11 21:10 /dev/stderr -> /proc/self/fd/2 lrwxrwxrwx 1 root root 15 12月 11 21:10 /dev/stdin -> /proc/self/fd/0 lrwxrwxrwx 1 root root 15 12月 11 21:10 /dev/stdout -> /proc/self/fd/1