目录
一、概述
二、字符设备
2.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;
抽象结构cdev{},具体看下字段
- 继承关系:kobj
- 属性: count,dev,owner
- 方法: ops
- 管理: list
2.1.1 字符设备方法
file_operations{},放到文件的方法中
2.1.2 cdev接口
- struct cdev *cdev_alloc(void);
cdev结构的分配可以使用动态或者静态的方法,cdev_alloc对应动态分配,简单看下实现:
struct cdev *cdev_alloc(void)
{
struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
if (p) {
INIT_LIST_HEAD(&p->list);
kobject_init(&p->kobj, &ktype_cdev_dynamic);
}
return p;
}
一般的cdev作为父类嵌入到具体的字符设备时,此时cdev是直接嵌入的,无法使用cdev_alloc了,这时候可以随子类一起分配,并调用
cdev_init初始化:
- void cdev_init(struct cdev *, const struct file_operations *);
cdev初始化完成,要被内核管理起来,通过下面的注册接口:
- int cdev_add(struct cdev *p, dev_t dev, unsigned count)
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, //是通过hash 主设备号保存数据cdev
exact_match, exact_lock, p);
if (error)
return error;
kobject_get(p->kobj.parent);
return 0;
}
-
void cdev_del(struct cdev *p)
2.2 设备号
字符设备通过设备号标识,在内核中通过如下结构表示:
[linux/include/types.h]
typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;
设备号分为主设备号和次设备号,主设备号指定驱动程序,次设备号指定设备在该驱动程序中的哪一个,需要使用下面的宏操作:
#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))
2.2.1 设备号相关的接口
- int register_chrdev_region(dev_t from, unsigned count, const char *name)
该函数分配调用者指定的设备号——从from到from+count
int register_chrdev_region(dev_t from, unsigned count, const char *name)
{
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
if (next > to) //从这可以看出,count是可以跨越主设备号的,但是处理上每次最多处理一个主设备号
next = to;
cd = __register_chrdev_region(MAJOR(n), MINOR(n),
next - n, name);
}
设备号在内核中是以hash管理的,相关的数据结构:
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 */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
共有CHRDEV_MAJOR_HASH_SIZE个slot,通过major,即主设备号进行hash,而 __register_chrdev_region在指定设备号的情况下,只是进行一个hash的插入操作(片段):
static struct char_device_struct *
__register_chrdev_region(unsigned int major, unsigned int baseminor,
int minorct, const char *name)
{
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;
cd->next = *cp;
*cp = cd;
}
- int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
动态分配主设备号。
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
{
struct char_device_struct *cd;
cd = __register_chrdev_region(0, baseminor, count, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
*dev = MKDEV(cd->major, cd->baseminor);
return 0;
}
在__register_chrdev_region对应如下片段:
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;
}
通过find_dynamic_major看到分配规则:
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--) { //254开始递减查找
if (chrdevs[i] == NULL)
return i;
}
#define CHRDEV_MAJOR_DYN_EXT_START 511
#define CHRDEV_MAJOR_DYN_EXT_END 384
for (i = CHRDEV_MAJOR_DYN_EXT_START;
i >= CHRDEV_MAJOR_DYN_EXT_END; i--) { //如果没有,则从511-384寻找
for (cd = chrdevs[major_to_index(i)]; cd; cd = cd->next)
if (cd->major == i)
break;
if (cd == NULL)
return i;
}
return -EBUSY;
}
- void unregister_chrdev_region(dev_t from, unsigned count)
三、设备节点
设备节点可以静态创建:
mknod /dev/testcdev c 2 0
也可以动态创建:TO DO
四、实例
在实际应用中,cdev处了用于真实驱动之外,还经常被作为一个基本的框架来使用,一些(如misc)利用cdev达到为上层提供更复杂功能的目的。从上面可知,一个字符驱动的基本步骤:
- 分配并初始化cdev
- 分配设备号
- 注册cdev到系统中
misc也是一个字符设备,他使用主设备号10,在misc_init中调用了register_chrdev
register_chrdev(MISC_MAJOR, "misc", &misc_fops)
int __register_chrdev(unsigned int major, unsigned int baseminor,
unsigned int count, const char *name,
const struct file_operations *fops)
{
struct char_device_struct *cd;
struct cdev *cdev;
cd = __register_chrdev_region(major, baseminor, count, name);
cdev = cdev_alloc();
cdev->owner = fops->owner;
cdev->ops = fops;
kobject_set_name(&cdev->kobj, "%s", name);
err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
}
可以看出就是上面三个步骤的罗列。
misc通过预先向cdev申请一个主设备号为10,次设备号数量为255的设备资源。通过提供misc_register接口提供misc设备注册,每个misc设备可以动态或静态指定次设备号,misc通过链表将misc设备管理起来并通过次设备号区分,当任意misc设备对其方法进行调用时,misc会将其重载成对应的misc设备方法,这样节省了主设备号
- int misc_register(struct miscdevice *misc)