简介
字符设备的驱动程序适合于大多数的简单硬件设备,比较容易上手。字符设备不可寻址,仅提供数据的流式访问,通过特殊的文件“字符设备节点”来访问。
1.驱动的配置过程
字符驱动程序的装载过程如上图所示,当驱动编写者编译出.ko文件后,通过insmod指令,执行__init 函数,在该函数中将会调用函数register_chrdev()来完成操作,首先完成设备号的申请、char_device_struct的申请并挂载到哈希表chrdevs中,然后申请到结构体cdev并进行初始化操作(文件操作符、kobj的关联),最后调用函数cdev_add()、kob_map()完成struct probe的申请,将对应的cdev结构体关联到probe结构体中,然后将初始化后的probe结构体按照主次设备号挂载到结构体kobj_map中的哈希表probes中。下面介绍相关的概念。
1.1设备号
主设备号标识设备对应的驱动程序,而次设备号则供内核使用,用于确认设备文件所致的设备。例如鼠标、键盘等虽然是不同的设备文件,但是由于他们都是usb协议,所以可以使用一个USB的驱动程序来驱动,那么他们的主设备号就是一致的,但是次设备号则有所不同。(这里还是有点不清晰,一个驱动程序在装载的过程中已经完成主次设备号的分配了)
这里的区分主次设备号是为了后面注册设备节点做准备,在使用指令mknod 时,主设备号是确认的,次设备号则根据当前设备节点使用情况和最大设备数量决定。
设备号的获取可以通过静态获取(即指定设备号进行获取)也可以通过动态获取。file_operations结构体通过主设备号被保存在哈希表(chrdevs:全局变量)中,对象设备通过次设备号被保存在哈希表(cdev_map->probe:全局变量)中。那么内核只要通过主次设备号和哈希表,就能找到驱动程序和对象设备。
相对来说,发布版本最好使用动态设备号获取,这是由于静态设备号可能会因为环境的变化而起冲突(动态设备号的获取只初始化函数需要在调用register_chrdev_region时传入的设备号为0,具体的原因已经在附录源代码处进行了注释)。
实际上。通过附录数据结构中的介绍,可以知道file_operations结构体本质上是裸机操作的接口,即通过封装的概念将一些底层的驱动程序封装在结构体中,然后在内核中申请设备号,并绑定底层的驱动接口,就可以驱动某些设备了。
#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))
在目前的linux内核中,设备号dev是一个32位的无符号整形数据,主设备号占用12位,次设备号则占用20位。通过内核提供的宏定义可以获取主次设备号。
1.2哈希表-chrdevs
哈希表本质上就是数组加链表的组合结构,这是综合这两者之间的优劣而形成的,他们之间具体的差异可以查看笔记FreeRTOS学习笔记-列表中的相关介绍。
哈希表的结构示意图:
哈希表拥有255个成员,其中每个成员是一个指向链表的指针,某些有关联的信息将构成一个节点,并将节点挂载到对应的链表中。对于这样的结构,快速的找到某个功能的链表入口,并进行访问,同样链表的遍历存在时效的问题。
在哈希表chrdevs中,以主设备号对255求模后的结果为数组的下标 ,次设备号用来辨识不同的设备文件。通过求模来尽量均分主设备号,以达到快速访问的目的。
在分配主设备号的过程中,有部分地方并不清晰,即函数__register_chrdev_region在调用函数find_dynamic_major后,返回了一个主设备号,按照代码执行情况,这个设备号应该是不会重复的:函数优先动态分配255-234的设备号,其次考虑511-384的设备号,在找到预分配设备号时,都会遍历所有的节点,当该节点的主设备号和预分配设备冲突时会重新分配。那么函数__register_chrdev_region中的重叠判断又显得多余了,这让我百思不得其解,有可能是没理解到位。
但是基于预分配非0时(即不通过函数find_dynamic_major动态分配)则会出现基于输入的主次设备号进行分配,那么可能会产生冲突。
主要的冲突在于主设备号冲突时,次设备号的映射范围是否重叠,若次设备号重叠了就无法通过主+次设备号的方式来唯一确认驱动程序和驱动对象!而重叠分为三类:左重叠、右重叠以及包含性重叠。
对于哈希表chrdevs来说,主要的功能是根据主次设备号存储char_device_struct,这个结构体与驱动是对应的。(内核如何使用该结构还不知道,具体看下方2.5)
1.3哈希表-obj_map->probes
与哈希表chrdevs不同的是,probes被保存在全局变量结构体kobj_map中,同样是通过主次设备号进行排序(主设备号决定挂载在probes中的偏移量,次设备号决定在链表中的顺序)。功能上,从chrdevs中可以找到对应驱动设备的文件操作接口、引用次数等等属性。
1.4文件操作接口
内核给驱动程序开放了文件操作接口,该接口可以在附录数据结构中看到,包含了偏移、读取、写入、堵塞式操作等等。驱动开发者只需完善底层程序,然后在初始化函数中调用函数register_chrdev(),即可完成文件操作接口与cdev的结构体(字符设备管理结构体)的绑定。
1.5简单了解设备驱动模型
为了统一设备驱动的模型,Linux提供了sysfs文件系统,他和/proc文件系统是一样的,都希望用户态的程序可以访问到特定的内核数据信息。而普通的kobj结构体是sysfs文件系统的核心数据结构:每个kobj结构体都对应sysfs中的一个目录。具体到后面再进行深入学习。
在Linux中,无论是驱动还是目录都是以文件的形式存在,对于驱动而言,Linux通过结构体kset来关联一个驱动目录,将驱动相关的属性都挂载在这个目录下面。kset结构体的内容可以看附录中的数据结构,它自身继承了kobject结构体的属性(包含了一个这样的结构体),又使用一个表头来控制kobj结构体的链表。实际上Linux通过kset将kobject组织成一棵层次树,kset是同类型kobject结构的一个集合体,这个类型被记录在kset中的ktype中。可以通过调用函数kset_add(); kset_put();或者kobject_add();kobject_put();函数来增减引用的次数,实际上来说,kset的引用次数是引用内嵌的kobject的引用次数。
2.附录 (内附代码注释)
2.1数据结构
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iterate) (struct file *, struct dir_context *);
int (*iterate_shared) (struct file *, struct dir_context *);
__poll_t (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
unsigned long mmap_supported_flags;
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
unsigned (*mmap_capabilities)(struct file *);
#endif
ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
loff_t, size_t, unsigned int);
int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,
u64);
int (*dedupe_file_range)(struct file *, loff_t, struct file *, loff_t,
u64);
int (*fadvise)(struct file *, loff_t, loff_t, int);
} __randomize_layout;
file_operations结构体是内核作为接口提供给驱动模块的工具,在Linux中,任意的模块都可以被看成是一个文件,为了实现这样的哲学思想,Linux内核使用使用了对象与方法的思路,将所有的操作都封装成了方法,所有的文件都看成对象。所以一方面文件操作结构体向应用层提供了驱动的接口,也向驱动层提供了接口函数的形式(传参、返回类型)。 值得注意的是,内核中,初始化这些方法的形式与平常结构体略有不同:
struct file_operations scull_fops{
.owner = THIS_MODULE,
.llseek = scull_llseek,
.read = scull_read,
};
在传统的结构体初始化中,必须是按顺序、全成员的赋值过程。而这里采用的是标准C的标记化结构初始语法,即在成员前加上一个.符号,这样的声明方式则不需要被束缚于传统的赋值规则,那么驱动开发者在开发时则不需要消耗过多的无用功(记忆成员),这样也能有效的提高效率(一些不需要赋值的成员即使赋值成NULL也会消耗性能),顺便吐槽下KEIL好像不支持这个操作。
#define CHRDEV_MAJOR_HASH_SIZE 255
static struct char_device_struct {
struct char_device_struct *next;
unsigned int major;
unsigned int baseminor;
int minorct;
char name[64];
struct cdev *cdev;
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
这里展示了哈希表的初始化过程,他通过声明char_device_struct结构体来指定哈系表中的成员是指向该结构体的指针。
struct kobj_type {
void (*release)(struct kobject *kobj);
const struct sysfs_ops *sysfs_ops;
struct attribute **default_attrs;
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
const void *(*namespace)(struct kobject *kobj);
void (*get_ownership)(struct kobject *kobj, kuid_t *uid, kgid_t *gid);
};
struct kobject {
const char *name;
struct list_head entry;
struct kobject *parent;
struct kset *kset;
struct kobj_type *ktype;
struct kernfs_node *sd;
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;
};
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
} __randomize_layout;
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;
};
struct kset {
struct list_head list;
spinlock_t list_lock;
struct kobject kobj;
const struct kset_uevent_ops *uevent_ops;
} __randomize_layout;
struct list_head {
struct list_head *next, *prev;
};
2.2函数
typedef unsigned long u_long;
typedef u_long dev_t;
int register_chrdev_region(dev_t from, unsigned count, const char *name)
{
struct char_device_struct *cd;
dev_t to = from + count;
dev_t n, next;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
if (next > to)
next = to;
cd = __register_chrdev_region(MAJOR(n), MINOR(n),
next - n, name);
if (IS_ERR(cd))
goto fail;
}
return 0;
fail:
to = n;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
}
return PTR_ERR(cd);
}
#define CHRDEV_MAJOR_DYN_END 234
#define CHRDEV_MAJOR_DYN_EXT_START 511
#define CHRDEV_MAJOR_DYN_EXT_END 384
static int find_dynamic_major(void)
{
int i;
struct char_device_struct *cd;
for (i = ARRAY_SIZE(chrdevs)-1; i >= CHRDEV_MAJOR_DYN_END; i--) {
if (chrdevs[i] == NULL)
return i;
}
for (i = CHRDEV_MAJOR_DYN_EXT_START;
i >= CHRDEV_MAJOR_DYN_EXT_END; i--) {
for (cd = chrdevs[major_to_index(i)]; cd; cd = cd->next)
if (cd->major == i)
break;
if (cd == NULL)
return i;
}
return -EBUSY;
}
static DEFINE_MUTEX(chrdevs_lock);
static struct char_device_struct *
__register_chrdev_region(unsigned int major, unsigned int baseminor,
int minorct, const char *name)
{
struct char_device_struct *cd, **cp;
int ret = 0;
int i;
cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
if (cd == NULL)
return ERR_PTR(-ENOMEM);
mutex_lock(&chrdevs_lock);
if (major == 0) {
ret = find_dynamic_major();
if (ret < 0) {
pr_err("CHRDEV \"%s\" dynamic allocation region is full\n",
name);
goto out;
}
major = ret;
}
if (major >= CHRDEV_MAJOR_MAX) {
pr_err("CHRDEV \"%s\" major requested (%u) is greater than the maximum (%u)\n",
name, major, CHRDEV_MAJOR_MAX-1);
ret = -EINVAL;
goto out;
}
cd->major = major;
cd->baseminor = baseminor;
cd->minorct = minorct;
strlcpy(cd->name, name, sizeof(cd->name));
i = major_to_index(major);
for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next){
if ((*cp)->major > major ||
((*cp)->major == major &&
(((*cp)->baseminor >= baseminor) ||
((*cp)->baseminor + (*cp)->minorct > baseminor))))
break;}
if (*cp && (*cp)->major == major) {
int old_min = (*cp)->baseminor;
int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
int new_min = baseminor;
int new_max = baseminor + minorct - 1;
if (new_max >= old_min && new_max <= old_max) {
ret = -EBUSY;
goto out;
}
if (new_min <= old_max && new_min >= old_min) {
ret = -EBUSY;
goto out;
}
if (new_min < old_min && new_max > old_max) {
ret = -EBUSY;
goto out;
}
}
cd->next = *cp;
*cp = cd;
mutex_unlock(&chrdevs_lock);
return cd;
out:
mutex_unlock(&chrdevs_lock);
kfree(cd);
return ERR_PTR(ret);
}
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)
{
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;
}
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;
}
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;
}
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;
}
|