昨天花了一天的時間在看linux0.11的核心,當看到main.c中的int()函式的時候被乙個系統呼叫難倒了(其實筆者的c,彙編等基礎並不是很厲害。但是處於某些原因,就入了這個核心的坑,被澆冷水是經常的事情)。於是各種資料各種搜尋,但是大家給出的資料都是千篇一律,不是一開始就介紹什麼是系統呼叫,就是沒有切實的去一句一句的剖析系統呼叫的原理,反正就是巴拉巴拉說的基本都是一樣,看下來還是什麼都不懂…… ps:可能是筆者真的太渣,沒辦法理解。
於是我便自己看是研究了,首先是從init哪那裡開始,針對fork()這個系統呼叫開始了深入的研究(其實也不是很深入了,哈哈哈)。跟蹤它實現的過程,我想現在或者以後也會有人遇到跟我類似的問題,再加上想更加鞏固一下掌握的知識,就著手寫了這篇博了,那麼開始正題吧!
首先要說的是,我們現在針對的是linux0.11版本核心中的fork()系統呼叫來對系統呼叫做分析。
當呼叫fork()函式時,在main.c中的init()函式中就出現了fork()的呼叫:
// 下面fork()用於建立乙個子程序(子任務)。對於被建立的子程序,fork()將返回0 值,
// 對於原(父程序)將返回子程序的程序號。所以180-184 句是子程序執行的內容。該子程序
// 關閉了控制代碼0(stdin),以唯讀方式開啟/etc/rc 檔案,並執行/bin/sh 程式,所帶引數和
// 環境變數分別由argv_rc 和envp_rc 陣列給出。參見後面的描述。
if (!(pid = fork ()))
大家應該都知道這個函式,確切的說應該是系統呼叫是用來建立乙個新程序的。但是大家會發現核心裡面根本找不到這個函式的具體原型,那它到底是怎麼實現的呢?我們可以用編輯器檢視的定義,我用的是source insight 3 ,發現在unistd中有它的函式原型定義:
int fork (void);// 對應各系統呼叫的函式原型定義。在檔案下的第235行
但是我們只能找到這個函式原型定義,卻找不到它的源。還是在這個檔案中有這樣一些函式定義:
// 以下定義系統呼叫嵌入式彙編巨集函式。
// 不帶引數的系統呼叫巨集函式。type name(void)。
// %0 - eax(__res),%1 - eax(__nr_##name)。其中name 是系統呼叫的名稱,與 __nr_ 組合形成上面
// 的系統呼叫符號常數,從而用來對系統呼叫表中函式指標定址。
// 返回:如果返回值大於等於0,則返回該值,否則置出錯號errno,並返回-1。
#define _syscall0(type,name) \
type name(void) \
剛看到這個嵌入彙編語句的c程式的時候也是一頭霧水,不知所云,但是根據注釋發現這可能跟系統呼叫有很大的關係,但是就是不清楚這個syscall(後面的0,1,2……表示的是所帶的引數,這裡暫且統稱為syscall吧)函式和fork函式的關係。後來通過一番死纏爛打的無賴詢問和翻閱書籍發現是這樣一回事:
在使用這些系統呼叫的檔案中會有這樣一些宣告,比如在main中:
/*
* 我們需要下面這些內嵌語句 - 從核心空間建立程序(forking)將導致沒有寫時複製(copy on write)!!!
* 直到乙個執行execve 呼叫。這對堆疊可能帶來問題。處理的方法是在fork()呼叫之後不讓main()使用
* 任何堆疊。因此就不能有函式呼叫 - 這意味著fork 也要使用內嵌的**,否則我們在從fork()退出
* 時就要使用堆疊了。
* 實際上只有pause 和fork 需要使用內嵌方式,以保證從main()中不會弄亂堆疊,但是我們同時還
* 定義了其它一些函式。
*/static inline
_syscall0 (int, fork) // 是unistd.h 中的內嵌巨集**。以嵌入彙編的形式呼叫
// linux 的系統呼叫中斷0x80。該中斷是所有系統呼叫的
// 入口。該條語句實際上是int fork()建立程序系統呼叫。
// syscall0 名稱中最後的0 表示無引數,1 表示1 個引數。
static inline _syscall0 (int, pause) // int pause()系統呼叫:暫停程序的執行,直到
// 收到乙個訊號。
static inline _syscall1 (int, setup, void *, bios) // int setup(void * bios)系統呼叫,僅用於
// linux 初始化(僅在這個程式中被呼叫)。
static inline _syscall0 (int, sync) // int sync()系統呼叫:更新檔案系統。
可以看到,這裡有乙個static inline _syscall0(int,fork),這樣的宣告,很明顯這就是fork()和syscall()之間的聯絡了。如果還是不明顯可以這樣,把宣告,巨集函式全部按照順序進行展開,把
staticinline_syscall0(int,fork)帶到syscall0函式中去,然後把巨集替換掉就會變成這樣的乙個函式:
void fork(void)
哈哈,沒錯這就是fork()函式了,在呼叫fork之前我們要在當前檔案下先進行上面main中的那樣宣告,這就是建立和呼叫了fork函式(可能這樣做的原因是減少系統**量吧,如果每個系統呼叫都要像函式那樣去宣告的話,如有60個系統呼叫,那麼就要有60個這樣的函式,如果用這樣的方法的話,那就只有短短幾個函式加上一些函式宣告了。可能這只是其中之一)。
那這個函式是如何工作的呢?
在上面的內嵌彙編語句中我們可以看到這樣的一些語句,其實上面也有詳細的說明我就不多解釋。系統呼叫要通過0x80中斷來實現,int $0x80就是呼叫0x80中斷的意思,返回值是__res,函系統中斷呼叫號是__nr_和name的鏈結,如果nam是fork的話,那系統呼叫號就是__nr_fork啦。系統呼叫號是在sys.h標頭檔案中(擷取):
extern int sys_setup (); // 系統啟動初始化設定函式。 (kernel/blk_drv/hd.c,71)
extern int sys_exit (); // 程式退出。 (kernel/exit.c, 137)
extern int sys_fork (); // 建立程序。 (kernel/system_call.s, 208)
extern int sys_read (); // 讀檔案。 (fs/read_write.c, 55)
extern int sys_write (); // 寫檔案。 (fs/read_write.c, 83)
extern int sys_open (); // 開啟檔案。 (fs/open.c, 138)
// 系統呼叫函式指標表。用於系統呼叫中斷處理程式(int 0x80),作為跳轉表。
fn_ptr sys_call_table = ;
其實__nr_fork對應的巨集定義是2,也就對應的是fn_ptr sys_call_table中的2號——sys_fork,這樣一來fork系統呼叫的具體實現也就轉到了sys_fork這個函式中來了。這個函式的位置就是
址 = _sys_call_table + %eax * 4。(未查證,其他博文中看到的)。接下來就是在核心狀態下的系統呼叫具體實現了。
其實最關鍵的還是在理解:
__asm__ volatile ( "int $0x80" :"=a" (__res) :"" (__nr_fork));
linux系統中斷處理程式int 0x80實現原理
gcc在c語言中內嵌彙編 call _volasile_
linux核心——fork()函式建立程序
linux系統呼叫的實現技術
系統呼叫的實現原理
在此非常各位感謝作者的無私奉獻
Linux核心學習之系統呼叫
文章參考linux核心修煉之道。1.系統呼叫概念 大家都知道作業系統的作用是管理計算機的軟硬體資源,但是作業系統要向使用者提供各種各樣的服務,而使用者應用程式訪問這些服務的方式就是通過系統呼叫。但是一般來說,我們都是通過作業系統封裝好的api 應用程式設計介面 來間接使用系統呼叫的。比如在windo...
Linux核心學習筆記四 系統呼叫
一 使用者空間和核心空間 linux核心將這4g位元組虛擬位址空間的空間分為兩部分 l 將最高的1g位元組 從虛擬位址0xc0000000到0xffffffff 供核心使用,稱為 核心空間 l 將較低的3g位元組 從虛擬位址 0x00000000到0xbfffffff 供各個程序使用,稱為 使用者空...
Linux核心學習筆記四 系統呼叫
一 使用者空間和核心空間 linux核心將這4g位元組虛擬位址空間的空間分為兩部分 l 將最高的1g位元組 從虛擬位址0xc0000000到0xffffffff 供核心使用,稱為 核心空間 l 將較低的3g位元組 從虛擬位址 0x00000000到0xbfffffff 供各個程序使用,稱為 使用者空...