嵌入式Linux設備驅動工作原理的研究

摘要:計算機軟件和集成電路技術的發展,爲嵌入式產業帶來了巨大的機遇和挑戰,Linux以其穩定、高效、易定製、硬件廣泛支持等特點,迅速崛起爲當今計算機領域的一匹黑馬。文章通過對與嵌入式Linux設備驅動程序相關內核源碼進行分析,從設備驅動的體系結構和內核環境兩方面入手,對嵌入式Linux設備驅動程序的工作原理進行剖析和闡述。
關鍵詞: Linux;嵌入式系統;設備驅動;內核環境


0. 引 言
設備驅動程序在Linux內核中佔有極其重要的位置,它是內核用於完成對物理設備的控制操作的功能模塊。除了CPU、內存以及其他很少的幾個部分以外,所有的設備控制操作都必須由與被控設備相關代碼——驅動程序來完成。否則設備就無法在Linux下正常工作,這就是驅動程序開發成爲Linux內核開發的主要工作的原因。
然而,在嵌入式Linux 系統中,內核提供保護機制,用戶空間的進程一般不能直接訪問硬件。進行嵌入式系統的開發,很大的工作量是爲各種設備編寫驅動程序,Linux 設備驅動程序在Linux 內核源代碼中佔有60%以上,從2.0、2.2 到2.4 版本的內核,源代碼的長度日益增加,其實主要是設備驅動程序在增加。
文章通過對與設備驅動相關內核源碼進行分析,從設備驅動的內部結構和內核環境兩方面入手,對設備驅動程序的工作原理進行深入地剖析和闡述。


1. Linux I/O子系統的體系結構
1.1 I/O子系統的層次結構
Linux的I/O子系統分爲上下兩個層次:其下層是與設備有關的,即設備驅動程序,它直接控制設備完成具體的I/O操作,並且向上層提供一組訪問接口;上層部分是與設備無關的,它根據進程的I/O請求設備驅動程序接口與設備進行通信。由於Linux把設備作爲文件管理,所以I/O子系統的上層實際上就是實現文件管理功能的虛擬文件系統VFS,進程的I/O請求經過VFS的轉換完成對設備的各種操作,而這些操作的具體實現是由設備驅動來完成的。(如圖1所示)
1.2 I/O驅動軟件的總體目標
I/O驅動軟件的總體目標是將軟件組織成一種層次結構,底層軟件用來屏蔽具體設備硬件的細節;高層軟件則爲用戶提供一個簡潔的界面,從而實現I/O設計的設備無關性。(如圖1所示)

2. 文件操作例程登記表
2.1 文件系統調用接口

struct file_operations IOdriver_fops = 
{
  read  : IOdriver_read,
  write : IOdriver_write,
};
2.2基本入口點函數結構
我們用以globar_read和read函數爲例來對驅動底層軟件結構是如何屏蔽設備硬件細節進行剖析。
static ssize_t globar_read(struct file *file, char *buf, size_t len, loff_t *off)
{
if(__copy_to_user(buf,&globar_var, sizeof(int)))
{
    return –EFAULT;
}
return sizeof(int);
} 
上述函數的任務是實現將數據從設備複製到用戶空間,也就是將內核空間緩衝區裏的數據複製其工作原理如圖2所示:
 

 
用戶程序讀操作的入口點到用戶空間的緩衝區中,


read和global_read函數之間的通信是靠字符設備驅動接口中的file_operations結構,基於Linux-2.4.20的<linux/fs.h>的定義如下:

struct file_operations {
struct module *owner;
/* 指向擁有本結構的module,用於內核維護模塊的引用技術 */
loff_t (*llseek) (struct file *, loff_t, int);
/* 改變當前文件的讀/寫位置並返回新位置,錯誤返回負數 */
ssize_t (*read) (struct file *, char *, size_t, loff_t);
/* 用來從設備接收數據,返回的非負數表示成功讀取的字節數 */
ssize_t (*write) (struct file *,const char *, size_t, loff_t *);
/* 用來往設備發數據,返回的非負數表示成功寫入的字節數 */
int(readdir))(struct inode *, struct file *, void *, filldir_t);
/* 僅用於文件系統的目錄讀取,不用於設備驅動 */
int (*select)(struct inode *, struct file *, int, select table *);
/* 用於查詢設備是否可讀、可寫或處於特殊的狀態 */
int (*ioctl)(struct inode *, struct file *, unsigned int, unsigned int );  
/* 用於給設備發送命令的接口函數。如驅動不提供,則所有調用失敗,返回ENOTTY */ 
int (*mmap)(struct inode *, struct file *, struct vm_area_struct *);
/* 用於請求將設備內存映射到進程空間。如果驅動不提供,則所有調用失敗,返回ENODEV */
int (*open)(struct inode *, struct file *);  
/* 打開設備,通常是對設備的第一個操作函數 */
void (*release)(struct inode *, struct file *); 
/* 關閉設備。只有當設備文件的所有備份都被釋放時,才進行release調用,而不是每次調用close時都執行。 */
int (*fsync)(struct inode *, struct file *);
/* 是系統調用fsync的背後支撐,用戶可調用fsync來刷新緩存數據 */
};

3. Linux驅動工作原理
3.1 驅動程序工作原理圖(如圖3所示)
 

3.2 驅動程序工作原理剖析
3.2.1註冊驅動程序
設備驅動是通過insmod命令加載到系統內核中,在內核裏由加載模塊Init_module()調用註冊函數register_chrdev()來完成(如圖3所示)。
註冊函數格式爲:

int register_chrdev(unsigned int major, const char *name, struct file_operation *fops);
 major :主設備號
 name :設備名
 fops   :驅動程序結構體的地址
 
當註冊函數在執行時,首先從字符設備註冊表chrdev[ ]的底部(實際是倒數第二個表項)開始向上依次查詢各個表項(如圖4所示),查到表項的成員項fops爲null時,說明它是一個空表項。這時把參數給出的設備名和驅動程序指針集合分別賦予該表項device_struct結構體(也被稱爲設備描述符,在fs/device.c中)的成員項name和fops,如下所示:
 

if (major == 0)
for (major=MAX_CHRDEV-1; major>0; major--)
{if (chrdev[major].fops == NULL)
{chrdev[major].name = name;
 chrdev[major].fops = fops;
 return major;}
return –EBUSY;
}
3.2.2註銷驅動程序
驅動程序的註銷是由rmmod命令調用cleanup_module卸載模塊,此時註銷函數unregister_chrdev()函數就會被調用執行(如圖3所示)。當註銷函數的參數給出了要註銷設備的主設備號major和設備名name後,根據參數major檢查註冊表項中註冊的設備名與參數給出的名字是否一致,若不同則不能註銷並返回錯誤值;如果一致則把該表項的兩個成員項都置爲null然後返回0值,如下所示:
if (strcmp(chrdev[major].name, name))
   reture –EINVAL;
else
   { chrdev[major].name = NULL;
     chrdev[major].fops = NULL;
  return 0;}
在Linux中由於設備管理是由內核實現的,所以設備管理的數據結構在內存的內核區,設備管理使用的函數都是內核函數。


4. 結束語
操作系統是通過各種驅動程序來駕馭硬件設備的,它爲用戶屏蔽了各種各樣的設備,驅動硬件是操作系統最基本的功能,並且提供統一的操作方式。設備驅動程序是內核的一部分,硬件驅動程序是操作系統最基本的組成部分,因此熟悉驅動的編寫是很重要的。
參考文獻:
[1] 劉淼.嵌入式系統接口設計與Linux驅動程序開發.北京:北京航空航天大學出版社,2006.5
[2] 孫紀坤,張小全.嵌入式Linux系統開發技術詳解——基於ARM.北京:人民郵電出版社,2007.6
[3] 倪繼利.Linux內核分析及編程[M].北京:電子工業出版社,2005.9
[4] 王巖,王子牛. 嵌入式Linux設備驅動程序開發.貴州工業大學學報(第一期37卷),2008.2
[5] 賀剛,吳灝.Linux字符設備驅動程序工作原理.信息工程大學學報,2001.6

發佈了30 篇原創文章 · 獲贊 6 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章