码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • 【linux kernel】对linux内核设备的注册机制和查找机制分析


    文章目录

      • 1、简介
      • 2、device_initialize分析
      • 3、device_add分析
      • 4、总结


    🔺【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内存管理 | 释放内存页面》

    1、简介

    本文基于内核源码4.19.4分析。

    linux内核设备的注册由device_register()函数完成,这个函数是linux设备驱动模型的核心函数,实现在/drivers/base/core.c中:

    在device_register()函数中,分为两个步骤:

    • (1)调用device_initialize():该步骤用于初始化一个device。

    • (2)调用device_add():该函数用于将device添加到linux内核的device树中。

    2、device_initialize分析

    该函数接收一个struct device *dev参数,在该函数中初始化struct device结构中的几个重要成员:

    • 设置dev->kobj.kset为device_kset。device_kset是一个struct kset类型的全局变量,用于向sysfs文件系统中导出目录:/sys/device/* 。

    • 初始化dev中的kobject,并指定与这个对象相关联的ktype为device_type。

    • 初始化dma_pools链表。

    • 初始化struct device中的各种锁:

    • 初始化device的电源管理:

    • 如果在NUMA下,还会初始化设置device的numa_node为-1。

    • 接着初始化device下的links中的链表:

    在struct device 中的links表示链接到该设备的suppliers和consumers,由struct dev_links_info表示:

    • 设置device下的links.status值为DL_DEV_NO_DRIVER,表示此时还没有对应驱动attach到这个设备。

    以上步骤则是device_initialize()初始化设备时完成的操作。

    3、device_add分析

    • (1)调用get_device(dev)增加device的引用计数。

    • (2)如果dev->p为NULL,则调用device_private_init()设置device的私有数据:

    • (3)设置device的name:

    如果开启支持pr_debug()函数,则会打印出对应的设备名称。

    • (4)寻找父设备和父设备对应的kobj,并调用kobject_add()将dev->kobj添加到dev->kobj.parent:

    • (5)使用device_create_file为device创建sysfs属性文件:
    error = device_create_file(dev, &dev_attr_uevent);
    
    • 1

    dev_attr_uevent是一个struct device_attribute类型的数据,该结构用于描述导出设备属性的接口,定义如下:

    struct device_attribute {
    	struct attribute	attr;
    	ssize_t (*show)(struct device *dev, struct device_attribute *attr,
    			char *buf);
    	ssize_t (*store)(struct device *dev, struct device_attribute *attr,
    			 const char *buf, size_t count);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • (6)添加类的符号链接:
    error = device_add_class_symlinks(dev);
    
    • 1

    device_add_class_symlinks()的功能是将设备添加到指定的设备类中,并在/sys/class目录下为设备创建符号链接,以便用户空间程序能够方便地访问和管理设备。

    • (7)调用device_add_attrs()为设备添加属性:
    error = device_add_attrs(dev);
    
    • 1

    device_add_attrs()的功能是为设备添加属性,并在/sys/devices目录下创建相应的属性文件。这样,用户空间程序可以通过访问设备的属性文件来读取和修改设备的属性值。这个函数在设备驱动的初始化过程中常常被调用,以确保设备的属性能够正确地显示和访问。

    • (8)调用bus_add_device()添加设备到bus:


    bus_add_device用于将设备添加到总线上。它的功能是将一个设备(struct device结构体)添加到指定总线(struct bus_type结构体)上,并进行相应的初始化和注册操作。

    bus_add_device的执行逻辑:

    • (1)从dev->bus中取得bus_type*类型的指针bus,如果获取bus不成功,则函数直接返回;如果bus获取成功,则会继续后续的第(2)步操作。
    • (2)调用device_add_attrs接口,将由bus->dev_attrs指针定义的默认attribute添加到内核中,这个操作会体现在sysfs文件系统中的/sys/devices/xxx/xxx_device/目录中。
    • (3)调用device_add_groups将bus_dev_groups添加到内核中。
    • (4)调用sysfs_create_link将该设备在sysfs中的目录,链接到该bus的devices目录下
    • (5)接着依然调用sysfs_create_link,在该设备的sysfs目录中,创建一个指向该设备所在bus目录的链接,命名为subsystem。
    • (6)前面几个操作实则是向sysfs文件系统注册关于设备的信息,向用户空间抛出接口。最后步骤则是调用klist_add_tail()将该设备指针保存到bus->p->klist_devices中。
    • (9)调用device_pm_add()将一个设备添加到PM核心的active设备链表中。

    • (10)创建设备节点:

    • (11)通过bus_notifier告知系统设备已经添加:

    • (12)调用bus_probe_device()为该设备probe一个驱动。该函数实现如下:

    具体执行流程如下:

    • (1)从dev中解析出该dev所在而bus,如果bus不存在,则退出该函数。
    • (2)如果设置了driver_autoprobe,则调用device_initial_probe(dev)。该函数本质调用到device_attach(),尝试将设备连接到驱动程序。
    • (3)遍历bus上的子系统接口链表interfaces,如果add_dev函数指针存在,则调用对应的函数。(从源码来看有些驱动程序,会使用struct subsys_interface来实现,在此处实现对注册的subsys_interface下的add_dev的调用执行)
    • (13)如果父设备存在,则会将该设备添加到父设备的klist_children链表中(klist_children是包含此设备的所有子节点的链表):

    • (14)如果设备的class不为NULL,则会将class绑定到device:
    klist_add_tail(&dev->p->knode_class,&dev->class->p->klist_devices);
    
    • 1
    • (15)通知所有的interface接口:

    在内核中,struct class_interface是用于表示设备类和设备驱动之间的接口的结构体。它定义了设备类与设备驱动之间的关联关系,允许设备驱动在注册时与相应的设备类进行关联,并提供了一组函数指针,用于设备类调用设备驱动中的操作。

    struct class_interface结构体定义如下:

    struct class_interface {
        struct list_head node;
        struct class *class;
        int (*add)(struct device *dev, struct class_interface *class_intf);
        void (*remove)(struct device *dev, struct class_interface *class_intf);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • node: 用于将struct class_interface链接到设备类的接口链表中。
    • class: 指向与该接口相关联的设备类。
    • add: 指向设备类调用设备驱动的添加操作的函数指针。当设备添加到设备类时,会调用此函数。
    • remove: 指向设备类调用设备驱动的移除操作的函数指针。当设备从设备类中移除时,会调用此函数。

    通过使用struct class_interface,设备驱动可以与设备类进行交互,以便在设备添加或移除时执行相应的操作。这种机制允许设备驱动与设备类解耦,使得设备驱动可以在设备类的上下文中执行一些操作,而无需直接操作设备类。

    回到device_add()中,使用了list_for_each_entry()遍历interfaces链表,如果设置了class_intf->add_dev,则调用该回调函数指针指向的函数。

    4、总结

    结合本文内容和linux内核源码,得出以下结论:

    • (1)在设备驱动模型中,所有的设备注册操作最后都会调用device_register()函数实现。

    • (2)在笔者分析的linux版本下的device_register()中,存在两个数据结构:struct class_interface 和struct subsys_interface。从内核源码来看,这两个结构只在为数不多的几个特定的驱动程序中使用,具体有什么特殊目的咱不所知,猜测可能是linux内核历史发展遗留下来的代码,在device_register中仍然保留了对这部分代码的支持。

    • (3)在device_register()中调用了bus_probe_device(),从而证明在注册设备的时候发生了**『设备与驱动匹配』**的过程。

  • 相关阅读:
    【MindSpore】用coco2017训练Model_zoo上的 yolov4,迭代了两千多batch_size之后报错,大佬们帮忙看看。
    算法设计与分析期末复习不挂科
    模拟器安装magisk
    【CHI】Ordering保序
    【CSS】常见选择器用法以及常见的属性修改
    编写Android.mk / Android.bp 引用三方 jar 包,aar包,so 库
    计算机网络:IP
    tkmybatis 权威指南 官方文档
    C# 实现数独游戏
    python基于Vue的web信息收集程序设计
  • 原文地址:https://blog.csdn.net/iriczhao/article/details/133841054
  • 最新文章
  • 攻防演习之三天拿下官网站群
    数据安全治理学习——前期安全规划和安全管理体系建设
    企业安全 | 企业内一次钓鱼演练准备过程
    内网渗透测试 | Kerberos协议及其部分攻击手法
    0day的产生 | 不懂代码的"代码审计"
    安装scrcpy-client模块av模块异常,环境问题解决方案
    leetcode hot100【LeetCode 279. 完全平方数】java实现
    OpenWrt下安装Mosquitto
    AnatoMask论文汇总
    【AI日记】24.11.01 LangChain、openai api和github copilot
  • 热门文章
  • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
    奉劝各位学弟学妹们,该打造你的技术影响力了!
    五年了,我在 CSDN 的两个一百万。
    Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
    面试官都震惊,你这网络基础可以啊!
    你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
    心情不好的时候,用 Python 画棵樱花树送给自己吧
    通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
    13 万字 C 语言从入门到精通保姆级教程2021 年版
    10行代码集2000张美女图,Python爬虫120例,再上征途
Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1
正则表达式工具 cron表达式工具 密码生成工具

京公网安备 11010502049817号