版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)
文檔簡介
1、1-1本文是 linux 系統(tǒng)調(diào)用系列文章的第一篇,對linux 系統(tǒng)調(diào)用的定義、基本原理、使用方法和注意事項大概作了一個介紹,以便讀者對linux 系統(tǒng)調(diào)用建立一個大致的印象。什么是系統(tǒng)調(diào)用?linux 內(nèi)核中設(shè)置了一組用于實(shí)現(xiàn)各種系統(tǒng)功能的子程序,稱為系統(tǒng)調(diào)用。用戶可以通過系統(tǒng)調(diào)用命令在自己的應(yīng)用程序中調(diào)用它們。從某種角度來看, 系統(tǒng)調(diào)用和普通的函數(shù)調(diào)用非常相似。區(qū)別僅僅在于,系統(tǒng)調(diào)用由操作系統(tǒng)核心提供,運(yùn)行于核心態(tài);而普通的函數(shù)調(diào)用由函數(shù)庫或用戶自己提供,運(yùn)行于用戶態(tài)。 二者在使用方式上也有相似之處,在下面將會提到。隨 linux 核心還提供了一些c 語言函數(shù)庫,這些庫對系統(tǒng)調(diào)用進(jìn)行了一
2、些包裝和擴(kuò)展, 因為這些庫函數(shù)與系統(tǒng)調(diào)用的關(guān)系非常緊密,所以習(xí)慣上把這些函數(shù)也稱為系統(tǒng)調(diào)用。linux 中共有多少個系統(tǒng)調(diào)用?這個問題可不太好回答,就算讓linus torvaldz 本人也不見得一下子就能說清楚。在 2.4.4版內(nèi)核中 ,狹義上的系統(tǒng)調(diào)用共有221個,你可以在/include/asm-i386/unistd.h中找到它們的原本,也可以通過命令man 2 syscalls 察看它們的目錄( man pages 的版本一般比較老,可能有很多最新的調(diào)用都沒有包含在內(nèi))。廣義上的系統(tǒng)調(diào)用, 也就是以庫函數(shù)的形式實(shí)現(xiàn)的那些,它們的個數(shù)從來沒有人統(tǒng)計過,這是一件吃力不討好的活, 新內(nèi)核不
3、斷地在推出, 每一個新內(nèi)核中函數(shù)數(shù)目的變化根本就沒有人在乎,至少連內(nèi)核的修改者本人都不在乎,因為他們從來沒有發(fā)布過一個此類的聲明。隨本文一起有一份經(jīng)過整理的列表,它不可能非常全面, 但常見的系統(tǒng)調(diào)用基本都已經(jīng)包含在內(nèi), 那里面只有不多的一部分是你平時用得到的,本專欄將會有選擇的對它們進(jìn)行介紹。為什么要用系統(tǒng)調(diào)用?實(shí)際上,很多已經(jīng)被我們習(xí)以為常的c 語言標(biāo)準(zhǔn)函數(shù),在linux平臺上的實(shí)現(xiàn)都是靠系統(tǒng)調(diào)用完成的, 所以如果想對系統(tǒng)底層的原理作深入的了解,掌握各種系統(tǒng)調(diào)用是初步的要求。 進(jìn)一步,若想成為一名linux 下編程高手,也就是我們常說的hacker ,其標(biāo)志之一也是能對各種系統(tǒng)調(diào)用有透徹的了
4、解。即使除去上面的原因, 在平常的編程中你也會發(fā)現(xiàn), 在很多情況下, 系統(tǒng)調(diào)用是實(shí)現(xiàn)你的想法的簡潔有效的途徑, 所以有可能的話應(yīng)該盡量多掌握一些系統(tǒng)調(diào)用, 這會對你的程序設(shè)計過程帶來意想不到的幫助。系統(tǒng)調(diào)用是怎么工作的?一般的,進(jìn)程是不能訪問內(nèi)核的。它不能訪問內(nèi)核所占內(nèi)存空間也不能調(diào)用內(nèi)核函數(shù)。cpu 硬件決定了這些(這就是為什么它被稱作 保護(hù)模式 )。系統(tǒng)調(diào)用是這些規(guī)則的一個例 外。其原理是進(jìn)程先用適當(dāng)?shù)闹堤畛浼拇嫫?,然后調(diào)用一個特殊的指令,這個指令會跳到一個事先定義的內(nèi)核中的一個位置(當(dāng)然,這個位置是用戶進(jìn)程可讀但是不可寫的)。在 intel cpu 中,這個由中斷 0x80 實(shí)現(xiàn)。硬件知
5、道一旦你跳到這個位置,你就不是在限制模式下運(yùn)行的用戶,而是作為操作系統(tǒng)的內(nèi)核-所以你就可以為所欲為。進(jìn)程可以跳轉(zhuǎn)到的內(nèi)核位置叫做sysem_call。這個過程檢查系統(tǒng)調(diào)用號,這個號碼告訴 內(nèi)核進(jìn)程請求哪種服務(wù)。然后,它查看系統(tǒng)調(diào)用表(sys_call_table) 找到所調(diào)用的內(nèi)核函數(shù)入口地址。 接著, 就調(diào)用函數(shù), 等返回后, 做一些系統(tǒng)檢查, 最后返回到進(jìn)程 (或到其他進(jìn)程,如果這個進(jìn)程時間用盡)。如果你希望讀這段代碼,它在/kernel/entry.s ,entry(system_call) 的下一行。如何使用系統(tǒng)調(diào)用?先來看一個例子:#include/* 定義宏 _syscall1*/
6、 #include/* 定義類型 time_t*/_syscall1(time_t, time, time_t *, tloc) /*宏,展開后得到 time() 函數(shù)的原型 */main()time_tthe_time;the_time=time(time_t*)0); /* 調(diào)用 time 系統(tǒng)調(diào)用 */ printf(thetime is %ldn, the_time);系統(tǒng)調(diào)用 time 返回從格林尼治時間1970 年 1 月 1 日 0:00 開始到現(xiàn)在的秒數(shù)。這是最標(biāo)準(zhǔn)的系統(tǒng)調(diào)用的形式,宏_syscall1() 展開來得到一個函數(shù)原型,稍后我會作詳細(xì)解釋。但事實(shí)上,如果把程序改成下
7、面的樣子,程序也可以運(yùn)行得同樣的結(jié)果。#include main()time_tthe_time;the_time=time(time_t*)0); /* 調(diào)用 time 系統(tǒng)調(diào)用 */ printf(thetime is %ldn, the_time);1-2這是因為在time.h 中實(shí)際上已經(jīng)用庫函數(shù)的形式實(shí)現(xiàn)了time 這個系統(tǒng)調(diào)用,替我們省掉了調(diào)用 _syscall1 宏展開得到函數(shù)原型這一步。大多數(shù)系統(tǒng)調(diào)用都在各種c 語言函數(shù)庫中有所實(shí)現(xiàn),所以在一般情況下,我們都可以像調(diào)用普通的庫函數(shù)那樣調(diào)用系統(tǒng)調(diào)用,只在極個別的情況下, 我們才有機(jī)會用到 _syscall*()這幾個宏。_sysc
8、all*() 是什么?在 unistd.h 里定義了 7 個宏,分別是_syscall0(type,name)_syscall1(type,name, type1, arg1)_syscall2(type, name, type1, arg1, type2, arg2)_syscall3(type, name, type1, arg1, type2, arg2, type3, arg3)_syscall4(type, name, type1, arg1, type2, arg2, type3, arg3, type4, arg4)_syscall5(type, name, type1, arg
9、1, type2, arg2, type3, arg3, type4, arg4, type5, arg5)_syscall6(type, name, type1, arg1, type2, arg2, type3, arg3, type4, arg4, type5, arg5, type6, arg6)它們看起來似乎不太像宏,但其實(shí)質(zhì)和#definemaxsize100里面的 maxsize沒有任何區(qū)別。它們的作用是形成相應(yīng)的系統(tǒng)調(diào)用函數(shù)原型,供我們在程序中調(diào)用。我們很容易就能發(fā)現(xiàn)規(guī)律, _syscall 后面的數(shù)字和 typen, argn 的數(shù)目一樣多。事實(shí)上,_syscall 后面跟的
10、數(shù)字指明了展開后形成函數(shù)的參數(shù)的個數(shù),讓我們看一個實(shí)例,就是剛剛用過的time 系統(tǒng)調(diào)用:_syscall1(time_t,time, time_t *, tloc)展開后的情形是這樣:time_ttime(time_t *tloc)longres; asm do volatile(int $0x80 : =a (res) : 0 (13), b (long)(tloc);if(unsigned long)(res) = (unsigned long)(-125) errno = -(res); res= -1;return(time_t) (res); while (0) ;可以看出, _s
11、yscall1(time_t, time, time_t *, tloc)展開成一個名為time 的函數(shù),原參數(shù)time_t 就是函數(shù)的返回類型,原參數(shù)time_t * 和 tloc 分別構(gòu)成新函數(shù)的參數(shù)。事實(shí)上,程序中用到的 time 函數(shù)的原型就是它。errno 是什么?為防止和正常的返回值混淆,系統(tǒng)調(diào)用并不直接返回錯誤碼,而是將錯誤碼放入一個名為 errno 的全局變量中。如果一個系統(tǒng)調(diào)用失敗,你可以讀出errno 的值來確定問題所在。errno 不同數(shù)值所代表的錯誤消息定義在errno.h 中,你也可以通過命令man 3 errno 來察看它們。需要注意的是, errno 的值只在函數(shù)
12、發(fā)生錯誤時設(shè)置,如果函數(shù)不發(fā)生錯誤,errno 的值就無定義,并不會被置為0。另外,在處理 errno 前最好先把它的值存入另一個變量,因為在錯誤處理過程中,即使像printf() 這樣的函數(shù)出錯時也會改變errno 的值。系統(tǒng)調(diào)用兼容性好嗎?很遺憾,答案是 -不好。但這決不意味著你的程序會三天兩頭的導(dǎo)致系統(tǒng)崩潰,因為系統(tǒng)調(diào)用是 linux 的內(nèi)核提供的,所以它們工作起來非常穩(wěn)定,對于此點(diǎn)無需絲毫懷疑,在絕大多數(shù)的情況下,系統(tǒng)調(diào)用要比你自己編寫的代碼可靠而高效的多。但是,在 linux 的各版本內(nèi)核之間,系統(tǒng)調(diào)用的兼容性表現(xiàn)得并不像想象那么好,這是由 linux 本身的性質(zhì)決定的。 linux
13、 是一群程序設(shè)計高手利用業(yè)余時間開發(fā)出來的,他們中間的大部分人沒有把linux 當(dāng)成一個嚴(yán)肅的商業(yè)軟件, (現(xiàn)在的情況有些不同了, 隨著 linux 商業(yè)公司和以 linux 為生的人的增長,不少人的腦筋發(fā)生了變化。)結(jié)果就是,如果新的方 案在效率和兼容性上發(fā)生了矛盾,他們往往舍棄兼容性而追求效率,就這樣, 如果他們認(rèn)為某個系統(tǒng)調(diào)用實(shí)現(xiàn)的比較糟糕,他們就會毫不猶豫的作出修改,有些時候甚至連接口也一起 改掉了,更可怕的是,很多時候,他們對自己的修改連個招呼也不打,在任何文檔里都找不到關(guān)于修改的提示。這樣,每當(dāng)新內(nèi)核推出的時候,很可能都會悄悄的更新一些系統(tǒng)調(diào)用, 用戶編制的應(yīng)用程序也會跟著出錯。說
14、到這里,你是不是感覺前途一片昏暗呢?呵呵,不用太緊張,如前面所說,隨著越來越多的人把linux 當(dāng)成自己的飯碗,不兼容的情況也越來越罕見。從2.2 版本以后的linux 內(nèi)核已經(jīng)非常穩(wěn)定了, 不過盡管如此, 你還是有必要在每個新內(nèi)核推出之后,對自己的應(yīng)用程序進(jìn)行兼容性測試,以防止意外的發(fā)生。該如何學(xué)習(xí)使用 linux 系統(tǒng)調(diào)用呢?你可以用 man 2 系統(tǒng)調(diào)用名稱 的命令來查看各條系統(tǒng)調(diào)用的介紹,但這首先要求你要 有不錯的英語基礎(chǔ),其次還得有一定的程序設(shè)計和系統(tǒng)編程的功底,man pages 不會涉及太多的應(yīng)用細(xì)節(jié),因為它只是一個手冊而非教程。如果man pages 所提供的東西不能使你感到非
15、常滿意,那就跟我來吧,本專欄將向你展示linux 系統(tǒng)調(diào)用編程的無窮魅力。對讀者的兩點(diǎn)小小的要求:1) 讀者必須有一定的c 語言編程經(jīng)驗;2) 讀者必須有一定的 linux 使用經(jīng)驗。如果你能完全看懂本文從開頭到這里所講的東西, 你就合格了。收拾好行囊,準(zhǔn)備出發(fā)吧!進(jìn)程管理 2-1本文介紹了 linux 下的進(jìn)程概念, 并著重講解了與 linux 進(jìn)程管理相關(guān)的 4 個重要系統(tǒng)調(diào)用 getpid, fork, exit和_exit,輔助一些例程說明了它們的特點(diǎn)和使用方法。關(guān)于進(jìn)程的一些必要知識先看一下進(jìn)程在大學(xué)課本里的標(biāo)準(zhǔn)定義:“進(jìn)程是可并發(fā)執(zhí)行的程序在一個數(shù)據(jù)集合上的運(yùn)行過程。 ”這個定義非
16、常嚴(yán)謹(jǐn),而且難懂,如果你沒有一下子理解這句話,就不妨看看筆者自己的并不嚴(yán)謹(jǐn)?shù)慕忉?。我們大家都知道,硬盤上的一個可執(zhí)行文件經(jīng)常被稱作程序,在 linux 系統(tǒng)中,當(dāng)一個程序開始執(zhí)行后,在開始執(zhí)行到執(zhí)行完畢退出這段時間里,它在內(nèi)存中的部分就被稱作一個進(jìn)程。當(dāng)然,這個解釋并不完善,但好處是容易理解,在以下的文章中,我們將會對進(jìn)程作一些更全面的認(rèn)識。linux 進(jìn)程簡介linux 是一個多任務(wù)的操作系統(tǒng),也就是說,在同一個時間內(nèi),可以有多個進(jìn)程同時執(zhí)行。如果讀者對計算機(jī)硬件體系有一定了解的話,會知道我們大家常用的單cpu 計算機(jī)實(shí)際上在一個時間片斷內(nèi)只能執(zhí)行一條指令,那么 linux 是如何實(shí)現(xiàn)多進(jìn)程
17、同時執(zhí)行的呢?原來 linux 使用了一種稱為“進(jìn)程調(diào)度(process scheduling)”的手段,首先,為每個進(jìn)程指派一定的運(yùn)行時間,這個時間通常很短,短到以毫秒為單位,然后依照某種規(guī)則,從眾多進(jìn)程中挑選一個投入運(yùn)行, 其他的進(jìn)程暫時等待, 當(dāng)正在運(yùn)行的那個進(jìn)程時間耗盡,或執(zhí)行完畢退出,或因某種原因暫停,linux 就會重新進(jìn)行調(diào)度,挑選下一個進(jìn)程投入運(yùn)行。因為每個進(jìn)程占用的時間片都很短,在我們使用者的角度來看,就好像多個進(jìn)程同時運(yùn)行一樣了。在 linux中,每個進(jìn)程在創(chuàng)建時都會被分配一個數(shù)據(jù)結(jié)構(gòu),稱為進(jìn)程控制塊(process control block ,簡稱 pcb)。 pcb
18、中包含了很多重要的信息,供系統(tǒng)調(diào)度和進(jìn)程本身執(zhí)行使用,其中最重要的莫過于進(jìn)程id ( process id )了,進(jìn)程 id 也被稱作進(jìn)程標(biāo)識符,是一個非負(fù)的整數(shù),在linux 操作系統(tǒng)中唯一地標(biāo)志一個進(jìn)程,在我們最常使用的i386 架構(gòu)(即pc 使用的架構(gòu))上,一個非負(fù)的整數(shù)的變化范圍是0-32767 ,這也是我們所有可能取到的進(jìn)程 id 。其實(shí)從進(jìn)程 id 的名字就可以看出,它就是進(jìn)程的身份證號碼,每個人的身份證號碼都不會相同,每個進(jìn)程的進(jìn)程id 也不會相同。一個或多個進(jìn)程可以合起來構(gòu)成一個進(jìn)程組(process group ),一個或多個進(jìn)程組可以合起來構(gòu)成一個會話(session)。這
19、樣我們就有了對進(jìn)程進(jìn)行批量操作的能力,比如通過向某個進(jìn)程組發(fā)送信號來實(shí)現(xiàn)向該組中的每個進(jìn)程發(fā)送信號。最后,讓我們通過ps 命令親眼看一看自己的系統(tǒng)中目前有多少進(jìn)程在運(yùn)行:$ps -aux(以下是在我的計算機(jī)上的運(yùn)行結(jié)果,你的結(jié)果很可能與這不同。)以上除標(biāo)題外,每一行都代表一個進(jìn)程。在各列中,pid 一列代表了各進(jìn)程的進(jìn)程id , command一列代表了進(jìn)程的名稱或在shell 中調(diào)用的命令行,對其他列的具體含義,我就不再作解釋,有興趣的讀者可以去參考相關(guān)書籍。進(jìn)程管理 2-2getpid在 2.4.4 版內(nèi)核中, getpid 是第 20 號系統(tǒng)調(diào)用,其在linux 函數(shù)庫中的原型是:#in
20、clude /*提供類型 pid_t 的定義 */ #include /*提供函數(shù)的定義 */pid_tgetpid(void);getpid的作用很簡單,就是返回當(dāng)前進(jìn)程的進(jìn)程id ,請大家看以下的例子:/*getpid_test.c */ #includemain()printf(“ tchuerrent process id is %dn” , getpid();細(xì)心的讀者可能注意到了,這個程序的定義里并沒有包含頭文件sys/types.h,這是因為我們在程序中沒有用到pid_t 類型, pid_t 類型即為進(jìn)程id 的類型。事實(shí)上,在i386 架構(gòu)上(就是我們一般pc 計算機(jī)的架構(gòu))
21、, pid_t 類型是和 int 類型完全兼容的,我們可以用處理整形數(shù)的方法去處理pid_t 類型的數(shù)據(jù),比如,用 %d 把它打印出來。編譯并運(yùn)行程序 getpid_test.c:$gccgetpid_test.c -o getpid_test$./getpid_testthecurrent process id is 1980(你自己的運(yùn)行結(jié)果很可能與這個數(shù)字不一樣,這是很正常的。) 再運(yùn)行一遍:$./getpid_testthe current process id is 1981正如我們所見, 盡管是同一個應(yīng)用程序,每一次運(yùn)行的時候, 所分配的進(jìn)程標(biāo)識符都不相同。fork在 2.4.4
22、版內(nèi)核中, fork 是第 2 號系統(tǒng)調(diào)用,其在linux 函數(shù)庫中的原型是:#include /*提供類型 pid_t 的定義 */ #include /*提供函數(shù)的定義 */pid_tfork(void);只看 fork 的名字,可能難得有幾個人可以猜到它是做什么用的。 fork 系統(tǒng)調(diào)用的作用是復(fù)制一個進(jìn)程。 當(dāng)一個進(jìn)程調(diào)用它, 完成后就出現(xiàn)兩個幾乎一模一樣的進(jìn)程, 我們也由此得到了一個新進(jìn)程。據(jù)說 fork 的名字就是來源于這個與叉子的形狀頗有幾分相似的工作流程。在 linux 中,創(chuàng)造新進(jìn)程的方法只有一個,就是我們正在介紹的fork 。其他一些庫函數(shù), 如 system(),看起來似
23、乎它們也能創(chuàng)建新的進(jìn)程,如果能看一下它們的源碼就會明白,它們實(shí)際上也在內(nèi)部調(diào)用了fork 。包括我們在命令行下運(yùn)行應(yīng)用程序,新的進(jìn)程也是由shell 調(diào)用 fork 制造出來的。 fork 有一些很有意思的特征,下面就讓我們通過一個小程序來對它有更多的了解。/*fork_test.c */ #include #inlcudemain()pid_t pid;/* 此時僅有一個進(jìn)程*/ pid=fork();/* 此時已經(jīng)有兩個進(jìn)程在同時運(yùn)行*/ if(pid 0)printf(“ error in fork!” ); else if(pid=0)printf(“ i am the child p
24、rocess, my process id is %dn” , getpid();elseprintf(“ i am the parent process, my process id is %dn” , getpid();編譯并運(yùn)行:$gcc fork_test.c -o fork_test$./fork_testi am the parent process, my process id is 1991 i am the child process, my process id is 1992看這個程序的時候,頭腦中必須首先了解一個概念:在語句 pid=fork() 之前,只有一個進(jìn)程在執(zhí)
25、行這段代碼, 但在這條語句之后, 就變成兩個進(jìn)程在執(zhí)行了, 這兩個進(jìn)程的代碼部分完全相同,將要執(zhí)行的下一條語句都是 if(pid=0) 。兩個進(jìn)程中,原先就存在的那個被稱作“父進(jìn)程” ,新出現(xiàn)的那個被稱作“子進(jìn)程” 。父子進(jìn)程的區(qū)別除了進(jìn)程標(biāo)志符( process id)不同外,變量 pid 的值也不相同, pid 存放的是fork 的返回值。 fork 調(diào)用的一個奇妙之處就是它僅僅被調(diào)用一次,卻能夠返回兩次,它可能有三種不同的返回值:在父進(jìn)程中, fork 返回新創(chuàng)建子進(jìn)程的進(jìn)程id ; 在子進(jìn)程中, fork 返回 0;如果出現(xiàn)錯誤, fork 返回一個負(fù)值; 進(jìn)程管理 2-3fork 出
26、錯可能有兩種原因:( 1)當(dāng)前的進(jìn)程數(shù)已經(jīng)達(dá)到了系統(tǒng)規(guī)定的上限,這時 errno 的值被設(shè)置為 eagain 。( 2) 系統(tǒng)內(nèi)存不足,這時errno 的值被設(shè)置為 enomem 。(關(guān)于 errno 的意義,請參考本系列的第一篇文章。)fork 系統(tǒng)調(diào)用出錯的可能性很小,而且如果出錯,一般都為第一種錯誤。如果出現(xiàn)第二種錯誤,說明系統(tǒng)已經(jīng)沒有可分配的內(nèi)存,正處于崩潰的邊緣,這種情況對linux 來說是很罕見的。說到這里,聰明的讀者可能已經(jīng)完全看懂剩下的代碼了,如果pid 小于 0,說明出現(xiàn)了錯誤; pid=0 ,就說明 fork 返回了 0,也就說明當(dāng)前進(jìn)程是子進(jìn)程,就去執(zhí)行 printf(i
27、 am the child!) ,否則( else),當(dāng)前進(jìn)程就是父進(jìn)程,執(zhí)行printf(i am the parent!)。完美主義者會覺得這很冗余, 因為兩個進(jìn)程里都各有一條它們永遠(yuǎn)執(zhí)行不到的語句。不必過于為此耿耿于懷,畢竟很多年以前, unix 的鼻祖?zhèn)冊诋?dāng)時內(nèi)存小得無法想象的計算機(jī)上就是這樣寫程序的,以我們?nèi)缃竦摹昂A俊眱?nèi)存,完全可以把這幾個字節(jié)的顧慮拋到九霄云外。說到這里,可能有些讀者還有疑問:如果fork 后子進(jìn)程和父進(jìn)程幾乎完全一樣,而系統(tǒng)中產(chǎn)生新進(jìn)程唯一的方法就是fork ,那豈不是系統(tǒng)中所有的進(jìn)程都要一模一樣嗎?那我們要執(zhí)行新的應(yīng)用程序時候怎么辦呢?從對linux系統(tǒng)的經(jīng)驗
28、中,我們知道這種問題并不存在。至于采用了什么方法,我們把這個問題留到后面具體討論。exit在 2.4.4 版內(nèi)核中, exit 是第 1 號調(diào)用,其在linux 函數(shù)庫中的原型是:#include voidexit(int status);不像 fork 那么難理解,從 exit 的名字就能看出,這個系統(tǒng)調(diào)用是用來終止一個進(jìn)程的。無論在程序中的什么位置,只要執(zhí)行到 exit 系統(tǒng)調(diào)用, 進(jìn)程就會停止剩下的所有操作,清除包括 pcb 在內(nèi)的各種數(shù)據(jù)結(jié)構(gòu),并終止本進(jìn)程的運(yùn)行。請看下面的程序:/*exit_test1.c */ #includemain()printf(“ tphrioscess w
29、ill exit!n” ); exit(0);printf(“ nebverdisplayed!n” );編譯后運(yùn)行:$gccexit_test1.c -o exit_test1$./exit_test1thisprocess will exit!我們可以看到,程序并沒有打印后面的“never be displayed!n”,因為在此之前,在執(zhí)行到 exit(0) 時,進(jìn)程就已經(jīng)終止了。exit 系統(tǒng)調(diào)用帶有一個整數(shù)類型的參數(shù)status,我們可以利用這個參數(shù)傳遞進(jìn)程結(jié)束時 的狀態(tài),比如說,該進(jìn)程是正常結(jié)束的,還是出現(xiàn)某種意外而結(jié)束的,一般來說,0 表示沒有意外的正常結(jié)束;其他的數(shù)值表示出現(xiàn)了
30、錯誤,進(jìn)程非正常結(jié)束。我們在實(shí)際編程時,可以用 wait 系統(tǒng)調(diào)用接收子進(jìn)程的返回值,從而針對不同的情況進(jìn)行不同的處理。關(guān)于wait的詳細(xì)情況,我們將在以后的篇幅中進(jìn)行介紹。exit 和_exit作為系統(tǒng)調(diào)用而言, _exit 和 exit 是一對孿生兄弟,它們究竟相似到什么程度,我們可以從 linux 的源碼中找到答案:#definenrexitnr_exit /*摘自文件 include/asm-i386/unistd.h 第 334 行 */“nr_”是在 linux 的源碼中為每個系統(tǒng)調(diào)用加上的前綴,請注意第一個exit 前 有 2條下劃線,第二個exit 前只有 1 條下劃線。這時隨
31、便一個懂得c 語言并且頭腦清醒的人都會說,_exit 和 exit 沒有任何區(qū)別,但我們還要講一下這兩者之間的區(qū)別,這種區(qū)別主要體現(xiàn)在它們在函數(shù)庫中的定義。_exit在linux 函數(shù)庫中的原型是:#include void_exit(int status);和 exit 比較一下, exit() 函數(shù)定義在 stdlib.h 中,而_exit() 定義在 unistd.h 中,從名字上看,stdlib.h 似乎比 unistd.h 高級一點(diǎn), 那么,它們之間到底有什么區(qū)別呢?讓我們先來看流程圖, 通過下圖,我們會對這兩個系統(tǒng)調(diào)用的執(zhí)行過程產(chǎn)生一個較為直觀的認(rèn)識。從圖中可以看出, _exit(
32、) 函數(shù)的作用最為簡單:直接使進(jìn)程停止運(yùn)行,清除其使用的內(nèi)存空間,并銷毀其在內(nèi)核中的各種數(shù)據(jù)結(jié)構(gòu);exit() 函數(shù)則在這些基礎(chǔ)上作了一些包裝,在執(zhí)行退出之前加了若干道工序,也是因為這個原因, 有些人認(rèn)為 exit 已經(jīng)不能算是純粹的系統(tǒng)調(diào)用。exit() 函數(shù)與 _exit() 函數(shù)最大的區(qū)別就在于exit() 函數(shù)在調(diào)用 exit 系統(tǒng)調(diào)用之前要檢查文件的打開情況,把文件緩沖區(qū)中的內(nèi)容寫回文件,就是圖中的“清理i/o 緩沖”一項。在 linux 的標(biāo)準(zhǔn)函數(shù)庫中, 有一套稱作“高級 i/o ”的函數(shù), 我們熟知的 printf() 、fopen() 、fread()、 fwrite() 都在
33、此列,它們也被稱作“緩沖i/o (buffered i/o )”,其特征是對應(yīng)每一個 打開的文件,在內(nèi)存中都有一片緩沖區(qū),每次讀文件時,會多讀出若干條記錄,這樣下次讀文件時就可以直接從內(nèi)存的緩沖區(qū)中讀取,每次寫文件的時候, 也僅僅是寫入內(nèi)存中的緩沖區(qū),等滿足了一定的條件 (達(dá)到一定數(shù)量, 或遇到特定字符, 如換行符 n 和文件結(jié)束符 eof ),再將緩沖區(qū)中的內(nèi)容一次性寫入文件,這樣就大大增加了文件讀寫的速度,但也為我們編程 帶來了一點(diǎn)點(diǎn)麻煩。如果有一些數(shù)據(jù), 我們認(rèn)為已經(jīng)寫入了文件,實(shí)際上因為沒有滿足特定 的條件,它們還只是保存在緩沖區(qū)內(nèi),這時我們用_exit() 函數(shù)直接將進(jìn)程關(guān)閉,緩沖區(qū)
34、中的數(shù)據(jù)就會丟失,反之,如果想保證數(shù)據(jù)的完整性,就一定要使用exit() 函數(shù)。請看以下例程:/*exit2.c */ #includemain()printf(“ outpbuetginn” );printf(“ contiennbt uffer” ); exit(0);編譯并運(yùn)行:$gcc exit2.c -o exit2$./exit2outputbegin contentin buffer/*_exit1.c */#include main()printf(“ outpbuetginn” ); printf(“ contiennbt uffer” );_exit(0);編譯并運(yùn)行:$g
35、cc _exit1.c -o _exit1$./_exit1 outputbegin在 linux 中,標(biāo)準(zhǔn)輸入和標(biāo)準(zhǔn)輸出都是作為文件處理的,雖然是一類特殊的文件,但從程序員的角度來看, 它們和硬盤上存儲數(shù)據(jù)的普通文件并沒有任何區(qū)別。與所有其他文件一樣,它們在打開后也有自己的緩沖區(qū)。請讀者結(jié)合前面的敘述, 思考一下為什么這兩個程序會得出不同的結(jié)果。相信如果您理解了我前面所講的內(nèi)容,會很容易的得出結(jié)論。在這篇文章中, 我們對 linux 的進(jìn)程管理作了初步的了解,并在此基礎(chǔ)上學(xué)習(xí)了getpid 、fork 、exit 和 _exit 四個系統(tǒng)調(diào)用。在下一篇文章中,我們將學(xué)習(xí)與linux 進(jìn)程管理
36、相關(guān)的其他系統(tǒng)調(diào)用,并將作一些更深入的探討。僵尸進(jìn)程 3-1在前面的文章中,我們已經(jīng)了解了父進(jìn)程和子進(jìn)程的概念,并已經(jīng)掌握了系統(tǒng)調(diào)用exit 的用法,但可能很少有人意識到,在一個進(jìn)程調(diào)用了exit 之后,該進(jìn)程并非馬上就消失掉, 而是留下一個稱為僵尸進(jìn)程(zombie )的數(shù)據(jù)結(jié)構(gòu)。在linux 進(jìn)程的 5 種狀態(tài)中,僵尸進(jìn)程是非常特殊的一種, 它已經(jīng)放棄了幾乎所有內(nèi)存空間,沒有任何可執(zhí)行代碼, 也不能被調(diào)度, 僅僅在進(jìn)程列表中保留一個位置,記載該進(jìn)程的退出狀態(tài)等信息供其他進(jìn)程收集,除此之外, 僵尸進(jìn)程不再占有任何內(nèi)存空間。從這點(diǎn)來看, 僵尸進(jìn)程雖然有一個很酷的名字,但它的影響力遠(yuǎn)遠(yuǎn)抵不上那些
37、真正的僵尸兄弟,真正的僵尸總能令人感到恐怖,而僵尸進(jìn)程卻除了留下一些供人憑吊的信息,對系統(tǒng)毫無作用。也許讀者們還對這個新概念比較好奇,那就讓我們來看一眼linux 里的僵尸進(jìn)程究竟長什么樣子。當(dāng)一個進(jìn)程已退出,但其父進(jìn)程還沒有調(diào)用系統(tǒng)調(diào)用wait(稍后介紹) 對其進(jìn)行收集之前的這段時間里,它會一直保持僵尸狀態(tài),利用這個特點(diǎn),我們來寫一個簡單的小程序:/* zombie.c */#include #includemain()pid_t pid; pid=fork();if(pid0) /*如果出錯 */printf(erroroccurred!n);else if(pid=0)/*如果是子進(jìn)程
38、*/ exit(0);else/*如果是父進(jìn)程 */sleep(60);/*休眠 60 秒,這段時間里,父進(jìn)程什么也干不了*/ wait(null);/*收集僵尸進(jìn)程 */sleep 的作用是讓進(jìn)程休眠指定的秒數(shù),在這60 秒內(nèi),子進(jìn)程已經(jīng)退出,而父進(jìn)程正忙著睡覺,不可能對它進(jìn)行收集,這樣,我們就能保持子進(jìn)程60 秒的僵尸狀態(tài)。編譯這個程序:$ cc zombie.c -o zombie后臺運(yùn)行程序,以使我們能夠執(zhí)行下一條命令:$./zombie & 11577列一下系統(tǒng)內(nèi)的進(jìn)程:$ps -ax. .1177pts/0s0:00-bash1577pts/0s0:00./zombie1578pt
39、s/0z0:00zombie 1579pts/0r0:00ps -ax看到中間的“ z”了嗎?那就是僵尸進(jìn)程的標(biāo)志,它表示1578 號進(jìn)程現(xiàn)在就是一個僵尸進(jìn)程。我們已經(jīng)學(xué)習(xí)了系統(tǒng)調(diào)用exit ,它的作用是使進(jìn)程退出,但也僅僅限于將一個正常的進(jìn)程變成一個僵尸進(jìn)程,并不能將其完全銷毀。僵尸進(jìn)程雖然對其他進(jìn)程幾乎沒有什么影響, 不占用 cpu 時間,消耗的內(nèi)存也幾乎可以忽略不計,但有它在那里呆著,還是讓人覺得心里很不舒服。而且linux 系統(tǒng)中進(jìn)程數(shù)目是有限制的,在一些特殊的情況下,如果存在太多的僵尸進(jìn)程,也會影響到新進(jìn)程的產(chǎn)生。那么,我們該如何來消滅這些僵尸進(jìn)程呢?先來了解一下僵尸進(jìn)程的來由,我們
40、知道,linux 和 unix 總有著剪不斷理還亂的親緣關(guān)系, 僵尸進(jìn)程的概念也是從unix 上繼承來的, 而 unix的先驅(qū)們設(shè)計這個東西并非是因為閑來無聊想煩煩其他的程序員。僵尸進(jìn)程中保存著很多對程序員和系統(tǒng)管理員非常重要的 信息,首先,這個進(jìn)程是怎么死亡的?是正常退出呢,還是出現(xiàn)了錯誤,還是被其它進(jìn)程強(qiáng)迫退出的?其次, 這個進(jìn)程占用的總系統(tǒng)cpu 時間和總用戶 cpu 時間分別是多少?發(fā)生頁錯誤的數(shù)目和收到信號的數(shù)目。這些信息都被存儲在僵尸進(jìn)程中,試想如果沒有僵尸進(jìn)程,進(jìn)程一退出,所有與之相關(guān)的信息都立刻歸于無形,而此時程序員或系統(tǒng)管理員需要用到, 就只好干瞪眼了。僵尸進(jìn)程 3-2那么,
41、我們?nèi)绾问占@些信息,并終結(jié)這些僵尸進(jìn)程呢?就要靠我們下面要講到的waitpid 調(diào)用和 wait 調(diào)用。這兩者的作用都是收集僵尸進(jìn)程留下的信息,同時使這個進(jìn)程徹底消失。下面就對這兩個調(diào)用分別作詳細(xì)介紹。waitwait 的函數(shù)原型是:#include /*提供類型 pid_t 的定義 */ #includepid_t wait(int *status)進(jìn)程一旦調(diào)用了wait ,就立即阻塞自己,由wait 自動分析是否當(dāng)前進(jìn)程的某個子進(jìn)程已經(jīng)退出, 如果讓它找到了這樣一個已經(jīng)變成僵尸的子進(jìn)程,wait 就會收集這個子進(jìn)程的信息,并把它徹底銷毀后返回;如果沒有找到這樣一個子進(jìn)程,wait 就會一
42、直阻塞在這里,直到有一個出現(xiàn)為止。參數(shù) status 用來保存被收集進(jìn)程退出時的一些狀態(tài),它是一個指向 int 類型的指針。但如果我們對這個子進(jìn)程是如何死掉的毫不在意,只想把這個僵尸進(jìn)程消滅掉, (事實(shí)上絕大多數(shù)情況下,我們都會這樣想) ,我們就可以設(shè)定這個參數(shù)為 null ,就象下面這樣:pid= wait(null);如果成功, wait 會返回被收集的子進(jìn)程的進(jìn)程id ,如果調(diào)用進(jìn)程沒有子進(jìn)程,調(diào)用就會失敗,此時wait 返回 -1,同時 errno 被置為 echild 。實(shí)戰(zhàn)下面就讓我們用一個例子來實(shí)戰(zhàn)應(yīng)用一下wait 調(diào)用,程序中用到了系統(tǒng)調(diào)用fork ,如果你對此不大熟悉或已經(jīng)忘
43、記了,請參考上一篇文章進(jìn)程管理相關(guān)的系統(tǒng)調(diào)用(1)。/*wait1.c */#include #include #include #includemain()pid_t pc, pr; pc=fork();if(pc0)/*如果出錯 */printf(errorocurred!n);else if(pc=0)/*如果是子進(jìn)程 */printf(this is child process with pid of %dn, getpid(); sleep(10);/*睡眠 10 秒鐘 */else/*如果是父進(jìn)程 */pr=wait(null);/*在這里等待 */printf(i catched
44、 a child process with pid of %dn), pr);exit(0);編譯并運(yùn)行 :$ cc wait1.c -o wait1$ ./wait1this is child process with pid of 1508i catched a child process with pid of 1508可以明顯注意到,在第2 行結(jié)果打印出來前有10 秒鐘的等待時間,這就是我們設(shè)定的讓子進(jìn)程睡眠的時間, 只有子進(jìn)程從睡眠中蘇醒過來,它才能正常退出, 也就才能被父進(jìn)程捕捉到。 其實(shí)這里我們不管設(shè)定子進(jìn)程睡眠的時間有多長,父進(jìn)程都會一直等待下去,讀者如果有興趣的話,可以試著自
45、己修改一下這個數(shù)值,看看會出現(xiàn)怎樣的結(jié)果。參數(shù) status如果參數(shù) status 的值不是 null ,wait 就會把子進(jìn)程退出時的狀態(tài)取出并存入其中,這是一個整數(shù)值( int ),指出了子進(jìn)程是正常退出還是被非正常結(jié)束的(一個進(jìn)程也可以被其 他進(jìn)程用信號結(jié)束,我們將在以后的文章中介紹),以及正常結(jié)束時的返回值,或被哪一個信號結(jié)束的等信息。 由于這些信息被存放在一個整數(shù)的不同二進(jìn)制位中,所以用常規(guī)的方法讀取會非常麻煩,人們就設(shè)計了一套專門的宏(macro)來完成這項工作,下面我們來學(xué)習(xí)一下其中最常用的兩個:僵尸進(jìn)程 3-3 wifexited(status)這個宏用來指出子進(jìn)程是否為正常退
46、出的,如果是,它會返回一個非零值。(請注意,雖然名字一樣,這里的參數(shù)status 并不同于 wait 唯一的參數(shù) - 指向整數(shù)的指針 status,而是那個指針?biāo)赶虻恼麛?shù),切記不要搞混了。) wexitstatus(status)當(dāng) wifexited返回非零值時, 我們可以用這個宏來提取子進(jìn)程的返回值,如果子進(jìn)程調(diào) 用 exit(5) 退 出 , wexitstatus(status)就 會 返 回 5 ; 如 果 子 進(jìn) 程 調(diào) 用 exit(7) , wexitstatus(status)就會返回7 。請注意,如果進(jìn)程不是正常退出的,也就是說, wifexited返回 0,這個值就毫無
47、意義。下面通過例子來實(shí)戰(zhàn)一下我們剛剛學(xué)到的內(nèi)容:/*wait2.c */#include #include #includemain()int status; pid_t pc, pr; pc=fork();if(pc0)/*如果出錯 */printf(errorocurred!n); else if(pc=0)/*子進(jìn)程 */printf(this is child process with pid of %d.n, getpid();exit(3);/*子進(jìn)程返回 3 */else/*父 進(jìn) 程 */ pr=wait(&status);if(wifexited(status)/*如果 wi
48、fexited返回非零值 */ printf(the child process %d exit normally.n, pr);printf(thereturn code is %d.n, wexitstatus(status);else/*如果 wifexited返回零 */printf(the child process %d exit abnormally.n, pr);編譯并運(yùn)行 :$ cc wait2.c -o wait2$ ./wait2this is child process with pid of 1538. the child process 1538 exit norm
49、ally.thereturn code is 3.父進(jìn)程準(zhǔn)確捕捉到了子進(jìn)程的返回值3,并把它打印了出來。當(dāng)然, 處理進(jìn)程退出狀態(tài)的宏并不止這兩個,但它們當(dāng)中的絕大部分在平時的編程中很少用到,就也不在這里浪費(fèi)篇幅介紹了,有興趣的讀者可以自己參閱linux man pages 去了解它們的用法。進(jìn)程同步有時候, 父進(jìn)程要求子進(jìn)程的運(yùn)算結(jié)果進(jìn)行下一步的運(yùn)算,或者子進(jìn)程的功能是為父進(jìn)程提供了下一步執(zhí)行的先決條件(如:子進(jìn)程建立文件,而父進(jìn)程寫入數(shù)據(jù)),此時父進(jìn)程就必須在某一個位置停下來,等待子進(jìn)程運(yùn)行結(jié)束,而如果父進(jìn)程不等待而直接執(zhí)行下去的 話,可以想見,會出現(xiàn)極大的混亂。這種情況稱為進(jìn)程之間的同步,
50、更準(zhǔn)確地說,這是進(jìn)程同步的一種特例。進(jìn)程同步就是要協(xié)調(diào)好2 個以上的進(jìn)程,使之以安排好地次序依次執(zhí)行。解決進(jìn)程同步問題有更通用的方法,我們將在以后介紹, 但對于我們假設(shè)的這種情況,則完全可以用 wait 系統(tǒng)調(diào)用簡單的予以解決。請看下面這段程序:#include #includemain()pid_t pc, pr; int status; pc=fork(); if(pc0)printf(erroroccured on forking.n); else if(pc=0)/*子進(jìn)程的工作 */exit(0);else/*父進(jìn)程的工作 */ pr=wait(&status);/*利用子進(jìn)程的結(jié)果
51、*/這段程序只是個例子,不能真正拿來執(zhí)行,但它卻說明了一些問題,首先,當(dāng)fork調(diào)用成功后, 父子進(jìn)程各做各的事情, 但當(dāng)父進(jìn)程的工作告一段落, 需要用到子進(jìn)程的結(jié)果時, 它就停下來調(diào)用 wait ,一直等到子進(jìn)程運(yùn)行結(jié)束,然后利用子進(jìn)程的結(jié)果繼續(xù)執(zhí)行,這樣就圓滿地解決了我們提出的進(jìn)程同步問題。waitpidwaitpid 系統(tǒng)調(diào)用在 linux 函數(shù)庫中的原型是:#include /*提供類型 pid_t 的定義 */ #includepid_t waitpid(pid_t pid, int *status, int options)從本質(zhì)上講,系統(tǒng)調(diào)用 waitpid 和 wait 的作用
52、是完全相同的,但 waitpid 多出了兩個可由用戶控制的參數(shù) pid 和 options ,從而為我們編程提供了另一種更靈活的方式。 下面我們就來詳細(xì)介紹一下這兩個參數(shù): pid從參數(shù)的名字 pid 和類型 pid_t 中就可以看出,這里需要的是一個進(jìn)程 id 。但當(dāng) pid 取不同的值時,在這里有不同的意義。僵尸進(jìn)程 3-4pid0 時,只等待進(jìn)程 id 等于 pid 的子進(jìn)程, 不管其它已經(jīng)有多少子進(jìn)程運(yùn)行結(jié)束退出了,只要指定的子進(jìn)程還沒有結(jié)束,waitpid 就會一直等下去。pid=-1 時,等待任何一個子進(jìn)程退出,沒有任何限制,此時 waitpid 和 wait 的作用一模一樣。pid=0時,等待同一個進(jìn)程組中的任何子進(jìn)程,如果子進(jìn)程已經(jīng)加入了別的進(jìn)程組,waitpid 不會對它做任何理睬。pid-1 時,等待一個指定進(jìn)程組中的任何子進(jìn)程,這個進(jìn)程組的id 等于 pid 的絕對值。 optionsoptions提供了一些額外的選項來控制waitpid ,目前在 linux 中只支持 wnohang和wuntraced兩個選項,這是
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 承攬合同模板心得體會
- 房屋出售合同范例照
- 分立器件供貨合同模板
- 彩板購銷合同范例
- 發(fā)型師工作合同范例
- 房產(chǎn)過戶糾紛合同范例
- 工地范例改裝合同范例
- 2024年南京客運(yùn)資格證條件
- 2024年濟(jì)南客運(yùn)證考試模擬題考試
- 2024年南寧小型客運(yùn)從業(yè)資格證2024年考試題
- 悅納兒童的文化生長東莞市莞城中心小學(xué)“悅納教育”的思與行
- 2022年春期2064國開電大??啤豆芾韺W(xué)基礎(chǔ)》紙質(zhì)形成性考核冊答案
- 機(jī)械加工初步報價自動計算(含各種工時費(fèi))
- 碳酸氫鎂介穩(wěn)溶液應(yīng)用于萃取分離稀土過程中的基礎(chǔ)研究
- 城市地下綜合管廊施工組織設(shè)計
- 中國舞蹈考級細(xì)則
- 2023年中國鹽業(yè)集團(tuán)有限公司招聘筆試題庫及答案解析
- 2022年港口危險貨物安全管理人員機(jī)考試題(含答案)
- YY/T 0471.2-2004接觸性創(chuàng)面敷料試驗方法 第2部分:透氣膜敷料水蒸氣透過率
- GB/T 34722-2017浸漬膠膜紙飾面膠合板和細(xì)木工板
- GB/T 30306-2013家用和類似用途飲用水處理內(nèi)芯
評論
0/150
提交評論