字符设备驱动框架分析(基于linux2.6内核)
情景分析:
对于驱动,我们并不陌生,在linux端,由应用软件操作一个硬件设备,一般是不是直接去操作的,并不像单片机一样,我们是通过向linux内核申请设备节点,申请成功的设备节点在/dev目录,从而操作dev目录下的设备节点,控制我们的硬件设备,其中字符设备驱动框架是常见的一种驱动类型(字符设备、块设备、网络设备驱动三大类型)
那么在dev目录下的设备节点是怎么来的呢??很简单,当然是加载我们的驱动程序生成的,linux才不会那么智能,知道我有哪些外设,就生成对应的外设的,linux只是提供一个框架给我们,需要我们自己去编写对应的驱动,这样一来的好处就是在linux上操作硬件的时候,硬件和软件就直接分离开了,方便我们软件开发人员去操作硬件设备,而不需要我们软件人员了解硬件接口,那么怎么去加载这个设备节点呢??
下面我们将从如何生成这个设备节点开始讲起:由于是在linux系统下面,要想在linux系统下面生成设备节点,肯定是需要向linux内核申请的,linux内核中申请成功,才会给你一个唯一的一个设备节点(主设备号、次设备号),在linux内核中有这样的一个结构体struct cdev;linux2.6内核中定义该结构体的文件是linux-2.6.32.1\include\linux\ cdev.h,这个结构体将是连接软件和硬件的桥梁,包括申请的设备号(主设备号、次设备号),/dev目录下生成的文件名称、支持哪些操作
struct cdev {
struct kobject kobj; /*基于kobject*/
struct module *owner; /*所属模块*/
const struct file_operations *ops; /*设备文件操作函数集*/
struct list_head list; /*挂载设备的链表头*/
dev_t dev; /*设备号而dev_t则为U32类型的也就是32位,其中12位为主设备号,20位为次设备号*/
unsigned int count; /*该种类型设备数目*/
};
(一)、将设备号注册到内核中
- 动态注册
alloc_chrdev_region();-à由内核注册,自动分配没有使用的设备号
底层还是在调用__register_chrdev_region()函数该函数有2个功能,如果第一个参数传入的是0,那么会自动扫描全局变量chrdevs,根据填充的数字,继续往下给自动分配一个主设备号
- 静态注册(需要申请设备号)
int register_chrdev_region(dev_t from, unsigned count, const char *name)--à由程序员自己控制,有可能会导致注册失败,一般在注册失败后在转为自动注册函数源文件的路径在:linux-2.6.32.1\fs\Char_dev.c下面我们看register_chrdev_region的参数:
dev_t from:设备号(包括主设备号和次设备号起始数),将主设备号和次设备号起始数转换成dev_t类型的一个内核函数,MKDEV原型是#define MKDEV(ma,mi) ((ma)<<20 | (mi)) 这句话的意思就是主设备号是第20位-31位表示,次设备号是0-20位表示,换句话说就是低20位表示次设备号,高12位表示主设备号
unsigned count:次设备号的个数
const char *name:名称节点
register_chrdev_region()
--à __register_chrdev_region()在该函数内将一个全局变量chrdevs[]填充;
自动注册也好、手动注册也罢,其实底层都是在调用__register_chrdev_region()函数。还有一个就是直接调用__register_chrdev_region()函数,通过控制第一个参数是否为0,来控制自动注册和静态注册,这样可能看起来会更清楚,以上是对跟内核相关的操作,说到底,还是没有发现是怎么操作设备的,请看下面
(二)、初始化cdev结构体cdev_init();
函数原型:
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);//初始化kobj(有点看不懂,单独拿出来进行分析)
cdev->ops = fops;//将驱动里面写的fops函数注册到系统中
}
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;
};
struct kobj_type {
void (*release)(struct kobject *kobj);
struct sysfs_ops *sysfs_ops;
struct attribute **default_attrs;
};
分析函数kobject_init(struct kobject *kobj, struct kobj_type *ktype)
{
char *err_str;
if (!kobj) { //输入参数1判断
err_str = "invalid kobject pointer!";
goto error;
}
if (!ktype) {//输入参数2判断
err_str = "must have a ktype to be initialized properly!\n";
goto error;
}
if (kobj->state_initialized) { //指明kobject是否有被初始化
/* do not error out as sometimes we can recover */
printk(KERN_ERR "kobject (%p): tried to init an initialized "
"object, something is seriously wrong.\n", kobj);
dump_stack();
}
kobject_init_internal(kobj);
kobj->ktype = ktype;
return;
error:
printk(KERN_ERR "kobject (%p): %s\n", kobj, err_str);
dump_stack();
}
继续对上面的函数进行分析。
前面先对输入的2个参数进行参数校验,不需要管,
在判断传入的参数1是否被初始化(根据state_initialized成员变量)最重要的就是下面的2行代码:
1、kobject_init_internal(kobj);
2、kobj->ktype = ktype;
kobj->ktype = ktype ---》1、先分析第二行代码kobj->ktype = ktype;代码很好理解就是将传入的struct kobj_type 类型的变量 ktype赋值到kobj变量中,那么问题来了?传入的参数2从哪里来的?
参数2 :
ktype_cdev_default:是一个全局变量,结构体是
struct kobj_type {
void (*release)(struct kobject *kobj);
struct sysfs_ops *sysfs_ops;
struct attribute **default_attrs;
};
其中一个是void (*release)(struct kobject *kobj),是不是跟第一个参数的类型一样的呢????
是一个static的变量,继续填充该结构体
static struct kobj_type ktype_cdev_dynamic = {
.release = cdev_dynamic_release,
};
可以理解的就是cdev_dynamic_release 类型是struct kobject,我们会想,会不会是用这个结构体去初始化我们自己定义的变量struct cdev cdev 变量kobj的呢??
我们继续追踪cdev_dynamic_release,
static void cdev_dynamic_release(struct kobject *kobj)
{
struct cdev *p = container_of(kobj, struct cdev, kobj);//看不懂。。。。不做分析,差不多就是对kobj做处理,内核处理
cdev_purge(p);//看不太懂,大致上就是初始化一个链表,用来存储节点信息的
kfree(p);
}
在分析第一行代码kobject_init_internal(kobj);
{
if (!kobj)
return;
kref_init(&kobj->kref);
INIT_LIST_HEAD(&kobj->entry);
kobj->state_in_sysfs = 0;
kobj->state_add_uevent_sent = 0;
kobj->state_remove_uevent_sent = 0;
kobj->state_initialized = 1; //表示被初始化过
}
总结:cdev_init()函数主要是对我们自己定义的变量struct cdev _cdev进行一系列的初始化,包括建立链表,初始化表头,更包括初始化的标志位置1,好比是申请了一个容器,用来装我们的设备信息的。。。。
(三)、有的人就想问了,我们一开始我们在第一步申请了设备号,到底有什么用??不要急,慢慢来,既然我们有了第一步设备号信息,第二步还初始化了容器,建立了一些链表,第三步是不是需要将这第一步申请的设备号信息添加到容器中呢???果然,内核果然给我们提供了这个接口,int cdev_add(struct cdev *p, dev_t dev, unsigned count)连传入的参数类型都貌似一样的哦??
第一个参数是我们初始化的容器,链表,struct cdev类型的,
第二个参数是设备号dev_t类型的,
第三个参数是不是就是注册的次设备号的最大个数呢??
请看源代码
{
p->dev = dev;
p->count = count;
return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}
果然,跟我们猜想的一模一样,将p->dev填充dev和p->count填充count到容器链表中,
下面分析:kobj_map
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;
}
--------------分析不下去了,凉凉。。。可以得出结论就是将设备节点等加到系统中。
(四)、创建类------待更新中 10-19