linux
裝置驅動程式層次結構
作為unix
作業系統的乙個變種
,linux
作業系統實現了大多數
unix
操作系
統的系統設施。系統將所有的裝置
(不僅僅是磁碟上的檔案
)都看成檔案,並納入檔案系統得範疇,通過檔案系統介面對裝置進行操作。下圖是針對
linux
系統中普通裝置
(非磁碟檔案
)的驅動程式層次結構。
裝置檔案
應用程式如果要想訪問裝置,首先要在檔案系統中建立乙個裝置檔案節
點,這可以通過
mknod()
系統呼叫實現,也可以用使用者命令工具
mknod
完成。
注意雖然說
unix
類系統大多都將對裝置的訪問也納到檔案系統的範疇,在檔案系統介面層都是一樣的,但是對裝置的訪問與對普通磁碟檔案的訪問在內部實現上還是有很大的差異的,也就是內部的描述也不相同。所以在建立這兩種檔案的時候所使用的系統呼叫也就不一樣了。
為什麼稱之為「裝置檔案節點「?這是為了區別普通磁碟檔案。因為這個檔案本身不會存放真正的資料,而只是存放了一些裝置相關屬性描述,和如何訪問其對映的裝置
(指示檔案系統層如何找到裝置驅動程式的入口
)。所以使用者可以將其看成訪問裝置的視窗。
mknod
工具實際上是間接呼叫了
mknod()
系統呼叫。然後應用程式可以像讀寫
一般的檔案一樣對這個裝置檔案進行操作,作業系統會將具體的操作對映到
特定的裝置的裝置驅動程式上。
linux
使用了主裝置號
(major)
和次裝置號
(minor)
對同一類裝置
中的不同裝置進行區分。
linux
系統對字元裝置主裝置號進行動態分配是從
127開始的(最大
255),
0~127
是系統靜態指定的,而且通常已經使用了。
如果系統沒有其他更多的裝置要加入,可以直接使用
127~255
之間的字元裝置的主裝置號,不會出現衝突。而一般乙個裝置驅動程式只是針對了乙個物理裝置
(含有次裝置的裝置除外),
因此僅僅需要乙個次裝置號,
0~255
之間可任選,我們選用了第
0號次裝置號。因此在建立檔案節點時可以按照如下方式進行:
命令列方式:
mknod /dev/mydev c 127 0
系統呼叫方式:
mknod( 「/dev/ mydev」, s_ifchr|0666 , (127<<8) | (0) )
裝置驅動程式的編寫
驅動程式是直接與硬體裝置打交道的,涉及到的大部分**都是各種設
備讀寫邏輯,針對具體的裝置都是不一樣的,因此我們在這裡只對與操作系
統檔案系統介面的那一部分機制進行說明。
這裡以編寫乙個字元裝置驅動程式為例。
檔案系統介面
檔案系統管理中有乙個資料結構
file_operations,
要讓應用程式可以通過
檔案系統對裝置進行訪問,我們必須把驅動程式的各種讀寫控制函式的位址
註冊到對應的位置上去。比較完整的驅動程式至少應該提供基本的開啟、關
閉和讀寫操作,如
open, close, write, read
。功能更多的就應該提供
ioctl
之類的
操作。我們假設選定四個基本的操作,如下:
static struct file_operations mydev_fops=;
其中,mydev_x
是我們自己定義的函式,這些函式都必須按照指定引數和返
回值型別進行定義。具體形式和用法請參考
file_operations
資料結構。然後在驅動程式初始化函式中用
register_chrdev
註冊驅動程式。具體用法如下:
register_chrdev( 127, device_name, &mydev_fops );
127是主裝置號,
device_name
是裝置名,我們在這裡定義為「
mydev」,
然後提交
mydev_fops
結構的首位址以註冊操作。核心維護了兩個
device_struct
結構的陣列:
blkdevs
和chrdevs
,分別用於管理塊裝置和字元裝置。這裡的註冊操作就是要將
mydev_fops
位址根據主裝置號寫入
chrdevs
陣列的對應項。之後,只要知道主裝置號,我們就可以找到它的
file_operations
結構,進而找到這種裝置的驅動函式。
i/o埠資源
訪問裝置一定會涉及到埠讀寫操作,但是我們不能隨便使用
i/o埠
位址,那將引起系統的不穩定。如果要使用
i/o資源,應該先向系統申請,並
占用一定範圍的
i/o埠資源。這裡涉及了兩個系統呼叫
check_region和
request_region
。check_region
用於檢查埠是否已經被占用,而
request_region
則用於申請
i/o埠資源,並在系統中登記。��法如下:
if( check_region(dev_addr, range ) == -ebusy )
request_region( dev_addr, range, device_name );
在我們的系統中用了從
dev_addr
開始的range
個i/o
埠位址,如果埠已經被其它裝置占用,
check_region()
將會返回
-ebusy
。然後用
request_region()
申請並註冊。
解除安裝驅動
最後解除安裝驅動程式用到了
unregister_chrdev( major, device_name )。然
後是釋放
i/o埠資源
release_region( dev_addr, range )。
載入驅動程式
linux
環境下載入裝置驅動程式可以有好幾種方式。最直接的就是把裝置
驅動程式直接編譯進核心中,這樣就可以在系統引導的第二階段
(裝置初始化)
載入。這樣雖然省去了很多麻煩事,但是卻增大了核心**。另外一種,也是很常見的一種方式,通過模組的方式。
linux
的模組機制為我們增加系統功
能提供了最為便捷和靈活的方式,驅動程式就是最好的例子。我們可以把編
寫好的驅動程式
(按照模組的形式編譯
)動態加入到核心當中。
所選擇的載入驅動的方式不同,那麼程式的編寫方式也不相同。
模組方式
2.4.x
版本核心允許模組編寫者自己定義模組註冊和解除安裝函式名(比如
我們用了
mydev_init()
和mydev_cleanup()
兩個函式),然後通過兩個巨集
module_init( mydev_init )
和module_exit( mydev_cleanup )
來完成登記
工作。驅動函式的登記和
i/o埠資源的申請將在
mydev_init()
中完成,而驅動模組的解除安裝過程中則會呼叫
mydev_cleanup()
,它允許我們自己做一些特定的善後工作。編譯時應該加上以下幾個引數:
__kernel__
告訴編譯器**執行在核心模式下
module
告訴編譯器,**作為模組編譯
-c
僅僅進行編譯,不連線
(不可能完成連線工作)
-o2
要求編譯器做一定的優化
最後,如果要想在系統啟動時自動載入該模組,可以在啟動指令碼中加上
mknod /dev/mydev c 127 0
insmod –f mydev.o
直接編譯進核心
與作為模組載入不同,驅動程式載入核心之
後不需要手動解除安裝,因此不需要與
module_cleanup()
對應的函式,系統在退出時會自動完成相應的工作。而初始化函式完成的工作與
module_init()
一樣。
我們的初始化函式形式如下:
int __init mydev_init( void ){}
然後在mem.c
中的chr_dev_init
函式返回前加上
mydev_init(),
如下:
int __init chr_dev_init( void )
__init
說明該段**只是在系統初始化時有用,完成相應的工作之後,可以收回其記憶體空間。
Linux 裝置驅動併發控制簡述
答案 在linux中會遇到多個程序對共享資源的併發訪問,併發訪問會導致競態的發生,所以需要併發控制機制。併發與競態是指多個執行單位同時並行的被執行,而併發的執行單位對共享資源的訪問很容易導致競態 2.1 對稱多處理器 smp系統 的多個cpu 多個cpu共用同一條系統匯流排,因此可以訪問共同的外設和...
linux驅動程式設計
program for block device driver of devfs type 對linux的devfs型別的驅動程式的編寫可以從以下幾大內容理解和入手 通過分析驅動程式源 可以發現驅動程式一般可分三部分 核心資料結構 核心資料和資源的初始化,註冊以及註消,釋放 底層裝置操作函式 還有d...
Linux驅動程式設計(十) 驅動註冊
驅動註冊使用結構體platform driver,該結構體在 include linux platform device.h 中,使用命令開啟 vim include linux platform device.h驅動註冊函式platform driver register,驅動解除安裝函式plat...