Linux設(shè)備驅(qū)動開發(fā)_第1頁
Linux設(shè)備驅(qū)動開發(fā)_第2頁
Linux設(shè)備驅(qū)動開發(fā)_第3頁
Linux設(shè)備驅(qū)動開發(fā)_第4頁
Linux設(shè)備驅(qū)動開發(fā)_第5頁
已閱讀5頁,還剩128頁未讀, 繼續(xù)免費閱讀

下載本文檔

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

文檔簡介

會計學1Linux設(shè)備驅(qū)動開發(fā)設(shè)備驅(qū)動概述設(shè)備由兩部分組成,一個是被稱為控制器的電器部分,另一個是機械部分。一組寄存器組被賦予到各個控制器。I/O端口包含4組寄存器,即狀態(tài)寄存器,控制寄存器,數(shù)據(jù)輸入寄存器,數(shù)據(jù)輸出寄存器。狀態(tài)寄存器擁有可以被CPU讀取的(狀態(tài))位,用來指示當前命令是否執(zhí)行完畢,或者字節(jié)是否可以被讀出或?qū)懭耄约叭魏五e誤提示??刂萍拇嫫鲃t用于啟動一條命令(指令)或者改變設(shè)備的(工作)模式。數(shù)據(jù)輸入寄存器用于獲取輸入的數(shù)據(jù)。數(shù)據(jù)輸出寄存器則向CPU發(fā)送結(jié)果。第1頁/共133頁設(shè)備驅(qū)動概述操作系統(tǒng)是通過各種驅(qū)動程序來駕馭硬件設(shè)備,它為用戶屏蔽了各種各樣的設(shè)備。設(shè)備驅(qū)動程序是操作系統(tǒng)內(nèi)核和機器硬件之間的接口,系統(tǒng)調(diào)用是操作系統(tǒng)內(nèi)核和應(yīng)用程序之間的接口。在應(yīng)用程序看來,硬件設(shè)備只是一個設(shè)備文件,應(yīng)用程序可以象操作普通文件一樣對硬件設(shè)備進行操作.第2頁/共133頁4設(shè)備驅(qū)動概述驅(qū)動完成以下的功能:對設(shè)備初始化和釋放.把數(shù)據(jù)從內(nèi)核傳送到硬件和從硬件讀取數(shù)據(jù).讀取應(yīng)用程序傳送給設(shè)備文件的數(shù)據(jù)和回送應(yīng)用程序請求的數(shù)據(jù).檢測和處理設(shè)備出現(xiàn)的錯誤.第3頁/共133頁5設(shè)備驅(qū)動概述無操作系統(tǒng)的設(shè)備驅(qū)動有操作系統(tǒng)的設(shè)備驅(qū)動ApplicationDriverHardwareApplicationLibAPISystemcallEmbeddedOSHardware不帶操作系統(tǒng)軟件結(jié)構(gòu)帶操作系統(tǒng)軟件結(jié)構(gòu)Driver第4頁/共133頁6Linux設(shè)備驅(qū)動第5頁/共133頁7Linux設(shè)備驅(qū)動用戶級的程序使用內(nèi)核提供的標準系統(tǒng)調(diào)用來與內(nèi)核通訊,這些系統(tǒng)調(diào)用有:open(),read(),write(),ioctl(),close()等等。

Linux的內(nèi)核是映射到每一個進程的高1G空間。每一個用戶進程運行時都好像有一份內(nèi)核的拷貝,每當用戶進程使用系統(tǒng)調(diào)用時,都自動地將運行模式從用戶級轉(zhuǎn)為內(nèi)核級,此時進程在內(nèi)核的地址空間中運行。

第6頁/共133頁8Linux設(shè)備驅(qū)動Linux內(nèi)核使用“設(shè)備無關(guān)”的I/O子系統(tǒng)來為所有的設(shè)備服務(wù)。每個設(shè)備都提供標準接口給內(nèi)核,盡可能地隱藏了自己的特性。用戶程序使用一些基本的系統(tǒng)調(diào)用從設(shè)備讀取數(shù)據(jù)并且將它們存入緩沖的例子。我們可以看到,每當一個系統(tǒng)調(diào)用被使用時,內(nèi)核就轉(zhuǎn)到相應(yīng)的設(shè)備驅(qū)動例程來操縱硬件。

第7頁/共133頁Linux設(shè)備驅(qū)動Linux操作系統(tǒng)把設(shè)備納入文件系統(tǒng)的范疇來管理。每個設(shè)備在Linux系統(tǒng)上看起來都像一個文件,它們存放在/dev目錄中,稱為"設(shè)備節(jié)點"。對文件操作的系統(tǒng)調(diào)用大都適用于設(shè)備文件。

第8頁/共133頁10Linux設(shè)備驅(qū)動Linux下設(shè)備的屬性設(shè)備的類型:字符設(shè)備、塊設(shè)備、網(wǎng)絡(luò)設(shè)備主設(shè)備號:標識設(shè)備對應(yīng)的驅(qū)動程序。一般“一個主設(shè)備號對應(yīng)一個驅(qū)動程序”次設(shè)備號:每個驅(qū)動程序負責管理它所驅(qū)動的幾個硬件實例,這些硬件實例則由次設(shè)備號來表示。同一驅(qū)動下的實例編號,用于確定設(shè)備文件所指的設(shè)備??赏ㄟ^ls–l“設(shè)備文件名”命令查看設(shè)備的主次設(shè)備號,以及設(shè)備的類型。第9頁/共133頁11Linux設(shè)備驅(qū)動Linux設(shè)備驅(qū)動程序是一組由內(nèi)核中的相關(guān)子例程和數(shù)據(jù)組成的I/O設(shè)備軟件接口。每當用戶程序要訪問某個設(shè)備時,它就通過系統(tǒng)調(diào)用,讓內(nèi)核代替它調(diào)用相應(yīng)的驅(qū)動例程。這就使得控制從用戶進程轉(zhuǎn)移到了驅(qū)動例程,當驅(qū)動例程完成后,控制又被返回至用戶進程。第10頁/共133頁12一些重要的數(shù)據(jù)結(jié)構(gòu)大部分驅(qū)動程序涉及三個重要的內(nèi)核數(shù)據(jù)結(jié)構(gòu):文件操作file_operations結(jié)構(gòu)體文件對象file結(jié)構(gòu)體索引節(jié)點inode結(jié)構(gòu)體第11頁/共133頁13一些重要的數(shù)據(jù)結(jié)構(gòu)文件操作結(jié)構(gòu)體file_operations結(jié)構(gòu)體file_operations在頭文件linux/fs.h中定義,用來存儲驅(qū)動內(nèi)核模塊提供的對設(shè)備進行各種操作的函數(shù)的指針。結(jié)構(gòu)體的每個域都對應(yīng)著驅(qū)動模塊用來處理某個被請求的事務(wù)的函數(shù)的地址。structfile_operations{ structmodule*owner; ssize_t(*read)(structfile*,char__user*,size_t,loff_t*); ssize_t(*write)(structfile*,constchar__user*,size_t,loff_t*);

。。。

}第12頁/共133頁14一些重要的數(shù)據(jù)結(jié)構(gòu)file_operations重要的成員Structmodule*owner

,指向擁有該結(jié)構(gòu)體的模塊的指針。內(nèi)核使用該指針維護模塊使用計數(shù)。方法llseek用來修改文件的當前讀寫位置,把新位置作為返回值返回。loff_t是在LINUX中定義的長偏移量方法read用來從設(shè)備中讀取數(shù)據(jù)。非負返回值表示成功讀取的直接數(shù)。方法write向設(shè)備發(fā)送數(shù)據(jù)。方法ioctl提供一種執(zhí)行設(shè)備特定命令的方法。第13頁/共133頁15一些重要的數(shù)據(jù)結(jié)構(gòu)file_operations重要的成員unsignedint(*poll)(structfile*,structpoll_table_struct*);

系統(tǒng)調(diào)用select和poll的后端實現(xiàn),用這兩個系統(tǒng)調(diào)用來查詢設(shè)備是否可讀寫,或是否處于某種狀態(tài)。如果poll為空,則驅(qū)動設(shè)備會被認為即可讀又可寫,返回值是一個狀態(tài)掩碼。int(*mmap)(structfile*,structvm_area_struct*);

將設(shè)備內(nèi)存映射到進程地址空間

第14頁/共133頁16一些重要的數(shù)據(jù)結(jié)構(gòu)file_operations重要的成員驅(qū)動內(nèi)核模塊是不需要實現(xiàn)每個函數(shù)的。相對應(yīng)的file_operations的項就為NULL。Gcc的語法擴展,使得可以定義該結(jié)構(gòu)體:

structfile_operationsfops={ read:device_read, write:device_write, open:device_open, release:device_release};沒有顯示聲明的結(jié)構(gòu)體成員都被gcc初始化為NULL。第15頁/共133頁17一些重要的數(shù)據(jù)結(jié)構(gòu)file_operations重要的成員標準C的標記化結(jié)構(gòu)體的初始化方法:

structfile_operationsfops={ .read=device_read, .write=device_write, .open=device_open, .release=device_release };

推薦使用該方法,提高移植性,方法允許對結(jié)構(gòu)體成員進行重新排列。沒有顯示聲明的結(jié)構(gòu)體成員同樣都被gcc初始化為NULL。指向結(jié)構(gòu)體file_operations的指針通常命名為fops。第16頁/共133頁18一些重要的數(shù)據(jù)結(jié)構(gòu)文件對象file結(jié)構(gòu)體

文件對象file代表著一個打開的文件。進程通過文件描述符fd與已打開文件的file結(jié)構(gòu)相聯(lián)系。進程通過它對文件的線性邏輯空間進行操作。例如:file->f_op->read();Structfile在<linux/fs.h>中定義。指向結(jié)構(gòu)體structfile的指針通常命名為filp,或者file。建議使用文件指針filp。第17頁/共133頁19一些重要的數(shù)據(jù)結(jié)構(gòu)文件對象file結(jié)構(gòu)體的成員Structfile_operations*f_op;

與文件相關(guān)的操作結(jié)構(gòu)體指針。與文件相關(guān)的操作是在打開文件的時候確定下來的,也就是確定該指針的值。可在需要的時候,改變指針所指向的文件操作結(jié)構(gòu)體。用C語言實現(xiàn)面向?qū)ο缶幊痰姆椒ㄖ剌d。其他成員可先忽略,后面具體實例分析。因為設(shè)備驅(qū)動模塊并不自己直接填充結(jié)構(gòu)體file,只是使用file中的數(shù)據(jù)。第18頁/共133頁20一些重要的數(shù)據(jù)結(jié)構(gòu)索引節(jié)點inode結(jié)構(gòu)文件打開,在內(nèi)存建立副本后,由唯一的索引節(jié)點inode描述。與file結(jié)構(gòu)不同。file結(jié)構(gòu)是進程使用的結(jié)構(gòu),進程每打開一個文件,就建立一個file結(jié)構(gòu)。不同的進程打開同一個文件,建立不同的file結(jié)構(gòu)。Inode結(jié)構(gòu)是內(nèi)核使用的結(jié)構(gòu),文件在內(nèi)存建立副本,就建立一個inode結(jié)構(gòu)來描述。一個文件在內(nèi)存里面只有一個inode結(jié)構(gòu)對應(yīng)。第19頁/共133頁21一些重要的數(shù)據(jù)結(jié)構(gòu)索引節(jié)點inode結(jié)構(gòu)Inode結(jié)構(gòu)包含大量描述文件信息的成員變量。但是對于描述設(shè)備文件的inode,跟設(shè)備驅(qū)動有關(guān)的成員只有兩個。Dev_ti_rdev;包含真正的設(shè)備編號。Structcdev*i_cdev;指向cdev結(jié)構(gòu)體的指針。cdev是表示字符設(shè)備的內(nèi)核數(shù)據(jù)結(jié)構(gòu)。從inode中獲得主設(shè)備號和次設(shè)備號的宏:Unsignedintiminor(structinode*inode);Unsignedintimajor(structinode*inode);第20頁/共133頁22Linux設(shè)備驅(qū)動主設(shè)備號和次設(shè)備號的內(nèi)部表達:Dev_t類型用于保存設(shè)備號,稱為設(shè)備編號。/linux/types.h文件中定義。目前設(shè)備編號dev_t是一個32位的整數(shù),其中12位表示主設(shè)備號,20位表示次設(shè)備號。通過設(shè)備編號獲取主次設(shè)備號:MAJOR(dev_tdev);MINOR(dev_tdev);通過主次設(shè)備號合成設(shè)備編號:MKDEV(intmajor,intminor);Dev_t格式以后可能會發(fā)生變化,但只要使用這些宏,就可保證設(shè)備驅(qū)動程序的正確性。第21頁/共133頁23分配和釋放字符設(shè)備號編寫驅(qū)動程序要做的第一件事,為字符設(shè)備獲取一個設(shè)備號。事先知道所需要的設(shè)備編號(主設(shè)備號)的情況:intregister_chrdev_region(dev_tfirst,unsignedcount,constchar*name)first是要分配的起始設(shè)備編號值。first的次設(shè)備號通常設(shè)置為0。Count所請求的連續(xù)設(shè)備編號的個數(shù)。Name設(shè)備名稱,指和該編號范圍建立關(guān)系的設(shè)備。分配成功返回0。第22頁/共133頁24分配和釋放字符設(shè)備號動態(tài)分配設(shè)備編號(主要是主設(shè)備號)intalloc_chrdev_region(dev_t*dev,unsignedbaseminor,unsignedcount,constchar*name)dev是一個僅用于輸出的參數(shù),它在函數(shù)成功完成時保存已分配范圍的第一個編號。baseminor應(yīng)當是請求的第一個要用的次設(shè)備號,它常常是0.count和name參數(shù)跟request_chrdev_region的一樣.第23頁/共133頁25分配和釋放字符設(shè)備號不再使用時,釋放這些設(shè)備編號。使用以下函數(shù):voidunregister_chrdev_region(dev_tfrom,unsignedcount)在模塊的卸載函數(shù)中調(diào)用該函數(shù)。第24頁/共133頁26分配和釋放字符設(shè)備號新驅(qū)動程序,建議使用動態(tài)分配機制獲取主設(shè)備號,也就是使用alloc_chrdev_region()。動態(tài)分配導(dǎo)致無法預(yù)先創(chuàng)建設(shè)備節(jié)點??稍诜峙湓O(shè)備號后,從/proc/devices文件中獲取。為了加載后自動創(chuàng)建設(shè)備文件,可以通過編寫內(nèi)核模塊加載腳本實現(xiàn)。第25頁/共133頁27字符設(shè)備的注冊內(nèi)核內(nèi)部使用structcdev結(jié)構(gòu)表示字符設(shè)備。編寫設(shè)備驅(qū)動的第二步就是注冊該設(shè)備。包含<linux/cdev.h>頭文件。獲取一個獨立的cdev結(jié)構(gòu):structcdev*my_cdev=cdev_alloc();調(diào)用cdev_init初始化cdev結(jié)構(gòu)體voidcdev_init(structcdev*cdev,structfile_operations*fops);初始化該設(shè)備的所有者字段:dev->cdev.owner=THIS_MODULE;初始化該設(shè)備的可用操作集:dev->cdev.ops=&device_fops;第26頁/共133頁28字符設(shè)備的注冊編寫設(shè)備驅(qū)動的第二步就是注冊該設(shè)備。cdev結(jié)構(gòu)已建立和初始化,最后通過cdev_add函數(shù)把它告訴內(nèi)核:

intcdev_add(structcdev*dev,dev_tnum,unsignedintcount);dev是要添加的設(shè)備的cdev結(jié)構(gòu),num是這個設(shè)備對應(yīng)的第一個設(shè)備編號,count是應(yīng)當關(guān)聯(lián)到設(shè)備的設(shè)備號的數(shù)目.卸載字符設(shè)備時,調(diào)用相反的動作函數(shù):voidcdev_del(structcdev*dev);第27頁/共133頁29設(shè)備的注冊早期方法:內(nèi)核中仍有許多字符驅(qū)動不使用剛剛描述過的cdev接口。沒有更新到2.6內(nèi)核接口的老代碼。注冊一個字符設(shè)備的早期方法: intregister_chrdev(unsignedintmajor,constchar*name,structfile_operations*fops);major是給定的主設(shè)備號。為0代表什么?name是驅(qū)動的名字(將出現(xiàn)在/proc/devices),fops是設(shè)備驅(qū)動的file_operations結(jié)構(gòu)。register_chrdev將給設(shè)備分配0-255的次設(shè)備號,并且為每一個建立一個缺省的cdev結(jié)構(gòu)。從系統(tǒng)中卸載字符設(shè)備的函數(shù): intunregister_chrdev(unsignedintmajor,constchar*name);第28頁/共133頁30Open方法編寫字符設(shè)備驅(qū)動的第三步:定義設(shè)備驅(qū)動與文件系統(tǒng)的接口,file_operation結(jié)構(gòu)體的函數(shù)定義。open方法int(*open)(structinode*inode,structfile*filp);驅(qū)動程序提供open方法,讓用戶進程使用設(shè)備之前,進行一些初始化的工作。檢查設(shè)備特定的錯誤。如果第一次打開設(shè)備,則初始化設(shè)備。如果需要,更新f_op指針,更換操作方法集。分配并填充要放進filp->private_data的任何數(shù)據(jù)結(jié)構(gòu)。第29頁/共133頁31Open方法對于設(shè)備文件,inode參數(shù)只有兩個參數(shù)對設(shè)備驅(qū)動有用的。Dev_ti_rdev;包含真正的設(shè)備編號。Structcdev*i_cdev;指向cdev結(jié)構(gòu)體的指針。i_cdev里面包含我們之前建立的cdev結(jié)構(gòu)。但是有時候,我們需要的是包含cdev結(jié)構(gòu)的描述設(shè)備的結(jié)構(gòu)。使用通過成員地址獲取結(jié)構(gòu)體地址的宏container_of,在<linux/kernel.h>中定義:container_of(pointer,container_type,container_field);這個宏使用一個指向container_field類型的成員的指針,它在一個container_type類型的結(jié)構(gòu)中,宏通過分析他們關(guān)系,返回指向包含該成員的結(jié)構(gòu)體指針.第30頁/共133頁32Open方法在myscull_open,這個宏用來找到適當?shù)脑O(shè)備結(jié)構(gòu):dev=container_of(inode->i_cdev,structscull_dev,cdev);找到myscull_dev結(jié)構(gòu)后,scull在filp->private_data中存儲其指針,為以后存取使用.

filp->private_data=dev;

第31頁/共133頁33release方法release方法做open相反的工作釋放open分配給filp->private_data的內(nèi)存空間。在最后一次的關(guān)閉操作時,關(guān)閉設(shè)備。不是每個close系統(tǒng)調(diào)用引起調(diào)用release方法。第32頁/共133頁34Read和Write方法Read的任務(wù),就是從設(shè)備拷貝數(shù)據(jù)到用戶空間。Write的任務(wù),則從用戶空間拷貝數(shù)據(jù)到設(shè)備。ssize_tread(structfile*filp,char__user*buff,size_tcount,loff_t*offp);ssize_twrite(structfile*filp,constchar__user*buff,size_tcount,loff_t*offp);filp是文件對象指針,count

是請求的傳輸數(shù)據(jù)大小.buff

參數(shù)對write來說是指向持有被寫入數(shù)據(jù)的緩存,對read則是放入新數(shù)據(jù)的空緩存.offp

是指向一個“l(fā)ongoffsettype”的指針,它指出用戶正在存取的文件位置.返回值是“signedsizetype”類型;第33頁/共133頁35Read和Write方法read和write方法的buff參數(shù)是用戶空間指針,不能被內(nèi)核代碼直接解引用。__user字符串只是形式上的說明,表明是用戶空間地址。驅(qū)動必須能夠存取用戶空間緩存以完成它的工作。內(nèi)核如何解決這個問題?為安全起見,內(nèi)核提供專用的函數(shù)來完成對用戶空間的存取。這些專用函數(shù)在<asm/uaccess.h>中聲明。

unsignedlongcopy_to_user(void__user*to,constvoid*from,unsignedlongcount); unsignedlongcopy_from_user(void*to,constvoid__user*from,unsignedlongcount);大多數(shù)讀寫函數(shù)都會調(diào)用這兩個函數(shù),用于跟應(yīng)用程序空間交流信息。第34頁/共133頁36Read和Write方法典型的Read函數(shù)對參數(shù)的使用。第35頁/共133頁37llseek函數(shù)llseek函數(shù)用于對設(shè)備文件訪問定位。驅(qū)動接口loff_t(*llseek)(structfile*,loff_t,int);庫函數(shù)off_tlseek(intfiledes,off_toffset,intwhence);

參數(shù)offset的含義取決于參數(shù)whence:如果whence是SEEK_SET,文件偏移量將被設(shè)置為offset。如果whence是SEEK_CUR,文件偏移量將被設(shè)置為cfo加上offset,offset可以為正也可以為負。如果whence是SEEK_END,文件偏移量將被設(shè)置為文件長度加上offset,offset可以為正也可以為負。SEEK_SET、SEEK_CUR和SEEK_END是SystemV引入的,是0、1和2。第36頁/共133頁38ioctl

進行超出簡單的數(shù)據(jù)傳輸之外的操作,進行各種硬件控制操作.ioctl方法和用戶空間版本不同的原型:

int(*ioctl)(structinode*inode,structfile*filp,

unsignedintcmd,unsignedlongarg)不管可選的參數(shù)arg是否由用戶給定為一個整數(shù)或一個指針,它都以一個unsignedlong的形式傳遞。返回值

POSIX標準規(guī)定:如果使用了不合適的ioctl命令號,應(yīng)當返回-ENOTTY。這個錯誤碼被C庫解釋為“不合適的設(shè)備ioctl。

-EINVAL也是相當普遍的。第37頁/共133頁39結(jié)構(gòu)化設(shè)備驅(qū)動程序設(shè)備結(jié)構(gòu)體把與某設(shè)備相關(guān)的所有內(nèi)容定義為一個設(shè)備結(jié)構(gòu)體其中包括設(shè)備驅(qū)動涉及的硬件資源、全局軟件資源、控制(自旋鎖、互斥鎖、等待隊列、定時器等)在涉及設(shè)備的操作時,就僅僅操作這個結(jié)構(gòu)體第38頁/共133頁40Linux設(shè)備驅(qū)動的并發(fā)控制第39頁/共133頁41設(shè)備驅(qū)動的并發(fā)控制在驅(qū)動程序中,當多個線程同時訪問相同的資源時,可能會引發(fā)“競態(tài)”,必須對共享資源進行并發(fā)控制。并發(fā)和競態(tài)廣泛存在。并發(fā)控制的目的: 使得線程訪問共享資源的操作是原子操作。原子操作: 在執(zhí)行過程中不會被別的代碼路徑所中斷的操作。驅(qū)動程序中的全局變量是一種典型的共享資源。第40頁/共133頁42考慮一個非常簡單的共享資源的例子:一個全局整型變量和一個簡單的臨界區(qū),其中的操作僅僅是將整型變量的值增加1:

i++

該操作可以轉(zhuǎn)化成下面三條機器指令序列:得到當前變量i的值并拷貝到一個寄存器中將寄存器中的值加1把i的新值寫回到內(nèi)存中

原子操作第41頁/共133頁43原子操作內(nèi)核任務(wù)1內(nèi)核任務(wù)2獲得i(1)

增加

i(1->2)

寫回

i(2)

獲得

i(2)

增加

i(2->3)

寫回

i(3)內(nèi)核任務(wù)1內(nèi)核任務(wù)2獲得

i(1)增加i(1->2)

獲得i(1)

增加

i(1->2)

寫回i(2)寫回i(2)可能的實際執(zhí)行結(jié)果:期望的結(jié)果第42頁/共133頁44Linux內(nèi)核的并發(fā)控制在內(nèi)核空間的內(nèi)核任務(wù)需要考慮同步內(nèi)核空間中的共享數(shù)據(jù)對內(nèi)核中的所有任務(wù)可見,所以當在內(nèi)核中訪問數(shù)據(jù)時,就必須考慮是否會有其他內(nèi)核任務(wù)并發(fā)訪問的可能、是否會產(chǎn)生競爭條件、是否需要對數(shù)據(jù)同步。第43頁/共133頁45確定保護對象

找出哪些數(shù)據(jù)需要保護是關(guān)鍵所在內(nèi)核任務(wù)的局部數(shù)據(jù)僅僅被它本身訪問,顯然不需要保護。如果數(shù)據(jù)只會被特定的進程訪問,也不需加鎖

大多數(shù)內(nèi)核數(shù)據(jù)結(jié)構(gòu)都需要加鎖:若有其它內(nèi)核任務(wù)可以訪問這些數(shù)據(jù),那么就給這些數(shù)據(jù)加上某種形式的鎖;若任何其它東西能看到它,那么就要鎖住它。

Linux內(nèi)核的并發(fā)控制第44頁/共133頁46Linux內(nèi)核的并發(fā)控制并發(fā)控制的機制中斷屏蔽,原子數(shù)操作,自旋鎖和信號量都是解決并發(fā)問題的機制。中斷屏蔽很少被單獨使用,原子操作只能針對整數(shù)來進行。因此自旋鎖和信號量應(yīng)用最為廣泛。

第45頁/共133頁47中斷屏蔽單CPU系統(tǒng)中,避免竟態(tài)的一種簡單方式保證正在執(zhí)行的內(nèi)核執(zhí)行路徑不被中斷處理程序所搶占,防止竟態(tài)條件的發(fā)生。Local_irq_disable() //關(guān)中斷…Criticalsection //臨界區(qū)…Local_irq_enable() //開中斷中斷對內(nèi)核非常重要,長時間屏蔽中斷非常危險!只適合短時間的關(guān)閉對SMP多CPU引發(fā)的竟態(tài)無效第46頁/共133頁48鎖機制可以避免競爭狀態(tài)正如門鎖和門一樣,門后的房間可想象成一個臨界區(qū)。在一段時間內(nèi),房間里只能有一個內(nèi)核任務(wù)存在,當一個任務(wù)進入房間后,它會鎖住身后的房門;當它結(jié)束對共享數(shù)據(jù)的操作后,就會走出房間,打開門鎖。如果另一個任務(wù)在房門上鎖時來了,那么它就必須等待房間內(nèi)的任務(wù)出來并打開門鎖后,才能進入房間。

加鎖機制第47頁/共133頁49任何要訪問臨界資源的代碼首先都需要占住相應(yīng)的鎖,這樣該鎖就能阻止來自其它內(nèi)核任務(wù)的并發(fā)訪問:

任務(wù)1

試圖鎖定隊列成功:獲得鎖訪問隊列…

為隊列解除鎖…

任務(wù)2試圖鎖定隊列失敗:等待…

等待…

等待…

成功:獲得鎖

訪問隊列…為隊列解除鎖加鎖機制第48頁/共133頁50原子數(shù)操作整型原子數(shù)操作原子變量初始化

atomic_ttest=ATOMIC_INIT(i);設(shè)置原子變量的值

voidatomic_set(atomic_t*v,inti)獲得原子變量的值

atomic_read(v)原子變量加

voidatomic_add(inti,atomic_t*v)原子變量減

voidatomic_sub(inti,atomic_t*v)第49頁/共133頁51原子數(shù)操作整型原子數(shù)操作原子變量的自增操作

voidatomic_inc(atomic_t*v)原子變量的自減操作

voidatomic_dec(atomic_t*v)操作并測試(測試其是否為0,0為true,否為false)atomic_inc_and_test(atomic_t*v)atomic_dec_and_test(atomic_t*v)intatomic_sub_and_test(inti,atomic_t*v)操作并返回(返回新值)intatomic_add_return(inti,atomic_t*v)intatomic_sub_return(inti,atomic_t*v)第50頁/共133頁52原子數(shù)操作原子位操作設(shè)置位

voidset_bit(intnr,volatileunsignedlong*addr)清除位

voidclear_bit(intnr,volatileunsignedlong*addr)改變位

change_bit(nr,p)測試位

test_bit(intnr,constvolatileunsignedlong*p)測試并操作位

test_and_set_bit(nr,p)第51頁/共133頁53自旋鎖自旋鎖是專為防止多處理器并發(fā)而引入的一種鎖,它在內(nèi)核中大量應(yīng)用于中斷處理等部分。而對于單處理器來說,防止中斷處理中的并發(fā)可簡單采用關(guān)閉中斷的方式,不需要自旋鎖。自旋鎖最多只能被一個內(nèi)核任務(wù)持有,若一個內(nèi)核任務(wù)試圖請求一個已被持有的自旋鎖,那么這個任務(wù)就會一直進行忙循環(huán),也就是旋轉(zhuǎn),等待鎖重新可用。

自旋鎖可以在任何時刻防止多于一個的內(nèi)核任務(wù)同時進入臨界區(qū),因此這種鎖可有效地避免多處理器上并發(fā)運行的內(nèi)核任務(wù)競爭共享資源。第52頁/共133頁54自旋鎖自旋鎖的初衷就是:

在短期間內(nèi)進行輕量級的鎖定。一個被爭用的自旋鎖使得請求它的線程在等待鎖重新可用的期間進行自旋(特別浪費處理器時間),所以自旋鎖不應(yīng)該被持有時間過長。如果需要長時間鎖定的話,最好使用信號量。

第53頁/共133頁55自旋鎖自旋鎖防止在不同CPU上的執(zhí)行單元對共享資源的同時訪問,以及不同進程上下文互相搶占導(dǎo)致的對共享資源的非同步訪問。在單CPU且不可搶占的內(nèi)核下,自旋鎖的所有操作都是空操作。

自旋鎖不允許任務(wù)睡眠。第54頁/共133頁56自旋鎖自旋鎖的基本形式如下:

spin_lock(&mr_lock); /*臨界區(qū)*/ spin_unlock(&mr_lock);第55頁/共133頁57自旋鎖自旋鎖原語要求包含文件是<linux/spinlock.h>.鎖的類型是spinlock_t.鎖的兩種初始化方法:spinlock_tmy_lock=SPIN_LOCK_UNLOCKED;voidspin_lock_init(spinlock_t*lock);進入一個臨界區(qū)前,必須獲得需要的lock。voidspin_lock(spinlock_t*lock);自旋鎖等待是不可中斷的。一旦你調(diào)用spin_lock,將自旋直到鎖變?yōu)榭捎?。釋放一個鎖:voidspin_unlock(spinlock_t*lock);第56頁/共133頁58自旋鎖關(guān)中斷的自旋鎖Spin_lock_irq()Spin_unlock_irq()Spin_lock_irqsave()Spin_unlock_irqrestore()第57頁/共133頁59信號量Linux中的信號量是一種睡眠鎖。如果有一個任務(wù)試圖獲得一個已被持有的信號量時,信號量會將其推入等待隊列,然后讓其睡眠。當持有信號量的進程將信號量釋放后,在等待隊列中的一個任務(wù)將被喚醒,從而便可以獲得這個信號量。信號量的睡眠特性,使得信號量適用于鎖會被長時間持有的情況;信號量的操作信號量支持兩個原子操作P()和V(),前者做測試操作,后者叫做增加操作。Linux中分別叫做down()和up()。

第58頁/共133頁60信號量down()和up()。down()操作通過對信號量計數(shù)減1來請求獲得一個信號量。如果結(jié)果是0或大于0,信號量鎖被獲得,任務(wù)就可以進入臨界區(qū)了。如果結(jié)果是負數(shù),任務(wù)會被放入等待隊列,處理器執(zhí)行其它任務(wù)。相反,當臨界區(qū)中的操作完成后,up()操作用來釋放信號量,增加信號量的計數(shù)值。如果在該信號量上的等待隊列不為空,處于隊列中等待的任務(wù)在被喚醒的同時會獲得該信號量。第59頁/共133頁61信號量第60頁/共133頁62信號量第61頁/共133頁63Linux信號量的實現(xiàn)內(nèi)核代碼必須包含<asm/semaphore.h>,才能使用信號量。相關(guān)的類型是structsemaphore信號量的定義structsemaphore{

atomic_tcount;

intsleepers;

wait_queue_head_twait;

}

第62頁/共133頁64Linux信號量的實現(xiàn)信號量的聲明和初始化直接創(chuàng)建一個信號量structsemaphore*sem;接著使用sema_init來初始化這個信號量:

voidsema_init(structsemaphore*sem,intval);互斥模式的信號量聲明,內(nèi)核提供宏定義.DECLARE_MUTEX(name);

信號量初始化為1DECLARE_MUTEX_LOCKED(name);

信號量初始化為0第63頁/共133頁65Linux信號量的實現(xiàn)動態(tài)分配的互斥信號量聲明voidinit_MUTEX(structsemaphore*sem);

信號量初始化為1voidinit_MUTEX_LOCKED(structsemaphore*sem);

信號量初始化為0第64頁/共133頁66Linux信號量的實現(xiàn)信號量的P操作voiddown(structsemaphore*sem); down減小信號量的值,并根據(jù)信號量的值決定是否等待。不可中斷的等待。intdown_interruptible(structsemaphore*sem);

操作是可中斷的。intdown_trylock(structsemaphore*sem);

信號量在調(diào)用時不可用,down_trylock立刻返回一個非零值.第65頁/共133頁67Linux信號量的實現(xiàn)信號量的V操作voidup(structsemaphore*sem);

通過down操作進入臨界區(qū)的進程,再退出的時候都需要調(diào)用一個up操作,釋放信號量。第66頁/共133頁68Linux信號量的實現(xiàn)信號量基本使用形式為:staticDECLARE_MUTEX(mr_sem);//聲明互斥信號量…if(down_interruptible(&mr_sem))/*可被中斷的睡眠,當信號來到,睡眠的任務(wù)被喚醒*//*臨界區(qū)…*/up(&mr_sem);操作配套使用第67頁/共133頁69Linux

設(shè)備驅(qū)動調(diào)試

第68頁/共133頁70內(nèi)核調(diào)試選項內(nèi)核開發(fā)者在內(nèi)核自身中構(gòu)建了多個調(diào)試特性。這些特性會產(chǎn)生額外的輸出并降低性能,Linux發(fā)行版的內(nèi)核為了提高性能,去除這些調(diào)試特性。用來開發(fā)的內(nèi)核應(yīng)當激活的調(diào)試配置選項,是在“kernelhacking”

菜單中。第69頁/共133頁71通過打印調(diào)試Printkprintk通過附加不同的消息優(yōu)先級在消息上,對消息的嚴重程度進行分類。在<linux/kernel.h>定義了8個loglevel。DEFAULT_MESSAGE_LOGLEVEL為默認級別(printk.c)當消息優(yōu)先級小于console_loglevel,信息才能顯示出來。而console_loglevel的初值為DEFAULT_CONSOLE_LOGLEVEL。通過對/proc/sys/kernel/printk的訪問來改變console_loglevel的值。該文件包含四個數(shù)字:當前的loglevel、默認loglevel、最小允許的loglevel、引導(dǎo)時的默認loglevel。echo1>/proc/sys/kernel/printkecho8>/proc/sys/kernel/printk第70頁/共133頁72通過打印調(diào)試打開和關(guān)閉消息通過封裝printk函數(shù),快速打開調(diào)試信息或者關(guān)閉調(diào)試信息。

#definePDEBUG(fmt,args...)\ printk(KERN_DEBUG“myscull:"fmt,##args)通過在Makefile里面定義調(diào)試開關(guān)變量去決定調(diào)試信息是否打開。第71頁/共133頁73通過查詢調(diào)試獲取相關(guān)信息的最好方法:在需要的時候才去查詢系統(tǒng)信息,而不是持續(xù)不斷地產(chǎn)生數(shù)據(jù)。/proc文件系統(tǒng)是一種特殊的、由軟件創(chuàng)建的文件系統(tǒng),內(nèi)核使用他向外界導(dǎo)出信息。/proc下面的每個文件都綁定于一個內(nèi)核函數(shù),用戶讀取其中的文件時,該函數(shù)動態(tài)的生成文件的內(nèi)容。例如/proc/devices第72頁/共133頁74通過查詢調(diào)試包含<linux/proc_fs.h>在驅(qū)動中定義跟proc文件綁定的內(nèi)核函數(shù)read_proc,在函數(shù)里面定義要輸出的信息。在初始化函數(shù)中調(diào)用creat_proc_read_entry函數(shù)將/proc入口文件和read_proc函數(shù)聯(lián)系起來。卸載模塊時調(diào)用remove_proc_entry撤銷proc入口。第73頁/共133頁75通過查詢調(diào)試read_proc函數(shù)int(*read_proc)(char*page,char**start,off_toffset,intcount,int*eof,void*data);page是輸出數(shù)據(jù)的緩存內(nèi)存頁。進程讀取/proc文件時,內(nèi)核會分配一個內(nèi)存頁,read_proc將數(shù)據(jù)通過這個內(nèi)存頁返回到用戶空間。start是這個函數(shù)用來說有關(guān)的數(shù)據(jù)寫在頁中哪里eof,當沒有數(shù)據(jù)可返回時,驅(qū)動設(shè)置這個參數(shù)。Data是提供給驅(qū)動的專用數(shù)據(jù)指針。第74頁/共133頁76通過查詢調(diào)試intsprintf(char*buf,constchar*fmt,...)將數(shù)據(jù)打包成字符流的形式。內(nèi)核很多象printk函數(shù)一樣,通過庫函數(shù)的形式提供給內(nèi)核開發(fā)者的函數(shù),以滿足內(nèi)核開發(fā)中的一些簡單的需要。void*memset(void*s,charc,size_tcount);void*memcpy(void*dest,constvoid*src,size_tcount);

第75頁/共133頁77通過查詢調(diào)試creat_proc_read_entry函數(shù)

structproc_dir_entry*create_proc_read_entry(constchar*name,mode_tmode,structproc_dir_entry*base,read_proc_t*read_proc,void*data);name是要創(chuàng)建的proc文件名,mod是文件的訪問掩碼(缺省0),base指出要創(chuàng)建的文件的目錄;如果base是NULL,文件在/proc根下創(chuàng)建。

read_proc是實現(xiàn)文件內(nèi)容的read_proc函數(shù),data被內(nèi)核忽略(傳遞給read_proc).第76頁/共133頁78查看Oops信息大多數(shù)bug通常是因為廢棄了一個NULL指針或者使用了錯誤的指針值。這類bug導(dǎo)致的結(jié)果通常是一條oops消息。一條oops消息能夠顯示發(fā)生故障時CPU的狀態(tài),以及CPU寄存器的內(nèi)容和其他看似難以理解的信息。第77頁/共133頁79查看Oops信息例如訪問一個NULL指針。因為NULL不是一個可訪問的指針值,所以會引發(fā)一個錯誤,內(nèi)核會簡單地將其轉(zhuǎn)換為oops消息并顯示。然后其調(diào)用進程會被殺死。

UnabletohandlekernelNULLpointerdereferenceatvirtualaddress00000000

printingeip:d083a064Oops:0002[#1]SMPCPU:0EIP:0060:[]NottaintedEFLAGS:00010246(2.6.6)

EIPisatoops_example_write+0x4/0x10[oops_example]

eax:00000000ebx:00000000ecx:00000000edx:00000000

第78頁/共133頁80通過監(jiān)視調(diào)試strace命令可以顯示由用戶空間程序所發(fā)出的所有系統(tǒng)調(diào)用。還以符號形式顯示調(diào)用的參數(shù)和返回值。當一個系統(tǒng)調(diào)用失敗,錯誤的符號值(例如,ENOMEM)和對應(yīng)的字串(Outofmemory)都顯示.strace有很多命令行選項;-t來顯示每個調(diào)用執(zhí)行的時間,-T來顯示調(diào)用中花費的時間,-e來限制被跟蹤調(diào)用的類型,-o來重定向輸出到一個文件.缺省地,strace打印調(diào)用信息到stderr.第79頁/共133頁81Linux

的內(nèi)存分配

第80頁/共133頁82kmalloc函數(shù)void*kmalloc(size_tsize,intflags);所分配到的內(nèi)存在物理內(nèi)存中連續(xù)且保持原有的數(shù)據(jù)(不清零)。size是要分配的塊的大小。Linux創(chuàng)建一系列內(nèi)存對象slab,每個slab內(nèi)的內(nèi)存塊大小是固定。處理分配請求時,就直接在包含有足夠大內(nèi)存塊的slab中分配一個整塊給請求者。內(nèi)核只能分配一些預(yù)定義的、固定大小的字節(jié)數(shù)組。kmalloc能夠處理的最小內(nèi)存塊是32或64字節(jié)(體系結(jié)構(gòu)依賴),而內(nèi)存塊大小的上限隨著體系和內(nèi)核配置而變化。不應(yīng)分配大于128KB的內(nèi)存。若需多于幾個KB的內(nèi)存塊,最好使用其他方法。

第81頁/共133頁83kmalloc函數(shù)void*kmalloc(size_tsize,intflags);Flags分配標志,表示如何分配空間。所有標志都定義在<linux/gfp.h>GFP_KERNEL

內(nèi)存分配是代表運行在內(nèi)核空間的進程執(zhí)行的,并且在空閑內(nèi)存較少時把當前進程轉(zhuǎn)入休眠以等待一個頁面。

GFP_ATOMIC

內(nèi)核通常會為原子性的分配預(yù)留一些空閑頁。當當前進程不能被置為睡眠時,應(yīng)使用GFP_ATOMIC,這樣kmalloc甚至能夠使用最后一個空閑頁。如果連這最后一個空閑頁也不存在,則分配返回失敗。常用來從中斷處理和進程上下文之外的其他代碼中分配內(nèi)存,從不睡眠。GFP_DMA

要求分配可用于DMA的內(nèi)存。第82頁/共133頁84后備高速緩存

內(nèi)核為驅(qū)動程序常常需要反復(fù)分配許多相同大小內(nèi)存塊的情況,增加了一些特殊的內(nèi)存池,稱為后備高速緩存(lookasidecache)。設(shè)備驅(qū)動程序通常不會涉及后備高速緩存,但是也有例外:在Linux2.6中USB和SCSI驅(qū)動。第83頁/共133頁85后備高速緩存創(chuàng)建一個新的后備高速緩存kmem_cache_createname:name和這個后備高速緩存相關(guān)聯(lián);通常設(shè)置為被緩存的結(jié)構(gòu)類型的名字,不能包含空格。參數(shù)size:每個內(nèi)存區(qū)域的大小。offset:頁內(nèi)第一個對象的偏移量;用來確保被分配對象的特殊對齊,0表示缺省值。flags:控制分配方式的位掩碼:SLAB_NO_REAP

保護緩存在系統(tǒng)查找內(nèi)存時不被削減,不推薦。SLAB_HWCACHE_ALIGN

所有數(shù)據(jù)對象跟高速緩存行對齊,平臺依賴,可能浪費內(nèi)存。SLAB_CACHE_DMA

每個數(shù)據(jù)對象在DMA內(nèi)存區(qū)段分配.第84頁/共133頁86后備高速緩存創(chuàng)建一個新的后備高速緩存kmem_cache_create參數(shù)constructor和destructor是可選函數(shù),用來初始化新分配的對象和在內(nèi)存被作為整體釋放給系統(tǒng)之前“清理”對象。第85頁/共133頁87后備高速緩存kmem_cache_alloc從已創(chuàng)建的后備高速緩存中分配對象:

void*kmem_cache_alloc(kmem_cache_t*cache,intflags);flags和kmalloc的flags相同使用kmem_cache_free釋放一個對象:

voidkmem_cache_free(kmem_cache_t*cache,constvoid*obj);當驅(qū)動用完這個后備高速緩存(通常在當模塊被卸載時),釋放緩存: intkmem_cache_destroy(kmem_cache_t*cache);第86頁/共133頁88get_free_page相關(guān)函數(shù)如果一個模塊需要分配大塊的內(nèi)存,最好使用面向頁的分配技術(shù)。

__get_free_page(unsignedintflags);

返回一個指向新頁的指針,未清零該頁get_zeroed_page(unsignedintflags);

類似于__get_free_page,但用零填充該頁__get_free_pages(unsignedintflags,unsignedintorder);

分配是若干(物理連續(xù)的)頁面并返回指向該內(nèi)存區(qū)域的第一個字節(jié)的指針,該內(nèi)存區(qū)域未清零flags與kmalloc的用法相同order是請求或釋放的頁數(shù)以2為底的對數(shù)。若其值過大(沒有這么大的連續(xù)區(qū)可用),則分配失敗

第87頁/共133頁89get_free_page相關(guān)函數(shù)當程序不需要頁面時,用下列函數(shù)之一來釋放

voidfree_page(unsignedlongaddr);

voidfree_pages(unsignedlongaddr,unsignedlongorder);第88頁/共133頁90Linux

的中斷處理

第89頁/共133頁91為什么會有中斷中斷最初是為克服對I/O接口控制采用程序查詢所帶來的處理器低效率而產(chǎn)生的。處理器速度一般比外設(shè)快很多用輪詢的方式來查詢設(shè)備的狀態(tài),CPU效率不高,CPU和外設(shè)不能并行工作。中斷機制讓CPU啟動設(shè)備后,就去處理其他任務(wù),只有當外設(shè)真正完成數(shù)據(jù)傳輸?shù)臏蕚?,請求CPU服務(wù)的時候,CPU才轉(zhuǎn)過來處理外設(shè)的請求。第90頁/共133頁92中斷和異常外部中斷: 外部設(shè)備所發(fā)出的I/O請求。隨著計算機系統(tǒng)結(jié)構(gòu)的不斷改進以及應(yīng)用技術(shù)的日益提高,中斷的適用范圍也隨之擴大,出現(xiàn)了所謂的內(nèi)部中斷(或叫異常)。異常: 為解決機器運行時所出現(xiàn)的某些隨機事件及編程方便而出現(xiàn)的。第91頁/共133頁93I/O中斷處理為了保證系統(tǒng)對外部的響應(yīng),一個中斷處理程序必須被盡快的完成。因此,把所有的操作都放在中斷處理程序中并不合適Linux中把緊隨中斷要執(zhí)行的操作分為三類緊急的(critical)

一般關(guān)中斷運行。諸如對PIC應(yīng)答中斷,對PIC或是硬件控制器重新編程,或者修改由設(shè)備和處理器同時訪問的數(shù)據(jù)非緊急的(noncritical)

如修改那些只有處理器才會訪問的數(shù)據(jù)結(jié)構(gòu)(例如按下一個鍵后讀掃描碼),這些也要很快完成,因此由中斷處理程序立即執(zhí)行,不過一般在開中斷的情況下第92頁/共133頁94I/O中斷處理Linux中把緊隨中斷要執(zhí)行的操作分為三類非緊急可延遲的(noncriticaldeferrable)這些操作可以被延遲較長的時間間隔而不影響內(nèi)核操作,有興趣的進程將會等待數(shù)據(jù)。內(nèi)核用下半部分這樣一個機制來在一個更為合適的時機用獨立的函數(shù)來執(zhí)行這些操作。如把緩沖區(qū)內(nèi)容拷貝到某個進程的地址空間(例如把鍵盤緩沖區(qū)內(nèi)容發(fā)送到終端處理程序進程)。第93頁/共133頁95S3c2410的中斷中斷發(fā)生時,需要知道中斷的來源。芯片的引線有限,很難提供很多條中斷請求引線。使用中斷控制器管理中斷請求線。S3c2410將中斷控制器集成在CPU芯片中,“復(fù)用”GPIO端口引腳,具有作為中斷請求線的功能。SRCPND寄存器32位中的每一位對應(yīng)著一個中斷源,每一位被設(shè)置為1,則相應(yīng)的中斷源產(chǎn)生中斷請求并且等待中斷被服務(wù)。第94頁/共133頁96注冊中斷服務(wù)例程

中斷號是一個寶貴且常常有限的資源。內(nèi)核維護一個中斷號的注冊表。要使用中斷,就要進行中斷號的申請,也就是IRQ(InterruptReQuirement)。只有當設(shè)備需要中斷的時候才申請占用一個IRQ,或者是在申請IRQ時采用共享中斷的方式,讓更多的設(shè)備使用中斷。第95頁/共133頁97注冊中斷服務(wù)例程

在<linux/interrupt.h>實現(xiàn)中斷注冊接口: intrequest_irq(unsignedintirq, irqreturn_t(*handler)(int,void*,structpt_regs*), unsignedlongflags, constchar*dev_name, void*dev_id ); voidfree_irq(unsignedintirq,void*dev_id);request_irq的返回值是0指示申請成功,為負值時表示錯誤碼。函數(shù)返回-EBUSY表示已經(jīng)有另一個驅(qū)動占用了所要申請的中斷線。第96頁/共133頁98注冊中斷服務(wù)例程

request_irq的參數(shù)說明:unsignedintirq,要申請的中斷號。irqreturn_t(*handler)(int,void*,structpt_regs*),要安裝的中斷處理函數(shù)指針。constchar*dev_name,

用在/proc/interrupts中顯示中斷的擁有者。第97頁/共133頁99注冊中斷服務(wù)例程

request_irq的參數(shù)說明:unsignedlongflags,與中斷管理相關(guān)的位掩碼選項。Flags的每個位有不同含義SA_INTERRUPT當該位被設(shè)置時,表示這是一個“快速”中斷??焖僦袛嗵幚砝踢\行時,屏蔽中斷。SA_SHIRQ這個位表示中斷可以在設(shè)備間共享。void*dev_id

這個指針用于共享的中斷號。做為驅(qū)動程序的私有數(shù)據(jù)區(qū)(可用來識別那個設(shè)備產(chǎn)生的中斷)。不使用共享中斷線方式時,可設(shè)置為NULL。第98頁/共133頁100proc文件系統(tǒng)中的中斷信息/proc/interrupts反映系統(tǒng)的中斷信息第一列是IRQ號給出每個中斷線發(fā)生中斷的次數(shù)。給出處理中斷的可編程中斷控制器。給出在該中斷號上注冊中斷處理例程的設(shè)備名稱。第99頁/共133頁101實現(xiàn)中斷處理例程中斷處理例程特別之處:在中斷時間內(nèi)運行,不能向用戶空間發(fā)送或者接收數(shù)據(jù)。不能做任何導(dǎo)致休眠的操作。不能調(diào)用schedule函數(shù)。無論快速還是慢速中斷處理例程,都應(yīng)該設(shè)計成執(zhí)行時間盡可能短。第100頁/共133頁102實現(xiàn)中斷處理例程中斷處理函數(shù)的參數(shù)和返回值irqreturn_t(*handler)(intirq,void*dev_id,structpt_regs*regs)Irq中斷號Dev_id驅(qū)動程序可用的數(shù)據(jù)區(qū),通常可傳遞指向描述設(shè)備的數(shù)據(jù)結(jié)構(gòu)指針。structpt_regs*regs,保存了處理器進入中斷代碼之前的cpu寄存器的值。一般驅(qū)動可不要。第101頁/共133頁103實現(xiàn)中斷處理例程啟動和禁用中斷驅(qū)動禁止特定中斷線的中斷:#include<asm/irq.h>.voiddisable_irq(intirq);voidenable_irq(intirq);禁止所有中斷voidlocal_irq_save(unsignedlongflags);local_irq_save在當前處理器上禁止中斷遞交,在保存當前中斷狀態(tài)到flags。voidlocal_irq_disable(void);local_irq_disable關(guān)閉本地中斷遞交而不保存狀態(tài);第102頁/共133頁104實現(xiàn)中斷處理例程打開中斷:voidlocal_irq_restore(unsignedlongflags);

恢復(fù)由local_irq_save存儲于flags的狀態(tài),而local_irq_enable無條件打開中斷.voidlocal_irq_enable(void);第103頁/共133頁105頂半部和底半步中斷處理的一個主要問題是如何在處理中進行長時間的任務(wù)。響應(yīng)一次設(shè)備中斷需要完成一定數(shù)量的工作,但是中斷處理需要很快完成并且不使中斷阻塞太長。Linux把中斷處理例程分兩部分:頂部分:實際響應(yīng)中斷的例程。底部分:被頂部分調(diào)用,通過開中斷的方式進行。兩種機制實現(xiàn):Tasklet工作隊列workqueue第104頁/共133頁106頂半部和底半部頂半部頂半部的功能是“登記中斷”,當一個中斷發(fā)生時,它進行相應(yīng)地硬件讀寫后就把中斷例程的下半部掛到該設(shè)備的底半部執(zhí)行隊列中去。頂半部執(zhí)行的速度就會很快,可以服務(wù)更多的中斷請求。底半部僅有“登記中斷”是遠遠不夠的,因為中斷的事件可能很復(fù)雜。Linux引入了一個底半部,來完成中斷事件的絕大多數(shù)使命。底半部和頂半部最大的不同是底半部是可中斷的,而頂半部是不可中斷的,底半部幾乎做了中斷處理程序所有的事情,而且可以被新的中斷打斷!底半部則相對來說并不是非常緊急的,通常還是比較耗時的,因此由系統(tǒng)自行安排運行時機,不在中斷服務(wù)上下文中執(zhí)行。

第105頁/共133頁107小任務(wù)機制tasklet內(nèi)核在BH機制的基礎(chǔ)上進行了擴展,實現(xiàn)“軟中斷請求”(softirq)機制。利用軟中斷代替bottomhalfhandler的處理。tasklet機制正是利用軟中斷來完成對驅(qū)動bottomhalf的處理。tasklet會讓內(nèi)核選擇某個合適的時間來執(zhí)行給定的小任務(wù)。tasklet所對應(yīng)的處理函數(shù)就是tasklet_action,這個處理函數(shù)在系統(tǒng)啟動時初始化軟中斷時,就在軟中斷向量表中注冊。Tasklet_action遍歷一個全局的tasklet_vec鏈表。鏈表中的元素為tasklet_struct結(jié)構(gòu)體。第106頁/共133頁108軟中斷和tasklet的關(guān)系如下圖:小任務(wù)機制tasklet第107頁/共133頁109小任務(wù)機制taskletksoftirqd是一個后臺運行的內(nèi)核線程,它會周期的遍歷軟中斷的向量列表,如果發(fā)現(xiàn)哪個軟中斷向量被掛起了(pend),就執(zhí)行對應(yīng)的處理函數(shù)。tasklet所對應(yīng)的處理函數(shù)就是tasklet_action,這個處理函數(shù)在系統(tǒng)啟動時初始化軟中斷時,就在軟中斷向量表中注冊。第108頁/共133頁110小任務(wù)以數(shù)據(jù)結(jié)構(gòu)的形式存在:structtasklet_struct{ structtasklet_struct*next; unsignedlongstate; atomic_tcount; void(*func)(unsignedlong); unsignedlongdata;};每個結(jié)構(gòu)一個函數(shù)指針func,指向自定義的函數(shù)。這就是我們要執(zhí)行的小任務(wù)函數(shù)。小任務(wù)機制tasklet第109頁/共133頁111tasklet的接口DECLARE_TASKLET(name,function,data)此接口初始化一個tasklet;name是tasklet的名字,function是執(zhí)行tasklet的函數(shù);data是unsignedlong類型的function參數(shù)。

staticinlinevoidtasklet_schedule(structtasklet_struct*t)調(diào)度執(zhí)行指定的tasklet。將定義后的tasklet掛接到cpu的tasklet_vec鏈表。而且會引起一個軟tasklet的軟中斷,既把tasklet對應(yīng)的中斷向量掛起(pend)。小任務(wù)機制tasklet第110頁/共133頁112voidtasklet_disable(structtasklet_struct*t);這個函數(shù)禁止給定的tasklet.tasklet,但仍然可以被tasklet_schedule調(diào)度,但是它的執(zhí)行被延后直到這個tasklet被再次激活。voidtasklet_enable(structtasklet_struct*t);激活一個之前被禁止的tasklet.如果這個tasklet已經(jīng)被調(diào)度,它會很快運行.

一個對tasklet_enable的調(diào)用必須匹配每個對tasklet_disable的調(diào)用,因為內(nèi)核跟蹤每個tasklet的"禁止次數(shù)".小任務(wù)機制tasklet第111頁/共133頁113voidtasklet_hi_schedule(structtasklet_struct*t);調(diào)度tasklet在更高優(yōu)先級執(zhí)行.當軟中斷處理運行時,它在其他軟中斷之前處理高優(yōu)先級tasklet。voidtasklet_kill(structtasklet_struct*t);這個函數(shù)確保了這個tasklet沒被再次調(diào)度來運行;它常常被調(diào)用當一個設(shè)備正被關(guān)閉或者模塊卸載時.如果這個tasklet被調(diào)度來運行,這個函數(shù)等待直到它已執(zhí)行.

小任務(wù)機制tasklet第112頁/共133頁114工作隊列

工作隊列類似taskets,允許內(nèi)核代碼請求在將來某個時間調(diào)用一個函數(shù),不同在于:tasklet在軟件中斷上下文中運行,所以tasklet代碼必須是原子的。而工作隊列函數(shù)在一個特殊內(nèi)核進程上下文運行,有更多的靈活性,且能夠休眠。tasklet只能在最初被提交的處理器上運行,這只是工作隊列默認工作方式。內(nèi)核代碼可以請求工作隊列函數(shù)被延后一個給定的時間間隔。tasklet執(zhí)行的很快,短時期,并且在原子態(tài),而工作隊列函數(shù)可能是長周期且不需要是原子的,兩個機制有它適合的情形。第113頁/共133頁115工作隊列structworkqueue_struct類型在workqueue.h中定義。一個工作隊列必須明確的在使用前創(chuàng)建,宏為: structworkqueue_struct*create_workqueue(constchar*name); structworkqueue_struct*create_singlethread_workqueue(constchar*name);每個工作隊列有一個或多個專用的進程("內(nèi)核線程"),這些進程運行提交給這個隊列的函數(shù)。若使用create_workqueue,就得到一個工作隊列它在系統(tǒng)的每個處理器上有一個專用的線程。在很多情況下,過多線程對系統(tǒng)性能有影響,如果單個線程就足夠則使用create_singlethread_workqueue來創(chuàng)建工作隊列。第114頁/共133頁116工作隊列當用完一個工作隊列,可以去掉它,使用:voiddestroy_workqueue(structworkqueue_struct*queue);

第115頁/共133頁117Linux內(nèi)核中定義了一個timer_list結(jié)構(gòu),我們在驅(qū)動程序中可以利用定時器structtimer_list{

structlist_headlist;

unsignedlongexpires;//定時器到期時間

unsignedlongdata;//作為參數(shù)被傳入定時器處理函數(shù)

void(*function)(unsignedlong);

};第116頁/共133頁118定時器timer的API函數(shù):增加定時器

voidadd_timer(structtimer_list*timer);刪除定時器 intdel_timer(structtimer_list*timer);修改定時器的expire intmod_timer(structtimer_list*timer,unsignedlongexpires);第117頁/共133頁119定時器使用定時器的一般流程為:

(1)timer、編寫function;

(2)為timer的expires、data、function賦值;

(3)調(diào)用add_timer將timer加入列表;

(4)在定時器到期時,function被執(zhí)行;

(5)在程序中涉及timer控制的地方適當?shù)卣{(diào)用del_timer、mod_timer刪除timer或修改timer的expires。第118頁/共133頁120矩陣式鍵盤原理矩陣式鍵盤一般適用于按鍵數(shù)量較多的場合,它由行線和列線組成,按鍵位于行、列的交叉點上。如圖所示,一個4×4的行、列結(jié)構(gòu)可以構(gòu)成一個有16個按鍵的鍵盤。第119頁/共133頁121矩陣式鍵盤原理按鍵設(shè)置在行、列交叉點上,行、列分別連接到按鍵開關(guān)的兩端。行線通過上拉電阻接到十5V上。平時無按鍵動作時,行線處于高電平狀態(tài);而當有健按下時,行線電平狀態(tài)將由通過此按鍵的列線電平?jīng)Q定:列線電平如果為低,行線電平為低;列線電平如果為高,則行線電平亦為高。這一點是識別矩陣式鍵盤是否被按下的關(guān)鍵所在。第120頁/共133頁122矩陣式鍵盤原理矩陣鍵盤按鍵的識別方法分兩步進行:①識別鍵盤哪一行的鍵被按下。讓所有列線均為低電平,檢查各行線電平是否為低。如果有行線為低,則說明該行有鍵被按下,否則說明無鍵被按下。②如果某行有鍵被按下,識別鍵盤哪一列的鍵被按下

溫馨提示

  • 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)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
  • 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

最新文檔

評論

0/150

提交評論