字符驱动框架函数解析

版权声明:本文为博主原创文章,任何组织或者个人可以在任何媒介上发表或转载我的文章、图片等.且转载后必须注明出处和邮箱,博客地址(https://blog.csdn.net/u011011827),本人邮箱([email protected]) https://blog.csdn.net/u011011827/article/details/81983241
前言

本篇文章从 一个简单的字符驱动框架 入手.

讲述了 驱动注册\文件创建\应用程序的open\应用程序的read 四个方面来认识 字符设备驱动 到底做了什么.

内核版本为 4.14.8

并没有提供字符驱动框架代码,需要的话可以在网上搜索.

驱动注册

  • 驱动注册代码片段

struct file_operations fops = {
    .owner=THIS_MODULE,
    .open = open_driver,
    .read = read_driver,
    .write = write_driver,
    .unlocked_ioctl = unlocked_ioctl_driver,
    .release = release_driver,
};


struct cdev c_dev;


dev_t dev_number=MKDEV(500,0);

register_chrdev_region(dev_number,1,"new_device");//register a range of device numbers



cdev_init(&c_dev, &fops);//initialize a cdev structure
fops.owner = THIS_MODULE;
cdev_add(&c_dev,dev_number,1);//add a char device to the system


功能
    实际上就是将 主设备号和 此设备号 转换成一个 设备
函数操作的结构体
    无

功能
    register_chrdev_region 实际上就是在 chrdevs  哈希表中 添加了一个或 多个结构体
    通过chrdevs数组,我们就可以知道分配了哪些设备号

函数操作的结构体

static struct char_device_struct {                                                  
    struct char_device_struct *next;                                                
    unsigned int major;                                                             
    unsigned int baseminor;                                                         
    int minorct;                                                                    
    char name[64];                                                                  
    struct cdev *cdev;      /* will die */                                          
};




这个函数执行之后,就会在 /proc/devices 加上一行

500 new_device

是因为 cat /proc/devices  的时候会遍历 各个 设备节点的 设备号.



功能
    这个函数就是在填充 struct cdev 变量,没有和外界有任何接触.
    主要内容是将 ops 填充了
函数操作的结构体

struct cdev {
   struct kobject kobj;          // 每个 cdev 都是一个 kobject
   struct module *owner;       // 指向实现驱动的模块
   const struct file_operations *ops;   // 操纵这个字符设备文件的方法
   struct list_head list;       // 与 cdev 对应的字符设备文件的 inode->i_devices 的链表头
   dev_t dev;                   // 起始设备编号
   unsigned int count;       // 设备范围号大小
};




功能
    可见,将 cdev 结构体 又封装了一下,封装成了 struct probe ,然后 添加到 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;  //用来存储 cdev

  } *probes[255];

  struct mutex *lock;

};

驱动注册总结(字符驱动注册)


register_chrdev_region 
    将一个结构体 struct char_device_struct 插入哈希表 chrdevs 中
    struct char_device_struct 中有 设备号,名字(这个名字不是设备在文件系统中的名字,是设备号的名字)
cdev_init
    初始化一个结构体 struct cdev
    struct cdev 中 有  ops
cdev_add
    封装 struct cdev 到 struct probe里面,然后将 struct probe 插入哈希表 cdev_map 中
    struct probe 中有 ops  设备号

文件创建

  • mknod /dev/new_device c 500 0

功能
    创建一个 inode 结构体,并挂接到 与目录相关的 dentry 上

函数操作的结构体
/*
 * Keep mostly read-only and often accessed (especially for
 * the RCU path lookup and 'stat' data) fields at the beginning
 * of the 'struct inode'
 */
struct inode {
    umode_t         i_mode;
    unsigned short      i_opflags;
    kuid_t          i_uid;
    kgid_t          i_gid;
    unsigned int        i_flags;

#ifdef CONFIG_FS_POSIX_ACL
    struct posix_acl    *i_acl;
    struct posix_acl    *i_default_acl;
#endif

    const struct inode_operations   *i_op;
    struct super_block  *i_sb;
    struct address_space    *i_mapping;

#ifdef CONFIG_SECURITY
    void            *i_security;
#endif

    /* Stat data, not accessed from path walking */
    unsigned long       i_ino;
    /*
     * Filesystems may only read i_nlink directly.  They shall use the
     * following functions for modification:
     *
     *    (set|clear|inc|drop)_nlink
     *    inode_(inc|dec)_link_count
     */
    union {
        const unsigned int i_nlink;
        unsigned int __i_nlink;
    };
    dev_t           i_rdev;
    loff_t          i_size;
    struct timespec     i_atime;
    struct timespec     i_mtime;
    struct timespec     i_ctime;
    spinlock_t      i_lock; /* i_blocks, i_bytes, maybe i_size */
    unsigned short          i_bytes;
    unsigned int        i_blkbits;
    enum rw_hint        i_write_hint;
    blkcnt_t        i_blocks;

#ifdef __NEED_I_SIZE_ORDERED
    seqcount_t      i_size_seqcount;
#endif

    /* Misc */
    unsigned long       i_state;
    struct rw_semaphore i_rwsem;

    unsigned long       dirtied_when;   /* jiffies of first dirtying */
    unsigned long       dirtied_time_when;

    struct hlist_node   i_hash;
    struct list_head    i_io_list;  /* backing dev IO list */
#ifdef CONFIG_CGROUP_WRITEBACK
    struct bdi_writeback    *i_wb;      /* the associated cgroup wb */

    /* foreign inode detection, see wbc_detach_inode() */
    int         i_wb_frn_winner;
    u16         i_wb_frn_avg_time;
    u16         i_wb_frn_history;
#endif
    struct list_head    i_lru;      /* inode LRU list */
    struct list_head    i_sb_list;
    struct list_head    i_wb_list;  /* backing dev writeback list */
    union {
        struct hlist_head   i_dentry;
        struct rcu_head     i_rcu;
    };
    u64         i_version;
    atomic_t        i_count;
    atomic_t        i_dio_count;
    atomic_t        i_writecount;
#ifdef CONFIG_IMA
    atomic_t        i_readcount; /* struct files open RO */
#endif
    const struct file_operations    *i_fop; /* former ->i_op->default_file_ops */
    struct file_lock_context    *i_flctx;
    struct address_space    i_data;
    struct list_head    i_devices;
    union {
        struct pipe_inode_info  *i_pipe;
        struct block_device *i_bdev;
        struct cdev     *i_cdev;
        char            *i_link;
        unsigned        i_dir_seq;
    };

    __u32           i_generation;

#ifdef CONFIG_FSNOTIFY
    __u32           i_fsnotify_mask; /* all events this inode cares about */
    struct fsnotify_mark_connector __rcu    *i_fsnotify_marks;
#endif

#if IS_ENABLED(CONFIG_FS_ENCRYPTION)
    struct fscrypt_info *i_crypt_info;
#endif

    void            *i_private; /* fs or device private pointer */
} __randomize_layout;



操作流程


1. 函数注释

int mknod(const char *path, mode_t mode, dev_t dev);
第一个参数表示你要创建的文件的名称
第二个参数表示文件类型
第三个参数表示该文件对应的设备文件的设备号。
只有当文件类型为 S_IFCHR 或 S_IFBLK 的时候该文件才有设备号,创建普通文件时传入0即可。



2.调用流程

2.1     
asmlinkage long sys_mknod(const char __user *filename, int mode, unsigned dev) ;
    sys_mknod((const char __user *) "/dev/console",
            S_IFCHR | S_IRUSR | S_IWUSR,
        new_encode_dev(MKDEV(5, 1)));

2.2
最终调用到 ramfs 中的 ramfs_get_inode 和 d_instantiate

2.3
ramfs_get_inode 
    得到一个 inode 结构体
        inode->i_mode = mode;  // 创建模式
        inode->i_fop = &def_chr_fops; // ops,这个ops 不是我们写的驱动中的ops.
        inode->i_rdev = rdev; // 设备号

            const struct file_operations def_chr_fops = {                                       
                .open = chrdev_open,                                                            
                .llseek = noop_llseek,                                                          
            }; 

            static int chrdev_open(struct inode *inode, struct file *filp)
                filp->f_op->open(inode, filp);

2.4
d_instantiate 
    将 得到的 inode 结构体 挂到 dentry(和目录相关) 上


文件创建总结


在 ramfs 中创建了一个 inode 节点,然后挂接到了 对应的目录 dentry 上
struct inode 中有 ops mode 设备号,设备数目, (名字,按道理来讲,应该还有名字,但是没在代码中找到name 的存在)
ops 只有两个 ,一个是 open ,一个是 lseek
open 调用的是 filp->f_op->open


在创建文件的时候根本就没考虑驱动的事情.只是向内核 添加了一个结构体

open流程


功能
    匹配 struct inode  与 struct probe 
    互相填充  struct inode  与 struct probe 
    创建 struct file ,并填充 其 f_op 成员
    调用 字符驱动文件中的ops 中的 open 成员

操作的结构体
    inode 与 probe 和 file,前面已经列出 inode 和 probe,下面只列出 file

struct file {
    union {
        struct llist_node   fu_llist;
        struct rcu_head     fu_rcuhead;
    } f_u;
    struct path     f_path;
    struct inode        *f_inode;   /* cached value */
    const struct file_operations    *f_op;

    /*
     * Protects f_ep_links, f_flags.
     * Must not be taken from IRQ context.
     */
    spinlock_t      f_lock;
    enum rw_hint        f_write_hint;
    atomic_long_t       f_count;
    unsigned int        f_flags;
    fmode_t         f_mode;
    struct mutex        f_pos_lock;
    loff_t          f_pos;
    struct fown_struct  f_owner;
    const struct cred   *f_cred;
    struct file_ra_state    f_ra;

    u64         f_version;
#ifdef CONFIG_SECURITY
    void            *f_security;
#endif
    /* needed for tty driver, and maybe others */
    void            *private_data;

#ifdef CONFIG_EPOLL
    /* Used by fs/eventpoll.c to link all the hooks to this file */
    struct list_head    f_ep_links;
    struct list_head    f_tfile_llink;
#endif /* #ifdef CONFIG_EPOLL */
    struct address_space    *f_mapping;
    errseq_t        f_wb_err;
} __randomize_layout
  __attribute__((aligned(4)));  /* lest something weird decides that 2 is OK */

struct file_handle {
    __u32 handle_bytes;
    int handle_type;
    /* file identifier */
    unsigned char f_handle[0];
};



操作流程


系统调用open打开一个字符设备的时候

1.先创建一个 struct file对象

2.通过一系列调用, 会找到 inode
    然后找到 inode 的 inode->i_fop ,然后就找到 def_chr_fops ,然后就找到 def_chr_fops.open ,然后就找到chrdev_open


  int chrdev_open(struct inode * inode, struct file * filp)

  chrdev_open()所做的事情可以概括如下:

  1. 根据设备号(inode->i_rdev), 在字符设备驱动模型中查找对应的驱动程序, 这通过kobj_lookup() 来实现, kobj_lookup()会返回对应驱动程序cdev的kobject.
            kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);
  2. 设置inode->i_cdev , 指向找到的cdev.
            new = container_of(kobj, struct cdev, kobj);
            inode->i_cdev = p = new;
  3. 将inode添加到cdev->list的链表中.
            list_add(&inode->i_devices, &p->list);
  4. 使用cdev的ops 设置 file 对象的f_op
            fops = fops_get(p->ops);
            replace_fops(filp, fops);
            此时 file 对象 filp 的f_op 成员 指向cdev的ops,读写的时候就可以通过 filp->f_op->read 来调用
  5. 如果ops中定义了open方法,则调用该open方法
            filp->f_op->open(inode, filp);

open流程总结


1.
通过  struct inode  中的 inode->i_rdev(设备号) 找到了 struct probe .

2.
然后 互相填充了 对象的结构体
    将 probe->data(这是cdev) 填充到了 inode->i_cdev
    将 inode 插入了 cdev->list
3.
创建了 一个 struct file 结构体(其实在步骤1之前创建的,然后填充f_op 是在步骤2后面)
    将 probe->data->ops 填充到了 file->f_op

4.
调用 filp->f_op->open();



inode 和 probe 和 char_device_struct 都存在设备号成员

所以 open 可以 凭借设备号来 匹配这三者(其实,并没有用到char_device_struct)

文件读写


功能
    调用 cdev 中注册的 ops 

操作的结构体
    struct file 


操作流程

系统调用 read 打开一个字符设备的时候(在open 之后)

1. 找到对应的 struct file对象 filp

2. 调用 filp->f_op->read  //就是调用 cdev->ops->read


总结_all


这里面涉及到四个结构体,这里把 probe 和 cdev 看成了一个结构体
    char_device_struct 
    probe
        cdev
    inode
    file

char_device_struct 用来统计设备号

cdev  用来 存储 ops, probe 用来存储 cdev

inode 用来 对应文件节点

file  用来 对应打开的文件
其他
  • 这里有一点还没说,就是cdev 中的 kobject

猜你喜欢

转载自blog.csdn.net/u011011827/article/details/81983241