嵌入式linux学习记录二

  1. 把驱动程序编译进内核方法:

    1. 把驱动源码放到内核源码树中:根据设备类型不同,把源文件放在linux源码/drivers中相应的目录下。字符设备,通常放进 drivers/char/ 目录下

    2. 修改 Kconfig 文件(让图形配置菜单能看到它):

      1. 用文本编辑器打开 drivers/char/Kconfig

      2. 合适的位置(或者文件末尾 endmenu 之前),添加以下配置文本:
        config CONFIG_HELLO_DRV
        tristate "100ask Hello Driver Support"
        default y
        help
        This is a hello world driver for learning Linux device model. If you say Y here, the driver will be compiled into the kernel.

      3. tristate(三态) :表示这个驱动可以有三种选择:Y(编译进内核)、M(编译成模块)、N(不编译)。

      4. default y这一步是关键! 它意味着默认直接硬编码编译进内核(对应 * 号)。

    3. 修改 Makefile 文件

      1. 打开 drivers/char/Makefile

      2. 在文件里添加下面这一行:

        obj-$(CONFIG_HELLO_DRV) += hello_drv.o

    4. 配置并编译内核

      1. 进入内核源码的根目录

      2. 启动图形配置界面:make menuconfig

      3. 顺着路径找到刚才添加的菜单。

      4. 保存并退出 (会生成新的 .config 文件)。开始编译内核

  2. 把驱动程序编译成模块的另外一种方法:

    1. 在上面的章节(把驱动程序编译进内核方法)中,Kconfig中CONFIG_HELLO_DRV是三态。当选择m时:

      1. 确保你当前处于内核源码的根目录。

      cd /home/book/linux-4.4/

      2. 在根目录下编译内核镜像(可选)

      make Image -j4

      3. 在根目录下编译所有的驱动模块(这一步会生成 hello_drv.ko)

      make modules -j4

      4. 在根目录下执行模块安装命令(这一步会把 hello_drv.ko 提取并打包到你的 rootfs 中)

      make modules_install INSTALL_MOD_PATH=/home/book/rootfs

      #5. 当你刚运行完 make modules 编译成功时,.ko 文件并不会跑到什么神秘的发布文件夹里,而是直接生成在你的驱动源码 .c 文件所在的相同目录下

  3. linux中,/dev目录中的内容:/dev 目录下的每个文件,都代表了一个实打实的硬件接口或者是内核虚拟出来的设备。

    1. 如果你在终端输入 ls -l /dev,你会发现它的输出与普通目录很不一样:例如

      crw-rw---- 1 root dialout 4, 64 May 28 20:00 ttyS0

      brw-rw---- 1 root disk 8, 0 May 28 20:00 sda

      首字母 cb :普通文件是 -,目录是 d。而 /dev 下绝大多数文件开头是 c(字符设备,Character Device)b(块设备,Block Device)。
      没有文件大小,只有主次设备号
      :在原本显示文件大小(Byte)的位置,它们显示的是两个逗号隔开的数字(如 4, 64)。这两个数字就是主设备号次设备号

    2. /dev 目录不是存放在硬盘上的实际文件夹,它是一个由内核(通过 devtmpfs 文件系统)在内存中动态维护的硬件映射表 。它就像是一个硬件插线板,把复杂的底层驱动接口,整整齐齐地转换成了一个个可读写的"文件文件",供上层应用程序调用。

  4. 驱动源代码放入linux源码/drivers目录下编译驱动的方法:

    放置源码

    drivers 目录下找到适合你驱动类型的子目录(例如网络驱动放 net,块设备放 block,字符设备放 char)。如果属于自定义类别,也可以直接在 drivers/ 下新建一个目录。

    1. 示例路径drivers/char/my_driver/

    2. 创建文件 :将你的驱动源码(如 my_driver.cmy_driver.h)放入该目录。

    3. 创建或修改子目录的 Makefile

      内核编译系统依赖各自目录下的 Makefile 来决定编译哪些文件。在你的驱动目录(例如 drivers/char/my_driver/)下创建一个新的 Makefile,并添加以下内容:

      drivers/char/my_driver/Makefile

      CONFIG_MY_DRIVER 是你在 Kconfig 中定义的配置项

      obj-$(CONFIG_MY_DRIVER) += my_driver.o

      如果驱动由多个源文件组成,按如下方式编写:

      obj-$(CONFIG_MY_DRIVER) += my_driver_mod.o

      my_driver_mod-y := my_driver_main.o my_driver_utils.o

    4. 创建或修改子目录的 Kconfig

      为了能在内核的菜单配置(如 make menuconfig)中看到你的驱动,需要配置 Kconfig 文件。在你的驱动目录下创建一个 Kconfig 文件:

      drivers/char/my_driver/Kconfig

      menu "My Custom Driver Support"

      config MY_DRIVER

      tristate "Enable My Custom Driver"

      default n

      help

      This is a description for my custom driver.

      If unsure, say N.

      endmenu

      注意tristate 表示支持三种状态:编译进内核(Y)、编译为模块(M)、不编译(N)。如果只想支持内联或关闭,可以使用 bool

    5. 挂接至上层目录的 Makefile 和 Kconfig

      必须修改你驱动目录的上一级目录 中的文件,让内核编译系统能够递归查找到你的新目录。以挂载在 drivers/char/ 下为例:

      修改 drivers/char/Kconfig

      打开该文件,在适当的位置(通常是文件末尾 endmenu 之前)引入你的 Kconfig

      复制代码
      source "drivers/char/my_driver/Kconfig"

      修改 drivers/char/Makefile

      打开该文件,在适当位置添加整套驱动目录的编译引用:
      Makefile

      复制代码
      obj-$(CONFIG_MY_DRIVER) += my_driver/
    6. 配置与编译

      1. 完成上述文件修改后,返回内核源码的根目录执行以下步骤:

      2. 打开配置菜单

        复制代码
        make menuconfig
      3. 激活驱动 : 在菜单中根据你设置的路径找到 My Custom Driver Support,按键盘 Space 键将其配置为 <*>(编译进内核)或 <M>(编译为独立模块 .ko)。保存并退出。

      4. 开始编译

        • 如果配置为 <M>,可以单独编译该目录:

          复制代码
          make M=drivers/char/my_driver
        • 如果配置为 <*>,则需要编译整个内核:

          复制代码
          make -j$(nproc)
  5. 总线设备驱动模型:
    1.

    模型的核心三大要素

    整个模型可以简单概括为一个公式:Bus (总线) = Device (设备) + Driver (驱动)

    复制代码
            +-----------------------------------------+
            |                Bus (总线)               |
            +-----------------------------------------+
                  |                             |
                  v                             v
       +-----------------------+     +-----------------------+
       |    Device (设备)      |     |    Driver (驱动)      |
       |  (描述硬件: 资源/中断) |     |  (描述软件: 操作方法)  |
       +-----------------------+     +-----------------------+

    ❶ Bus (总线)

    总线是连接设备和驱动的桥梁。在 Linux 内核中,用 struct bus_type 结构体表示。

    1. 物理总线:真实存在的硬件总线,如 PCI、USB、I2C、SPI 等。

    2. 虚拟总线(Platform Bus):为了让那些没有物理总线的设备(如 SoC 内部集成的内存映射外设:GPIO、RTC、UART 等)也能复用这套模型,Linux 虚拟出来了一条平台总线(platform_bus)。

    3. 核心职责:负责匹配(Match)设备和驱动。当有新的设备或驱动注册到总线上时,总线会调用它的 match 函数来检查两者是否匹配。

❷ Device (设备)。类似于stm32中的用户程序,负责确定使用哪个引脚(资源),并且利用HAL库操作引脚。设备负责提供硬件参数(干活需要的数据)。在内核中用 struct device(或其派生的 struct platform_device)表示。

  • 包含内容:寄存器基地址、中断号(IRQ)、DMA 通道、时钟等硬件资源。
  • 特点:不包含控制硬件的代码逻辑,只纯粹描述"这个硬件长什么样、在哪里"。

❸ Driver (驱动),类似于stm32中HAL库的HAL_GPIO_TogglePin函数。这个函数可以操作所有的GPIO,是操作硬件的通用代码。驱动负责提供行为逻辑(怎么干活的代码)。在内核中用 struct device_driver(或其派生的 struct platform_driver)表示。

  • 包含内容probe(探针函数,用于初始化硬件)、remove(卸载函数)、suspend/resume(电源管理)以及标准的字符设备/块设备操作接口(open, read, write 等)。
  • 特点:同一份驱动代码可以支持多个相同的硬件设备。驱动本身不包含硬件死绑定的寄存器地址,而是等总线分发设备给自己时,去读取设备里的资源。

2. 核心工作流程:匹配与初始化

总线设备驱动模型最精妙的地方在于它的动态匹配机制 ,通常被称为 matchprobe

复制代码
[设备注册]                               [驱动注册]
platform_device_register()       platform_driver_register()
         \                                   /
          v                                 v
    +---------------------------------------------+
    |               进入总线设备/驱动链表          |
    +---------------------------------------------+
                           |
                           v
              总线调用:bus_type.match() 
                    (匹配名字/ID)
                           |
                     +-----+-----+
                     |  匹配成功? |
                     +-----+-----+
                           | 是
                           v
              总线调用:driver.probe()
              (分配资源、注册字符设备、运行)

步骤一:注册(Register)

  1. 当系统启动或热插拔时,设备端通过 platform_device_register() 将硬件信息注册进内核,挂在总线的设备链表上。

  2. 驱动程序加载时(如 insmod),通过 platform_driver_register() 将驱动注册进内核,挂在总线的驱动链表上。

步骤二:匹配(Match)

每当链表里新增了成员,总线就会遍历对侧的链表进行一一比对。常见的匹配方式有:

  1. 设备树匹配(最常用) :比对驱动中 of_match_table.compatible 属性与设备树(DTS)节点的 compatible 属性是否一致。

  2. ACPI 匹配:基于 ACPI 表。

  3. ID 列表匹配 :比对驱动的 id_table 内部支持的 ID。

  4. 名字匹配 :直接比对 device.namedriver.name 字符串是否相同。

步骤三:探针(Probe)

如果 match 函数返回 true,总线就会调用驱动中定义的 probe 函数,并将匹配成功的设备指针 作为参数传进去。 在 probe 函数中,驱动会完成以下事情:

  1. 从设备结构体中提取寄存器地址、中断号等资源。

  2. 使用 ioremap 映射寄存器物理地址。

  3. 申请中断(request_irq)。

  4. 注册具体的设备类型(如字符设备 cdev_add),生成 /dev/xxx 节点,供用户空间调用。

  5. platform_device_register和platform_driver_register作用:

  6. platform_device_register:

  • 加入设备链表 :将传入的 struct platform_device 结构体指针挂到平台总线(platform_bus_type)的 设备链表(Devices List) 末尾。

  • 创建 sysfs 节点 :在虚拟文件系统 /sys/devices/platform/ 下创建一个以该设备名字命名的文件夹,暴露硬件拓扑结构。

  1. platform_driver_register:
  • 加入驱动链表 :将传入的 struct platform_driver 结构体指针挂到平台总线(platform_bus_type)的 驱动链表(Drivers List) 末尾。

  • 创建 sysfs 节点 :在虚拟文件系统 /sys/bus/platform/drivers/ 下创建一个以该驱动名字命名的文件夹。

相关推荐
9分钟带帽1 小时前
linux_通过NFS挂载远程服务器的硬盘
linux·服务器
weixin_468466851 小时前
MoneyPrinterTurbo 短视频自动化生产实战指南
运维·人工智能·自动化·大模型·音视频·moneyprinter
難釋懷2 小时前
Nginx自签名-图形化工具 XCA
运维·nginx
元气少女小圆丶3 小时前
SenseGlove Nova 2+Unity开发笔记1
笔记·学习·unity
nashane3 小时前
HarmonyOS 6学习:应用退出动画优化实战——从“闪退“到优雅退出的完美蜕变
学习·华为·harmonyos
运维栈记3 小时前
API Error: 400 Request body format invalid
linux·ai
志栋智能3 小时前
小步快跑:从单一场景开启超自动化巡检之旅
运维·网络·人工智能·自动化
AugustRed4 小时前
Linux 运维常用命令大全(超全速查表)
运维·网络·php
小白兔奶糖ovo4 小时前
【Leetcode】231. 2的幂
linux·算法·leetcode