




版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
1、.1Linux 設備驅動設計 梁紅elvinema-kelvinema-.設備驅動概述n設備由兩部分組成,一個是被稱為控制器的電器部分,另一個是機械部分。 n一組寄存器組被賦予到各個控制器。I/O端口包含4組寄存器,即狀態寄存器,控制寄存器,數據輸入寄存器,數據輸出寄存器。n狀態寄存器擁有可以被CPU讀取的(狀態)位,用來 指示當前命令是否執行完畢,或者字節是否可以被讀出或寫入,以及任何錯誤提示。n控制寄存器則用于啟動一條命令(指令)或者改變設備的(工作)模式。n數據輸入寄存器用于獲取輸入的數據。n數據輸出寄存器則向CPU發送結果。.設備驅動概述n操作系統是通過各種驅
2、動程序來駕馭硬件設備,它為用戶屏蔽了各種各樣的設備。n設備驅動程序是操作系統內核和機器硬件之間的接口,系統調用是操作系統內核和應用程序之間的接口。n在應用程序看來,硬件設備只是一個設備文件, 應用程序可以象操作普通文件一樣對硬件設備進行操作. .4設備驅動概述n驅動完成以下的功能: n對設備初始化和釋放. n把數據從內核傳送到硬件和從硬件讀取數據. n讀取應用用程序傳送給設備文件的數據和回送應用用程序請求的數據. n檢測和處理設備出現的錯誤. .5設備驅動概述n無操作系統的設備驅動n有操作系統的設備驅動ApplicationDriverHardwareApplicationLib APISys
3、tem callEmbedded OSHardware不帶操作系統軟件結構 帶操作系統軟件結構Driver.6Linux設備驅動.7Linux設備驅動n用戶級的程序使用內核提供的標準系統調用來與內核通訊,這些系統調用有:open(), read(), write(), ioctl(), close() 等等。 nLinux的內核是映射到每一個進程的高1G空間。每一個用戶進程運行時都好像有一份內核的拷貝,每當用戶進程使用系統調用時,都自動地將運行模式從用戶級轉為內核級,此時進程在內核的地址空間中運行。 .8Linux設備驅動nLinux內核使用“設備無關”的I/O子系統來為所有的設備服務。n每個
4、設備都提供標準接口給內核,盡可能地隱藏了自己的特性。n用戶程序使用一些基本的系統調用從設備讀取數據并且將它們存入緩沖的例子。我們可以看到,每當一個系統調用被使用時,內核就轉到相應的設備驅動例程來操縱硬件。 .Linux設備驅動nLinux操作系統把設備納入文件系統的范疇來管理。n每個設備在Linux系統上看起來都像一個文件,它們存放在/dev目錄中,稱為設備節點。n對文件操作的系統調用大都適用于設備文件。 .10Linux設備驅動nLinux下設備的屬性n設備的類型:字符設備、塊設備、網絡設備n主設備號:標識設備對應的驅動程序。一般“一個主設備號對應一個驅動程序”n次設備號:每個驅動程序負責管
5、理它所驅動的幾個硬件實例,這些硬件實例則由次設備號來表示。同一驅動下的實例編號,用于確定設備文件所指的設備。n可通過ls l “設備文件名”命令查看設備的主次設備號,以及設備的類型。.11Linux設備驅動nLinux設備驅動程序是一組由內核中的相關子例程和數據組成的I/O設備軟件接口。n每當用戶程序要訪問某個設備時,它就通過系統調用,讓內核代替它調用相應的驅動例程。這就使得控制從用戶進程轉移到了驅動例程,當驅動例程完成后,控制又被返回至用戶進程。.12一些重要的數據結構n大部分驅動程序涉及三個重要的內核數據結構:n文件操作file_operations結構體n文件對象file結構體n索引節點
6、inode結構體.13一些重要的數據結構n文件操作結構體file_operationsn結構體file_operations在頭文件 linux/fs.h中定義,用來存儲驅動內核模塊提供的對設備進行各種操作的函數的指針。n結構體的每個域都對應著驅動模塊用來處理某個被請求的事務的函數的地址。struct file_operations struct module *owner; ssize_t(*read) (struct file *, char _user *, size_t, loff_t *); ssize_t(*write) (struct file *, const char _us
7、er *, size_t, loff_t *);。 .14一些重要的數據結構nfile_operations重要的成員nStruct module *owner ,指向擁有該結構體的模塊的指針。內核使用該指針維護模塊使用計數。n方法llseek用來修改文件的當前讀寫位置,把新位置作為返回值返回。loff_t是在LINUX中定義的長偏移量 n方法read用來從設備中讀取數據。非負返回值表示成功讀取的直接數。n方法write向設備發送數據。n方法ioctl提供一種執行設備特定命令的方法。.15一些重要的數據結構nfile_operations重要的成員nunsigned int (*poll) (
8、struct file *, struct poll_table_struct *);系統調用select和poll的后端實現,用這兩個系統調用來查詢 設備是否可讀寫,或是否處于某種狀態。如果poll為空,則驅動設備會被認為即可讀又可寫,返回值是一個狀態掩碼。nint (*mmap) (struct file *, struct vm_area_struct *);將設備內存映射到進程地址空間.16一些重要的數據結構nfile_operations重要的成員n驅動內核模塊是不需要實現每個函數的。相對應的file_operations的項就為 NULL。nGcc的語法擴展,使得可以定義該結構體:
9、struct file_operations fops = read: device_read, write: device_write, open: device_open, release: device_release ; n沒有顯示聲明的結構體成員都被gcc初始化為NULL。 .17一些重要的數據結構nfile_operations重要的成員n標準C的標記化結構體的初始化方法:struct file_operations fops = .read = device_read,.write = device_write, .open = device_open, .release = d
10、evice_release; n推薦使用該方法,提高移植性,方法允許對結構體成員進行重新排列。沒有顯示聲明的結構體成員同樣都被gcc初始化為NULL。n指向結構體file_operations的指針通常命名為fops。 .18一些重要的數據結構n文件對象file結構體n文件對象file代表著一個打開的文件。進程通過文件描述符fd與已打開文件的file結構相聯系。進程通過它對文件的線性邏輯空間進行操作。例如:file-f_op-read();nStruct file 在中定義。n指向結構體struct file的指針通常命名為filp,或者file。建議使用文件指針filp。.19一些重要的數據
11、結構n文件對象file結構體的成員nStruct file_operations *f_op;與文件相關的操作結構體指針。與文件相關的操作是在打開文件的時候確定下來的,也就是確定該指針的值。可在需要的時候,改變指針所指向的文件操作結構體。用C語言實現面向對象編程的方法重載。n其他成員可先忽略,后面具體實例分析。因為設備驅動模塊并不自己直接填充結構體 file,只是使用file中的數據。 .20一些重要的數據結構n索引節點inode結構n文件打開,在內存建立副本后,由唯一的索引節點inode描述。n與file結構不同。nfile結構是進程使用的結構,進程每打開一個文件,就建立一個file結構。不
12、同的進程打開同一個文件,建立不同的file結構。nInode結構是內核使用的結構,文件在內存建立副本,就建立一個inode結構來描述。一個文件在內存里面只有一個inode結構對應。.21一些重要的數據結構n索引節點inode結構nInode結構包含大量描述文件信息的成員變量。n但是對于描述設備文件的inode,跟設備驅動有關的成員只有兩個。nDev_t i_rdev; 包含真正的設備編號。nStruct cdev *i_cdev; 指向cdev結構體的指針。cdev是表示字符設備的內核數據結構。n從inode中獲得主設備號和次設備號的宏:nUnsigned int iminor(struct
13、inode *inode);nUnsigned int imajor(struct inode *inode);.22Linux設備驅動n主設備號和次設備號的內部表達:nDev_t類型用于保存設備號,稱為設備編號。/linux/types.h文件中定義。n目前設備編號dev_t是一個32位的整數,其中12位表示主設備號,20位表示次設備號。n通過設備編號獲取主次設備號:nMAJOR(dev_t dev);nMINOR(dev_t dev);n通過主次設備號合成設備編號:nMKDEV(int major, int minor);nDev_t格式以后可能會發生變化,但只要使用這些宏,就可保證設備驅
14、動程序的正確性。.23分配和釋放字符設備號n編寫驅動程序要做的第一件事,為字符設備獲取一個設備號。n事先知道所需要的設備編號(主設備號)的情況:nint register_chrdev_region(dev_t first, unsigned count, const char *name)nfirst是要分配的起始設備編號值。 first的次設備號通常設置為0。nCount 所請求的連續設備編號的個數。nName設備名稱,指和該編號范圍建立關系的設備。n分配成功返回0。.24分配和釋放字符設備號n動態分配設備編號(主要是主設備號)nint alloc_chrdev_region(dev_t
15、*dev, unsigned baseminor, unsigned count,const char *name)ndev 是一個僅用于輸出的參數, 它在函數成功完成時保存已分配范圍的第一個編號。nbaseminor 應當是請求的第一個要用的次設備號,它常常是 0. ncount 和 name 參數跟request_chrdev_region 的一樣.25分配和釋放字符設備號n不再使用時,釋放這些設備編號。使用以下函數:nvoid unregister_chrdev_region(dev_t from, unsigned count)n在模塊的卸載函數中調用該函數。.26分配和釋放字符設備號
16、n新驅動程序,建議使用動態分配機制獲取主設備號,也就是使用alloc_chrdev_region()。n動態分配導致無法預先創建設備節點。n可在分配設備號后,從/proc/devices文件中獲取。n為了加載后自動創建設備文件,可以通過編寫內核模塊加載腳本實現。.27字符設備的注冊n內核內部使用struct cdev結構表示字符設備。編寫設備驅動的第二步就是注冊該設備。n包含頭文件。n獲取一個獨立的cdev結構:struct cdev *my_cdev = cdev_alloc();n調用cdev_init初始化cdev結構體void cdev_init(struct cdev *cdev,
17、struct file_operations *fops);n初始化該設備的所有者字段:dev-cdev.owner = THIS_MODULE;n初始化該設備的可用操作集:dev-cdev.ops = &device_fops;.28字符設備的注冊n編寫設備驅動的第二步就是注冊該設備。ncdev 結構已建立和初始化, 最后通過cdev_add函數把它告訴內核:int cdev_add(struct cdev *dev, dev_t num, unsigned int count);ndev 是要添加的設備的 cdev 結構, nnum 是這個設備對應的第一個設備編號,ncount 是
18、應當關聯到設備的設備號的數目. n卸載字符設備時,調用相反的動作函數:nvoid cdev_del(struct cdev *dev);.29設備的注冊n早期方法:n內核中仍有許多字符驅動不使用剛剛描述過的cdev 接口。沒有更新到 2.6 內核接口的老代碼。n注冊一個字符設備的早期方法:int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);nmajor 是給定的主設備號。為0代表什么?nname 是驅動的名字(將出現在 /proc/devices), nfops 是設備驅
19、動的file_operations 結構。nregister_chrdev 將給設備分配 0 - 255 的次設備號, 并且為每一個建立一個缺省的 cdev 結構。n從系統中卸載字符設備的函數:int unregister_chrdev(unsigned int major, const char *name);.30Open方法n編寫字符設備驅動的第三步:定義設備驅動與文件系統的接口,file_operation結構體的函數定義。nopen 方法nint (*open)(struct inode *inode, struct file *filp);n驅動程序提供open 方法,讓用戶進程使
20、用設備之前,進行一些初始化的工作。n檢查設備特定的錯誤。n如果第一次打開設備, 則初始化設備。n如果需要, 更新 f_op 指針,更換操作方法集。n分配并填充要放進 filp-private_data 的任何數據結構。.31Open方法n對于設備文件,inode 參數只有兩個參數對設備驅動有用的。nDev_t i_rdev; 包含真正的設備編號。nStruct cdev *i_cdev; 指向cdev結構體的指針。ni_cdev里面包含我們之前建立的 cdev 結構。但是有時候,我們需要的是包含 cdev 結構的描述設備的結構。n使用通過成員地址獲取結構體地址的宏container_of,在
21、中定義:ncontainer_of(pointer, container_type, container_field);n這個宏使用一個指向 container_field 類型的成員的指針, 它在一個 container_type 類型的結構中,宏通過分析他們關系,返回指向包含該成員的結構體指針.32Open方法n在 myscull_open, 這個宏用來找到適當的設備結構:dev = container_of(inode-i_cdev, struct scull_dev, cdev);n找到 myscull_dev 結構后, scull 在filp-private_data 中存儲其指針,
22、 為以后存取使用.filp-private_data = dev; .33release 方法nrelease 方法做open相反的工作n釋放 open 分配給filp-private_data的內存空間。n在最后一次的關閉操作時,關閉設備。n不是每個 close 系統調用引起調用 release 方法。.34Read和Write方法nRead的任務, 就是從設備拷貝數據到用戶空間。nWrite的任務,則從用戶空間拷貝數據到設備。ssize_t read(struct file *filp, char _user *buff, size_t count, loff_t *offp);ssize
23、_t write(struct file *filp, const char _user *buff, size_t count, loff_t *offp);nfilp 是文件對象指針, ncount 是請求的傳輸數據大小. nbuff 參數對write來說是指向持有被寫入數據的緩存, 對read則是放入新數據的空緩存. noffp 是指向一個“long offset type”的指針, 它指出用戶正在存取的文件位置. n返回值是“signed size type”類型;.35Read和Write方法nread 和 write 方法的 buff 參數是用戶空間指針,不能被內核代碼直接解引用。
24、_user字符串只是形式上的說明,表明是用戶空間地址。n驅動必須能夠存取用戶空間緩存以完成它的工作。內核如何解決這個問題?n為安全起見,內核提供專用的函數來完成對用戶空間的存取。這些專用函數在中聲明。中聲明。unsigned long copy_to_user(void _user *to,const void *from,unsigned long count);unsigned long copy_from_user(void *to,const void _user *from,unsigned long count);n大多數讀寫函數都會調用這兩個函數,用于跟應用程序空間交流信息。.3
25、6Read和Write方法n典型的Read函數對參數的使用。.37llseek函數nllseek函數用于對設備文件訪問定位。n驅動接口loff_t (*llseek) (struct file *, loff_t, int);n庫函數off_t lseek(int filedes, off_t offset, int whence);參數 offset 的含義取決于參數 whence:n如果 whence 是 SEEK_SET,文件偏移量將被設置為 offset。n如果 whence 是 SEEK_CUR,文件偏移量將被設置為 cfo 加上 offset,offset 可以為正也可以為負。n如
26、果 whence 是 SEEK_END,文件偏移量將被設置為文件長度加上 offset, offset 可以為正也可以為負。nSEEK_SET、SEEK_CUR 和 SEEK_END 是 System V 引入的,是 0、1 和 2。.38ioctl n進行超出簡單的數據傳輸之外的操作,進行各種硬件控制操作. ioctl 方法和用戶空間版本不同的原型:int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)n不管可選的參數arg是否由用戶給定為一個整數或一個指針,它都以一
27、個unsigned long的形式傳遞。n返回值返回值POSIX 標準規定:如果使用了不合適的 ioctl 命令號,應當返回-ENOTTY 。這個錯誤碼被 C 庫解釋為“不合適的設備 ioctl。 -EINVAL也是相當普遍的。 .39結構化設備驅動程序 n設備結構體 n把與某設備相關的所有內容定義為一個設備結構體n其中包括設備驅動涉及的硬件資源、全局軟件資源、控制(自旋鎖、互斥鎖、等待隊列、定時器等)n在涉及設備的操作時,就僅僅操作這個結構體.40Linux設備驅動的并發控制.41設備驅動的并發控制 n在驅動程序中,當多個線程同時訪問相同的資源時,可能會引發“競態”,必須對共享資源進行并發控
28、制。n并發和競態廣泛存在。n并發控制的目的:使得線程訪問共享資源的操作是原子操作。n原子操作:在執行過程中不會被別的代碼路徑所中斷的操作。 n驅動程序中的全局變量是一種典型的共享資源。.42n考慮一個非常簡單的共享資源的例子:一個全局整型變量和一個簡單的臨界區,其中的操作僅僅是將整型變量的值增加1: i+ n該操作可以轉化成下面三條機器指令序列:得到當前變量i的值并拷貝到一個寄存器中將寄存器中的值加1把i的新值寫回到內存中 原子操作.43原子操作內核任務內核任務1 1 內核任務內核任務2 2獲得i(1) - 增加 i(1-2) - 寫回 i(2) - 獲得 i(2) 增加 i(2-3) 寫回
29、i(3)內核任務內核任務1 1 內核任務內核任務2 2獲得 i(1) -增加 i(1-2) - - 獲得 i(1) - 增加 i(1-2)- 寫回 i(2)寫回 i(2) -可能的實際執行結果:期望的結果.44Linux內核的并發控制 n在內核空間的內核任務需要考慮同步n內核空間中的共享數據對內核中的所有任務可見,所以當在內核中訪問數據時,就必須考慮是否會有其他內核任務并發訪問的可能、是否會產生競爭條件、是否需要對數據同步。.45 確定保護對象確定保護對象 n找出哪些數據需要保護是關鍵所在n內核任務的局部數據僅僅被它本身訪問,顯然不需要保護。n如果數據只會被特定的進程訪問,也不需加鎖 n大多數
30、內核數據結構都需要加鎖:若有其它內核任務可以訪問這些數據,那么就給這些數據加上某種形式的鎖;若任何其它東西能看到它,那么就要鎖住它。 Linux內核的并發控制.46Linux內核的并發控制n并發控制的機制n中斷屏蔽,原子數操作,自旋鎖和信號量都是解決并發問題的機制。n中斷屏蔽很少被單獨使用,原子操作只能針對整數來進行。因此自旋鎖和信號量應用最為廣泛。 .47中斷屏蔽n單CPU系統中,避免竟態的一種簡單方式n保證正在執行的內核執行路徑不被中斷處理程序所搶占,防止竟態條件的發生。Local_irq_disable() /關中斷Critical section /臨界區Local_irq_enabl
31、e() /開中斷n中斷對內核非常重要,長時間屏蔽中斷非常危險!n只適合短時間的關閉n對SMP多CPU引發的竟態無效.48n鎖機制可以避免競爭狀態正如門鎖和門一樣,門后的房間可想象成一個臨界區。n在一段時間內,房間里只能有一個內核任務存在,當一個任務進入房間后,它會鎖住身后的房門;當它結束對共享數據的操作后,就會走出房間,打開門鎖。如果另一個任務在房門上鎖時來了,那么它就必須等待房間內的任務出來并打開門鎖后,才能進入房間。 加鎖機制 .49n任何要訪問臨界資源的代碼首先都需要占住相應的鎖,這樣該鎖就能阻止來自其它內核任務的并發訪問: 任務任務 1 1 試圖鎖定隊列 成功:獲得鎖 訪問隊列 為隊列
32、解除鎖 任務任務2 2 試圖鎖定隊列失敗:等待 等待 等待 成功:獲得鎖 訪問隊列 為隊列解除鎖加鎖機制 .50原子數操作n整型原子數操作n原子變量初始化atomic_t test = ATOMIC_INIT(i);n設置原子變量的值void atomic_set(atomic_t *v, int i)n獲得原子變量的值atomic_read(v)n原子變量加void atomic_add(int i, atomic_t *v)n原子變量減void atomic_sub(int i, atomic_t *v).51原子數操作n整型原子數操作n原子變量的自增操作void atomic_inc(a
33、tomic_t *v)n原子變量的自減操作void atomic_dec(atomic_t *v)n操作并測試 (測試其是否為0,0為true,否為false)atomic_inc_and_test(atomic_t *v)atomic_dec_and_test(atomic_t *v)int atomic_sub_and_test(int i, atomic_t *v)n操作并返回 (返回新值)int atomic_add_return(int i, atomic_t *v)int atomic_sub_return(int i, atomic_t *v).52原子數操作n原子位操作n設置位
34、void set_bit(int nr, volatile unsigned long * addr)n清除位void clear_bit(int nr, volatile unsigned long * addr)n改變位change_bit(nr,p)n測試位test_bit(int nr, const volatile unsigned long * p)n測試并操作位test_and_set_bit(nr,p).53自旋鎖n自旋鎖是專為防止多處理器并發而引入的一種鎖,它在內核中大量應用于中斷處理等部分。而對于單處理器來說,防止中斷處理中的并發可簡單采用關閉中斷的方式,不需要自旋鎖。 n
35、自旋鎖最多只能被一個內核任務持有,若一個內核任務試圖請求一個已被持有的自旋鎖,那么這個任務就會一直進行忙循環,也就是旋轉,等待鎖重新可用。 n自旋鎖可以在任何時刻防止多于一個的內核任務同時進入臨界區,因此這種鎖可有效地避免多處理器上并發運行的內核任務競爭共享資源。 .54自旋鎖n自旋鎖的初衷就是:在短期間內進行輕量級的鎖定。一個被爭用的自旋鎖使得請求它的線程在等待鎖重新可用的期間進行自旋(特別浪費處理器時間),所以自旋鎖不應該被持有時間過長。如果需要長時間鎖定的話, 最好使用信號量。 .55自旋鎖n自旋鎖防止在不同CPU上的執行單元對共享資源的同時訪問,以及不同進程上下文互相搶占導致的對共享資
36、源的非同步訪問。n在單CPU且不可搶占的內核下,自旋鎖的所有操作都是空操作。 n自旋鎖不允許任務睡眠。.56自旋鎖n自旋鎖的基本形式如下:spin_lock(&mr_lock);/*臨界區*/spin_unlock(&mr_lock);.57自旋鎖n自旋鎖原語要求包含文件是 . 鎖的類型是 spinlock_t.n鎖的兩種初始化方法:nspinlock_t my_lock = SPIN_LOCK_UNLOCKED;nvoid spin_lock_init(spinlock_t *lock);n進入一個臨界區前, 必須獲得需要的 lock。nvoid spin_lock(spin
37、lock_t *lock);n自旋鎖等待是不可中斷的。一旦你調用spin_lock, 將自旋直到鎖變為可用。n釋放一個鎖:nvoid spin_unlock(spinlock_t *lock);.58自旋鎖n關中斷的自旋鎖nSpin_lock_irq( )nSpin_unlock_irq( )nSpin_lock_irqsave ( )nSpin_unlock_irqrestore ( ).59信號量nLinux中的信號量是一種睡眠鎖。n如果有一個任務試圖獲得一個已被持有的信號量時,信號量會將其推入等待隊列,然后讓其睡眠。n當持有信號量的進程將信號量釋放后,在等待隊列中的一個任務將被喚醒,從而
38、便可以獲得這個信號量。n信號量的睡眠特性,使得信號量適用于鎖會被長時間持有的情況;n信號量的操作n信號量支持兩個原子操作P()和V(),前者做測試操作,后者叫做增加操作。nLinux中分別叫做down()和up()。 .60信號量ndown()和up()。ndown()操作通過對信號量計數減1來請求獲得一個信號量。n如果結果是0或大于0,信號量鎖被獲得,任務就可以進入臨界區了。n如果結果是負數,任務會被放入等待隊列,處理器執行其它任務。n相反,當臨界區中的操作完成后,up()操作用來釋放信號量,增加信號量的計數值。n如果在該信號量上的等待隊列不為空,處于隊列中等待的任務在被喚醒的同時會獲得該信
39、號量。.61信號量.62信號量.63Linux信號量的實現n內核代碼必須包含 ,才能使用信號量。n相關的類型是 struct semaphore信號量的定義struct semaphore atomic_t count; int sleepers; wait_queue_head_t wait; .64Linux信號量的實現n信號量的聲明和初始化n直接創建一個信號量 struct semaphore * sem;n接著使用 sema_init 來初始化這個信號量:void sema_init(struct semaphore *sem, int val);n互斥模式的信號量聲明,內核提供宏定義
40、.nDECLARE_MUTEX(name);信號量初始化為 1nDECLARE_MUTEX_LOCKED(name);信號量初始化為0.65Linux信號量的實現n動態分配的互斥信號量聲明nvoid init_MUTEX(struct semaphore *sem);信號量初始化為 1nvoid init_MUTEX_LOCKED(struct semaphore *sem);信號量初始化為0.66Linux信號量的實現n信號量的P操作nvoid down(struct semaphore *sem);down減小信號量的值,并根據信號量的值決定是否等待。不可中斷的等待。nint down_i
41、nterruptible(struct semaphore *sem);操作是可中斷的。nint down_trylock(struct semaphore *sem);信號量在調用時不可用, down_trylock 立刻返回一個非零值.67Linux信號量的實現n信號量的V操作nvoid up(struct semaphore *sem);通過down操作進入臨界區的進程,再退出的時候都需要調用一個up操作,釋放信號量。.68Linux信號量的實現n信號量基本使用形式為:static DECLARE_MUTEX(mr_sem);/聲明互斥信號量if(down_interruptible(&
42、amp;mr_sem)/*可被中斷的睡眠,當信號來到,睡眠的任務被喚醒 */*臨界區*/up(&mr_sem);n操作配套使用.69Linux 設備驅動調試 .70內核調試選項n內核開發者在內核自身中構建了多個調試特性。這些特性會產生額外的輸出并降低性能,Linux發行版的內核為了提高性能,去除這些調試特性。n用來開發的內核應當激活的調試配置選項,是在“kernel hacking” 菜單中。.71通過打印調試 nPrintknprintk 通過附加不同的消息優先級在消息上,對消息的嚴重程度進行分類。在 定義了8個loglevel。nDEFAULT_MESSAGE_LOGLEVEL為默
43、認級別(printk.c )n當消息優先級小于console_loglevel,信息才能顯示出來。而console_loglevel的初值為DEFAULT_CONSOLE_LOGLEVEL。n通過對/proc/sys/kernel/printk的訪問來改變console_loglevel的值。該文件包含四個數字:當前的loglevel、默認loglevel、最小允許的loglevel、引導時的默認loglevel。echo 1 /proc/sys/kernel/printk echo 8 /proc/sys/kernel/printk.72通過打印調試n打開和關閉消息n通過封裝printk函數
44、,快速打開調試信息或者關閉調試信息。# define PDEBUG(fmt, args.) printk( KERN_DEBUG “myscull: fmt, # args) n通過在Makefile里面定義調試開關變量去決定調試信息是否打開。.73通過查詢調試n獲取相關信息的最好方法:在需要的時候才去查詢系統信息,而不是持續不斷地產生數據。n/proc文件系統是一種特殊的、由軟件創建的文件系統,內核使用他向外界導出信息。n/proc下面的每個文件都綁定于一個內核函數,用戶讀取其中的文件時,該函數動態的生成文件的內容。 例如/proc/devices.74通過查詢調試n包含包含 n在驅動中定義
45、跟proc文件綁定的內核函數read_proc,在函數里面定義要輸出的信息。n在初始化函數中調用creat_proc_read_entry函數將/proc入口文件和read_proc函數聯系起來。n卸載模塊時調用remove_proc_entry撤銷proc入口。.75通過查詢調試nread_proc函數int (*read_proc)(char *page, char *start, off_t offset, int count, int *eof, void *data);npage 是輸出數據的緩存內存頁。進程讀取/proc文件時,內核會分配一個內存頁,read_proc將數據通過這個
46、內存頁返回到用戶空間。 nstart 是這個函數用來說有關的數據寫在頁中哪里neof,當沒有數據可返回時,驅動設置這個參數。nData是提供給驅動的專用數據指針。.76通過查詢調試nint sprintf (char *buf, const char *fmt, .)n將數據打包成字符流的形式。n內核很多象printk函數一樣,通過庫函數的形式提供給內核開發者的函數,以滿足內核開發中的一些簡單的需要。nvoid *memset (void *s, char c, size_t count);nvoid *memcpy (void *dest, const void *src, size_t c
47、ount);.77通過查詢調試ncreat_proc_read_entry函數struct proc_dir_entry *create_proc_read_entry(const char *name,mode_t mode, struct proc_dir_entry *base, read_proc_t *read_proc, void *data);nname 是要創建的proc文件名, nmod 是文件的訪問掩碼(缺省0 ), nbase 指出要創建的文件的目錄;如果 base 是 NULL, 文件在 /proc 根下創建 。n read_proc 是實現文件內容的 read_pro
48、c 函數, ndata 被內核忽略(傳遞給 read_proc).78查看Oops信息n大多數bug通常是因為廢棄了一個NULL指針或者使用了錯誤的指針值。這類bug導致的結果通常是一條oops消息。 n一條oops消息能夠顯示發生故障時CPU的狀態,以及CPU寄存器的內容和其他看似難以理解的信息。.79查看Oops信息n例如訪問一個NULL指針。因為NULL不是一個可訪問的指針值,所以會引發一個錯誤,內核會簡單地將其轉換為oops消息并顯示。然后其調用進程會被殺死。 Unable to handle kernel NULL pointer dereference at virtual add
49、ress 00000000 printing eip: d083a064 Oops: 0002 #1 SMP CPU: 0 EIP: 0060: Not tainted EFLAGS: 00010246 (2.6.6) EIP is at oops_example _write+0 x4/0 x10 oops_example eax: 00000000 ebx: 00000000 ecx: 00000000 edx: 00000000 .80通過監視調試nstrace命令可以顯示由用戶空間程序所發出的所有系統調用。n還以符號形式顯示調用的參數和返回值。當一個系統調用失敗, 錯誤的符號值(例如,
50、 ENOMEM)和對應的字串(Out of memory) 都顯示. nstrace 有很多命令行選項; n -t 來顯示每個調用執行的時間,n -T 來顯示調用中花費的時間, n-e 來限制被跟蹤調用的類型, n-o 來重定向輸出到一個文件. 缺省地, strace 打印調用信息到 stderr.81Linux 的內存分配 .82kmalloc函數void *kmalloc(size_t size,int flags);n所分配到的內存在物理內存中連續且保持原有的數據(不清零)。 nsize是要分配的塊的大小。Linux 創建一系列內存對象slab,每個slab內的內存塊大小是固定。處理分配
51、請求時,就直接在包含有足夠大內存塊的slab中分配一個整塊給請求者。n內核只能分配一些預定義的、固定大小的字節數組。kmalloc 能夠處理的最小內存塊是 32 或 64 字節(體系結構依賴),而內存塊大小的上限隨著體系和內核配置而變化。n不應分配大于 128 KB的內存。若需多于幾個 KB的內存塊,最好使用其他方法。 .83kmalloc函數void *kmalloc(size_t size,int flags);nFlags 分配標志,表示如何分配空間 。所有標志都定義在 nGFP_KERNEL 內存分配內存分配是代表運行在內核內核空間的進程執行的,并且在空閑內存較少時把當前進程轉入休眠以
52、等待一個頁面。n GFP_ATOMIC 內核通常會為原子性的分配預留一些空閑頁。當當前進程不能被置為睡眠時,應使用 GFP_ATOMIC,這樣kmalloc 甚至能夠使用最后一個空閑頁。如果連這最后一個空閑頁也不存在,則分配返回失敗。常用來從中斷處理和進程上下文之外的其他代碼中分配內存,從不睡眠。nGFP_DMA 要求分配可用于DMA的內存。 .84后備高速緩存后備高速緩存 n內核為驅動程序常常需要反復分配許多相同大小內存塊的情況,增加了一些特殊的內存池,稱為后備高速緩存(lookaside cache)。 設備驅動程序通常不會涉及后備高速緩存,但是也有例外:在 Linux 2.6 中 USB
53、 和 SCSI 驅動。.85后備高速緩存后備高速緩存n創建一個新的后備高速緩存 kmem_cache_create nname: name和這個后備高速緩存相關聯;通常設置為被緩存的結構類型的名字,不能包含空格。n參數size:每個內存區域的大小。noffset:頁內第一個對象的偏移量;用來確保被分配對象的特殊對齊,0 表示缺省值。 nflags:控制分配方式的位掩碼:nSLAB_NO_REAP 保護緩存在系統查找內存時不被削減,不推薦。 nSLAB_HWCACHE_ALIGN 所有數據對象跟高速緩存行對齊,平臺依賴,可能浪費內存。 nSLAB_CACHE_DMA 每個數據對象在 DMA 內存
54、區段分配.86后備高速緩存后備高速緩存n創建一個新的后備高速緩存 kmem_cache_create n參數constructor 和 destructor 是可選函數,用來初始化新分配的對象和在內存被作為整體釋放給系統之前“清理”對象。.87后備高速緩存后備高速緩存nkmem_cache_alloc 從已創建的后備高速緩存中分配對象: void *kmem_cache_alloc(kmem_cache_t *cache, int flags);flags 和kmalloc 的flags相同n使用 kmem_cache_free釋放一個對象: void kmem_cache_free(kmem
55、_cache_t *cache, const void *obj);n當驅動用完這個后備高速緩存(通常在當模塊被卸載時),釋放緩存:int kmem_cache_destroy(kmem_cache_t *cache); .88get_free_page相關函數 n如果一個模塊需要分配大塊的內存,最好使用面向頁的分配技術。 n_get_free_page(unsigned int flags);返回一個指向新頁的指針, 未清零該頁nget_zeroed_page(unsigned int flags); 類似于_get_free_page,但用零填充該頁n_get_free_pages(uns
56、igned int flags, unsigned int order);n 分配是若干(物理連續的)頁面并返回指向該內存區域的第一個字節的指針,該內存區域未清零nflags 與 kmalloc 的用法相同norder 是請求或釋放的頁數以 2 為底的對數。若其值過大(沒有這么大的連續區可用), 則分配失敗 .89get_free_page相關函數n當程序不需要頁面時,用下列函數之一來釋放void free_page(unsigned long addr);void free_pages(unsigned long addr, unsigned long order);.90Linux 的中斷
57、處理 .91為什么會有中斷n中斷最初是為克服對I/O接口控制采用程序查詢所帶來的處理器低效率而產生的。n處理器速度一般比外設快很多n用輪詢的方式來查詢設備的狀態,CPU效率不高,CPU和外設不能并行工作。n中斷機制讓CPU啟動設備后,就去處理其他任務,只有當外設真正完成數據傳輸的準備,請求CPU服務的時候,CPU才轉過來處理外設的請求。.92中斷和異常n外部中斷:外部設備所發出的I/O請求。n隨著計算機系統結構的不斷改進以及應用技術的日益提高,中斷的適用范圍也隨之擴大,出現了所謂的內部中斷(或叫異常)。n異常:為解決機器運行時所出現的某些隨機事件及編程方便而出現的。.93I/O中斷處理n為了保
58、證系統對外部的響應,一個中斷處理程序必須被盡快的完成。因此,把所有的操作都放在中斷處理程序中并不合適nLinux中把緊隨中斷要執行的操作分為三類n緊急的(critical)一般關中斷運行。諸如對PIC應答中斷,對PIC或是硬件控制器重新編程,或者修改由設備和處理器同時訪問的數據n非緊急的(noncritical)如修改那些只有處理器才會訪問的數據結構(例如按下一個鍵后讀掃描碼),這些也要很快完成,因此由中斷處理程序立即執行,不過一般在開中斷的情況下.94I/O中斷處理nLinux中把緊隨中斷要執行的操作分為三類n非緊急可延遲的(noncritical deferrable)n這些操作可以被延遲
59、較長的時間間隔而不影響內核操作,有興趣的進程將會等待數據。內核用下半部分這樣一個機制來在一個更為合適的時機用獨立的函數來執行這些操作。n如把緩沖區內容拷貝到某個進程的地址空間(例如把鍵盤緩沖區內容發送到終端處理程序進程)。.95S3c2410的中斷n中斷發生時,需要知道中斷的來源。n芯片的引線有限,很難提供很多條中斷請求引線。使用中斷控制器管理中斷請求線。nS3c2410將中斷控制器集成在CPU芯片中,“復用”GPIO端口引腳,具有作為中斷請求線的功能。nSRCPND寄存器32位中的每一位對應著一個中斷源,每一位被設置為1,則相應的中斷源產生中斷請求并且等待中斷被服務。 .96注冊中斷服務例程
60、 n中斷號是一個寶貴且常常有限的資源。內核維護一個中斷號的注冊表。n要使用中斷,就要進行中斷號的申請,也就是IRQ(Interrupt ReQuirement)。n只有當設備需要中斷的時候才申請占用一個IRQ,或者是在申請IRQ時采用共享中斷的方式,讓更多的設備使用中斷。.97注冊中斷服務例程 n在 實現中斷注冊接口:int request_irq(unsigned int irq, irqreturn_t (*handler)(int, void *, struct pt_regs *),unsigned long flags,const char *dev_name, void *dev_id);void free_irq(
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業或盈利用途。
- 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 公司節能降耗活動方案
- 公司組織KTV唱歌活動方案
- 公司網絡經營活動方案
- 公司福利旅行活動方案
- 公司每月團聚活動方案
- 公司節日互動活動方案
- 公司組織娛樂活動方案
- 公司每日員工活動方案
- 公司組織去爬山活動方案
- 公司班組趣味活動方案
- 2023風光互補路燈設計方案
- 2023年山東省夏季普通高中學業水平合格考試會考生物試題及參考答案
- 2024年山東省青島市中考英語試卷附答案
- 2023-2024學年山東省臨沂市蘭山區八年級(下)期末數學試卷(含答案)
- 材料力學(山東聯盟-中國石油大學(華東))智慧樹知到期末考試答案章節答案2024年中國石油大學(華東)
- 江西省南昌二中心遠教育集團九灣學校2023-2024學年八年級下學期期末考試物理試題
- 深入理解Nginx(模塊開發與架構解析)
- MOOC 中國文化概論-華南師范大學 中國大學慕課答案
- 初中人教版八年級下冊期末物理真題模擬試卷經典套題
- JBT 11699-2013 高處作業吊籃安裝、拆卸、使用技術規程
- 家長會課件:初中七年級家長會課件
評論
0/150
提交評論