linux下camera驱动分析_《Linux设备驱动程序》(五)——字符设备驱动(下)
上一節(jié)介紹了字符驅(qū)動(dòng)中的一些概念,這一節(jié)我們將會(huì)基于系統(tǒng)內(nèi)存編寫一個(gè)字符設(shè)備驅(qū)動(dòng),加深對(duì)上一節(jié)中的概念的理解。
本節(jié)主要學(xué)會(huì)的內(nèi)容:
- 字符設(shè)備注冊(cè)
- 對(duì)設(shè)備節(jié)點(diǎn)進(jìn)行cat和echo操作
驅(qū)動(dòng)設(shè)計(jì)
編寫驅(qū)動(dòng)之前,我們要明確我們的驅(qū)動(dòng)需要或者能夠?yàn)橛脩舫绦蛱峁┦裁垂δ?#xff0c;這也是我們之前提到的機(jī)制。
在《設(shè)備驅(qū)動(dòng)程序》一書中,關(guān)于字符設(shè)備驅(qū)動(dòng)程序的章節(jié)介紹了一個(gè)scull(Simple Character Utility for Loading Localities,區(qū)域裝載的簡(jiǎn)單字符工具)設(shè)備,其設(shè)計(jì)的機(jī)制比較復(fù)雜,同時(shí),其中的部分函數(shù)也已經(jīng)被淘汰了。因此,本文簡(jiǎn)化其機(jī)制,并使用新的函數(shù)進(jìn)行實(shí)現(xiàn)。
本文設(shè)計(jì)的設(shè)備名稱沿用書本中的名字:scull。
本文設(shè)計(jì)的設(shè)備提供一個(gè)固定大小的內(nèi)存區(qū)域(由宏DATA_SIZE決定),可以往其中存入(echo)數(shù)據(jù)(最大不大于指定的大小),同時(shí)可以讀出(cat)其中的存儲(chǔ)的數(shù)據(jù)。
代碼分析
設(shè)備驅(qū)動(dòng)的代碼比較長(zhǎng),所以就不全部列舉出來,本文只對(duì)代碼中的重點(diǎn)部分進(jìn)行簡(jiǎn)要說明,如果需要全部代碼,請(qǐng)看:https://gitee.com/Quehehe/LinuxDeviceDriver
設(shè)備結(jié)構(gòu)體
在頭文件中(scull.h)定義了一個(gè)結(jié)構(gòu)體,內(nèi)容如下:
struct scull_dev { char *data; int data_length; struct cdev cdev;};這個(gè)結(jié)構(gòu)體可以理解為設(shè)備:其包含了struct cdev,可以理解為繼承的概念(當(dāng)然,在C中沒有繼承的說法),說明此設(shè)備是一個(gè)字符設(shè)備;存儲(chǔ)了數(shù)據(jù)的起始地址的指針,數(shù)據(jù)的長(zhǎng)度。
如果有其他與設(shè)備相關(guān)的數(shù)據(jù),都可以放到該結(jié)構(gòu)體中,這樣,只要獲取到這個(gè)結(jié)構(gòu)體就相當(dāng)于獲取了這個(gè)設(shè)備。
文件操作相關(guān)的數(shù)據(jù)結(jié)構(gòu)
static struct file_operations fops = { .owner = THIS_MODULE, .open = scull_open, .release = scull_release, .read = scull_read, .write = scull_write, .unlocked_ioctl = scull_ioctl,};這個(gè)就是上一節(jié)中說的struct file_operations結(jié)構(gòu)體,本文實(shí)現(xiàn)了其中的open、release、read、write和unlocked_ioctl函數(shù)。
open()、read()和write()函數(shù)說明
針對(duì)上面的open()、read()和write()的實(shí)現(xiàn)函數(shù)進(jìn)行說明。
open()函數(shù)的實(shí)現(xiàn)如下:
int scull_open (struct inode *node, struct file *filp){ struct scull_dev *dev; dev = container_of(node->i_cdev, struct scull_dev, cdev); filp->private_data = dev; return 0;}其中container_of()這個(gè)宏的作用是:根據(jù)結(jié)構(gòu)體變量A中的某個(gè)屬性的地址計(jì)算出結(jié)構(gòu)體變量A的地址。利用node中的icdev的地址來獲取上面的struct scull_dev變量的地址。
將獲取到的struct scull_dev地址存儲(chǔ)在filp->private_data變量中,這個(gè)變量上一節(jié)有說明,是一個(gè)void *指針變量,后續(xù)的read、write等操作都可以通過filp來獲取struct scull_dev變量。
read()函數(shù)的實(shí)現(xiàn)如下(截取):
ssize_t scull_read (struct file *filp, char __user *buf, size_t size, loff_t *loff){ ...... if(dev->data_length == 0 || *loff != 0) { return 0; } ...... ret = raw_copy_to_user(buf, dev->data, size); ...... *loff += size; return size;}raw_copy_to_user函數(shù)后面再說明。
這里對(duì)read()的返回值進(jìn)行說明,其返回值有以下幾種:
- 返回值等于形參size,則表示請(qǐng)求的數(shù)據(jù)讀取完成,這是最理想的狀態(tài);
- 返回值是一個(gè)小于size的正整數(shù),表明讀取了部分?jǐn)?shù)據(jù),這種情況根據(jù)應(yīng)用程序需要選擇繼續(xù)讀取或者停止讀取;
- 返回值為0,表示到達(dá)了數(shù)據(jù)的結(jié)尾,后續(xù)沒有數(shù)據(jù)可以傳輸了;
- 返回值為負(fù)數(shù),表示讀取數(shù)據(jù)出錯(cuò);
write()函數(shù)的實(shí)現(xiàn)如下(截取):
ssize_t scull_write (struct file *filp, const char __user *buf, size_t size, loff_t *loff){ ...... ret = raw_copy_from_user(dev->data, buf, size); ...... dev->data_length = size; return size;}raw_copy_from_user()函數(shù)后面再說明。
這里對(duì)write()函數(shù)的返回值進(jìn)行說明,其返回值有以下幾種情況:
- 返回值等于形參size,則表示寫入的數(shù)據(jù)完成;
- 返回值是小于形參size的正整數(shù),則表示未全部寫入,應(yīng)用程序根據(jù)需要可以繼續(xù)寫入或者停止寫入;
- 返回值為0,表示沒有寫入任何數(shù)據(jù),但是出現(xiàn)這個(gè)的原因并不是因?yàn)槿魏五e(cuò)誤導(dǎo)致的,而是其他非原因;
- 返回值為負(fù)數(shù),表示寫入數(shù)據(jù)出錯(cuò);
raw_copy_to_user()與raw_copy_from_user()
這兩個(gè)函數(shù)定義在頭文件中,其作用是將內(nèi)核空間和用戶空間之間相互拷貝數(shù)據(jù),從名字就可以看出:raw_copy_to_user從內(nèi)核向用戶空間拷貝;raw_copy_from_user從用戶空間向內(nèi)核空間拷貝。
內(nèi)核空間與用戶空間的地址是不能直接相互引用的,原因如下:
- 在內(nèi)核模式運(yùn)行時(shí),用戶空間的指針可能是無效的;
- 即使用戶空間的指針與內(nèi)核空間的指針代表的是相同的東西,但用戶空間的內(nèi)存是分頁的,在系統(tǒng)調(diào)用時(shí),涉及到的內(nèi)存可能不在RAM中;
- 出于安全考慮,防止該指針指向一個(gè)惡意程序后者存在缺陷的程序。
字符設(shè)備注冊(cè)(截取)
字符設(shè)備驅(qū)動(dòng)注冊(cè)的部分代碼如下:
static int scull_init(void){ ...... scull_dev.data = (char *)kmalloc(DATA_SIZE, GFP_KERNEL); /** 分配數(shù)據(jù)空間 */ ...... scull_dev.data_length = 0; ret = alloc_chrdev_region(&dev_num, 0, DEVICE_COUNT, DEVICE_NAME); /** 分配設(shè)備號(hào) * cdev_init(&scull_dev.cdev, &fops); /** 初始化字符設(shè)備 */ scull_dev.cdev.owner = THIS_MODULE; ret = cdev_add(&scull_dev.cdev, dev_num, DEVICE_COUNT); /** 添加字符設(shè)備 */ if(ret < 0) { printk(KERN_ALERT "cdev add failed!"); goto cdev_add_err; } /* 創(chuàng)建節(jié)點(diǎn) */ scull_class = class_create(THIS_MODULE, DEVICE_NAME); for(i = 0; i < DEVICE_COUNT; i++) { device_create(scull_class, NULL, MKDEV(MAJOR(dev_num), MINOR(dev_num) + i), NULL, DEVICE_NAME"%d", i); } printk(KERN_ALERT "cdev add complete!"); return 0;cdev_add_err: unregister_chrdev_region(dev_num, DEVICE_COUNT); /** 添加字符設(shè)備出錯(cuò)的話,將之前分配的設(shè)備號(hào)釋放 */alloc_dev_num_err: kfree(scull_dev.data);alloc_data_err: return ret; }首先分配存儲(chǔ)數(shù)據(jù)的空間,空間大小由宏DATA_SIZE決定。
然后分配設(shè)備號(hào),這個(gè)上一節(jié)有講過。
接下來是注冊(cè)字符設(shè)備相關(guān)的操作,主要涉及:cdev_init(),cdev_add()兩個(gè)函數(shù)
cdev_init()將設(shè)備與file_operations綁定在一起。
cdev_add()將設(shè)備與設(shè)備編號(hào)綁定在一起,并添加到系統(tǒng)中。
此時(shí)系統(tǒng)中并沒有設(shè)備節(jié)點(diǎn)的存在(內(nèi)核2.6.0之后),還需要下面class_create()和device_create()來創(chuàng)建設(shè)備節(jié)點(diǎn)。
class_create()會(huì)在/sys/class/目錄下創(chuàng)建相應(yīng)的設(shè)備目錄。
device_create()會(huì)在指定的目錄下創(chuàng)建設(shè)備節(jié)點(diǎn),節(jié)點(diǎn)創(chuàng)建后,相應(yīng)的節(jié)點(diǎn)會(huì)添加到/dev目錄下。
至此,完成了設(shè)備的注冊(cè)過程。
運(yùn)行結(jié)果
在項(xiàng)目的根目錄下運(yùn)行make,編譯得到scull.ko模塊文件,將該模塊加載到系統(tǒng)中。這些操作都是之前有過介紹的,這里就不再詳細(xì)說明了。
加載成功后,結(jié)果如圖所示:
scull模塊加載成功
此時(shí)打印出設(shè)備的主設(shè)備號(hào)240和從設(shè)備號(hào)0。
加載成功后,可以切換到/dev目錄下,目錄中會(huì)生成scull0~3共四個(gè)設(shè)備,如圖所示:
dev目錄下生成設(shè)備節(jié)點(diǎn)
最開始的“c”表示這是一個(gè)字符設(shè)備。
接下來對(duì)scull0這個(gè)設(shè)備進(jìn)行cat和echo操作。
注意到設(shè)備節(jié)點(diǎn)的權(quán)限為rw-------,因此,只有root用戶能夠?qū)?jié)點(diǎn)進(jìn)行讀寫。為了后續(xù)操作方便,利用sudo chmod命令去改變節(jié)點(diǎn)的權(quán)限,使得other用戶也能讀寫,命令如下圖所示:
修改節(jié)點(diǎn)權(quán)限
對(duì)節(jié)點(diǎn)進(jìn)行cat操作,結(jié)果如下:
第一次cat節(jié)點(diǎn)
沒有打印任何信息,因?yàn)榇藭r(shí)數(shù)據(jù)長(zhǎng)度為0。
接下來利用echo往節(jié)點(diǎn)中隨便寫入一些數(shù)據(jù):
第一次echo寫入數(shù)據(jù)
第二次cat節(jié)點(diǎn)結(jié)果
可以看到,上面echo寫入的數(shù)據(jù)被讀取出來了,由此判斷我們的驅(qū)動(dòng)正常運(yùn)行。
可以多試幾次,結(jié)果如下:
多次嘗試結(jié)果
從上面可知,驅(qū)動(dòng)程序正常運(yùn)行。
至此,完成了字符設(shè)備驅(qū)動(dòng)的相關(guān)介紹。
當(dāng)然,這個(gè)驅(qū)動(dòng)示例也只能當(dāng)做一個(gè)簡(jiǎn)單的示例,還有許多需要完善的地方,后續(xù)根據(jù)學(xué)習(xí)情況慢慢添加。
(代碼同步放在:https://gitee.com/Quehehe/LinuxDeviceDriver.git)
總結(jié)
以上是生活随笔為你收集整理的linux下camera驱动分析_《Linux设备驱动程序》(五)——字符设备驱动(下)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python微信聊天机器人_python
- 下一篇: linux 其他常用命令