Linux Device Drivers 2nd Edition (779877), страница 24
Текст из файла (страница 24)
Instead of writing a specific sample driver to try outdevfs, we added devfs support to the scull driver. If you load scull to a kernel thatuses devfs, you’ll need to directly invoke insmod instead of running the scull_loadscript.We chose to create a directory to host all scull special files because the structure ofdevfs is highly hierarchical and there’s no reason not to adhere to this convention.Moreover, we can thus show how a directory is created and removed.Within scull_init, the following code deals with device creation, using a fieldwithin the device structure (called handle) to keep track of what devices havebeen registered:/* If we have devfs, create /dev/scull to put files in there */scull_devfs_dir = devfs_mk_dir(NULL, "scull", NULL);if (!scull_devfs_dir) return -EBUSY; /* problem */for (i=0; i < scull_nr_devs; i++) {sprintf(devname, "%i", i);devfs_register(scull_devfs_dir, devname,DEVFS_FL_AUTO_DEVNUM,0, 0, S_IFCHR | S_IRUGO | S_IWUGO,&scull_fops,scull_devices+i);}The previous code is paired by the two lines that are part of the following excerptfrom scull_cleanup:if (scull_devices) {for (i=0; i<scull_nr_devs; i++) {scull_trim(scull_devices+i);/* the following line is only used for devfs */devfs_unregister(scull_devices[i].handle);}kfree(scull_devices);}/* once again, only for devfs */devfs_unregister(scull_devfs_dir);8822 June 2001 16:35http://openlib.org.uaThe Device FilesystemPart of the previous code fragments is protected by #ifdef CONFIG_DEVFS_FS.If the feature is not enabled in the current kernel, scull will revert to register_chrdev.The only extra task that needs to be performed in order to support both environments is dealing with initialization of filp->f_ops and filp->private_datain the open device method.
The former pointer is simply not modified, since theright file operations have been specified in devfs_r egister. The latter will only needto be initialized by the open method if it is NULL, since it will only be NULL ifdevfs is not being used./** 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 */}/* 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 */}Once equipped with the code shown, the scull module can be loaded to a systemrunning devfs.
It will show the following lines as output of ls -l /dev/scull:crw-rw-rwcrw-rw-rwcrw-rw-rwcrw-rw-rwcrw-rw-rwcrw-rw-rwcrw-rw-rwcrw-rw-rwcrw-rw-rwcrw-rw-rwcrw-rw-rwcrw-rw-rw-111111111111rootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootroot144, 1 Jan 1 1970144, 2 Jan 1 1970144, 3 Jan 1 1970144, 4 Jan 1 1970144, 5 Jan 1 1970144, 6 Jan 1 1970144, 7 Jan 1 1970144, 8 Jan 1 1970144, 12 Jan 1 1970144, 9 Jan 1 1970144, 10 Jan 1 1970144, 11 Jan 1 19700123pipe0pipe1pipe2pipe3privsingleuserwuserThe functionality of the various files is the same as that of the “normal” scull module, the only difference being in device pathnames: what used to be /dev/scull0 isnow /dev/scull/0.8922 June 2001 16:35http://openlib.org.uaChapter 3: Char DriversPortability Issues and devfsThe source files of scull are somewhat complicated by the need to be able to compile and run well with Linux versions 2.0, 2.2, and 2.4.
This portability requirementbrings in several instances of conditional compilation based on CONFIG_DEVFS_FS.Fortunately, most developers agree that #ifdef constructs are basically bad whenthey appear in the body of function definitions (as opposed to being used inheader files). Therefore, the addition of devfs brings in the needed machinery tocompletely avoid #ifdef in your code.
We still have conditional compilation inscull because older versions of the kernel headers can’t offer support for that.If your code is meant to only be used with version 2.4 of the kernel, you canavoid conditional compilation by calling kernel functions to initialize the driver inboth ways; things are arranged so that one of the initializations will do nothing atall, while returning success.
The following is an example of what initializationmight look like:#include <devfs_fs_kernel.h>int init_module(){/* request a major: does nothing if devfs is used */result = devfs_register_chrdev(major, "name", &fops);if (result < 0) return result;/* register using devfs: does nothing if not in use */devfs_register(NULL, "name", /* .... */ );return 0;}You can resort to similar tricks in your own header files, as long as you are carefulnot to redefine functions that are already defined by kernel headers. Removingconditional compilation is a good thing because it improves readability of the codeand reduces the amount of possible bugs by letting the compiler parse the wholeinput file.
Whenever conditional compilation is used, there is the risk of introducing typos or other errors that can slip through unnoticed if they happen in a placethat is discarded by the C preprocessor because of #ifdef.This is, for example, how scull.h avoids conditional compilation in the cleanuppart of the program. This code is portable to all kernel versions because it doesn’tdepend on devfs being known to the header files:#ifdef CONFIG_DEVFS_FS /* only if enabled, to avoid errors in 2.0 */#include <linux/devfs_fs_kernel.h>#elsetypedef void * devfs_handle_t; /* avoid #ifdef inside the structure */#endif9022 June 2001 16:35http://openlib.org.uaBackward CompatibilityNothing is defined in sysdep.h because it is very hard to implement this kind ofhack generically enough to be of general use. Each driver should arrange for itsown needs to avoid excessive #ifdef statements in function code.
Also, wechose not to support devfs in the sample code for this book, with the exception ofscull. We hope this discussion is enough to help readers exploit devfs if they wantto; devfs support has been omitted from the rest of the sample files in order tokeep the code simple.Backward CompatibilityThis chapter, so far, has described the kernel programming interface for version2.4 of the Linux kernel. Unfortunately, this interface has changed significantly overthe course of kernel development.
These changes represent improvements in howthings are done, but, once again, they also pose a challenge for those who wish towrite drivers that are compatible across multiple versions of the kernel.Insofar as this chapter is concerned, there are few noticeable differences betweenversions 2.4 and 2.2. Version 2.2, however, changed many of the prototypes of thefile_operations methods from what 2.0 had; access to user space was greatlymodified (and simplified) as well.
The semaphore mechanism was not as welldeveloped in Linux 2.0. And, finally, the 2.1 development series introduced thedirectory entry (dentry) cache.Changes in the File Operations StructureA number of factors drove the changes in the file_operations methods. Thelongstanding 2 GB file-size limit caused problems even in the Linux 2.0 days. As aresult, the 2.1 development series started using the loff_t type, a 64-bit value, torepresent file positions and lengths.
Large file support was not completely integrated until version 2.4 of the kernel, but much of the groundwork was done earlier and had to be accommodated by driver writers.Another change introduced during 2.1 development was the addition of thef_pos pointer argument to the read and write methods. This change was made tosupport the POSIX pr ead and pwrite system calls, which explicitly set the file offset where data is to be read or written. Without these system calls, threaded programs can run into race conditions when moving around in files.Almost all methods in Linux 2.0 received an explicit inode pointer argument. The2.1 development series removed this parameter from several of the methods, sinceit was rarely needed. If you need the inode pointer, you can still retrieve it fromthe filp argument.The end result is that the prototypes of the commonly used file_operationsmethods looked like this in 2.0:9122 June 2001 16:35http://openlib.org.uaChapter 3: Char Driversint (*lseek) (struct inode *, struct file *, off_t, int);Note that this method is called lseek in Linux 2.0, instead of llseek.
The namechange was made to recognize that seeks could now happen with 64-bit offsetvalues.int (*read) (struct inode *, struct file *, char *, int);int (*write) (struct inode *, struct file *, const char *,int);As mentioned, these functions in Linux 2.0 had the inode pointer as an argument, and lacked the position argument.void (*release) (struct inode *, struct file *);In the 2.0 kernel, the release method could not fail, and thus returned void.There have been many other changes to the file_operations structure; wewill cover them in the following chapters as we get to them. Meanwhile, it isworth a moment to look at how portable code can be written that accounts for thechanges we have seen so far. The changes in these methods are large, and there isno simple, elegant way to cover them over.The way the sample code handles these changes is to define a set of small wrapper functions that “translate” from the old API to the new.
These wrappers areonly used when compiling under 2.0 headers, and must be substituted for the“real” device methods within the file_operations structure. This is the codeimplementing the wrappers for the scull driver:/** The following wrappers are meant to make things work with 2.0 kernels*/#ifdef LINUX_20int scull_lseek_20(struct inode *ino, struct file *f,off_t offset, int whence){return (int)scull_llseek(f, offset, whence);}int scull_read_20(struct inode *ino, struct file *f, char *buf,int count){return (int)scull_read(f, buf, count, &f->f_pos);}int scull_write_20(struct inode *ino, struct file *f, const char *b,int c){return (int)scull_write(f, b, c, &f->f_pos);}void scull_release_20(struct inode *ino, struct file *f){9222 June 2001 16:35http://openlib.org.uaBackward Compatibilityscull_release(ino, f);}/* Redefine "real" names to the 2.0 ones */#define scull_llseek scull_lseek_20#define scull_read scull_read_20#define scull_write scull_write_20#define scull_release scull_release_20#define llseek lseek#endif /* LINUX_20 */Redefining names in this manner can also account for structure members whosenames have changed over time (such as the change from lseek to llseek).Needless to say, this sort of redefinition of the names should be done with care;these lines should appear before the definition of the file_operations structure, but after any other use of those names.Two other incompatibilities are related to the file_operations structure.