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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux驱动编程 step-by-step (九)字符设备模拟pipe的驱动程序

發布時間:2024/9/21 linux 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux驱动编程 step-by-step (九)字符设备模拟pipe的驱动程序 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

字符設備模擬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?)??
  • ?{??
  • ?????//char7_dev[index].count?=?0??
  • ?????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?)??
  • ???{???????????????????
  • ???????/*init?the?data?ptr*/???
  • ???????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;??????????????????????????????????
  • ??
  • ???????/*init?semaphore,?waitqueue_head?and?so?on*/??
  • ???????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,??
  • //??.ioctl???=?simple_ioctl,??
  • ????.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)?//empty?buffer??
  • ?????{????????????????
  • ????
  • ????????while(dev->datawr?==?dev->datard)//循環檢測是否buffer中是否已經有數據??
  • ????????{??
  • ????????????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)//wait_wvent_interrupt返回,獲取信號量??
  • ????????????{??
  • ????????????????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?);???
  • ??
  • ????????????/*讀取從當前位置到buffer結尾的數據長度*/??
  • ????????????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");??
  • ????????????//??up(&dev->semp);??
  • ????????????}?????????????
  • ???????????????
  • ????????????
  • ????????????/*從buffer頭部讀取剩余的長度*/?????????
  • ????????????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)//即將寫入的位置是buffer尾部??
  • ????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;???//can?be?read??
  • }??
  • ??
  • if(dev->datawr+1?==?dev->dataend)??
  • ????next_ptr?=?dev->data;??
  • else??
  • ????next_ptr?=?dev->datawr+1;??
  • ??
  • if(next_ptr?!=?dev->datard)??
  • {??
  • ????mask?|=?POLLOUT?|?POLLWRNORM;?//can?be?write??
  • }??
  • 總結

    以上是生活随笔為你收集整理的Linux驱动编程 step-by-step (九)字符设备模拟pipe的驱动程序的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。