本文共 9404 字,大约阅读时间需要 31 分钟。
本篇文章从 一个简单的字符驱动框架 入手.
讲述了 驱动注册\文件创建\应用程序的open\应用程序的read 四个方面来认识 字符设备驱动 到底做了什么.
内核版本为 4.0
提供了
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 numberscdev_init(&c_dev, &fops);//initialize a cdev structurefops.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 中 有 opscdev_add 封装 struct cdev 到 struct probe里面,然后将 struct probe 插入哈希表 cdev_map 中 struct probe 中有 ops 设备号
功能 创建一个 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_instantiate2.3ramfs_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.4d_instantiate 将 得到的 inode 结构体 挂到 dentry(和目录相关) 上
在 ramfs 中创建了一个 inode 节点,然后挂接到了 对应的目录 dentry 上struct inode 中有 ops mode 设备号,设备数目, (名字,按道理来讲,应该还有名字,但是没在代码中找到name 的存在)ops 只有两个 ,一个是 open ,一个是 lseekopen 调用的是 filp->f_op->open在创建文件的时候根本就没考虑驱动的事情.只是向内核 添加了一个结构体
功能 匹配 struct inode 与 struct probe 互相填充 struct inode 与 struct probe 创建 struct file ,并填充 其 f_op 成员 调用 字符驱动文件中的ops 中的 open 成员操作的结构体 inode 与 probe 和 file,前面已经列出 inode 和 probe,下面只列出 filestruct 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);
1.通过 struct inode 中的 inode->i_rdev(设备号) 找到了 struct probe .2.然后 互相填充了 对象的结构体 将 probe->data(这是cdev) 填充到了 inode->i_cdev 将 inode 插入了 cdev->list3.创建了 一个 struct file 结构体(其实在步骤1之前创建的,然后填充f_op 是在步骤2后面) 将 probe->data->ops 填充到了 file->f_op4.调用 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
这里面涉及到四个结构体,这里把 probe 和 cdev 看成了一个结构体 char_device_struct probe cdev inode filechar_device_struct 用来统计设备号cdev 用来 存储 ops, probe 用来存储 cdevinode 用来 对应文件节点file 用来 对应打开的文件
转载地址:http://xxigi.baihongyu.com/