linux驱动开发 --miscdevice

前言

在linux驱动中,需要提供主设备号和次设备号号,通常使用的主设备号是从
0到 255之间的数,仅仅使用主设备号,还是不叫紧张的,因此需要利用次设备号。

linux中,提供了miscdevice这种杂项设备,指定主设备号为10,次设备号可以设置为
系统动态分配。

在具体分析miscdevice之前,先给出miscdevice的核心设计思想。

应用层,打开/dev/xxx 节点,会得到 文件描述符 fd,通过fd,可以执行 read()、
wriete()、ioctl()、mmap()等对这个文件的操作。

应用层调用 open()函数,经过系统调用后,会调用驱动程序的 open()函数。 在前边分析
调用过程时,分析过这个open()的调用过程。

驱动程序中,调用file_opeartions的open()的时候,此时,调用到另外一个 file_operations的
open(),此时应用层会得到这个open()时的 文件描述符fd,此时应用层 read()、write()等操作,
就调用到了这个file_operations。故在cdev_init时的 fops 充当一个中转的作用,转到具体的
fops 来操作。

给出在linux驱动开发之字符设备–自动创建设备节点
代码上的一个 diff

@@ -134,6 +135,19 @@ static struct file_operations fops ={
     .unlocked_ioctl = cdev_demo_ioctl,
 };

+static int misc_open(struct inode * inode, struct file * file)
+{
+    printk("%s,%d\n",__func__,__LINE__);
+   
+   file->f_op = &fops;
+   return file->f_op->open(inode,file) ;
+}
+
+static struct file_operations misc_fops = {
+   .owner = THIS_MODULE;
+   .open = misc_open,
+};
+
 char cdev_buf[2] = "a";

 static ssize_t cdev_demo_show(struct device *dev,struct device_attribute *attr,    char *buf)  
@@ -171,7 +185,7 @@ static int __init cdev_demo_init(void)
         return -ENOMEM;
     }

-    cdev_init(pdev,&fops);
+    cdev_init(pdev,&misc_fops);

     ret = alloc_chrdev_region(&dev,minor,count,DEVICE_NAME);
     if(ret){

关键的代码是

    file->f_op = &fops;
    return file->f_op->open(inode,file) ;

将变量 fops 赋值给了 file 的 f_op (const struct file_operations *f_op;)

在返回的时候,调用这个file->f_op-open,其实质是调用到 fops的open()。

这样的一种方式,实现了 中转的作用。 利用这种中转,可以进行代码的分层,在通用层中
实现抽象的open(),在具体的设备中,实现具体的open()、read()、write()。

miscdevice分析

miscdevice 结构体

struct miscdevice  {
    int minor;            //次设备号  通常为MISC_DYNAMIC_MINOR  动态分配
    const char *name;     //设备为的名字
    const struct file_operations *fops;//函数操作集
    struct list_head list;
    struct device *parent;
    struct device *this_device;
    const char *nodename;
    umode_t mode;
};

通常 miscdevice的 minor 、name 和 fops是需要实现的。

int misc_register(struct miscdevice * misc)

misc 注册函数,成功返回0 ,失败返回负数

int misc_deregister(struct miscdevice *misc)

misc 注销函数,成功返回0 ,失败返回负数

/*
 * Head entry for the doubly linked miscdevice list
 */
static LIST_HEAD(misc_list);
static DEFINE_MUTEX(misc_mtx);

/*
 * Assigned numbers, used for dynamic minors
 */
#define DYNAMIC_MINORS 64 /* like dynamic majors */
static DECLARE_BITMAP(misc_minors, DYNAMIC_MINORS);
static int misc_open(struct inode * inode, struct file * file)
{
    //取出次设备号
    int minor = iminor(inode);
    struct miscdevice *c;
    int err = -ENODEV;
    const struct file_operations *old_fops, *new_fops = NULL;

    mutex_lock(&misc_mtx);
    //misc_list中,取出具体设备的misc_device来,得到fops
    list_for_each_entry(c, &misc_list, list) {
        if (c->minor == minor) {
            new_fops = fops_get(c->fops);       
            break;
        }
    }

    if (!new_fops) {
        mutex_unlock(&misc_mtx);
        request_module("char-major-%d-%d", MISC_MAJOR, minor);
        mutex_lock(&misc_mtx);

        list_for_each_entry(c, &misc_list, list) {
            if (c->minor == minor) {    
                new_fops = fops_get(c->fops);
                break;
            }
        }
        if (!new_fops)
            goto fail;
    }

    err = 0;
    //保存原有的fops
    old_fops = file->f_op;
    //赋值新的fops
    file->f_op = new_fops;
    //如果设备实现了open函数
    if (file->f_op->open) {


        /*
         * FIXME: this is a workaround for pmem
         * PMEM need private_data to be NULL for an unmapped file.
         * The private_data is used to store PMEM internal data strucutre.
         * Leakage may happen if we just override this field in pmem_open function
         */
        if (strcmp(c->name, "pmem_multimedia") != 0 && strcmp(c->name, "vmem_multimedia") != 0)
            file->private_data = c;

        //调用open函数
        err=file->f_op->open(inode,file);
        if (err) {
            //调用失败,恢复fops为保存old_fops
            fops_put(file->f_op);
            file->f_op = fops_get(old_fops);
        }
    }
    fops_put(old_fops);
fail:
    mutex_unlock(&misc_mtx);
    return err;
}

static struct class *misc_class;

static const struct file_operations misc_fops = {
    .owner      = THIS_MODULE,
    .open       = misc_open,
    .llseek     = noop_llseek,
};

/**
 *  misc_register   -   register a miscellaneous device
 *  @misc: device structure
 *  
 *  Register a miscellaneous device with the kernel. If the minor
 *  number is set to %MISC_DYNAMIC_MINOR a minor number is assigned
 *  and placed in the minor field of the structure. For other cases
 *  the minor number requested is used.
 *
 *  The structure passed is linked into the kernel and may not be
 *  destroyed until it has been unregistered.
 *
 *  A zero is returned on success and a negative errno code for
 *  failure.
 */

int misc_register(struct miscdevice * misc)
{
    dev_t dev;
    int err = 0;

    INIT_LIST_HEAD(&misc->list);

    mutex_lock(&misc_mtx);
    //动态分配次设备号
    if (misc->minor == MISC_DYNAMIC_MINOR) {
        //从位图中,从 misc_minors 开始,查找最小的 zero位,做多查找64次
        int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS);
        if (i >= DYNAMIC_MINORS) {
            mutex_unlock(&misc_mtx);
            //没有次设备号,可以分配
            return -EBUSY;
        }
        //赋值次设备号 
        misc->minor = DYNAMIC_MINORS - i - 1;
        //设置位图
        set_bit(i, misc_minors);
    } else {
        //指定次设备号
        struct miscdevice *c;
        //从 misc_list中,查看是否制定的这个 minor是否已经注册 
        list_for_each_entry(c, &misc_list, list) {
            if (c->minor == misc->minor) {
                mutex_unlock(&misc_mtx);
                return -EBUSY;
            }
        }
    }
    //使用MKDEV创建设备号
    dev = MKDEV(MISC_MAJOR, misc->minor);
    //创建设备
    misc->this_device = device_create(misc_class, misc->parent, dev,
                      misc, "%s", misc->name);
    //判断设备是否创建成功,不成功,则销毁 次设备号
    if (IS_ERR(misc->this_device)) {
        int i = DYNAMIC_MINORS - misc->minor - 1;
        if (i < DYNAMIC_MINORS && i >= 0)
            clear_bit(i, misc_minors);
        err = PTR_ERR(misc->this_device);
        goto out;
    }

    /*
     * Add it to the front, so that later devices can "override"
     * earlier defaults
     */
     //将设备添加到misc_list的头部
    list_add(&misc->list, &misc_list);
 out:
    mutex_unlock(&misc_mtx);
    return err;
}

/**
 *  misc_deregister - unregister a miscellaneous device
 *  @misc: device to unregister
 *
 *  Unregister a miscellaneous device that was previously
 *  successfully registered with misc_register(). Success
 *  is indicated by a zero return, a negative errno code
 *  indicates an error.
 */

int misc_deregister(struct miscdevice *misc)
{
    int i = DYNAMIC_MINORS - misc->minor - 1;

    if (WARN_ON(list_empty(&misc->list)))
        return -EINVAL;

    mutex_lock(&misc_mtx);
    //删除链表 misc->list,相当于同时在misc_list 中删除了这个 misc
    list_del(&misc->list);
    //销毁设备
    device_destroy(misc_class, MKDEV(MISC_MAJOR, misc->minor));
    //清理次设备号
    if (i < DYNAMIC_MINORS && i >= 0)
        clear_bit(i, misc_minors);
    mutex_unlock(&misc_mtx);
    return 0;
}

EXPORT_SYMBOL(misc_register);
EXPORT_SYMBOL(misc_deregister);

static char *misc_devnode(struct device *dev, umode_t *mode)
{
    struct miscdevice *c = dev_get_drvdata(dev);

    if (mode && c->mode)
        *mode = c->mode;
    if (c->nodename)
        return kstrdup(c->nodename, GFP_KERNEL);
    return NULL;
}

static int __init misc_init(void)
{
    int err;
//创建proc 文件系统
#ifdef CONFIG_PROC_FS
    proc_create("misc", 0, NULL, &misc_proc_fops);
#endif
    misc_class = class_create(THIS_MODULE, "misc");
    err = PTR_ERR(misc_class);
    if (IS_ERR(misc_class))
        goto fail_remove;

    err = -EIO;
    if (register_chrdev(MISC_MAJOR,"misc",&misc_fops))
        goto fail_printk;
    misc_class->devnode = misc_devnode;
    return 0;

fail_printk:
    printk("unable to get major %d for misc devices\n", MISC_MAJOR);
    class_destroy(misc_class);
fail_remove:
    remove_proc_entry("misc", NULL);
    return err;
}
subsys_initcall(misc_init);

1.利用链表,进行存储具体的设备
2.使用open时的中转功能
3.通用层: miscdevice,提供结构体、创建类,提供创建设备的核心函数
具体设备层: 填充结构题,实现具体功能; 利用提供的核心函数实现注册设备 。

参考文献

linux驱动开发之字符设备框架 -调用过程分析

发布了84 篇原创文章 · 获赞 25 · 访问量 12万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章