特此說明: 劉超的趣談linux作業系統是比較重要的參考資料,本文大部分内容和所有圖檔來源于這個專欄。
相關概念
程式vs程序vs指令: Linux系統上所有的操作由
程序
完成,
程序
的運作是動态的,在此之前是一個靜态的
程式
。使用者用一個程式來啟動一個程序,這個程式可以是别人寫好的(最終被編譯成可執行檔案),比如
ls
、
pwd
cat
,也可以是我們自己寫的。
系統調用: 無論如何,程式最後運作起來都是程序,并且一個程式想要在系統上跑,要用到
系統調用
,這是系統給使用者提供的API接口。
trace指令: Linux有個指令strace,常用來跟蹤程序執行時系統調用和所接收的信号。通過
manstrace
檢視具體描述。
Glibc: 作為一個開發者,也許平時并沒有直接使用系統調用,而是Glibc庫。Glibc是Linux下使用的開源的标準C庫它是GNU釋出的libc庫。
Glibc
即系統調用的封裝。
介紹系統調用
然後本文開始介紹這些系統調用,先上圖
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLi0zaHRGcWdUYuVzVa9GczoVdG1mWfVGc5RHLwkzX39GZhh2csATMflHLwEzX4xSZz91ZsADMx8FdsYkRGZkRG9lcvx2bjxSa2EWNhJTW1AlUxEFeVRUUfRHelRHL2EzXlpXazxyayFWbyVGdhd3LcV2Zh1Wa9M3clN2byBXLzN3btg3PnVGcq5yMzEDZ0YWNjhTN4UmNiFTMlVGO1QjZkdzY3UDN3AzMw8CX4IzLcZDMxIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjL4M3Lc9CX6MHc0RHaiojIsJye.jpeg)
程序管理
linux作業系統使用
fork
從父程序中建立子程序,子程序
execve
運作程式(二進制檔案),父程序
waitpid
等待子程序結束。
所有程序都是父程序
fork
出來的,對于作業系統而言第一個鼻祖程序是哪來的呢? 系統啟動的時候先建立一個所有使用者程序的“祖宗程序”。
記憶體管理
在作業系統中,每個程序都有自己的
程序記憶體空間
。其中布局就有
代碼段
資料段
堆
。
一個程序的記憶體空間是很大的,32位的是4G,64位的就更大了。實體空間是有限的,是以程序的空間不能事先配置設定好的,一定是需要的時候再配置設定。
brk
和
mmap
是官員堆配置設定記憶體的系統調用,配置設定記憶體數量比較小的時候,使用
brk
會和原來的堆的資料連在一起。當配置設定的記憶體數量比較大的時候,使用mmap,會重新劃分一塊區域。
檔案管理
檔案系統相當于公司的資料庫,用于儲存一些永久性質的資料。能做到長期儲存,檔案之是以能做到這一點,一方面是因為
媒體
,另一方面是因為
格式
對于檔案的操作,無非是
建立creat
打開open
讀read
寫write
等等
Linux中一切皆檔案
就包括
二進制檔案
文本檔案
stdout檔案
Socket檔案
裝置檔案
目錄檔案
,包括程序運作起來在
/proc
下生成的程序号也是檔案。對于每一個檔案,Linux配置設定了
檔案描述符
,這是一個整數。
信号處理
信号是異步處理機制,用于緊急突發情況。每一種信号都有預設動作,當然使用者也能編寫信号處理函數,通過
sigaction
系統調用進行處理。
程序間通信
本地程序之間實作資料的互通,比較常見的處理機制有
消息隊列
共享記憶體
通過
msgget
建立一個新的隊列,
msgsnd
将消息發送到消息隊列,而消息接收方可以使用
msgrcv
從隊列中取消息; 我們可以通過
shmget
建立一個共享記憶體塊,通過
shmat
将共享記憶體映射到自己的記憶體空間,然後就可以讀寫了。
網絡通信
核心中有TCP/IP網絡協定棧的實作,可以通過socket來實作跨系統的程序間通信。
檢視源碼
下載下傳核心源碼,找到
./include/asm-x86_64/unistd.h
檔案,裡面對于系統調用的定義
hinzer@ubuntu:linux-2.6.11$ head ./include/asm-x86_64/unistd.h
#ifndef _ASM_X86_64_UNISTD_H_
#define _ASM_X86_64_UNISTD_H_
#ifndef __SYSCALL
#define __SYSCALL(a,b)
#endif
/*
* This file contains the system call numbers.
*
檢視過程
程序通過系統調用從使用者态到核心态,
使用者态 - 系統調用 - 儲存寄存器 - 核心态執行系統調用 - 恢複寄存器 - 傳回使用者态
。對于應用開發,上層還通過
glibc
庫(對系統調用的一個封裝庫)。
從glibc提供的open函數出發,剖析如何從glibc的調用到核心的
open
!!!
sys_open
glibc封裝
# 以下相關檔案
./sysdeps/unix/syscalls.list # 列出所有glibc的函數對應的系統調用
./sysdeps/unix/make-syscalls.sh # 根據上面的配置檔案,對于每一個封裝好的系統調用,生成一個檔案
./sysdeps/unix/syscall-template.S # 定義了這個系統調用的調用方式
./sysdeps/hppa/sysdep.h # 通過 `vim -t PSEUDO` 找到 PSEUDO 這個宏的定義。
經過分析: open函數的代碼邏輯,得出結論: 對于任何的系統調用,會調用
DO_CALL
。這也是一個宏,這個宏 32 位和 64 位的定義是不一樣的。
32位系統調用過程
繼續分析glibc源碼,發現宏
DO_CALL
定義處
unix/sysv/linux/i386/sysdep.h
了解: 使用者調用通過軟中斷進入核心态,在系統調用表中找到對應的核心系統調用,執行核心調用之前儲存使用者态寄存器,執行核心調用後傳回并恢複使用者态。
64位系統調用過程
DO_CALL定義在源碼位置
unix/sysv/linux/x86_64/sysdep.h
,還是将系統調用名稱轉換為系統調用号,放到寄存器 rax。和32位不同的是,這裡是真正進行調用,不是用中斷了,而是改用 syscall 指令了,而且傳遞參數的寄存器也變了。
了解: 使用者調用通過syscall指令進入核心态,在系統調用表中找到對應的核心系統調用,執行核心調用之前儲存使用者态寄存器,執行核心調用後傳回并恢複使用者态。
整個流程
補充
系統調用表
資料結構定義在
arch/x86/entry/syscall_64.c
,系統調用清單輸出在
arch/x86/entry/syscalls/syscall_64.tbl
#系統調用号 abi類型 函數名 系統調用名
2 common open sys_open
系統調用函數聲明
系統調用函數實作
編譯規則
參考資料
- 趣談Linux作業系統 - 05系統調用
- 趣談Linux系統專欄 - 09系統調用
- 如何下載下傳檢視glibc源代碼
- glibc源碼分析(一)系統調用
- linux下實作一個系統調用