前面两篇文章仅仅把最简单的字符设备的代码放上来了,并末做更深入的分析,下面就对函数进行一个个的分析。
首先我们在注册函数里面调用了register_chrdev(MEM_MAJOR,"mem",&memory_fops),向内核注册了一个字符设备。
第一个参数是主设备号,0代表动态分配,这里的MEM_MAJOR是1。第二个参数是设备的名字,第三个参数是文件操作指针。
完成注册后,在/proc/devices中的第一个字符设备我们就看到了:1 mem。
1.前面提到了注册,那这个字符设备到底注册到哪里去了呢?这是要弄明白的第一个问题。
其实是注册到一个存放字符设备的链表中了:
fs/char_dev.c
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的大小是255,也就是这里最多能够存放的主设备号最多有255个。
这里还是一点需要注意的是这里分配的是一个指针数组。
cd->next = *cp;
*cp = cd;
首先*cp代表的是当前相同的主设备号中最后面的一个,当然这这里的*cp是指向NULL的,然后把*cp更新为cd。
可以注意到这里好像并没有struct cdev什么事情,下面就对其进行初始化。
2.cdev_add第二个任务,add a char device to the system,make it live immediately
先介绍两个遇到的结构体:
include/linux/cdev.h
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
可进行的操作有:
void cdev_init(struct cdev *, const struct file_operations *);
struct cdev *cdev_alloc(void);
void cdev_put(struct cdev *p);
int cdev_add(struct cdev *, dev_t, unsigned);
void cdev_del(struct cdev *);
void cd_forget(struct inode *);
在结构体cdev里出现了另外一个极其重要的结构体struct kobject,include/linux/kobject.h
struct kobject {
const char *name;
struct list_head entry;
struct kobject *parent;
struct kset *kset;
struct kobj_type *ktype;
struct sysfs_dirent *sd;
struct kref kref;
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;
};
主要的操作有:
extern void kobject_init(struct kobject *kobj, struct kobj_type *ktype);
extern void kobject_del(struct kobject *kobj);
extern struct kobject *kobject_get(struct kobject *kobj);
extern void kobject_put(struct kobject *kobj);extern void kobject_put(struct kobject *kobj);
cdev_add里面只调用了一个函数:kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
cdev_map是fs/char_dev.h里定义的一个结构体变量,而kobj_map的作用就是初始化它。
static struct kobj_map *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;
} *probes[255];
struct mutex *lock;
};
kobj_map:
内核中所有都字符设备都会记录在一个 kobj_map 结构的 cdev_map 变量中。这个结构的变量中包含一个散列表用来快速存取所有的对象。kobj_map() 函数就是用来把字符设备编号和 cdev 结构变量一起保存到 cdev_map 这个散列表里。当后续要打开一个字符设备文件时,通过调用 kobj_lookup() 函数,根据设备编号就可以找到 cdev 结构变量,从而取出其中的 ops 字段。
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(sizeof(struct probe) * n, 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;
}
现在完成的仅仅是注册,下面还有一些重要的事情需要完成。