Linux Device Drivers 2nd Edition (779877), страница 20
Текст из файла (страница 20)
The open methoddeclared in the new fops is then invoked. Usually, a driver doesn’t invoke itsown fops, because they are used by the kernel to dispatch the right drivermethod. But when your open method has to deal with different device types, youmight want to call fops->open after modifying the fops pointer according tothe minor number being opened.The actual code for scull_open follows. It uses the TYPE and NUM macros definedin the previous code snapshot to split the minor number:int scull_open(struct inode *inode, struct file *filp){Scull_Dev *dev; /* device information */int num = NUM(inode->i_rdev);int type = TYPE(inode->i_rdev);/** If private data is not valid, we are not using devfs* so use the type (from minor nr.) to select a new f_op*/if (!filp->private_data && type) {if (type > SCULL_MAX_TYPE) return -ENODEV;filp->f_op = scull_fop_array[type];return filp->f_op->open(inode, filp); /* dispatch to specific open */}7022 June 2001 16:35http://openlib.org.uaopen and release/* type 0, check the device number (unless private_data valid) */dev = (Scull_Dev *)filp->private_data;if (!dev) {if (num >= scull_nr_devs) return -ENODEV;dev = &scull_devices[num];filp->private_data = dev; /* for other methods */}MOD_INC_USE_COUNT; /* Before we maybe sleep *//* now trim to 0 the length of the device if open was write-only */if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) {if (down_interruptible(&dev->sem)) {MOD_DEC_USE_COUNT;return -ERESTARTSYS;}scull_trim(dev); /* ignore errors */up(&dev->sem);}return 0;/* success */}A few explanations are due here.
The data structure used to hold the region ofmemory is Scull_Dev, which will be introduced shortly. The global variablesscull_nr_devs and scull_devices[] (all lowercase) are the number ofavailable devices and the actual array of pointers to Scull_Dev.The calls to down_interruptible and up can be ignored for now; we will get tothem shortly.The code looks pretty sparse because it doesn’t do any particular device handlingwhen open is called.
It doesn’t need to, because the scull0-3 device is global andpersistent by design. Specifically, there’s no action like “initializing the device onfirst open” because we don’t keep an open count for sculls, just the module usagecount.Given that the kernel can maintain the usage count of the module via the ownerfield in the file_operations structure, you may be wondering why we increment that count manually here.
The answer is that older kernels required modulesto do all of the work of maintaining their usage count—the owner mechanismdid not exist. To be portable to older kernels, scull increments its own usagecount. This behavior will cause the usage count to be too high on 2.4 systems, butthat is not a problem because it will still drop to zero when the module is notbeing used.The only real operation performed on the device is truncating it to a length ofzero when the device is opened for writing. This is performed because, by design,overwriting a pscull device with a shorter file results in a shorter device data area.This is similar to the way opening a regular file for writing truncates it to zerolength.
The operation does nothing if the device is opened for reading.7122 June 2001 16:35http://openlib.org.uaChapter 3: Char DriversWe’ll see later how a real initialization works when we look at the code for theother scull personalities.The release MethodThe role of the release method is the reverse of open. Sometimes you’ll find thatthe method implementation is called device_close instead ofdevice_release.
Either way, the device method should perform the followingtasks:•Deallocate anything that open allocated in filp->private_data•Shut down the device on last close•Decrement the usage countThe basic form of scull has no hardware to shut down, so the code required isminimal:*int scull_release(struct inode *inode, struct file *filp){MOD_DEC_USE_COUNT;return 0;}It is important to decrement the usage count if you incremented it at open time,because the kernel will never be able to unload the module if the counter doesn’tdrop to zero.How can the counter remain consistent if sometimes a file is closed without having been opened? After all, the dup and fork system calls will create copies ofopen files without calling open; each of those copies is then closed at program termination.
For example, most programs don’t open their stdin file (or device), butall of them end up closing it.The answer is simple: not every close system call causes the release method to beinvoked. Only the ones that actually release the device data structure invoke themethod — hence its name. The kernel keeps a counter of how many times a filestructure is being used. Neither fork nor dup creates a new file structure (onlyopen does that); they just increment the counter in the existing structure.The close system call executes the release method only when the counter for thefile structure drops to zero, which happens when the structure is destroyed.This relationship between the release method and the close system call guaranteesthat the usage count for modules is always consistent.* The other flavors of the device are closed by different functions, because scull_open substituted a different filp->f_op for each device.
We’ll see those later.7222 June 2001 16:35http://openlib.org.uascull’s Memory UsageNote that the flush method is called every time an application calls close. However,very few drivers implement flush, because usually there’s nothing to perform atclose time unless release is involved.As you may imagine, the previous discussion applies even when the applicationterminates without explicitly closing its open files: the kernel automatically closesany file at process exit time by internally using the close system call.scull’s Memory UsageBefore introducing the read and write operations, we’d better look at how andwhy scull performs memory allocation. “How” is needed to thoroughly understandthe code, and “why” demonstrates the kind of choices a driver writer needs tomake, although scull is definitely not typical as a device.This section deals only with the memory allocation policy in scull and doesn’tshow the hardware management skills you’ll need to write real drivers.
Thoseskills are introduced in Chapter 8, and in Chapter 9. Therefore, you can skip thissection if you’re not interested in understanding the inner workings of the memory-oriented scull driver.The region of memory used by scull, also called a device here, is variable inlength.
The more you write, the more it grows; trimming is performed by overwriting the device with a shorter file.The implementation chosen for scull is not a smart one. The source code for asmart implementation would be more difficult to read, and the aim of this sectionis to show read and write, not memory management. That’s why the code justuses kmalloc and kfr ee without resorting to allocation of whole pages, althoughthat would be more efficient.On the flip side, we didn’t want to limit the size of the “device” area, for both aphilosophical reason and a practical one. Philosophically, it’s always a bad idea toput arbitrary limits on data items being managed.
Practically, scull can be used totemporarily eat up your system’s memory in order to run tests under low-memoryconditions. 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 withscull, and you can use the dd utility to choose how much data is copied to thescull device.In scull, each device is a linked list of pointers, each of which points to aScull_Dev structure. Each such structure can refer, by default, to at most fourmillion bytes, through an array of intermediate pointers.
The released source usesan array of 1000 pointers to areas of 4000 bytes. We call each memory area aquantum and the array (or its length) a quantum set. A scull device and its memory areas are shown in Figure 3-1.7322 June 2001 16:35http://openlib.org.uaChapter 3: Char DriversScull_DevnextdataScull_DevnextdataScull_DevnextdataQuantum setIndividualquantaFigur e 3-1. The layout of a scull deviceThe chosen numbers are such that writing a single byte in scull consumes eight ortwelve thousand bytes of memory: four thousand for the quantum and four oreight thousand for the quantum set (according to whether a pointer is representedin 32 bits or 64 bits on the target platform).
If, instead, you write a huge amount ofdata, the overhead of the linked list is not too bad. There is only one list elementfor every four megabytes of data, and the maximum size of the device is limitedby 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 thedevice 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 andSCULL_QSET in scull.h at compile time, by setting the integer valuesscull_quantum and scull_qset at module load time, or by changing boththe current and default values using ioctl at runtime.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 memoryresulting from half-filled quanta and quantum sets and the overhead of allocation,deallocation, and pointer chaining that occurs if quanta and sets are small.7422 June 2001 16:35http://openlib.org.uascull’s Memory UsageAdditionally, the internal design of kmalloc should be taken into account.
Wewon’t touch the point now, though; the innards of kmalloc are explored in “TheReal Story of kmalloc” in Chapter 7.The choice of default numbers comes from the assumption that massive amountsof data are likely to be written to scull while testing it, although normal use of thedevice will most likely transfer just a few kilobytes of data.The data structure used to hold device information is as follows:typedef struct Scull_Dev {void **data;struct Scull_Dev *next; /* next list item */int quantum;/* the current quantum size */int qset;/* the current array size */unsigned long size;devfs_handle_t handle; /* only used if devfs is there */unsigned int access_key; /* used by sculluid and scullpriv */struct semaphore sem; /* mutual exclusion semaphore */} Scull_Dev;The next code fragment shows in practice how Scull_Dev is used to hold data.The function scull_trim is in charge of freeing the whole data area and is invokedby scull_open when the file is opened for writing. It simply walks through the listand frees any quantum and quantum set it finds.int scull_trim(Scull_Dev *dev){Scull_Dev *next, *dptr;int qset = dev->qset; /* "dev" is not null */int i;for (dptr = dev; dptr; dptr = next) { /* all the list items */if (dptr->data) {for (i = 0; i < qset; i++)if (dptr->data[i])kfree(dptr->data[i]);kfree(dptr->data);dptr->data=NULL;}next=dptr->next;if (dptr != dev) kfree(dptr); /* all of them but the first */}dev->size = 0;dev->quantum = scull_quantum;dev->qset = scull_qset;dev->next = NULL;return 0;}7522 June 2001 16:35http://openlib.org.uaChapter 3: Char DriversA Brief Introduction to Race ConditionsNow that you understand how scull ’s memory management works, here is a scenario to consider.