美文网首页
字符设备驱动

字符设备驱动

作者: 谦悠 | 来源:发表于2020-04-05 16:37 被阅读0次

    字符设备驱动

    设备号

    此处仅仅介绍api,详细解析请参考设备号

    1. 设备号的数据类型:dev_t
      typedef unsigned int dev_t;

    2. 设备号是一个统称,分为主设备号和次设备号。
      主设备号保存在高12位
      次设备号保存在低20位

    3. 设备号的功能
      主设备号:一个设备驱动对应一个主设备号。应用程序通过设备文件中的主设备号在内核中找到对应的设备驱动。
      次设备号:一个设备对应一个次设备号。当一个驱动程序管理多个设备时,驱动程序通过设备文件中的次设备号,找到需要操作的设备。

    4. 设备号操作

    /* 通过已知的主次设备号,合成设备号 */
    dev_t dev = MKDEV(major, minor);   
    /* 通过已知的设备号,获取主设备号 */
    major = MAJOR(dev);   
    /* 通过已知的设备号,获取次设备号 */
    minor = MINOR(dev);    
    

    上述宏的实现:

    #define MINORBITS   20
    #define MINORMASK   ((1U << MINORBITS) - 1)
    
    #define MAJOR(dev)  ((unsigned int) ((dev) >> MINORBITS))
    #define MINOR(dev)  ((unsigned int) ((dev) & MINORMASK))
    #define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))
    

    设备号申请方法:

    /**
     * alloc_chrdev_region() - register a range of char device numbers
     * @dev: output parameter for first assigned number
     * @baseminor: first of the requested range of minor numbers
     * @count: the number of minor numbers required
     * @name: the name of the associated device or driver
     *
     * Allocates a range of char device numbers.  The major number will be
     * chosen dynamically, and returned (along with the first minor number)
     * in @dev.  Returns zero or a negative error code.
     */
    int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
                const char *name);
    

    name是设备名称,可通过cat /proc/devices查看

    设备号释放的方法:

    /**
     * unregister_chrdev_region() - unregister a range of device numbers
     * @from: the first in the range of numbers to unregister
     * @count: the number of device numbers to unregister
     *
     * This function will unregister a range of @count device numbers,
     * starting with @from.  The caller should normally be the one who
     * allocated those numbers in the first place...
     */
    void unregister_chrdev_region(dev_t from, unsigned count);
    

    字符设备

    首先了解字符设备对象:

    struct cdev {
        struct kobject kobj;
        struct module *owner;    //指向拥有该驱动程序的模块,一般为THIS_MODULE
        const struct file_operations *ops;    //设备文件操作方法集合的结构体的指针
        struct list_head list;    
        dev_t dev;    //设备号
        unsigned int count;    //驱动程序管理的外设数量
    };
    

    file_operations结构体:

    struct file_operations {
        struct module *owner;
        ... ...
        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
        long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
        int (*mmap) (struct file *, struct vm_area_struct *);
        int (*open) (struct inode *, struct file *);
        int (*release) (struct inode *, struct file *);
        ... ...
    };
    

    还需要关注kobject结构体:

    struct kobject {
        const char      *name;
        struct list_head    entry;
        struct kobject      *parent;
        struct kset     *kset;
        struct kobj_type    *ktype;    /* 用来描述一种kobject的共同特性 */
        struct kernfs_node  *sd; /* sysfs directory entry */
        struct kref     kref;    /* 引用计数 */
    #ifdef CONFIG_DEBUG_KOBJECT_RELEASE
        struct delayed_work release;
    #endif
        unsigned int state_initialized:1;
        unsigned int state_in_sysfs:1;
        unsigned int state_add_uevent_sent:1;
        unsigned int state_remove_uevent_sent:1;
        unsigned int uevent_suppress:1;
    };
    

    初始化cdev

    在定义字符设备对象之后,需要将其初始化,并添加到内核:

    /**
     * cdev_init() - initialize a cdev structure
     * @cdev: the structure to initialize
     * @fops: the file_operations for this device
     *
     * Initializes @cdev, remembering @fops, making it ready to add to the
     * system with cdev_add().
     */
    void cdev_init(struct cdev *cdev, const struct file_operations *fops)
    {
        memset(cdev, 0, sizeof *cdev);
        INIT_LIST_HEAD(&cdev->list);
        kobject_init(&cdev->kobj, &ktype_cdev_default);
        cdev->ops = fops;
    }
    

    初始化cdev中的成员,需要关注的是cdev->ops = fops;,其实就是将实现的file_operations赋值给cdev。
    初始化kobj成员:

    /**
     * kref_init - initialize object.
     * @kref: object in question.
     */
    static inline void kref_init(struct kref *kref)
    {
        atomic_set(&kref->refcount, 1);  /* 原子操作 */
    }
    
    static void kobject_init_internal(struct kobject *kobj)
    {
        if (!kobj)
            return;
        kref_init(&kobj->kref);
        INIT_LIST_HEAD(&kobj->entry);
        kobj->state_in_sysfs = 0;
        kobj->state_add_uevent_sent = 0;
        kobj->state_remove_uevent_sent = 0;
        kobj->state_initialized = 1;
    }
    
    /**
     * kobject_init - initialize a kobject structure
     * @kobj: pointer to the kobject to initialize
     * @ktype: pointer to the ktype for this kobject.
     *
     * This function will properly initialize a kobject such that it can then
     * be passed to the kobject_add() call.
     *
     * After this function is called, the kobject MUST be cleaned up by a call
     * to kobject_put(), not by a call to kfree directly to ensure that all of
     * the memory is cleaned up properly.
     */
    void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
    {
        char *err_str;
    
        if (!kobj) {
            err_str = "invalid kobject pointer!";
            goto error;
        }
        if (!ktype) {
            err_str = "must have a ktype to be initialized properly!\n";
            goto error;
        }
        if (kobj->state_initialized) {
            /* do not error out as sometimes we can recover */
            printk(KERN_ERR "kobject (%p): tried to init an initialized "
                   "object, something is seriously wrong.\n", kobj);
            dump_stack();
        }
    
        kobject_init_internal(kobj);
        kobj->ktype = ktype;
        return;
    
    error:
        printk(KERN_ERR "kobject (%p): %s\n", kobj, err_str);
        dump_stack();
    }
    EXPORT_SYMBOL(kobject_init);
    

    kobject_init的第二个参数是ktype_cdev_default,它被赋值给了ktype,用来描述一类kobject(cdev使用的kobject)的共同特性。

    static void cdev_default_release(struct kobject *kobj)
    {
        struct cdev *p = container_of(kobj, struct cdev, kobj);
        struct kobject *parent = kobj->parent;
    
        cdev_purge(p);
        kobject_put(parent);
    }
    
    static struct kobj_type ktype_cdev_default = {
        .release    = cdev_default_release,
    };
    

    初始化完毕,将cdev添加到内核

    static struct kobj_map *cdev_map;
    /**
     * cdev_add() - add a char device to the system
     * @p: the cdev structure for the device
     * @dev: the first device number for which this device is responsible
     * @count: the number of consecutive minor numbers corresponding to this
     *         device
     *
     * cdev_add() adds the device represented by @p to the system, making it
     * live immediately.  A negative error code is returned on failure.
     */
    int cdev_add(struct cdev *p, dev_t dev, unsigned count)
    {
        int error;
    
        p->dev = dev;
        p->count = count;
    
        error = kobj_map(cdev_map, dev, count, NULL,
                 exact_match, exact_lock, p);
        if (error)
            return error;
    
        kobject_get(p->kobj.parent);
    
        return 0;
    }
    

    为cdev填充设备号,然后通过kobj_map函数将cdev添加到用于管理设备号和对应设备的kobj_map结构体(cdev_map)中。

    struct kobj_map {
        struct probe {
            struct probe *next;
            dev_t dev;
            unsigned long range;
            struct module *owner;
            kobj_probe_t *get;
            int (*lock)(dev_t, void *);
            void *data;
        } *probes[255];
        struct mutex *lock;
    };
    
    int kobj_map(struct kobj_map *domain, dev_t dev, unsigned long range,
             struct module *module, kobj_probe_t *probe,
             int (*lock)(dev_t, void *), void *data)
    {
        // 1. 根据cdev的信息,对struct probe结构体初始化
        unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1;
        unsigned index = MAJOR(dev);
        unsigned i;
        struct probe *p;
    
        if (n > 255)
            n = 255;
    
        p = kmalloc_array(n, sizeof(struct probe), GFP_KERNEL);
        if (p == NULL)
            return -ENOMEM;
    
        for (i = 0; i < n; i++, p++) {
            p->owner = module;
            p->get = probe;
            p->lock = lock;
            p->dev = dev;
            p->range = range;
            p->data = data;
        }
        // 2. 将载有cdev信息的probe结构体插入到kobj_map中,至此将cdev注册到内核
        mutex_lock(domain->lock);
        for (i = 0, p -= n; i < n; i++, p++, index++) {
            struct probe **s = &domain->probes[index % 255];
            while (*s && (*s)->range < range)
                s = &(*s)->next;
            p->next = *s;
            *s = p;
        }
        mutex_unlock(domain->lock);
        return 0;
    
    

    和cdev_add配套使用的函数是cdev_del函数:

    /**
     * cdev_del() - remove a cdev from the system
     * @p: the cdev structure to be removed
     *
     * cdev_del() removes @p from the system, possibly freeing the structure
     * itself.
     */
    void cdev_del(struct cdev *p)
    {
        cdev_unmap(p->dev, p->count);
        kobject_put(&p->kobj);
    }
    

    初始化硬件资源

    ... ...

    硬件操作接口实现

    上面有提到file_operations结构体,该结构体为文件操作提供了接口。

    一个驱动管理多个外设

    一个主设备号对应一个驱动程序,同一个主设备号不同的每个次设备号对应一个外设,驱动程序如何精确找到外设呢?
    需要使用设备号。那如何找到设备号呢?在file_operations结构描述的一系列文件操作的函数指针的参数中,有两个结构体需要特别注意。(inode和file结构体

    inode结构体

    作用:用来描述一个文件的物理信息(大小,创建日期,修改日期,用户和组,权限等)
    生命周期:文件被创建,内核即创建一个inode结构来描述该文件的物理信息;文件被删除,内核会删除掉对应的inode结构。
    特点:linux允许多个文件共用一个inode结构(硬链接)

    struct inode {
        dev_t           i_rdev;    //存放设备号
        union {
            struct pipe_inode_info  *i_pipe;
            struct block_device *i_bdev;
            struct cdev     *i_cdev;    //描述字符设备的结构
            char            *i_link;
        };
    ... ...
    };
    

    从inode中获取主次设备号的方法:
    unsigned int iminor(struct inode *inode);
    unsigned int imajor(struct inode *inode);

    fs.h中源码实现:

    static inline unsigned iminor(const struct inode *inode)
    {
        return MINOR(inode->i_rdev);
    }
    
    static inline unsigned imajor(const struct inode *inode)
    {
        return MAJOR(inode->i_rdev);
    }
    

    可以看到,就是使用了设备号操作宏MINOR和MAJOR。

    结论:得到inode对象,就可以得到主次设备号

    file结构体

    作用:描述一个文件被打开(open)以后的状态属性
    生命周期:当一个文件被成功打开,内核会创建一个file结构;当文件被关闭,内核也会销毁对应的file对象。

    struct file {
        struct path     f_path;
        struct inode        *f_inode;   /* cached value */
        const struct file_operations    *f_op;
        /* needed for tty driver, and maybe others */
        void            *private_data;
    ... ...
    };
    

    inode和file之间的关系
    struct inode *inode = file->f_inode;
    struct inode *inode = file->f_path.dentry->d_inode;
    找到次设备号
    int minor = MINOR(inode->i_rdev);


    相关文章

      网友评论

          本文标题:字符设备驱动

          本文链接:https://www.haomeiwen.com/subject/cioouhtx.html