【linux kernel】linux内核设备驱动的注册机制

news2024/11/28 20:44:22

文章目录

    • 1、简介
    • 2、driver_register分析
      • 👉(2-1)driver_find分析
      • 👉(2-2)bus_add_driver分析
    • 3、总结


🔺【linux内核系列文章】

👉对一些文章内容进行了勘误,本系列文章长期不定时更新,希望能分享出优质的文章!

  • 1、《linux内核数据结构分析之哈希表》
  • 2、《一文总结linux内核通知链》
  • 3、《linux内核中的debugfs》
  • 4、《linux内核数据结构分析之链表》
  • 5、《linux media子系统分析之media控制器设备》
  • 6、《V4L2-PCI驱动程序样例分析(上)》
  • 7、《v4l2框架分析之v4l2_fh》
  • 8、《 v4l2框架分析之v4l2_subdev》
  • 9、《 v4l2框架分析之v4l2_device》
  • 10、《v4l2框架分析之video_device》
  • 11、《linux内核重要函数 | do_initcalls》
  • 12、《Linux设备驱动模型 | bus》
  • 13、《linux内核裁剪随想》
  • 14、《基于ARM64分析linux内核的链接脚本vmlinux.lds.S》
  • 15、《linux内核start_kernel函数的早期操作》
  • 16、《start_kernel函数详解系列之proc_caches_init》
  • 17、《start_kernel函数详解系列之fork_init》
  • 18、《start_kernel函数详解系列之rcu_init》
  • 19、《start_kernel函数详解系列之proc_root_init》
  • 20、《start_kernel详解系列之【setup_arch】》
  • 21、《linux内核如何启动用户空间进程(上)》
  • 22、《linux内核如何启动用户空间进程(下)》
  • 23、《一文总结linux内核的完成量机制》
  • 24、《一文总结linux内核设备驱动的注册和卸载》
  • 25、《linux内核的启动加载程序的总结》
  • 26、《linux内核入口:head.o》
  • 27、《挂载根文件系统之rootfs》
  • 28、《mount系统调用剖析》
  • 29、《devtmpfs文件系统分析》
  • 30、《linux内核的kthreadd线程》
  • 31、《linux内核的进程调度—调度策略》
  • 32、《linux系统调用实践(Arm架构)》
  • 33、《对linux内核__init机制的实践》
  • 34、《linux 内核中EXPORT_SYMBOL()分析与实践》
  • 35、《linux内核如何挂载根文件系统》
  • 36、《linux内核如何唤醒线程》
  • 37、《linux内核的init线程》
  • 38、《linux内核伪文件系统—sysfs分析》
  • 39、《linux 内核设备模型的初始化(上)》
  • 40、《linux 内核设备模型的初始化(下)》
  • 41、《linux内核伪文件系统—proc分析》
  • 42、《linux中断管理—workqueue工作队列》
  • 43、《linux中断管理—软中断》
  • 44、《linux中断管理 | tasklet》
  • 45、《linux中断管理 | 中断管理框架(01)》
  • 46、《linux内存管理 | 分配物理内存页面》
  • 47、《linux内存管理 | 释放内存页面》
  • 48、《对linux内核设备的注册机制和查找机制分析》

相关文件:
/drivers/base/driver.c

1、简介

向linux内核注册驱动由driver_register()完成。它将驱动程序的信息添加到内核的驱动程序列表中,使得内核能够在需要时与该驱动程序进行交互。

当调用driver_register()函数时,内核会将驱动程序添加到内核驱动程序列表中,并在需要时使用该驱动程序来匹配和初始化设备。驱动程序的探测函数(probe)将在设备与驱动程序匹配时调用,以便进行设备的初始化。移除函数(remove)将在设备从系统中移除时调用,以进行相关的清理操作。

通过调用driver_register()函数,驱动程序可以将自身注册到内核中,从而使得内核能够管理和与驱动程序进行交互。这为设备的探测、初始化、配置和移除提供了必要的框架和支持。

内核中,几乎所有的驱动子系统都会以该函数进行封装出对应驱动的注册函数,例如PCI驱动,在/drivers/pic/pci-driver.c文件中会调用该函数:

再比如对应i2c设备驱动,在i2c驱动框架下的/driver/i2c/i2c-core.c文件中有如下代码:

综上可知,driver_register()在几乎所有的驱动子系统中都会使用到。兜兜转转,最后都会调用到该函数。

2、driver_register分析

在Linux内核中,struct device_driver结构体用于表示一个设备驱动程序。它包含了驱动程序的相关信息,如名称、总线类型、探测函数、移除函数等,用于与设备进行匹配、初始化和清理操作。struct device_driver结构体提供了设备驱动程序的基本信息和回调函数,用于与设备进行匹配、初始化、清理和管理。通过使用该结构体,驱动程序能够在设备与驱动程序匹配时进行初始化操作,并在设备移除或系统关机时进行相应的清理操作。此外,还可以通过属性组和电源管理操作等扩展功能来增强驱动程序的功能和灵活性。该结构定义如下:

struct device_driver {
	const char		*name; //
	struct bus_type		*bus;//驱动程序所属的总线类型。

	struct module		*owner; //模块拥有者
	const char		*mod_name;	//在构建内建模块时使用

	bool suppress_bind_attrs;	//是否禁用通过sysfs bound/unbound操作

	const struct of_device_id	*of_match_table; //设备树匹配表
	const struct acpi_device_id	*acpi_match_table; //ACPI匹配表。

	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);//驱动程序的恢复函数,用于在设备从挂起状态恢复时进行相关的操作。
	const struct attribute_group **groups;// 驱动程序的属性组,用于提供设备的特定属性。

	const struct dev_pm_ops *pm;// 驱动程序的电源管理操作,用于控制设备的电源管理。

	struct driver_private *p;//驱动核心的私有数据。驱动核心能访问。
};

driver_register函数用与向设备驱动模型注册一个设备驱动,实现在/drivers/base/driver.c文件中:

int driver_register(struct device_driver *drv)
{
	int ret;
	struct device_driver *other;

	BUG_ON(!drv->bus->p);

	if ((drv->bus->probe && drv->probe) ||
	    (drv->bus->remove && drv->remove) ||
	    (drv->bus->shutdown && drv->shutdown))
		printk(KERN_WARNING "Driver '%s' needs updating - please use "
			"bus_type methods\n", drv->name);

	other = driver_find(drv->name, drv->bus);
	if (other) {
		printk(KERN_ERR "Error: Driver '%s' is already registered, "
			"aborting...\n", drv->name);
		return -EBUSY;
	}

	ret = bus_add_driver(drv);
	if (ret)
		return ret;
	ret = driver_add_groups(drv, drv->groups);
	if (ret) {
		bus_remove_driver(drv);
		return ret;
	}
	kobject_uevent(&drv->p->kobj, KOBJ_ADD);

	return ret;
}

driver_register()具体执行流程如下:

  • (1)调用driver_find()通过名字找到bus上的driver。

  • (2)调用bus_add_driver()添加一个driver到bus。

  • (3)调用driver_add_groups()将属性组(attribute_group)添加到驱动程序中。

  • (4)调用kobject_uevent()触发内核KOBJ_ADD事件,用于向用户空间发送KOBJ_ADD事件通知。

🔺下文将展开driver_find()bus_add_driver()进行分析。

👉(2-1)driver_find分析

该函数接收两个参数:

  • (1)name:驱动程序的名称。

  • (2)bus:待被扫描的bus。

函数实现如下:

调用kset_find_obj()根据name寻找是否有kobject,如果找到了,则使用to_driver()返解出struct driver_private,然后将driver_private->driver作为参数返回;否则返回NULL。

该行代码中:

struct kobject *k = kset_find_obj(bus->p->drivers_kset, name);

bus->p->drivers_kset本质是struct ksetstruct kset是linux内核对象的集合,添加的设备驱动将作为内核对象添加到对应的kset集合中。kset_find_obj()则用于在kset中搜索出对应name的内核对象,该函数实现如下:

回到driver_register()中,如果driver_find()找到了对应的device_driver,则证明该设备驱动已经注册过了,这时候则返回退出driver_register();否则继续执行后续的bus_add_driver()操作。

👉(2-2)bus_add_driver分析

bus_add_driver()实现在/drivers/base/bus.c中,将执行下列具体的逻辑:

  • (1)从drv->bus中解析出bus,如果bus为NULL,则返回退出bus_add_driver:
	bus = bus_get(drv->bus);
	if (!bus)
		return -EINVAL;
  • (2)调用kzalloc()分配一个struct driver_private内存空间:

  • (3)调用klist_init()初始化设备链表klist_devices
klist_init(&priv->klist_devices, NULL, NULL);
  • (4)设置驱动私有数据的driverkobj.kset字段,并将驱动私有数据设置到驱动程序的->p字段:
	priv->driver = drv;
	drv->p = priv;
	priv->kobj.kset = bus->p->drivers_kset;
  • (5)调用kobject_init_and_add()初始化设备私有数据中的内核对象,并指定驱动类型driver_ktype:
	error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
				     "%s", drv->name);
	if (error)
		goto out_unregister;

driver_ktype定义如下:

static struct kobj_type driver_ktype = {
	.sysfs_ops	= &driver_sysfs_ops,
	.release	= driver_release,
};
  • (6)使用klist_add_tail()priv->knode_bus节点添加到bus->p->klist_drivers总线的驱动链表中。

  • (7)如果设置了驱动所属的bus的drivers_autoprobe,则调用drvier_attach()尝试将驱动程序绑定到设备:

  • (8)使用moudle_add_drvier将设备驱动程序添加到内核模块系统中,使之可以与设备进行关联。通过调用这个函数,可以将设备驱动程序注册到内核的设备驱动程序列表中,以便在设备被发现时自动加载并与之匹配。

  • (9)调用driver_create_file()为设备驱动创建sysfs中的属性文件。该文件将显示在/sys/bus/<bus_name>/drivers/<driver_name>/目录下,其中<bus_name>是设备所属的总线类型,<driver_name>是设备驱动程序的名称。

  • (10)调用driver_add_groups()将设备驱动程序的bus的属性组添加到sysfs中,这些属性组将显示在/sys/bus/<bus_name>/drivers/<driver_name>/目录下,其中<bus_name>是设备所属的总线类型,<driver_name>是设备驱动程序的名称。

3、总结

driver_register()是 Linux 内核中用于注册设备驱动程序的函数。它属于Linux设备模型的一部分,用于将驱动程序添加到内核的设备驱动程序列表中,以便内核可以与相应的硬件设备进行交互。在内核中的大部分驱动都会形成自己的驱动框架核心,例如:USB、i2c、spi等,这些驱动框架核心也一般都会封装出自己的注册函数,但是这些注册函数本质上都是调用driver_register()实现驱动的注册。例如下图所示:

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

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

相关文章

SpringBoot热部署和整合Mybatis

目录 一、SpringBoot热部署 1.1 添加DevTools依赖 1.2 在idea中设置自动编译 1.3 在Idea设置自动运行 二、SpringBoot整合Mybatis 2.1 准备数据 2.2 添加相关依赖 2.3 在配置文件进行数据源配置 2.4 编写Mapper接口和Mapper文件 2.5 测试 一、SpringBoot热部署 热部…

Spring 事务的简单了解

目录 设计实现 工作原理 简单示例 隔离级别 事务传播机制 Spring框架提供了强大的事务管理支持&#xff0c;它允许开发者以声明性或编程性的方式来管理事务&#xff0c;从而保证数据的一致性和完整性。Spring事务管理的主要特点包括&#xff1a; 声明式事务管理&#xff1…

C语言基础算法复习

003 斐波那契数列问题 #include<stdio.h> int main() {int i,f11,f21,f3,num;printf("%5d %5d",f1,f2);num2;for(i1; i<18; i){f3f1f2;f1f2;f2f3;num;printf("%5d",f3);if(num%40) printf("\n");}return 0; }//#输数斐波那契数列的前20…

k8s-12 存储之configmap

开启之后 先看集群是否正常 Configmap用于保存配置数据&#xff0c;以键值对形式存储configMap 资源提供了向 Pod 注入配置数据的方法旨在让镜像和配置文件解耦&#xff0c;以便实现镜像的可移植性和可复用性 典型的使用场景 填充环境变量的值 设置容器内的命令行参数 填充卷的…

百度OCR识别图片文本字符串——物联网上位机软件

一、开发背景 根据项目需求&#xff0c;我们需要完成LED显示屏实时显示歌词的效果。最优的方法是调用歌曲播放器的API获取歌词&#xff0c;但是由于这个开发资格不是很好申请&#xff0c;因此我们采用其他方案&#xff0c;即通过OCR识别获取歌词&#xff0c;并投射到LED显示屏上…

Potato靶机

信息搜集 设备发现 扫描端口 综合扫描 开放了80端口的HTTP服务和7120端口的SSH服务 目录扫描 扫描目录 看看这个info.php&#xff0c;发现只有php的版本信息&#xff0c;没有可以利用的注入点 SSH突破 hydra 爆破 考虑到 7120 端口是 ssh 服务&#xff0c;尝试利用 hydra …

【LeetCode热题100】--169.多数元素

169.多数元素 使用哈希表&#xff1a; class Solution {public int majorityElement(int[] nums) {int n nums.length;int m n/2;Map<Integer,Integer> map new HashMap<>(); //定义一个hashfor(int num:nums){Integer count map.get(num); //Map.get() 方法…

ARM架构的基本知识

ARM两种授权 体系结构授权, 一种硬件规范, 用来约定指令集, 芯片内部体系结构(内存管理, 高速缓存管理), 只约定每一条指令的格式, 行为规范, 参数, 客户根据这个规范自行设计与之兼容的处理器处理IP授权, ARM公司根据某个版本的体系结构设计处理器, 再把处理器设计方案授权给…

Java 全栈体系(三)

第一章 Java 基础语法 八、标识符 业内大多数程序员都在遵守阿里巴巴的命名规则。 1. 硬性要求 必须要这么做&#xff0c;否则代码会报错。 必须由数字、字母、下划线_、美元符号$组成。数字不能开头不能是关键字区分大小写的。 2. 软性建议 如果不这么做&#xff0c;代…

前后端数据导入导出Excel

一&#xff1a;导入 Excel有读取也便有写出&#xff0c;Hutool针对将数据写出到Excel做了封装。 原理 Hutool将Excel写出封装为ExcelWriter&#xff0c;原理为包装了Workbook对象&#xff0c;每次调用merge&#xff08;合并单元格&#xff09;或者write&#xff08;写出数据&…

【实操】基于ChatGPT构建知识库

前言 最近有些实践&#xff0c;因为后面要去研究fine-tune了&#xff0c;想着记录一下chatgpt向量数据库构建知识库的一些实操经验&#xff0c;不记我很快就忘了&#xff0c;哈哈。 首先&#xff0c;提一下为啥会出现向量数据库这个技术方案&#xff1f; 大家经过实践发现&…

10.2手动推导linux中file, cdev, inode之间的关系

是时候可以手动推导一下linux里面基类父类和子类的关系了 代码放最后把 简单说明版 详细流程 第一步注册驱动 cdev结构体能看做是一个基类,那么链表里面都是字符设备驱动的cdev连载一起,啥串口,lcd的,通过cdev->list_head连接 那cdev结构体里有主次设备号 第一步 使用r…

【机器学习】特征工程:从理论到实践的关键技巧

机器学习之特征工程实践技巧 1、引言2、了解特征工程2.1 机器学习2.2 特征工程 3、特征工程的思路4、特征工程的方法5、特征工程的实践6、总结 1、引言 小屌丝&#xff1a;鱼哥&#xff0c; 你不厚道~ 小鱼&#xff1a;啥情况&#xff1f; 小屌丝&#xff1a;你为啥不给我讲…

GMSH如何对STL模型再次划分网格

由于时间比较忙&#xff0c;一直未曾继续写博文&#xff0c;今天写一篇关于Gmsh对STL模型二次划分网格的测试。此项测试还不够完善&#xff0c;目前仅实现了STL模型的细分&#xff0c;效果还可以&#xff0c;在OSG下显示的效果如下&#xff1a; 一、原始STL显示效果&#xff0…

SVN服务端客户端安装配置

SVN服务端客户端安装配置 1、服务端下载安装1.1 软件下载1.2 软件安装 2、客户端下载安装2.1 软件下载2.2 软件安装2.2.1 安装2.2.2. 汉化 3、SVN配置3.1 SVN服务器端配置3. 2 SVN客户端配置 1、服务端下载安装 1.1 软件下载 百度网盘链接&#xff1a;VisualSVN-Server-5.3.0…

YApi和Swagger接口管理

这篇博客针对苍穹外卖而写 YApi 之前的官网&#xff1a;yapi.smart-xwork.cn 由于之前的网址访问不了&#xff0c;现在我用的是这个网址&#xff1a;YApi Pro-高效、易用、功能强大的可视化接口管理平台 登录之后如下 创建两个工作空间 用户端接口也是如法炮制 Swagger 使用…

SSM - Springboot - MyBatis-Plus 全栈体系(二十七)

第六章 SpringBoot 二、SpringBoot3 配置文件 1. 统一配置管理概述 SpringBoot 工程下&#xff0c;进行统一的配置管理&#xff0c;你想设置的任何参数&#xff08;端口号、项目根路径、数据库连接信息等等)都集中到一个固定位置和命名的配置文件&#xff08;application.pro…

九、K8S之Ingress

Ingress 一、Ingress 概念 Ingress提供从集群外部到集群内服务的Http路由&#xff0c;流量路由由 Ingress 资源所定义的规则来控制。通过配置&#xff0c;Ingress可为Service提供外部可访问的URL&#xff0c;对其流量作负载均衡&#xff0c;代理转发。Ingress 的使用可以大大…

基于EtherCAT的机器人多轴同步运动控制

随着工业自动化的发展&#xff0c;机器人在生产线上的应用越来越广泛。为了实现高效、精确的运动控制&#xff0c;机器人的多轴运动必须能够实现同步操作&#xff0c;它能够提高机器人的运动精度和稳定性&#xff0c;实现更高效的生产线操作。同时&#xff0c;它也为机器人的协…

力扣刷题 day45:10-15

1&#xff0c;整数拆分 给定一个正整数 n &#xff0c;将其拆分为 k 个 正整数 的和&#xff08; k > 2 &#xff09;&#xff0c;并使这些整数的乘积最大化。 返回 你可以获得的最大乘积 。 方法一&#xff1a;动态规划 #方法一&#xff1a;动态规划 def integerBreak…