Linux設備驅動中的阻塞和非阻塞I/O

【基本概念】node

一、阻塞linux

  阻塞操做是指在執行設備操做時,託不能得到資源,則掛起進程直到知足操做所需的條件後再進行操做。被掛起的進程進入休眠狀態(不佔用cpu資源),從調度器的運行隊列轉移到等待隊列,直到條件知足。express

二、非阻塞編程

  非阻塞操做是指在進行設備操做是,若操做條件不知足並不會掛起,而是直接返回或從新查詢(一直佔用CPU資源)直到操做條件知足爲止。數據結構

  當用戶空間的應用程序調用read(),write()等方法時,若設備的資源不能被獲取,而用戶又但願以阻塞的方式來訪問設備,驅動程序應當在設備驅動層的對應 read(),write()操做中,將該進程阻塞直到資源能夠獲取爲止;若用戶是以非阻塞方式獲取資源,當資源不能獲取時設備驅動的read()、write()應當當即返回,用戶空間的read()、write()也相應的當即返回。app

   阻塞從字面上聽起來彷佛意味着效率低,其實不是這樣。若是以非阻塞方式,用戶想獲取某一資源只能不停地查詢,這樣會佔用CPU大量資源。而阻塞訪問,若不能獲取資源就會進入休眠從而節省CPU資源給其餘進程使用。異步

  很顯然阻塞的進程會進入到休眠狀態,所以必須保證有一個地方可以喚醒休眠的進程。喚醒進程的地方通常都在中斷裏面,意味硬件資源的得到同時每每伴隨着一箇中斷。ide

  在設備驅動中,阻塞的實現一般是經過等待隊列。函數

1、阻塞IO實現(等待隊列)

  在Linux驅動程序中,可使用等待隊列(wait queue)來實現阻塞進程的喚醒。wait queue在很早就做爲一個基本的功能出如今Linux內核裏了,它是一種以隊列爲基礎的數據結構,與進程調度機制緊密結合,可以用於實現內核中的異步事件通知機制。等待隊列能夠用來同步對系統資源的訪問,信號量在內核中也依賴等待隊列來實現。atom

  在Linux設備驅動中等待隊列實現阻塞通常方式是:

  1. 定義一個等待隊列頭 wait_queue_head_t  wq_h;
  2. 初始化等待隊列頭
  3. 當有操做要以阻塞方式訪問資源時,調用 wait_event()加入到等待隊列中便可
  4. 在條件知足時調用wake_up()喚醒等待隊列 

涉及到兩個比較重要的數據結構:__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 步驟,若是確認條件知足,退出等待事件函數。

等待隊列的操做接口函數

一、定義和初始化「等待隊列頭」

wait_queue_head_t my_queue;  /* 定義等待隊列頭 */
init_waitqueue_head(&my_queue);  /* 初始化等待隊列頭 */

 也可使用Linux內核中的宏來同時完成「等待隊列頭」的定義和初始化

DECLARE_WAIT_QUEUE_HEAD(my_queue);

二、定義等待隊列:

DECLARE_WAITQUEUE(name,tsk);  /* 定義並初始化一個名爲name的等待隊列。*/

三、(從等待隊列頭中)添加/移出等待隊列: 

/* 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);

四、等待事件:

(1)wait_event(queue, condition)宏:

/** 
 * wait_event - sleep until a condition gets true 
 * @wq: the waitqueue to wait on 
 * @condition: a C expression for the event to wait for 
 * 
 * The process is put to sleep (TASK_UNINTERRUPTIBLE) until the 
 * @condition evaluates to true. The @condition is checked each time 
 * the waitqueue @wq is woken up. 
 * 
 * wake_up() has to be called after changing any variable that could 
 * change the result of the wait condition. 
 */  
#define wait_event(wq, condition)                   \  
do {                                    \  
  if (condition)                          \  
  break;                          \  
  __wait_event(wq, condition);                    \  
} while (0)  

    在等待會列中睡眠直到condition爲真。在等待的期間,進程會被置爲TASK_UNINTERRUPTIBLE進入睡眠,直到condition變量變爲真。每次進程被喚醒的時候都會檢查condition的值.

(2)wait_event_interruptible(queue, condition)函數:

   和wait_event()的區別是調用該宏在等待的過程當中當前進程會被設置爲TASK_INTERRUPTIBLE狀態.在每次被喚醒的時候,首先檢查condition是否爲真,若是爲真則返回,不然檢查若是進程是被信號喚醒,會返回-ERESTARTSYS錯誤碼.若是是condition爲真,則返回0.

(3)wait_event_timeout(queue, condition, timeout)宏:

   也與wait_event()相似.不過若是所給的睡眠時間爲負數則當即返回.若是在睡眠期間被喚醒,且condition爲真則返回剩餘的睡眠時間,不然繼續睡眠直到到達或超過給定的睡眠時間,而後返回0
(4)wait_event_interruptible_timeout(wq, condition, timeout)宏:
   與wait_event_timeout()相似,不過若是在睡眠期間被信號打斷則返回ERESTARTSYS錯誤碼.
(5) wait_event_interruptible_exclusive(wq, condition)宏

五、喚醒隊列

(1)wake_up(x)函數 

#define wake_up(x)          __wake_up(x, TASK_NORMAL, 1, NULL)  
/** 
 * __wake_up - wake up threads blocked on a waitqueue. 
 * @q: the waitqueue 
 * @mode: which threads 
 * @nr_exclusive: how many wake-one or wake-many threads to wake up 
 * @key: is directly passed to the wakeup function 
 */  
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成對使用。

下面看一個實例:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/errno.h>

#include <asm/current.h>
#include <linux/sched.h>

#include <linux/uaccess.h>

#include <asm/atomic.h>
#include <linux/mutex.h>

#include <linux/wait.h>

#include <linux/device.h>
static struct class *cls = NULL;

static int major = 0;
static int minor = 0;
const  int count = 6;

#define DEVNAME    "demo"

static struct cdev *demop = NULL;
static atomic_t tv;
//等待隊列頭
static wait_queue_head_t wq;

static int counter = 0;

//打開設備
static int demo_open(struct inode *inode, struct file *filp)
{
    //get major and minor from inode
    printk(KERN_INFO "(major=%d, minor=%d), %s : %s : %d\n",
        imajor(inode), iminor(inode), __FILE__, __func__, __LINE__);

    if(!atomic_dec_and_test(&tv)){
        atomic_inc(&tv);
        return -EBUSY;
    }

    return 0;
}

//關閉設備
static int demo_release(struct inode *inode, struct file *filp)
{
    //get major and minor from inode
    printk(KERN_INFO "(major=%d, minor=%d), %s : %s : %d\n",
        imajor(inode), iminor(inode), __FILE__, __func__, __LINE__);

    atomic_inc(&tv);
    return 0;
}

//讀設備
//ssize_t read(int fd, void *buf, size_t count)
static ssize_t demo_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
    int err = 0;
    struct inode *inode = filp->f_path.dentry->d_inode;
    //get major and minor from inode
    printk(KERN_INFO "(major=%d, minor=%d), %s : %s : %d\n",
        imajor(inode), iminor(inode), __FILE__, __func__, __LINE__);

    if(!counter){
        if(filp->f_flags & O_NONBLOCK){
            return -EAGAIN;
        }

        err = wait_event_interruptible(wq, (0 != counter));
        if(err){
            return err;
        }
    }

    //...

    counter = 0;
    return 0;
}

//寫設備
static ssize_t demo_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
    struct inode *inode = filp->f_path.dentry->d_inode;
    //get major and minor from inode
    printk(KERN_INFO "(major=%d, minor=%d), %s : %s : %d\n",
        imajor(inode), iminor(inode), __FILE__, __func__, __LINE__);

    counter = 10;
    wake_up_interruptible(&wq);

    return 0;
}

static struct file_operations fops = {
    .owner    = THIS_MODULE,
    .open    = demo_open,
    .release= demo_release,
    .read    = demo_read,
    .write    = demo_write,
};

static int __init demo_init(void)
{
    dev_t devnum;
    int ret, i;

    struct device *devp = NULL;

    //get command and pid
    printk(KERN_INFO "(%s:pid=%d), %s : %s : %d\n",
        current->comm, current->pid, __FILE__, __func__, __LINE__);

    //1. alloc cdev obj    動態分配 字符設備結構體空間
    demop = cdev_alloc();
    if(NULL == demop){
        return -ENOMEM;
    }

    //2. init cdev obj    初始化字符設備,並註冊操做方法集
    cdev_init(demop, &fops);

    //動態分配設備號
    ret = alloc_chrdev_region(&devnum, minor, count, DEVNAME);
    if(ret){
        goto ERR_STEP;
    }
    major = MAJOR(devnum);

    //3. register cdev obj
    ret = cdev_add(demop, devnum, count);
    if(ret){
        goto ERR_STEP1;
    }

    cls = class_create(THIS_MODULE, DEVNAME);
    if(IS_ERR(cls)){
        ret = PTR_ERR(cls);
        goto ERR_STEP1;
    }

    for(i = minor; i < (count+minor); i++){
        devp = device_create(cls, NULL, MKDEV(major, i), NULL, "%s%d", DEVNAME, i);
        if(IS_ERR(devp)){
            ret = PTR_ERR(devp);
            goto ERR_STEP2;
        }
    }

    // init atomic_t    初始化原子量
    atomic_set(&tv, 1);

    init_waitqueue_head(&wq);        //初始化等待隊列

    //get command and pid
    printk(KERN_INFO "(%s:pid=%d), %s : %s : %d - ok.\n",
        current->comm, current->pid, __FILE__, __func__, __LINE__);
    return 0;

ERR_STEP2:
    for(--i; i >= minor; i--){
        device_destroy(cls, MKDEV(major, i));
    }
    class_destroy(cls);

ERR_STEP1:
    unregister_chrdev_region(devnum, count);

ERR_STEP:
    cdev_del(demop);

    //get command and pid
    printk(KERN_INFO "(%s:pid=%d), %s : %s : %d - fail.\n",
        current->comm, current->pid, __FILE__, __func__, __LINE__);
    return ret;
}

static void __exit demo_exit(void)
{
    int i;
    //get command and pid
    printk(KERN_INFO "(%s:pid=%d), %s : %s : %d - leave.\n",
        current->comm, current->pid, __FILE__, __func__, __LINE__);

    for(i=minor; i < (count+minor); i++){
        device_destroy(cls, MKDEV(major, i));
    }
    class_destroy(cls);

    unregister_chrdev_region(MKDEV(major, minor), count);
    cdev_del(demop);
}

module_init(demo_init);
module_exit(demo_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Farsight");
MODULE_DESCRIPTION("Demo for kernel module");
工做隊列使用範例

注意兩個概念:

a --  瘋狂獸羣

      wake_up的時候,全部阻塞在隊列的進程都會被喚醒,可是由於condition的限制,只有一個進程獲得資源,其餘進程又會再次休眠,若是數量很大,稱爲 瘋狂獸羣

b -- 獨佔等待

      等待隊列的入口設置一個WQ_FLAG_EXCLUSIVE標誌,就會添加到等待隊列的尾部,沒有設置設置的添加到頭部,wake up的時候遇到第一個具備WQ_FLAG_EXCLUSIVE這個標誌的進程就中止喚醒其餘進程。

2、非阻塞I/O實現方式 —— 多路複用

一、輪詢的概念和做用

      在用戶程序中,select() 和 poll() 也是設備阻塞和非阻塞訪問息息相關的論題。使用非阻塞I/O的應用程序一般會使用select() 和 poll() 系統調用查詢是否可對設備進行無阻塞的訪問。select() 和 poll() 系統調用最終會引起設備驅動中的 poll()函數被執行。 

二、應用程序中的輪詢編程

      在用戶程序中,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);

最後咱們利用上面的文件描述符集的相關來寫個驗證添加了設備輪詢的驅動,把上邊兩塊聯繫起來 

三、設備驅動中的輪詢編程

       設備驅動中的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繼續不返回     

下面是一個典型模板: 

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(...)//可讀
    { 
       POLLIN | POLLRDNORM;    //標識數據可得到  
    }  
    if(...)//可寫  
    {
          mask |= POLLOUT | POLLWRNORM;    //標識數據可寫入  
    }  
    ..  
    return mask;  
}          

四、調用過程:

Linux下select調用的過程:

一、用戶層應用程序調用select(),底層調用poll())
二、核心層調用sys_select() ------> do_select()

  最終調用文件描述符fd對應的struct file類型變量的struct file_operations *f_op的poll函數。
  poll指向的函數返回當前能否讀寫的信息。
  1)若是當前可讀寫,返回讀寫信息。
  2)若是當前不可讀寫,則阻塞進程,並等待驅動程序喚醒,從新調用poll函數,或超時返回。

三、驅動須要實現poll函數
當驅動發現有數據能夠讀寫時,通知核心層,核心層從新調用poll指向的函數查詢信息。

poll_wait(filp,&wait_q,wait) // 此處將當前進程加入到等待隊列中,但並不阻塞

在中斷中使用wake_up_interruptible(&wait_q)喚醒等待隊列。
四、實例分析

一、memdev.h 

/*mem設備描述結構體*/
struct mem_dev                                     
{                                                        
  char *data;                      
  unsigned long size; 
  wait_queue_head_t inq;  
};

#endif /* _MEMDEV_H_ */

二、驅動程序 memdev.c

#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,而用while */  
  {  
        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;  
}  
  
/* seek文件定位函數 */  
static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)  
{   
    loff_t newpos;  
  
    switch(whence) {  
      case 0: /* SEEK_SET */  
        newpos = offset;  
        break;  
  
      case 1: /* SEEK_CUR */  
        newpos = filp->f_pos + offset;  
        break;  
  
      case 2: /* SEEK_END */  
        newpos = MEMDEV_SIZE -1 + offset;  
        break;  
  
      default: /* can't happen */  
        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_table */  
    poll_wait(filp, &dev->inq,  wait);  
   
      
    if (have_data)         mask |= POLLIN | POLLRDNORM;  /* readable */  
  
    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結構*/  
  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));  
     //init_waitqueue_head(&(mem_devp[i].outq));  
  }  
     
  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);  

三、應用程序 app-write.c

#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;      
  
}  

四、應用程序 app-read.c 

#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];  
      
    /*初始化Buf*/  
    strcpy(Buf,"memdev is char dev!");  
    printf("BUF: %s\n",Buf);  
      
    /*打開設備文件*/  
    fd = open("/dev/memdev0",O_RDWR);  
      
    FD_ZERO(&rds);  
    FD_SET(fd, &rds);  
  
    /*清除Buf*/  
    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;      
}  
相關文章
相關標籤/搜索