Linux早期时候,一个驱动对应一个设备,也就对应一个硬件地址,那当有两个一样的设备的时候,就要写两个驱动,显然是不合理的。应该是从Linux2.5开始,就引入了device-bus-driver模型。 其中设备驱动模型主要结构分为kset、kobject、ktype。
设备驱动模型的核心即是kobject,是为了管理日益增多的设备,使得设备在底层都具体统一的接口。他与sysfs文件系统紧密相连,每个注册的kobject都对应sysfs文件系统中的一个目录。为了直观管理,统一存放的路径,使用了kset。但是仅仅有这些目录没有意义,这两个结构体只能表示出设备的层次关系,所以基本不单独使用,会嵌入到更大的结构体中,(如希望在驱动目录下能看到挂在该总线上的各种驱动,而在设备目录下能看到挂在该总线的各种设备,就将kobject嵌入到描述设备以及驱动的结构体中,这样每次注册设备或驱动,都会在sys目录下有描述)
这个图其实还漏了一个ktype,kobject都应该包含一个ktype。 Linux设备模型的目的是:为内核建立起一个统一的设备模型,从而有一个对系统结构的一般性抽象描述。 我们可以先看下一个小的测试程序: //点击图片可放大 可以看到,我们在使用kobject、kset、ktype结构,就在sysfs虚拟文件系统下创建(通过kset_create_and_add和kobject_init_and_add函数)了一些子目录(kobject_test)和属性文件。kset和kobject都可以创建出目录,但是kset的目录下存放kobject目录,kobject下存放属性文件(可以对属性文件进行读写操作,如上图name属性文件,而且kobject目录下也可以存放kobject目录,只需parent指向它即可)。
我们对着Linux kernel源码分析下,可以下看看三个结构体的成员:
其实说到设备驱动模型,很容易想到platform,下面我们就来具体分析这个吧:
这是driver_init函数: 我们看下devices_init函数: 这里面调用kset_create_and_add创建kset并返回给devices_kset,注意这里的devices_kset,可以说是/sys下最大的boss之一了,所有的物理设备都会在device目录下管理,/sys/device/目录是内核对系统中所有设备的分层次表达模型,保存了系统所有的设备。
这里我们先看kobject_create_and_add函数,再分析kset_create_and_add函数: 其实里面函数也没啥,先创建kobject,初始化它,再添加,没啥好说的。
在kset_create_and_add函数里也会用到kobject,所以我们现在来分析下kset_create_and_add函数: 里面就是具体的创建和注册kset了。
static struct kset *kset_create(const char *name, const struct kset_uevent_ops *uevent_ops, struct kobject *parent_kobj) { struct kset *kset; int retval;
kset = kzalloc(sizeof(*kset), GFP_KERNEL);//分配kset空间 if (!kset) return NULL;//失败就返回 retval = kobject_set_name(&kset->kobj, '%s', name);//设置kset的名字,也即内嵌kobject的名字 if (retval) { kfree(kset); return NULL; } kset->uevent_ops = uevent_ops;//kset属性操作 kset->kobj.parent = parent_kobj;//设置其parent kset->kobj.ktype = &kset_ktype;//ktype指定为kset_ktype kset->kobj.kset = NULL;
return kset; } 可以看出kset_create函数内容为: 1)调用kobject_set_name函数设置kobject的名称 这里需要一个注意的,就是ktype 这个结构,即kset_ktype: 这里填充了一个释放函数,每个kobject必须有一个释放函数,并且这个kobject必须保持直到这个释放函数被调用到。如果这个条件不能被满足,则这个代码是有缺陷的。注意,假如你忘了提供释放函数,内核会提出警告的;不要尝试提供一个空的释放函数来消除这个警告,你会收到kobject维护者的无情嘲笑。
读文件时,会调用到.show的回调函数。 看完了创建函数,接下来是注册函数: kset_init函数主要是对kset初始化,会将初始化引用计数器(即kobj->kref)为1(当计数器引用计数没到0之前不可以被释放)。接着初始化entry链表结点,用于与所属的kset的list成员组成链表(INIT_LIST_HEAD(&kobj->entry)),以及一些参数的赋值。最后,还初始化以list成员为头结点的链表,它和子kobject的entry成员组成链表(INIT_LIST_HEAD(&k->list))。 kobject_add_internal函数就是关键的kobject函数了: static int kobject_add_internal(struct kobject *kobj) { int error = 0; struct kobject *parent;
if (!kobj) return -ENOENT;
if (!kobj->name || !kobj->name[0]) {//如果kobject的名字为空.退出 WARN(1, 'kobject: (%p): attempted to be registered with empty ' 'name!\n', kobj); return -EINVAL; }
parent = kobject_get(kobj->parent);//如果kobj-parent为真,则增加kobj->kref计数,即父节点的引用计数 /* join kset if set, use it as parent if we do not already have one */ if (kobj->kset) { if (!parent) parent = kobject_get(&kobj->kset->kobj);//如果parent父节点为NULL那么就用kobj->kset->kobj作其父节点,并增加其引用计数 kobj_kset_join(kobj);//把kobj的entry成员添加到kobj->kset>list的尾部,现在的层次就是kobj->kset->list指向kobj->entry kobj->parent = parent; } /*删除了部分调试内容*/ error = create_dir(kobj);//利用kobj创建目录和属性文件,其中会判断,如果parent为NULL那么就在sysfs_root_kn下创建 if (error) { /*删除了部分内容*/ } else kobj->state_in_sysfs = 1;//如果创建成功。将state_in_sysfs建为1。表示该object已经在sysfs中了
return error; } kobject_add_internal函数内容在注释里都写好了,可以概括为: 1)如果kobject的parent成员为NULL,则把它指向kset的kobject成员。 注册函数里最后一个调用就是kobject_uevent函数了,应该是关于热拔插机制的,这不是我们现在关心的内容。
接下来继续回到文章开头进入到的devices_init函数:
我们之前分析的是devices_init函数,其实接下来几个函数都是一样的,在/sys/目录下创建各个目录。
接下来看下platform_bus_init函数 也就是我们之前用的platform总线了!!
这里,device_register就是在/sys/device/目录下创建platform 其实也就包含两个函数,一个初始化,一个添加: void device_initialize(struct device *dev) { dev->kobj.kset = devices_kset;//设置设备的kobject所属集合,devices_kset即对应/sys/devices/ kobject_init(&dev->kobj, &device_ktype);//初始化设备的kobject INIT_LIST_HEAD(&dev->dma_pools);//初始化设备的DMA池,用于传递大数据 mutex_init(&dev->mutex); lockdep_set_novalidate_class(&dev->mutex); spin_lock_init(&dev->devres_lock);//初始化自旋锁,用于同步子设备链表 INIT_LIST_HEAD(&dev->devres_head);//初始化子设备链表头 device_pm_init(dev); set_dev_node(dev, -1);#ifdef CONFIG_GENERIC_MSI_IRQ INIT_LIST_HEAD(&dev->msi_list);#endif } 注释都写好了,看下device_add函数: int device_add(struct device *dev) { struct device *parent = NULL; struct kobject *kobj; struct class_interface *class_intf; int error = -EINVAL; struct kobject *glue_dir = NULL;
dev = get_device(dev);//增加设备的kobject的引用计数 if (!dev) goto done;
if (!dev->p) { error = device_private_init(dev);//初始化dev的私有成员,及其链表操作函数 if (error) goto done; }
if (dev->init_name) {//保存设备名,以后需要获取时使用dev_name函数获取 dev_set_name(dev, '%s', dev->init_name); dev->init_name = NULL; }
/* subsystems can specify simple device enumeration */ if (!dev_name(dev) && dev->bus && dev->bus->dev_name) dev_set_name(dev, '%s%u', dev->bus->dev_name, dev->id);
if (!dev_name(dev)) { error = -EINVAL; goto name_error; }
pr_debug('device: '%s': %s\n', dev_name(dev), __func__);
parent = get_device(dev->parent);//返回父节点,增加父节点引用计数,如果没有返回NULL kobj = get_device_parent(dev, parent);//以上层devices为准重设dev->kobj.parent if (kobj) dev->kobj.parent = kobj;
/* use parent numa_node */ if (parent && (dev_to_node(dev) == NUMA_NO_NODE)) set_dev_node(dev, dev_to_node(parent));
/* first, register with generic layer. */ /* we require the name to be set before, and pass NULL */ error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);//设置dev->kobj的名字和父对象,并建立相应目录 if (error) { glue_dir = get_glue_dir(dev); goto Error; }
/* notify platform of device entry */ if (platform_notify) platform_notify(dev);
error = device_create_file(dev, &dev_attr_uevent);//建立uevent属性文件 if (error) goto attrError;
error = device_add_class_symlinks(dev); if (error) goto SymlinkError; error = device_add_attrs(dev); if (error) goto AttrsError; error = bus_add_device(dev); if (error) goto BusError; error = dpm_sysfs_add(dev); if (error) goto DPMError; device_pm_add(dev);
if (MAJOR(dev->devt)) { error = device_create_file(dev, &dev_attr_dev);//在sys下产生dev属性文件 if (error) goto DevAttrError;
error = device_create_sys_dev_entry(dev);//在/sys/dev目录建立对设备的软链接 if (error) goto SysEntryError;
devtmpfs_create_node(dev); }
/* Notify clients of device addition. This call must come * after dpm_sysfs_add() and before kobject_uevent(). */ if (dev->bus) blocking_notifier_call_chain(&dev->bus->p->bus_notifier, BUS_NOTIFY_ADD_DEVICE, dev);
kobject_uevent(&dev->kobj, KOBJ_ADD);//向用户空间发出KOBJ_ADD 事件 bus_probe_device(dev);//检测驱动中有无适合的设备进行匹配,现在只添加了设备,还没有加载驱动,所以不会进行匹配 if (parent) klist_add_tail(&dev->p->knode_parent, &parent->p->klist_children);//把该设备的节点挂到其父节点的链表
if (dev->class) { mutex_lock(&dev->class->p->mutex); /* tie the class to the device */ klist_add_tail(&dev->knode_class, &dev->class->p->klist_devices);
/* notify any interfaces that the device is here */ list_for_each_entry(class_intf, &dev->class->p->interfaces, node) if (class_intf->add_dev) class_intf->add_dev(dev, class_intf); mutex_unlock(&dev->class->p->mutex); } /*省略部分error内容*/ }
device_add函数是比较重要的,注释基本都写好了,可以概括为:
其中,驱动检测函数:bus_probe_device 可以自行百度一下。 最后,我们接着看 bus_register(&platform_bus_type);
再次强调:
最后,bus_register函数里还调用了kset_create_and_add函数在/sys/platform/目录下创建devices和drivers目录,里面存放我们platform平台下注册的设备和驱动。 好了,到此,我们就来再次小小归纳下 :
现在,是不是对设备驱动模型有了更为直观的认识?现在回头看看文章开头的小程序,是不是轻而易举的理解了呢? |
|
来自: 西北望msm66g9f > 《编程》