歡迎您光臨本站 註冊首頁

Linux設備驅動開發概述

←手機掃碼閱讀     火星人 @ 2014-03-09 , reply:0
作者:宋寶華 email:author@linuxdriver.cn
在過去這些年,Linux已經成功應用於伺服器和桌面系統,而近年來,隨著嵌入式系統應用的持續升溫,Linux也開始廣泛應用於嵌入式領域,逐步成為通信、工業控制、消費電子等領域的主流操作系統.Linux正以其獨特的優勢極大地吸引電子設計工程師,很多工程師從自己編寫的或專用的RTOS轉移到Linux,Linux在嵌入式系統中的佔有率與日俱增.全世界有無數的嵌入式產品正使用Linux作為其操作系統,在這些採用Linux作為操作系統的設備中,無一例外都包含著多個Linux設備驅動,沒有這些設備驅動,用戶便無法享受Linux上諸多精彩紛呈的應用.
1.Linux設備驅動開發的基礎
Linux設備驅動的開發需要牢固的硬體基礎,並需要對驅動中所涉及的Linux內核知識有良好的掌握,具體表現在:
(1)驅動直接與硬體打交道,在編寫某類硬體設備的驅動時,我們必須對該驅動涉及到的硬體的工作原理和介面有清楚的掌握,許多時候,我們需要直接操作寄存器、控制中斷和DMA.
(2)編寫Linux設備驅動涉及到許多Linux內核的API,會大量使用自旋鎖、信號量、等待隊列、tasklet、內存與I/O訪問,如果對內核中的相關API了解不夠充分,很難寫出高質量的驅動.
在Linux設備驅動開發中,自旋鎖和信號量是兩種最常用的用於併發控制的手段,幾乎所有的設備驅動中都使用了自旋鎖或信號量.自旋鎖和信號量控制臨界區的方法相似:
spin_lock (&lock) ; //獲取自旋鎖,保護臨界區
critical section //臨界區
spin_unlock (&lock) ; //釋放自旋鎖

down(&mount_sem);//獲取信號量,保護臨界區
critical section //臨界區


up(&mount_sem);//釋放信號量
自旋鎖或信號量的區別在於:信號量是進程級的,用於多個進程之間對資源的互斥,雖然也是在內核中,但是該內核執行路徑是以進程的身份,代表進程來爭奪資源的.如果競爭失敗,會發生進程上下文切換(當前進程進入睡眠狀態,CPU運行其它進程).當所要保護的臨界區訪問時間比較短時,用自旋鎖是非常方便的,它節省上下文切換的時間.自旋鎖鎖定期間不允許阻塞,因此要求鎖定的臨界區小.
阻塞和非阻塞I/O是設備訪問的兩種不同模式,阻塞操作意味著在執行設備操作時,若不能獲得資源,則掛起進程直到滿足可操作的條件后再進行操作,被掛起的進程進入休眠狀態,被從調度器的運行隊列移走,直到等待的條件被滿足.而非阻塞操作的進程在不能進行設備操作時,並不掛起,它或者放棄,或者不停地查詢,直至可以進行操作為止.應用程序多以阻塞方式訪問設備,在Linux驅動程序中,經常使用等待隊列(wait queue)來實現進程的阻塞與喚醒控制,一個典型的流程如下所示:
1 static ssize_t xxx_write(struct file *file, const char *buffer, size_t count,
2 loff_t *ppos)
3 {
4 ...
5 DECLARE_WAITQUEUE(wait, current); //定義等待隊列
6 add_wait_queue(&xxx_wait, &wait); //添加等待隊列
7
8 ret = count;
9 /* 等待設備緩衝區可寫 */
10 do
11 {
12 avail = device_writable(...);
13 if (avail < 0)
14 __set_current_state(TASK_INTERRUPTIBLE);//改變進程狀態
15
16 if (avail < 0)
17 {
18 if (file->f_flags &O_NONBLOCK) //非阻塞
19 {
20 if (!ret)
21 ret = - EAGAIN;
22 goto out;
23 }


24 schedule(); //調度其他進程執行
25 if (signal_pending(current))//如果是信號喚醒
26 {
27 if (!ret)
28 ret = - ERESTARTSYS;
29 goto out;
30 }
31 }
32 }while (avail < 0);
33
34 /* 寫設備緩衝區 */
35 device_write(...)
36 out:
37 remove_wait_queue(&xxx_wait, &wait);//將等待隊列移出等待隊列頭
38 set_current_state(TASK_RUNNING);//設置進程狀態為TASK_RUNNING
39 return ret;
40 }
上述流程中,當設備暫時不可寫時,驅動主動通過schedule()調度其他進程執行本身進入睡眠狀態,由於進程進入被加入了等待隊列,它可以被中斷或其他執行路徑喚醒.
大多數外設都包含一個以上的中斷,Linux將中斷分成了2個半部,即頂半部和底半部,頂半部完成儘可能少的比較緊急的功能,它往往只是簡單地讀取寄存器中的中斷狀態並清除中斷標誌后就進行「登記中斷」的工作.「登記中斷」意味著將底半部處理程序掛到該設備的底半部執行隊列中去.這樣,頂半部執行的速度就會很快,可以服務更多的中斷請求,而中斷處理工作的重心就落在了底半部的頭上,它來完成中斷事件的絕大多數任務.底半部可以被新的中斷打斷,這也是底半部和頂半部的最大不同.tasklet、work-queue是Linux內核中常用的用於調度底半部執行的機制,調度底半部的典型方法如下:
1 /*定義tasklet和底半部函數並關聯*/
2 void xxx_do_tasklet(unsigned long);
3 DECLARE_TASKLET(xxx_tasklet, xxx_do_tasklet, 0);
4
5 /*中斷處理底半部*/
6 void xxx_do_tasklet(unsigned long)
7 {
8 ...
9 }
10
11 /*中斷處理頂半部*/
12 irqreturn_t xxx_interrupt(int irq, void *dev_id, struct pt_regs *regs)


13 {
14 ...
15 tasklet_schedule(&xxx_tasklet); //調度底半部執行
16 ...
17 }
高性能處理器一般會提供一個內存管理單元(MMU),該單元輔助操作系統進行內存管理,提供虛擬地址和物理地址的映射、內存訪問許可權保護和CACHE緩存控制等硬體支持.Linux的每個進程可以訪問4GB的內存,0~3GB位於用戶空間,對所有進程單獨控制,3GB~4GB位於內核空間,被所有進程共享.在Linux內核空間申請內存涉及到的函數主要包括kmalloc()、__get_free_pages()和vmalloc()等.kmalloc()和__get_free_pages()申請的內存在物理上也是連續的,它們與真實的物理地址只有一個固定的偏移,因此存在較簡單的轉換關係.而vmalloc() 在虛擬內存空間給出一塊連續的內存區,實質上,這片連續的虛擬內存在物理內存中並不一定連續,而vmalloc()申請的虛擬內存和物理內存之間也沒有簡單的換算關係.
在驅動中,對於外設的寄存器,不能直接訪問物理地址,需訪問經過映射后的虛擬地址.外設的寄存器可以用2種方式被映射到虛擬地址,一是靜態映射,二是通過ioremap()動態映射.靜態映射的方法是在將Linux移植到特定平台時建立一個 map_desc數組,通過 MACHINE_START和MACHINE_END宏之間的.map_io成員函數建立頁面.
2.Linux設備驅動的架構
近年來內核在驅動方面更偏向於提供設備驅動的架構(Framework)而非單個設備驅動,考慮到框架更強的兼容性,字元設備、塊設備、網路設備、MTD設備、TTY設備、I2C設備、LCD設備、音頻設備、攝像頭、USB設備、PCI設備等驅動的體系結構都變得愈發複雜.
Linux設備驅動作為一個內核模塊而存在,模塊可以直接編譯進內核或編譯為.ko文件通過insmod、modprobe動態載入.


Linux字元設備驅動的核心是file_operations結構體,驅動的主體是實現其中的read()、write()、ioctl()等成員函數,如:
1 struct file_operations xxx_fops =
2 {
3 .owner = THIS_MODULE,
4 .read = xxx_read,
5 .write = xxx_write,
6 .ioctl = xxx_ioctl,
7 ...
8 };
Linux塊設備驅動並不直接實現file_operations成員函數,其主體變成處理實現block_device_operations成員函數以及處理上層下達的I/O請求,處理I/O請求典型流程如下:
1 static void xxx_request(request_queue_t *q)
2 {
3 struct request *req;
4 while ((req = elv_next_request(q)) != NULL)
5 {
6 struct xxx_dev *dev = req->rq_disk->private_data;
7 if (!blk_fs_request(req)) //不是文件系統請求
8 {
9 printk(KERN_NOTICE "Skip non-fs requestn");
10 end_request(req, 0);//通知請求處理失敗
11 continue;
12 }
13 xxx_transfer(dev, req->sector, req->current_nr_sectors, req->buffer,
14 rq_data_dir(req)); //處理這個請求
15 end_request(req, 1); //通知成功完成這個請求
16 }

Linux網路設備驅動的結構如上圖所示,其中設備驅動功能層各函數是網路設備介面層net_device 數據結構的具體成員,是驅使網路設備硬體完成相應動作的程序,它通過hard_start_xmit()函數啟動發送操作,並通過網路設備上的中斷觸發接收操作.sk_buff 結構體用於表示描述網路包,它定義了對應於傳輸層TCP/UDP(及ICMP 和IGMP)、網路層 和和鏈路層協議的協議頭.
Linux下編寫網路設備驅動的主體工作是完成net_device結構體的填充以及成員函數的實現,底層最核心的工作是:發送數據包和接收數據包,接收數據包是由中斷觸發的.發送數據包函數的典型結構如下:


1 int xxx_tx(struct sk_buff *skb, struct net_device *dev)
2 {
3 int len;
4 char *data, shortpkt[ETH_ZLEN];
5 /* 獲得有效數據指針和長度 */
6 data = skb->data;
7 len = skb->len;
8 if (len < ETH_ZLEN)
9 {
10 /* 如果幀長小於以太幀最小長度,補0 */
11 memset(shortpkt, 0, ETH_ZLEN);
12 memcpy(shortpkt, skb->data, skb->len);
13 len = ETH_ZLEN;
14 data = shortpkt;
15 }
16
17 dev->trans_start = jiffies; /* 記錄發送時間戳 */
18
19 /* 設置硬體寄存器讓硬體把數據包發送出去 */
20 xxx_hw_tx(data, len, dev);
21 ...
22 }
接收數據包的典型結構是:
1 static void xxx_interrupt(int irq, void *dev_id, struct pt_regs *regs)
2 {
3 ...
4 switch (status &ISQ_EVENT_MASK)
5 {
6 case ISQ_RECEIVER_EVENT:
7 /* 獲取數據包 */
8 xxx_rx(dev);
9 break;
10 /* 其他類型的中斷 */
11 }
12 }
13 static void xxx_rx(struct xxx_device *dev)
14 {
15 ...
16 length = get_rev_len (...);
17 /* 分配新的套接字緩衝區 */
18 skb = dev_alloc_skb(length 2);
19
20 skb_reserve(skb, 2); /* 對齊 */
21 skb->dev = dev;
22
23 /* 讀取硬體上接收到的數據 */
24 insw(ioaddr RX_FRAME_PORT, skb_put(skb, length), length >> 1);
25 if (length &1)
26 skb->data[length - 1] = inw(ioaddr RX_FRAME_PORT);
27
28 /* 獲取上層協議類型 */
29 skb->protocol = eth_type_trans(skb, dev);
30
31 /*把數據包交給上層 */
32 netif_rx(skb);
33
34 /* 記錄接收時間戳 */
35 dev->last_rx = jiffies;
36 ...


37 }
對於其他如MTD設備、TTY設備、I2C設備、LCD設備、音頻設備、攝像頭、USB設備、PCI設備等,Linux都定義了類似於網路設備驅動的複雜的層次結構,如TTY設備驅動的層次如下:

這些複雜結構的定義,加大了Linux驅動的開發門檻,同時也是的開發Linux驅動甚至具有了類似於使用VC 開發MFC程序的特點.
3.總結
簡言之,可以得出如下等式:Linux設備驅動開發=硬體控制+Linux內核API(用於併發/同步控制、阻塞/喚醒、中斷底半部調度、內存和I/O訪問等)+驅動框架.等式右邊的3個要素缺一不可,開發高質量的Linux驅動也勢必要求工程師對這些知識有良好的掌握,拙著《Linux設備驅動開發詳解》一書對這些知識都進行了深入講解.


[火星人 ] Linux設備驅動開發概述已經有320次圍觀

http://coctec.com/docs/linux/show-post-55003.html