Linux驱动学习笔记(九)

设备模型

1.kobject的全称为kernel object,即内核对象,每一个kobject都会对应到系统/sys/下的一个目录,这些目录的子目录也是一个kobject,以此类推,这些kobject构成树状关系,如下图:

kobject定义在内核源码目录下的/include/linux/kobject.h文件中,如下图:

kset是一组kobject的集合,kset将多个kobject链接了起来,kset同样定义在/include/linux/kobject.h文件中,如下图:

kset通过list成员将多个kobject的entry成员链接起来,形成一个kobject集合(可参考讯为Linux驱动视频第九期P2)。

2.与kobject有关的函数如下(均定义在内核源码目录下的/lib/kobkect.c中):

  • struct kobject *kobject_create_and_add(const char *name, struct kobject *parent):该函数用于动态创建一个kobject。其中name为要创建的kobject的名字;parent为创建的kobject的父kobject,传入NULL表示直接在/sys/目录下创建kobject。创建成功会返回指向新kobject的指针,并将该kobject加入到系统/sys/下的层级目录中。
  • int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype, struct kobject *parent, const char *fmt, ...):该函数用于初始化一个kobject(静态创建)。其中kobj为要初始化的kobject;ktype为关联的kobj_type,定义了kobject的行为(如属性、释放函数等),这里的ktype不能传入NULL;parent为要初始化的kobject的父kobject,传入NULL表示直接在/sys/目录下创建kobject,但如果kobject属于某个kset,传入NULL会在kobject->kset指向的kset对应的目录下创建;fmt为可变参数,用来指定kobject的名字。初始化成功会返回0,并将该kobject加入到系统/sys/下的层级目录中。
  • void kobject_put(struct kobject *kobj):该函数用于递减kobject的引用计数,当计数归零时,触发ktype->release释放kobject资源。

这几个函数的具体使用示例如下:

3.与kset有关的函数如下(均定义在内核源码目录下的/lib/kobkect.c中):

  • struct kset *kset_create_and_add(const char *name, const struct kset_uevent_ops *uevent_ops, struct kobject *parent_kobj):该函数用于创建一个kset并初始化。其中name为要创建的kset的名字;uevent_ops用于定义kset的事件操作(如filter、name、uevent回调函数);parent为创建的kset的父kobject,传入NULL表示直接在/sys/目录下创建kset。创建成功会返回指向新kset的指针,并将该kset加入到系统/sys/下的层级目录中。
  • void kset_unregister(struct kset *k):该函数用于注销先前通过kset_create_and_add()或类似函数创建的kset,并清理相关资源。其中k为要移除的kset。

这几个函数的具体使用示例如下:

4.为了做好设备驱动的管理,并降低驱动开发难度,兼容设备的热拔插和电源管理。Linux对硬件设备进行了分类和归纳,并抽象出来了一套标准的数据结构和接口,这个就是设备模型。设备模型包含总线,设备,驱动和类四个概念:

  • 总线:总线是CPU和设备进行信息交互的通道,所有的设备都要连接到总线上,总线包含虚拟总线和外设总线。在设备模型中,使用bus_type来描述总线,stuct bus_type结构体定义在内核源码目录下的/include/linux/device/bus.h中,如下图所示(只截取了部分):

  • 设备:将系统中所有硬件设备的共同属性,比如名字,属性,从属关系,类等信息抽象出来就是设备。在设备模型中,使用device来描述设备。struct device结构体定义在内核源码目录下的/include/linux/device.h中,如下图所示(只截取了部分):

  • 类:具有相似功能或者属性的设备。在设备模型中,使用class来描述类。struct class结构体定义在内核源码目录下的/include/linux/device/class.h中,如下图所示(只截取了部分):

  • 驱动:硬件设备对应的驱动程序。在设备模型中,使用device_driver来描述驱动。stuct device_driver结构体定义在内核源码目录下的/include/linux/device/driver.h中,如下图所示(只截取了部分):

设备控制器(如GPIO控制器、LCD控制器等)直接和CPU连接,CPU可以通过寻址操作访问它们,但设备模型应该具备普适性,因此Linux就虚构了一条patform总线,供这些设备链接。

5.sysfs文件系统是Linux2.6版本引入的虚拟文件系统,sysfs把连接在系统上的设备模型组织成为一个分级的层次视图,并且可以向用户空间导出内核数据结构以及属性。kobject和kset是设备模型的基本框架,在使用kobiect的时候,一般不会单独使用,要嵌入到一个数据结构中,这样就可以把高级对象接入到设备模型里面,例如下面字符设备的结构体:

所以可以把总线、设备等看作是kobject的派生类,kobject是设备模型的基石,一个kobject对应/sys/目录下一个目录。追踪kobject创建的函数调用(下述前面几个函数均定义在内核源码目录下的/lib/kobject.c文件中,最后一个除外),动态创建:kobject_create_and_add->kobject_add->kobject_add_varg->kobject_add_internal->create_dir->sysfs_create_dir_ns(/fs/sysfs/dir.c)或静态创建:kobject_init_and_add->kobject_add_varg->kobject_add_internal->create_dir->sysfs_create_dir_ns(/fs/sysfs/dir.c),其中在sysfs_create_dir_ns函数中会判断传入的parent节点是否为NULL,若为NULL则将其parent指向sysfs_root_kn(代表/sys/对应的节点,全局变量sysfs_root_kn会在内核源码目录下的/fs/sysfs/mount.c文件中的sysfs_init函数中被初始化),所以当创建kobject的时候,若传入parent节点为NULL,会在系统根目录/sys/目录下创建。如下图:

通过查看上述几个函数的具体实现,可知在创建kobject时,有以下规则(可参考讯为Linux驱动视频第九期P5):

  • kobject无父目录(parent传入NULL),无kset,则将在sysfs的根目录(即/sys/)下创建目录
  • kobject无父目录(parent传入NULL),有kset,则将在kset对应的目录下下创建目录,并将kobject加入kset.list
  • kobject有父目录,无kset,则将在parent对应的目录下创建目录
  • kobject有父目录,有kset,则将在parent对应的目录下创建目录,并将kobject加入kset.list

6./sys/目录下与设备模型有关的文件夹为/sys/devices/、/sys/bus/、/sys/class/。/sys/devices/目录下是连接到总线的全部设备,从设备级联角度进行展示;/sys/bus/目录下是Linux系统支持并且已经注册的总线,从总线这个角度展示现在有哪些总线以及总线下连接了什么设备和驱动,这下面的所有设备都是/sys/devices/下的设备的软连接;在/sys/class/对设备进行归类,类下的所有设备都是/sys/devices/下的设备的软连接(可参考讯为Linux驱动视频第九期P6)。如下图:

7.引用计数器:当硬件设备插上时,系统会生成一个设备节点,用户在应用空间操作这个设备节点就可以操作设备。将硬件断开时,驱动不会被立刻释放,会等应用程序关闭,再在去释放驱动,Linux系统通过引用计数来实现这一功能。即用kref这个变量记录某个驱动或者某块内存的引用次数,其初始值为1,每引用一次加1,每取消引用一次减1,当计数值为0的时候,自动调用自定义的释放函数进行释放驱动或者内存。struct kref定义在内核源码目录下的/include/linux/kref.h文件中,本质是一个原子变量,如下图:

引用计数器kref一般被嵌入其他结构体中来使用,如在kobject中:

与kref有关的函数有(均定义在/include/linux/kref.h文件中):

  • void kref_init(struct kref *kref):该函数将kref的值初始化为1
  • unsigned int kref_read(const struct kref *kref):该函数用于读取kref的值
  • void kref_get(struct kref *kref):该函数用将kref的值加1
  • int kref_put(struct kref *kref, void(*release)(struct kref *kref)):该函数将kref的值减1,若计数值减为0,会调用release函数执行释放操作

如下图:

因为每一个kobject都会对应到系统/sys/下的一个目录,所以对于一个kobject(结构体)中的kref,它的引用计数值应该为1+该kobject下面的直接子kobject数量(即该kobject对应目录下的直接子目录和文件总数,+1表示加上自身),这才能保证在当前kobject中的所有直接子目录或文件被release后该kobject才可能被release(可参考讯为Linux驱动视频第九期P8)。

8.在驱动出口函数中调用kobject_put()函数减少引用计数kref时,kobject_put()会调用kref_put(),这个函数的参数传入了一个release函数kobject_release(),这个被传入的kobject_release()会被调用,kobject_release()又会调用kobject_cleanup(),其定义如下图(定义在/lib/kobject.c中):

可见它会调用kobject->ktype->release去释放kobject,这个release函数是在创建kobject时绑定的。两种方式创建kobject时的函数调用路径分析如下,第一种不用手动申请内存:kobject_create_and_add()->kobject_create()和kobject_add(),其中kobject_create()会调用kzalloc()分配内存创建kobject,并调用kobject_init()初始化kobject,将kobject的ktype成员初始化为dynamic_kobj_ktype,dynamic_kobj_ktype的定义如下图:

可见dynamic_kobj_ktype中的release函数为dynamic_kobj_release,它完成了对在kobject_create()中申请的动态内存的释放,这个release函数会在引用计数器kref的值减至0时被调用。kobject_add()会调用kobject_add_varg()完成/sys/下对应目录的创建等工作。第二种创建kobject的方式需要先手动申请内存创建kobject:kzalloc()->kobject_init_and_add()->kobject_init()和kobject_add_varg(),所以在函数kobject_init_and_add中传入的ktype需要自己定义,定义ktype的release成员(也可以定义为NULL,则释放kobject时什么也不做),且ktype参数不能传入NULL,否则会报错,见本章笔记第2点。

9.一个kobject对应/sys/下的一个目录,在这些目录中可以创建属性(文件),并对这些属性(文件)进行读写操作,属性(文件)的创建和读写与kobj_type结构体中的sysfs_ops、default_attrs两个成员有关系,如下图:

其中sysfs_ops结构体中的show和store成员分别用来定义属性(文件)的读和写操作,default_attrs数组中的每个attribute定义了各个属性(文件)的名字和权限,如下图:

具体实现时需要在show或store函数中根据参数attribute的值去判断当前要读写的是哪个属性(文件),然后执行对应的操作。但要想通过直接定义kobj_type来实现创建属性(文件)的功能,就只能使用kobject_init_and_add去创建kobject,因为kobject_create_and_add在创建kobject时kobj_type是无法自定义的(可参考讯为Linux驱动视频第九期P11)。为了更好地将各个属性与其读写操作联系起来,可以使用linux提供的结构体kobj_attribute以及宏定义__ATTR,然后利用container_of函数根据attribute获取对应的kobj_attribute,进而执行对应的show或store函数,如下图(可参考讯为Linux驱动视频第九期P12):

也可以自定义结构体完成对attribute、show和store的封装。如果想要使用kobject_create_and_add创建kobject(其实kobject_create_and_add函数创建kobject时使用的系统默认的kobj_type中的show和store函数也是利用kobj_attribute结构体完成了对attribute、show和store的封装),并实现属性(文件)相关功能,则可以使用int sysfs_create_file(struct kobject *kobj, const struct attribute *attr);函数添加属性(文件),其中kobj是目标kobject,attr是要添加的属性(文件),但是此函数一次只能添加一个属性(文件)。可以用int sysfs_create_group(struct kobject *kobj, const struct attribute_group *grp)函数一次添加多个属性(文件),其中kobj是目标kobject,grp是要添加的属性(文件)组,struct attribute_group定义如下图(定义在内核源码目录下的文件/kernel/include/linux/sysfs.h中):

若在初始化时没有定义struct attribute_group的name成员,则所有属性(文件)直接被创建在kobject对应的目录下,若指定了name,则所有属性(文件)被创建在“name”目录下,而“name”会被创建在kobject对应的目录下(可参考讯为Linux驱动视频第九期P14)。

10./sys/bus/目录下的每个子目录代表一个总线类型,可以自己注册总线,注册的总线会在/sys/bus/目录下生成对应的子目录。总线对应的结构体为bus_type(参考本章第4点),bus_type结构体中的name成员即为/sys/bus/目录下的子目录名,bus_type中的match成员绑定的函数用于匹配设备和驱动,匹配成功后回去执行bus_type中的probe成员绑定的函数,可以在该probe函数中调用设备对应驱动的结构体device_driver中绑定的probe函数,如下图所示:

总线的注册和注销函数分别是bus_register(&mybus)和bus_unregister(&mybus),可以分别在module_init和module_exit中注册的函数中被调用。与kobject类似,可以在总线对应的目录下创建属性(文件),可以通过调用bus_create_file函数创建(定义在内核源码目录下的/drivers/base/bus.c中):

该函数其实是对前面提到的sysfs_create_file函数的封装,该函数的第一个参数bus就是目标总线,第二个参数attr是将属性(文件)的属性、show、probe操作封装在一起的结构体bus_attribute(与前面的kobj_attribute类似),如下图(定义在内核源码目录下的/include/linux/device/bus.h中):

还可以使用void bus_remove_file(struct bus_type *, struct bus_attribute *)函数删除创建的属性(文件)。

11.使用bus_register注册总线时,在bus_register函数中,会将注册的总线放在名为bus_kset的kset下,bus_kset对应的目录即为/sys/bus/,所以所有注册的总线会放在/sys/bus/目录下。bus_register函数还会为总线默认创建几个kset(文件)和属性(文件),下图截取了部分(更多bus_register函数的分析可参考讯为Linux驱动视频第九期P17):

12.platform总线的注册流程:linux系统在启动时会执行platform_bus_init函数(定义在内核源码目录下的/drivers/base/platform.c中),如下图:

platform_bus_init会调用bus_register注册platform总线,platform_bus_type结构体的定义如下图:

platform_bus_type中的platform_match函数用于匹配platform总线上的platform_device和platform_driver,platform_match函数定义如下图:

从这个函数中就可以看出匹配的优先级为:driver.of_match_table>id_table>driver.name。

13.可以使用int device_register(struct device *dev)函数向总线上注册设备,其中dev为要注册的设备对应的结构体指针,注册的设备会在/sys/devices/目录下的某个目录中生成对应的目录(如果没有指定设备的parent节点则会直接在/sys/devices/目录下生成对应的目录),device_register函数定义如下(在内核源码目录下的/kernel/drivers/base/core.c中定义):

device_initialize函数主要完成一些数据结构的初始化,如下图:

device_add函数用于创建一些软链接(/sys/bus/、/sys/class/等目录下的软链接),并将设备注册到总线上(可参考讯为Linux驱动视频第九期P20),如下图:

void device_unregister(struct device *dev)函数用于注销总线上的设备。设备注册的一个示例如下:

加载这个驱动之后默认会在/sys/devices/目录下生成mydevice子目录。对于platform_device,它的注册过程其实就是对一般的设备注册device_register的包装,如下图:

platform_device_register函数(在内核源码目录下的/kernel/drivers/base/platform.c中定义)也是先调用device_initialize完成了一些初始化工作,然后调用platform_device_add将platform_device设备注册到platform总线上,在platform_device_add函数中会调用device_add函数。对于platform设备,会在/sys/devices/platform/目录下生成对应的设备文件,因为系统在初始化时在调用bus_register函数注册platform总线之前,注册了一个platform_bus设备,之后所有的platform_device设备都会默认被保存到该目录下(见本章第12点中的platform_bus_init函数)。

14.可以使用int driver_register(struct device_driver *drv)函数向总线上注册驱动,其中drv为要注册的设备驱动对应的结构体指针,该函数的定义如下(在内核源码目录下的/kernel/drivers/base/driver.c中定义):

注册成功后会在/sys/bus/注册的总线/drivers/目录下生成对应的目录(注意这里不是软链接)。在驱动和设备匹配成功后,若drivers_autoprobe成员被初始化为1则会自动执行对应的probe函数,这部分代码如下图:

probe函数执行的函数调用链为:driver_register->bus_add_driver->driver_attach->bus_for_each_dev->__driver_attach->device_driver_attach->driver_probe_device->really_probe->对应的probe函数,如下图:

可见会先选择执行bus中定义的probe函数,若bus没定义probe才会去执行驱动中定义的probe函数。可以直接在用户空间通过对属性文件进行写操作去修改这里的drivers_autoprobe变量的值,如下图:

然而不管是先insmod驱动对应的.ko文件还是先insmod设备对应的.ko文件,只要drivers_autoprobe变量的值为1,设备和驱动匹配成功后都会自动取执行对应的probe函数,那是因为在注册设备时也会对drivers_autoprobe的值进行判断,如下图:

此时probe函数执行的函数调用链为:device_register->device_add->bus_probe_device->__device_attach->bus_for_each_drv->__device_attach_driver->driver_probe_device->really_probe->对应的probe函数(可参考讯为Linux驱动视频第九期P26)。对于platform_driver,在注册时platform_driver_register函数(在内核源码目录下的/kernel/drivers/base/platform.c中定义)也是调用driver_register函数完成注册的,如下图:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/diannao/84464.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

25年上半年五月之软考之设计模式

目录 一、单例模式 二、工厂模式 三、 抽象工厂模式 四、适配器模式 五、策略模式 六、装饰器模式 ​编辑 考点:会挖空super(coffeOpertion); 七、代理模式 为什么必须要使用代理对象? 和装饰器模式的区别 八、备忘录模式 一、单例模式 这个…

Python打卡第36天

浙大疏锦行 作业: 对之前的信贷项目,利用神经网络训练下,尝试用到目前的知识点让代码更加规范和美观。 import torch import torch.nn as nn import torch.optim as optim from sklearn.model_selection import train_test_split from sklear…

全面理解类和对象(下)

文章目录 再谈构造函数初始化列表 static概念: 友元友元函数友元类 内部类再次理解类和对象 再谈构造函数 class Date { public:Date(int year, int month, int day){_year year;_month month;_day day;} private:int _year;int _month;int _day; };上述代码有了…

TomatoSCI分析日记——层次聚类

TomatoSCI分析日记——层次聚类 今天介绍的是一种常见的聚类方法——层次聚类。层次聚类会将数据集划分成嵌套的簇,形成一个层次结构(树状图),经常用于探究样本的相似性。用大白话来说,就是:我有一大堆样品…

mysql都有哪些锁?

MySQL中的锁机制是确保数据库并发操作正确性和一致性的重要组成部分,根据锁的粒度、用途和特性,可以分为多种类型。以下是MySQL中常见的锁及其详细说明: 一、按锁的粒度划分 行级锁(Row-level Locks) 描述:…

flutter 项目调试、flutter run --debug调试模式 devtools界面说明

Flutter DevTools 网页界面说明 1. 顶部导航栏 Inspector:查看和调试 Widget 树,实时定位 UI 问题。Performance-- 性能分析面板,查看帧率、CPU 和 GPU 使用情况,识别卡顿和性能瓶颈。Memory-- 内存使用和对象分配分析&#xff…

使用Kotlin创建Spring Boot用户应用项目

项目初始化与配置 通过Spring Initializr创建Kotlin项目 若需使用Kotlin语言开发Spring Boot应用(假设已安装Kotlin环境),可通过start.spring.io进行项目初始化。在项目创建页面需进行以下关键配置: 语言选择:切换至Kotlin选项项目元数据:需填写Group(如com.apress.us…

【Linux网络篇】:Socket网络套接字以及简单的UDP网络程序编写

✨感谢您阅读本篇文章,文章内容是个人学习笔记的整理,如果哪里有误的话还请您指正噢✨ ✨ 个人主页:余辉zmh–CSDN博客 ✨ 文章所属专栏:Linux篇–CSDN博客 文章目录 网络编程套接字一.预备知识1.理解源IP地址和目的IP地址2.认识端…

Python爬虫实战:研究Newspaper框架相关技术

1. 引言 1.1 研究背景与意义 互联网的快速发展使得新闻信息呈现爆炸式增长,如何高效地获取和分析这些新闻数据成为研究热点。新闻爬虫作为一种自动获取网页内容的技术工具,能够帮助用户从海量的互联网信息中提取有价值的新闻内容。本文基于 Python 的 …

【node.js】实战项目

个人主页:Guiat 归属专栏:node.js 文章目录 1. 项目概览与架构设计1.1 实战项目:企业级电商管理系统1.2 技术栈选择 2. 项目初始化与基础架构2.1 项目结构设计2.2 基础配置管理 3. 用户服务实现3.1 用户服务架构3.2 用户模型设计3.3 用户服务…

Mybatis框架的构建(IDEA)

选择maven项目 修改设置 在设置中添加自定义代码模板 开始写代码 动态SQL语句的示例&#xff1a; pom文件&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"…

经济法-6-公司法律制度知识点

一、出资期限 1.有限责任公司&#xff1a;全体股东需在公司成立之日起5年内缴足认缴的注册资本 2.股份有限公司&#xff1a;以发起方式设立的&#xff0c;发起人需在公司登记前实缴全部股款 3.认缴期加速到期 公司不能清偿到期债务的&#xff0c;公司或者已到期债权的债权人…

jquery.table2excel方法导出

jquery提供了一个table2excel方法可以用来导出页面到xls等 $("#grid_595607").table2excel({exclude: ".noExport", // 排除类名为 noExport 的元素filename: "导出数据.xls",exclude_img: true, // 不导出图片exclude_links: true, // 不导…

echarts设置标线和最大值最小值

echarts设置标线和最大值最小值 基本ECharts图表初始化配置 设置动态的y轴范围&#xff08;min/max值&#xff09; 通过markPoint标记最大值和最小值点 使用markLine添加水平参考线 配置双y轴图表 自定义标记点和线的样式&#xff08;颜色、符号等&#xff09; 响应式调整图表大…

Java文件操作:从“Hello World”到“Hello File”

&#x1f50d; 开发者资源导航 &#x1f50d;&#x1f3f7;️ 博客主页&#xff1a; 个人主页&#x1f4da; 专栏订阅&#xff1a; JavaEE全栈专栏 文件 什么是文件&#xff1f; 广义&#xff1a;操作系统进行资源管理的一种机制&#xff0c;很多的软件/硬件资源&#xff0c;…

2025第三届黄河流域网络安全技能挑战赛--Crypto--WriteUp

2025第三届黄河流域网络安全技能挑战赛–Crypto–WriteUp Crypto sandwitch task from Crypto.Util.number import * import gmpy2 flag bflag{fake_flag} assert len(flag) 39 p getPrime(512) q getPrime(512) n p * q e 0x3 pad1 beasy_problem pad2 bHow_to_so…

三重天理论

第一重天&#xff1a;公理层&#xff08;形而上地基&#xff09; 这里构建的是人类理性的"操作系统"&#xff0c;公理作为不证自明的逻辑起点&#xff08;如矛盾律/同一律&#xff09;&#xff0c;恰似海德格尔所说的"存在之镜"。黑格尔辩证法在此显现为动…

2025年第八届广西大学生程序设计大赛(正式赛)题解(更新中)

知乎评价&#xff1a;如何评价2025年第八届GXCPC广西大学生程序设计大赛暨中国-东盟国际大学生程序设计大赛&#xff1f; 榜单&#xff1a;牛客比赛排名 题目链接&#xff1a;第八届广西大学生程序设计大赛暨2025邀请赛 TIP&#xff1a;提交处可查看别人过题代码 难度签到题普通…

WHAT - 兆比特每秒 vs 兆字节每秒

文章目录 Mbps 解释Mbps 和 MB/s&#xff08;兆字节每秒&#xff09;换算总结网络场景1. 在路由器设置中的 Mbps2. 在游戏下载时的 Mbps / MB/s总结 Mbps 解释 首先&#xff0c;Mbps 是一个常见的网络带宽单位&#xff0c;意思是&#xff1a; Megabits per second&#xff08;…

[C语言实战]C语言内存管理实战:实现自定义malloc与free(四)

[C语言实战]C语言内存管理实战&#xff1a;实现自定义malloc与free&#xff08;四&#xff09; 摘要&#xff1a;通过实现简化版的内存管理器&#xff0c;深入理解动态内存分配的核心原理。本文包含内存块设计、分配算法、空闲合并策略的完整实现&#xff0c;并附可运行的代码…