《Linux內核分析》實驗指導書10_第1頁
《Linux內核分析》實驗指導書10_第2頁
《Linux內核分析》實驗指導書10_第3頁
《Linux內核分析》實驗指導書10_第4頁
《Linux內核分析》實驗指導書10_第5頁
已閱讀5頁,還剩14頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

《Linux內核分析》實驗指導書10《Linux內核分析》課程試驗指導書

試驗一、進程管理試驗

1、加深對進程概念的理解,明確進程和程序的區(qū)分

2、進一步熟悉并發(fā)執(zhí)行的實質

3、分析進程爭用資源的現象,學習理解進程互斥的方法

4、了解linux系統(tǒng)中進程通信的基本原理

編寫一段程序,實現軟中斷通信。

使用系統(tǒng)調用fork()創(chuàng)建兩個子進程,再用系統(tǒng)調用signal()讓父進程捕獲鍵盤上發(fā)出的中斷信號(即按Del鍵),當父進程接受到這兩個軟中斷的其中某一個后,父進程用系統(tǒng)調用kill()向兩個子進程分別發(fā)送整數值為16和17軟中斷信號,子進程獲得對應軟中斷信號,然后分別輸出下列信息后終止:

Childprocess1iskilledbyparent!!Childprocess2iskilledbyparent!!

父進程調用wait()函數等待兩個子進程終止后,輸出以下信息,結束進程執(zhí)行:Parentprocessiskilled!!

多運行幾次編寫的程序,簡略分析消失不同結果的緣由。

(1)算法流程圖(圖1-1)

圖1-1軟中斷通信程序流程圖(2)參考程序源代碼

#include

#include

#include

#include

intwait_flag;

voidstop();

main()

{

intpid1,pid2;//定義兩個進程號變量

signal(3,stop);//或者signal(14,stop);

while((pid1=fork())==-1);//若創(chuàng)建子進程1不勝利,則空循環(huán)。if(pid1>0){//子進程創(chuàng)建勝利,pid1為進程號

while((pid2=fork())==-1);//創(chuàng)建子進程2

if(pid2>0){

wait_flag=1;

sleep(5);//父進程等待5秒

kill(pid1,16);//殺死進程1

kill(pid2,17);//殺死進程2

wait(0);//等待子進程1結束的信號

wait(0);//等待子進程2結束的信號

printf(“\nParentprocessiskilled!!\n”);

exit(0);//父進程結束

}

else{

wait_flag=1;

signal(17,stop);//等待進程2被殺死的中斷號17printf(“\nChildprocess2iskilledbyparent!!\n”);

exit(0);

}

}

else{

wait_flag=1;

signal(16,stop);//等待進程1被殺死的中斷號16printf(“\nChildprocess1iskilledbyparent!!\n”);

exit(0);

}

}

voidstop(){

wait_flag=0;

}

(3)程序運行結果

編譯運行后,等待從鍵盤輸入“Del”,有如下結果:Childprocess1iskilledbyparent!!Childprocess2iskilledbyparent!!Parentprocessiskilled!!

或者:(運行多次后會消失如下結果)

Childprocess2iskilledbyparent!!

Childprocess1iskilledbyparent!!

Parentprocessiskilled!!

試驗二、模塊編程試驗

通過學習內核模塊的編寫和運行,了解模塊是LinuxOS的一種特有的機制,可依據用戶的實際需要在不需要對內核進行重新編譯的狀況下,模塊能在內核中被動態(tài)地加載和卸載。編寫一個模塊,將它作為LinuxOS內核空間的擴展來執(zhí)行,并通過insmod命令來手工加載,通過命令rmmod來手工卸載。

Linux模塊是一些可以作為獨立程序來編譯的函數和數據類型的集合。在裝載這些模塊時,將它的代碼鏈接到內核中。Linux模塊有兩種裝載方式:靜態(tài)裝載(內核啟動時裝載)和動態(tài)裝載(在內核運行過程中裝載)。若在模塊裝載之前就調用了動態(tài)模塊的一個函數,則此調用將失??;若模塊已被裝載,則內核就可以使用系統(tǒng)調用,并將其傳遞到模塊中的相應函數。模塊通常用來實現設備驅動程序(這要求模塊的API和設備驅動程序的API相全都)。模塊可用來實現所期望的任何功能。

一、模塊的組織結構

模塊一旦被裝載進系統(tǒng),就在內核地址空間中管態(tài)下執(zhí)行。它就像任何標準的內核代碼一樣成為內核的一部分,并擁有其它內核代碼相同的權限和職責(當然也會引起系統(tǒng)的崩潰)。若模塊知道內核數據結構的地址,則它可以讀寫內核數據結構。但Linux是一個整體式的內核(monolithickernel)結構,整個內核是一個單獨的且非

常大的程序,從而存在一個普遍的問題:在一個文件中實現的函數可能需要在其它文

件中定義的數據。在傳統(tǒng)的程序中,這個問題是通過鏈接編輯器在生成可執(zhí)行對象文

件時,使用鏈接編輯器可以解析的外部(全局)變量來解決的。又由于模塊的設計和

實現與內核無關,所以模塊不能靠靜態(tài)鏈接通過變量名引用內核數據結構。恰好相反,

Linux內核采納了另外一種機制:實現數據結構的文件可以導出結構的符號名(可以

從文件/proc/ksyms或文件/…/kernel/ksyms.c中以文本方式讀取這個公開符號表),

這樣在運行時就可以使用這個結構了。不過在編寫模塊的過程中,編寫(修改)導出

變量時要非常留意,由于通過修轉變量會修改內核的狀態(tài),其結果可能并不是內核設

計者所期望的。在確信自己了解修改內核變量的后果之前,應當對這些變量只進行讀

操作。

模塊作為一種抽象數據類型,它具有一個可以通過靜態(tài)內核中斷的接口。最小的

模塊結構必需包括兩個函數,它們在系統(tǒng)裝載模塊和卸載模塊時調用,分別是

init_module()和cleanup_module()??梢跃帉懸粋€只包括這兩個函數的模塊,這樣

該模塊中唯一會被調用的函數就是模塊被裝載時所調用的函數init_module()和模塊

被卸載時所調用的函數cleanup_module()。并且用函數init_module()來啟動模塊裝

載期間的操作,用函數cleanup_module()來停止這些操作。

由于模塊可以實現相當簡單的功能,故可以在模塊中加入許多新函數以實現所要

期望的功能。不過加入模塊的每個新函數都必需在該模塊裝載到內核中時進行注冊。

若該模塊是靜態(tài)裝載的,則該模塊的全部函數都是在內核啟動時進行注冊的;若該模

塊是動態(tài)裝載的,則這些新函數必需在裝載這個模塊時動態(tài)注冊。當然,假如該模塊

被動態(tài)卸載了,則該模塊的函數都必需從系統(tǒng)中注銷。通過這種方式,當這個模塊不

在系統(tǒng)中時,就不能調用該模塊的函數。其中注冊工作通常是在函數init_module()

中完成的,而注銷工作通常是在函數cleanup_module()中完成的。

由上述定義的模塊應有如下的格式:

#include#include……//其它header信息

intinit_module(){……//裝載時,初始化模塊的編碼

}……

……//期望該模塊所能實現的一些功能函數,如open()、release()、

write()、

//read()、ioctl()等函數

……

voidcleanup_module()

{……//卸載時,注銷模塊的編碼

二.模塊的編譯

一旦設計并編寫好模塊,必需將其編譯成一個適合內核裝載的對象文件。由于編

寫模塊是用C語言來完成的,故采納gcc編譯器來進行編譯。若需要通知編譯程序把

這個模塊作為內核代碼而不是一般的用戶代碼來編譯,則就需向gcc編譯器傳遞參數

“-D__KERNEL__”;若需要通知編譯程序這個文件是一個模塊而不是一個一般文件,

則就需向gcc編譯器傳遞參數“-DMODULE”;若需要對模塊程序進行優(yōu)化編譯、連接,

則就需使用“-O2”參數;若還需要對裝載后的模塊進行調試,則就需使用“-g”參

數;同時需要使用“-Wall”參數來向裝載程序傳遞all,使用“-c”開關通知編譯程

序在編譯完這個模塊文件后不調用鏈接程序。

一般編譯模塊文件的命令格式如下:

#gcc-O2-g-Wall-DMODULE-D__KERNEL__-cfilename.c//filename.c為自己編寫的模塊程序源代碼文件

執(zhí)行命令后就會得到文件filename.o,該文件就是一個可裝載的目標代碼文件。

三.模塊的裝載

內核模塊的裝載方式有兩種。一種是使用insmod命令手工裝載模塊;另一種是

懇求裝載demandloading(在需要時裝載模塊),即當有必要裝載某個模塊時,若用

戶安裝了核心中不存在的文件系統(tǒng)時,核心將懇求內核守護進程kerneld預備裝載適

當的模塊。該內核守護進程是一個帶有超級用戶權限的一般用戶進程。此試驗中我們

主要采納insmod命令手工裝載模塊。

系統(tǒng)啟動時,kerneld開頭執(zhí)行,并為內核打開一個IPC通道,內核通過向kerneld

發(fā)送消息懇求執(zhí)行各種任務。kerneld的主要功能是裝載和卸載內核模塊,kerneld

自身并不執(zhí)行這些任務,它通過某些程序(如insmod)來完成。Kerneld只是內核的

代理,只為內核進行調度。

insmod程序必需找到懇求裝載的內核模塊(該懇求裝載的模塊一般被保存在

/lib/modules/kernel-version中)。這些模塊與系統(tǒng)中其它程序一樣是已連接的目標

文件,但不同的是它們被連接成可重定位映象(即映象沒有被連接到在特定的地址上

運行,其文件格式是a.out或ELF)。亦就是說,模塊在用戶空間(使用適當的標志)

進行編譯,結果產生一個可執(zhí)行格式的文件。在用insmod命令裝載一個模塊時,將

會發(fā)生如下大事:

(1)新模塊(通過內核函數create_module())加入到內核地址空間。

(2)insmod執(zhí)行一個特權級系統(tǒng)調用get_kernel_syms()函數以找到內核的輸

出符號(一個符號表示為符號名和符號值,如地址值)。

(3)create_module()為這個模塊安排內存空間,并將新模塊添加在內核模塊鏈

表的尾部,然后將新模塊標記為UNINITIALIZED(模塊未初始化)。

(4)通過init_module()系統(tǒng)調用裝載模塊。(該模塊定義的符號在此時被導出,供其它可能后來裝載的模塊使用)

(5)insmod為新裝載的模塊調用init_module()函數,然后將新模塊標志為RUNNING(模塊正在運行)。

在執(zhí)行完insmod命令后,就可在/proc/modules文件中看到裝載的新模塊了。(為證明其正確性,可在執(zhí)行insmod命令之前先查看/proc/modules文件,執(zhí)行之后再查看比較)

四.模塊的卸載

當一個模塊不需要使用時,可以使用rmmod命令卸載該模塊。由于無需連接,故它的任務比加載模塊要簡潔得多。但假如懇求裝載模塊在其使用計數為0時,kerneld將自動從系統(tǒng)中卸載該模塊。卸載時調用模塊的cleanup_module()釋放安排給該模塊的內核資源,并將其標志為DELETED(模塊被卸載);同時斷開內核模塊鏈表中的連接,修改它所依靠的其它模塊的引用,重新安排模塊所占的內核內存。

五.模塊連接到內核的示意圖

該圖比較明顯地展現了模塊連接到內核所使用的命令和函數,以及各個函數之間的調用關系。通過該圖,可以比較清楚地看出模塊連接到內核的整個連接過程,這也有助于內核模塊的編寫。

六.模塊程序中管理模塊的幾個文件操作

在內核內部用一個file結構來識別模塊,而且內核使用file_operatuions結構

來訪問模塊程序中的函數。file_operatuions結構是一個定義在中的函

數指針表。管理模塊的文件操作,通常也稱為“方法”,它們都為struct

file_operations供應函數指針。在structfile_operations中的操作一般按如下順

序消失,除非說明,它們返回0值時表示訪問勝利;發(fā)生錯誤時返回一個負的錯誤值

(目前共有13個操作):

int(*lseek)()、int(*read)()、int(*write)()、int(*readdir)()、int

(*select)()、int(*ioctl)()、int(*mmap)()、int(*open)()、void(*release)()、

int(*fsync)()、int(*fasync)()、int(*check_media_change)()、int

(*revalidate)()下面我們只簡潔介紹其中的幾個操作,其它在以后涉準時再介紹:

1、方法int(*read)(structinode*,structfile*,char*,int)

該方法用來從模塊中讀取數據。當其為NULL指針時將引起read系統(tǒng)調用返回

-EINVAL(“非法參數”)。函數返回一個非負值表示勝利地讀取了多少字節(jié)。

2、方法int(*write)(structinode*,structfile*,constchar*,int)

該方法用來向模塊發(fā)送數據。當其為NULL指針時將引起write系統(tǒng)調用返回

-EINVAL。假如函數返回一個非負值,則表示勝利地寫入了多少字節(jié)。

3、方法int(*open)(structinode*,structfile*)

該方法是用來打開模塊的操作,它是操作在模塊節(jié)點上的第一個操作,即使這樣,

該方法還是可以為NULL指針。假如為NULL指針,則表示該模塊的打開操作永久勝利,

但系統(tǒng)不會通知你的模塊程序。

4、方法void(*release)(structinode*,structfile*)

該方法是用來關閉模塊的操作。當節(jié)點被關閉時就調用這個操作。與open類似,

release也可以為NULL指針。

當在你的模塊中需要上面這些方法時,相應的方法若沒有,則在struct

file_operations中相應的地方將其令為NULL指針。這樣我們需要的也許象下面這樣:structfile_operationsmodulename_fops={NULL,//modulename_lseekmodulename_read,modulename_write,NULL,//modulename_readdirNULL,//modulename_select

NULL,//modulename_ioctlNULL,//modulename_mmapmodulename_open,modulename_release,NULL,//modulename_fsyncNULL,//modulename_fasyncNULL,//modulename_check_media_changeNULL//modulename_revalidate}

1、編寫一個簡潔的內核模塊,該模塊至少需要有兩個函數:一個是init_module()

函數,在把模塊裝載到內核時被調用,它為內核的某些東西注冊一個處理程序,或是

用自身的代碼取代某個內核函數;另一個是cleanup_module()函數,在卸載模塊時被

調用,其任務是清除init_module()函數所做的一切操作。編寫完成后進行該模塊的

編譯、裝載和卸載操作。

2、向上面模塊中再添加一些新函數,如open()、release()、write()和read()

函數,并編寫一個函數來測試你的模塊能否實現自己添加的函數的功能。其中open()、

release()和write()函數都可以是空操作或較少的操作,它們僅僅為結構

file_operations供應函數指針。

一、一個簡潔的內核模塊

1、必要的header文件:

除了前面講到的頭文件#include和#include

外,假如你的內核打開了版本檢查,那么我們就還必需增加頭文件

#include,否則就會出錯。

2、init_module()函數:

由于題目的要求不高,故可只在該函數里完成一個打印功能,如printk(“Hello!

Thisisatestingmodule!\n”);等。為便于檢查模塊是否裝載勝利,我們可以給

一個返回值,如return0;若返回一個非0值,則表示init_module()失敗,從而不

能裝載模塊。

3、cleanup_module()函數:

只需用一條打印語句來取消init_module()函數所做的打印功能操作就可以了,

如printk(“Sorry!Thetestingmoduleisunloadednow!\n”);等。

4、模塊的編寫:

此處把該模塊文件取名為testmodule.c#include//在內核模塊中共享

#include//一個模塊

//處理CONFIG_MODVERSIONS#ifCONFIG_MODVERSIONS==1#defineMODVERSIONS#include#endifintinit_module()//初始化模塊

{printk(“Hello!Thisisatestingmodule!\n”);

return0;

}voidcleanup_module()//取消init_module()函數所做的打印功能操作

{printk(“Sorry!Thetestingmoduleisunloadingnow!\n”);

}

5、模塊的編譯、裝載和卸載:

#gcc–O2–Wall–DMODULE–D__KERNEL__-ctestmodule.c#ls–s//在當前名目下查看生成的目標文件testmodule.o現在,模塊testmodule已經編譯好了。用下面命令將它裝載到系統(tǒng)中:

#insmod–ftestmodule.o

假如裝載勝利,則在/proc/modules文件中就可看到模塊testmodule,并可看到

它的主設備號。同時在終端顯示:

Hello!Thisisatestingmodule!

假如要卸載,就用如下命令:

#rmmodtestmodule

假如卸載勝利,則在/proc/devices文件中就可看到模塊testmodule已經不存在

了。同時在終端顯示:

Sorry!Thetestingmoduleisunloadingnow!

二、向testmodule模塊中添加新函數open()、release()、write()和read()

1、函數open()

intopen(structinode*inode,structfile*filp){MOD_INC_USE_COUNT;//增加該模塊的用戶數目

printk(“Thismoduleisinopen!\n”);

return0;

}

2、函數release()

voidrelease(structinode*inode,structfile*filp)

{MOD_DEC_USE_COUNT;//該模塊的用戶數目減1printk(“Thismoduleisinrelease!\n”);

return0;

#ifdefDEBUGprintk(“release(%p,%p)\n”,inode,filp);

#endif}

3、函數read()

intread(structinode*inode,structfile*filp,char*buf,intcount)

{

intleave;

if(verify_area(VERIFY_WRITE,buf,count)==DEFAULT)returnDEFAULT;

for(leave=count;leave>0;leave--)

{

__put_user(1,buf,1);

buf++;

}

returncount;

}

4、函數write()

intwrite(structinode*inode,structfile*filp,constchar*buf,int

count){returncount;

}三、模塊的測試

在該模塊程序編譯加載后,再在/dev名目下創(chuàng)建模塊設備文件moduledev,使用

命令:#mknod/dev/moduledevcmajorminor,其中“c”表示moduledev是

字符設備,“major”是moduledev的主設備號。(該字符設備驅動程序編譯加載后,

可在/proc/modules文件中獲得主設備號,或者使用命令:#cat

/proc/modules|awk”\\$2==\”moduledev\”{print\\$1}”獲得主設備號)

#include#include#include#includemain(){inti,testmoduledev;

charbuf;

testmoduledev=open(“/dev/moduledev”,O_RDWR);

if(testmoduledev==-1){printf(“Can’topenthefile!\n”);

exit(0);

}read(testmoduledev,buf,10);

for(i=0;i<10;i++)printf(“%d\n”,buf);

close(testmoduledev);

return0;

}

試驗三、定時器試驗

1、把握Linux下的定時器編程方法;

2、把握Linux下的常用時間函數編程方法。

1、編寫定時器程序timer;

2、編寫Makefile文件;

3、下載并調試timer。

1、C語言的基礎學問;

2、程序調試的基礎學問和方法;

3、Linux的基本操作;

4、把握Linux下的程序編譯與交叉編譯過程;

5、把握Linux下基本的應用程序編寫方法。

操作系統(tǒng)應當能夠在將來某個時刻準時調度某個任務。所以需要一種能保證任務較準時調度運行的機制。盼望支持每種操作系統(tǒng)的微處理器必需包含一個可周期性中斷它的可編程間隔定時器。這個周期性中斷被稱為系統(tǒng)時鐘滴答,它象節(jié)拍器一樣來組織系統(tǒng)任務。Linux的時鐘觀念很簡潔:它表示系統(tǒng)啟動后的以時鐘滴答記數的時間。全部的系統(tǒng)時鐘都基于這種量度,在系統(tǒng)中的名稱和一個全局變量相同-jiffies。

Linux包含兩種類型的系統(tǒng)定時器,它們都可以在某個系統(tǒng)時間上被隊列例程使用,但是它們的實現稍有區(qū)分。

第一個是老的定時器機制,它包含指向timer_struct結構的32位指針的靜態(tài)數組以當前活動定時器的屏蔽碼:time_active。

此定時器表中的位置是靜態(tài)定義的(類似底層部分處理表bh_base)。其入口在系統(tǒng)初始化時被加入到表中。其次種是相對較新的定時器,它使用一個到期時間以升序排列的timer_list結構鏈表。

這兩種方法都使用jiffies作為終結時間,這樣盼望運行5秒的定時器將不得不將5秒時間轉換成jiffies的單位并且將它和以jiffies記數的當前系統(tǒng)時間相加從而得到定時器的終結時間。在每個系統(tǒng)時鐘滴答時,定時器的底層部分處理過程被標記成活動狀態(tài)以便調度管理器下次運行時能進行定時器隊列的處理。定時器底層部分處理過程包含兩種類型的系統(tǒng)定時器。老的系統(tǒng)定時器將檢查timer_active位是否置位。假如活動定時器已經到期則其定時器例程將被調用同時

它的活動位也被清除。新定時器位于timer_list結構鏈表中的入口也將受到檢查。每個過期定時器將從鏈表中清除,同時它的例程將被調用。新定時器機制的優(yōu)點之一是能傳遞一個參數給定時器例程。

在本試驗應用程序中,需要進行時間相關的編程動作,如獵取當前時間,對某一段工作進行計時處理以及定時執(zhí)行某一動作等。本試驗將介紹如何在Linux調用時間相關函數完成上述功能。

1、獵取當前時間在程序當中,可以使用下面兩個函數輸出系統(tǒng)當前的時間:

time_ttime(time_t*tloc);

char*ctime(consttime_t*clock);

time函數返回從1970年1月1日0點以來的秒數.存儲在time_t結構之中.這個函數的返回值由于不夠直觀,以人類的理解方式,這組抽象的數字好像缺乏實際意義。所以我們可以另一個函數ctime(consttime_t*clock)將抽象的時間記錄轉化為直觀的字符串,以便顯示。

2、計時處理

有時候我們要計算程序執(zhí)行的時間。比如我們要對算法進行時間分析。這個時候可以使用下面這個函數,加在需要計算時間的程序的兩端:

intgettimeofday(structtimeval*tv,structtimezone*tz)

第一個參數為timeval類型的結構,該結構聲明如下:

Struttimeval{

Longtv_sec;//秒數

Longtv_usec;//微秒數

}

gettimeofday將時間保存在結構tv之中。

3、定時器

Linux操作系統(tǒng)為每一個進程供應了3個內部間隔計時器。ITIMER_REAL:削減實際時間。到時的時候發(fā)出SIGALRM信號。TIMER_VIRTUAL:削減有效時間(進程執(zhí)行的時間)。到時的時候產生SIGVTALRM信號。

ITIME_PROF:削減進程的有效時間和系統(tǒng)時間(為進程調度用的時間)。到時的時候產生SIGPROF信號。

詳細的操作函數是:

intgetitimer(intwhich,structitimerval*value);

intsetitimer(intwhich,structitimerval*newval,structitimerval*oldval)

相關結構類型聲明如下:

structitimerval{

structtimevalit_interval;

structtimevalitvalue;

}

getitimer函數得到間隔計時器的時間值并保存在value中。setitimer函數設置間隔計時器的時間值為newval,并將舊值保存在oldval中。which表示使用三個計時器中的哪一個。itimerval結構中的it_value是削減的時間,當這個值為0的時候就發(fā)出相應的信號了,然后設置為it_interval值。

編寫timer.c程序源代碼

#include

#include

#include

structtimevaltpstart,tpend;

floattimeuse;

statictimer_count=0;

voidprompt_info(intsigno)

{

time_tt=time(NULL);

/*2secondsturned,printsomething*/

printf("prompt_infocalled\n",++timer_count);

/*getcurrenttimeandprintit*/

ctime(

printf("currenttime%s",ctime(

/*stopgettime,andprintit*/

gettimeofday(

timeuse=1000000*(tpend.tv_sec-tpstart.tv_sec)+tpend.tv_usec-tpstart.tv_usec;

timeuse/=1000000;

printf("UsedTime:%f\n",timeuse);

}

voidinit_sigaction(void)

{

structsigactionact;act.sa_handler=prompt_info;act.sa_flags=0;sigemptyset(sigaction(SIGPROF,

/*begingetthetime*/gettimeofday(printf("begintime\n");

}

voidinit_time()

{

structitimervalvalue;value.it_value.tv_sec=2;value.it_value.tv_usec=0;value.it_interval=value.it_value;setitimer(ITIMER_PROF,

}

/*

*timerapplicationcode

*/

intmain(intargc,char**argv)

{

init_sigaction();init_time();while(1);exit(0);

}

3、編寫Makefile文件,Makefile內容如下:

CC=gcc

LD=ld

EXEC=timer

OBJS=timer.o

CFLAGS+=LDFLAGS+=

all:$(EXEC)

$(EXEC):$(OBJS)

$(CC)$(LDFLAGS)-o$@$(OBJS)$(LDLIBS$(LDLIBS_$@))

clean:

-rm-f$(EXEC)*.elf*.gdb*.o

4、編譯timer,在timer名目下,終端輸入如下命令:

#makeclean

#make

#./timer

輸出結果如下:

prompt_infocalled

currenttimeSatMar3015:58:232024

UsedTime:2.003054

prompt_infocalled

currenttimeSatMar3015:58:252024

UsedTime:4.001216

prompt_infocalled

currenttimeSatMar3015:58:272024

UsedTime:6.001144

prompt_infocalled

currenttimeSatMar3015:58:292024

UsedTime:8.001138

該程序正確執(zhí)行時將每隔兩秒鐘打印一次上述信息,“prompt_infocalled”字符串是在定時器處理函數中打印的,還打印了當前系統(tǒng)時間和從第1次啟動定時器開頭獵取的時間間隔。

試驗四、設備驅動試驗

通過本試驗的學習,了解Linux操作系統(tǒng)中的設備驅動程序包括哪些組成部分,并能編寫簡潔的字符設備(scull,SimpleCharacterUtilityforLoadingLocalities)和塊設備(sbull,SimpleBlockUtilityforLoadingLocalities)的驅動程序以及對所編寫設備驅動程序的測試,最終了解Linux操作系統(tǒng)是如何管理設備的。

一.設備驅動程序的簡潔介紹

Linux設備驅動程序集成在內核中,實際上是處理或操作硬件掌握器的軟件。從本質上講,驅動程序是常駐內存的低級硬件處理程序的共享庫,設備驅動程序就是對設備的抽象處理;也即是說,設備驅動程序是內核中具有高特權級的、常駐內存的、可共享的下層硬件處理例程。

設備驅動程序軟件封裝了如何掌握這些設備的技術細節(jié),并通過特定的接口導出一個規(guī)范的操作集合(見圖1);內核使用規(guī)范的設備接口(字符設備接口和塊設備接口)通過文件系統(tǒng)接口把設備操作導出到用戶空間程序中。(由于本試驗不涉及網絡設備,故在此就不作爭論)

圖1字符(塊)設備、驅動

在Linux中,字符設備和塊設備的I/O操作是有區(qū)分的。塊設備在每次硬件操作時把多個字節(jié)傳送到主存緩存中或從主存緩存中把多個字節(jié)信息傳送到設備中;而字符設備并不使用緩存,信息傳送是一個字節(jié)一個字節(jié)地進行的。

Linux操作系統(tǒng)允許設備驅動程序作為可裝載內核模塊實現,這也就是說,設備的接口實現不僅可以在Linux操作系統(tǒng)啟動時進行注冊,而且還可以在Linux操作系統(tǒng)啟動后裝載模塊時進行注冊。

總之,Linux操作系統(tǒng)支持多種設備,這些設備的驅動程序有如下一些特點:

(1)內核代碼:設備驅動程序是內核的一部分,假如驅動程序出錯,則可能導致系統(tǒng)崩潰。

(2)內核接口:設備驅動程序必需為內核或者其子系統(tǒng)供應一個標準接口。比如,一個終端驅動程序必需為內核供應一個文件I/O接口;一個SCSI設備驅動程序應當為SCSI子系統(tǒng)供應一個SCSI設備接口,同時SCSI子系統(tǒng)也必需為內核供應文件的I/O接口及緩沖區(qū)。

(3)內核機制和服務:設備驅動程序使用一些標準的內核服務,如內存安排等。

(4)可裝載:大多數的Linux操作系統(tǒng)設備驅動程序都可以在需要時裝載進內核,在不需要時從內核中卸載。

(5)可設置:Linux操作系統(tǒng)設備驅動程序可以集成為內核的一部分,并可以依據需要把其中的某一部分集成到內核中,這只需要在系統(tǒng)編譯時進行相應的設置即可。

(6)動態(tài)性:當系統(tǒng)啟動且各個設備驅動程序初始化后,驅動程序將維護其掌握的設備。假如該設備驅動程序掌握的設備不存在也不影響系統(tǒng)的運行,此時的設備驅動程序只是多占用了一點系統(tǒng)內存罷了。

二.設備驅動程序與外界的接口

每種類型的驅動程序,不管是字符設備還是塊設備都為內核供應相同的調用接口,故內核能以相同的方式處理不同的設備。Linux為每種不同類型的設備驅動程序維護相應的數據結構,以便定義統(tǒng)一的接口并實現驅動程序的可裝載性和動態(tài)性。

Linux設備驅動程序與外界的接口可以分為如下三個部分:

(1)驅動程序與操作系統(tǒng)內核的接口:這是通過數據結構file_operations來完成的。

(2)驅動程序與系統(tǒng)引導的接口:這部分利用驅動程序對設備進行初始化。

(3)驅動程序與設備的接口:這部分描述了驅動程序如何與設備進行交互,這與詳細設備親密相關。

可歸結為如下圖2:

圖2設備驅動程序與

三.設備驅動程序的組織結構

設備驅動程序有一個比較標準的組織結構,一般可以分為下面三個主要組成部分:

(1)自動配置和初始化子程序

這部分程序負責檢測所要驅動的硬件設備是否存在以及是否能正常工作。假如該設備正常,則對設備及其驅動程序所需要的相關軟件狀態(tài)進行初始化。這部分程序僅在初始化時被調用一次。

(2)服務于I/O懇求的子程序

該部分又可稱為驅動程序的上半部分。系統(tǒng)調用對這部分進行調用。系統(tǒng)認為這部分程序在執(zhí)行時和進行調用的進程屬于同一個進程,只是由用戶態(tài)變成了內核態(tài),而且具有進行此系統(tǒng)調用的用戶程序的運行環(huán)境。故可以在其中調用與進程運行環(huán)境有關的函數。

(3)中斷服務子程序

該部分又可稱為驅動程序的下半部分。設備在I/O懇求結束時或其它狀態(tài)轉變時產生中斷。中斷可以產生在任何一個進程運行時,因此中斷服務子程序被調用時不能依靠于任何進程的狀態(tài),因而也就不能調用與進程運行環(huán)境有關的函數。由于設備驅動程序一般支持同一類型的若干設備,所以一般在系統(tǒng)調用中斷服務子程序時都帶有一個或多個參數,以唯一標識懇求服務的設備。

四.設備驅動程序的代碼

設備驅動程序是一些函數和數據結構的集合,這些函數和數據結構是為實現管理設備的一個簡潔接口。操作系統(tǒng)內核使用這個接口來懇求驅動程序對設備進行I/O操作。甚至,我們可以把設備驅動程序看成是一個抽象數據類型,它為計算機中的每個硬件設備都建立了一個通用函數接口。由于一個設備驅動程序就是一個模塊,所以在內核內部用一個file結構來識別設備驅動程序,而且內核使用file_operatuions結構來訪問設備驅動程序中的函數。

了解設備驅動程序代碼的如下幾個部分:

◆驅動程序的注冊與注銷?!粼O備的打開與釋放?!粼O備的讀寫操作。

◆設備的掌握操作?!粼O備的中斷和輪詢處理。

五、字符設備驅動程序的代碼

1、了解什么是字符設備

2、了解字符設備的基本入口點

字符設備的基本入口點也可稱為子程序,它們被包含在驅動程序的file_operations結構中。

①open()函數;②release()函數;③read()函數;④write()函數;

⑤ioctl()函數;⑥select()函數。

3、字符設備的注冊

設備驅動程序供應的入口點在設備驅動程序初始化時向系統(tǒng)登記,以便系統(tǒng)調

用。Linux系統(tǒng)通過調用register_chrdev()向系統(tǒng)注冊字符型設備驅動程序。

register_chrdev()定義如下:

#include#include

intregister_chrdev(unsignedintmajor,constchar*name,struct

file_operations*ops);

其中major時設備驅動程序向系統(tǒng)申請的主設備號。假如它為0,則系統(tǒng)為該驅

動程序動態(tài)地安排第一個空閑的主設備號,并把設備名和文件操作表的指針置于

chrdevs表的相應位置。name是設備名,ops是對各個調用入口點的說明。

register_chrdev()函數返回0表示注冊勝利;返回-EINVAL表示申請的主設備號非法,

一般主設備號大于系統(tǒng)所允許的最大設備號;返回-EBUSY表示所申請的主設備號正被

其它設備驅動程序使用。假如動態(tài)安排主設備號勝利,則該函數將返回所安排的主設

備號。假如register_chrdev()操作勝利,則設備名就會消失在/proc/devices文件

中。

字符設備注冊以后,還必需在文件系統(tǒng)中為其創(chuàng)建一個代表節(jié)點。該節(jié)點可以是

在/dev名目中的一個節(jié)點,這種節(jié)點都是文件節(jié)點,且每個節(jié)點代表一個詳細的設備。

不過要有主設備號和從設備號兩個參數才能創(chuàng)建一個節(jié)點。還可以是在devfs設備文

件名目下的一個節(jié)點,對于這種節(jié)點應依據主設備號給每一種設備都創(chuàng)建一個名目節(jié)

點,在這個名目下才是代表詳細設備的文件節(jié)點。

編寫一個簡潔的字符設備驅動程序。要求該字符設備包括scull_open()、

scull_write()、scull_read()、scull_ioctl()和scull_release()五個基本操作,

并編寫一個測試程序來測試你所編寫的字符設備驅動程序。

先給出字符設備驅動程序要用到的數據結構定義:

structdevice_struct{constchar*name;

structfile_operations*chops;

};

staticstructdevice_structchrdevs;

typedefstructScull_Dev{

void**data;

intquantum;//thecurrentquantumsizeintqset;//thecurrentarraysizeunsignedlongsize;

unsignedintaccess_key;//usedbysculluidandscullprivunsignedintusage;//lockthedevicewhileusingitstructScull_Dev*next;//nextlistitem}scull;

1、字符設備的結構

字符設備的結構即字符設備的開關表。當字符設備注冊到內核后,字符設備的名

字和相關操作被添加到device_struct結構類型的chrdevs全局數組中,稱chrdevs

為字符設備的開關表。下面以一個簡潔的例子說明字符設備驅動程序中字符設備結構

的定義:(假設設備名為scull)

****file_operation結構定義如下,即定義chr設備的_fops****

staticintscull_open(structinode*inode,structfile*filp);

staticintscull_release(structinode*inode,structfile*filp);

staticssize_tscull_write(structinode*inode,structfile*filp,const

char*buffer,intcount);

staticssize_tscull_read(structinode*inode,structfile*filp,char

*buffer,intcount);

staticintscull_ioctl(structinode*inode,structfile*filp,unsigned

longintcmd,unsignedlongarg);

structfile_operationchr_fops={

NULL,//seek

scull_read,//read

scull_write,//write

NULL,//readdir

NULL,//poll

scull_ioctl,//ioctl

NULL,//mmap

scull_open,//open

NULL,//flush

scull_release,//release

NULL,//fsync

NULL,//fasync

NULL,//checkmediachange

NULL,//revalidate

NULL//lock

};

2、字符設備驅動程序入口點

字符設備驅動程序入口點主要包括初始化字符設備、字符設備的I/O調用和中斷。在引導系統(tǒng)時,每個設備驅動程序通過其內部的初始化函數init()對其掌握的設備及其自身初始化。字符設備初始化函數為chr_dev_init(),包含在/linux/drivers/char/mem.c中,它的主要功能之一是在內核中登記設備驅動程序。詳細調用是通過register_chrdev()函數。register_chrdev()函數定義如下:

#include

#include

intregister_chrdev(unsignedintmajor,constchar*name,structfile_operation*fops);

其中major是為設備驅動程序向系統(tǒng)申請的主設備號。假如為0,則系統(tǒng)為此驅動程序動態(tài)地安排一個主設備號。name是設備名。fops是前面定義的file_operation結構的指針。在登記勝利的狀況下,假如指定了major,則register_chrdev()函數返回值為0;假如major值為0,則返回內核安排的主設備號。并且register_chrdev()函數操作勝利,設備名就會消失在/proc/devices文件里;在登記失敗的狀況下,register_chrdev()函數返回值為負。

初始化部分一般還負責給設備驅動程

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論