日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux设备驱动开发概述

發(fā)布時(shí)間:2025/3/20 linux 20 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux设备驱动开发概述 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
作者:宋寶華 email:author@linuxdriver.cn
??? 在過去這些年,Linux已經(jīng)成功應(yīng)用于服務(wù)器和桌面系統(tǒng),而近年來,隨著嵌入式系統(tǒng)應(yīng)用的持續(xù)升溫,Linux也開始廣泛應(yīng)用于嵌入式領(lǐng)域,逐步成為通信、工業(yè)控制、消費(fèi)電子等領(lǐng)域的主流操作系統(tǒng)。Linux正以其獨(dú)特的優(yōu)勢(shì)極大地吸引電子設(shè)計(jì)工程師,很多工程師從自己編寫的或?qū)S玫腞TOS轉(zhuǎn)移到Linux,Linux在嵌入式系統(tǒng)中的占有率與日俱增。全世界有無數(shù)的嵌入式產(chǎn)品正使用Linux作為其操作系統(tǒng),在這些采用Linux作為操作系統(tǒng)的設(shè)備中,無一例外都包含著多個(gè)Linux設(shè)備驅(qū)動(dòng),沒有這些設(shè)備驅(qū)動(dòng),用戶便無法享受Linux上諸多精彩紛呈的應(yīng)用。
1.Linux設(shè)備驅(qū)動(dòng)開發(fā)的基礎(chǔ)
??? Linux設(shè)備驅(qū)動(dòng)的開發(fā)需要牢固的硬件基礎(chǔ),并需要對(duì)驅(qū)動(dòng)中所涉及的Linux內(nèi)核知識(shí)有良好的掌握,具體表現(xiàn)在:
??? (1)驅(qū)動(dòng)直接與硬件打交道,在編寫某類硬件設(shè)備的驅(qū)動(dòng)時(shí),我們必須對(duì)該驅(qū)動(dòng)涉及到的硬件的工作原理和接口有清楚的掌握,因?yàn)樵S多時(shí)候,我們需要直接操作寄存器、控制中斷和DMA。
??? (2)編寫Linux設(shè)備驅(qū)動(dòng)涉及到許多Linux內(nèi)核的API,會(huì)大量使用自旋鎖、信號(hào)量、等待隊(duì)列、tasklet、內(nèi)存與I/O訪問,如果對(duì)內(nèi)核中的相關(guān)API了解不夠充分,很難寫出高質(zhì)量的驅(qū)動(dòng)。
??? 在Linux設(shè)備驅(qū)動(dòng)開發(fā)中,自旋鎖和信號(hào)量是兩種最常用的用于并發(fā)控制的手段,幾乎所有的設(shè)備驅(qū)動(dòng)中都使用了自旋鎖或信號(hào)量。自旋鎖和信號(hào)量控制臨界區(qū)的方法相似:
spin_lock (&lock) ; //獲取自旋鎖,保護(hù)臨界區(qū)
critical section //臨界區(qū)
spin_unlock (&lock) ; //釋放自旋鎖

down(&mount_sem);//獲取信號(hào)量,保護(hù)臨界區(qū)
critical section //臨界區(qū)
up(&mount_sem);//釋放信號(hào)量
??? 自旋鎖或信號(hào)量的區(qū)別在于:信號(hào)量是進(jìn)程級(jí)的,用于多個(gè)進(jìn)程之間對(duì)資源的互斥,雖然也是在內(nèi)核中,但是該內(nèi)核執(zhí)行路徑是以進(jìn)程的身份,代表進(jìn)程來爭(zhēng)奪資源的。如果競(jìng)爭(zhēng)失敗,會(huì)發(fā)生進(jìn)程上下文切換(當(dāng)前進(jìn)程進(jìn)入睡眠狀態(tài),CPU運(yùn)行其它進(jìn)程)。當(dāng)所要保護(hù)的臨界區(qū)訪問時(shí)間比較短時(shí),用自旋鎖是非常方便的,因?yàn)樗?jié)省上下文切換的時(shí)間。自旋鎖鎖定期間不允許阻塞,因此要求鎖定的臨界區(qū)小。
??? 阻塞和非阻塞I/O是設(shè)備訪問的兩種不同模式,阻塞操作意味著在執(zhí)行設(shè)備操作時(shí),若不能獲得資源,則掛起進(jìn)程直到滿足可操作的條件后再進(jìn)行操作,被掛起的進(jìn)程進(jìn)入休眠狀態(tài),被從調(diào)度器的運(yùn)行隊(duì)列移走,直到等待的條件被滿足。而非阻塞操作的進(jìn)程在不能進(jìn)行設(shè)備操作時(shí),并不掛起,它或者放棄,或者不停地查詢,直至可以進(jìn)行操作為止。應(yīng)用程序多以阻塞方式訪問設(shè)備,在Linux驅(qū)動(dòng)程序中,經(jīng)常使用等待隊(duì)列(wait queue)來實(shí)現(xiàn)進(jìn)程的阻塞與喚醒控制,一個(gè)典型的流程如下所示:
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); //定義等待隊(duì)列
6??? add_wait_queue(&xxx_wait, &wait);? //添加等待隊(duì)列
7?
8??? ret = count;
9??? /* 等待設(shè)備緩沖區(qū)可寫 */
10?? do
11?? {
12???? avail = device_writable(...);
13???? if (avail < 0)
14?????? __set_current_state(TASK_INTERRUPTIBLE);//改變進(jìn)程狀態(tài)
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();?? //調(diào)度其他進(jìn)程執(zhí)行
25?????? if (signal_pending(current))//如果是因?yàn)樾盘?hào)喚醒
26?????? {
27???????? if (!ret)
28?????????? ret =? - ERESTARTSYS;
29???????? goto out;
30?????? }
31???? }
32?? }while (avail < 0);
33
34?? /* 寫設(shè)備緩沖區(qū) */
35?? device_write(...)
36?? out:
37?? remove_wait_queue(&xxx_wait, &wait);//將等待隊(duì)列移出等待隊(duì)列頭
38?? set_current_state(TASK_RUNNING);//設(shè)置進(jìn)程狀態(tài)為TASK_RUNNING
39?? return ret;
40 }
??? 上述流程中,當(dāng)設(shè)備暫時(shí)不可寫時(shí),驅(qū)動(dòng)主動(dòng)通過schedule()調(diào)度其他進(jìn)程執(zhí)行本身進(jìn)入睡眠狀態(tài),由于進(jìn)程進(jìn)入被加入了等待隊(duì)列,它可以被中斷或其他執(zhí)行路徑喚醒。
??? 大多數(shù)外設(shè)都包含一個(gè)以上的中斷,Linux將中斷分成了2個(gè)半部,即頂半部和底半部,頂半部完成盡可能少的比較緊急的功能,它往往只是簡(jiǎn)單地讀取寄存器中的中斷狀態(tài)并清除中斷標(biāo)志后就進(jìn)行“登記中斷”的工作。“登記中斷”意味著將底半部處理程序掛到該設(shè)備的底半部執(zhí)行隊(duì)列中去。這樣,頂半部執(zhí)行的速度就會(huì)很快,可以服務(wù)更多的中斷請(qǐng)求,而中斷處理工作的重心就落在了底半部的頭上,它來完成中斷事件的絕大多數(shù)任務(wù)。底半部可以被新的中斷打斷,這也是底半部和頂半部的最大不同。tasklet、work-queue是Linux內(nèi)核中常用的用于調(diào)度底半部執(zhí)行的機(jī)制,調(diào)度底半部的典型方法如下:
1? /*定義tasklet和底半部函數(shù)并關(guān)聯(lián)*/
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); //調(diào)度底半部執(zhí)行
16 ??? ...
17 }
??? 高性能處理器一般會(huì)提供一個(gè)內(nèi)存管理單元(MMU),該單元輔助操作系統(tǒng)進(jìn)行內(nèi)存管理,提供虛擬地址和物理地址的映射、內(nèi)存訪問權(quán)限保護(hù)和CACHE緩存控制等硬件支持。Linux的每個(gè)進(jìn)程可以訪問4GB的內(nèi)存,0~3GB位于用戶空間,對(duì)所有進(jìn)程單獨(dú)控制,3GB~4GB位于內(nèi)核空間,被所有進(jìn)程共享。在Linux內(nèi)核空間申請(qǐng)內(nèi)存涉及到的函數(shù)主要包括kmalloc()、__get_free_pages()和vmalloc()等。kmalloc()和__get_free_pages()申請(qǐng)的內(nèi)存在物理上也是連續(xù)的,它們與真實(shí)的物理地址只有一個(gè)固定的偏移,因此存在較簡(jiǎn)單的轉(zhuǎn)換關(guān)系。而vmalloc() 在虛擬內(nèi)存空間給出一塊連續(xù)的內(nèi)存區(qū),實(shí)質(zhì)上,這片連續(xù)的虛擬內(nèi)存在物理內(nèi)存中并不一定連續(xù),而vmalloc()申請(qǐng)的虛擬內(nèi)存和物理內(nèi)存之間也沒有簡(jiǎn)單的換算關(guān)系。
??? 在驅(qū)動(dòng)中,對(duì)于外設(shè)的寄存器,不能直接訪問物理地址,需訪問經(jīng)過映射后的虛擬地址。外設(shè)的寄存器可以用2種方式被映射到虛擬地址,一是靜態(tài)映射,二是通過ioremap()動(dòng)態(tài)映射。靜態(tài)映射的方法是在將Linux移植到特定平臺(tái)時(shí)建立一個(gè) map_desc數(shù)組,通過 MACHINE_START和MACHINE_END宏之間的.map_io成員函數(shù)建立頁面。??? ???
2.Linux設(shè)備驅(qū)動(dòng)的架構(gòu)
??? 近年來內(nèi)核在驅(qū)動(dòng)方面更偏向于提供設(shè)備驅(qū)動(dòng)的架構(gòu)(Framework)而非單個(gè)設(shè)備驅(qū)動(dòng),考慮到框架更強(qiáng)的兼容性,字符設(shè)備、塊設(shè)備、網(wǎng)絡(luò)設(shè)備、MTD設(shè)備、TTY設(shè)備、I2C設(shè)備、LCD設(shè)備、音頻設(shè)備、攝像頭、USB設(shè)備、PCI設(shè)備等驅(qū)動(dòng)的體系結(jié)構(gòu)都變得愈發(fā)復(fù)雜。
??? Linux設(shè)備驅(qū)動(dòng)首先作為一個(gè)內(nèi)核模塊而存在,模塊可以直接編譯進(jìn)內(nèi)核或編譯為.ko文件通過insmod、modprobe動(dòng)態(tài)加載。
??? Linux字符設(shè)備驅(qū)動(dòng)的核心是file_operations結(jié)構(gòu)體,驅(qū)動(dòng)的主體是實(shí)現(xiàn)其中的read()、write()、ioctl()等成員函數(shù),如:
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塊設(shè)備驅(qū)動(dòng)并不直接實(shí)現(xiàn)file_operations成員函數(shù),其主體變成處理實(shí)現(xiàn)block_device_operations成員函數(shù)以及處理上層下達(dá)的I/O請(qǐng)求,處理I/O請(qǐng)求典型流程如下:
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)) //不是文件系統(tǒng)請(qǐng)求
8????? {
9??????? printk(KERN_NOTICE "Skip non-fs request\n");
10?????? end_request(req, 0);//通知請(qǐng)求處理失敗
11?????? continue;
12???? }
13???? xxx_transfer(dev, req->sector, req->current_nr_sectors, req->buffer,
14?????? rq_data_dir(req)); //處理這個(gè)請(qǐng)求
15???? end_request(req, 1); //通知成功完成這個(gè)請(qǐng)求
16?? }

??? Linux網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)的結(jié)構(gòu)如上圖所示,其中設(shè)備驅(qū)動(dòng)功能層各函數(shù)是網(wǎng)絡(luò)設(shè)備接口層net_device 數(shù)據(jù)結(jié)構(gòu)的具體成員,是驅(qū)使網(wǎng)絡(luò)設(shè)備硬件完成相應(yīng)動(dòng)作的程序,它通過hard_start_xmit()函數(shù)啟動(dòng)發(fā)送操作,并通過網(wǎng)絡(luò)設(shè)備上的中斷觸發(fā)接收操作。sk_buff 結(jié)構(gòu)體用于表示描述網(wǎng)絡(luò)包,它定義了對(duì)應(yīng)于傳輸層TCP/UDP(及ICMP 和IGMP)、網(wǎng)絡(luò)層 和和鏈路層協(xié)議的協(xié)議頭。
??? Linux下編寫網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)的主體工作是完成net_device結(jié)構(gòu)體的填充以及成員函數(shù)的實(shí)現(xiàn),底層最核心的工作是:發(fā)送數(shù)據(jù)包和接收數(shù)據(jù)包,接收數(shù)據(jù)包是由中斷觸發(fā)的。發(fā)送數(shù)據(jù)包函數(shù)的典型結(jié)構(gòu)如下:
1 int xxx_tx(struct sk_buff *skb, struct net_device *dev)
2 {
3???? int len;
4???? char *data, shortpkt[ETH_ZLEN];
5???? /* 獲得有效數(shù)據(jù)指針和長(zhǎng)度 */
6???? data = skb->data;
7???? len = skb->len;
8???? if (len < ETH_ZLEN)
9???? {
10????????? /* 如果幀長(zhǎng)小于以太幀最小長(zhǎng)度,補(bǔ)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; /* 記錄發(fā)送時(shí)間戳 */
18
19???? /* 設(shè)置硬件寄存器讓硬件把數(shù)據(jù)包發(fā)送出去 */
20???? xxx_hw_tx(data, len, dev);
21???? ...
22 }
??? 接收數(shù)據(jù)包的典型結(jié)構(gòu)是:
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????????? /* 獲取數(shù)據(jù)包 */
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???? /* 分配新的套接字緩沖區(qū) */
18???? skb = dev_alloc_skb(length + 2);
19
20???? skb_reserve(skb, 2); /* 對(duì)齊 */
21???? skb->dev = dev;
22
23???? /* 讀取硬件上接收到的數(shù)據(jù) */
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???? /* 獲取上層協(xié)議類型 */
29???? skb->protocol = eth_type_trans(skb, dev);
30
31???? /*把數(shù)據(jù)包交給上層 */
32???? netif_rx(skb);
33
34???? /* 記錄接收時(shí)間戳 */
35???? dev->last_rx = jiffies;
36???? ...
37 }
??? 對(duì)于其他如MTD設(shè)備、TTY設(shè)備、I2C設(shè)備、LCD設(shè)備、音頻設(shè)備、攝像頭、USB設(shè)備、PCI設(shè)備等,Linux都定義了類似于網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)的復(fù)雜的層次結(jié)構(gòu),如TTY設(shè)備驅(qū)動(dòng)的層次如下:

??? 這些復(fù)雜結(jié)構(gòu)的定義,加大了Linux驅(qū)動(dòng)的開發(fā)門檻,同時(shí)也使得開發(fā)Linux驅(qū)動(dòng)甚至具有了類似于使用VC++開發(fā)MFC程序的特點(diǎn)。
3.總結(jié)
??? 簡(jiǎn)言之,可以得出如下等式:Linux設(shè)備驅(qū)動(dòng)開發(fā)=硬件控制+Linux內(nèi)核API(用于并發(fā)/同步控制、阻塞/喚醒、中斷底半部調(diào)度、內(nèi)存和I/O訪問等)+驅(qū)動(dòng)框架。等式右邊的3個(gè)要素缺一不可,開發(fā)高質(zhì)量的Linux驅(qū)動(dòng)也勢(shì)必要求工程師對(duì)這些知識(shí)有良好的掌握,拙著《Linux設(shè)備驅(qū)動(dòng)開發(fā)詳解》一書對(duì)這些知識(shí)都進(jìn)行了深入講解。

轉(zhuǎn)載于:https://blog.51cto.com/21cnbao/120012

總結(jié)

以上是生活随笔為你收集整理的Linux设备驱动开发概述的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。