




版權說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權,請進行舉報或認領
文檔簡介
1、Linux 設備驅(qū)動程序設計15 年來, Linux 從一份大學生的作業(yè)演變成了 Windows 最強勁的競爭對手,在網(wǎng)絡、企業(yè)、政府和 消費電子市場中逐步占據(jù)了重要的地位,在有些領域甚至成了最主要的角色。 15 年來, Linux 在歐洲、在 美國、在亞洲向微軟發(fā)起強勁挑戰(zhàn),以至微軟 CEO 鮑爾默一度相信微軟會被 Linux 擊敗。隨著 Linux 進入嵌入式設備領域后,關注和投身 Linux 開發(fā)的開發(fā)人員越來越多,但目前市面上介紹 Linux 開發(fā)的資料卻非常稀少,很多開發(fā)人員感到入行無門,我參照 Linux 驅(qū)動開發(fā)詳解華清遠見, 宋寶華以及其他一些參考資料,編寫了本教案,由于時間倉
2、促,沒有對應制作相關的 ppt ,請同學們諒 解,希望能給大家一些幫助。在講課之前,我們預備把所有的參考資料都列舉出來,詳細的知識點請大家去對應查找我們所列出的G 廿-i-r4r 參考書籍。1 、 Linux 程序設計人民郵電出版社,陳健等譯第 3版本主要關注第 18 章2、 Linux 驅(qū)動開發(fā)詳解華清遠見,宋寶華3 、嵌入式設計及 Linux 驅(qū)動開發(fā)指南 -基于 ARM9 處理器第 2 版電子工業(yè)出版社,孫天澤4、嵌入式軟件調(diào)試技術電子工業(yè)出版社,羅克露等5 、嵌入式Linux 應用開發(fā)詳解電子工業(yè)出版社,洗進等6 、嵌入式Linux 程序設計案例與實驗教程機械工業(yè)出版社,俞輝7、嵌入式
3、系統(tǒng)課程設計機械工業(yè)出版社,陳虎等 還有一些互連網(wǎng)資料,這里就不一一列舉了。請大家盡量去找這些資料進行學習。第一講 Linux 設備驅(qū)動編程之引言目前, Linux 軟件工程師大致可分為兩個層次: 1 Linux 應用軟件工程師 Application Software Engineer :主要利用 C 庫函數(shù)和 Linux API 進 行應用軟件的編寫; 2 Linux 固件工程師 Firmware Engineer :主要進行 Bootloader 、 Linux 的移植及 Linux 設備驅(qū) 動程序的設計。一般而言, 固件工程師的要求要高于應用軟件工程師的層次, 而其中的 Linux 設
4、備驅(qū)動編程又是 Linux 程序設計中比較復雜的部分,究其原因,主要包括如下幾個方面: 1設備驅(qū)動屬于 Linux 內(nèi)核的部分,編寫 Linux 設備驅(qū)動需要有一定的 Linux 操作系統(tǒng)內(nèi)核基礎; 2編寫 Linux 設備驅(qū)動需要對硬件的原理有相當?shù)牧私?,大多?shù)情況下我們是針對一個特定的嵌 入式硬件平臺編寫驅(qū)動的;3Linux 設備驅(qū)動中廣泛涉及到多進程并發(fā)的同步、互斥等控制,容易出現(xiàn)bug ; 4由于屬于內(nèi)核的一部分, Linux 設備驅(qū)動的調(diào)試也相當復雜。目前,市面上的 Linux 設備驅(qū)動程序參考書籍非常稀缺,少有的經(jīng)典是由 Linux 社區(qū)的三位領導者Jonathan Corbet
5、、Alessandro Rubini 、 Greg Kroah-Hartman 編寫的 Linux Device Drivers 目前該書 學習文檔 僅供參考已經(jīng)出版到第3版,中文譯本由中國電力出版社出版。該書將 Linux設備驅(qū)動編寫技術進行了較系統(tǒng)的 展現(xiàn),但是該書所列舉實例的背景過于復雜,使得讀者需要將過多的精力投放于對例子背景的理解上,很 難完全集中精力于 Linux驅(qū)動程序本身。往往需要將此書翻來覆去地研讀許多遍,才能有較深的體會。我們這里將仍然秉承Linux Device Drivers一書以實例為主的風格,但是實例的背景將非常簡單,以求使讀者能將集中精力于Linux設備驅(qū)動本身,
6、理解 Linux內(nèi)核模塊、Linux設備驅(qū)動的結構、Linux設備驅(qū)動中的并發(fā)控制等內(nèi)容。另外,與Li nux Device Drivers所不同的是,針對設備驅(qū)動的實例,我還給出了用戶態(tài)的程序來訪問該設備,展現(xiàn)設備驅(qū)動的運行情況及用戶態(tài)和內(nèi)核態(tài)的交互。相信同學們學習 完本部分內(nèi)容,將為進一步領悟Linux Device Drivers一書中的深一點的內(nèi)容打下很好的基礎。我們所列舉的例程除引用的以外全部已經(jīng)親自調(diào)試通過,主要基于的內(nèi)核版本為Linux 2.4,例子要在其他內(nèi)核上運行只需要做少量的修改。請注意:驅(qū)動程序的開發(fā)平臺我們建議大家全部采用我們實驗室的純Linux系統(tǒng)下進行,并且在我們講
7、完本部分內(nèi)容講解適合我們創(chuàng)維特實驗箱的JXARM2410-1型評估板上的硬件驅(qū)動程序真正的驅(qū)動編寫的時候,建議大家完全采用這種方法。也就是說既不要通過在 Windows平臺上安裝VMWare虛擬機,并在VMWare虛擬機上安裝 Red Hat的方法如果開發(fā)應用層或者80X86對應的驅(qū)動還是可以,也不要在前面我們在內(nèi)核編程基本知識當中為了教學而采用的Cygwin平臺。但請大家注意,后兩種方法并非說完全地不能開發(fā)異構系統(tǒng)即非 80X86的CPU丨的驅(qū)動程序,只是因為還要做很多設置,相當麻煩, 所以我們僅僅介紹在純粹Linux系統(tǒng)下的開發(fā)驅(qū)動的標準方法。第二講Linux設備驅(qū)動編程之內(nèi)核模塊Linu
8、x設備驅(qū)動屬于內(nèi)核的一部分,Linux內(nèi)核的一個模塊可以以兩種方式被編譯和加載:1直接編譯進Linux內(nèi)核,隨同Linux啟動時加載;2丨編譯成一個可加載和刪除的模塊,使用insmod加載modprobe和insmod命令類似,但依賴于相關的配置文件,rmmod刪除。這種方式控制了內(nèi)核的大小,而模塊一旦被插入內(nèi)核,它就和內(nèi)核其 他部分一樣。F面我們給出一個內(nèi)核模塊的例子:#i nclude II#in clude /in it&exit所有模塊都需要的頭文件相關宏MODULE_LICENSE(GPL); static int _in it hello_ in it (void) prin tk
9、(Hello module in itn); return 0;分析上述程序,發(fā)現(xiàn)一個Linux內(nèi)核模塊需包含模塊初始化和模塊卸載函數(shù),前者在insmod的時候運行,后者在rmmod的時候運行。初始化與卸載函數(shù)必須在宏modulenit和module_exit使用前定義,否則會出現(xiàn)編譯錯誤。程序中的MODULE_LICENSE(GPL)用于聲明模塊的許可證。如果要把上述程序編譯為一個運行時加載和刪除的模塊,則編譯命令為:由此可見,Linux內(nèi)核模塊的編譯需要給 gcc指示-D_KERNEL_ -DMODULE -DLINUX 參數(shù)。-I選 項跟著Linux內(nèi)核源代碼中Include目錄的路徑。
10、以下命令將可加載 hello模塊:以下命令完成相反過程:如果要將其直接編譯入Linux內(nèi)核,則需要將源代碼文件拷貝入Linux內(nèi)核源代碼的相應路徑里,并修改 Makefile 。我們有必要補充一下Linux內(nèi)核編程的一些基本知識:內(nèi)存在Linux內(nèi)核模式下,我們不能使用用戶態(tài)的malloc()和free()函數(shù)申請和釋放內(nèi)存。進行內(nèi)核編程時,最常用的內(nèi)存申請和釋放函數(shù)為在include/linux/kernel.h文件中聲明的kmalloc()和kfree(),其原型為:void *kmalloc(unsigned int len, int priority);void kfree(void
11、*_ptr);kmalloc的priority參數(shù)通常設置為GFP_KERNEL ,如果在中斷服務程序里申請內(nèi)存則要用GFP_ATOMIC參數(shù),因為使用 GFP_KERNEL參數(shù)可能會引起睡眠,不能用于非進程上下文中在中斷 中是不允許睡眠的。由于內(nèi)核態(tài)和用戶態(tài)使用不同的內(nèi)存定義,所以二者之間不能直接訪問對方的內(nèi)存。而應該使用Linux中的用戶和內(nèi)核態(tài)內(nèi)存交互函數(shù)這些函數(shù)在in clude/asm/uaccess.h中被聲明:un sig ned long copy_from_user(void *to, const void *from, un sig ned long n);un sig n
12、ed long copy_to_user (void * to, void * from, un sig ned long len);copy_from_user、copy_to_user 函數(shù)返回不能被復制的字節(jié)數(shù),因此,如果完全復制成功,返回值 為0。include/asm/uaccess.h中定義的put_user和get_user用于內(nèi)核空間和用戶空間的單值交互如char、int、long。這里給出的僅僅是關于內(nèi)核中內(nèi)存管理的皮毛,關于Linux內(nèi)存管理的更多細節(jié)知識,我們會在本文第9節(jié)內(nèi)存與I/O操作進行更加深入地介紹。輸出在內(nèi)核編程中,我們不能使用用戶態(tài) C庫函數(shù)中的printf(
13、)函數(shù)輸出信息,而只能使用printk()。但是, 內(nèi)核中printk()函數(shù)的設計目的并不是為了和用戶交流,它實際上是內(nèi)核的一種日志機制, 用來記錄下日志信息或者給出警告提示。每個printk都會有個優(yōu)先級,內(nèi)核一共有8個優(yōu)先級,它們都有對應的宏定義。如果未指定優(yōu)先級,內(nèi)核會選擇默認的優(yōu)先級DEFAULT_MESSAGE_LOGLEVEL。如果優(yōu)先級數(shù)字比int console_loglevel變量小的話,消息就會打印到控制臺上。如果syslogd和klogd守護進程在運行的話,則不管是否向控制臺輸出,消息都會被追加進/var/log/messages 文件。klogd只處理內(nèi)核消息,sys
14、logd處理其他系統(tǒng)消息, 比方應用程序。模塊參數(shù)2.4內(nèi)核下,include/linux/module.h中定義的宏 MODULE_PARM(var,type)用于向模塊傳遞命令行參數(shù)。var為接受參數(shù)值的變量名,type為采取如下格式的字符串min-maxb,h,i,l,s 。min及max用于表示當參數(shù)為數(shù)組類型時,允許輸入的數(shù)組元素的個數(shù)范圍;b: byte ; h: short ; i: int; l: long ; s: string 。在裝載內(nèi)核模塊時,用戶可以向模塊傳遞一些參數(shù):in smod modn ame var=value如果用戶未指定參數(shù),var將使用模塊內(nèi)定義的缺省
15、值。第三講Linux設備驅(qū)動之字符設備驅(qū)動程序Linux下的設備驅(qū)動程序被組織為一組完成不同任務的函數(shù)的集合,通過這些函數(shù)使得linux的設備操作猶如文件一般。在應用程序看來,硬件設備只是一個設備文件,應用程序可以象操作普通文件一樣對硬件設備進行操作,如 open ()、close ()、read ()、write ()等。Linux主要將設備分為二類:字符設備和塊設備。字符設備是指設備發(fā)送和接收數(shù)據(jù)以字符的形式進 行;而塊設備則以整個數(shù)據(jù)緩沖區(qū)的形式進行。字符設備的驅(qū)動相比照較簡單。下面我們來假設一個非常簡單的虛擬字符設備:這個設備中只有一個4個字節(jié)的全局變量intglobal_var,而這
16、個設備的名字叫做 gobalvar。對gobalvar設備的讀寫等操作即是對其中全局變量 global_var 的操作。驅(qū)動程序是內(nèi)核的一部分,因此我們需要給其添加模塊初始化函數(shù),該函數(shù)用來完成對所控設備的初 始化工作,并調(diào)用 register_chrdev()函數(shù)注冊字符設備:其中,register_chrdev 函數(shù)中的參數(shù) MAJOR_NUM 為主設備號,gobalvar為設備名,gobalvar_fops 為包含基本函數(shù)入口點的結構體,類型為 file_operations。當gobalvar模塊被加載時,gobalvar_init被執(zhí) 行,它將調(diào)用內(nèi)核函數(shù)register_chrde
17、v,把驅(qū)動程序的基本入口點指針存放在內(nèi)核的字符設備地址表中,在用戶進程對該設備執(zhí)行系統(tǒng)調(diào)用時提供入口地址。與模塊初始化函數(shù)對應的就是模塊卸載函數(shù),需要調(diào)用register_chrdev()的反函數(shù)”un register_chrdev() :static void _exit gobalvar_exit(void)if (un register_chrdev(MAJOR_NUM, gobalvar )卸載失敗else/卸載成功隨著內(nèi)核不斷增加新的功能,file_operati ons結構體已逐漸變得越來越大,但是大多數(shù)的驅(qū)動程序只是利用了其中的一部分。對于字符設備來說,要提供的主要入口有:op
18、en ()、release ()、read ()、write ()、ioctl () llseek()、poll()等。open()函數(shù) 對設備特殊文件進行open()系統(tǒng)調(diào)用時,將調(diào)用驅(qū)動程序的open ()函數(shù):in t (static ssize_t globalvar_write(struct file *filp, const char *buf, size_t len, loff_t off)poll()函數(shù)poll方法是poll和select這兩個系統(tǒng)調(diào)用的后端實現(xiàn),用來查詢設備是否可讀或可寫,或是否處于某種特殊狀態(tài),原型為:un sig ned in t (*poll) (st
19、ruct file *, struct poll_table_struct *);我們將在設備的阻塞與非阻塞操作一節(jié)對該函數(shù)進行更深入的介紹。設備gobalvar的驅(qū)動程序的這些函數(shù)應分別命名為 gobalvar_open 、gobalvar_ release、 gobalvar_read、gobalvar_write、gobalvar_ioctl ,因此設備gobalvar的基本入 口點結構變量 gobalvar_fops 賦值如下:struct file_operati ons gobalvar_fops = read: gobalvar_read, write: gobalvar_wri
20、te,;上述代碼中對gobalvar_fops的初始化方法并不是標準C所支持的,屬于GNU擴展語法。完整的globalvar.c文件源代碼如下:#i nclude #in clude #in clude #in clude ope n) (struct inode * struct file *);其中參數(shù)in ode為設備特殊文件的in ode (索引結點)結構的指針,參數(shù)file是指向這一設備的文件結 構的指針。open()的主要任務是確定硬件處在就緒狀態(tài)、驗證次設備號的合法性(次設備號可以用MINOR(inode- i - rdev)取得)、控制使用設備的進程數(shù)、根據(jù)執(zhí)行情況返回狀態(tài)碼(
21、0表示成功,負數(shù)表示存在錯誤)等;release。函數(shù)當最后一個打開設備的用戶進程執(zhí)行close ()系統(tǒng)調(diào)用時,內(nèi)核將調(diào)用驅(qū)動程序的release ()函數(shù):void (*release) (struct inode * ,struct file *);release函數(shù)的主要任務是清理未結束的輸入/輸出操作、釋放資源、用戶自定義排他標志的復位等。read()函數(shù)當對設備特殊文件進行read()系統(tǒng)調(diào)用時,將調(diào)用驅(qū)動程序read()函數(shù):ssize_t (*read) (struct file *, char *, size_t, loff_t *);用來從設備中讀取數(shù)據(jù)。當該函數(shù)指針被賦為
22、NULL值時,將導致read系統(tǒng)調(diào)用出錯并返回-EINVALInvalid argument ,非法參數(shù)”。函數(shù)返回非負值表示成功讀取的字節(jié)數(shù)返回值為signed size數(shù)據(jù)類型,通常就是目標平臺上的固有整數(shù)類型。globalvar_read函數(shù)中內(nèi)核空間與用戶空間的內(nèi)存交互需要借助第2節(jié)所介紹的函數(shù):static ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t *off)copy_to_user(buf, &global_var, sizeof( in t);I,write()函數(shù)當設備特殊文件
23、進行write ()系統(tǒng)調(diào)用時,將調(diào)用驅(qū)動程序的write ()函數(shù):ssize_t (*write) (struct file *, const char *, size_t, loff_t *);向設備發(fā)送數(shù)據(jù)。如果沒有這個函數(shù),write系統(tǒng)調(diào)用會向調(diào)用程序返回一個-EINVAL。如果返回值非負,則表示成功寫入的字節(jié)數(shù)。globalvar_write函數(shù)中內(nèi)核空間與用戶空間的內(nèi)存交互需要借助第2節(jié)所介紹的函數(shù):ioctl()函數(shù)數(shù)原型為:該函數(shù)是特殊的控制函數(shù),可以通過它向設備傳遞控制信息或從設備取得狀態(tài)信息,函int (*ioctl) (struct inode * struct fi
24、le * ,unsigned int ,unsigned Iong);unsigned int參數(shù)為設備驅(qū)動程序要執(zhí)行的命令的代碼,由用戶自定義,unsigned long參數(shù)為相應的命令提供參數(shù),類型可以是整型、指針等。如果設備不提供ioctl入口點,則對于任何內(nèi)核未預先定義的請求,ioctl系統(tǒng)調(diào)用將返回錯誤-ENOTTY , No such ioctl fordevice ,該設備無此ioctl命令。如果 該設備方法返回一個非負值,那么該值會被返回給調(diào)用程序以表示調(diào)用成功。llseek()函數(shù)該函數(shù)用來修改文件的當前讀寫位置,并將新位置作為正的返回值返回,原型為:loff_t (*lls
25、eek) (struct file *, loff_t, i nt);MODULE_LICENSE(GPL);#define MAJOR_NUM 254 / 主設備號static ssize_t globalvar_read(struct file *, char *, size_t, loff_t*);static ssize_t globalvar_write(struct file *, const char *, size_t, loff_t*);II初始化字符設備驅(qū)動的file_operations 結構體struct file_operati ons globalvar_fops
26、=read: globalvar_read, write: globalvar_write,;static in t global_var = 0; /globalvar設備的全局變量static int _in it globalvar_ in it(void)int ret;II注冊設備驅(qū)動ret = register_chrdev(MAJOR_NUM, globalvar, & globalvar_fops);if (ret)prin tk(globalvar register failure);elseprin tk(globalvar register success);return
27、 ret;static void _exit globalvar_exit(void)int ret;II注銷設備驅(qū)動ret = un register_chrdev(MAJOR_NUM, globalvar);if (ret)prin tk(globalvar un register failure);elseprin tk(globalvar un register success);static ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t *off)/將global_var從內(nèi)核空間復制到
28、用戶空間if (copy_to_user(buf, &global_var, sizeof( in t)return - EFAULT;retur n sizeof( in t);static ssize_t globalvar_write(struct file *filp, const char *buf, size_t len, loff_t *off)/將用戶空間的數(shù)據(jù)復制到內(nèi)核空間的 global_varif (copy_from_user( &global_var, buf, sizeof( in t)return - EFAULT;retur n sizeof( in t);mo
29、dule_i nit(globalvar_i nit); module_exit(globalvar_exit);運行:編譯代碼,運行:加載globalvar模塊,再運行:發(fā)現(xiàn)其中多出了 254 globalvar 一行,如以下圖:接著我們可以運行:mknod /dev/globalvar c 254 0創(chuàng)建設備節(jié)點,用戶進程通過/dev/globalvar這個路徑就可以訪問到這個全局變量虛擬設備了。我們寫一個用戶態(tài)的程序 globalvartest.c來驗證上述設備:#in elude #in clude #i nclude #in clude main ()int fd, num;/ 打開
30、/dev/globalvarfd = ope n(/dev/globalvar, O_RDWR, S_IRUSR | S_IWUSR);if (fd != -1 )/ 初次讀 globalvarread(fd, &num, sizeof( in t);prin tf(The globalvar is %dn, nu m);/ 寫 globalvarprin tf(Please in put the num written to globalvar n);scan f(%d, &n um);write(fd, &num, sizeof( in t);/ 再次讀 globalvar read(fd
31、, &num, sizeof( in t);prin tf(The globalvar is %dn, nu m);/ 關閉/dev/globalvar close(fd);elseprintf(Device open failuren);編譯上述文件:運行可以發(fā)現(xiàn)globalvar設備可以正確的讀寫。第四講Linux設備驅(qū)動之并發(fā)控制在驅(qū)動程序中,當多個線程同時訪問相同的資源時驅(qū)動程序中的全局變量是一種典型的共享資源可能會引發(fā)”競態(tài)”,因此我們必須對共享資源進行并發(fā)控制。Linux內(nèi)核中解決并發(fā)控制的最常用方法是自旋鎖與信號量絕大多數(shù)時候作為互斥鎖使用。自旋鎖與信號量類似而不類”,類似說的是
32、它們功能上的相似性,不類指代它們在本質(zhì)和實現(xiàn)機理上完全不一樣,不屬于一類。自旋鎖不會引起調(diào)用者睡眠,如果自旋鎖已經(jīng)被別的執(zhí)行單元保持,調(diào)用者就一直循環(huán)查看是否該自 旋鎖的保持者已經(jīng)釋放了鎖,自旋就是在原地打轉(zhuǎn)”。而信號量則引起調(diào)用者睡眠,它把進程從運行隊列上拖出去,除非獲得鎖。這就是它們的”不類”。但是,無論是信號量,還是自旋鎖,在任何時刻,最多只能有一個保持者,即在任何時刻最多只能有 一個執(zhí)行單元獲得鎖。這就是它們的類似。鑒于自旋鎖與信號量的上述特點,一般而言,自旋鎖適合于保持時間非常短的情況,它可以在任何上 下文使用;信號量適合于保持時間較長的情況,會只能在進程上下文使用。如果被保護的共享
33、資源只在進程上下文訪問,則可以以信號量來保護該共享資源,如果對共享資源的訪問時間非常短,自旋鎖也是好的 選擇。但是,如果被保護的共享資源需要在中斷上下文訪問包括底半部即中斷處理句柄和頂半部即軟中 斷,就必須使用自旋鎖。與信號量相關的API主要有:定義信口 曰.號量struct semaphore sem;初始化信號量void sema_ in it (struct semaphore *sem, int val);該函數(shù)初始化信號量,并設置信號量sem的值為valvoid ini t_MUTEX (struct semaphore *sem);該函數(shù)用于初始化一個互斥鎖,即它把信號量sem的值
34、設置為1,等同于sema_init (struct semaphore*sem, 1);void in it_MUTEX_LOCKED (struct semaphore *sem);該函數(shù)也用于初始化一個互斥鎖,但它把信號量 sem的值設置為0,等同于sema_init (structsemaphore *sem, 0);獲得信號量void dow n(struct semaphore * sem);該函數(shù)用于獲得信號量 sem,它會導致睡眠,因此不能在中斷上下文使用;int dow n_in terruptible(struct semaphore * sem);該函數(shù)功能與down類似,
35、不同之處為,down不能被信號打斷,但down_interruptible 能被信號打斷;int dow n_trylock(struct semaphore * sem);該函數(shù)嘗試獲得信號量 sem,如果能夠立刻獲得,它就獲得該信號量并返回0,否則,返回非0值。它不會導致調(diào)用者睡眠,可以在中斷上下文使用。釋放信號量void up(struct semaphore * sem);該函數(shù)釋放信號量 sem,喚醒等待者。與自旋鎖相關的 API主要有:定義自旋鎖初始化自旋鎖該宏用于動態(tài)初始化自旋鎖lock獲得自旋鎖該宏用于獲得自旋鎖lock,如果能夠立即獲得鎖,它就馬上返回,否則,它將自旋在那里,
36、直到該自 旋鎖的保持者釋放;該宏嘗試獲得自旋鎖lock,如果能立即獲得鎖,它獲得鎖并返回真,否則立即返回假,實際上不再 在原地打轉(zhuǎn)”;釋放自旋鎖該宏釋放自旋鎖lock,它與spin_trylock 或spin_lock配對使用;除此之外,還有一組自旋鎖使用于中斷情況下的API。static int globalvar_open(struct inode *inode, struct file *filp)/獲得自選鎖spin _lock (&spin);臨界資源訪問if (globalvar_co unt)spin_unl ock (&spin);return - EBUSY;globalva
37、r_co un t+;/釋放自選鎖spin_unl ock (&spin);return 0;static int globalvar_release(struct inode *inode, struct file *filp)globalvar_co un t-;return 0;static ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t*off)if (dow n_in terruptible(&sem)return - ERESTARTSYS;if (copy_to_user(buf, &
38、global_var, sizeof( in t)up( &sem);return - EFAULT;up( &sem);retur n sizeof( in t);static ssize_t globalvar_write(struct file *filp, const char *buf, size_t le n, loff_t *off)if (dow n_in terruptible(&sem)retur n - ERESTARTSYS;if (copy_from_user( &global_var, buf, sizeof( in t) up( &sem);return - EF
39、AULT;up( &sem);retur n sizeof( in t);module_i nit(globalvar_i nit); module_exit(globalvar_exit);為了上述驅(qū)動程序的效果,我們啟動兩個進程分別打開/dev/globalvar。在兩個終端中調(diào)用./globalvartest.o 測試程序,當一個進程打開/dev/globalvar后,另外一個進程將打開失敗,輸出deviceopen failure,如以下圖:Itlllw Ivli # V i i b IIMW if Mm” LIjJ *l:i I if rnrtlj4 一 J 第五講Linux設備驅(qū)動
40、編程之阻塞與非阻塞阻塞操作是指,在執(zhí)行設備操作時,假設不能獲得資源,則進程掛起直到滿足可操作的條件再進行操 作。非阻塞操作的進程在不能進行設備操作時,并不掛起。被掛起的進程進入sleep狀態(tài),被從調(diào)度器的運行隊列移走,直到等待的條件被滿足。在Linux驅(qū)動程序中,我們可以使用等待隊列 wait queue丨來實現(xiàn)阻塞操作。wait queue很早就作為一個基本的功能單位出現(xiàn)在Linux內(nèi)核里了,它以隊列為基礎數(shù)據(jù)結構,與進程調(diào)度機制緊密結合,能夠用于實現(xiàn)核心的異步事件通知機制。等待隊列可以用來同步對系統(tǒng)資源的訪問,上節(jié)中所講述Linux信號量在內(nèi)核中也是由等待隊列來實現(xiàn)的。下面我們重新定義設備
41、globalvar,它可以被多個進程打開,但是每次只有當一個進程寫入了一個數(shù) 據(jù)之后本進程或其它進程才可以讀取該數(shù)據(jù),否則一直阻塞。#i nclude #in clude #in clude #in clude #in clude #in clude MODULE_LICENSE(GPL);#defi ne MAJOR_NUM 254static ssize_t globalvar_read(struct file *, char *, size_t, loff_t*);static ssize_t globalvar_write(struct file *, const char *, si
42、ze_t, loff_t*);struct file_operati ons globalvar_fops =read: globalvar_read, write: globalvar_write,;static int global_var = 0;static struct semaphore sem;static wait_queue_head_t outq;static int flag = 0;static int _in it globalvar_ in it(void)int ret;ret = register_chrdev(MAJOR_NUM, globalvar, & g
43、lobalvar_fops);if (ret)prin tk(globalvar register failure);elseprin tk(globalvar register success);in it_MUTEX( &sem);ini t_waitqueue_head(&o utq);return ret;static void _exit globalvar_exit(void)int ret;ret = unregister_chrdev(MAJOR_NUM, globalvar);if (ret)prin tk(globalvar un register failure);els
44、eprin tk(globalvar un register success);static ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t *off)/等待數(shù)據(jù)可獲得if (wait_eve nt_in terruptible(outq, flag != 0)return - ERESTARTSYS;if (dow n_in terruptible(&sem)return - ERESTARTSYS;flag = 0;if (copy_to_user(buf, &global_var, sizeo
45、f( in t)up( &sem);return - EFAULT;up( &sem);retur n sizeof( in t);static ssize_t globalvar_write(struct file *filp, const char *buf, size_t le n,loff_t *off)if (dow n_in terruptible(&sem)retur n - ERESTARTSYS;if (copy_from_user( &global_var, buf, sizeof( in t) up( &sem); return - EFAULT;up( &sem);fl
46、ag = 1;/通知數(shù)據(jù)可獲得wake_up_i nterruptible(&o utq);retur n sizeof( in t);module_i nit(globalvar_i nit);module_exit(globalvar_exit);編寫兩個用戶態(tài)的程序來測試,第一個用于阻塞地讀/dev/globalvar,另一個用于寫/dev/globalvar只有當后一個對/dev/globalvar進行了輸入之后,前者的 read才能返回。讀的程序為:#in clude #in clude #i nclude #in clude main ()int fd, num;fd = ope
47、n(/dev/globalvar, O_RDWR, S_IRUSR | S_IWUSR);if (fd != - 1) while (1) read(fd, &num, sizeof(i nt); /程序?qū)⒆枞诖苏Z句,除非有針對globalvar 的輸入prin tf(The globalvar is %dn, nu m);/如果輸入是0,則退出if (num = 0) close(fd); break; 寫的程序為:打開兩個終端,分別運行上述兩個應用程序,發(fā)現(xiàn)當在第二個終端中沒有輸入數(shù)據(jù)時,第一個終端沒 有輸出阻塞,每當我們在第二個終端中給 globalvar輸入一個值,第一個終端就會輸出
48、這個值,如以 下圖:1 r n w耳HLL-1*C1jjh 峙畀 Si、t 1 Lfrf ri 1 h*|g pii.f g _|f fe iIfif lui- lit. 1)A 1町p f k |i |ll/I*1*1 Witatrh*豊価41i |.3Ikr-g liJ11 p tp t411 IE* 1 ik可論4 BltlA1 1|.dwv h! 1”wiTi I -h ihp4 -j關于上述例程,我們補充說一點,如果將驅(qū)動程序中的read函數(shù)改為:return sizeof( in t);即交換 wait_event_interruptible(outq, flag != 0) 和
49、downnterruptible(&sem)的順序,這個驅(qū)動程序?qū)⒆兊貌豢蛇\行。實際上,當兩個可能要阻塞的事件同時出現(xiàn)時,即兩個wait_event或down擺在一起的時候,將變得非常危險,死鎖的可能性很大,這個時候我們要特別留意它們的出現(xiàn)順序。當然,我們應該盡 可能地防止這種情況的發(fā)生!還有一個與設備阻塞與非阻塞訪問息息相關的論題,即select和poll,select和poll的本質(zhì)一樣,前者在BSD Unix中引入,后者在 System V中引入。poll和select用于查詢設備的狀態(tài),以便用戶程序獲 知是否能對設備進行非阻塞的訪問,它們都需要設備驅(qū)動程序中的poll函數(shù)支持。驅(qū)動程序
50、中poll函數(shù)中最主要用到的一個API是poll_wait,其原型如下:void poll_wait(struct file *filp, wait_queue_heat_t *queue, poll_table * wait);poll_wait函數(shù)所做的工作是把當前進程添加到wait參數(shù)指定的等待列表poll_table丨中。下面我們給globalvar的驅(qū)動添加一個 poll函數(shù):static unsigned int globalvar_poll(struct file *filp, poll_table *wait)un sig ned int mask = 0;poll_wait(
51、filp, &outq, wait);/數(shù)據(jù)是否可獲得?if (flag != 0)mask |= POLLIN | POLLRDNORM; / 標示數(shù)據(jù)可獲得return mask;需要說明的是,poll_wait函數(shù)并不阻塞,程序中poll_wait(filp, &outq, wait)這句話的意思并不是說一直等待outq信號量可獲得,真正的阻塞動作是上層的select/poll函數(shù)中完成的。select/poll會在一個循環(huán)中對每個需要監(jiān)聽的設備調(diào)用它們自己的poll支持函數(shù)以使得當前進程被加入各個設備的等待列表。假設當前沒有任何被監(jiān)聽的設備就緒,則內(nèi)核進行調(diào)度調(diào)用schedule丨讓出
52、cpu進入阻塞狀態(tài),schedule返回時將再次循環(huán)檢測是否有操作可以進行,如此反復;否則,假設有任意一個設備就緒,select/poll都立即返回。我們編寫一個用戶態(tài)應用程序來測試改寫后的驅(qū)動。程序中要用到BSD Unix中引入的select函數(shù),其原型為:int select(i nt num fds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct其中readfds、writefds、exceptfds分別是被 select。監(jiān)視的讀、寫和異常處理的文件描述符集合, numfds的值是需要檢查的號碼最高的文件描述符
53、加1 otimeout參數(shù)是一個指向struct timeval類型的指針,它可以使select。在等待timeout時間后假設沒有文件描述符準備好則返回。struct timeval數(shù)據(jù)結構為:除此之外,我們還將使用以下API :FD_ZERO(fd_set *set) FD_SET( int fd,fd_set *set) FD_CLR(int fd,f d_set *set) FD_ISSET(i nt fd,fd_set *set)清除一個文件描述符集;將一個文件描述符加入文件描述符集中; 將一個文件描述符從文件描述符集中清除;判斷文件描述符是否被置位。下面的用戶態(tài)測試程序等待/dev
54、/globalvar可讀,但是設置了5秒的等待超時,假設超過5秒仍然沒有數(shù)據(jù)可讀,則輸出 No data within 5 seco nds開兩個終端,分別運行程序:一個對 globalvar進行寫,一個用上述程序?qū)lobalvar進行讀。當我們在寫終端給globalvar輸入一個值后,讀終端立即就能輸出該值,當我們連續(xù)5秒沒有輸入時,No datawithin 5 seco nds 在讀終端被輸出,如以下圖:iF百Bb”卄Ik.Ml*咼鼻 !liar ti*h &AalfiAiMIihlI h lit $Ht Ii h ip Mi-ddMhsN (1 |B Khf iFYr1 lirtSi
55、 1* it kiW9lb ti I*qarflc I dl* i甲1 9命 $1 Jcril iF 1/ 1 |llisli,I11IB 1#1第六講Linux設備驅(qū)動編程之異步通知結合阻塞與非阻塞訪問、poll函數(shù)可以較好地解決設備的讀寫,但是如果有了異步通知就更方便了。異步通知的意思是:一旦設備就緒,則主動通知應用程序,這樣應用程序根本就不需要查詢設備狀態(tài),這 一點非常類似于硬件上中斷哋概念,比較準確的稱謂是 ”信號驅(qū)動(SIGIO)的異步I/O。啟動我們先來看一個使用信號驅(qū)動的例子,它通過signal(SIGIO,input_handler)對STDIN_FILENO信號機制,輸入可獲
56、得時input_handler被調(diào)用,其源代碼如下:#in clude #in clude #i nclude #in clude #in clude #in clude #defi ne MAX_LEN 100void in put_ha ndler(i nt num)char dataMAX_LEN;int len;/讀取并輸出STDIN_FILENC上的輸入len = read(STDIN_FILENO, &data, MAX_LEN); datale n = 0;prin tf(i nput available:%sn, data);main ()int oflags;/啟動信號驅(qū)動機
57、制sig nal(SIGIO, i nput_ha ndler); fcntl(STDIN_FILENO, F_SETOWN, getpid(); oflags = fcntl(STDIN_FILENO, F_GETFL);fcntl(STDIN_FILENO, F_SETFL, oflags | FASYNC);/最后進入一個死循環(huán),程序什么都不干了,只有信號能激發(fā)input_handler的運行/如果程序中沒有這個死循環(huán),會立即執(zhí)行完畢while (1);程序的運行效果如以下圖:為了使設備支持該機制,我們需要在驅(qū)動程序中實現(xiàn)fasync()函數(shù),并在write()函數(shù)中當數(shù)據(jù)被寫入時,調(diào)用
58、kill_fasync()函數(shù)激發(fā)一個信號,此部分工作留給讀者來完成。第七講Linux設備驅(qū)動編程之中斷處理IRQ 的 API request_irq()和 free_irq(),與Linux設備驅(qū)動中中斷處理相關的首先是申請與釋放request_irq()的原型為:int request_irq (un sig ned int irq,void (*ha ndler)(i nt irq, void *dev_id, struct pt_regs *regs), un sig ned long irqflags, const char * dev name,void *dev_id);irq是
59、要申請的硬件中斷號;handler是向系統(tǒng)登記的中斷處理函數(shù),是一個回調(diào)函數(shù),中斷發(fā)生時,系統(tǒng)調(diào)用這個函數(shù),dev_id參數(shù)將被傳遞;irqflags是中斷處理的屬性, 假設設置SA_INTERRUPT ,標明中斷處理程序是快速處理程序, 快速處 理程序被調(diào)用時屏蔽所有中斷, 慢速處理程序不屏蔽;假設設置SA_SHIRQ,則多個設備共享中斷,dev_id 在中斷共享時會用到,一般設置為這個設備的device結構本身或者NULL。free_irq()的原型為:void free_irq (un sig ned int irq,void *dev_id);另外,與Linux中斷息息相關的一個重要概
60、念是Linux中斷分為兩個半部:上半部tophalf和下半部(bottom half)。上半部的功能是”登記中斷”,當一個中斷發(fā)生時,它進行相應地硬件讀寫后就把中斷例程的下半部掛到該設備的下半部執(zhí)行隊列中去。因此,上半部執(zhí)行的速度就會很快,可以服務更多的中斷請 求。但是,僅有登記中斷”是遠遠不夠的,因為中斷的事件可能很復雜。因此,Linux引入了一個下半部,來完成中斷事件的絕大多數(shù)使命。下半部和上半部最大的不同是下半部是可中斷的,而上半部是不可中斷 的,下半部幾乎做了中斷處理程序所有的事情,而且可以被新的中斷打斷!下半部則相對來說并不是非常 緊急的,通常還是比較耗時的,因此由系統(tǒng)自行安排運行時
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經(jīng)權益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
- 6. 下載文件中如有侵權或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 信息化技術在農(nóng)業(yè)生產(chǎn)中的合作協(xié)議
- 農(nóng)民工在崗培訓與勞務派遣合同
- 購買物業(yè)管理服務協(xié)議書
- 農(nóng)業(yè)生產(chǎn)經(jīng)營資金互助保障協(xié)議
- 智慧寓言伊索寓言故事解讀
- 高考語文復習:專題六、七
- 體育培訓中心學員意外事故的免責及保障協(xié)議
- 高考文言文斷句100題專項練習(附答案及翻譯最方便)
- 小馬過河自我成長的故事解讀
- 農(nóng)業(yè)旅游開發(fā)手冊
- 2024年福建省廈門市翔安區(qū)殘疾人聯(lián)合會招聘殘疾人工作聯(lián)絡員29人歷年重點基礎提升難、易點模擬試題(共500題)附帶答案詳解
- 幼兒園家長會疾病預防
- 《儲糧害蟲防治技術》課件-第六章 儲糧保護劑及其應用
- 排水管道施工組織設計排水管道施工組織設計排水施工排水管道施工施工設計
- 人工智能科普教育活動方案設計
- 2024未來會議:AI與協(xié)作前沿趨勢白皮書
- 2024年廣東普通專升本《公共英語》完整版真題
- 國家中長期科技發(fā)展規(guī)劃(2021-2035)
- 中國民族音樂的宮庭音樂
- 單原子催化劑的合成與應用
- 水利工程施工驗收規(guī)范對工程監(jiān)理單位的要求
評論
0/150
提交評論