Linux高級字符設備驅動

Linux高級字符設備驅動

設備Ioctl控制

[日期:2012-05-17] 來源:Linux社區  作者:yinjiabin [字體:  ]
        來源地址:http://www.linuxidc.com/Linux/2012-05/60469p3.htm

1. Ioctl 用來做什麼?

大部分驅動除了需要具備讀寫設備的能力外,還需要具備對硬件控制的能力。例如,要求設備報告錯誤信息,改變波特率,這些操作常常通過ioctl方法來實現。

1.1 用戶使用方法
在用戶空間,使用ioctl 系統調用來控制設備,原型如下:
         int ioctl(int fd,unsigned long cmd,...)
原型中的點表示這是一個可選的參數,存在與否依賴於控制命令(第2 個參數)是否涉及到與設備的數據交互。

1.2 驅動ioctl方法
        ioctl 驅動方法有和用戶空間版本不同的原型:
        int (*ioctl)(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)
        cmd參數從用戶空間傳下來,可選的參數arg 以一個unsigned long 的形式傳遞,不管它是一個整數或一個指針。如果cmd命令不涉及數據傳輸,則第3 個參數arg的值無任何意義。

2. Ioctl實現
2.1 實現Ioctl方法的步驟:
      1)  定義命令
      2.)  實現命令

2.2 定義命令
        在編寫ioctl代碼之前,首先需要定義命令。爲了防止對錯誤的設備使用正確的命令,命令號應該在系統範圍內是唯一的。ioctl 命令編碼被劃分爲幾個位段,include/asm/ioctl.h中定義了這些位字段:類型(幻數),序號,傳送方向,參數的大小。Documentation/ioctl-number.txt文件中羅列了在內核中已經使用了的幻數。
        定義ioctl 命令的正確方法是使用4 個位段, 這個列表中介紹的符號定義在<linux/ioctl.h>中:
1) Type
     幻數(類型): 表明哪個設備的命令,在參考了ioctlnumber.txt之後選出,8 位寬。
2)  Number
      序號,表明設備命令中的第幾個,8 位寬
3) Direction
     數據傳送的方向,可能的值是_IOC_NONE(沒有數據傳輸),_IOC_READ, _IOC_WRITE。數據傳送是從應用程序的觀點來看待的,_IOC_READ 意思是從設備讀。
4) Size
用戶數據的大小。(13/14位寬,視處理器而定)

內核提供了下列宏來幫助定義命令:
1)  _IO(type,nr)
    沒有參數的命令
2)  _IOR(type,nr,datatype)
    從驅動中讀數據
3)  _IOW(type,nr,datatype)
   寫數據到驅動
4) _IOWR(type,nr,datatype)
   雙向傳送,type 和number 成員作爲參數被傳遞。

定義命令(範例)
#define MEM_IOC_MAGIC ‘m’ //定義幻數
#define MEM_IOCSET
_IOW(MEM_IOC_MAGIC, 0, int)
#define MEM_IOCGQSET
_IOR(MEM_IOC_MAGIC, 1, int)

2.3 Ioctl函數實現
        定義好了命令,下一步就是要實現Ioctl函數了,Ioctl函數的實現包括如下3個技術環節:
       1)  返回值
       2) 參數使用
       3) 命令操作

 

2.3.1 Ioctl函數實現(返回值)
        Ioctl函數的實現通常是根據命令執行的一個switch語句。但是,當命令號不能匹配任何一個設備所支持的命令時,通常返回-EINVAL(“非法參數”)。

2.3..2 Ioctl函數實現(參數)
         如何使用Ioctl中的參數?
         如果是一個整數,可以直接使用。如果是指針,我們必須確保這個用戶地址是有效的,因此使用前需進行正確的檢查。

2.3.3 Ioctl函數實現(參數檢查)
        不需要檢測:
        1) copy_from_user

        2) copy_to_user
        3) get_user
        4) put_user
        需要檢測:
       1) __get_user
       2) __put_user

         int access_ok(int type, const void *addr, unsigned long size)

        第一個參數是VERIFY_READ 或者VERIFY_WRITE,用來表明是讀用戶內存還是寫用戶內存。addr 參數是要操作的用戶內存地址,size 是操作的長度。如果ioctl 需要從用戶空間讀一個整數,那麼size參數等於sizeof(int)。access_ok 返回一個布爾值: 1 是成功(存取沒問題)和0 是失敗(存取有問題),如果該函數返回失敗, 則Ioctl應當返回–EFAULT 。

3. Ioctl函數實現範例

if (_IOC_DIR(cmd) & _IOC_READ)
         err = !access_ok(VERIFY_WRITE, (void __user *)arg,_IOC_SIZE(cmd));   //why _IOC_READ 對應VERIFY_WRITE ???
else if (_IOC_DIR(cmd) & _IOC_WRITE)
        err = !access_ok(VERIFY_READ, (void __user *)arg,_IOC_SIZE(cmd));
if (err)
      return -EFAULT;


switch(cmd)
{
           case MEM_IOCSQUANTUM: /* Set: arg points to the value */
           retval = __get_user(scull_quantum, (int *)arg);
           break;


           case MEM_IOCGQUANTUM: /* Get: arg is pointer to result */
           retval = __put_user(scull_quantum, (int *)arg);
           break;


          default:
          return –EINVAL;
}

在Linux驅動程序設計中,可以使用等待隊列來實現進程的阻塞,等待隊列可看作保存進程的容器,在阻塞進程時,將進程放入等待隊列,當喚醒進程時,從等待等列中取出進程。
Linux 2.6內核提供瞭如下關於等待隊列的操作:
1、定義等待隊列
       wait_queue_head_t my_queue
2、初始化等待隊列
       init_waitqueue_head(&my_queue)
3、定義並初始化等待隊列
       DECLARE_WAIT_QUEUE_HEAD(my_queue)
4、有條件睡眠
      1)wait_event(queue,condition)
      當condition(一個布爾表達式)爲真時,立即返回;否則讓進程進入TASK_UNINTERRUPTIBLE模式的睡眠,並掛在queue參數所指定的等待隊列上。
      2)wait_event_interruptible(queue,condition)
     當condition(一個布爾表達式)爲真時,立即返回;否則讓進程進入TASK_INTERRUPTIBLE的睡眠,並掛在queue參數所指定的等待隊列上。
      3)int wait_event_killable(wait_queue_t queue, condition)
     當condition(一個布爾表達式)爲真時,立即返回;否則讓進程進入TASK_KILLABLE的睡眠,並掛在queue參數所指定的等待隊列上。
5、無條件睡眠(老版本,建議不再使用)
      1)sleep_on(wait_queue_head_t *q)
      讓進程進入不可中斷的睡眠,並把它放入等待隊列q。
      2)interruptible_sleep_on(wait_queue_head_t *q)
      讓進程進入可中斷的睡眠,並把它放入等待隊列q。
6、從等待隊列中喚醒進程
      1) wake_up(wait_queue_t *q)
      從等待隊列q中喚醒狀態爲TASK_UNINTERRUPTIBLE,TASK_INTERRUPTIBLE,TASK_KILLABLE 的所有進程。
      2) wake_up_interruptible(wait_queue_t *q)
     從等待隊列q中喚醒狀態爲TASK_INTERRUPTIBLE 的進程。

1、阻塞型字符設備驅動的功能
      當一個設備無法立刻滿足用戶的讀寫請求時應當如何處理? 例如:調用read時沒有數據可讀, 但以後可能會有;或者一個進程試圖向設備寫入數據,但是設備暫時沒有準備好接收數據。應用程序通常不關心這種問題,應用程序只是調用 read 或 write 並得到返回值。驅動程序應當(缺省地)阻塞進程,使它進入睡眠,直到請求可以得到滿足。
2、阻塞方式
      1)在阻塞型驅動程序中,Read實現方式如下:如果進程調用read,但設備沒有數據或數據不足,進程阻塞。當新數據到達後,喚醒被阻塞進程。
      2)在阻塞型驅動程序中,Write實現方式如下:如果進程調用了write,但設備沒有足夠的空間供其寫入數據,進程阻塞。當設備中的數據被讀走後,緩衝區中空出部分空間,則喚醒進程。
3、非阻塞方式
      阻塞方式是文件讀寫操作的默認方式,但應用程序員可通過使用O_NONBLOCK標誌來人爲的設置讀寫操作爲非阻塞方式(該標誌定義在<linux/fcntl.h>中,在打開文件時指定)。
      如果設置了O_NONBLOCK標誌,read和write的行爲是不同的。如果進程在沒有數據就緒時調用了read,或者在緩衝區沒有空間時調用了write,系統只是簡單地返回-EAGAIN,而不會阻塞進程。

4、實例分析

        程序實現的功能當進程讀文件時,沒有數據可讀,則該進程阻塞。

        1)memdev.h源代碼

#ifndef _MEMDEV_H_
#define _MEMDEV_H_

#ifndef MEMDEV_MAJOR
#define MEMDEV_MAJOR 0   /*預設的mem的主設備號*/
#endif

#ifndef MEMDEV_NR_DEVS
#define MEMDEV_NR_DEVS 2    /*設備數*/
#endif

#ifndef MEMDEV_SIZE
#define MEMDEV_SIZE 4096
#endif

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

#endif /* _MEMDEV_H_ */

        2)阻塞型字符驅動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 "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; 
}

/*release函數*/
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。答:爲了排除由於中斷喚醒等待隊列,但此時並沒有數據可讀,故次用while和interruptible配合的原因*/

{

          /*判斷用戶是否設置了非阻塞方式*/

        if (filp->f_flags & O_NONBLOCK)
            return -EAGAIN; /*設置了非阻塞方式*/
        /*當設置了阻塞方式*/

       wait_event_interruptible(dev->inq,have_data);/**當have_data爲真時,立即返回,否則讓進程進入TASK_KILL

                                                                                        的睡眠 並掛在dev->inq隊列上*/

}


  /*讀數據到用戶空間*/
  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;

}

/*?文件操作結構體*/
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,
};

/*設備驅動模塊加載函數*/
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));
  }
   
  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("yinjiabin");
MODULE_LICENSE("GPL");

module_init(memdev_init);

module_exit(memdev_exit);

3)測試程序源碼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;     
}
1、什麼是Poll方法,功能是什麼?



2、Select系統調用(功能)
      Select系統調用用於多路監控,當沒有一個文件滿足要求時,select將阻塞調用進程。
      int select(int maxfd, fd_set *readfds, fd_set *writefds, fe_set *exceptfds, const struct timeval *timeout)
     Select系統調用(參數)
     1)Maxfd:
           文件描述符的範圍,比待檢測的最大文件描述符大1
     2)Readfds:
           被讀監控的文件描述符集
     3)Writefds:
           被寫監控的文件描述符集
     4)Exceptfds:
           被異常監控的文件描述符集;
     5)Timeout:

           定時器,Timeout取不同的值,該調用有不同的表現:

          1>Timeout值爲0,不管是否有文件滿足要求,都立刻返回,無文件滿足要求返回0,有文件滿足要求返回一個正值。
          2>Timeout爲NULL,select將阻塞進程,直到某個文件滿足要求
          3>Timeout 值 爲 正 整 數 , 就 是 等 待 的 最 長 時 間 , 即select在timeout時間內阻塞進程。
3、Select系統調用(返回值)
      Select調用返回時,返回值有如下情況:
      1)正常情況下返回滿足要求的文件描述符個數;
      2)經過了timeout等待後仍無文件滿足要求,返回值爲0;
      3)如果select被某個信號中斷,它將返回-1並設置errno爲EINTR。
      4)如果出錯,返回-1並設置相應的errno。
4、Select系統調用(使用方法)
      1)將要監控的文件添加到文件描述符集
      2)調用Select開始監控
      3)判斷文件是否發生變化
        系統提供了4個宏對描述符集進行操作:
        #include <sys/select.h>
        void FD_SET(int fd, fd_set *fdset)
        void FD_CLR(int fd, fd_set *fdset)
        void FD_ZERO(fd_set *fdset)
        void FD_ISSET(int fd, fd_set *fdset)
        宏FD_SET將文件描述符fd添加到文件描述符集fdset中;
        宏FD_CLR從文件描述符集fdset中清除文件描述符fd;
        宏FD_ZERO清空文件描述符集fdset;
        在調用select後使用FD_ISSET來檢測文件描述符集fdset中的文件fd發生了變化。
        FD_ZERO(&fds); //清空集合
        FD_SET(fd1,&fds); //設置描述符
        FD_SET(fd2,&fds); //設置描述符
        maxfdp=fd1+1; //描述符最大值加1,假設fd1>fd2
        switch(select(maxfdp,&fds,NULL,NULL,&timeout))
                 case -1: exit(-1);break; //select錯誤,退出程序
                 case 0:break;
                default:
        if(FD_ISSET(fd1,&fds)) //測試fd1是否可讀

5、poll方法

      應用程序常常使用select系統調用,它可能會阻塞進程。這個調用由驅動的 poll 方法實現,原型爲:unsigned int (*poll)(struct file *filp,poll_table *wait)

      Poll設備方法負責完成:
      1)使用poll_wait將等待隊列添加到poll_table中。
      2)返回描述設備是否可讀或可寫的掩碼。
      位掩碼
      1>POLLIN 設備可讀
      2>POLLRDNORM數據可讀
      3>POLLOUT\設備可寫
      4>POLLWRNORM數據可寫
      設備可讀通常返回(POLLIN|POLLRDNORM )
      設備可寫通常返回(POLLOUT|POLLWRNORM )
6、範例
static unsigned int mem_poll(struct file *filp,poll_table *wait)
{
struct scull_pipe *dev =filp->private_data;
unsigned int mask =0;
/* 把等待隊列添加到poll_table */
poll_wait(filp,&dev->inq,wait);
/*返回掩碼*/
if (有數據可讀)
mask = POLLIN |POLLRDNORM;/*設備可讀*/
return mask;
}
7、工作原理
      Poll方法只是做一個登記,真正的阻塞發生在select.c 中的 do_select函數。

8、實例分析

      1)poll型設備驅動memdev.h源碼


#ifndef _MEMDEV_H_
#define _MEMDEV_H_

#ifndef MEMDEV_MAJOR
#define MEMDEV_MAJOR 0   /*預設的mem的主設備號*/
#endif

#ifndef MEMDEV_NR_DEVS
#define MEMDEV_NR_DEVS 2    /*設備數*/
#endif

#ifndef MEMDEV_SIZE
#define MEMDEV_SIZE 4096
#endif

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

  wait_queue_head_t inq;  

};

#endif /* _MEMDEV_H_ */     

        2)Poll型設備驅動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);

3)測試程序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);//調用select()監控函數
    if (ret < 0)  
    {
        printf("select error!\n");
        exit(1);
    }
    if (FD_ISSET(fd, &rds))   //測試fd1是否可讀  
        read(fd, Buf, sizeof(Buf));             
     
    /*檢測結果*/ 
    printf("Read BUF2: %s\n",Buf);
    
    close(fd); 
     
    return 0;     
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章