• linux驱动 设备驱动模型


    前言

    在早期的Liux内核中并没有为设备驱动提供统一的设备模型。随着内核的不断扩大及系统更加复杂,编写一个驱动程序越来越困难,所以在Liux2.6内核中添加了一个统一的设备模型。这样,写设备驱动程序就稍微容易一些了。本章将对设备模型进行详细的介绍。


    一、设备驱动模型概述

    设备驱动模型比较复杂,Lux系统将设备和驱动归一到设备驱动模型中来管理。设备驱动模型的提出,解决了以前编写驱动程序没有统一方法的局面。设备驱动模型给各种驱动程序提供了很多辅助性的函数,这些函数经过严格测试,可以很大程度地提高驱动开发人员的工作效率。

    1.1 设备驱动模型的功能

    Liux内核的早期版本为编写驱动程序提供了简单的功能:分配内存、分配I/O地址、分配中断请求等。写好驱动之后,直接把程序加入到内核的相关初始化函数中,这是一个非常复杂的过程,所以开发驱动程序并不简单。并且,由于没有统一的设备驱动模型,几乎每一种设备驱动程序都需要自己完成所有的工作,驱动程序中不免会产生错误和大量的重复代码。

    有了设备驱动模型后,现在的情况就不一样了。设备驱动模型提供了硬件的抽象,内核使用该抽象可以完成很多硬件重复的工作。这样很多重复的代码就不需要重新编写和调试了,编写驱动程序的难度有所下降。这些抽象包括如下几个方面:

    1.电源管理

    电源管理一直是内核的一个组成部分,在笔记本和嵌入式系统中更是如此,它们使用电池来供电。简单地说,电源管理就是当系统的某些设备不需要工作时,暂时的以最低电耗的方式挂起设备,以节省系统的电能。电源管理的一个重要功能是:在省电模式下,使系统中的设备以一定的先后顺序挂起:在全速工作模式下,使系统中的设备以一定的先后顺序恢复运行。

    例如:一条总线上连接了A、B、C三个设备,只有当A、B、C三个设备都挂起时,总线才能挂起。当A、B、C三个设备中的任何一个恢复以前,总线必须恢复。总之,设备驱动模型使得电源管理子系统能够以正确的顺序遍历系统上的设备。

    2.即插即用设备支持

    越来越多的设备可以即插即用了,最常用的设备就是U盘,甚至连(移动)硬盘也可以即插即用。这种即插即用机制,使得用户可以根据自己的需要安装和卸载设备。设备驱动模型自动捕捉插拔信号,加载驱动程序,使内核容易与设备进行通信。

    3.与用户空间的通信

    用户空间程序通过Sys虚拟文件系统访问设备的相关信息。这些信息被组织成层次结构,用sysfs虚拟文件系统来表示。用户通过对sysfs文件系统的操作,就能够控制设备,或者从系统中读出设备的当前信息。


    1.2 sysfs文件系统

    sysfs文件系统是Linux众多文件系统中的一个。在Linux系统中,每一个文件系统都
    有其特殊的用途。例如ext2用于快速读写存储文件:ext3用来记录日志文件。Liux设备驱动模型由大量的数据结构和算法组成。这些数据结构之间的关系非常的复杂,多数结构之间通过指针互相关联,构成树形或者网状关系。显示这种关系的最好方法是利用一种树形的文件系统,但是这种文件系统需要具有其他文件系统没有的功能,例
    显示内核中的一些关于设备、驱动和总线的信息。为了达到这个目的,Liux内核开发者创建了一种新的文件系统,这就是sysfs文件系统。

    1. sysfs概述

    sysfs文件系统是Liux2.6内核的一个新特性,其是一个只存在于内存中的文件系统。内核通过这个文件系统将信息导出到用户空间中。syss文件系统的目录之间的关系非常复杂,各目录与文件之间既有树形关系,又有目录关系。在内核中,这种关系由设备驱动模型来表示。在syss文件系统中产生的文件大多数是ASCⅡ文件,通常每个文件有一个值,也可叫属性文件。文件的ASCI码特性保证了被导出信息的准确性,而且易于访问,这些特点使syss成为2.6内核最直观、最有用的特性之一。

    2. sysfs文件系统与内核结构的关系

    sysfs文件系统内核对象(kobject)、属性(kobj_type)及它们的相互关系的一种表现机制。用户可以从sysfs文件系统中读出内核的数据,也可以将用户空间的数据写入内核中。这是syss文件系统非常重要的特性,通过这个特性,用户空间的数据就能够传送到内核空间中,从而设置驱动程序的属性和状态。表11.1揭示了内核中的数据结构与sysfs文件系统的关系。

    表11.1 内核结构与sysfs的对应关系

    Linux内核中的结构sysfs中的结构
    kobject目录
    kobj_type属性文件
    对象之间的关系符号链接

    3. sysfs文件系统的目录结构

    sysfs文件系统中包含了一些重要的目录,这些目录中包含了与设备和驱动等相关的信息,现对其详细介绍如下:

    1.sysfs文件系统的目录

    sysfs文件系统与其他文件系统一样,由目录、文件、链接组成。与其他文件系统不同的是,sysfs文件系统表示的内容与其他文件系统中的内容不同。另外,sysfs文件系统只存在于内存中,动态的表示着内核的数据结构。

    sysfs文件系统挂接了一些子目录,这些目录代表了注册了sysfs中的主要的子系统。
    要查看这些子目录和文件,可以使用 ls 命令,命令执行如下:

    [root@tom sys]#ls
    block bus class dev devices firmware fs kernel module power
    
    • 1
    • 2

    当设备启动时,设备驱动模型会注册kobject对象,并在sysfs文件系统中产生以上的目录。现对其中的主要目录所包含的信息进行说明。

    2.block目录
    root@tom :/home/admin# ls /sys/block/
    loop0  loop1  loop2  loop3  loop4  loop5  loop6  loop7  sda
    root@tom :/home/admin# 
    
    • 1
    • 2
    • 3

    块目录包含了在系统中发现的每个块设备的子目录,每个块设备对应一个子目录。每个块设备的目录中有各种属性,描述了设备的各种信息。例如设备的大小、设备号等。

    root@tom :/home/admin# ls /sys/block/sda
    alignment_offset  capability  device             events        events_poll_msecs  hidden   inflight   mq     queue  removable  sda1  sda3  slaves  subsystem  uevent
    bdi               dev         discard_alignment  events_async  ext_range          holders  integrity  power  range  ro         sda2  size  stat    trace
    
    • 1
    • 2
    • 3

    块设备目录中有一个表示了 I/O 调度器的目录,这个目录中提供了一些属性文件。它们是关于设备请求队列信息和一些可调整的特性。用户和管理员可以用它们优化性能,包括用它们动态改变 I/O 调度器。块设备的每个分区表示为块设备的子目录,这些目录中包含了分区的读写属性。

    root@tom :/home/admin# ls /sys/block/sda/sda*
    /sys/block/sda/sda1:
    alignment_offset  dev  discard_alignment  holders  inflight  partition  power  ro  size  start  stat  subsystem  trace  uevent
    
    /sys/block/sda/sda2:
    alignment_offset  dev  discard_alignment  holders  inflight  partition  power  ro  size  start  stat  subsystem  trace  uevent
    
    /sys/block/sda/sda3:
    alignment_offset  dev  discard_alignment  holders  inflight  partition  power  ro  size  start  stat  subsystem  trace  uevent
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    3.bus目录

    总线目录包含了在内核中注册而得到支持的每个物理总线的子目录,例如ide、pci、scsi、usb、i2c和pnp总线等。使用ls命令可以查看bus目录的结构信息,如下所示。

    [root@tom bus]#ls
    ac97 gameport i2c pci pcmcia pnp serio virtio
    acpi hid isa pci express platform scsi usb
    
    • 1
    • 2
    • 3

    ls 命令列出了注册到系统中的总线,其中每个目录中的结构都大同小异。这里以usb目录为例,分析其目录的结构关系。使用cd usb命令,进入usb目录,然后使用ls命令列出usb目录中包含的目录和文件,如下所示。

    [root@tom bus]#cd usb
    [root@tom usb]#1s
    devices drivers drivers autoprobe drivers probe uevent
    
    • 1
    • 2
    • 3

    usb目录中包含了devices和drivers目录。devices目录包含了USB总线下所有设备的列表,这些列表实际上是指向设备目录中相应设备的符号链接。使用 ls 命令查看如下所示。

    root@tom:/home/admin# ls /sys/bus/usb/devices/ -la
    total 0
    drwxr-xr-x 2 root root 0 Mar 20 20:44 .
    drwxr-xr-x 4 root root 0 Mar 20 20:44 ..
    lrwxrwxrwx 1 root root 0 Mar 20 20:44 1-0:1.0 -> ../../../devices/pci0000:00/0000:00:14.0/usb1/1-0:1.0
    lrwxrwxrwx 1 root root 0 Mar 20 20:44 1-1 -> ../../../devices/pci0000:00/0000:00:14.0/usb1/1-1
    lrwxrwxrwx 1 root root 0 Mar 20 20:44 1-1.1 -> ../../../devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1.1
    lrwxrwxrwx 1 root root 0 Mar 20 20:44 1-1:1.0 -> ../../../devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1:1.0
    lrwxrwxrwx 1 root root 0 Mar 20 20:44 1-1.1:1.0 -> ../../../devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1.1/1-1.1:1.0
    lrwxrwxrwx 1 root root 0 Mar 20 20:44 1-1.1:1.1 -> ../../../devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1.1/1-1.1:1.1
    lrwxrwxrwx 1 root root 0 Mar 20 20:44 2-0:1.0 -> ../../../devices/pci0000:00/0000:00:14.0/usb2/2-0:1.0
    lrwxrwxrwx 1 root root 0 Mar 20 20:44 usb1 -> ../../../devices/pci0000:00/0000:00:14.0/usb1
    lrwxrwxrwx 1 root root 0 Mar 20 20:44 usb2 -> ../../../devices/pci0000:00/0000:00:14.0/usb2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    其中1-0:1.0和2-0:1.0是USB设备的名字,这些名字由USB协议规范来定义。可以看出devices目录下包含的是符号链接,其指向/sys/devices目录下的相应硬件设备。硬件的设备文件是在/sys/devices/.目录及其子目录下,这个链接的目的是为了构建sysfs文件系统的层次结构。

    drivers目录包含了USB总线下注册时所有驱动程序的目录。每个驱动目录中有允许查看和操作设备参数的属性文件,和指向该设备所绑定的物理设备的符号链接。

    4.class目录

    类目录中的子目录表示每一个注册到内核中的设备类。例如固件类(firmware)、混杂设备类(misc)、图形类(graphics)、声音类(sound)和输入类(input).等。这些类如下所示。

    root@tom:/home/admin# ls /sys/class/
    ata_device  bsg          dmi             hwmon          mdio_bus        phy           rtc           thermal
    ata_link    dax          drm             i2c-adapter    mem             powercap      scsi_device   tty
    ata_port    dca          drm_dp_aux_dev  input          misc            power_supply  scsi_disk     vc
    backlight   devcoredump  firmware        intel_scu_ipc  net             pps           scsi_generic  vtconsole
    bdi         devfreq      gpio            iommu          nvme            ptp           scsi_host     wakeup
    bf          devlink      graphics        ipmi           nvme-subsystem  pwm           spidev        watchdog
    block       dma          hidraw          leds           pci_bus         regulator     spi_master    wmi_bus
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    类对象只包含一些设备的总称,例如网络类包含一切的网络设备,集中在/sys/class/net目录下。输入设备类包含一切的输入设备,如鼠标、键盘和触摸板等,它们集中在sys/class/input目录下。关于类的详细概念将在后面讲述。


    二、设备驱动模型的核心数据结构

    设备驱动模型由几个核心的数据结构组成,分别是kobject、kset 和 subsystem。这些结构使设备驱动模型组成了一个层次结构。该层次结构将驱动、设备和总线等联系起来,形成一个完整的设备模型。下面分别对这些结构进行详细的介绍。

    2.1 kobject结构体

    宏观上来说,设备驱动模型是一个设备和驱动组成的层次结构。例如一条总线上挂接了很多设备,总线在 Linux 中也是一种设备,为了表述清楚,这里将其命名为A。在A总线上挂接了一个USB控制器硬件B,在B上挂接了设备C和D,当然如果C和D是一种可以挂接其他设备的父设备,那么在C和D设备下也可以挂接其他设备,但这里认为它们是普通设备。另外在A总线上还挂接了E和F设备,则这些设备的关系如图11.1所示。
    在这里插入图片描述
    在sysfs文件系统中,这些设备使用树形目录来表示,如下所示。
    在这里插入图片描述
    树形结构中每一个目录与一个kobject对象相对应,其包含了目录的组织结构和名字等信息。在Linux系统中,kobject 结构体是组成设备驱动模型的基本结构。最初它作为设备的一个引用计数使用,随着系统功能的增加,它的任务也越来越多。kobject提供了最基本的设备对象管理能力,每一个在内核中注册的 kobject 对象都对应于sysfs文件系统中的一个目录。kobject结构体的定义如下:

    1. kobject 结构体

    kobject 结构体的定义如下:
    在这里插入图片描述

    下面对kobject的几个重要成员介绍如下:

    • 第02行是kobject结构体的名称,该名称将显示在sysfs文件系统中,作为一个目录的名字。
    • 第06行代表kobject的属性,可以将属性看成sysfs中的一个属性文件。每个对象都有属性,例如,电源管理需要一个属性表示是否支持挂起:热插拔事件管理需要一个属性来现实设备的状态。因为大部分的同类设备都有相同的属性,因此将这个属性单独组织为一个数据结构kobj_type,存放在ktype中。这样就可以灵活地管理属性了。需要注意的是,对于sysfs中的普通文件读写操作都是由kobject->ktype-.>sysfs_ops指针来完成的。对kobj_ype的详细说明将在后面列出。
    • 第08行的kref字段表示该对象引用的计数,内核通过kref实现对象引用计数管理。内核提供两个函数kobject_get()、kobject_put()分别用于增加和减少引用计数,当引用计数为0时,所有该对象使用的资源被释放。下文将对这两个函数详细解释。
    • 第09行的state initialized表示kobject是否已经初始化过,1表示初始化,0表示未初始化。unsigned int state initialized:1 中的 1 表示,只用unsigned int的最低 1 位表示这个布尔值。
    • 第 10 行的state_in_sysfs 表示 kobject 是否已经注册到sysfs文件系统中。
    2.kobjects结构体的初始化函数kobject_init()

    对kobejct结构体进行初始化有些复杂。但无论如何,首先应将整个kobject设置为0,一般使用memset()函数来完成。如果没有对kobject置 0,那么在以后使用kobject时,可能发生一些奇怪的错误。对kobject置 0 后,可以调用kobject_init()函数,对其中的成员进行初始化,该函数的代码如下:
    在这里插入图片描述

    • 第04~11行,检查kobj和ktype是否合法,它们都不应该是一个空指针。

    • 第12~16行,判断该kobj是否已经初始化过了,如果已经初始化,则打印出错信息。

    • 第18行调用kobject_init_internal()函数初始化kobj结构体的内部成员,该函数将在下面介绍。

    • 第19行将定义的一个属性结构体ktype赋给 kobj->ktype。.这是一个kobj_type结构体,与sysfs文件的属性有关,将在后面介绍。例如一个喇叭设备在sysfs目录中注册了一个A目录,该目录对应一个名为A的kobject结构体。即使再普通的喇叭也应该有一个音量属性,用来控制和显示声音的大小,这个属性可以在A目录下用一个名为B的属性文件来表示。很显然,如果要控制喇叭的声音大小,应该对B文件进行写操作,将新的音量值写入:如果要查看当前的音量,应该读B文件。所以属性文件B应该是一个可读可写的文件。

    3.初始化kobject的内部成员函数kobject_init_internal()

    在前面的函数kobject_init()第 18 行,调用了kobject_init_internal()函数初始化kobject的内部成员。该函数的代码如下:
    在这里插入图片描述
    该函数主要对kobject的内部成员进行初始化,例如引用计数kref,连接kboject的entry链表等。

    4.kobject 结构体的引用计数操作

    kobject_get() 函数是用来增加kobject的引用计数,引用计数由kobject结构体的kref成员表示。只要对象的引用计数大于等于1,对象就必须继续存在。kobject_get() 函数的代码如下:
    在这里插入图片描述
    kobject_get() 函数将增加kobject的引用计数,并返回指向kobject的指针,如果当kobject 对象已经在释放的过程中,那么kobject_get()函数将返回NULL值。

    kobject_put()函数用来减少kobject的引用计数,当kobject的引用计数为0时,系统就将释放该对象和其占用的资源。前面讲的kobject_init()函数设置了引用计数为 1,所以在创建kobject对象时,就不需要调用kobject_get() 函数增加引用计数了。当删除kobject对象时,需要调用kobject_put()函数减少引用计数。该函数的代码如下:
    在这里插入图片描述
    前面已经说过,当kobject的引用计数为0时,将释放kobject对象和其占用的资源。由于每一个kobject对象所占用的资源都不一样,所以需要驱动开发人员自己实现释放对象资源的函数。该释放函数需要在kobject的引用技术为0时,被系统自动调用。
    kobject_put()函数中第 08 行的 kref_put() 函数的第2个参数指定了释放函数,该释放函数是kobject_release(),其由内核实现,在其内部调用了 kobj_type 结构中自定义的release()函数。由此可见 kobj_type 中的 release() 函数是需要驱动开发人员真正实现的释放函数。从kobject_put()函数到调用自定义的release()函数的路径如图 11.2 所示。
    在这里插入图片描述

    5.设置 kobject 名字的函数

    用来设置kobject.name的函数有两个,分别是kobject_set_name() 和kobject_rename() 函数,这两个函数的原型是:
    在这里插入图片描述

    第1个函数用来直接设置kobject结构体的名字。该函数的第1个参数是需要设置名字的kobject对象,第2个参数是一个用来格式化名字的字符串,与C语言中printf() 函数的对应参数相似。

    第2个函数用来当kobject已经注册到系统后,如果一定要改kobject结构体的名字时使用。


    2.2设备属性 kobj_type

    每个kobject对象都有一些属性,这些属性由 kobj_type 结构体表示。最开始,内核开发者考虑将属性包含在kobject结构体中,后来考虑到同类设备会具有相同的属性,所以将属性隔离开来,由kobj_type表示。kobject中有指向kobj_type的指针,如图 11.3 所示。

    在这里插入图片描述
    结合图11.3,将解释几个重要的问题。

    • kobject 始终代表sysfs文件系统中的一个目录,而不是文件。对kobject_add() 函数的调用将在sysfs文件系统中创建一个目录。最底层目录对应于系统中的一个设备、驱动或者其他内容。通常一个目录中包含一个或者多个属性,以文件的方式表示,属性由kype指向。
    • kobject对象的成员name是sysfs文件系统中的目录名,通常使用kobject_set_name()函数来设置。在同一个目录下,不能有相同的目录名。
    • kobject在sysfs文件系统中的位置由parent指针指定。parent指针指向一个kojbect结构体,kobject对应一个目录。
    • kobj_type是kobject的属性。一个kobject可以有一个或者多个属性。属性用文件来表示,放在kobject对应的目录下。
    • attribute表示一个属性,其具体定义将在下面介绍。
    • sysfs_ops表示对属性的操作函数。一个属性只有两种操作,一种是读操作,一种是写操作。
    1.属性结构体kobj_type

    创建 kobject 结构体的时候,会给 kobject 一些默认的属性.这些属性保存在 kobj_type 结构体中,该结构体定义如下:
    在这里插入图片描述

    kobj_type的 default_attrs 成员保存了属性数组,每一个kobject对象可以有一个或者多
    个属性。属性结构体的定义如下:

    在这里插入图片描述
    在这个结构体中,name 是属性的名字,对应某个目录下的一个文件的名字。owner指向实现这个属性的模块指针,就是驱动模块的指针。在x86平台上,己经不推荐使用了。mode是属性的读写权限,也就是sysfs中文件的读写权限。这些权限在include\linux\stat.h 文件中定义。S_IRUGO表示属性可读;S_IWUGO表示属性可写。

    2.操作结构体sysfs_ops

    kobj_type 结构的字段 default_attrs 数组说明了一个kobject都有哪些属性,但是并没有说明如何操作这些属性。这个任务要使用 kobj_type->sysfs_ops 成员来完成,sysfs_ops结构体的定义如下:
    在这里插入图片描述

    • show() 函数用于读取一个属性到用户空间。函数的第1个参数是要读取的kobject 的指针,它对应要读的目录:第2个参数是要读的属性:第3个参数是存放读到的属性的缓存区。当函数调用成功后,会返回实际读取的数据长度,这个长度不能超过PAGE SIZE个字节大小。

    • store() 函数将属性写入内核中。函数的第1个参数是与写相关的kobject的指针,它对应要写的目录:第2个参数是要写的属性:第3个参数是要写入的数据:第4个参数是要写入的参数长度。这个长度不能超过PAGE SIZE个字节大小。只有当拥有属性有写权限时,才能调用store()函数。

    • 说明:sys文件系统约定一个属性不能太长,一般一至两行左右,如果太长,需要把它分为多个属性。

    这两个函数比较复杂,下面举一个关于这两个函数的例子。代码如下:
    在这里插入图片描述
    在这里插入图片描述
    kobject_test_show() 函数将kobject的名字赋给buf,并返回给用户空间。例如在用户空间使用cat命令查看属性文件时,会调用kobject test show()函数,并显示kobject的名字。

    kobject_test_store()函数用于将来自用户空间的buf数据写入内核中,此次并没有实际的写入操作,可以根据具体情况写入一些需要的数据。

    3.kobj_type结构体的release()函数

    在上面讨论kobj_type的过程中,遗留了一个重要的函数,就是release() 函数。该函数表示当kojbect的引用计数为0时,将对kobject采取什么样的操作。对kobject_put()函数的讲解中,已经对该函数做了铺垫,该函数的原型如下:
    在这里插入图片描述
    该函数的存在至少有两个原因:第一,每一个kobject对象在释放时,可能都有一些不同的操作,所以并没有一个统一的函数对kobject及其包含的结构进行释放操作。第二,创建kobject的代码并不知道什么时候应该释放koject对象。所以kobject维护了一个引用计数,当计数为0时,则在合适的时候系统会调用自定义的release()函数来释放kobject对象。一个release() 函数的模板如下:

    在这里插入图片描述
    kobject一般包含在一个更大的自定义结构中,这里就是my_object对象。在驱动程序中,为了完成驱动的一些功能,该对象在系统中申请了一些资源,这些资源的释放就在自定义的kobject_test_release()中完成。
    需要注意的是;每一个kobject对象都有一个release()方法,此方法会自动在引用计数为0时,被内核调用,不需要程序员来调用。如果在引用计数不为0时调用,就会出现错误。

    4.非默认属性

    注意:下面的两个函数 sysfs_create_file(struct kobject *kobj, const struct attribute *attr) 和 sysfs_remove_file(struct kobject *kobj, const struct attribute *attr) ,用来给客户增加额外特殊的属性文件使用。即:在 kobject 默认的属性之外,增加特殊的属性文件。

    在许多的情况下,kobject类型的 default_attrs 成员定义了 kobject 拥有的所有默认属性。但是在特殊情况下,也可以对kobject添加一些非默认的属性,用来控制kobject代表的总线、设备和驱动的行为。例如为驱动的kobject结构体添加一个属性文件switch,用来选择驱动的功能。假设驱动有功能A和B,如果写switch为A,那么选择驱动的A功能,写switch为B,则选择驱动的B功能。添加非默认属性的函数原型如下:
    在这里插入图片描述

    如果函数执行成功,则使用attribute结构中的名字创建一个属性文件,并返回0,否则返回一个负的错误码。这里举一个创建switch属性的例子,其代码如下:
    在这里插入图片描述

    内核提供了sysfs_remove_file() 函数来删除属性,其函数原型如下:

    void sysfs remove file(struct kobject kobj,const struct attribute attr);

    调用该函数成功,将在sysfs文件系统中删除 attr 属性指定的文件。当属性文件删除后,如果用户空间的某一程序仍然拥有该属性文件的文件描述符,那么利用该文件描述符对属性文件的操作会出现错误,需要引起开发者的注意。


    三. 注册kobject到sysfs中的实例

    为了对kobject对象有一个清晰的认识,这里将尽快给读者展现一个完整的实例代码。在讲解这个实例代码之前,需要重点讲解一下到目前为止,我们需要知道的设备驱动模型结构。

    1. 设备驱动模型结构

    在Linux设备驱动模型中,设备驱动模型在内核中的关系用kobject结构体来表示。在用户空间的关系用sysfs文件系统的结构来表示。如图11.4所示,左边是bus子系统在内核中的关系,使用kobject结构体来组织。右边是sysfs文件系统的结构关系,使用目录和文件来表示。左边的kobject和右边的目录或者文件是一一对应的关系,如果左边有一个kobject对象,那么右边就对应一个目录。文件表示该kobject的属性,并不与kobject相对应。

    在这里插入图片描述

    2. kset集合

    kobject通过kset组织成层次化的结构。kset 是具有相同类型的kobject集合,像驱动程序一样放在/sys/drivers/目录下,目录drivers是一个kset对象,包含系统中的驱动程序对应的目录,驱动程序的目录由kobject表示。

    root@tom:/home/admin# ls /host/image-202111.0-dirty-20220630.095412/rw/usr/lib/modules/5.10.0-8-2-amd64/kernel/drivers -l
    total 672
    drwxr-xr-x  3 root root  4096 Jun 30  2022 accessibility
    drwxr-xr-x  3 root root  4096 Jul 13  2022 acpi
    drwxr-xr-x  2 root root  4096 Jul 13  2022 android
    drwxr-xr-x  2 root root 12288 Jul 13  2022 ata
    drwxr-xr-x  2 root root  4096 Jul 13  2022 atm
    drwxr-xr-x  3 root root  4096 Jun 30  2022 base
    drwxr-xr-x  2 root root  4096 Jul 13  2022 bcma
    drwxr-xr-x  8 root root  4096 Jul 13  2022 block
    drwxr-xr-x  2 root root  4096 Jul 13  2022 bluetooth
    drwxr-xr-x  3 root root  4096 Jun 30  2022 bus
    drwxr-xr-x  2 root root  4096 Jul 13  2022 cdrom
    drwxr-xr-x  7 root root  4096 Jul 13  2022 char
    drwxr-xr-x  2 root root  4096 Jul 13  2022 cpufreq
    drwxr-xr-x  6 root root  4096 Jul 13  2022 crypto
    drwxr-xr-x  4 root root  4096 Jul 13  2022 dax
    drwxr-xr-x  2 root root  4096 Jul 13  2022 dca
    drwxr-xr-x  2 root root  4096 Jul 13  2022 devfreq
    drwxr-xr-x  4 root root  4096 Jul 13  2022 dma
    drwxr-xr-x  2 root root  4096 Jul 13  2022 edac
    drwxr-xr-x  2 root root  4096 Jul 13  2022 extcon
    drwxr-xr-x  2 root root  4096 Jul 13  2022 firewire
    drwxr-xr-x  3 root root  4096 Jul 13  2022 firmware
    drwxr-xr-x  2 root root  4096 Jul 13  2022 gnss
    drwxr-xr-x  2 root root  4096 Jul 13  2022 gpio
    drwxr-xr-x  3 root root  4096 Jun 30  2022 gpu
    drwxr-xr-x  5 root root 20480 Jul 13  2022 hid
    drwxr-xr-x  2 root root  4096 Jul 13  2022 hv
    drwxr-xr-x  3 root root 20480 Jul 13  2022 hwmon
    drwxr-xr-x  3 root root  4096 Jun 30  2022 hwtracing
    drwxr-xr-x  5 root root  4096 Jul 13  2022 i2c
    drwxr-xr-x 16 root root  4096 Jul 13  2022 iio
    drwxr-xr-x  6 root root  4096 Jun 30  2022 infiniband
    drwxr-xr-x 11 root root  4096 Jul 13  2022 input
    drwxr-xr-x  5 root root  4096 Jun 30  2022 isdn
    drwxr-xr-x  3 root root  4096 Jul 13  2022 leds
    drwxr-xr-x  4 root root  4096 Jul 13  2022 md
    drwxr-xr-x 18 root root  4096 Jun 30  2022 media
    drwxr-xr-x  4 root root  4096 Jun 30  2022 memstick
    drwxr-xr-x  3 root root  4096 Jun 30  2022 message
    drwxr-xr-x  2 root root  4096 Jul 13  2022 mfd
    drwxr-xr-x 11 root root  4096 Jul 13  2022 misc
    drwxr-xr-x  4 root root  4096 Jun 30  2022 mmc
    drwxr-xr-x 10 root root  4096 Jul 13  2022 mtd
    drwxr-xr-x 28 root root  4096 Jul 13  2022 net
    drwxr-xr-x  4 root root  4096 Jul 13  2022 nfc
    drwxr-xr-x  2 root root  4096 Jul 13  2022 nvdimm
    drwxr-xr-x  4 root root  4096 Jun 30  2022 nvme
    drwxr-xr-x  2 root root  4096 Jul 13  2022 parport
    drwxr-xr-x  5 root root  4096 Jul 13  2022 pci
    drwxr-xr-x  2 root root  4096 Jul 13  2022 pcmcia
    drwxr-xr-x  3 root root  4096 Jun 30  2022 pinctrl
    drwxr-xr-x  5 root root  4096 Jun 30  2022 platform
    drwxr-xr-x  3 root root  4096 Jun 30  2022 power
    drwxr-xr-x  2 root root  4096 Jul 13  2022 powercap
    drwxr-xr-x  3 root root  4096 Jul 13  2022 pps
    drwxr-xr-x  2 root root  4096 Jul 13  2022 ptp
    drwxr-xr-x  2 root root  4096 Jul 13  2022 pwm
    drwxr-xr-x 33 root root 12288 Jul 13  2022 scsi
    drwxr-xr-x  2 root root  4096 Jul 13  2022 slimbus
    drwxr-xr-x  3 root root  4096 Jun 30  2022 soc
    drwxr-xr-x  2 root root  4096 Jul 13  2022 soundwire
    drwxr-xr-x  2 root root  4096 Jul 13  2022 spi
    drwxr-xr-x  2 root root  4096 Jul 13  2022 ssb
    drwxr-xr-x 13 root root  4096 Jun 30  2022 staging
    drwxr-xr-x  6 root root  4096 Jul 13  2022 target
    drwxr-xr-x  3 root root  4096 Jun 30  2022 thermal
    drwxr-xr-x  2 root root  4096 Jul 13  2022 thunderbolt
    drwxr-xr-x  4 root root  4096 Jul 13  2022 tty
    drwxr-xr-x  2 root root  4096 Jul 13  2022 uio
    drwxr-xr-x 16 root root  4096 Jun 30  2022 usb
    drwxr-xr-x  4 root root  4096 Jul 13  2022 vfio
    drwxr-xr-x  2 root root  4096 Jul 13  2022 vhost
    drwxr-xr-x  4 root root  4096 Jul 13  2022 video
    drwxr-xr-x  3 root root  4096 Jun 30  2022 virt
    drwxr-xr-x  2 root root  4096 Jul 13  2022 virtio
    drwxr-xr-x  4 root root  4096 Jul 13  2022 w1
    drwxr-xr-x  2 root root  4096 Jul 13  2022 watchdog
    drwxr-xr-x  4 root root  4096 Jul 13  2022 xen
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    1.kset集合

    kset结构体的定义如下代码:
    在这里插入图片描述

    • 第 01 行表示一个链表。包含在kset中的所有kobject对象被组织成一个双向循环链表,Iist就是这个链表的头部。
    • 第 03 行是用来从list中添加或者删除kobject的自旋锁。
    • 第 04 行是一个内嵌的kobject对象.所有属于这个kset集合的kobject对象的parent指针,均指向这个内嵌的kobject对象。另外kset的引用计数就是内嵌的kobject对象的引用计数。
    • 第 05 行是支持热插拔事件的函数集。
    2. 热插拔事件kset_uevent_ops

    一个热插拔事件是从内核空间发送到用户空间的通知,表明系统某些部分的配置已经发生变化。用户空间接收到内核空间的通知后,会调用相应的程序,处理配置的变化。例如,当U盘插入到USB系统时,会产生一个热插拔事件,内核会捕获这个热插拔事件,并调用用户空间的/sbi/hotplug程序,该程序通过加载驱动程序来响应U盘插入的动作。在早期的系统中,如果要加入一个新设备,必须要关闭计算机,插入设备,然后再重启,这是一个非常繁琐的过程。现在计算机系统的硬软件已经有能力支持设备的热插拔,这种特性带来的好处是,设备可以即插即用,节省用户的时间。

    内核将在什么时候产生热插拔事件呢?当驱动程序将 kobject 注册到设备驱动模型时,会产生这些事件。也就是当内核调用 kobject_add() 和 kobject_del() 函数时,会产生热插拔事件。热插拔事件产生时,内核会根据 kobject 的 kset 指针找到所属的 kset 结构体,执行 kset 结构体中 uevent_ops 包含的热插拔函数。这些函数的定义如下:
    在这里插入图片描述

    • 第02行的filter()函数是一个过滤函数。通过filter() 函数,内核可以决定是否向用户空间发送事件产生信号。如果filter() 返回0,表示不产生事件:如果filter()返回1,表示产生事件。例如,在块设备子系统中可以使用该函数决定哪些事件应该发送给用户空间。在块设备子系统中至少存在3种类型的kobject结构体:磁盘、分区和请求队列。用户空间需要对磁盘和分区的改变产生响应,但一般不需要对请求队列的变化产生响应。在把事件发送给用户空间时,可以使用filter()函数过滤不需要产生的事件。块设备子系统的过滤函数如下:
      在这里插入图片描述
    • 第03行的name() 函数在用户空间的热插拔程序需要知道子系统的名字时被调用。该函数将返回给用户空间程序一个字符串数据。该函数的一个例子是dev_uevent_name() 函数,代码如下:
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      该函数先由 kobj 获得 device 类型的 dev 指针。如果该设备的总线存在,则返回总线的名字,否则返回设备类的名字。
    • 任何热插拔程序需要的信息可以通过环境变量来传递。uevent()函数可以在热插拔程序执行前,向环境变量中写入值。
      在这里插入图片描述
      在这里插入图片描述

    3. kset与kobject的关系

    kset 是 kobject 的一个集合,用来与kobject建立层次关系。内核可以将相似的kobject结构连接在kset集合中,这些相似的kobject可能有相似的属性,使用统一的kset来表示。
    图11.5 显示了kset集合和koject之间的关系。

    • kset集合包含了属于其的kobject结构体,kset.list链表用来连接第一个和最后一个kobject对象。第一个kobject使用 entry 连接 kset 集合和第二个 kobject 对象。第二个kobject对象使用 entry 连接第一个 kobject 对象和第三个 kobject 对象,依次类推,最终形成一个kobject对象的链表。

    • 所有 kobject 结构的 parent 指针指向kset包含的kobject对象,构成一个父子层次关系。

    • kobject的所有kset指针指向包含它的kset集合,所以通过kobject对象很容易就能找到kset集合。

    • kobject 的 kobj_ype 指针指向自身的 kobj_type, 每一个kobject都有一个单独的kobj_type结构。另外在kset集合中也有一个kobject结构体,该结构体的xxx也指向一个kobj_type结构体。从前文中知道,kobj_type中定义了一组属性和操作属性的方法。这里需要注意的是,kset中kobj_type的优先级要高于kobject对象中kobj_type的优先级。如果两个kobj_type都存在,那么优先调用kset中的函数。如果kset中的kobj_type为空,才调用各个kobject结构体自身对应的kobj_type中的函数。
      在这里插入图片描述

    • kset中的kobj也负责对kset的引用计数。

    4. kset相关的操作函数

    kset相关的操作函数与kobject的函数相似,也有初始化、注册和注销等函数。下面对这些函数进行介绍。

    1.初始化函数kset_init()

    kset_init()函数用来初始化kset对象的成员,其中最重要的是初始化kset.kobj成员,使用上面介绍过的kobject_init_internal() 函数。
    在这里插入图片描述
    在这里插入图片描述

    2.注册函数kset_register()

    kset_register()函数用来完成系统对kset的注册,函数的原型如下:
    在这里插入图片描述

    3.注销函数kset_unregister()

    kset_unregister() 函数用来完成系统对kset的注销,函数的原型如下:
    在这里插入图片描述

    4.kset的引用计数

    kset也有引用计数,该引用计数由kset的kobj成员来维护。可以使用kset_get()函数增加引用计数,使用kset_put()函数减少引用计数。这两个函数的原型如下:
    在这里插入图片描述

    5. 注册kobject到sysfs中的实例

    对kobject和kset有所了解后,本节将讲解一个实例程序,以使读者对这些概念有更清楚的认识。这个实例程序的功能是:在/sys目录下添加了一个名为kobject_test的目录名,并在该目录下添加了一个名为kobject_test_attr 的文件,这个文件就是属性文件。本实例可以通过kobject_test_show() 函数显示属性的值;也可以通过kobject_test_store() 函数向属性中写入一个值。这个实例的完整代码如下:


  • 相关阅读:
    虹科分享 | 网络保险:有效承保网络风险解决方案
    算法笔记-第十章-动态规划2
    2021世界最佳葡萄园榜单出炉,瑞格尔侯爵酒庄再次位居前列
    LeetCode50天刷题计划(Day 14—— 删除链表的倒数第 N 个结点(12.20-13.00)
    Oracle/PLSQL: Atan Function
    go 中的循环依赖
    Leetcode236 二叉树两节点的最近公共祖先
    JavaScript 模块导出示例
    香港日本服务器好机推荐CN2三网直连高速又稳定
    HTTP的本质理解
  • 原文地址:https://blog.csdn.net/weixin_42109053/article/details/126279965