In continuation of the previous text第三章:字符设备驱动-11:scull’s Memory Usage-1, let's GO ahead.
On the flip side, we didn’t want to limit the size of the “device” area, for both a philosophical reason and a practical one. Philosophically, it’s always a bad idea to put arbitrary limits on data items being managed. Practically, scull can be used to temporarily eat up your system’s memory in order to run tests under low-memory conditions. Running such tests might help you understand the system’s internals. You can use the command cp /dev/zero /dev/scull0 to eat all the real RAM with scull, and you can use the dd utility to choose how much data is copied to the scull device.
另一方面,我们不想限制 “设备” 区域的大小,这既有理论上的原因,也有实际应用的考虑。从理论上讲,对被管理的数据项设置任意限制总是不明智的;从实际应用来看,scull 可用于临时占用系统内存,以便在低内存条件下进行测试。这类测试有助于理解系统内部机制:你可以使用 cp /dev/zero /dev/scull0 命令让 scull 占用所有物理内存,也可以使用 dd 工具控制复制到 scull 设备的数据量。
In scull, each device is a linked list of pointers, each of which points to a scull_dev structure. Each such structure can refer, by default, to at most four million bytes, through an array of intermediate pointers. The released source uses an array of 1000 pointers to areas of 4000 bytes. We call each memory area a quantum and the array (or its length) a quantum set. A scull device and its memory areas are shown in Figure 3-1.
在 scull 中,每个设备是一个指针链表,每个指针指向一个 scull_dev 结构。默认情况下,每个 scull_dev 结构通过一个中间指针数组,最多可引用 400 万字节的数据。官方源码中使用了一个包含 1000 个指针的数组,每个指针指向 4000 字节的内存区域。我们将每个内存区域称为量子(quantum),将这个数组(或其长度)称为量子集(quantum set)。scull 设备及其内存区域的结构如图 3-1 所示(示意图)。

The chosen numbers are such that writing a single byte in scull consumes 8000 or 12,000 thousand bytes of memory: 4000 for the quantum and 4000 or 8000 for the quantum set (according to whether a pointer is represented in 32 bits or 64 bits on the target platform). If, instead, you write a huge amount of data, the overhead of the linked list is not too bad. There is only one list element for every four megabytes of data, and the maximum size of the device is limited by the computer’s memory size.
当前选择的参数会导致在 scull 中写入 1 字节数据就消耗 8000 或 12000 字节内存:4000 字节用于量子,4000 或 8000 字节用于量子集(具体取决于目标平台的指针是 32 位还是 64 位)。但如果写入大量数据,链表的开销就变得微不足道了 —— 每 4MB 数据仅需 1 个链表元素,且设备的最大大小仅受计算机内存限制。
Choosing the appropriate values for the quantum and the quantum set is a question of policy, rather than mechanism, and the optimal sizes depend on how the device is used. Thus, the scull driver should not force the use of any particular values for the quantum and quantum set sizes. In scull, the user can change the values in charge in several ways: by changing the macros SCULL_QUANTUM and SCULL_QSET in scull.h at compile time, by setting the integer values scull_quantum and scull_qset at module load time, or by changing both the current and default values using ioctl at runtime.
为量子和量子集选择合适的值属于策略问题,而非机制问题,其最优大小取决于设备的使用场景。因此,scull 驱动不应强制使用特定的量子或量子集大小,用户可通过以下 3 种方式修改这些值:
-
编译时配置:修改
scull.h中的宏SCULL_QUANTUM(默认量子大小)和SCULL_QSET(默认量子集大小); -
模块加载时配置:通过模块参数设置整数变量
scull_quantum和scull_qset(加载命令如insmod scull.ko scull_quantum=8192 scull_qset=500); -
运行时配置:通过
ioctl命令动态修改当前设备的量子 / 量子集大小,以及默认值(后续章节会介绍ioctl实现)。
Using a macro and an integer value to allow both compile-time and load-time configuration is reminiscent of how the major number is selected. We use this technique for whatever value in the driver is arbitrary or related to policy.
这种 “宏定义 + 整数变量” 的设计,允许编译时和加载时双重配置,与主设备号的选择方式类似。对于驱动中任意性的或与策略相关的参数,我们都可采用这种技术。
The only question left is how the default numbers have been chosen. In this particular case, the problem is finding the best balance between the waste of memory resulting from half-filled quanta and quantum sets and the overhead of allocation, deallocation, and pointer chaining that occurs if quanta and sets are small. Additionally, the internal design of kmalloc should be taken into account. (We won’t pursue the point now, though; the innards of kmalloc are explored in Chapter 8.) The choice of default numbers comes from the assumption that massive amounts of data are
likely to be written to scull while testing it, although normal use of the device will most likely transfer just a few kilobytes of data.
仅剩的问题是:默认参数是如何确定的?在这个场景下,核心是在 “半填充量子 / 量子集导致的内存浪费” 与 “量子 / 量子集过小时产生的分配、释放及指针链开销” 之间找到最佳平衡。此外,还需考虑 kmalloc 的内部设计(我们暂不深入探讨这一点,第 8 章会详细分析 kmalloc 的底层原理)。选择当前默认值的依据是:假设测试 scull 时可能会向其写入大量数据,而设备的常规使用则大概率仅传输几 KB 数据。
We have already seen the scull_dev structure that represents our device internally. That structure’s quantum and qset fields hold the device’s quantum and quantum set sizes, respectively. The actual data, however, is tracked by a different structure, which we call struct scull_qset:
我们之前已经见过 scull 在内部表示设备所使用的 scull_dev 结构,该结构中的 quantum 和 qset 字段分别存储设备的量子大小和量子集大小。但实际数据的跟踪由另一个结构负责,我们将其定义为 struct scull_qset:
struct scull_qset {
void **data;
struct scull_qset *next;
};
The next code fragment shows in practice how struct scull_dev and struct scull_qset are used to hold data. The function scull_trim is in charge of freeing the whole data area and is invoked by scull_open when the file is opened for writing. It simply walks through the list and frees any quantum and quantum set it finds.
下面的代码片段展示了 struct scull_dev 和 struct scull_qset 如何协同存储数据。scull_trim 函数负责释放整个数据区域,当文件以写方式打开时,scull_open 会调用它。该函数的逻辑很简单:遍历量子集链表,释放找到的所有量子和量子集。
int scull_trim(struct scull_dev *dev)
{
struct scull_qset *next, *dptr;
int qset = dev->qset;
/* "dev" is not-null */
int i;
for (dptr = dev->data; dptr; dptr = next) { /* all the list items */
if (dptr->data) {
for (i = 0; i < qset; i++)
kfree(dptr->data[i]);
kfree(dptr->data);
dptr->data = NULL;
}
next = dptr->next;
kfree(dptr);
}
dev->size = 0;
dev->quantum = scull_quantum;
dev->qset = scull_qset;
dev->data = NULL;
return 0;
}
scull_trim is also used in the module cleanup function to return memory used byscull to the system.
scull_trim 还会在模块清理函数中被调用,用于将 scull 占用的内存归还给系统(避免模块卸载时内存泄漏)。
补充说明:
-
struct scull_qset结构的核心作用该结构是 scull 三级内存模型(设备→量子集链表→量子数组→量子)的关键中间层:
-
next:将多个量子集串联成链表,实现设备内存的动态扩展(一个量子集存满后,新增节点继续存储)。 -
data:是一个二级指针,指向 “量子指针数组”—— 数组中每个元素是一个量子(kmalloc分配的内存块)的地址;
-
-
scull_trim的释放顺序逻辑函数严格按照 “从内到外” 的顺序释放内存,避免野指针和内存泄漏,若颠倒顺序(如先释放量子集节点),会导致内部量子和数组的地址丢失,引发内存泄漏。
-
最后释放量子集节点本身(
dptr); -
再释放存储量子指针的数组(
dptr->data); -
先释放量子集内部的每个量子(
dptr->data[i]);
-
-
重置设备状态的意义
释放内存后,函数将
dev->size设为 0(表示设备当前无数据),并恢复quantum和qset为默认值(scull_quantum/scull_qset),确保后续操作(如重新写入数据)从初始状态开始,避免旧状态干扰。 -
与
scull_open的联动场景当用户以写方式(
O_WRONLY)打开设备时,scull_open调用scull_trim清空旧数据 —— 这模拟了普通文件 “打开即清空” 的行为,保证新写入的数据从设备起始位置存储,符合用户直觉。
2661

被折叠的 条评论
为什么被折叠?



