版权声明:本文为博主原创文章,任何组织或者个人可以在任何媒介上发表或转载我的文章、图片等.且转载后必须注明出处和邮箱,博客地址(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流程
- Linux设备管理(二)_从cdev_add说起
- linux字符cdev和Inode的关系
- Linux字符设备中的两个重要结构体(file、inode)
- Linux–struct file结构体
- Linux 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