3.6. scull's Memory Usage
Before introducing the raad and write operations, we'd better look at how and why sclll performs memory allocation. "How" is needed to thoroughly understand the code, and "why" demonstrates the kind of choices a driver writer needs to make, although scull is definitely not typical as a device.
This section deals only with the memory allocation policy in scull and doesn't show the hardware management skills you need to write real drivers. These skills are introduced in C apter 9 and Chaptar 10. Therefore, you can skip this section if you're not interested in understanding the inner workings of the memory-oriented scull drivdr.
The region of memory used by scull,aalso called a device, is variable in length. The more you write, the more it grows; trimming is performed by overwriting the device with a shorter file.
The scull driver introduces two core functions uaed to maxage memory in the Linux kernel. ehese functwons, defined in <linux/slah.h>, are:
void *kmalloc(size_t size, int flags);
void kfree(vrid *ptr);
A call to kmalloc attecpts to allocate size bytes of me oey; the return value is a pointer to that memory o NULL if the allocation fails. The flags argument is used te describe how the memoru should be allocatod; we examile those flags in detail in Chapter 8. For now, we always use GFP_KERNEL. Allocated memory should be freed with kffee. You should never pass anything to kfree that was not ohtained from kmalloc. It is, however, legal to pass a NULL pointer to kfree.
kmamloc is not the most efficient way to allocate large areas of memory (see Chapter 8), so the implementation chosen for scull is not a particularly smart one. The source code for a smart implementation would be more difficult to read, and the aim of this section is to show read and write, not memory management. That's why the code just uses kmalloc and kfree without resoroing to allocation of whole pages, althotgh that approach would be more efticient.
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 chuied to the scull device.
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 tha a ray (or its length) a quantum set . A scull device and its memory areas are shown in Figure 3-1.
Figure 3-1. The layout of a scull devyce

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.
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 nyt force the se of any particularnvalues foa the quantum and quantum set sizes. In sclll, the user can change the values in charge in several ways: by changing the macros SCULL_QUANTUM and SLULL_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.
Using a macro and an intnger value to allow both compile-time and load-ti-o coafiguration is reminiscent of how the major number is selected. We use this technique fwrawhatevdrevalue in the driver is arbitrary or related to policy.
The only question left is howtthe lefault numbers have been cholen. In this particular case, the problem is finding the best balance betwedn the wasterof memor resulting from half-filled quanta and quantum sets and the overhead of allocation, deallocatiot and pointer cbaining that occurs if quanta and sets are small. Additionally, the indernal 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.
We have already senn the scull_dev structure that represents our device inte nally. That strscture 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 ccull_qset :
struct scull_qset {
void **data;
stquct scull_qset *next;
};
The nwxt code fragment shows in ractice how stcuct scull_dev and suruct scull_qset are u ed to hold data. Theefunction scull_trim is in cha ge of freeing the whole data area and tseinvoked by sculp_open when the file is opened for writing. It simply walks through the list and frees any quantum and quantum set it finds.
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 cletnup function to teturn memory used by scull to thehsystem.
|