Linux device driver拓扑结构-用户视角

 

在这一篇文章中,我尝试从用户的角度去观察Linux的设备与设备驱动,以及呈现方式。

  1. kobject是如何与sysfs中关连的。
  2. device与driver与bus是怎么关联起来的,又以什么形式在linux展现出来的。

还没有写完,陆续更新中。。。

 

=======================================================================

 在进行Linux driver的分析时,我们首先要搞清楚的就是Linux devicer driver的模型,其在设计时用了面向对象的思想,用C写的相当的有技巧,这句话相信大家在csdn上一搜都是这样子写的,希望大家已经很清晰。

既然是面向对象,那就有子类,以及方法以及继承等关系,我们后面都会遇到

不过我们还是要在这里说几句关于它的实现的方式。且听我慢慢胡扯起来。【借用wowotech的图片一张】

1. 传说中的kobject

我们知道在java中,会有object对象,在Linux kernel也有一个,就是我们阅读代码时经常看到的kobject(Kernel Ojbect的缩写),而且它与sysfs关联,化作sys 下的一个目录。也可派生出很多子类(kset,device,device_driver, bus_type等),也表现为一个目录。

对于kobject子类的实现,如果一个对象里面包含kobject对象,则我们就认为他是kobject中的一个子类,

不过有一个细节device_driver中,会有一个device_driver_private的指针,而不是直接在device_driver中包含一个kobject。

1.1 kojbect真面目

kobject中比较重要的几个成员是什么呢,先说说我的理解:

1. entry表示自身, 通过entry到如kset中的list中,阅读下面的函数摘要kobj_kset_join(), 

   list_add_tail(&kobj->entry, &kobj->kset->list); 

2. *parent,这个更重要,目录中的层级关系就是通过他来实现的。

            如指定parent,则在parent下建立,如果没有指定,则会尝试使用kset中的obj为parent。

3. *kset,kojbect可以属于一个kset,kset简单说就是kobj的一个集合,kset的list实现。

4. *sd,就是这个kobject在sysfs中的目录节点了,要不我们怎么知道他是哪个目录。

5.  其他的用到后再说

为了说明的需要,顺便在这里写一下kset的定义,有list,就是有“串串”

1.2 kobject要怎么玩

我们可以用kobject派生出子类,然后将子类或者object加入到一个set中(属于一个set),或者给他安排parent。

1.3 kobject的运用实例

kobject提供的函数有下面几个:

void kobject_init(struct kobject *kobj, struct kobj_type *ktype);

int kobject_add(struct kobject *kobj, struct kobject *parent,
		const char *fmt, ...)

int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,
			 struct kobject *parent, const char *fmt, ...)

int kobject_move(struct kobject *kobj, struct kobject *new_parent)
void kobject_del(struct kobject *kobj)

我们用一个函数流程将这几个概念串起来。

当我们添加一个devcie时,先不说device具体是怎么实现,主线只关注kobject的走向与parent,以及kset的关系

device_add---->kobject_add_internal----->kobject_set_join

就可以看到几个变量的使用,

1. kobject的parent,每次去add时,都会设置parent来创建kobject的目录关系

如果有设置parent,那么ojbect的父目录就是parent,

如果没有设置parent,不过设置了kset,则其parent就是kset中的,

如果都没有,那就直接出现在sys目录下【kset就是没有parent的一个例子,参考下面的例子】

int __init devices_init(void)
{

    devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);

}

2.在add时, 如果有kset,还会将自己加入到kset的“串串”list中【见kobj_kset_join】。

int device_add(struct device *dev){
    ......
    parent = get_device(dev->parent);
    setup_parent(dev, parent);
    ......

    error = kobject_add(&dev->kobj, dev->kobj.parent, "%s", dev->bus_id);
      |
}     |
      |
      |
      ---> kobject_add_varg
              |
              |
              |--->kobject_add_internal
                     |
                     |

2 kobject的子类:kset,devcie,device_driver, bus_type

他的子类有下面的这些:

  • kset
  • device
  • device_driver
  • bus_type

2.1 kset : 

在上面的例子中,我们已经看到了kset的,也知道它了kobject与其子类的容器,在实现时就是利用了kobject中的list与parent。我们再仔细看一下:

struct kset {
	struct list_head list;
	spinlock_t list_lock;
	struct kobject kobj;
	struct kset_uevent_ops *uevent_ops;
};

在其定义里面还有一个是*uevent_ops,这里会有一个uevent的概念,如果之前没有听说过,我们再开一章来说。

【简单说来:kernel有什么变化时,要在一个合适的时机通知userspace,如一个设置增加了,一个设备被一处了】

稍微总结一下:

kset的set字眼点明了他的作用,相同类别的kobject子类可以加入到kset中【由他的list串联】

什么是相同类别的kobject,举个例子,我们常见的device,在其初始化时,,也代表现实世界中的一个设备(当然也可以是我们虚拟的一个设备),而我们说的 当将一个kojbect加入到kset时,这个kojbect的parent指向kset的kobject,这样了就可以在sysfs中建立树状关系。

  • 他是一个kobject的个容器,如他的名字所展现的一样;表现在sys目录中,就是一个目录。虽然kobject也是一个目录,但是他是一类的;但是加进去的是哪个case,理论上任何一个kobject的子类都可以进入,但是我们知道,在实际使用过程中会哪一类,如果人以类聚一样,在代码中,也是以功能来分类的。
  • 那到底有哪些呢,我们要有一个直接的表现,那就看看, 这个在Linux启动时,也是很重要的一个流程

2.1.1 Kernel中kset的使用场景

void __init driver_init(void)
{
	/* These are the core pieces */
	devices_init();  //kset
	buses_init();    //kset
	classes_init();  //kset
	firmware_init(); //kset
	hypervisor_init();

	/* These are also core pieces, but must come after the
	 * core core pieces.
	 */
	platform_bus_init();//kset
	system_bus_init();  //kset
	cpu_dev_init();
	memory_dev_init();
}

目录 devices,bus,class,system这些目录,悉数登场。

/sys$ ls
block  class  devices   fs          kernel  power
bus    dev    firmware  hypervisor  module

可以观察下sys子目录,我们在代码中都可以找到他的创建之处。如devices目录:

int __init devices_init(void)
{
	devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
	if (!devices_kset)
		return -ENOMEM;
    ......
}

当然kset也可以出现在其他的地方,如platform_bus的目录下【/sys/platform/devices, /sys/platform/drivers两个目录】

int __init buses_init(void)
{
	bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);
    ......
}

int __init classes_init(void)
{
	class_kset = kset_create_and_add("class", NULL, NULL);
    ......
}
int __init system_bus_init(void)
{
	system_kset = kset_create_and_add("system", NULL, &devices_kset->kobj);
	if (!system_kset)
		return -ENOMEM;
	return 0;
}

那kset与一个普通的kobject一个重要的区别就是 有一个kset_uevent_ops ,如下:

bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);

红字的部分就是他的差异,至于他什么时候用,我们后面的时候再去做。

函数最终调用到kobject_add_internal,参照上面的流程。

2.2 bus_type

Bus_type是kobject重要子类,从实现来看他是kset直接子类。

先看一下bus_type的定义,重点看bus_type_private数据,bus_type_private是framework的范畴,应用层不用去修改。

 

在现实生活中,会有很多bus(总线),如IIC, CAN, 以及USB. 虽然不尽相同, 不过可以抽象下,一个具体的设备device会挂在一条总线上,然后通过总线bus协议进行通信。例如IIC设备,当然也可以有虚拟总线platform_bus。

如果我们自己设计,这个bus上挂的设备(device)会是在哪里,这个bus上有的driver有哪些?看一个截图,让我们有整体的认识。

 

2.2.1 Bus汽车总站

bus的起始点是什么呢, 用户在使用时经常用到的/sys/bus在哪里创建的?

【driver_init->buses_init】in bus.c

从实现看,/sys/bus本身是一个kset,那么在kernel中是如何使用的这个bus_kset呢?

来看一个特定的bus注册函数如下:

 

上面的代码可以看出:

1. 按照层关系,bus_type这个kset被放在bus_kset(/sys/bus)里面,devices与drivers这两个我们在后面会经常用到。

2. 看下真实系统中的截图,常见的platform,i2c,usb等会经常看到。(/sys/bus/platform, /sys/bus/i2c, sys/bus/usb )

其代码摘录如下

platform注册完后,其在/sys/bus/platform如下

那么bus_type上怎么添加devcie与device_driver呢。我们在章节3里面讲解。

 

2.2.2 bus_type小结一下

  1. 总线的目录在/sys/bus, bus目录的创建是在driver_init->buses_init,
  2.  之后就是每一个bus的注册,例如platform bus的初始化platform_bus_init, 由此我们platform的bus就已经注册完成。
  3. 当注册完成后,就会有bus目录下呈现不同的目录
/sys/bus/platform$ ls
devices  drivers  drivers_autoprobe  drivers_probe  uevent


/sys/bus/i2c$ ls
devices  drivers  drivers_autoprobe  drivers_probe  uevent

 

2.3 device

分析一下device的定义,以及device对外呈现,以及代码的流程,先从我们眼睛看到的开始。

2.3.1 引出/sys/devices 目录

系统中,所有通过device_register的device都加入到了devcies_kset中去了。我们遍历这个kset就可以找到他。其实现如下

int device_register(struct device *dev)
{
	device_initialize(dev);
	return device_add(dev);
}
void device_initialize(struct device *dev)
{
	dev->kobj.kset = devices_kset;
    .....
}

但我们看到的/sys/devices的目录下,还是非常有规律的,也是有相应的目录规律,例如我们下面要说的platform目录

 

2.3.2 devcie 与/sys/devcies的关系

以platform_device为例子说明,因为platform_device都在这个目录下。

 

但是之前先说platform_bus这个device (/sys/devices/platform)的注册,其定义超级简单,不过他的意义是什么?分门别类,

[ device初始化时,指定其kset为devces_kset ]

int device_register(struct device *dev)
{
	device_initialize(dev);
	return device_add(dev);
}

而前面的则是device_register, 在注册时,它是放在devices_kset,所以遍历这个set就OK可以到所有的device【见devcie_initialize】

void device_initialize(struct device *dev)
{
	dev->kobj.kset = devices_kset;
    .....
}

 

/sys/devices/platform目录可以看很多的设备,其注册流程如下:(通过parent来实现的)

int platform_device_add(struct platform_device *pdev)
{
	int i, ret = 0;

	if (!pdev)
		return -EINVAL;
  //如果没有指定,则默认为platform_bus
	if (!pdev->dev.parent)
		pdev->dev.parent = &platform_bus;//这个不是bus,一个devcie,

    //说明是platform_bus_type.
	pdev->dev.bus = &platform_bus_type;
....
}

2.3.3 devcie的kojbect中的parent参数是如何定义。

先说一个概念:我们都知道在device中最后产生目录层次关系的是kobject,kobj的parent的依次关系是:

1. 首先尝试使用devcie的parent设备,存在与否未知

       1.1 如果没有parent,但是dev的class非空,则产生一个virtual parent /sys/devices/virtual, 

       1.2 如果parent不为空,parent的class非空且dev的class的namespace为空,则parent确定,【看注释是避免名字空间污染】

        1.3 除了以上情况,还需要从glue_dirs中查找,【这里还要再分析】

2. 经过上面的步骤,还不ok,parent就是空了,那么在add时,看kset的,以kset.kobj为parent。

 

所以platform_bus device在add时,没有parent,也没有class,但是有kset:devices_kset[device_initialize时],所以出现的目录在 /sys/devices/platform.

2.3.4 platform_devcie_register()

后面调用流程如下

devcie_add时会有与device_driver关联,

2.4 device_driver

 

【在kobject中,我们是通过kobject中的parent来建立目录的树形关系的,如果有kset,则使用的是kset的kobject。】

【如果我们使用内核框架提供的接口,基本上不会设计到适应目录以及框架的修改,我们只要去填写数据就OK了】

 

上面我们说到,kobject的子类时,说到只要有kobject对象就是他的一个子类,这里其实是有点奇怪的。

对于deivce,其中直接嵌入一个,这个我还比较好理解,但是到了device_driver时,其实现如下,使用了一个指针。但是从其描述文档中,有如下描述(kernel 4.9)

 * @p:		Driver core's private data, no one other than the driver
 *		core can touch this.

所以看来,这一部分是framework要使用的部分,对于driver的开发者这是不需要的。

struct device_driver {
	const char		*name;
	struct bus_type		*bus;

	struct module		*owner;
	const char 		*mod_name;	/* used for built-in modules */

	int (*probe) (struct device *dev);
	int (*remove) (struct device *dev);
	void (*shutdown) (struct device *dev);
	int (*suspend) (struct device *dev, pm_message_t state);
	int (*resume) (struct device *dev);
	struct attribute_group **groups;

	struct pm_ops *pm;

	struct driver_private *p;//why this part use a pointer??
};

不过在private data中有嵌入一个Kobject,这是一个本体,然后再保存一个指向device driver的一个指针,这样子就可以找到,

struct driver_private {
	struct kobject kobj;
	struct klist klist_devices; //一个driver可以驱动很多实体的设备
	struct klist_node knode_bus;
	struct module_kobject *mkobj;
	struct device_driver *driver;
};
#define to_driver(obj) container_of(obj, struct driver_private, kobj)

我们仔细看一下,这里主要是与Kobject相关的内容,这一部分对于driver应用层来说,如果再去考虑,从分层的角度来看就有交叉的问题了。

面向对象就会有一个概念,虚函数,以及重载的概念,还有可能是需要重写与重新定义。

2.5 device与device_driver实例

 以他的子类platform_driver来看: 里面有probe,remove等都需要重载的。

struct platform_driver {
	int (*probe)(struct platform_device *);
	int (*remove)(struct platform_device *);
	void (*shutdown)(struct platform_device *);
	int (*suspend)(struct platform_device *, pm_message_t state);
	int (*suspend_late)(struct platform_device *, pm_message_t state);
	int (*resume_early)(struct platform_device *);
	int (*resume)(struct platform_device *);
	struct pm_ext_ops *pm;
	struct device_driver driver;
};

那么为什么device中没有一个类似device_private呢?从设计的层次来说,一个device就是一个具体的设备,看了一下,在device中会有一个void的类型,里面有相应的数据处理。

struct device {
	struct kobject kobj;

	void		*driver_data;	/* data private to the driver */
	void		*platform_data;	/* Platform specific data, device
					   core doesn't touch it */
}

不过我们看到这些driver core都不会去处理,而且留给driver的开发者自行利用。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值