当前位置: 首页 > ops >正文

驱动相关基础

一、驱动分类与区别

    字符设备驱动

        一个字节一个字节进行读写操作的设备,以字符流的形式进行数据传输(如鼠标、键盘、串口)。        

    块设备驱动

        以为单位进行读写操作的设备,块的大小通常为 512 字节、1024 字节。

        块设备驱动主要用于管理存储设备,支持随机访问和高效的数据传输(磁盘、硬盘、SD 卡)

    网络设备驱动

        负责将网络数据包物理层传输到网络层,并将网络层的数据包封装成物理层可以传输的形式。

        网络设备驱动需要支持多种网络协议接口标准(以太网网卡驱动)。

二、驱动的编译流程

    (1)内核源码目录内

        在 /drivers/char 目录下编写驱动,在 Makefile 中添加 

obj -m $(XXXX) += xxx.o

Kconfig 中添加对应驱动的相关选项信息,在顶层目录中通过 make menuconfig 找到添加好的驱动选项并选为 M/Y动态编译/静态编译),会将驱动的编译方式写入到 .config 文件,输入

make / make modules 即可被静态编译进内核或者编译成 .ko 模块。

    (2)内核源码目录外

        编译好驱动后,通过 Makefile 指定源码路径,当前目录路径,驱动模块名等,make 编译即可在当前目录下生成驱动模块。

三、静态编译和动态编译的区别

    静态编译:

        将驱动直接编译进内核,后续如果要从内核中删除该驱动,需要通过 make menuconfig 将该驱动选项选为 N ,重新编译内核,开发调试较为复杂。

    动态编译:

        将驱动编译成 .ko 模块,后续如果更改了驱动直接重新编译生成新的 .ko 模块即可,方便开发、调试。

四、字符型设备驱动的流程

  (1)构建 cdev 结构体,用来描述字符型设备相关信息。

  (2)构建 file_operation 结构体,用来描述驱动层操作函数。

  (3)完成 module_init()module_exit()宏的填充,指定驱动模块的初始化和注销函数,

MODULE_LICENSE 指定 GNU 组织的 GPL 协定。

  (4)实现驱动的初始化 init 函数,调用

        aloc_chrdev_region / register_chrdev_region

        cdev_init

        cdev_add

        class_create

        device_create

        完成对字符型设备的注册、cdev 结构体的初始化、cdev 结构体增加到系统,自动生成设备节点,此后完成相应硬件初始化。

  (5)实现驱动的注销 exit 函数,调用

        unregister_chrdev_region

        cdev_del

        class_destroy

        device_destroy

        完成对字符型设备的注销、cdev 的注销、节点删除,再调用相关函数完成硬件的注销。

  (6)完善 file_operation 结构体中的操作函数,如 openreadwriterelease 等函数。

五、静态注册字符型设备和动态注册的区别

    静态注册:

        事先定义好设备号,将定义好的设备号注册到系统。

    动态注册:

        让内核自动分配一个未被占用的设备号,可避免和内核中的设备号冲突。

六、如何手动创建设备节点

    mknod /dev/xxx c 250

    /dev/xxx :设备节点名

    c :设备类型,字符型设备

    250:主设备号

    0 :次设备号

七、如何加载驱动模块

    insmod / modprobe

八、inmsod 和 modprobe 加载驱动模块的区别

    如果要加载多个驱动模块时,多个驱动模块之间可能存在依赖关系(比如 A.ko 依赖 B.ko,此时如果 insmod 直接加载 A.ko 会导致失败,因为 insmod 不会分析驱动模块间的依赖关系)

    modprobe:能够分析驱动模块间的依赖关系,确保在加载一个驱动模块时,其依赖的其他驱动模块也会被加载。

    insmod:不会分析驱动模块间的依赖关系。

九、主设备号和次设备号的区别

    主设备号:确定是哪一类设备。

    次设备号:该类设备下的具体哪个设备。

十、设备号的大小

    Linux 中,用 32 位的 unsigned int 表示设备号,高 12 位是主设备号,低 20 位是次设备号。

十一、如何构建设备号

    通过 MKDEV 宏传入主次设备号构建一个新的设备号。

十二、__init 修饰 init 函数的作用

    __init 用来标记驱动模块的初始化函数,当执行 insmod 时,该初始化函数会被调用将标记的代码放在特定的内存(.init.text)在加载驱动模块后,这些初始化代码所占用的内存会被自动释放,节省内核空间。

十三、__exit 修饰 init 函数的作用

    __exit 用来标记驱动模块的注销函数,当执行 rmmod 时,该注销函数会被调用,释放相应资源,__exit 宏会将标记的代码段存放在特定的内存区域(.exit.text)

十四、杂项设备驱动的主设备号是多少

    10

十五、杂项设备驱动和字符型设备驱动的区别

    字符型设备驱动不会自动生成设备节点,需要手动用 mknod 生成或者调用 class_create

device_create 自动创建设备节点,主设备号不固定。

    杂项设备驱动可以自动生成设备节点,主设备号固定是 10,代表设备类型。

十六、platform 总线思想

    platform ,虚拟总线,通过 device-driver-bus 实现设备和驱动分离的思想,将 device driver 注册到 platform 总线,通过 device name driver name 匹配,匹配成功后执行 driver 中的 probe 函数,注销执行 remove 函数。

十七、platform 中的 device 和 driver 如何匹配

    platform_match 函数通过比较总线上的 device 和 driver 的名字来实现设备和驱动匹配。

十八、platform 驱动实现的流程

  (1)构建 device.cdriver.c

  (2)device.c 中完善 platform_device 结构体描述设备信息,通过调用 init 函数中的 platform_device_register 函数将设备注册到 bus 总线,调用 exit 函数中的 platform_device_unregister 函数将设备从 bus 总线注销。

  (3)driver.c 中构建 platform_driver 结构体,填充 proberemove 等成员;

           在 init 函数中调用 platform_driver_register 函数将驱动注册到 bus 总线;

           exit 函数中调用 platform_driver_unregister 函数将驱动从 bus 总线注销;

           probe 函数中实现字符型设备驱动的相关初始化以及硬件初始化;

           remove 函数中实现字符型设备驱动的相关注销以及硬件注销。

  (4)device driver bus 总线上通过 platform_match 函数匹配成功后执行驱动中的 probe 函数。

十九、platform_match 函数匹配原理

    strcmp 比较 device 和 driver 的 .name 字段 ,匹配成功函数返回 1,失败返回 0。

二十、I2C 的驱动框架介绍(I2C子系统)

    I2C_hardware 层:具体硬件层(I2C传感器)。

    I2C_adapter:I2C 适配器层,驱动厂家实现。

    I2C_coreI2C 核心层,提供 I2C 设备和驱动的注册、匹配及通信方法。

    I2C_client / I2C_driver :遵循 platform 设备驱动分离思想,匹配成功后执行 probe 函数。

    I2C 传感器挂载在对应的 I2C 总线上,I2C 适配器层驱动由芯片厂家实现,I2C 核心层用来管理 

I2C 设备和驱动的注册、匹配以及 I2C 通信方法,设备驱动层通过将 I2C 总线上的设备和驱动匹配,执行 probe 函数。应用层调动驱动层接口实现 I2C 的读取写入。

二十一、I2C 子系统中,device 和 driver 匹配的过程

1.注册 I2C 适配器

    在 device 文件中,定义设备,携带控制器寄存器地址、中断等信息。

static struct resource s3c_i2c_resource[] = {[0] = { .start = S3C2440_PA_I2C, .end = S3C2440_PA_I2C + 0x3f, .flags = IORESOURCE_MEM },[1] = { .start = IRQ_I2C, .end = IRQ_I2C, .flags = IORESOURCE_IRQ },
};static struct platform_device s3c_device_i2c = {.name = "s3c2440-i2c",.id = 0,.num_resources = ARRAY_SIZE(s3c_i2c_resource),.resource = s3c_i2c_resource,
};

    在 driver 文件中,在 probe 函数中初始化 I2C 适配器;

static int s3c2440_i2c_probe(struct platform_device *pdev) {struct s3c2440_i2c *i2c = devm_kzalloc(&pdev->dev, sizeof(*i2c), GFP_KERNEL);// 映射寄存器、申请中断、设置时钟频率等i2c->adapter.name = "s3c2440-i2c";i2c->adapter.algo = &s3c2440_i2c_algo; // 通信算法(如 xfer 函数)int ret = i2c_add_adapter(&i2c->adapter); // 注册适配器到 I2C 总线return ret;
}

    i2c_add_adapter 函数会触发 I2C 总线扫描,尝试与已注册的设备匹配。

2.注册 I2C 设备

    在 device 文件在中,定义设备信息,如从机地址、设备名称,适配器初始化时自动创建设备。

static struct i2c_board_info smdk2440_i2c_devs[] __initdata = {{I2C_BOARD_INFO("24c02", 0x50), // 设备名称为 "24c02",地址 0x50.platform_data = &eeprom_platform_data, // 可选平台数据},
};static void __init smdk2440_i2c_init(void) {i2c_register_board_info(0, smdk2440_i2c_devs, ARRAY_SIZE(smdk2440_i2c_devs));
}

    i2c_register_board_info 会在适配器注册时(通过 i2c_add_adapter)自动调用 i2c_new_device 来创建 i2c_device

3.注册 I2C 驱动

    通过 i2c_driver 结构体注册到 I2C 总线:

static struct i2c_device_id eeprom_ids[] = {{ "24c02", 0 }, // 匹配设备名称 "24c02"{ }
};
MODULE_DEVICE_TABLE(i2c, eeprom_ids);static struct i2c_driver eeprom_driver = {.driver = {.name = "eeprom", // 驱动名称},.probe = eeprom_probe,.remove = eeprom_remove,.id_table = eeprom_ids, // 匹配表
};static int __init eeprom_driver_init(void) {return i2c_add_driver(&eeprom_driver); // 注册驱动到 I2C 总线
}
module_init(eeprom_driver_init);

匹配逻辑:

    当设备或驱动注册到 I2C 总线时,触发匹配流程(i2c_bus_type.match 函数)

        1.设备注册时:

                新设备(i2c_device)注册到总线后,总线遍历所有已注册的驱动。

                对每个驱动,检查以下条件:

                        (1)ID 表匹配:遍历驱动的 id_table,检查设备名称(name)或从机地址是否匹配。比如,设备名称为 “myi2c”,驱动 id_table 包含 “myi2c”,则匹配成功。

                        (2)若驱动没有提供 id_table,直接比较设备名称与驱动名称。

                匹配成功后,调用驱动的 probe 函数,并传入设备结构体。

        2.驱动注册时:

                总线遍历所有已注册好的设备,也执行上述匹配逻辑。

驱动探测 probe 函数与绑定:

        匹配成功后,内核调用驱动的 probe 函数,完成设备初始化。

static int eeprom_probe(struct i2c_client *client, const struct i2c_device_id *id) {struct eeprom_device *eeprom = devm_kzalloc(&client->dev, sizeof(*eeprom), GFP_KERNEL);eeprom->client = client;// 读取设备数据、创建文件系统节点等return 0;
}

struct i2c_client i2c_device 的封装,包含设备地址、适配器、名称等信息。

驱动通过 i2c_transfer 等函数与设备通信。

http://www.xdnf.cn/news/7666.html

相关文章:

  • leetcode刷题日记——从前序与中序遍历序列构造二叉树
  • MES管理系统电子看板驱动企业智能制造
  • python Numpy-数组
  • 探索nsupdate:动态DNS更新的终极指南
  • 码钉枪行业2025数据分析报告
  • Java程序员从0学AI(二)
  • 使用F5-tts复刻音色
  • ArrayList源码分析
  • 实现商品列表
  • 建站系统哪个好?
  • 基于CATIA参数化圆锥建模的自动化插件开发实践——NX建模之圆锥体命令的参考与移植(二)
  • 笔记:显示实现接口如何实现,作用是什么
  • ollama部署模型
  • 工单派单应用:5 大核心功能提升协作效率
  • Ai学习之LangChain框架
  • STM32外设应用详解——从基础到高级应用的全面指南
  • 差分数组:原理与应用
  • 文献分享-临床预测模型-基于围手术期时间数据肝切除术后肝衰竭早期检测
  • CSS 背景全解析:从基础属性到视觉魔法
  • MinIO集群故障,其中一块driver-4异常
  • 网络安全之带正常数字签名的后门样本分析
  • 软件测试之环境搭建及测试流程
  • 见多识广10:大模型的一些基础概念
  • Python训练营打卡——DAY31(2025.5.20)
  • 类和对象------2
  • Leetcode百题斩-字典树
  • MySQL 安全更新大量数据
  • MySQL高可用之ProxySQL + MGR 实现读写分离实战
  • 面向AI研究的模块化即插即用架构综述与资源整理全覆盖
  • 数据库实验——备份与恢复