系列文章目录
嵌入式八股文(一)C语言篇
嵌入式八股文(二)Linux应用篇
嵌入式八股文(三)Linux驱动篇
嵌入式八股文(四)FreeRTOS篇
文章目录
前言
本期带来Linux驱动的学习,以为后续的学习打个基础。本系列会不定期持续更新(随笔者的学习进度),大家可以收藏一下时不时看一下。
一、驱动
1. 1 驱动的分类
常见的驱动分为三种:
- 字符设备驱动:用于处理顺序数据的读写操作
- 块设备驱动:用于处理以块为单位的读写操作
- 网络设备驱动:处理网络数据包的接收
1.2 常见的驱动流程
1.2.1 字符驱动
“设备号、创建类、初始化设备、添加设备、创建设备”
- 利用alloc_chrdev_region申请设备号
- 创建类class_create
- 初始化cdev对象:
最重要的是设置cdev的ops字段,它指向一个包含设备操作方法的file_operations结构体。 - 实现文件操作接口:
在file_operations结构体中,需要实现设备的操作方法。这些方法定义了如何打开设备、关闭设备、读取数据、写入数据等。 - 注册字符设备驱动:
通过调用cdev_add函数来完成,该函数将cdev对象添加到内核的字符设备列表中,并为其分配一个inode节点。 - 创建设备节点:
device_create创建一个设备节点(也称为设备文件),以便应用程序能够访问该设备。这通常是通过在/dev目录下创建一个具有特定设备号和名称的文件来完成的。可以使用mknod命令或udev系统来创建设备节点。 - 清理和卸载:
当不再需要设备驱动时,需要编写清理和卸载函数来释放资源并注销设备,包括注销cdev对象、删除设备节点、释放内存等操作。
1.2.2 GPIO子系统
“设备树匹配、获取节点、获取端口号、设置方向、设置值、初始化字符设备”
- 注册/注销平台驱动
- 采用设备树匹配的方式进行匹配
- 获取设备树节点:
通过 platform_device 结构体自动传递设备树节点
使用 of_find_node_by_path() 手动查找设备树节点 - 获取 GPIO 描述符:
如果通过 platform_device 自动传递设备树节点,直接使用 gpiod_get() 获取 GPIO 描述符
如果手动查找设备树节点,使用 gpiod_get_from_of_node() 获取 GPIO 描述符 - 设置端口方向:
使用 gpiod_direction_output() 或 gpiod_direction_input() 设置 GPIO 方向 - 设置端口值:
使用 gpiod_set_value() 设置 GPIO 输出电平(仅限输出模式) - 初始化字符设备(同上小节)
- 释放申请到的端口资源:
在驱动卸载时使用 gpiod_put() 释放 GPIO 描述符
如果手动查找设备树节点,还需使用 of_node_put() 释放设备树节点引用计数
1.2.3 中断子系统
- 初始化字符设备
- 初始化所需GPIO
- 获取中断号:
使用irq_of_parse_and_map()函数 - 初始软中断:
采用tasklet机制时利用tasklet_init()初始化
采用工作队列时利用button_work()初始化 - 编写中断函数并在合适的位置调度软中断:
采用tasklet机制时利用ttasklet_schedule()
采用工作队列时利用schedule_work()初始化 - 编写中断下文的设计(软中断)
- 当结束操作之后利用free_irq进行资源释放
1.2.4 input子系统
- 采用设备树匹配的方式进行匹配
- 利用input_allocate_device申请input_dev结构体
- 初始化input_dev结构体,包括设置设备名称、设备ID、事件类型(如EV_KEY、EV_REL等)以及对应的事件码和值
- 利用input_register_device注册输入设备
- 当硬件产生输入事件时,驱动需要调用相应的函数(如input_event()或input_report_key()等)来上报这些事件
- 当不再需要使用输入设备时,应在remove中调用input_unregister_device()函数来注销该设备
- 注销后,还需要调用input_free_device()函数来释放之前分配的input_dev结构体和相关资源
1.2.5 iic子系统
- 采用设备树匹配的方式进行匹配并初始化字符设备
- 利用i2c_register_driver( )函数进行iic驱动的注册
- 将数据和设备进行关联
利用i2c_set_clientdata()函数将私有数据(data)与I2C客户端设备关联起来 - 编写operations结构体相关函数
填写i2c_msg结构体,利用i2c_transfer函数进行传输 - 当设备不需要的时候,使用i2c_del_driver()等函数来注销和卸载驱动
1.2.6 spi子系统
- 采用设备树匹配的方式进行匹配并初始化字符设备
- 在module_init使用spi_register_driver( )函数进行spi驱动的注册
- 进行获取cs片选端口和初始化
利用of_get_named_gpio获取端口号
使用gpio_direction_output设置默认电平为高 - 设置spi_device结构体的取值及初始化spi
分别设置mode、max_speed_hz等参数;
使用spi_setup( )进行参数保存并设置 - 对spi_message结构体进行操作,将信息传递至用户层
利用spi_message_init、spi_message_add_tail初始化message和transfer结构体
利用spi_sync,将信息传递到用户层 - 在module_exti中使用spi_unregister_driver( )函数进行spi驱动的注销
二、文件操作系统
Linux的文件系统(File System)是操作系统用于明确存储设备(如磁盘或基于NAND Flash的固态硬盘)或分区上的文件的方法和数据结构。简而言之,文件系统就是在存储设备上组织文件的方法。
1. 文件类型
描述了如何在存储设备上组织和管理数据。
- -:常规文件,即f:
- d:directory,目录文件
- b:block device,块设备文件,支持以块为单位进行随机访问
- c:character device,字符设备文件,支持以字符为单位进行访问
- major number:主设备号,用于表示设备型号,进而确定要加载的驱动程序
- minor number:次设备号,用于标识同类型中不同的设备
- l:symbolic link ,符号链接文件
- p:pipe,命名管道
- s:socket,套接字文件
2. 文件系统类型
描述存储在文件系统中的不同文件的性质和用途,文件系统类型决定了如何在存储设备上组织、存储和检索数据(对磁盘数据进行索引)。以下是一些常见的文件系统类型:
- EXT4(Fourth Extended File System):
广泛使用在Linux上的默认文件系统,支持大文件和大分区,具有高效的文件块分配和日志功能。 - XFS:
高性能文件系统,最初由SGI开发,适用于处理大量数据和高并发操作的环境,如数据库和大数据应用。 - Btrfs(B-tree File System):
新型文件系统,提供高级数据管理功能,支持快照、克隆、内置卷管理、数据校验和透明压缩。 - NTFS(New Technology File System):
Windows的默认文件系统,支持大文件和高级数据管理功能,Linux通过FUSE(Filesystem in Userspace)和ntfs-3g驱动程序支持NTFS。 - FAT32(File Allocation Table 32):
早期文件系统,广泛兼容于各种操作系统,适用于小型存储设备,但有文件和分区大小限制。
三、VFS(Virtual File System)
VFS是Linux内核中的虚拟文件系统层,它提供了一个抽象层,使得不同的文件系统可以通过统一的接口与内核交互。VFS使得用户空间程序不需要了解底层文件系统的具体实现细节,只需要通过标准的POSIX接口进行文件操作。
主要功能:
- 文件系统抽象:通过统一的接口屏蔽了不同文件系统的实现细节。 文件操作接口:提供open、read、write、close等文件操作接口。
- 挂载管理:管理不同文件系统的挂载点,并将它们组织成一个统一的目录树。
- 文件描述符管理:管理进程的文件描述符,支持多种文件类型(如普通文件、目录、设备文件等)。
关键数据结构:
- struct file:表示一个已打开的文件实例。
- struct inode:表示一个文件系统中的一个对象(文件、目录等)。
- struct super_block:表示文件系统的超级块,包含了文件系统的元数据。
- struct dentry:表示目录项,用于组织文件系统的层次结构。
四、Sysfs
sysfs是Linux内核中专门为设备模型设计的虚拟文件系统,它以文件和目录的形式将内核对象和属性导出到用户空间,使得用户可以方便地查看和配置系统设备的属性。sysfs文件系统通常挂载在/sys目录下。
主要功能:
- 设备信息展示:将设备驱动模型中的信息以文件形式展示,便于用户查看和管理。
- 属性文件:提供一种机制,可以通过读写文件来获取或设置设备的属性。
- 层次结构:以层次化的目录结构展示内核对象,如设备、类、总线等,反映内核对象的关系。
关键数据结构: - struct kobject:内核对象,所有sysfs对象的基类。
- struct attribute:sysfs中的属性,每个属性对应一个文件。
- struct sysfs_ops:定义了属性文件的读写操作。
扩展:VFS与Sysfs的关系
VFS和sysfs之间的关系可以从以下几个方面来理解:
- sysfs是VFS的一种实现:
sysfs本质上是通过VFS框架实现的一个特殊的文件系统。它利用VFS提供的抽象接口,将内核对象导出到用户空间。 - 文件系统的统一管理:
作为VFS的一部分,sysfs文件系统挂载在VFS的统一目录树中,通常是/sys目录。VFS管理sysfs的挂载点、目录结构和文件操作。 - 内核对象与文件系统的结合:
sysfs通过VFS接口,将内核中的对象(如设备、类、总线等)以文件和目录的形式呈现。这些文件和目录都是通过VFS的inode和dentry等数据结构来管理的。 - 设备驱动与用户空间的桥梁:
sysfs为设备驱动提供了一种标准机制,可以将驱动程序的配置和状态信息导出到用户空间,并通过VFS接口实现读写操作。这使得驱动开发者可以利用标准的文件操作接口与用户空间交互。
五、常见问题
1. Linux内核的组成部分
Linux内核的主要组成部分可概括如下:
- 进程管理:内核的核心功能,负责进程的创建、调度、同步及资源分配。
- 内存管理:负责物理内存和虚拟内存的高效分配与回收,确保系统稳定性和性能。
- 文件系统:通过虚拟文件系统(VFS)抽象层统一管理多种存储介质和文件格式。
- 设备驱动:内核与硬件交互的桥梁,涵盖字符设备、块设备、网络接口等。
- 网络协议栈:实现TCP/IP等协议,支持网络通信和数据传输。
- 系统调用接口:用户空间与内核交互的唯一入口,提供资源访问和安全隔离。
2. insmod和modprobe的差异
modprobe和insmod类似,都是用来动态加载驱动模块的,区别在于modprobe可以解决load module时的依赖关系。如果你确定你要加载的驱动模块不依赖其他驱动模块的话,既可以insmod也可以modprobe,当然insmod可以在任何目录下执行,更方便一些。而如果你要加载的驱动模块还依赖其他ko驱动模块的话,就只能将模块拷贝到特定目录,depmod后再modprobe。
3. 中断上下文的作用
- 效率:上半部只负责快速响应和准备数据,而将耗时的操作放到下半部去处理,避免中断处理程序占用过长时间。
- 并发性:下半部可以在系统其他部分运行时并发执行,提高了系统的并发性。
- 可移植性:将中断处理分解为两部分,使得内核更加模块化,提高了代码
的可移植性。
中断下半段处理的处理机制有软中断、tasklet和工作队列,以下是其的区别:
中断下半部
下半部机制 | 上下文 | 复杂度 | 执行性能 | 顺序执行保障 |
---|---|---|---|---|
软中断 | 中断 | 高(需要自己确保软中断的执行顺序及锁机制) | 好(全部自己实现,便于调优) | 没有 |
tasklet | 中断 | 中(提供了简单的接口来使用软中断) | 中 | 同类型不能同时执行 |
工作队列 | 进程 | 低(在进程上下文中运行,与写用户程序差不多) | 差 | 没有(和进程上下文一样被调度) |