在前面學習網絡編程時,曾經學過I/O模型?Linux 系統應用編程——網絡編程(I/O模型),下面學習一下I/O模型在設備驅動中的應用。
? ? ? ?回顧一下在Unix/Linux下共有五種I/O模型,分別是:
a -- 阻塞I/O
b -- 非阻塞I/O
c -- I/O復用(select和poll)
d -- 信號驅動I/O(SIGIO)
e -- 異步I/O(Posix.1的aio_系列函數)
?
? ? ? ?下面我們先學習阻塞I/O、非阻塞I/O?、I/O復用(select和poll),先學習一下基礎概念
a -- 阻塞?
? ? ? ?阻塞操作是指在執行設備操作時,若不能獲得資源,則掛起進程知道滿足可操作的條件后再進行操作;被掛起的進程進入休眠狀態(放棄CPU),被從調度器的運行隊列移走,直到等待的條件被滿足;?
b -- 非阻塞
? ? ? 非阻塞的進程在不能進行設備操作時,并不掛起(繼續占用CPU),它或者放棄,或者不停地查詢,直到可以操作為止;
? ? ? 二者的區別可以看應用程序的調用是否立即返回!
? ? ? 驅動程序通常需要提供這樣的能力:當應用程序進行 read()、write() 等系統調用時,若設備的資源不能獲取,而用戶又希望以阻塞的方式訪問設備,驅動程序應在設備驅動的xxx_read()、xxx_write() 等操作中將進程阻塞直到資源可以獲取,此后,應用程序的?read()、write() 才返回,整個過程仍然進行了正確的設備 訪問,用戶并沒感知到;若用戶以非阻塞的方式訪問設備文件,則當設備資源不可獲取時,設備驅動的?xxx_read()、xxx_write() 等操作立刻返回,?read()、write() 等系統調用也隨即被返回。
? ? ? 因為阻塞的進程會進入休眠狀態,因此,必須確保有一個地方能夠喚醒休眠的進程,否則,進程就真的掛了。喚醒進程的地方最大可能發生在中斷里面,因為硬件資源獲得的同時往往伴隨著一個中斷。
? ? ??阻塞I/O通常由等待隊列來實現,而非阻塞I/O由輪詢來實現。
一、阻塞I/O實現 —— 等待隊列
1、基礎概念
? ? ? ?在Linux 驅動程序中,可以使用等待隊列(wait queue)來實現阻塞進程的喚醒。wait queue 很早就作為一個基本的功能單位出現在Linux 內核里了,它以隊列為基礎數據結構,與進程調度機制緊密結合,能夠實現內核中的異步事件通知機制。等待隊列可以用來同步對系統資源的訪問,上一篇文章所述的信號量在內核中也依賴等待隊列來實現。
? ? ??在Linux內核中使用等待隊列的過程很簡單,首先定義一個wait_queue_head,然后如果一個task想等待某種事件,那么調用wait_event(等待隊列,事件)就可以了。
? ? ? 等待隊列應用廣泛,但是內核實現卻十分簡單。其涉及到兩個比較重要的數據結構:__wait_queue_head,該結構描述了等待隊列的鏈頭,其包含一個鏈表和一個原子鎖,結構定義如下: ? ?
struct __wait_queue_head?
{
spinlock_t lock; ? ? ? ? ? ? ? ? ?
?/* 保護等待隊列的原子鎖 */
struct list_head task_list; ? ? ? ?
/* 等待隊列 */
};
typedef struct __wait_queue_head wait_queue_head_t;
__wait_queue,該結構是對一個等待任務的抽象。每個等待任務都會抽象成一個wait_queue,并且掛載到wait_queue_head上。該結構定義如下:
struct __wait_queue?
{
unsigned int flags;
void *private; ? ? ? ? ? ? ? ? ? ? ?
/* 通常指向當前任務控制塊 */ /* 任務喚醒操作方法,該方法在內核中提供,通常為autoremove_wake_function */
wait_queue_func_t func; ? ? ? ? ? ??
struct list_head task_list; ? ? ? ? ? ?
?/* 掛入wait_queue_head的掛載點 */
};
? ? Linux中等待隊列的實現思想如下圖所示,當一個任務需要在某個wait_queue_head上睡眠時,將自己的進程控制塊信息封裝到wait_queue中,然后掛載到wait_queue的鏈表中,執行調度睡眠。當某些事件發生后,另一個任務(進程)會喚醒wait_queue_head上的某個或者所有任務,喚醒工作也就是將等待隊列中的任務設置為可調度的狀態,并且從隊列中刪除。
? ? ? ?使用等待隊列時首先需要定義一個wait_queue_head,這可以通過DECLARE_WAIT_QUEUE_HEAD宏來完成,這是靜態定義的方法。該宏會定義一個wait_queue_head,并且初始化結構中的鎖以及等待隊列。當然,動態初始化的方法也很簡單,初始化一下鎖及隊列就可以了。
? ? ? ?一個任務需要等待某一事件的發生時,通常調用wait_event,該函數會定義一個wait_queue,描述等待任務,并且用當前的進程描述塊初始化wait_queue,然后將wait_queue加入到wait_queue_head中。
函數實現流程說明如下:
a -- 用當前的進程描述塊(PCB)初始化一個wait_queue描述的等待任務。
b -- 在等待隊列鎖資源的保護下,將等待任務加入等待隊列。
c -- 判斷等待條件是否滿足,如果滿足,那么將等待任務從隊列中移出,退出函數。
d -- ?如果條件不滿足,那么任務調度,將CPU資源交與其它任務。
e -- 當睡眠任務被喚醒之后,需要重復b、c 步驟,如果確認條件滿足,退出等待事件函數。
2、等待隊列接口函數
1、定義并初始化
/* 定義“等待隊列頭” */ wait_queue_head_t my_queue;
/* 初始化“等待隊列頭”*/
init_waitqueue_head(&my_queue);
直接定義并初始化。init_waitqueue_head()函數會將自旋鎖初始化為未鎖,等待隊列初始化為空的雙向循環鏈表。
DECLARE_WAIT_QUEUE_HEAD(my_queue);?定義并初始化,可以作為定義并初始化等待隊列頭的快捷方式。
2、定義等待隊列:
DECLARE_WAITQUEUE(name,tsk);
定義并初始化一個名為name的等待隊列。
3、(從等待隊列頭中)添加/移出等待隊列:
/* add_wait_queue()函數,設置等待的進程為非互斥進程,并將其添加進等待隊列頭(q)的隊頭中*/
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
/* 該函數也和add_wait_queue()函數功能基本一樣,只不過它是將等待的進程(wait)設置為互斥進程。*/
void add_wait_queue_exclusive(wait_queue_head_t *q, wait_queue_t *wait);
4、等待事件:
(1)wait_event()宏:
[cpp]?view plaincopy
? ? ? ? ? ? ? ? ? ? ? ?? ?? #define?wait_event(wq,?condition)???????????????????\?? ?? do?{????????????????????????????????????\?? ????if?(condition)??????????????????????????\?? ????????break;??????????????????????????\?? ?????__wait_event(wq,?condition);????????????????????\?? }?while?(0)??
? ? 在等待會列中睡眠直到condition為真。在等待的期間,進程會被置為TASK_UNINTERRUPTIBLE進入睡眠,直到condition變量變為真。每次進程被喚醒的時候都會檢查condition的值.
(2)wait_event_interruptible()函數:
? ?和wait_event()的區別是調用該宏在等待的過程中當前進程會被設置為TASK_INTERRUPTIBLE狀態.在每次被喚醒的時候,首先檢查condition是否為真,如果為真則返回,否則檢查如果進程是被信號喚醒,會返回-ERESTARTSYS錯誤碼.如果是condition為真,則返回0.
(3)wait_event_timeout()宏:
? ?也與wait_event()類似.不過如果所給的睡眠時間為負數則立即返回.如果在睡眠期間被喚醒,且condition為真則返回剩余的睡眠時間,否則繼續睡眠直到到達或超過給定的睡眠時間,然后返回0
(4)wait_event_interruptible_timeout()宏:
? ?與wait_event_timeout()類似,不過如果在睡眠期間被信號打斷則返回ERESTARTSYS錯誤碼.
(5) wait_event_interruptible_exclusive()宏
? ?同樣和wait_event_interruptible()一樣,不過該睡眠的進程是一個互斥進程.
5、喚醒隊列
(1)wake_up()函數
[cpp]?view plaincopy
#define?wake_up(x)??????????__wake_up(x,?TASK_NORMAL,?1,?NULL)?? ? ? ? ? ? ? ?? void?__wake_up(wait_queue_head_t?*q,?unsigned?int?mode,?? ????????????int?nr_exclusive,?void?*key)?? {?? ????unsigned?long?flags;?? ??? ????spin_lock_irqsave(&q->lock,?flags);?? ????__wake_up_common(q,?mode,?nr_exclusive,?0,?key);?? ????spin_unlock_irqrestore(&q->lock,?flags);?? }?? EXPORT_SYMBOL(__wake_up);??
喚醒等待隊列.可喚醒處于TASK_INTERRUPTIBLE和TASK_UNINTERUPTIBLE狀態的進程,和wait_event/wait_event_timeout成對使用.
(2)wake_up_interruptible()函數:
#define wake_up_interruptible(x) ? ?__wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
和wake_up()唯一的區別是它只能喚醒TASK_INTERRUPTIBLE狀態的進程.,與wait_event_interruptible/wait_event_interruptible_timeout/ wait_event_interruptible_exclusive成對使用。
下面看一個實例:
[cpp]?view plaincopy
static?ssize_t?hello_read(struct?file?*filep,?char?__user?*buf,?size_t?len,?loff_t?*pos)?? {?? ? ? ?? ????if(len>64)?? ????{?? ????????len?=64;?? ????}?? ????wait_event_interruptible(wq,?have_data?==?1);?? ?? ????if(copy_to_user(buf,temp,len))?? ????{?? ????????return?-EFAULT;?? ????}?????? ????have_data?=?0;?? ????return?len;?? }?? static?ssize_t?hello_write(struct?file?*filep,?const?char?__user?*buf,?size_t?len,?loff_t?*pos)?? {?? ????if(len?>?64)?? ????{?? ????????len?=?64;?? ????}?? ?? ????if(copy_from_user(temp,buf,len))?? ????{?? ????????return?-EFAULT;?? ????}?? ????printk("write?%s\n",temp);?? ????have_data?=?1;?? ????wake_up_interruptible(&wq);?? ????return?len;?? }??
注意兩個概念:
a -- ?瘋狂獸群
? ? ??wake_up的時候,所有阻塞在隊列的進程都會被喚醒,但是因為condition的限制,只有一個進程得到資源,其他進程又會再次休眠,如果數量很大,稱為?瘋狂獸群。
b -- 獨占等待
? ? ? 等待隊列的入口設置一個WQ_FLAG_EXCLUSIVE標志,就會添加到等待隊列的尾部,沒有設置設置的添加到頭部,wake up的時候遇到第一個具有WQ_FLAG_EXCLUSIVE這個標志的進程就停止喚醒其他進程。
二、非阻塞I/O實現方式 —— 多路復用
1、輪詢的概念和作用
? ? ??在用戶程序中,select()?和?poll()?也是設備阻塞和非阻塞訪問息息相關的論題。使用非阻塞I/O的應用程序通常會使用select() 和 poll() 系統調用查詢是否可對設備進行無阻塞的訪問。select() 和 poll() 系統調用最終會引發設備驅動中的 poll()函數被執行。
2、應用程序中的輪詢編程
? ? ? 在用戶程序中,select()和poll()本質上是一樣的, 不同只是引入的方式不同,前者是在BSD UNIX中引入的,后者是在System V中引入的。用的比較廣泛的是select系統調用。原型如下:
int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptionfds, struct timeval *timeout);
??? 其中readfs,writefds,exceptfds分別是select()監視的讀,寫和異常處理的文件描述符集合,numfds的值是需要檢查的號碼最高的文件描述符加1,timeout則是一個時間上限值,超過該值后,即使仍沒有描述符準備好也會返回。
struct timeval
{int tv_sec; ? //秒int tv_usec; ? //微秒
}
涉及到文件描述符集合的操作主要有以下幾種:
1)清除一個文件描述符集 ? FD_ZERO(fd_set *set);
2)將一個文件描述符加入文件描述符集中 ? ?FD_SET(int fd,fd_set *set);
3)將一個文件描述符從文件描述符集中清除 ?FD_CLR(int fd,fd_set *set);
4)判斷文件描述符是否被置位 ? ?FD_ISSET(int fd,fd_set *set);
最后我們利用上面的文件描述符集的相關來寫個驗證添加了設備輪詢的驅動,把上邊兩塊聯系起來
3、設備驅動中的輪詢編程
? ? ? ?設備驅動中的poll() 函數原型如下
unsigned int(*poll)(struct file *filp, struct poll_table * wait);
第一個參數是file結構體指針,第二個參數是輪詢表指針,poll設備方法完成兩件事:
a -- 對可能引起設備文件狀態變化的等待隊列調用poll_wait()函數,將對應的等待隊列頭添加到poll_table,如果沒有文件描述符可用來執行 I/O, 則內核使進程在傳遞到該系統調用的所有文件描述符對應的等待隊列上等待。
b -- 返回表示是否能對設備進行無阻塞讀、寫訪問的掩碼。
位掩碼:POLLRDNORM, POLLIN,POLLOUT,POLLWRNORM
設備可讀,通常返回:(POLLIN | POLLRDNORM)
設備可寫,通常返回:(POLLOUT | POLLWRNORM)
? ? ???
poll_wait()函數:用于向 poll_table注冊等待隊列
?void poll_wait(struct file *filp, wait_queue_head_t *queue,poll_table *wait)
?
? ? ??poll_wait()函數不會引起阻塞,它所做的工作是把當前進程添加到wait 參數指定的等待列表(poll_table)中。
? ? ?真正的阻塞動作是上層的select/poll函數中完成的。select/poll會在一個循環中對每個需要監聽的設備調用它們自己的poll支持函數以使得當前進程被加入各個設備的等待列表。若當前沒有任何被監聽的設備就緒,則內核進行調度(調用schedule)讓出cpu進入阻塞狀態,schedule返回時將再次循環檢測是否有操作可以進行,如此反復;否則,若有任意一個設備就緒,select/poll都立即返回。
具體過程如下:
a --?用戶程序第一次調用select或者poll,驅動調用poll_wait并使兩條隊列都加入poll_table結構中作為下次調用驅動函數poll的條件,一個mask返回值指示設備是否可操作,0為未準備狀態,如果文件描述符未準備好可讀或可寫,用戶進程被會加入到寫或讀等待隊列中進入睡眠狀態。
b --?當驅動執行了某些操作,例如,寫緩沖或讀緩沖,寫緩沖使讀隊列被喚醒,讀緩沖使寫隊列被喚醒,于是select或者poll系統調用在將要返回給用戶進程時再次調用驅動函數poll,驅動依然調用poll_wait 并使兩條隊列都加入poll_table結構中,并判斷可寫或可讀條件是否滿足,如果mask返回POLLIN | POLLRDNORM或POLLOUT | POLLWRNORM則指示可讀或可寫,這時select或poll真正返回給用戶進程,如果mask還是返回0,則系統調用select或poll繼續不返回
? ? ?
下面是一個典型模板:
[cpp]?view plaincopy
static?unsigned?int?XXX_poll(struct?file?*filp,?poll_table?*wait)?? {?? ????unsigned?int?mask?=?0;?? ????????struct?XXX_dev?*dev?=?filp->private_data;??????? ????...?? ????poll_wait(filp,?&dev->r_wait,?wait);?????? ????poll_wait(filp?,&dev->w_wait,?wait);?????? ?????? ????if(...)?? ????{?? ??????????mask?|=?POLLIN?|?POLLRDNORM;?????? ?????}?? ????if(...)?? ????{?? ??????????mask?|=?POLLOUT?|?POLLWRNORM;?????? ?????}?? ????..?? ????return?mask;?? }??
4、調用過程:
Linux下select調用的過程:
1、用戶層應用程序調用select(),底層調用poll())
2、核心層調用sys_select() ------> do_select()
最終調用文件描述符fd對應的struct file類型變量的struct file_operations *f_op的poll函數。
poll指向的函數返回當前可否讀寫的信息。
1)如果當前可讀寫,返回讀寫信息。
2)如果當前不可讀寫,則阻塞進程,并等待驅動程序喚醒,重新調用poll函數,或超時返回。
3、驅動需要實現poll函數
當驅動發現有數據可以讀寫時,通知核心層,核心層重新調用poll指向的函數查詢信息。
poll_wait(filp,&wait_q,wait)
// 此處將當前進程加入到等待隊列中,但并不阻塞
在中斷中使用wake_up_interruptible(&wait_q)喚醒等待隊列。
4、實例分析
1、memdev.h
/*mem設備描述結構體*/
struct mem_dev
{
char *data; unsigned
long size; wait_queue_head_t inq;
};
#endif /* _MEMDEV_H_ */
2、驅動程序 memdev.c
[cpp]?view plaincopy
#include?<linux/module.h>?? #include?<linux/types.h>?? #include?<linux/fs.h>?? #include?<linux/errno.h>?? #include?<linux/mm.h>?? #include?<linux/sched.h>?? #include?<linux/init.h>?? #include?<linux/cdev.h>?? #include?<asm/io.h>?? #include?<asm/system.h>?? #include?<asm/uaccess.h>?? ?? #include?<linux/poll.h>?? #include?"memdev.h"?? ?? static?mem_major?=?MEMDEV_MAJOR;?? bool?have_data?=?false;??? ?? module_param(mem_major,?int,?S_IRUGO);?? ?? struct?mem_dev?*mem_devp;??? ?? struct?cdev?cdev;??? ?? ?? int?mem_open(struct?inode?*inode,?struct?file?*filp)?? {?? ????struct?mem_dev?*dev;?? ?????? ?????? ????int?num?=?MINOR(inode->i_rdev);?? ?? ????if?(num?>=?MEMDEV_NR_DEVS)??? ????????????return?-ENODEV;?? ????dev?=?&mem_devp[num];?? ?????? ?????? ????filp->private_data?=?dev;?? ?????? ????return?0;??? }?? ?? ?? int?mem_release(struct?inode?*inode,?struct?file?*filp)?? {?? ??return?0;?? }?? ?? ?? static?ssize_t?mem_read(struct?file?*filp,?char?__user?*buf,?size_t?size,?loff_t?*ppos)?? {?? ??unsigned?long?p?=??*ppos;?? ??unsigned?int?count?=?size;?? ??int?ret?=?0;?? ??struct?mem_dev?*dev?=?filp->private_data;??? ?? ???? ??if?(p?>=?MEMDEV_SIZE)?? ????return?0;?? ??if?(count?>?MEMDEV_SIZE?-?p)?? ????count?=?MEMDEV_SIZE?-?p;?? ?????? ??while?(!have_data)??? ??{?? ????????if?(filp->f_flags?&?O_NONBLOCK)?? ????????????return?-EAGAIN;?? ?????? ????wait_event_interruptible(dev->inq,have_data);?? ??}?? ?? ???? ??if?(copy_to_user(buf,?(void*)(dev->data?+?p),?count))?? ??{?? ????ret?=??-?EFAULT;?? ??}?? ??else?? ??{?? ????*ppos?+=?count;?? ????ret?=?count;?? ????? ????printk(KERN_INFO?"read?%d?bytes(s)?from?%d\n",?count,?p);?? ??}?? ???? ??have_data?=?false;??? ???? ??return?ret;?? }?? ?? ?? static?ssize_t?mem_write(struct?file?*filp,?const?char?__user?*buf,?size_t?size,?loff_t?*ppos)?? {?? ??unsigned?long?p?=??*ppos;?? ??unsigned?int?count?=?size;?? ??int?ret?=?0;?? ??struct?mem_dev?*dev?=?filp->private_data;??? ???? ???? ??if?(p?>=?MEMDEV_SIZE)?? ????return?0;?? ??if?(count?>?MEMDEV_SIZE?-?p)?? ????count?=?MEMDEV_SIZE?-?p;?? ?? ???? ??if?(copy_from_user(dev->data?+?p,?buf,?count))?? ????ret?=??-?EFAULT;?? ??else?? ??{?? ????*ppos?+=?count;?? ????ret?=?count;?? ?????? ????printk(KERN_INFO?"written?%d?bytes(s)?from?%d\n",?count,?p);?? ??}?? ???? ??have_data?=?true;??? ?????? ?????? ????wake_up(&(dev->inq));?? ?? ??return?ret;?? }?? ?? ?? static?loff_t?mem_llseek(struct?file?*filp,?loff_t?offset,?int?whence)?? {??? ????loff_t?newpos;?? ?? ????switch(whence)?{?? ??????case?0:??? ????????newpos?=?offset;?? ????????break;?? ?? ??????case?1:??? ????????newpos?=?filp->f_pos?+?offset;?? ????????break;?? ?? ??????case?2:??? ????????newpos?=?MEMDEV_SIZE?-1?+?offset;?? ????????break;?? ?? ??????default:??? ????????return?-EINVAL;?? ????}?? ????if?((newpos<0)?||?(newpos>MEMDEV_SIZE))?? ????????return?-EINVAL;?? ?????????? ????filp->f_pos?=?newpos;?? ????return?newpos;?? ?? }?? unsigned?int?mem_poll(struct?file?*filp,?poll_table?*wait)?? {?? ????struct?mem_dev??*dev?=?filp->private_data;??? ????unsigned?int?mask?=?0;?? ?????? ????? ????poll_wait(filp,?&dev->inq,??wait);?? ??? ?????? ????if?(have_data)?????????mask?|=?POLLIN?|?POLLRDNORM;???? ?? ????return?mask;?? }?? ?? ?? ?? static?const?struct?file_operations?mem_fops?=?? {?? ??.owner?=?THIS_MODULE,?? ??.llseek?=?mem_llseek,?? ??.read?=?mem_read,?? ??.write?=?mem_write,?? ??.open?=?mem_open,?? ??.release?=?mem_release,?? ??.poll?=?mem_poll,?? };?? ?? ?? static?int?memdev_init(void)?? {?? ??int?result;?? ??int?i;?? ?? ??dev_t?devno?=?MKDEV(mem_major,?0);?? ?? ???? ??if?(mem_major)?? ????result?=?register_chrdev_region(devno,?2,?"memdev");?? ??else???? ??{?? ????result?=?alloc_chrdev_region(&devno,?0,?2,?"memdev");?? ????mem_major?=?MAJOR(devno);?? ??}???? ???? ??if?(result?<?0)?? ????return?result;?? ?? ???? ??cdev_init(&cdev,?&mem_fops);?? ??cdev.owner?=?THIS_MODULE;?? ??cdev.ops?=?&mem_fops;?? ???? ???? ??cdev_add(&cdev,?MKDEV(mem_major,?0),?MEMDEV_NR_DEVS);?? ????? ???? ??mem_devp?=?kmalloc(MEMDEV_NR_DEVS?*?sizeof(struct?mem_dev),?GFP_KERNEL);?? ??if?(!mem_devp)?????? ??{?? ????result?=??-?ENOMEM;?? ????goto?fail_malloc;?? ??}?? ??memset(mem_devp,?0,?sizeof(struct?mem_dev));?? ???? ???? ??for?(i=0;?i?<?MEMDEV_NR_DEVS;?i++)??? ??{?? ????????mem_devp[i].size?=?MEMDEV_SIZE;?? ????????mem_devp[i].data?=?kmalloc(MEMDEV_SIZE,?GFP_KERNEL);?? ????????memset(mem_devp[i].data,?0,?MEMDEV_SIZE);?? ???? ???????? ?????init_waitqueue_head(&(mem_devp[i].inq));?? ??????? ??}?? ????? ??return?0;?? ?? ??fail_malloc:??? ??unregister_chrdev_region(devno,?1);?? ???? ??return?result;?? }?? ?? ?? static?void?memdev_exit(void)?? {?? ??cdev_del(&cdev);????? ??kfree(mem_devp);??????? ??unregister_chrdev_region(MKDEV(mem_major,?0),?2);??? }?? ?? MODULE_AUTHOR("David?Xie");?? MODULE_LICENSE("GPL");?? ?? module_init(memdev_init);?? module_exit(memdev_exit);??
3、應用程序 app-write.c
[cpp]?view plaincopy
#include?<stdio.h>?? ?? int?main()?? {?? ????FILE?*fp?=?NULL;?? ????char?Buf[128];?? ?????? ?????? ?????? ????fp?=?fopen("/dev/memdev0","r+");?? ????if?(fp?==?NULL)?? ????{?? ????????printf("Open?Dev?memdev?Error!\n");?? ????????return?-1;?? ????}?? ?????? ?????? ????strcpy(Buf,"memdev?is?char?dev!");?? ????printf("Write?BUF:?%s\n",Buf);?? ????fwrite(Buf,?sizeof(Buf),?1,?fp);?? ?????? ????sleep(5);?? ????fclose(fp);?? ?????? ????return?0;?????? ?? }??
4、應用程序 app-read.c
[cpp]?view plaincopy
#include?<stdio.h>?? #include?<stdlib.h>?? #include?<unistd.h>?? #include?<sys/ioctl.h>?? #include?<sys/types.h>?? #include?<sys/stat.h>?? #include?<fcntl.h>?? #include?<sys/select.h>?? #include?<sys/time.h>?? #include?<errno.h>?? ?? int?main()?? {?? ????int?fd;?? ????fd_set?rds;?? ????int?ret;?? ????char?Buf[128];?? ?????? ?????? ????strcpy(Buf,"memdev?is?char?dev!");?? ????printf("BUF:?%s\n",Buf);?? ?????? ?????? ????fd?=?open("/dev/memdev0",O_RDWR);?? ?????? ????FD_ZERO(&rds);?? ????FD_SET(fd,?&rds);?? ?? ?????? ????strcpy(Buf,"Buf?is?NULL!");?? ????printf("Read?BUF1:?%s\n",Buf);?? ?? ????ret?=?select(fd?+?1,?&rds,?NULL,?NULL,?NULL);?? ????if?(ret?<?0)??? ????{?? ????????printf("select?error!\n");?? ????????exit(1);?? ????}?? ????if?(FD_ISSET(fd,?&rds))??? ????????read(fd,?Buf,?sizeof(Buf));?????????????? ?????? ?????? ????printf("Read?BUF2:?%s\n",Buf);?? ?????? ????close(fd);?? ?????? ????return?0;?????? }?
總結
以上是生活随笔為你收集整理的Linux 设备驱动中的 I/O模型(一)—— 阻塞和非阻塞I/O的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。