1. 理解scull字符驱动的基本架构
scull(Simple Character Utility for Loading Localities)是LDD3(Linux Device Drivers 3rd Edition)中经典的字符设备驱动示例。这个虚拟驱动不依赖任何硬件设备,而是通过内核内存模拟字符设备的行为。它的设计初衷是帮助开发者理解Linux设备驱动的核心机制,包括:
- 字符设备注册与注销
- 文件操作接口实现
- 内存管理技巧
- 并发控制方法
在scull的实现中,最核心的数据结构是scull_dev,它包含了设备的状态信息:
c复制struct scull_dev {
struct scull_qset *data; // 指向第一个量子集的指针
int quantum; // 当前量子大小
int qset; // 当前量子集大小
unsigned long size; // 存储在此设备中的数据量
unsigned int access_key; // 由sculluid和scullpriv使用
struct semaphore sem; // 互斥信号量
struct cdev cdev; // 字符设备结构
};
这个结构体贯穿了整个驱动的实现,理解它的每个字段作用是掌握scull的关键。比如quantum和qset决定了内存分配的策略,而sem则用于处理并发访问。
提示:虽然scull是虚拟设备,但它的内存管理策略模拟了真实设备可能遇到的情况。量子(quantum)和量子集(qset)的概念特别值得关注,它们构成了scull独特的内存组织方式。
2. 设备注册与初始化的实现细节
2.1 主次设备号分配
在Linux中,字符设备通过主设备号(major number)和次设备号(minor number)来标识。scull驱动提供了多种设备号分配方式:
- 静态分配:开发者手动指定主设备号
- 动态分配:请求内核自动分配可用的主设备号
推荐使用动态分配方式,这可以避免设备号冲突。相关代码通常出现在模块初始化函数中:
c复制if (scull_major) {
dev = MKDEV(scull_major, scull_minor);
result = register_chrdev_region(dev, scull_nr_devs, "scull");
} else {
result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs, "scull");
scull_major = MAJOR(dev);
}
2.2 cdev结构体初始化
Linux内核使用struct cdev来表示字符设备。scull驱动的初始化过程包括:
- 分配cdev结构(静态或动态)
- 初始化cdev结构
- 将cdev添加到系统
关键代码示例:
c复制cdev_init(&dev->cdev, &scull_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &scull_fops;
err = cdev_add(&dev->cdev, devno, 1);
scull_fops是文件操作结构体,包含了各种操作函数的指针,如open、release、read、write等。这是驱动与用户空间交互的主要接口。
2.3 内存初始化策略
scull驱动在初始化时并不立即分配内存,而是采用"懒分配"策略:
- 设备打开时
