《Linux內(nèi)核設(shè)計與實現(xiàn)》讀書筆記 第五章

本章講解了系統(tǒng)調(diào)用的抽象概念與實現(xiàn)方式琅束;總的來說募狂,系統(tǒng)調(diào)用是內(nèi)核提供的方便用戶進程與內(nèi)核進行交互的一組接口;主要系統(tǒng)穩(wěn)定可靠,避免應(yīng)用程序恣意妄為蜻懦;

作用

  1. 為用戶空間提供了一種硬件的抽象接口;
  2. 系統(tǒng)調(diào)用保證了系統(tǒng)的穩(wěn)定與安全;
  3. 如果應(yīng)用程序可以隨意訪問硬件而內(nèi)核又對此一無所知的話,幾乎無法實現(xiàn)多任務(wù)和虛擬內(nèi)存煌恢;

摘自http://blog.chinaunix.net/uid-20321537-id-1966859.html

系統(tǒng)調(diào)用處理程序

您或許疑惑: “當(dāng)我輸入 cat /proc/cpuinfo 時,cpuinfo() 函數(shù)是如何被調(diào)用的震庭?”內(nèi)核完成引導(dǎo)后瑰抵,控制流就從相對直觀的“接下來調(diào)用哪個函數(shù)?”改變?yōu)槿Q于系統(tǒng)調(diào)用器联、異常和中斷二汛。

用戶空間的程序無法直接執(zhí)行內(nèi)核代碼。它們不能直接調(diào)用內(nèi)核空間中的函數(shù)拨拓,因為內(nèi)核駐留在受保護的地址空間上肴颊。如果進程可以直接在內(nèi)核的地址空間上讀寫的話,系統(tǒng)安全就會失去控制渣磷。所以婿着,應(yīng)用程序應(yīng)該以某種方式通知系統(tǒng),告訴內(nèi)核自己需要執(zhí)行一個系統(tǒng)調(diào)用醋界,希望系統(tǒng)切換到內(nèi)核態(tài)竟宋,這樣內(nèi)核就可以代表應(yīng)用程序來執(zhí)行該系統(tǒng)調(diào)用了。

通知內(nèi)核的機制是靠軟件中斷實現(xiàn)的形纺。首先丘侠,用戶程序為系統(tǒng)調(diào)用設(shè)置參數(shù)。其中一個參數(shù)是系統(tǒng)調(diào)用編號逐样。參數(shù)設(shè)置完成后蜗字,程序執(zhí)行“系統(tǒng)調(diào)用”指令。x86系統(tǒng)上的軟中斷由int產(chǎn)生官研。這個指令會導(dǎo)致一個異常:產(chǎn)生一個事件秽澳,這個事件會致使處理器切換到內(nèi)核態(tài)并跳轉(zhuǎn)到一個新的地址,并開始執(zhí)行那里的異常處理程序戏羽。此時的異常處理程序?qū)嶋H上就是系統(tǒng)調(diào)用處理程序担神。它與硬件體系結(jié)構(gòu)緊密相關(guān)。

新地址的指令會保存程序的狀態(tài)始花,計算出應(yīng)該調(diào)用哪個系統(tǒng)調(diào)用妄讯,調(diào)用內(nèi)核中實現(xiàn)那個系統(tǒng)調(diào)用的函數(shù),恢復(fù)用戶程序狀態(tài)酷宵,然后將控制權(quán)返還給用戶程序亥贸。系統(tǒng)調(diào)用是設(shè)備驅(qū)動程序中定義的函數(shù)最終被調(diào)用的一種方式。

系統(tǒng)調(diào)用號

在Linux中浇垦,每個系統(tǒng)調(diào)用被賦予一個系統(tǒng)調(diào)用號炕置。這樣,通過這個獨一無二的號就可以關(guān)聯(lián)系統(tǒng)調(diào)用。當(dāng)用戶空間的進程執(zhí)行一個系統(tǒng)調(diào)用的時候朴摊,這個系統(tǒng)調(diào)用號就被用來指明到底是要執(zhí)行哪個系統(tǒng)調(diào)用默垄。進程不會提及系統(tǒng)調(diào)用的名稱。

系統(tǒng)調(diào)用號相當(dāng)關(guān)鍵甚纲,一旦分配就不能再有任何變更口锭,否則編譯好的應(yīng)用程序就會崩潰。Linux有一個“未實現(xiàn)”系統(tǒng)調(diào)用sys_ni_syscall()介杆,它除了返回一ENOSYS外不做任何其他工作鹃操,這個錯誤號就是專門針對無效的系統(tǒng)調(diào)用而設(shè)的。

因為所有的系統(tǒng)調(diào)用陷入內(nèi)核的方式都一樣春哨,所以僅僅是陷入內(nèi)核空間是不夠的荆隘。因此必須把系統(tǒng)調(diào)用號一并傳給內(nèi)核。在x86上悲靴,系統(tǒng)調(diào)用號是通過eax寄存器傳遞給內(nèi)核的臭胜。在陷人內(nèi)核之前,用戶空間就把相應(yīng)系統(tǒng)調(diào)用所對應(yīng)的號放入eax中了癞尚。這樣系統(tǒng)調(diào)用處理程序一旦運行,就可以從eax中得到數(shù)據(jù)乱陡。其他體系結(jié)構(gòu)上的實現(xiàn)也都類似浇揩。

內(nèi)核記錄了系統(tǒng)調(diào)用表中的所有已注冊過的系統(tǒng)調(diào)用的列表,存儲在sys_call_table中憨颠。它與體系結(jié)構(gòu)有關(guān)胳徽,一般在entry.s中定義。這個表中為每一個有效的系統(tǒng)調(diào)用指定了惟一的系統(tǒng)調(diào)用號爽彤。sys_call_table是一張由指向?qū)崿F(xiàn)各種系統(tǒng)調(diào)用的內(nèi)核函數(shù)的函數(shù)指針組成的表:

ENTRY(sys_call_table)
.long SYMBOL_NAME(sys_ni_syscall) /* 0 - old "setup()" system call*/
.long SYMBOL_NAME(sys_exit)
.long SYMBOL_NAME(sys_fork)
.long SYMBOL_NAME(sys_read)
.long SYMBOL_NAME(sys_write)
.long SYMBOL_NAME(sys_open) /* 5 */
.long SYMBOL_NAME(sys_close)
.long SYMBOL_NAME(sys_waitpid)
......
.long SYMBOL_NAME(sys_capget)
.long SYMBOL_NAME(sys_capset)      /* 185 */
.long SYMBOL_NAME(sys_sigaltstack)
.long SYMBOL_NAME(sys_sendfile)
.long SYMBOL_NAME(sys_ni_syscall) /* streams1 */
.long SYMBOL_NAME(sys_ni_syscall) /* streams2 */
.long SYMBOL_NAME(sys_vfork)      /* 190 */

system_call()函數(shù)通過將給定的系統(tǒng)調(diào)用號與NR_syscalls做比較來檢查其有效性养盗。如果它大于或者等于NR syscalls,該函數(shù)就返回一ENOSYS。否則适篙,就執(zhí)行相應(yīng)的系統(tǒng)調(diào)用往核。

call *sys_ call-table(,%eax, 4)

由于系統(tǒng)調(diào)用表中的表項是以32位(4字節(jié))類型存放的嚷节,所以內(nèi)核需要將給定的系統(tǒng)調(diào)用號乘以4聂儒,然后用所得的結(jié)果在該表中查詢其位置

參數(shù)傳遞

除了系統(tǒng)調(diào)用號以外,大部分系統(tǒng)調(diào)用都還需要一些外部的參數(shù)輸人硫痰。所以衩婚,在發(fā)生異常的時候,應(yīng)該把這些參數(shù)從用戶空間傳給內(nèi)核效斑。最簡單的辦法就是像傳遞系統(tǒng)調(diào)用號一樣把這些參數(shù)也存放在寄存器里非春。在x86系統(tǒng)上,ebx, ecx, edx, esi和edi按照順序存放前五個參數(shù)。需要六個或六個以上參數(shù)的情況不多見奇昙,此時护侮,應(yīng)該用一個單獨的寄存器存放指向所有這些參數(shù)在用戶空間地址的指針。

給用戶空間的返回值也通過寄存器傳遞敬矩。在x86系統(tǒng)上概行,它存放在eax寄存器中。接下來許多關(guān)于系統(tǒng)調(diào)用處理程序的描述都是針對x86版本的弧岳。但不用擔(dān)心凳忙,所有體系結(jié)構(gòu)的實現(xiàn)都很類似。

參數(shù)驗證

系統(tǒng)調(diào)用必須仔細檢查它們所有的參數(shù)是否合法有效禽炬。舉例來說涧卵,與文件I/O相關(guān)的系統(tǒng)調(diào)用必須檢查文件描述符是否有效。與進程相關(guān)的函數(shù)必須檢查提供的PID是否有效腹尖。必須檢查每個參數(shù)柳恐,保證它們不但合法有效,而且正確热幔。

最重要的一種檢查就是檢查用戶提供的指針是否有效乐设。試想,如果一個進程可以給內(nèi)核傳遞指針而又無須被檢查绎巨,那么它就可以給出一個它根本就沒有訪問權(quán)限的指針近尚,哄騙內(nèi)核去為它拷貝本不允許它訪問的數(shù)據(jù),如原本屬于其他進程的數(shù)據(jù)场勤。在接收一個用戶空間的指針之前戈锻,內(nèi)核必須保證:
2 指針指向的內(nèi)存區(qū)域?qū)儆谟脩艨臻g。進程決不能哄騙內(nèi)核去讀內(nèi)核空間的數(shù)據(jù)和媳。
2 指針指向的內(nèi)存區(qū)域在進程的地址空間里格遭。進程決不能哄騙內(nèi)核去讀其他進程的數(shù)據(jù)。
2 如果是讀留瞳,該內(nèi)存應(yīng)被標(biāo)記為可讀拒迅。如果是寫,該內(nèi)存應(yīng)被標(biāo)記為可寫撼港。進程決不能繞過內(nèi)存訪問限制坪它。

內(nèi)核提供了兩個方法來完成必須的檢查和內(nèi)核空間與用戶空間之間數(shù)據(jù)的來回拷貝。注意帝牡,內(nèi)核無論何時都不能輕率地接受來自用戶空間的指針!這兩個方法中必須有一個被調(diào)用往毡。為了向用戶空間寫入數(shù)據(jù),內(nèi)核提供了copy_to_user()靶溜,它需要三個參數(shù)开瞭。第一個參數(shù)是進程空間中的目的內(nèi)存地址懒震。第二個是內(nèi)核空間內(nèi)的源地址。最后一個參數(shù)是需要拷貝的數(shù)據(jù)長度(字節(jié)數(shù))嗤详。

為了從用戶空間讀取數(shù)據(jù)个扰,內(nèi)核提供了copy_from_ user(),它和copy-to-User()相似葱色。該函數(shù)把第二個參數(shù)指定的位置上的數(shù)據(jù)拷貝到第一個參數(shù)指定的位置上递宅,拷貝的數(shù)據(jù)長度由第三個參數(shù)決定。

如果執(zhí)行失敗苍狰,這兩個函數(shù)返回的都是沒能完成拷貝的數(shù)據(jù)的字節(jié)數(shù)办龄。如果成功,返回0淋昭。當(dāng)出現(xiàn)上述錯誤時俐填,系統(tǒng)調(diào)用返回標(biāo)準(zhǔn)-EFAULT。

注意copy_to_user()和copy_from_user()都有可能引起阻塞翔忽。當(dāng)包含用戶數(shù)據(jù)的頁被換出到硬盤上而不是在物理內(nèi)存上的時候英融,這種情況就會發(fā)生。此時歇式,進程就會休眠驶悟,直到缺頁處理程序?qū)⒃擁搹挠脖P重新?lián)Q回物理內(nèi)存。

系統(tǒng)調(diào)用的返回值

系統(tǒng)調(diào)用(在Linux中常稱作syscalls)通常通過函數(shù)進行調(diào)用材失。它們通常都需要定義一個或幾個參數(shù)(輸入)而且可能產(chǎn)生一些副作用撩银,例如寫某個文件或向給定的指針拷貝數(shù)據(jù)等等。為防止和正常的返回值混淆豺憔,系統(tǒng)調(diào)用并不直接返回錯誤碼,而是將錯誤碼放入一個名為errno的全局變量中够庙。通常用一個負的返回值來表明錯誤恭应。返回一個0值通常表明成功。如果一個系統(tǒng)調(diào)用失敗耘眨,你可以讀出errno的值來確定問題所在昼榛。通過調(diào)用perror()庫函數(shù),可以把該變量翻譯成用戶可以理解的錯誤字符串剔难。

errno不同數(shù)值所代表的錯誤消息定義在errno.h中胆屿,你也可以通過命令"man 3 errno"來察看它們。需要注意的是偶宫,errno的值只在函數(shù)發(fā)生錯誤時設(shè)置非迹,如果函數(shù)不發(fā)生錯誤,errno的值就無定義纯趋,并不會被置為0憎兽。另外冷离,在處理errno前最好先把它的值存入另一個變量,因為在錯誤處理過程中纯命,即使像printf()這樣的函數(shù)出錯時也會改變errno的值西剥。

當(dāng)然,系統(tǒng)調(diào)用最終具有一種明確的操作亿汞。舉例來說瞭空,如getpid()系統(tǒng)調(diào)用,根據(jù)定義它會返回當(dāng)前進程的PID疗我。內(nèi)核中它的實現(xiàn)非常簡單:

asmlinkage long sys_ getpid(void)
{
    return current-> tgid;
}

上述的系統(tǒng)調(diào)用盡管非常簡單咆畏,但我們還是可以從中發(fā)現(xiàn)兩個特別之處。首先碍粥,注意函數(shù)聲明中的asmlinkage限定詞鳖眼,這是一個小戲法,用于通知編譯器僅從棧中提取該函數(shù)的參數(shù)嚼摩。所有的系統(tǒng)調(diào)用都需要這個限定詞钦讳。其次,注意系統(tǒng)調(diào)用get_pid()在內(nèi)核中被定義成sys_ getpid枕面。這是Linux中所有系統(tǒng)調(diào)用都應(yīng)該遵守的命名規(guī)則

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末愿卒,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子潮秘,更是在濱河造成了極大的恐慌琼开,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件枕荞,死亡現(xiàn)場離奇詭異柜候,居然都是意外死亡,警方通過查閱死者的電腦和手機躏精,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門渣刷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人矗烛,你說我怎么就攤上這事辅柴。” “怎么了瞭吃?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵碌嘀,是天一觀的道長。 經(jīng)常有香客問我歪架,道長股冗,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任牡拇,我火速辦了婚禮魁瞪,結(jié)果婚禮上穆律,老公的妹妹穿的比我還像新娘。我一直安慰自己导俘,他們只是感情好峦耘,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著旅薄,像睡著了一般辅髓。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上少梁,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天洛口,我揣著相機與錄音,去河邊找鬼凯沪。 笑死第焰,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的妨马。 我是一名探鬼主播挺举,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼烘跺!你這毒婦竟也來了湘纵?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤滤淳,失蹤者是張志新(化名)和其女友劉穎梧喷,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體脖咐,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡铺敌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了屁擅。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片适刀。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖煤蹭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情取视,我是刑警寧澤硝皂,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站作谭,受9級特大地震影響稽物,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜折欠,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一贝或、第九天 我趴在偏房一處隱蔽的房頂上張望吼过。 院中可真熱鬧,春花似錦咪奖、人聲如沸盗忱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽趟佃。三九已至,卻和暖如春昧捷,著一層夾襖步出監(jiān)牢的瞬間闲昭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工靡挥, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留序矩,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓跋破,卻偏偏與公主長得像簸淀,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子幔烛,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容