Linux 操作系統(tǒng)分析實(shí)驗(yàn)指導(dǎo)書(2015)_第1頁
Linux 操作系統(tǒng)分析實(shí)驗(yàn)指導(dǎo)書(2015)_第2頁
Linux 操作系統(tǒng)分析實(shí)驗(yàn)指導(dǎo)書(2015)_第3頁
Linux 操作系統(tǒng)分析實(shí)驗(yàn)指導(dǎo)書(2015)_第4頁
Linux 操作系統(tǒng)分析實(shí)驗(yàn)指導(dǎo)書(2015)_第5頁
已閱讀5頁,還剩28頁未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

1、Linux 操作系統(tǒng)分析實(shí)驗(yàn)指導(dǎo)書計(jì)算機(jī)科學(xué)與教育軟件學(xué)院計(jì)算機(jī)科學(xué)系實(shí)驗(yàn)一 進(jìn)程控制與進(jìn)程互斥:Linux系統(tǒng)下多進(jìn)程與多線程編程 -2實(shí)驗(yàn)二 進(jìn)程通信:Linux系統(tǒng)的進(jìn)程間通信-管道通信 -9 實(shí)驗(yàn)三 內(nèi)存分配與回收:Linux系統(tǒng)下利用鏈表實(shí)現(xiàn)動態(tài)內(nèi)存分配 (也可以做課本中的內(nèi)存管理實(shí)例) -11實(shí)驗(yàn)四 文件操作算法: Linux系統(tǒng)下文件管理模擬實(shí)驗(yàn) -18 實(shí)驗(yàn)五 設(shè)備驅(qū)動: Linux系統(tǒng)下的字符設(shè)備驅(qū)動程序編程 -22 實(shí)驗(yàn)一 進(jìn)程控制與進(jìn)程互斥:Linux系統(tǒng)下多進(jìn)程與多線程編程一、實(shí)驗(yàn)?zāi)康?、理解Linux下進(jìn)程的結(jié)構(gòu);2、理解Linux下產(chǎn)生新進(jìn)程的方法(系統(tǒng)調(diào)用fork

2、函數(shù));3、掌握如何啟動另一程序的執(zhí)行;4、理解Linux下線程的結(jié)構(gòu);5、理解Linux下產(chǎn)生新線程的方法;6、理解Linux系統(tǒng)下多進(jìn)程與多線程的區(qū)別二、實(shí)驗(yàn)內(nèi)容1、利用fork函數(shù)創(chuàng)建新進(jìn)程,并根據(jù)fork函數(shù)的返回值,判斷自己是處于父進(jìn)程還是子進(jìn)程中;2、在新創(chuàng)建的子進(jìn)程中,使用exec類的函數(shù)啟動另一程序的執(zhí)行;分析多進(jìn)程時系統(tǒng)的運(yùn)行狀態(tài)和輸出結(jié)果;3、利用最常用的三個函數(shù)pthread_create,pthread_join和pthread_exit編寫了一個最簡單的多線程程序。理解多線程的運(yùn)行和輸出情況;4、利用信號量機(jī)制控制多線程的運(yùn)行順序,并實(shí)現(xiàn)多線程中數(shù)據(jù)的共享;5、分析L

3、inux系統(tǒng)下多進(jìn)程與多線程中的區(qū)別。三、實(shí)驗(yàn)指導(dǎo)(一)Linux系統(tǒng)下多進(jìn)程編程:1、理解Linux下進(jìn)程的結(jié)構(gòu)Linux下一個進(jìn)程在內(nèi)存里有三部份的數(shù)據(jù),就是“數(shù)據(jù)段”,“堆棧段”和“代碼段”,一般的CPU, 如I386,都有上述三種段寄存器,以方便操作系統(tǒng)的運(yùn)行。“代碼段”,顧名思義,就是存放了程序代碼的數(shù)據(jù),假如機(jī)器中有數(shù)個進(jìn)程運(yùn)行相同的一個程序,那么它們就可以使用同一個代碼段。 堆棧段存放的就是子程序的返回地址、子程序的參數(shù)以及程序的局部變量。而數(shù)據(jù)段則存放程序的全局變量,常數(shù)以及動態(tài)數(shù)據(jù)分配的數(shù)據(jù)空間(比如用malloc之類的函數(shù)取得的空間)。系統(tǒng)如果同時運(yùn)行數(shù)個相同的程

4、序,它們之間就不能使用同一個堆棧段和數(shù)據(jù)段。2、如何使用fork函數(shù)在Linux下產(chǎn)生新進(jìn)程的系統(tǒng)調(diào)用就是fork函數(shù),這個函數(shù)名是英文中“分叉”的意思。一個進(jìn)程在運(yùn)行中,如果使用了fork,就產(chǎn)生了另一個進(jìn)程,于是進(jìn)程就“分叉”了,所以這個名字取得很形象。那么調(diào)用這個fork函數(shù)時發(fā)生了什么呢?一個程序調(diào)用fork函數(shù),系統(tǒng)就為一個新的進(jìn)程準(zhǔn)備了前述三個段,首先,系統(tǒng)讓新的進(jìn)程與舊的進(jìn)程使用同一個代碼段,因?yàn)樗鼈兊某绦蜻€是相同的,對于數(shù)據(jù)段和堆棧段,系統(tǒng)則復(fù)制一份給新的進(jìn)程,這樣,父進(jìn)程的所有數(shù)據(jù)都可以留給子進(jìn)程,但是,子進(jìn)程一旦開始運(yùn)行,雖然它繼承了父進(jìn)程的一切數(shù)據(jù),但實(shí)際上數(shù)據(jù)卻已經(jīng)分開

5、,相互之間不再有影響了,也就是說,它們之間不再共享任何數(shù)據(jù)了。而如果兩個進(jìn)程要共享什么數(shù)據(jù)的話,就要使用另一套函數(shù)(shmget,shmat,shmdt等)來操作。現(xiàn)在,已經(jīng)是兩個進(jìn)程了,對于父進(jìn)程,fork函數(shù)返回了子程序的進(jìn)程號,而對于子程序,fork函數(shù)則返回零,這樣,對于程序,只要判斷fork函數(shù)的返回值,就知道自己是處于父進(jìn)程還是子進(jìn)程中。 但是,如果一個大程序在運(yùn)行中,它的數(shù)據(jù)段和堆棧都很大,調(diào)用一次fork就要復(fù)制一次,那么fork的系統(tǒng)開銷不是很大嗎?其實(shí),一般CPU都是以“頁”為單位分配空間的,像INTEL的CPU,其一頁在通常情況下是4K字節(jié)大小,而無論是數(shù)據(jù)段還

6、是堆棧段都是由許多“頁”構(gòu)成的,fork函數(shù)復(fù)制這兩個段,只是“邏輯”上的,并非“物理”上的,也就是說,實(shí)際執(zhí)行fork時,物理空間上兩個進(jìn)程的數(shù)據(jù)段和堆棧段都還是共享著的,當(dāng)有一個進(jìn)程寫了某個數(shù)據(jù)時,利用寫時復(fù)制技術(shù),該進(jìn)程的數(shù)據(jù)被寫入另一數(shù)據(jù)段,這時兩個進(jìn)程之間的數(shù)據(jù)才有了區(qū)別,系統(tǒng)就將有區(qū)別的“頁”從物理上也分開。系統(tǒng)在空間上的開銷就可以達(dá)到最小。3、如何啟動另一程序的執(zhí)行在Linux中要使用exec類的函數(shù)來啟動另一程序的執(zhí)行,exec類的函數(shù)不止一個,但大致相同,在Linux中,它們分別是:execl,execlp,execle,execv,execve和execvp。一個進(jìn)程一旦調(diào)

7、用exec類函數(shù),它本身就“死亡”了,系統(tǒng)把代碼段替換成新的程序的代碼,廢棄原有的數(shù)據(jù)段和堆棧段,并為新程序分配新的數(shù)據(jù)段與堆棧段,唯一留下的,就是進(jìn)程號,也就是說,對系統(tǒng)而言,還是同一個進(jìn)程,不過已經(jīng)是另一個程序了。(不過exec類函數(shù)中有的還允許繼承環(huán)境變量之類的信息)。那么如果我的程序想啟動另一程序的執(zhí)行但自己仍想繼續(xù)運(yùn)行的話,怎么辦呢?那就是結(jié)合fork與exec的使用。下面一段代碼顯示如何啟動運(yùn)行其它程序: #include<stdio.h>#include<unistd.h>main( ) int pid; pid=fork( ); /*創(chuàng)建子進(jìn)程*/swi

8、tch(pid) case -1: /*創(chuàng)建失敗*/ printf("fork fail!n"); exit(1); case 0: /*子進(jìn)程*/ execl("/bin/ls","ls","-1","-color",NULL); printf("exec fail!n"); exit(1); default: /*父進(jìn)程*/ wait(NULL); /*同步*/ printf("ls completed !n"); exit(0); 運(yùn)行結(jié)果執(zhí)行命令l

9、s -l -color ,(按倒序)列出當(dāng)前目錄下所有文件和子目錄;ls completed!程序在調(diào)用fork( )建立一個子進(jìn)程后,馬上調(diào)用wait( ),使父進(jìn)程在子進(jìn)程結(jié)束之前,一直處于睡眠狀態(tài)。子進(jìn)程用exec( )裝入命令ls ,exec( )后,子進(jìn)程的代碼被ls的代碼取代,這時子進(jìn)程的PC指向ls的第1條語句,開始執(zhí)行l(wèi)s的命令代碼。(二)Linux系統(tǒng)下多線程編程:1、為什么有了進(jìn)程的概念后,還要再引入線程呢?使用多線程到底有哪些好處?什么的系統(tǒng)應(yīng)該選用多線程?線程(thread)技術(shù)早在60年代就被提出,但真正應(yīng)用多線程到操作系統(tǒng)中去,是在80年代中期,solaris是這方

10、面的佼佼者。傳統(tǒng)的Unix也支持線程的概念,但是在一個進(jìn)程(process)中只允許有一個線程,這樣多線程就意味著多進(jìn)程。現(xiàn)在,多線程技術(shù)已經(jīng)被許多操作系統(tǒng)所支持,包括Windows/NT,當(dāng)然,也包括Linux。 使用多線程的理由之一是和進(jìn)程相比,它是一種非常"節(jié)儉"的多任務(wù)操作方式。在Linux系統(tǒng)下,啟動一個新的進(jìn)程必須分配給它獨(dú)立的地址空間,建立眾多的數(shù)據(jù)表來維護(hù)它的代碼段、堆棧段和數(shù)據(jù)段,這是一種"昂貴"的多任務(wù)工作方式。而運(yùn)行于一個進(jìn)程中的多個線程,它們彼此之間使用相同的地址空間,共享大部分?jǐn)?shù)據(jù),啟動一個線程所花費(fèi)的空間遠(yuǎn)遠(yuǎn)小于啟動一個進(jìn)程所

11、花費(fèi)的空間,而且,線程間彼此切換所需的時間也遠(yuǎn)遠(yuǎn)小于進(jìn)程間切換所需要的時間。據(jù)統(tǒng)計(jì),總的說來,一個進(jìn)程的開銷大約是一個線程開銷的30倍左右,當(dāng)然,在具體的系統(tǒng)上,這個數(shù)據(jù)可能會有較大的區(qū)別。使用多線程的理由之二是線程間方便的通信機(jī)制。對不同進(jìn)程來說,它們具有獨(dú)立的數(shù)據(jù)空間,要進(jìn)行數(shù)據(jù)的傳遞只能通過通信的方式進(jìn)行,這種方式不僅費(fèi)時,而且很不方便。線程則不然,由于同一進(jìn)程下的線程之間共享數(shù)據(jù)空間,所以一個線程的數(shù)據(jù)可以直接為其它線程所用,這不僅快捷,而且方便。當(dāng)然,數(shù)據(jù)的共享也帶來其他一些問題,有的變量不能同時被兩個線程所修改,有的子程序中聲明為static的數(shù)據(jù)更有可能給多線程程序帶來災(zāi)難性的打

12、擊,這些正是編寫多線程程序時最需要注意的地方。除了以上所說的優(yōu)點(diǎn)外,多線程程序作為一種多任務(wù)、并發(fā)的工作方式,還有以下的優(yōu)點(diǎn):1) 提高應(yīng)用程序響應(yīng)。這對圖形界面的程序尤其有意義,當(dāng)一個操作耗時很長時,整個系統(tǒng)都會等待這個操作,此時程序不會響應(yīng)鍵盤、鼠標(biāo)、菜單的操作,而使用多線程技術(shù),將耗時長的操作(time consuming)置于一個新的線程,可以避免這種尷尬的情況。2) 使多CPU系統(tǒng)更加有效。操作系統(tǒng)會保證當(dāng)線程數(shù)不大于CPU數(shù)目時,不同的線程運(yùn)行于不同的CPU上。3) 改善程序結(jié)構(gòu)。一個既長又復(fù)雜的進(jìn)程可以考慮分為多個線程,成為幾個獨(dú)立或半獨(dú)立的運(yùn)行部分,這樣的程序會利于理解和修改。

13、2、簡單的多線程編程Linux系統(tǒng)下的多線程遵循POSIX線程接口,稱為pthread。編寫Linux下的多線程程序,需要使用頭文件pthread.h,連接時需要使用庫libpthread.a。順便說一下,Linux下pthread的實(shí)現(xiàn)是通過系統(tǒng)調(diào)用clone()來實(shí)現(xiàn)的。clone()是Linux所特有的系統(tǒng)調(diào)用,它的使用方式類似fork。下面展示一個最簡單的多線程程序example1.c。/* example.c*/#include<stdio.h> #include<pthreadtypes.h> void thread(void)int i;for(i=0;i

14、<3;i+)printf("This is a pthread.n");int main(void)pthread_t id;int i,ret;ret=pthread_create(&id,NULL,(void *) thread,NULL);if(ret!=0)printf ("Create pthread error!n");exit (1);for(i=0;i<3;i+)printf("This is the main process.n");pthread_join(id,NULL);return (0)

15、;我們編譯此程序:gcc example1.c -lpthread -o example1運(yùn)行example1,我們得到如下結(jié)果:This is the main process.This is a pthread.This is the main process.This is the main process.This is a pthread.This is a pthread.再次運(yùn)行,我們可能得到如下結(jié)果:This is a pthread.This is the main process.This is a pthread.This is the main process.This

16、 is a pthread.This is the main process.前后兩次結(jié)果不一樣,這是兩個線程爭奪CPU資源的結(jié)果。上面的示例中,我們使用到了兩個函數(shù),pthread_create和pthread_join,并聲明了一個pthread_t型的變量。pthread_t在頭文件/usr/include/bits/pthreadtypes.h中定義:typedef unsigned long int pthread_t;它是一個線程的標(biāo)識符。函數(shù)pthread_create用來創(chuàng)建一個線程,它的原型為:extern int pthread_create _P (pthread_t *

17、_thread, _const pthread_attr_t *_attr,void *(*_start_routine) (void *), void *_arg);第一個參數(shù)為指向線程標(biāo)識符的指針,第二個參數(shù)用來設(shè)置線程屬性,第三個參數(shù)是線程運(yùn)行函數(shù)的起始地址,最后一個參數(shù)是運(yùn)行函數(shù)的參數(shù)。這里,我們的函數(shù)thread不需要參數(shù),所以最后一個參數(shù)設(shè)為空指針。第二個參數(shù)我們也設(shè)為空指針,這樣將生成默認(rèn)屬性的線程。當(dāng)創(chuàng)建線程成功時,函數(shù)返回0,若不為0則說明創(chuàng)建線程失敗,常見的錯誤返回代碼為EAGAIN和EINVAL。前者表示系統(tǒng)限制創(chuàng)建新的線程,例如線程數(shù)目過多了;后者表示第二個參數(shù)代表的線

18、程屬性值非法。創(chuàng)建線程成功后,新創(chuàng)建的線程則運(yùn)行參數(shù)三和參數(shù)四確定的函數(shù),原來的線程則繼續(xù)運(yùn)行下一行代碼。 函數(shù)pthread_join用來等待一個線程的結(jié)束。函數(shù)原型為:extern int pthread_join _P (pthread_t _th, void *_thread_return);第一個參數(shù)為被等待的線程標(biāo)識符,第二個參數(shù)為一個用戶定義的指針,它可以用來存儲被等待線程的返回值。這個函數(shù)是一個線程阻塞的函數(shù),調(diào)用它的函數(shù)將一直等待到被等待的線程結(jié)束為止,當(dāng)函數(shù)返回時,被等待線程的資源被收回。一個線程的結(jié)束有兩種途徑,一種是象我們上面的例子一樣,函數(shù)結(jié)束了,調(diào)用它的線程也就結(jié)束

19、了;另一種方式是通過函數(shù)pthread_exit來實(shí)現(xiàn)。它的函數(shù)原型為:extern void pthread_exit _P (void *_retval) _attribute_ (_noreturn_);唯一的參數(shù)是函數(shù)的返回代碼,只要pthread_join中的第二個參數(shù)thread_return不是NULL,這個值將被傳遞給thread_return。最后要說明的是,一個線程不能被多個線程等待,否則第一個接收到信號的線程成功返回,其余調(diào)用pthread_join的線程則返回錯誤代碼ESRCH。3、線程的數(shù)據(jù)處理和進(jìn)程相比,線程的最大優(yōu)點(diǎn)之一是數(shù)據(jù)的共享性,各個進(jìn)程共享父進(jìn)程處沿襲的數(shù)

20、據(jù)段,可以方便的獲得、修改數(shù)據(jù)。但這也給多線程編程帶來了許多問題。我們必須當(dāng)心有多個不同的進(jìn)程訪問相同的變量。許多函數(shù)是不可重入的,即同時不能運(yùn)行一個函數(shù)的多個拷貝(除非使用不同的數(shù)據(jù)段)。在函數(shù)中聲明的靜態(tài)變量常常帶來問題,函數(shù)的返回值也會有問題。因?yàn)槿绻祷氐氖呛瘮?shù)內(nèi)部靜態(tài)聲明的空間的地址,則在一個線程調(diào)用該函數(shù)得到地址后使用該地址指向的數(shù)據(jù)時,別的線程可能調(diào)用此函數(shù)并修改了這一段數(shù)據(jù)。在進(jìn)程中共享的變量必須用關(guān)鍵字volatile來定義,這是為了防止編譯器在優(yōu)化時(如gcc中使用-OX參數(shù))改變它們的使用方式。為了保護(hù)變量,我們必須使用信號量、互斥等方法來保證我們對變量的正確使用。4、信

21、號量信號量本質(zhì)上是一個非負(fù)的整數(shù)計(jì)數(shù)器,它被用來控制對公共資源的訪問。當(dāng)公共資源增加時,調(diào)用函數(shù)sem_post()增加信號量。只有當(dāng)信號量值大于時,才能使用公共資源,使用后,函數(shù)sem_wait()減少信號量。函數(shù)sem_trywait()和函數(shù)pthread_ mutex_trylock()起同樣的作用,它是函數(shù)sem_wait()的非阻塞版本。下面我們逐個介紹和信號量有關(guān)的一些函數(shù),它們都在頭文件/usr/include/semaphore.h中定義。信號量的數(shù)據(jù)類型為結(jié)構(gòu)sem_t,它本質(zhì)上是一個長整型的數(shù)。函數(shù)sem_init()用來初始化一個信號量。它的原型為:extern int

22、 sem_init _P (sem_t *_sem, int _pshared, unsigned int _value);sem為指向信號量結(jié)構(gòu)的一個指針;pshared不為時此信號量在進(jìn)程間共享,否則只能為當(dāng)前進(jìn)程的所有線程共享;value給出了信號量的初始值。函數(shù)sem_post( sem_t *sem )用來增加信號量的值。當(dāng)有線程阻塞在這個信號量上時,調(diào)用這個函數(shù)會使其中的一個線程不在阻塞,選擇機(jī)制同樣是由線程的調(diào)度策略決定的。函數(shù)sem_wait( sem_t *sem )被用來阻塞當(dāng)前線程直到信號量sem的值大于0,解除阻塞后將sem的值減一,表明公共資源經(jīng)使用后減少。函數(shù)sem

23、_trywait ( sem_t *sem )是函數(shù)sem_wait()的非阻塞版本,它直接將信號量sem的值減一。函數(shù)sem_destroy(sem_t *sem)用來釋放信號量sem。下面看一個使用信號量的例子。在這個例子中,一共有4個線程,其中兩個線程負(fù)責(zé)從文件讀取數(shù)據(jù)到公共的緩沖區(qū),另兩個線程從緩沖區(qū)讀取數(shù)據(jù)作不同的處理(加和乘運(yùn)算)。/* File sem.c */#include<stdio.h> #include<pthreadtypes.h> #include <semaphore.h>#define MAXSTACK 100int stac

24、kMAXSTACK2;int size=0;sem_t sem;/* 從文件1.dat讀取數(shù)據(jù),每讀一次,信號量加一*/void ReadData1(void)FILE *fp=fopen("1.dat","r");while(!feof(fp)fscanf(fp,"%d %d",&stacksize0,&stacksize1);sem_post(&sem);+size;fclose(fp);/*從文件2.dat讀取數(shù)據(jù)*/void ReadData2(void)FILE *fp=fopen("2.d

25、at","r");while(!feof(fp)fscanf(fp,"%d %d",&stacksize0,&stacksize1);sem_post(&sem);+size;fclose(fp);/*阻塞等待緩沖區(qū)有數(shù)據(jù),讀取數(shù)據(jù)后,釋放空間,繼續(xù)等待*/void HandleData1(void)while(1)sem_wait(&sem);printf("Plus:%d+%d=%dn",stacksize0,stacksize1,stacksize0+stacksize1);-size;

26、void HandleData2(void)while(1)sem_wait(&sem);printf("Multiply:%d*%d=%dn",stacksize0,stacksize1,stacksize0*stacksize1);-size;int main(void)pthread_t t1,t2,t3,t4;sem_init(&sem,0,0);pthread_create(&t1,NULL,(void *)HandleData1,NULL);pthread_create(&t2,NULL,(void *)HandleData2,N

27、ULL);pthread_create(&t3,NULL,(void *)ReadData1,NULL);pthread_create(&t4,NULL,(void *)ReadData2,NULL);/* 防止程序過早退出,讓它在此無限期等待*/pthread_join(t1,NULL);在Linux下,用命令gcc -lpthread sem.c -o sem生成可執(zhí)行文件sem。 我們事先編輯好數(shù)據(jù)文件1.dat和2.dat,假設(shè)它們的內(nèi)容分別為1 2 3 4 5 6 7 8 9 10和 -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 ,我們運(yùn)行sem,得到

28、如下的結(jié)果:Multiply:-1*-2=2Plus:-1+-2=-3Multiply:9*10=90Plus:-9+-10=-19Multiply:-7*-8=56Plus:-5+-6=-11Multiply:-3*-4=12Plus:9+10=19Plus:7+8=15Plus:5+6=11從中我們可以看出各個線程間的競爭關(guān)系。而數(shù)值并未按我們原先的順序顯示出來,這是由于size這個數(shù)值被各個線程任意修改的緣故。這也往往是多線程編程要注意的問題。多線程編程是一個很有意思也很有用的技術(shù),使用多線程技術(shù)的網(wǎng)絡(luò)螞蟻是目前最常用的下載工具之一,使用多線程技術(shù)的grep比單線程的grep要快上幾倍,

29、類似的例子還有很多。希望大家能用多線程技術(shù)寫出高效實(shí)用的好程序來。四、實(shí)驗(yàn)步驟1、利用fork函數(shù)編寫一個簡單的多進(jìn)程程序,用ps命令查看系統(tǒng)中進(jìn)程的運(yùn)行狀況,并分析輸出結(jié)果。2、在上面的多進(jìn)程程序中利用exec函數(shù),啟動另一個程序的執(zhí)行。用ps命令顯示本機(jī)運(yùn)行的所有進(jìn)程的詳細(xì)列表,并分析列表中不同進(jìn)程的內(nèi)存占用情況。3、編寫一個最簡單的多線程程序。理解多線程的運(yùn)行和輸出情況;4、利用信號量機(jī)制控制多線程的運(yùn)行順序,分析多線程中數(shù)據(jù)的共享情況;5、分析Linux系統(tǒng)下多進(jìn)程與多線程中的區(qū)別。實(shí)驗(yàn)二 進(jìn)程通信:Linux系統(tǒng)的進(jìn)程間通信-管道通信 實(shí)驗(yàn)?zāi)康?、了解什么是管道2、熟悉UNIX/LI

30、NUX支持的管道通信方式實(shí)驗(yàn)內(nèi)容編寫程序?qū)崿F(xiàn)進(jìn)程的管道通信。用系統(tǒng)調(diào)用pipe( )建立一管道,二個子進(jìn)程P1和P2分別向管道各寫一句話: Child 1 is sending a message! Child 2 is sending a message!父進(jìn)程從管道中讀出二個來自子進(jìn)程的信息并顯示(要求先接收P1,后P2)。實(shí)驗(yàn)指導(dǎo)一、什么是管道UNIX系統(tǒng)在OS的發(fā)展上,最重要的貢獻(xiàn)之一便是該系統(tǒng)首創(chuàng)了管道(pipe)。這也是UNIX系統(tǒng)的一大特色。所謂管道,是指能夠連接一個寫進(jìn)程和一個讀進(jìn)程的、并允許它們以生產(chǎn)者消費(fèi)者方式進(jìn)行通信的一個共享文件,又稱為pipe文件。由寫進(jìn)程從管道的寫入

31、端(句柄1)將數(shù)據(jù)寫入管道,而讀進(jìn)程則從管道的讀出端(句柄0)讀出數(shù)據(jù)。句柄fd0句柄fd1 讀出端寫入端二、管道的類型:1、有名管道一個可以在文件系統(tǒng)中長期存在的、具有路徑名的文件。用系統(tǒng)調(diào)用mknod( )建立。它克服無名管道使用上的局限性,可讓更多的進(jìn)程也能利用管道進(jìn)行通信。因而其它進(jìn)程可以知道它的存在,并能利用路徑名來訪問該文件。對有名管道的訪問方式與訪問其他文件一樣,需先用open( )打開。2、無名管道一個臨時文件。利用pipe( )建立起來的無名文件(無路徑名)。只用該系統(tǒng)調(diào)用所返回的文件描述符來標(biāo)識該文件,故只有調(diào)用pipe( )的進(jìn)程及其子孫進(jìn)程才能識別此文件描述符,才能利用

32、該文件(管道)進(jìn)行通信。當(dāng)這些進(jìn)程不再使用此管道時,核心收回其索引結(jié)點(diǎn)。二種管道的讀寫方式是相同的,本文只講無名管道。3、pipe文件的建立分配磁盤和內(nèi)存索引結(jié)點(diǎn)、為讀進(jìn)程分配文件表項(xiàng)、為寫進(jìn)程分配文件表項(xiàng)、分配用戶文件描述符4、讀/寫進(jìn)程互斥內(nèi)核為地址設(shè)置一個讀指針和一個寫指針,按先進(jìn)先出順序讀、寫。為使讀、寫進(jìn)程互斥地訪問pipe文件,需使各進(jìn)程互斥地訪問pipe文件索引結(jié)點(diǎn)中的直接地址項(xiàng)。因此,每次進(jìn)程在訪問pipe文件前,都需檢查該索引文件是否已被上鎖。若是,進(jìn)程便睡眠等待,否則,將其上鎖,進(jìn)行讀/寫。操作結(jié)束后解鎖,并喚醒因該索引結(jié)點(diǎn)上鎖而睡眠的進(jìn)程。三、所涉及的系統(tǒng)調(diào)用 1、pip

33、e( )建立一無名管道。系統(tǒng)調(diào)用格式 pipe(filedes)參數(shù)定義int pipe(filedes);int filedes2;其中,filedes1是寫入端,filedes0是讀出端。該函數(shù)使用頭文件如下:#include <unistd.h>#inlcude <signal.h>#include <stdio.h> 2、read( ) 系統(tǒng)調(diào)用格式 read(fd,buf,nbyte) 功能:從fd所指示的文件中讀出nbyte個字節(jié)的數(shù)據(jù),并將它們送至由指針buf所指示的緩沖區(qū)中。如該文件被加鎖,等待,直到鎖打開為止。 參數(shù)定義 int read(

34、fd,buf,nbyte); int fd; char *buf; unsigned nbyte; 3、write( )系統(tǒng)調(diào)用格式 read(fd,buf,nbyte)功能:把nbyte 個字節(jié)的數(shù)據(jù),從buf所指向的緩沖區(qū)寫到由fd所指向的文件中。如文件加鎖,暫停寫入,直至開鎖。參數(shù)定義同read( )。四、參考程序#include <unistd.h>#include <signal.h>#include <stdio.h>int pid1,pid2; main( ) int fd2;char outpipe100,inpipe100;pipe(fd)

35、; /int pid; /*創(chuàng)建一個管道*/while (pid1=fork( )= =-1);if(pid1= =0) lockf(fd1,1,0); sprintf(outpipe,"child 1 process is sending message!"); /*把串放入數(shù)組outpipe中*/ write(fd1,outpipe,50); /*向管道寫長為50字節(jié)的串*/ sleep(5); /*自我阻塞5秒*/ lockf(fd1,0,0); exit(0); else while(pid2=fork( )= =-1); if(pid2= =0) lockf(fd

36、1,1,0); /*互斥*/ sprintf(outpipe,"child 2 process is sending message!"); write(fd1,outpipe,50); sleep(5); lockf(fd1,0,0); exit(0); else wait(0); /*同步*/ read(fd0,inpipe,50); /*從管道中讀長為50字節(jié)的串*/ printf("%sn",inpipe); wait(0); read(fd0,inpipe,50); printf("%sn",inpipe); exit(0)

37、; 五、運(yùn)行結(jié)果 延遲5秒后顯示child 1 process is sending message! 再延遲5秒 child 2 process is sending message!六、思考題1、程序中的sleep(5)起什么作用?2、子進(jìn)程1和2為什么也能對管道進(jìn)行操作?實(shí)驗(yàn)三 內(nèi)存分配與回收:Linux系統(tǒng)下利用鏈表實(shí)現(xiàn)動態(tài)內(nèi)存分配 (也可以做課本中的內(nèi)存管理實(shí)例)一、 實(shí)驗(yàn)?zāi)康?、 了解靜態(tài)內(nèi)存與動態(tài)內(nèi)存的區(qū)別;2、 理解動態(tài)內(nèi)存的分配和釋放原理;3、 掌握如何調(diào)整動態(tài)內(nèi)存的大小;4、 利用鏈表實(shí)現(xiàn)動態(tài)內(nèi)存分配。二、 實(shí)驗(yàn)內(nèi)容1、 利用malloc和 calloc函數(shù)實(shí)現(xiàn)動態(tài)內(nèi)存的分

38、配;利用free函數(shù)實(shí)現(xiàn)動態(tài)內(nèi)存的釋放;2、 利用realloc函數(shù)實(shí)現(xiàn)調(diào)整內(nèi)存空間的大小;3、 利用鏈表實(shí)現(xiàn)動態(tài)內(nèi)存分配。三、 實(shí)驗(yàn)指導(dǎo)1、 靜態(tài)內(nèi)存與動態(tài)內(nèi)存 按分配內(nèi)存空間的方式不同,一個程序所使用的內(nèi)存區(qū)域可以分為靜態(tài)內(nèi)存與動態(tài)內(nèi)存。在程序開始運(yùn)行時有系統(tǒng)分配的內(nèi)存稱為靜態(tài)內(nèi)存,在程序運(yùn)行過程中由用戶自己申請分配的內(nèi)存稱為動態(tài)內(nèi)存。 靜態(tài)內(nèi)存的申請是有編譯器來分配的。對于用戶程序中的各種變量,編譯器在編譯源程序時處理了為各種變量分配所需內(nèi)存的工作。當(dāng)程序執(zhí)行時,系統(tǒng)就為變量分配所需的內(nèi)存空間,至使用該變量的函數(shù)執(zhí)行完畢返回時,自動釋放所占用的內(nèi)存空間。使用靜態(tài)內(nèi)存對用戶來說是很方便的。

39、用戶并不需要了解分配內(nèi)存的具體細(xì)節(jié),也不需要時刻考慮由于程序結(jié)束前未釋放所占用的內(nèi)存空間而帶來的可用內(nèi)存泄漏。同時,靜態(tài)內(nèi)存也是不通過指針而使用變量的唯一方法。但是,靜態(tài)內(nèi)存也存在一定的缺陷:首先,由于靜態(tài)內(nèi)存總是預(yù)先定義了存放數(shù)據(jù)的數(shù)組大小,這就有可能因?yàn)樗鶄魅氲臄?shù)據(jù)量大于數(shù)組容量而引發(fā)的溢出問題。或因?yàn)槎x了一個大數(shù)組,而所傳入的數(shù)據(jù)量遠(yuǎn)小于數(shù)組容量,而對內(nèi)存空間造成浪費(fèi)。其次,由于在某個函數(shù)中分配的靜態(tài)內(nèi)存將在此函數(shù)運(yùn)行結(jié)束時被系統(tǒng)自動釋放,使用指針由子函數(shù)向主函數(shù)傳遞數(shù)據(jù)的設(shè)想是無法被實(shí)現(xiàn)的。使用動態(tài)內(nèi)存時,用戶可以根據(jù)需要隨時申請內(nèi)存,使用完畢后手動將此內(nèi)存區(qū)釋放。在實(shí)際應(yīng)用中非常方

40、便。但動態(tài)內(nèi)存的使用也存在著巨大的隱患。任何處理過大型項(xiàng)目的用戶都知道動態(tài)內(nèi)存的使用會使內(nèi)存管理變得多么復(fù)雜,以及要確切地記得在使用完畢后釋放所占用的內(nèi)存空間是多么困難的事情。在大型應(yīng)用程序中,由于在釋放某塊動態(tài)內(nèi)存前將指向該內(nèi)存區(qū)域的指針重新賦值,從而使得此內(nèi)存區(qū)域無法被釋放的情況是十分常見的。通常將內(nèi)存分配后沒有被釋放而導(dǎo)致可用內(nèi)存減少稱之為內(nèi)存泄漏。避免內(nèi)存泄漏耗盡系統(tǒng)資源正是許多服務(wù)器每隔一段時間就需要重新啟動的原因。另外還要注意,由于分配動態(tài)內(nèi)存時,用戶得到的是一塊void類型的內(nèi)存,用戶可以將其作為任何類型的內(nèi)存空間使用,也可能引發(fā)一些無法預(yù)計(jì)的結(jié)果。2、 動態(tài)內(nèi)存的分配分配動態(tài)內(nèi)

41、存空間所使用的函數(shù)調(diào)用如下:#include<stdio.h>void *malloc(size_t size);void *calloc(size_t nmemb, size_t size);函數(shù)malloc和calloc都是用于分配動態(tài)內(nèi)存空間的函數(shù)。函數(shù)malloc的參數(shù)size表示申請分配的內(nèi)存空間的大小,以字節(jié)記。函數(shù)calloc的參數(shù)nmemb表示申請分配的內(nèi)存空間占的數(shù)據(jù)項(xiàng)數(shù)目,參數(shù)size表示一個數(shù)據(jù)項(xiàng)的大小,以字節(jié)記。也就是說,calloc函數(shù)分配大小為nmemb*size大小的內(nèi)存空間。函數(shù)calloc與函數(shù)malloc的最大區(qū)別就是函數(shù)calloc將初始化所分

42、配的內(nèi)存空間,把所有位置0。調(diào)用成功時,函數(shù)calloc與函數(shù)malloc的返回值都為被分配的內(nèi)存空間的指針;調(diào)用失敗時,返回值為NULL。3、 動態(tài)內(nèi)存的釋放 釋放動態(tài)內(nèi)存空間所使用的函數(shù)調(diào)用如下: #include<stdio.h> void free(void *ptr); 此函數(shù)的作用是釋放由函數(shù)calloc或函數(shù)malloc分配的動態(tài)內(nèi)存。參數(shù)ptr是指向要釋放的動態(tài)內(nèi)存的指針。注意:當(dāng)動態(tài)內(nèi)存被釋放后,原來指向它的指針就會變?yōu)閼铱罩羔槨4藭r使用該指針將會產(chǎn)生錯誤。下面的例子就是使用動態(tài)內(nèi)存實(shí)現(xiàn)將子函數(shù)中分配的內(nèi)存空間指針返回主函數(shù),實(shí)現(xiàn)數(shù)據(jù)的傳遞。#include &l

43、t;stdio.h>#include <string.h>char *upcase(char *inputstring);int main(void) char *str1, *str2; str1=upcase("Hello" ); str2=capitao("Goodbye"); printf("str1=%s, str2=%sn", str1, str2); free(str1); free(str2); return 0;char *upcase(char *inputstring) char *newstr

44、ing; int counter; if(!(newstring=malloc(strlen(inputstring)+1) printf("ERROR ALLOCATING MEMORY! n"); exit(255); strcpy(newstring, inputstring);for(counter=0; counter<strlen(newstring); counter+) if(newstringcounter>=97&&newstringcounter<=122) newstringcounter-=32;return ne

45、wstring;在這個程序中由于使用了動態(tài)內(nèi)存,使得子函數(shù)可以靈活地分配所需的內(nèi)存空間。4、 調(diào)整動態(tài)內(nèi)存的大小對于用函數(shù)calloc與函數(shù)malloc分配好的動態(tài)內(nèi)存,可以使用realloc函數(shù)來調(diào)整它的大小。該函數(shù)的說明如下:#include<stdio.h> void*realloc(void *ptr, size_t size);realloc函數(shù)的作用是重新調(diào)整一塊動態(tài)內(nèi)存區(qū)域的大小,參數(shù)ptr是指向要調(diào)整的動態(tài)內(nèi)存的指針,應(yīng)是函數(shù)calloc與函數(shù)malloc的返回值。參數(shù)size是新定義的動態(tài)內(nèi)存的大小。Size可以大于或小于動態(tài)內(nèi)存的原大小,調(diào)用realloc函數(shù)時

46、,通常是在原來的內(nèi)存空間調(diào)整動態(tài)內(nèi)存的大小,原有數(shù)據(jù)不被改動。當(dāng)size大于原大小,而原位置中無法完成調(diào)整時,將重新開辟內(nèi)存空間并將原數(shù)據(jù)拷貝到新的內(nèi)存空間中。注意:如果參數(shù)ptr為NULL,則函數(shù)realloc的作用相當(dāng)于函數(shù)malloc。如果參數(shù)size為0,則函數(shù)realloc的作用相當(dāng)于函數(shù)free。下面的例子說明了該函數(shù)的應(yīng)用:在這個程序中,針對函數(shù)upcase的參數(shù)newstring是否為NULL,采取了不同的處理方式。如果newstring不為NULL,則直接分配內(nèi)存空間。否則調(diào)整原空間的大小以適應(yīng)新的需要。#include <stdio.h>#include <

47、;string.h>void upcase(char *inputstring, char *newstring);int main(void) char *string; upcase("Hello",string); printf("str1=%s n", string);capitao("Goodbye", string); printf("str2=%sn", string); free(string); return 0;void upcase(char *inputstring, char *ne

48、wstring) int counter; if(!newstring) if(!(newstring=realloc(NULL, strlen(inputstring)+1) printf("ERROR ALLOCATING MEMORY! n"); exit(255); else if(!(newstring=realloc(newstring, sizeof(inputstring)+1) printf("ERROR REALLOCATING MEMORY! n"); exit(255); strcpy(newstring, inputstring

49、);for(counter=0; counter<strlen(newstring); counter+) if(newstringcounter>=97&&newstringcounter<=122) newstringcounter-=32;return ;5、 使用鏈表進(jìn)行動態(tài)內(nèi)存的分配雖然使用動態(tài)內(nèi)存可以方便地使用內(nèi)存,但動態(tài)內(nèi)存也有局限性,就是在數(shù)據(jù)輸入到程序之前必須知道數(shù)據(jù)的大小,以便申請相應(yīng)的動態(tài)內(nèi)存。然而,在很多情況下,用戶都無法事先知道這個值,因而也就無法申請相應(yīng)的內(nèi)存空間。對于這種事先未知大小的數(shù)據(jù)輸入,可以使用鏈表將其分塊保存。鏈表是一種

50、動態(tài)地進(jìn)行存儲分配的結(jié)構(gòu)。鏈表中的各個元素是一個結(jié)構(gòu),每個元素稱為鏈表的一個結(jié)點(diǎn)。此結(jié)構(gòu)中包含有一個指向此結(jié)構(gòu)的指針,用于指向鏈表中的下一個結(jié)點(diǎn)。鏈表的最后一個結(jié)點(diǎn)的指針NULL,表示鏈表結(jié)束。下面的例子說明了使用鏈表獲得動態(tài)內(nèi)存的方法:這個程序?qū)⒔K端輸入的一系列字符串用鏈表的形式保存下來。然后再將這些數(shù)據(jù)組裝起來,回顯到輸出終端。鏈表的結(jié)點(diǎn)為stringdata結(jié)構(gòu)。stringdata結(jié)構(gòu)中的整型量iscontinuing用于表示當(dāng)前結(jié)點(diǎn)是否為鏈表的末尾。如果iscontinuing有值,則表示此結(jié)點(diǎn)不是鏈表的末尾。#include <stdio.h>#include <

51、stdlib.h>#include <string.h>#define DATASIZE 10typedef struct stringdata char *string; int iscontinuing; struct stringdata *next; mydata;mydata *append(mydata *start, char *input);void displaydata(mydata *start);void freedata(mydata *start);int main(void) char inputDATASIZE; mydata *start=N

52、ULL; printf("ENTER SOME DATA,AND PRESS Ctrl+D WHEN DONE. n"); while(fgets(input, sizeof(input), stdin) start=append(start, input); displaydata(start); freedata(start); return 0;mydata *append(mydata *start, char *input) mydata *cur=start, *prev=NULL, *new; while(cur) prev=cur; cur=cur->next; cur=prev; new=malloc(sizeof(mydata); if(!new) printf("COULDNT ALLOCATE MEMORY! n"); exit(255); if(cur) cur->next=new

溫馨提示

  • 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)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論