字符設備模擬pipe的驅動程序
讓我們用一個”pipe“的設備驅動去結束簡單字符設備吧(這里所說的pipe并非標準的pipe只是模擬了一個從一端寫入從另一端寫入的設備)
測試代碼1?? ? ?測試代碼2
設計思路
用一個圖來說明(可是畫了很久喲)
簡單說來就是一個進程寫入緩沖區,另一個進程可以讀出,讀出后原buffer中的數據被置為無效值,
自定義一個結構
[cpp]?view plaincopy
#define?MAX_SIMPLE_LEN?1024?//buffer?數據長度?? struct?simple_dev{?? ????char?*data;??????????? ????char?*datard;????????? ????char?*datawr;????????? ????char?*dataend;???????? ????wait_queue_head_t?inq;???? ????wait_queue_head_t?outq;??? ????struct?cdev?cdev;????????? ????struct?semaphore?semp;???? };??
申請設備號
?之前有介紹,不再贅述
為自定義結構體分配區內存
[cpp]?view plaincopy
D("alloc?simple_dev?struct?\n");?? char7_dev?=?kmalloc(DEV_COUNT*sizeof(struct?simple_dev),?GFP_KERNEL);?? if(?char7_dev?==?NULL)?? {?? ????printk(KERN_ERR?"kmalloc?simple_dev?err?no?memory");?? ????unregister_chrdev_region(dev,?DEV_COUNT);?? ????return?-ENOMEM;?? }???
使用kmalloc 給設備結構他分配內存,因為系統可能因為暫時不能分配到內存(雖然很少見),所以在分配到內存后需要檢測是否分配成功,在檢測到成功分配內存后還需要將所分配的內存區清零,以免以后出現奇怪的錯誤難以調試(一定要記住)
[cpp]?view plaincopy
memset(char7_dev,?0,?DEV_COUNT*sizeof(struct?simple_dev));??
為緩沖區申請內存
在為自定義結構他申請好內存后,我們需要為每個結構體內的buffer申請內存,使用相同的方法,分配并清零
[cpp]?view plaincopy
?for(?index?=?0?;?index?<?DEV_COUNT?;?++index?)?? ?{?? ??????? ?????if((?char7_dev[index].data?=?kmalloc(MAX_SIMPLE_LEN,?GFP_KERNEL)?)!=?NULL?)?? ?????{?? ?????????memset(char7_dev[index].data,?0,?MAX_SIMPLE_LEN);?? ?????????D("kmalloc?the?data?space?OK!?\n");?? ?????}?? ?????else{?? ????????for(?--index?;?index?>=?0?;?--index?)?? ????????{?? ?????????????kfree(char7_dev[index].data);?? ????????}??? ????????printk(KERN_ERR?"kmalloc?simple_dev?data?number?err?no?memory");?? ????????kfree(char7_dev);?? ????????unregister_chrdev_region(dev,?DEV_COUNT);?? ????????return?-ENOMEM;?? ????}?? }??
這里需要注意的一點是 在申請某個buffer緩沖區失敗時候,需要將已經成功申請的內存釋放掉(else中做了這個工作)
初始化結構體中的各數據指針、信號量、等待隊列頭
在申請好設備及各設備buffer內存后,我們需要對結構中的一些變量進行初始化,
初始化數據指針
將data指向buffer起始位置, datawr,datard 初始化也指向起始位置(表示空buffer),dataend 指向buffer末尾一個無效位置,用于判斷讀寫位置是否合法
初始化信號量以及讀寫隊列頭
[cpp]?view plaincopy
for(?index?=?0?;?index?<?DEV_COUNT?;?++index?)?? ???{??????????????????? ?????????? ???????char7_dev[index].datard?=?char7_dev[index].data;?? ???????char7_dev[index].datawr?=?char7_dev[index].data;?? ???????char7_dev[index].dataend?=?char7_dev[index].data?+?MAX_SIMPLE_LEN;?????????????????????????????????? ?? ????????? ???????sema_init(&(char7_dev[index].semp),?1);?? ???????init_waitqueue_head(&(char7_dev[index].inq));?? ???????init_waitqueue_head(&(char7_dev[index].outq));?? ???}??
初始化字符設備添加字符設備
逐個設備初始化(注冊)并添加(告知內核)
[cpp]?view plaincopy
for(?index?=?0?;?index?<?DEV_COUNT?;?++index?)?? {?? ????cdev_init(&(char7_dev[index].cdev),?&simple_fops);?? ????char7_dev[index].cdev.owner?=?THIS_MODULE;?? ?? ????err?=?cdev_add(&(char7_dev[index].cdev),?dev,?1);?? ????if(err?<?0)?? ????{?? ????????printk(KERN_ERR?"add?cdev?err?\n");?? ????????goto?error1;?? ????}?? ????else?? ????{????? ????????D(?"add?%d?char?dev?OK!\n",?index+1);?? ????}?? ?? }??
字符設備操作
[cpp]?view plaincopy
struct?file_operations?simple_fops={?? ????.owner???=?THIS_MODULE,?? ????.open????=?simple_open,?? ????.release?=?simple_close,?? ????.read????=?simple_read,?? ????.write???=?simple_write,?? ????.llseek??=?simple_llseek,?? ?? ????.poll????=?simple_poll,?? ????.mmap????=?simple_mmap,?? };??
因為2.6.35之后文件操作已經沒有ioctl方法,所以不在介紹了
打開及關閉操作
打開關閉函數于之前的設備驅動沒有差異,故不敘述
讀寫操作
首先獲取信號量(讀寫操作都一樣)
[cpp]?view plaincopy
if(down_interruptible(&dev->semp)?<?0)?? ???{?? ???????printk(KERN_ERR?"[%s]get?the?mutex?lock?error?%d,?%s",?? ???????????????current->comm,?__LINE__,?__func__);?? ???????return?-ERESTARTSYS;?? ???}?? ???else?? ???{?? ???????D("have?get?the?mutex?%d\n",?__LINE__);?? ???}??
讀操作
對于讀操作需要檢測buffer中是否有數據
[cpp]?view plaincopy
?if(dev->datawr?==?dev->datard)??? ?????{???????????????? ???? ????????while(dev->datawr?==?dev->datard)?? ????????{?? ????????????up(&dev->semp);?? ????????????if(filp->f_flags?&?O_NONBLOCK)??? ????????????{?? ????????????????D("set?NONBLOCK?mask?%d\n",?__LINE__);?? ????????????????return?-EAGAIN;?? ????????????}?? ????????????D("[%s]?reading?going?to?sleep!",?current->comm);??? ?????????????? ????????????if(wait_event_interruptible(dev->inq,?dev->datard?!=?dev->datawr))?? ????????????{?? ????????????????return?-ERESTARTSYS;?? ????????????}??????????????? ????????????if(down_interruptible(&dev->semp)?<?0)?? ????????????{?? ????????????????printk(KERN_ERR?"[%s]get?the?mutex?lock?error?%d,?%s",?? ????????????????????????current->comm,?__LINE__,?__func__);?? ????????????????return?-ERESTARTSYS;?? ????????????}?? ????????????else?? ????????????{?? ????????????????D("have?get?the?mutex?%d\n",?__LINE__);?? ????????????}?? ????????}?? ????}??
看到這里你可能已經知道上邊的流程圖有一些錯誤(up 和 down 操作應該在while循環中去做,而不是在整個if 中)由于時間問題上圖就不做修改了
計算buffer 剩余的數據
如果讀指針在寫指針之后(datard > datawr)則buffer中的數據就從讀位置到buffer結尾,又buffer開頭轉到寫位置
[cpp]?view plaincopy
if(dev->datawr?<?dev->datard)?? {?? ????data_remain?=?(dev->dataend?-?dev->datard)?? ????????????????+?(dev->datawr-dev->data);?? }?? else?? {?? ????data_remain?=?dev->datawr?-?dev->datard;?? }??
如果有一些不理解 你可以參照下邊的示意圖,應該很容易就理解上述計算buffer內數據長度的
判斷數據長度的合法性并計算能夠寫入用戶空間的長度
[cpp]?view plaincopy
if(data_remain?<?0)?? {?? ????printk(KERN_ERR?"the?remain?data?we?calculate?is?wrong?check!?%d?\n",?__LINE__);?? }?? else?if(count?>?data_remain)?? {?? ????WAR("the?data?is?less?than?the?user?want?to?read\n");?? ????D("we?can?only?copy?%d?bytes?to?user\n",?data_remain);?? ????count?=?data_remain;?? }?? else?? {?? }??
向用戶空間傳入數據
1、當讀取操作不會讀到buffer尾部時候,直接將數據copy給用戶,調整讀指針, 喚醒睡眠在寫隊列上的進程,釋放信號量,相用戶返回已經讀取的數據長度
[cpp]?view plaincopy
????if((?dev->datawr?>?dev->datard?)?||?(dev->datard?+?count?<=?dev->dataend))?? ????{?? ????????err?=?copy_to_user(userstr,?dev->datard,?count);?? ????????if(err?!=?0)?? ????????{?? ????????????printk(KERN_ERR?"an?error?occured?when?copy?data?to?user:%d\n",?__LINE__);?? ????????????up(&dev->semp);?? ????????????return?err;?? ????????}?? ????????else?? ????????{?? ????????????D("data?copy?to?user?OK\n");?? ????????????dev->datard?=?dev->datard?+?count;?????????????? ????????????if(dev->datard?==?dataend)?? ?????????????????dev->datard?=?dev->data;?? ????????????wake_up_interruptible(&dev->outq);?? ????????????up(&dev->semp);?? ????????????return?count;?? ????????}?? ????}??
2、如果讀到buffer末尾還需要繞回來從數據頭部再讀取
則先讀取read 指針到buffer末尾的數據
然后再從頭部讀取相應長度的數據?
同樣在成功讀取后需要喚醒寫等待隊列, 調整讀指針, 釋放信號量
[cpp]?view plaincopy
????????else?? ????????{?? ????????????data_remain=?(dev->dataend?-dev->datard?);??? ?? ?????????????? ????????????err?=?copy_to_user(userstr,?dev->datard+1,?data_remain);?? ????????????if(err?!=?0)?? ????????????{?? ????????????????printk(KERN_ERR?"an?error?occured?when?copy?data?to?user:%d\n",?__LINE__);?? ????????????????up(&dev->semp);?? ????????????????return?err;?? ????????????}?? ????????????else?? ????????????{?? ????????????????D("data?copy?to?user?OK\n");?? ?????????????? ????????????}????????????? ??????????????? ???????????? ????????????????????? ????????????err?=?copy_to_user(userstr+data_remain,?dev->data,?count-data_remain);?? ????????????if(err?!=?0)?? ????????????{?? ????????????????printk(KERN_ERR?"an?error?occured?when?copy?data?to?user:%d\n",?__LINE__);?? ????????????????up(&dev->semp);?? ????????????????return?err;?? ????????????}?? ????????????else?? ????????????{?? ????????????????D("data?copy?to?user?OK\n");?? ????????????????dev->datard?=?dev->data+(count-data_remain);?? ????????????????wake_up_interruptible(&dev->outq);?? ????????????????up(&dev->semp);?? ????????????????return?count;?? ????????????}?? ????????}??
寫操作
與讀操作類似
獲取即將寫入的位置
檢測buffer是否已經滿需要檢測下即將寫入的地址是否有效(是否已經到了尾部位置),如果數據已經寫到buffer的結尾則需要調整寫
[cpp]?view plaincopy
if?(dev->datawr+1?==?dev->dataend)?? ????next_ptr?=?dev->data;??????????? else?? ????next_ptr?=?dev->datawr?+?1;??
判斷buffer是否已滿
當即將寫入的位置 正好是讀指針指向的位置則表示buffer已滿需要等待讀進程
[cpp]?view plaincopy
if(?next_ptr?==?dev->datard?)?? {?? ????while(next_ptr?==?dev->datard)?? ????{?? ????????up(&dev->semp);?? ????????if(filp->f_flags?&?O_NONBLOCK)?? ????????{?? ????????????D("set?NONBLOCK?mask?%d\n",?__LINE__);?? ????????????return?-EAGAIN;?? ????????}?? ????????D("[%s]?writing?going?to?sleep!",?current->comm);?? ?? ????????if(wait_event_interruptible(dev->outq,?next_ptr?!=?dev->datard))?? ????????{?? ????????????return?-ERESTARTSYS;?? ????????}?? ?? ????????if(down_interruptible(&dev->semp)?<?0)?? ????????{?? ????????????printk(KERN_ERR?"[%s]get?the?mutex?lock?error?%d,?%s",?? ????????????????????current->comm,?__LINE__,?__func__);?? ????????????return?-ERESTARTSYS;?? ????????}?? ????????else?? ????????{?? ????????????D("have?get?the?mutex?%d\n",?__LINE__);?? ????????}?? ????}?? }??
計算buffer剩余長度(可以寫入的數據的長度)
同樣需要分兩種情況,可以參照上圖
[cpp]?view plaincopy
if(dev->datawr?>=?dev->datard)?? {?? ????remain_space?=?(dev->dataend?-?dev->datawr-1)?? ????????????????+?(dev->datard?-?dev->data);?? }?? else?? {?? ????remain_space?=?dev->datard?-?dev->datawr?-?1;?? }??
向buffer寫入數據調整讀寫指針
[cpp]?view plaincopy
????if(?(dev->datawr?<?dev->datard)?||?(dev->datawr?+?count?<?dev->dataend)?)?? ????{?? ????????err?=?copy_from_user(dev->datawr,?userstr,?count);?? ????????if(err?!=?0)?? ????????{?? ????????????printk(KERN_ERR?"error?occured?when?copy?data?from?user?%d\n",?__LINE__);?? ????????????up(&dev->semp);?? ????????????return?err;?? ????????}?? ????????else?? ????????{?? ????????????D("data?copy?from?user?OK\n");?? ????????????dev->datawr?=?dev->datawr?+?count?;?? ????????????wake_up_interruptible(&dev->inq);?? ????????????up(&dev->semp);?? ????????????return?count;?? ????????}?? ????}?? ?? ????else?? ????{?? ????????remain_space?=?dev->dataend?-?dev->datawr?;?? ????????err?=?copy_from_user(dev->datawr,?userstr,?remain_space);?? ????????if(err?!=?0)?? ????????{?? ????????????printk(KERN_ERR?"error?occured?when?copy?data?from?user?%d\n",?__LINE__);?? ????????????up(&dev->semp);?? ????????????return?err;?? ????????}?? ????????else??? ????????{?? ???????????D("copy?part?of?the?data?from?user\n");?? ????????}?? ????????err?=?copy_from_user(dev->data,?userstr+remain_space,?count-remain_space);?? ????????if(err?!=?0)?? ????????{?? ????????????printk(KERN_ERR?"error?occured?when?copy?data?from?user?%d\n",?__LINE__);?? ????????????up(&dev->semp);?? ????????????return?err;?? ???????}?? ???????else{?? ???????????D("data?copy?from?user?OK\n");?? ???????????dev->datawr?=?dev->data?+?(count-remain_space);?? ???????????wake_up_interruptible(&dev->inq);?? ???????????up(&dev->semp);?? ???????????return?count;?? ??????}?? ??}??
poll方法
在等待隊列上調用poll_wait
[cpp]?view plaincopy
poll_wait(filp,?&dev->inq,?wait);?? poll_wait(filp,?&dev->outq,?wait);??
檢測文件是否可讀或者可寫
[cpp]?view plaincopy
if(dev->datard?!=?dev->datawr)?? {?? ????mask?|=?POLLIN?|?POLLRDNORM;????? }?? ?? if(dev->datawr+1?==?dev->dataend)?? ????next_ptr?=?dev->data;?? else?? ????next_ptr?=?dev->datawr+1;?? ?? if(next_ptr?!=?dev->datard)?? {?? ????mask?|=?POLLOUT?|?POLLWRNORM;??? }??
總結
以上是生活随笔為你收集整理的Linux驱动编程 step-by-step (九)字符设备模拟pipe的驱动程序的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。