自动创建设备文件

来源:互联网 发布:mac 进入应用程序目录 编辑:程序博客网 时间:2024/04/28 02:26
设备文件是非常重要的文件,是应用程序与设备驱动交换数据,控制硬件的桥梁。在驱动程序中open、release的实现过程中其中的一个参数struct inode实质就是设备文件的索引,没有这个索引也就没有后期的各种操作,通常设备文件也被称为设备文件节点。因此没有设备文件后期的各种实现都是多余的。设备文件的创建有两种方法,其中就是在创建文件系统过程中用到的mknod命令。
该命令的形式如下:
mknod filename (type,c,b,l) 主设备号 次设备号
其中type说明是那一类设备(字符设备c,块设备b,套接字l),主设备号用来确定那一类设备,而次设备号主要用来确定这一类设备中的某一个设备。
例如:mknod memdev0  c 555 0 就是创建了一个主设备号为555,次设备号为0的字符设备。

这种方法比较快速,但是在编写设备驱动的时候很难确定那个设备号是可以使用的,因此很不方便开发。在2.4内核中引入了devfs,但是因为性能等方面的原因,在2.6内核中被udev逐渐取代。udev的设备命名策略、权限控制和事件处理都是在用户态下完成的,它利用sysfs中的信息来进行创建设备文件节点等工作。其实对于我们写程序而言并没有多大的区别,这是内核的设计者考虑的问题。两个都能够实现设备文件的动态创建,具体的实现方法也很类似。在嵌入式中是采用mdev实现类似udev的动态创建设备文件,在制作文件系统的过程中应该注意在linux system项选上mdev,不过一般默认情况下都选择上。


在驱动中动态添加设备文件节点会减少麻烦。
具体的实现主要包括两个过程。
1、创建一个设备类,主要依据函数class_create()实现。
2、依据设备类创建一个设备文件,主要依据device_create()或者有些较低版本中的class_device_create()实现。

基本的实现过程应该是在设备驱动初始化过程中首先得到申请到设备号之后创建一个设备类,采用class_create()实现。
函数class_create()的形式如下:
  1. #define class_create(owner, name)        \
  2. ({                        \
  3.     static struct lock_class_key __key;    \
  4.     __class_create(owner, name, &__key);    \
  5. })
参数的意义:owner是指设备的拥有者,因此可以直接THIS_MODULE复制给owner,而name是设备类的名字。返回值是一个设备类的指针。这样就创建了一个设备类。
  1. static struct class *myclass;
  2. ...
  3. static int memdev_init(void)
  4. {
  5.   ...
  6.  /*如果定义了主设备号采用静态申请的方式*/
  7.         if(mem_major)
  8.                 result = register_chrdev_region(devno,2,"mem_dev");
  9.         else/*动态申请设备号*/
  10.         {
  11.                 result = alloc_chrdev_region(&devno,0,2,"mem_dev");
  12.                 mem_major = MAJOR(result);
  13.         }
  14.         /*错误处理*/
  15.         if(result < 0)
  16.                 return result;

  17.         /*在设备号申请完成以后可以为设备创建一个设备类,用于设备文件的创建*/
  18.         myclass = class_create(THIS_MODULE,"memdev_class");
  19.         /*创建一个设备*/

  20.         /*初始化cdev,并将相关的文件操作添加进来*/
  21.         cdev_init(&cdev,&mem_fops);
  22.    ...
  23. }
在设备初始化完成、绑定好文件操作、设备添加到内核中以后然后根据设备类要创建设备文件,依据device_create实现函数,其中的函数形式如下实现。
  1. struct device *device_create(struct class *class, struct device *parent,
  2.              dev_t devt, void *drvdata, const char *fmt, ...)
  3. {
  4.     va_list vargs;
  5.     struct device *dev;

  6.     va_start(vargs, fmt);
  7.     dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
  8.     va_end(vargs);
  9.     return dev;
  10. }
参数的意义分别是设备类指针、设备的父设备,设备号、以及设备的数据、然后是设备文件的名字,参数具有不定性。具体的实现如下:
  1. ...

  2.        /*初始化cdev,并将相关的文件操作添加进来*/
  3.         cdev_init(&cdev,&mem_fops);
  4.         /*设备引用*/
  5.         cdev.owner = THIS_MODULE;
  6.         cdev.ops = &mem_fops;
  7.         /*注册字符设备*/
  8.         cdev_add(&cdev,MKDEV(mem_major,0),MEMDEV_NR_DEVS);
  9.         /*以上设备添加完成*/

  10.         /*分配两个内存空间,此处是在物理内存上实现分配,实质是创建两个设备的描述*/
  11.         mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev),GFP_KERNEL);
  12.         if(!mem_devp)/*出错的相应操作*/
  13.         {
  14.                 result = -ENOMEM;
  15.                 /*错误处理,采用典型的goto语句*/
  16.                 goto fail_malloc;
  17.         }

  18.         /*清除空间*/
  19.         memset(mem_devp,0,sizeof(struct mem_dev));

  20.         for(= 0; i < MEMDEV_NR_DEVS; ++i)
  21.         {
  22.                 device_create(myclass,NULL,MKDEV(mem_major,i),NULL,"memdev%d",i);
  23.                 /*
  24.                 myclass为设备类
  25.                 NULL 表示父设备为空
  26.                 MKDEV(mem_major,i) 表示设备号
  27.                 NULL 表示设备数据为空
  28.                 后面的参数是用来设置 设备文件的名字
  29.                 */
  30.                 mem_devp[i].size = MEMDEV_SIZE;
  31.                 /*对设备的数据空间分配空间*/
  32.                 mem_devp[i].data = kmalloc(MEMDEV_SIZE,GFP_KERNEL);
  33.                 /*问题,没有进行错误的控制*/
  34.                 memset(mem_devp[i].data,0,MEMDEV_SIZE);

  35.                 /*初始化定义的互信息量*/
  36.                 //mutex_init(&mem_devp[i].sem);
  37.                 //初始化定义的自旋锁ua
  38.                 spin_lock_init(&mem_devp[i].lock);
  39.         }
  40. ...
以上的操作都是在模块初始化过程中完成的。这样在加载过程中就会在/dev目录下添加好设备文件。在设备退出过程中我们当然也要释放分配好的这些资源。具体的采用device_destroy释放分配好的设备文件,
  1. void device_destroy(struct class *class, dev_t devt)
  2. {
  3.     struct device *dev;

  4.     dev = class_find_device(class, NULL, &devt, __match_devt);
  5.     if (dev) {
  6.         put_device(dev);
  7.         device_unregister(dev);
  8.     }
  9. }
参数主要是设备类和设备号。
同时也要释放设备类。主要采用函数class_destroy()
  1. void class_destroy(struct class *cls)
  2. {
  3.     if ((cls == NULL) || (IS_ERR(cls)))
  4.         return;

  5.     class_unregister(cls);
  6. }
参数是设备类。
设备的退出过程如下:
  1. /*模块清除函数*/
  2. static void memdev_exit(void)
  3. {
  4.         cdev_del(&cdev);/*注销字符设备*/
  5.         /*释放两个物理内存*/

  6.         kfree(mem_devp[0].data);
  7.         kfree(mem_devp[1].data);
  8.         device_destroy(myclass,MKDEV(mem_major,0));
  9.         device_destroy(myclass,MKDEV(mem_major,1));

  10.         kfree(mem_devp);/*释放设备结构体内存*/

  11.         class_destroy(myclass);
  12.         unregister_chrdev_region(MKDEV(mem_major,0),2);
  13. }
基本的形式如上所示。
驱动的出错顺序与错误处理顺序应该是一个相反的过程,这样才能保证区域的包含关系。由于设备类的创建过程是在设备号申请的后面完成,因此释放应该在设备号释放之前注销掉。因此形成一个先进后处理的关系,类似于一个堆栈的形式。
测试过程:
[gong@Gong-Computer mem_waitqueue]$ ls -al /dev/mem*
crw-r----- 1 root kmem 1, 1 Dec  5 12:55 /dev/mem
[gong@Gong-Computer mem_waitqueue]$ sudo insmod memwait_queue.ko 
[sudo] password for gong: 
[gong@Gong-Computer mem_waitqueue]$ ls -al /dev/mem*
crw-r----- 1 root kmem   1, 1 Dec  5 12:55 /dev/mem
crw------- 1 root root 555, 0 Dec  5 16:50 /dev/memdev0
crw------- 1 root root 555, 1 Dec  5 16:50 /dev/memdev1
[gong@Gong-Computer mem_waitqueue]$ sudo rmmod memwait_queue
[gong@Gong-Computer mem_waitqueue]$ ls -al /dev/mem*
crw-r----- 1 root kmem 1, 1 Dec  5 12:55 /dev/mem

以上的结果表明,采用上面的方式能够自动的创建设备文件,相比手动创建更加的方便自如。

下面以一个简单字符设备驱动来展示如何使用这几个函数 
   #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 <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/device.h>
#include "memdev.h"

static int mem_major = MEMDEV_MAJOR;
struct mem_dev *mem_devp; /*设备结构体指针*/
bool have_data = false; /*表明设备有足够数据可供读*/
struct cdev cdev; 
struct class *myclass=NULL;

/*文件打开函数*/
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", (int)count, (int)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", (int)count, (int)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 int  mem_ioctl(struct inode * inode,struct file *flip,unsigned int cmd,
//            unsigned long arg)
static int  mem_ioctl(struct file *flip,unsigned int cmd,
            unsigned long arg)
{
  struct mem_dev *dev = flip->private_data; /*获得设备结构体指针*/
  
  int err = 0;
  int ret = 0;
  int ioarg = 0;
    
  /* 检测命令的有效性 */
  if (_IOC_TYPE(cmd) != MEMDEV_IOC_MAGIC) 
      return -EINVAL;
  if (_IOC_NR(cmd) > MEMDEV_IOC_MAXNR) 
        return -EINVAL;

    /* 根据命令类型,检测参数空间是否可以访问 */
    if (_IOC_DIR(cmd) & _IOC_READ)
        err = !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd));
    else if (_IOC_DIR(cmd) & _IOC_WRITE)
        err = !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd));
    if (err) 
        return -EFAULT;
    
    
    /* 根据命令,执行相应的操作 */
    switch(cmd) {

      /* 打印当前设备信息 */
      case MEMDEV_IOCPRINT:
       printk("<--- CMD MEMDEV_IOCPRINT Done--->\n\n");
        break;

      case MEMDEV_IOCMEMEST:
    memset(dev->data, 0, MEMDEV_SIZE);   
        break;
      
      /* 获取参数 */
      case MEMDEV_IOCGETDATA:

        ioarg = 1101;
        ret = __put_user(ioarg, (int *)arg);
        break;
      
      /* 设置参数 */
      case MEMDEV_IOCSETDATA:

        ret = __get_user(ioarg, (int *)arg);
        printk("<--- In Kernel MEMDEV_IOCSETDATA ioarg = %d --->\n\n",ioarg);
        break;

      default:  
        return -EINVAL;
    }
    return ret;

 
}
/*文件操作结构体*/
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,
  .unlocked_ioctl = mem_ioctl,  
};


/*设备驱动模块加载函数*/
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,"ZXJ");
 else
 {
    result = alloc_chrdev_region(&devno,0,2,"ZXJ");
    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) * MEMDEV_NR_DEVS);   
  /*  创建类/sys文件夹下面  */ 
  myclass=class_create(THIS_MODULE,"test");
  /*为设备分配内存*/
  for (i=0; i < MEMDEV_NR_DEVS; i++) 
  {
        mem_devp[i].size = MEMDEV_SIZE;
        mem_devp[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);
        if (!mem_devp[i].data)
          printk("Bad Kmalloc\n");
        memset(mem_devp[i].data, 0, MEMDEV_SIZE);
        sprintf(mem_devp[i].name,"memdev%d",i);
     cdev_init(&mem_devp[i].cdev,&mem_fops);
      mem_devp[i].cdev.owner = THIS_MODULE;
     result = cdev_add(&mem_devp[i].cdev,MKDEV(mem_major, i),1);                  
        if (result)
        {
          printk("Bad cdev\n");
          return result;
        }
                /* 初始化信号量 */
     init_waitqueue_head(&(mem_devp[i].inq));  
     device_create(myclass,NULL,MKDEV(mem_major, i),NULL,"memdev%d",i); 
  }
  return 0;
    
  fail_malloc: 
  unregister_chrdev_region(devno, 1);
  
  return result;  
  
}

/*模块卸载函数*/
static void memdev_exit(void)
{
  int i;
  for (i=0; i < MEMDEV_NR_DEVS; i++) 
  {  
   device_destroy(myclass,MKDEV(mem_major, i));//删除设备文件
   cdev_del(&mem_devp[i].cdev);   /*注销设备*/ 
   kfree(mem_devp[i].data);     /*释放内存*/  
 } 
  class_destroy(myclass); 
  kfree(mem_devp);     /*释放设备结构体内存*/
  unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*释放设备号*/
}

 

MODULE_AUTHOR("Smart.zhao");
MODULE_LICENSE("GPL");

module_init(memdev_init);
module_exit(memdev_exit);

 

 

当加载模块的时候,会在/dev/memdev0,/dev/memdev1两个设备文件,如图所示

class_create(),device_create自动创建设备文件结点 . - Smart_zhao - 军惠博客

原创粉丝点击