前言
数据结构是字符设备驱动的核心,其它很多代码都是围绕数据结构展开的,所以有必要对字符设备驱动数据结构做一个梳理,先看三张图,便于从宏观上了解字符设备驱动程序.
一.管理系统设备号分配
定义:
static struct char_device_struct {
struct char_device_struct *next; // 指向下一个char_device_struct 结构体
unsigned int major; // 主设备号
unsigned int baseminor; // 从设备号起始值
int minorct;// 从设备号数量
char name[64];// 设备或驱动的名字
struct cdev *cdev; /* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE]; // CHRDEV_MAJOR_HASH_SIZE是255
说明:
1.新分配的设备号
从254开始搜索到0,如果chrdevs[i]为空,则让chrdevs[i]指向新分配的主设备号char_device_struct 结构体,i就是分配的设备号
2.cat /proc/devices 可以查看系统的字符设备和块设备,这些信息就取自chrdevs
内核代码如下:
void chrdev_show(struct seq_file *f, off_t offset)
{
struct char_device_struct *cd;
if (offset < CHRDEV_MAJOR_HASH_SIZE) {
mutex_lock(&chrdevs_lock);
for (cd = chrdevs[offset]; cd; cd = cd->next)
seq_printf(f, "%3d %s\n", cd->major, cd->name);
mutex_unlock(&chrdevs_lock);
}
}
查询结果:
二.字符设备结构体
定义:
struct cdev {
struct kobject kobj; // sysfs 内核对象
struct module *owner;//该字符设备所在的内核模块(所有者)的对象指针,一般为THIS_MODULE主要用于模块计数
const struct file_operations *ops;//该结构描述了字符设备所能实现的操作集(打开、关闭、读/写、...),是极为关键的一个结构
struct list_head list;//用来将已经向内核注册的所有字符设备形成链表
dev_t dev; //字符设备的设备号,由主设备号和次设备号构成(如果是一次申请多个设备号,此设备号为第一
unsigned int count;//隶属于同一主设备号的次设备号的个数
};
static struct cdev gpio_cdev;
说明:
1.cdev表示一个字符设备结构体,在字符设备注册时作为kobj_map 结构体的data数据成员
三.管理字符设备注册进系统
定义:
struct kobj_map {
struct probe { // 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;
};
static struct kobj_map *cdev_map;
说明:
1.cdev_map指针初始化:start_kernel()->vfs_caches_init()->chrdev_init()->kobj_map_init();
2.cdev_map指向每个注册到系统的字符设备
三.inode结构体
定义:
/*
* 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;
uid_t i_uid;
gid_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;
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;
blkcnt_t i_blocks;
loff_t i_size;
#ifdef __NEED_I_SIZE_ORDERED
seqcount_t i_size_seqcount;
#endif
/* Misc */
unsigned long i_state;
struct mutex i_mutex;
unsigned long dirtied_when; /* jiffies of first dirtying */
struct hlist_node i_hash;
struct list_head i_wb_list; /* backing dev IO list */
struct list_head i_lru; /* inode LRU list */
struct list_head i_sb_list;
union {
struct list_head i_dentry;
struct rcu_head i_rcu;
};
atomic_t i_count;
unsigned int i_blkbits;
u64 i_version;
atomic_t i_dio_count;
atomic_t i_writecount;
const struct file_operations *i_fop; /* former ->i_op->default_file_ops */
struct file_lock *i_flock;
struct address_space i_data;
#ifdef CONFIG_QUOTA
struct dquot *i_dquot[MAXQUOTAS];
#endif
struct list_head i_devices;
union {
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
struct cdev *i_cdev;// 字符设备驱动cdev结构体
};
__u32 i_generation;
#ifdef CONFIG_FSNOTIFY
__u32 i_fsnotify_mask; /* all events this inode cares about */
struct hlist_head i_fsnotify_marks;
#endif
#ifdef CONFIG_IMA
atomic_t i_readcount; /* struct files open RO */
#endif
void *i_private; /* fs or device private pointer */
};
说明:
inode结构体的详细解析参考深入浅出理解linux inode结构
VFS inode 包含文件访问权限、属主、组、大小、生成时间、访问时间、最后修改时间等信息。它是Linux 管理文件系统的最基本单位,也是文件系统连接任何子目录、文件的桥梁。
内核使用inode结构体在内核内部表示一个文件。因此,它与表示一个已经打开的文件描述符的结构体(即file 文件结构)是不同的,我们可以使用多个file 文件结构表示同一个文件的多个文件描述符,但此时,所有的这些file文件结构全部都必须只能指向一个inode结构体。
inode结构体包含了一大堆文件相关的信息,但是就针对驱动代码来说,我们只要关心其中的两个域即可:
(1) dev_t i_rdev;
表示设备文件的结点,这个域实际上包含了设备号。
(2) struct cdev *i_cdev;
struct cdev是内核的一个内部结构,它是用来表示字符设备的,当inode结点指向一个字符设备文件时,此域为一个指向inode结构的指针。
下面看一下上层应用open() 调用系统调用函数的过程
对于一个字符设备文件, 其inode->i_cdev 指向字符驱动对象cdev, 如果i_cdev为 NULL ,则说明该设备文件没有被打开.
由于多个设备可以共用同一个驱动程序.所以,通过字符设备的inode 中的i_devices 和 cdev中的list组成一个链表
首先,系统调用open打开一个字符设备的时候, 通过一系列调用,最终会执行到 chrdev_open
(最终是通过调用到def_chr_fops中的.open, 而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.
2. 设置inode->i_cdev , 指向找到的cdev.
3. 将inode添加到cdev->list 的链表中.
4. 使用cdev的ops 设置file对象的f_op
5. 如果ops中定义了open方法,则调用该open方法
6. 返回
执行完 chrdev_open()之后,file对象的f_op指向cdev的ops,因而之后对设备进行的read, write等操作,就会执行cdev的相应操作。