0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

设备驱动模型直观的认识

Q4MP_gh_c472c21 来源:未知 作者:李倩 2018-05-18 14:58 次阅读

Linux早期时候,一个驱动对应一个设备,也就对应一个硬件地址,那当有两个一样的设备的时候,就要写两个驱动,显然是不合理的。应该是从Linux2.5开始,就引入了device-bus-driver模型。 其中设备驱动模型主要结构分为kset、kobject、ktype。

kset是同类型kobject对象的集合,可以说是一个容器。

kobject是总线、驱动、设备的三种对象的一个基类,实现公共接口

ktype,记录了kobject对象的一些属性。

设备驱动模型的核心即是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,下面我们就来具体分析这个吧:

init/main.c里:

这是driver_init函数:

我们看下devices_init函数:

这里面调用kset_create_and_add创建kset并返回给devices_kset,注意这里的devices_kset,可以说是/sys下最大的boss之一了,所有的物理设备都会在device目录下管理,/sys/device/目录是内核对系统中所有设备的分层次表达模型,保存了系统所有的设备。

然后调用kobject_create_and_add函数在/sys/目录下创建dev目录,/sys/dev目录下维护一个按照字符设备和块设备的主次号码(major:minor)链接到真是设备(/sys/devices)的符号链接文件,应用程序通过对这些文件的读写和控制,可以访问实际的设备。

最后再以dev_kobj为父节点,在/sys/dev/目录下创建block和char目录。

这里我们先看kobject_create_and_add函数,再分析kset_create_and_add函数:

其实里面函数也没啥,先创建kobject,初始化它,再添加,没啥好说的。

倒是除了kobject_create_and_add函数,还有一个类似的函数:kobject_init_and_add。

kobject_init_and_add传入一个kobject指针和kobj_type指针,然后进行初始化

kobject_create_and_add创建一个kobject变量,并返回其指针,它不用传入kobj_type指针

在kset_create_and_add函数里也会用到kobject,所以我们现在来分析下kset_create_and_add函数:

里面就是具体的创建和注册kset了。

先说创建函数:

staticstructkset *kset_create(constchar*name,

conststructkset_uevent_ops *uevent_ops,

structkobject *parent_kobj)

{

structkset *kset;

intretval;

kset = kzalloc(sizeof(*kset), GFP_KERNEL);//分配kset空间

if(!kset)

returnNULL;//失败就返回

retval = kobject_set_name(&kset->kobj, "%s", name);//设置kset的名字,也即内嵌kobject的名字

if(retval) {

kfree(kset);

returnNULL;

}

kset->uevent_ops = uevent_ops;//kset属性操作

kset->kobj.parent= parent_kobj;//设置其parent

kset->kobj.ktype= &kset_ktype;//ktype指定为kset_ktype

kset->kobj.kset= NULL;

returnkset;

}

可以看出kset_create函数内容为:

1)调用kobject_set_name函数设置kobject的名称2)设置kobject的uevent_ops、parent为传入的形参uevent_ops、parent_kobj3)设置kobject的ktype为系统定义好的ktype变量4)设置kobject的所属kset为NULL,意思是kobject所属的kset就是kset本身,因为kset结构体包含了一个kobject成员。

这里需要一个注意的,就是ktype 这个结构,即kset_ktype:

这里填充了一个释放函数,每个kobject必须有一个释放函数,并且这个kobject必须保持直到这个释放函数被调用到。如果这个条件不能被满足,则这个代码是有缺陷的。注意,假如你忘了提供释放函数,内核会提出警告的;不要尝试提供一个空的释放函数来消除这个警告,你会收到kobject维护者的无情嘲笑。

至于kobj_sysfs_ops,则是关于读写操作相关的操作集:

读文件时,会调用到.show的回调函数。写文件时,会调用到.store的回调函数。

看完了创建函数,接下来是注册函数:

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 intkobject_add_internal(struct kobject *kobj)

{

interror= 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中了

returnerror;

}

kobject_add_internal函数内容在注释里都写好了,可以概括为:

1)如果kobject的parent成员为NULL,则把它指向kset的kobject成员。2)如果kobject的kset成员不为NULL,它会调用kobj_kset_join函数把kobject的entry成员添加到kset的list链表中3)最后调用create_dir函数创建sys目录

注册函数里最后一个调用就是kobject_uevent函数了,应该是关于热拔插机制的,这不是我们现在关心的内容。

好了,经过上面的折腾,就会在/sys/目录下建立一个devices目录。

接下来继续回到文章开头进入到的devices_init函数:

我们之前分析的是devices_init函数,其实接下来几个函数都是一样的,在/sys/目录下创建各个目录。

只需要记住devices_kset对应/sys/devices目录bus_kset对应/sys/bus目录devices_kset对应/sys/devices目录system_kset对应/sys/devices/system目录class_kset对应/sys/class目录firmware_kobj对应/sys/firmware目录hypervisor_kobj对应/sys/hypervisor目录

接下来看下platform_bus_init函数

也就是我们之前用的platform总线了!!

在driver/base/platform.c文件:

这里,device_register就是在/sys/device/目录下创建platform

其实也就包含两个函数,一个初始化,一个添加:

voiddevice_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);#ifdefCONFIG_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函数是比较重要的,注释基本都写好了,可以概括为:

1)增加kobj->kref计数2)初始化dev的私有成员3)设置设备名称4)增加父节点引用计数5)将dev->kobj添加到dev->kobj.parent对应目录下6)dev->kobj下创建属性文件7)在/sys/dev目录建立对设备的软链接8)驱动检测

其中,驱动检测函数:bus_probe_device可以自行百度一下。

最后,我们接着看 bus_register(&platform_bus_type);

今天篇幅有点长了,函数就写点重要的即可:

再次强调:

priv->subsys.kobj.kset = bus_kset;priv->subsys.kobj.ktype = &bus_ktype;

这里设置了所属的kset和ktype。ktype结构体里包含了sysfs_ops结构体,里面就是对文件的读写操作:

最后,bus_register函数里还调用了kset_create_and_add函数在/sys/platform/目录下创建devices和drivers目录,里面存放我们platform平台下注册的设备和驱动。

好了,到此,我们就来再次小小归纳下 :

在kset下还可能会有更深的kset

kset包含一个或多个kobject,方便管理

kobject并不一定需要kset

kobject下有属性文件,·向用户层提供了表示和操作这个 kobject 的属性特征的接口

kobject 下还有一些符号链接文件,指向其它的 kobject

现在,是不是对设备驱动模型有了更为直观的认识?现在回头看看文章开头的小程序,是不是轻而易举的理解了呢?

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux
    +关注

    关注

    87

    文章

    11292

    浏览量

    209323
  • 设备驱动
    +关注

    关注

    0

    文章

    68

    浏览量

    10883

原文标题:面试如果被问到Linux设备驱动模型怎么答?看完这篇就能给出满意答案了

文章出处:【微信号:gh_c472c2199c88,微信公众号:嵌入式微处理器】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    总线设备驱动模型浅析

    本帖最后由 weidongshan 于 2017-9-27 15:40 编辑 复习总线设备驱动模型,做了一点小笔记,大牛略过。一、Linux系统的驱动框架的基础很大一部分是围绕着
    发表于 08-22 16:19

    字符设备驱动,平台设备驱动设备驱动模型,sysfs的比较

    字符设备驱动,平台设备驱动设备驱动模型,sysfs
    发表于 09-03 12:04

    字符设备驱动设备驱动模型、sysfs、平台设备驱动的关系 -----从需求的角度去理解Linux之三

    学习Linux设备驱动开发的过程中自然会遇到字符设备驱动、平台设备驱动
    发表于 12-17 16:16

    详解linux设备驱动模型架构

    LDD3中说:“Linux内核需要一个对系统结构的一般性描述。”这个描述就是linux设备驱动模型(下面简称为LDDM)。LDDM不是独立存在,其体系如下图所示:
    发表于 07-25 07:25

    基于总线设备驱动模型的LED驱动的相关资料分享

    继续来点灯~学了一段时间的嵌入式Linux发现LED程序挺香的。。我们可以从LED程序中榨取很多知识:基本的驱动框架、驱动的简单分层、驱动的分层+分离思想、总线设备
    发表于 12-24 07:25

    机械展示、认识与分析直观现场教学

    实验五 机械展示、认识与分析直观现场教学 一、实验目的了解各零部件的结构和工作原理,增加感性认识,巩固课堂教学。
    发表于 03-13 19:00 2022次阅读

    Linux设备驱动模型摘抄

    Linux2.6 内核提供了新的设备模型,目的是为了对计算机上的所有设备进行统一地表示和操作,包括设备本身和设备之间的连接关系。这个
    发表于 03-19 15:15 39次下载

    深度解析字符设备驱动模型

    ,read,write和ioctl等例程。所以根据应用不同,字符驱动能会调用其他驱动模块,如i2c、spi和v4l2等,于是字符驱动还可分WDT驱动、RTC
    发表于 10-17 10:09 0次下载

    Linux设备驱动模型摘抄

    Linux设备驱动模型摘抄
    发表于 10-31 09:00 8次下载
    Linux<b class='flag-5'>设备</b><b class='flag-5'>驱动</b>的<b class='flag-5'>模型</b>摘抄

    你知道Linux设备驱动模型是怎么样构成的?

    Linux设备模型的目的:为内核建立一个统一的设备模型,从而又一个对系统结构的一般性抽象描述。换句话说,Linux设备
    发表于 04-28 17:26 853次阅读
    你知道Linux<b class='flag-5'>设备</b><b class='flag-5'>驱动</b><b class='flag-5'>模型</b>是怎么样构成的?

    驱动之路-设备模型之上层模型

    底层模型决定上层模型,在总线,设备驱动的结构体中你总是可以看到它们间接或者直接的包含了kobject结构或kset结构。
    发表于 05-15 17:04 633次阅读
    <b class='flag-5'>驱动</b>之路-<b class='flag-5'>设备</b><b class='flag-5'>模型</b>之上层<b class='flag-5'>模型</b>

    如何正确认识Linux设驱动模型

    Linux设备林林总总,嵌入式开发一个绕不开的话题就是设备驱动开发,在做具体设备驱动开发之前,有必要对Linux设
    的头像 发表于 09-13 09:30 2154次阅读
    如何正确<b class='flag-5'>认识</b>Linux设<b class='flag-5'>驱动</b><b class='flag-5'>模型</b>

    Linux总线、设备驱动模型的探究

    Linux总线、设备驱动模型的探究
    发表于 02-14 12:01 7次下载

    一文带你直观认识片上仪器

    一个芯片搭载十数种仪器功能,测量仪器正不断突破与创新,它能做些什么?6分钟视频带你直观认识片上仪器。
    的头像 发表于 05-05 10:49 2036次阅读

    Linux USB设备驱动模型查看

    1. BUS/DEV/DRV 模型 "USB 接口"是逻辑上的 USB 设备 ,编写的 usb_driver 驱动程序,支持的是"USB 接口": USB 控制器或 Hub 识别出 USB
    的头像 发表于 07-17 17:38 972次阅读
    Linux USB<b class='flag-5'>设备</b><b class='flag-5'>驱动</b><b class='flag-5'>模型</b>查看