29.使用register_chrdev_region()系列來注冊字符設備


1.之前注冊字符設備用的如下函數注冊字符設備驅動:

register_chrdev(unsigned int major, const char *name,const struct file_operations *fops);

但其實這個函數是linux版本2.4之前的注冊方式,它的原理是:

(1)確定一個主設備號

(2)構造一個file_operations結構體, 然后放在chrdevs數組中

(3)注冊:register_chrdev

然后當讀寫字符設備的時候,就會根據主設備號從chrdevs數組中取出相應的結構體,並調用相應的處理函數。

它會有個很大的缺點:

每注冊個字符設備,還會連續注冊0~255個次設備號,使它們綁定在同一個file_operations操作方法結構體上,在大多數情況下,都只用極少的次設備號,所以會浪費很多資源.

 

2.所以在2.4版本后,內核里就加入了以下幾個函數也可以來實現注冊字符設備:

分為了靜態注冊(指定設備編號來注冊)、動態分配(不指定設備編號來注冊),以及有連續注冊的次設備編號范圍區間,避免了register_chrdev()浪費資源的缺點   

2.1: 

/*指定設備編號來靜態注冊一個字符設備*/
int register_chrdev_region(dev_t from, unsigned count, const char *name);   

from: 注冊的指定起始設備編號,比如:MKDEV(100, 0),表示起始主設備號100, 起始次設備號為0

count:需要連續注冊的次設備編號個數,比如: 起始次設備號為0,count=100,表示0~99的次設備號都要綁定在同一個file_operations操作方法結構體上

*name:字符設備名稱

當返回值小於0,表示注冊失敗

2.2:

/*動態分配一個字符設備,注冊成功並將分配到的主次設備號放入*dev里*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name);

*dev: 存放起始設備編號的指針,當注冊成功, *dev就會等於分配到的起始設備編號,可以通過MAJOR()MINNOR()函數來提取主次設備號

baseminor:次設備號基地址,也就是起始次設備號

count:需要連續注冊的次設備編號個數,比如: 起始次設備號(baseminor)為0,baseminor=2,表示0~1的此設備號都要綁定在同一個file_operations操作方法結構體上

*name:字符設備名稱

當返回值小於0,表示注冊失敗

2.3:  

 /*初始化cdev結構體,並將file_operations結構體放入cdev-> ops 里*/
void cdev_init(struct cdev *cdev, const struct file_operations *fops);

其中cdev結構體的成員,如下所示:

struct cdev {
       struct kobject    kobj;                   // 內嵌的kobject對象 
       struct module   *owner;                   //所屬模塊
       const struct file_operations  *ops;     //操作方法結構體
       struct list_head  list;            //與 cdev 對應的字符設備文件的 inode->i_devices 的鏈表頭
       dev_t dev;                    //起始設備編號,可以通過MAJOR(),MINOR()來提取主次設備號
       unsigned int count;                //連續注冊的次設備號個數
};

2.4: 

/*將cdev結構體添加到系統中,並將dev(注冊好的設備編號)放入cdev-> dev里,  count(次設備編號個數)放入cdev->count里*/
int cdev_add(struct cdev *p, dev_t dev, unsigned count);

2.5:

/*將系統中的cdev結構體刪除掉*/
void cdev_del(struct cdev *p);

 

2.6: 

 /*注銷字符設備*/
void unregister_chrdev_region(dev_t from, unsigned count);

from: 注銷的指定起始設備編號,比如:MKDEV(100, 0),表示起始主設備號100, 起始次設備號為0

count:需要連續注銷的次設備編號個數,比如: 起始次設備號為0,baseminor=100,表示注銷掉0~99的次設備號

 

3.接下來,我們便來寫一個字符設備驅動

里面調用兩次上面的函數,構造兩個不同的file_operations操作結構體,

次設備號0~1對應第一個file_operations,

次設備號2~3對應第二個file_operations,

然后在/dev/下,通過次設備號(0~4)創建5個設備節點, 利用應用程序打開這5個文件,看有什么現象

3.1 驅動代碼如下:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/list.h>
#include <linux/cdev.h>

static int hello_fops1_open(struct inode *inode, struct file *file)
{
    printk("open_hello1!!!\n");
    return 0;
}

 

static int hello_fops2_open (struct inode *inode, struct file *file)
{
    printk("open_hello2!!!\n");
    return 0;
}

 
  /*  操作結構體1   */
static struct file_operations hello1_fops={
        .owner=THIS_MODULE,
        .open =hello_fops1_open,
};

  /*  操作結構體2   */
static struct file_operations hello2_fops={
        .owner=THIS_MODULE,
        .open =hello_fops2_open,
};

 
static int major;                                 //主設備
static struct cdev hello1_cdev;        //保存 hello1_fops操作結構體的字符設備 
static struct cdev hello2_cdev;         //保存 hello2_fops操作結構體的字符設備 
static struct class *cls;

static int chrdev_ragion_init(void)
{
     dev_t  devid;  

     alloc_chrdev_region(&devid, 0, 4,"hello");    //動態分配字符設備: (major,0)  (major,1)   (major,2)  (major,3)

     major=MAJOR(devid);

     cdev_init(&hello1_cdev, &hello1_fops);
     cdev_add(&hello1_cdev, MKDEV(major,0), 2);           //(major,0) (major,1)


     cdev_init(&hello2_cdev, &hello2_fops);
     cdev_add(&hello2_cdev,MKDEV(major,2), 2);           //(major,2) (major,3)     

     cls=class_create(THIS_MODULE, "hello");
     /*創建字符設備節點*/
     class_device_create(cls,0, MKDEV(major,0), 0, "hello0");   //對應hello_fops1操作結構體
     class_device_create(cls,0, MKDEV(major,1), 0, "hello1");   //對應hello_fops1操作結構體
     class_device_create(cls,0, MKDEV(major,2), 0, "hello2");   //對應hello_fops2操作結構體
     class_device_create(cls,0, MKDEV(major,3), 0, "hello3");   //對應hello_fops2操作結構體
     class_device_create(cls,0, MKDEV(major,4), 0, "hello4");   //對應空
        return 0;
}

void chrdev_ragion_exit(void)
{
   class_device_destroy(cls, MKDEV(major,4));
   class_device_destroy(cls, MKDEV(major,3));
   class_device_destroy(cls, MKDEV(major,2));
   class_device_destroy(cls, MKDEV(major,1));
   class_device_destroy(cls, MKDEV(major,0));

   class_destroy(cls);


   cdev_del(&hello1_cdev);     
   cdev_del(&hello2_cdev); 
   unregister_chrdev_region(MKDEV(major,0), 4);     //注銷(major,0)~(major,3)
} 

module_init(chrdev_ragion_init);
module_exit(chrdev_ragion_exit);
MODULE_LICENSE("GPL");

 

3.2 測試代碼如下所示:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
void print_useg(char arg[])    //打印使用幫助信息
{
         printf("useg:  \n");
         printf("%s   [dev]\n",arg);
}

int main(int argc,char **argv)
{

  int fd;
  if(argc!=2)
    {
        print_useg(argv[0]);
        return -1;
    }

  fd=open(argv[1],O_RDWR);
  if(fd<0)
      printf("can't open %s \n",argv[1]);
  else
      printf("can open %s \n",argv[1]);
  return 0;
}

 

4.運行測試:

如下圖,掛載驅動后,通過 ls /dev/hello* -l ,看到創建了5個字符設備節點

 

 

接下來開始測試驅動,如下圖所示,

打開/dev/hello0時,調用的是驅動代碼的操作結構體hello1_fops里的.open(),

打開/dev/hello2時,調用的是驅動代碼的操作結構體hello1_fops里的.open(),

打開/dev/hello4時,打開無效,因為在驅動代碼里沒有分配次設備號4的操作結構體,

 

 

總結:

  使用register_chrdev_region()等函數來注冊字符設備,里面可以存放多個不同的file_oprations操作結構體,實現各種不同的功能

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM