Allocating and Freeing Device Numbers
One of the first things your driver will need to do when setting up a char device is to obtain one or more device numbers to work with. The necessary function for this task is register_chrdev_region, which is declared in
int register_chrdev_region(dev_t first, unsigned int count, char *name);
Here, first is the beginning device number of the range you would like to allocate. The minor number portion of first is often 0, but there is no requirement to that effect. count is the total number of contiguous device numbers you are requesting. Note that, if count is large, the range you request could spill over to the next major number; but everything will still work properly as long as the number range you request is available. Finally, name is the name of the device that should be associated with this number range; it will appear in /proc/devices and sysfs. 在这里,first 是您要分配的范围的起始设备号。 first 的次要数字部分通常为 0,但对此没有要求。 count 是您请求的连续设备编号的总数。 请注意,如果 count 很大,您请求的范围可能会溢出到下一个主要数字; 但只要您请求的号码范围可用,一切都会正常工作。 最后,name 是应该与这个数字范围相关联的设备的名称; 它将出现在 /proc/devices 和 sysfs 中。
As with most kernel functions, the return value from register_chrdev_region will be 0 if the allocation was successfully performed. In case of error, a negative error code will be returned, and you will not have access to the requested region. 与大多数内核函数一样,如果分配成功执行,则 register_chrdev_region 的返回值将为 0。 如果出现错误,将返回负错误代码,您将无法访问请求的区域。
register_chrdev_region works well if you know ahead of time exactly which device numbers you want. Often, however, you will not know which major numbers your device will use; there is a constant effort within the Linux kernel development community to move over to the use of dynamicly-allocated device numbers. The kernel will happily allocate a major number for you on the fly, but you must request this allocation by using a different function: 如果您提前确切知道您想要哪些设备编号,则 register_chrdev_region 效果很好。 但是,您通常不知道您的设备将使用哪些主要号码。 Linux 内核开发社区一直在努力转向使用动态分配的设备编号。 内核会很高兴地为你分配一个主编号,但你必须使用不同的函数来请求这个分配:
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
With this function, dev is an output-only parameter that will, on successful completion, hold the first number in your allocated range. firstminor should be the requested first minor number to use; it is usually 0. The count and name parameters work like those given to request_chrdev_region. 使用此函数,dev 是一个仅输出参数,成功完成后,它将保存分配范围内的第一个数字。 firstminor 应该是请求使用的第一个次要号码; 它通常为 0。count 和 name 参数的工作方式与提供给 request_chrdev_region 的参数相同。
Regardless of how you allocate your device numbers, you should free them when they are no longer in use. Device numbers are freed with: 无论您如何分配设备编号,都应该在它们不再使用时释放它们。 设备编号通过以下方式释放:
void unregister_chrdev_region(dev_t first, unsigned int count);
The usual place to call unregister_chrdev_region would be in your module's cleanup function. 调用 unregister_chrdev_region 的通常位置是在模块的清理函数中。
The above functions allocate device numbers for your driver's use, but they do not tell the kernel anything about what you will actually do with those numbers. Before a user-space program can access one of those device numbers, your driver needs to connect them to its internal functions that implement the device's operations. We will describe how this connection is accomplished shortly, but there are a couple of necessary digressions to take care of first. 上述函数分配设备编号供驱动程序使用,但它们并没有告诉内核您将如何处理这些编号。 在用户空间程序可以访问这些设备号之一之前,您的驱动程序需要将它们连接到实现设备操作的内部函数。 我们将很快描述这种连接是如何实现的,但首先需要处理一些必要的题外话。
Dynamic Allocation of Major Numbers
Some major device numbers are statically assigned to the most common devices. A list of those devices can be found in Documentation/devices.txt within the kernel source tree. The chances of a static number having already been assigned for the use of your new driver are small, however, and new numbers are not being assigned. So, as a driver writer, you have a choice: you can simply pick a number that appears to be unused, or you can allocate major numbers in a dynamic manner. Picking a number may work as long as the only user of your driver is you; once your driver is more widely deployed, a randomly picked major number will lead to conflicts and trouble. 一些主要设备编号被静态分配给最常见的设备。 这些设备的列表可以在内核源代码树的 Documentation/devices.txt 中找到。 但是,已经为您的新驱动程序分配了静态编号的可能性很小,并且没有分配新编号。 因此,作为驱动程序编写者,您有一个选择:您可以简单地选择一个似乎未使用的编号,或者您可以以动态方式分配主要编号。 只要您的司机的唯一用户是您,选择一个号码就可以工作; 一旦您的驱动程序被更广泛地部署,随机选择的主要号码将导致冲突和麻烦。
Thus, for new drivers, we strongly suggest that you use dynamic allocation to obtain your major device number, rather than choosing a number randomly from the ones that are currently free. In other words, your drivers should almost certainly be using alloc_chrdev_region rather than register_chrdev_region. 因此,对于新的驱动,我们强烈建议您使用动态分配来获取您的主设备编号,而不是从当前空闲的编号中随机选择一个编号。 换句话说,您的驱动程序几乎肯定应该使用 alloc_chrdev_region 而不是 register_chrdev_region。
The disadvantage of dynamic assignment is that you can't create the device nodes in advance, because the major number assigned to your module will vary. For normal use of the driver, this is hardly a problem, because once the number has been assigned, you can read it from /proc/devices.[1] 动态分配的缺点是你不能提前创建设备节点,因为分配给你的模块的主设备号会有所不同。 对于驱动程序的正常使用,这几乎不是问题,因为一旦分配了编号,您就可以从 /proc/devices 中读取它。 [1]
To load a driver using a dynamic major number, therefore, the invocation of insmod can be replaced by a simple script that, after calling insmod, reads /proc/devices in order to create the special file(s). 因此,要使用动态主编号加载驱动程序,可以用一个简单的脚本替换 insmod 的调用,该脚本在调用 insmod 后读取 /proc/devices 以创建特殊文件。
A typical /proc/devices file looks like the following: 典型的 /proc/devices 文件如下所示
Character devices: 字符设备
1 mem
2 pty
3 ttyp
4 ttyS
6 lp
7 vcs
10 misc
13 input
14 sound
21 sg
180 usb
Block devices: 块设备
2 fd
8 sd
11 sr
65 sd
66 sd
The script to load a module that has been assigned a dynamic number can, therefore, be written using a tool such as awk to retrieve information from /proc/devices in order to create the files in /dev. 因此,可以使用诸如 awk 之类的工具来编写加载已分配动态编号的模块的脚本,以从 /proc/devices 检索信息,以便在 /dev 中创建文件。
The following script, scull_load, is part of the scull distribution. The user of a driver that is distributed in the form of a module can invoke such a script from the system's rc.local file or call it manually whenever the module is needed. 以下脚本 scull_load 是 scull 分发的一部分。 以模块形式分发的驱动程序的用户可以从系统的 rc.local 文件中调用这样的脚本,或者在需要模块时手动调用它。
#!/bin/sh
module="scull"
device="scull"
mode="664"
# invoke insmod with all arguments we got
# and use a pathname, as newer modutils don't look in . by default
/sbin/insmod ./$module.ko $* || exit 1
# remove stale nodes
rm -f /dev/${device}[0-3]
major=$(awk "\\$2= =\"$module\" {print \\$1}" /proc/devices)
mknod /dev/${device}0 c $major 0
mknod /dev/${device}1 c $major 1
mknod /dev/${device}2 c $major 2
mknod /dev/${device}3 c $major 3
# give appropriate group/permissions, and change the group.
# Not all distributions have staff, some have "wheel" instead.
group="staff"
grep -q '^staff:' /etc/group || group="wheel"
chgrp $group /dev/${device}[0-3]
chmod $mode /dev/${device}[0-3]
The script can be adapted for another driver by redefining the variables and adjusting the mknod lines. The script just shown creates four devices because four is the default in the scull sources. 该脚本可以通过重新定义变量和调整 mknod 行来适应另一个驱动程序。 刚刚显示的脚本创建了四个设备,因为四个是 scull 源中的默认值。
The last few lines of the script may seem obscure: why change the group and mode of a device? The reason is that the script must be run by the superuser, so newly created special files are owned by root. The permission bits default so that only root has write access, while anyone can get read access. Normally, a device node requires a different access policy, so in some way or another access rights must be changed. The default in our script is to give access to a group of users, but your needs may vary. In Section 6.6 in Chapter 3, the code for sculluid demonstrates how the driver can enforce its own kind of authorization for device access. 脚本的最后几行可能看起来晦涩难懂:为什么要更改设备的组和模式? 原因是脚本必须由超级用户运行,所以新创建的特殊文件归根用户所有。 权限位默认为只有 root 具有写访问权限,而任何人都可以获得读访问权限。 通常,设备节点需要不同的访问策略,因此必须以某种方式更改访问权限。 我们脚本中的默认设置是授予一组用户访问权限,但您的需求可能会有所不同。 在第 3 章的 6.6 节中,sculluid 的代码演示了驱动程序如何强制执行其自己的设备访问授权。
A scull_unload script is also available to clean up the /dev directory and remove the module. scull_unload 脚本也可用于清理 /dev 目录并删除模块。
As an alternative to using a pair of scripts for loading and unloading, you could write an init script, ready to be placed in the directory your distribution uses for these scripts.[2] As part of the scull source, we offer a fairly complete and configurable example of an init script, called scull.init; it accepts the conventional arguments—start, stop, and restart—and performs the role of both scull_load and scull_unload. 作为使用一对脚本进行加载和卸载的替代方法,您可以编写一个初始化脚本,准备好放置在您的发行版用于这些脚本的目录中。 [2] 作为 scull 源代码的一部分,我们提供了一个相当完整且可配置的 init 脚本示例,称为 scull.init; 它接受常规参数——开始、停止和重新启动——并执行 scull_load 和 scull_unload 的角色。
If repeatedly creating and destroying /dev nodes sounds like overkill, there is a useful workaround. If you are loading and unloading only a single driver, you can just use rmmod and insmod after the first time you create the special files with your script: dynamic numbers are not randomized,[3] and you can count on the same number being chosen each time if you don't load any other (dynamic) modules. Avoiding lengthy scripts is useful during development. But this trick, clearly, doesn't scale to more than one driver at a time. 如果重复创建和销毁 /dev 节点听起来有点矫枉过正,那么有一个有用的解决方法。 如果您只加载和卸载一个驱动程序,您可以在第一次使用脚本创建特殊文件后使用 rmmod 和 insmod:动态数字不是随机的,[3] 您可以指望选择相同的数字 每次如果您不加载任何其他(动态)模块。 避免冗长的脚本在开发过程中很有用。 但很明显,这个技巧一次只适用于一个以上的驱动程序。
The best way to assign major numbers, in our opinion, is by defaulting to dynamic allocation while leaving yourself the option of specifying the major number at load time, or even at compile time. The scull implementation works in this way; it uses a global variable, scull_major, to hold the chosen number (there is also a scull_minor for the minor number). The variable is initialized to SCULL_MAJOR, defined in scull.h. The default value of SCULL_MAJOR in the distributed source is 0, which means "use dynamic assignment." The user can accept the default or choose a particular major number, either by modifying the macro before compiling or by specifying a value for scull_major on the insmod command line. Finally, by using the scull_load script, the user can pass arguments to insmod on scull_load 's command line.[4] 在我们看来,分配主编号的最佳方式是默认为动态分配,同时让您自己选择在加载时甚至在编译时指定主编号。 scull 实现以这种方式工作; 它使用全局变量 scull_major 来保存选择的数字(还有一个 scull_minor 用于次要数字)。 该变量被初始化为 SCULL_MAJOR,在 scull.h 中定义。 分布式源中 SCULL_MAJOR 的默认值为 0,表示“使用动态分配”。 用户可以接受默认值或选择特定的主编号,方法是在编译前修改宏或在 insmod 命令行上为 scull_major 指定一个值。 最后,通过使用 scull_load 脚本,用户可以在 scull_load 的命令行上将参数传递给 insmod。 [4]
Here's the code we use in scull 's source to get a major number: 这是我们在 scull 的源代码中用于获取主编号的代码:
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);
}
if (result < 0) {
printk(KERN_WARNING "scull: can't get major %d\n", scull_major);
return result;
}
Almost all of the sample drivers used in this book use similar code for their major number assignment. 本书中使用的几乎所有示例驱动程序都使用类似的代码来分配主编号。
Some Important Data Structures
As you can imagine, device number registration is just the first of many tasks that driver code must carry out. We will soon look at other important driver components, but one other digression is needed first. Most of the fundamental driver operations involve three important kernel data structures, called file_operations, file, and inode. A basic familiarity with these structures is required to be able to do much of anything interesting, so we will now take a quick look at each of them before getting into the details of how to implement the fundamental driver operations. 可以想象,设备号注册只是驱动程序代码必须执行的许多任务中的第一个。 我们将很快研究其他重要的驱动程序组件,但首先需要另一个题外话。 大多数基本驱动程序操作涉及三个重要的内核数据结构,称为 file_operations、file 和 inode。 要能够做很多有趣的事情,需要对这些结构有基本的了解,所以我们现在将快速浏览它们中的每一个,然后再深入了解如何实现基本驱动程序操作的细节。
File Operations
So far, we have reserved some device numbers for our use, but we have not yet connected any of our driver's operations to those numbers. The file_operations structure is how a char driver sets up this connection. The structure, defined in
Conventionally, a file_operations structure or a pointer to one is called fops (or some variation thereof ). Each field in the structure must point to the function in the driver that implements a specific operation, or be left NULL for unsupported operations. The exact behavior of the kernel when a NULL pointer is specified is different for each function, as the list later in this section shows. 通常,file_operations 结构或指向其中的指针称为 fops(或其一些变体)。 结构中的每个字段都必须指向驱动程序中实现特定操作的函数,或者为不受支持的操作留空。 当指定 NULL 指针时,内核的确切行为对于每个函数都是不同的,如本节后面的列表所示。
The following list introduces all the operations that an application can invoke on a device. We've tried to keep the list brief so it can be used as a reference, merely summarizing each operation and the default kernel behavior when a NULL pointer is used. 以下列表介绍了应用程序可以在设备上调用的所有操作。 我们试图保持列表简短,以便将其用作参考,仅总结每个操作和使用 NULL 指针时的默认内核行为。
As you read through the list of file_operations methods, you will note that a number of parameters include the string _ _user. This annotation is a form of documentation, noting that a pointer is a user-space address that cannot be directly dereferenced. For normal compilation, _ _user has no effect, but it can be used by external checking software to find misuse of user-space addresses. 当您阅读 file_operations 方法列表时,您会注意到许多参数包括字符串 __user。 此注释是一种文档形式,指出指针是不能直接取消引用的用户空间地址。 对于正常编译,__user 没有任何作用,但是可以被外部检查软件用来发现用户空间地址的滥用。
The rest of the chapter, after describing some other important data structures, explains the role of the most important operations and offers hints, caveats, and real code examples. We defer discussion of the more complex operations to later chapters, because we aren't ready to dig into topics such as memory management, blocking operations, and asynchronous notification quite yet. 本章的其余部分,在描述了一些其他重要的数据结构之后,解释了最重要的操作的作用,并提供了提示、注意事项和真实的代码示例。 我们将更复杂的操作的讨论推迟到后面的章节,因为我们还没有准备好深入研究诸如内存管理、阻塞操作和异步通知等主题。
struct module *owner
The first file_operations field is not an operation at all; it is a pointer to the module that "owns" the structure. This field is used to prevent the module from being unloaded while its operations are in use. Almost all the time, it is simply initialized to THIS_MODULE, a macro defined in
loff_t (*llseek) (struct file *, loff_t, int);
The llseek method is used to change the current read/write position in a file, and the new position is returned as a (positive) return value. The loff_t parameter is a "long offset" and is at least 64 bits wide even on 32-bit platforms. Errors are signaled by a negative return value. If this function pointer is NULL, seek calls will modify the position counter in the file structure (described in Section 3.3.2) in potentially unpredictable ways. llseek 方法用于更改文件中当前的读/写位置,新位置作为(正)返回值返回。 loff_t 参数是一个“长偏移量”,即使在 32 位平台上也至少有 64 位宽。 错误由负返回值表示。 如果这个函数指针为 NULL,seek 调用将以潜在的不可预测的方式修改文件结构中的位置计数器(在第 3.3.2 节中描述)。
ssize_t (*read) (struct file *, char _ _user *, size_t, loff_t *);
Used to retrieve data from the device. A null pointer in this position causes the read system call to fail with -EINVAL ("Invalid argument"). A nonnegative return value represents the number of bytes successfully read (the return value is a "signed size" type, usually the native integer type for the target platform). 用于从设备检索数据。 此位置的空指针会导致读取系统调用失败并显示 -EINVAL(“无效参数”)。 非负返回值表示成功读取的字节数(返回值是“有符号大小”类型,通常是目标平台的本机整数类型)。
ssize_t (*aio_read)(struct kiocb *, char _ _user *, size_t, loff_t);
Initiates an asynchronous read—a read operation that might not complete before the function returns. If this method is NULL, all operations will be processed (synchronously) by read instead. 启动异步读取——在函数返回之前可能无法完成的读取操作。 如果此方法为 NULL,则所有操作都将通过读取(同步)处理。
ssize_t (*write) (struct file *, const char _ _user *, size_t, loff_t *);
Sends data to the device. If NULL, -EINVAL is returned to the program calling the write system call. The return value, if nonnegative, represents the number of bytes successfully written. 向设备发送数据。 如果为 NULL,则将 -EINVAL 返回给调用 write 系统调用的程序。 如果返回值非负,则表示成功写入的字节数。
ssize_t (*aio_write)(struct kiocb *, const char _ _user *, size_t, loff_t *);
Initiates an asynchronous write operation on the device. 在设备上启动异步写入操作。
int (*readdir) (struct file *, void *, filldir_t);
This field should be NULL for device files; it is used for reading directories and is useful only for filesystems. 对于设备文件,该字段应为 NULL; 它用于读取目录,仅对文件系统有用。
unsigned int (*poll) (struct file *, struct poll_table_struct *);
The poll method is the back end of three system calls: poll, epoll, and select, all of which are used to query whether a read or write to one or more file descriptors would block. The poll method should return a bit mask indicating whether non-blocking reads or writes are possible, and, possibly, provide the kernel with information that can be used to put the calling process to sleep until I/O becomes possible. If a driver leaves its poll method NULL, the device is assumed to be both readable and writable without blocking. poll 方法是三个系统调用的后端:poll、epoll 和 select,它们都用于查询对一个或多个文件描述符的读取或写入是否会阻塞。 poll 方法应该返回一个位掩码,指示是否可以进行非阻塞读取或写入,并且可能会向内核提供可用于使调用进程进入睡眠状态的信息,直到 I/O 成为可能。 如果驱动程序将其轮询方法保留为 NULL,则假定设备在没有阻塞的情况下既可读又可写。
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
The ioctl system call offers a way to issue device-specific commands (such as formatting a track of a floppy disk, which is neither reading nor writing). Additionally, a few ioctl commands are recognized by the kernel without referring to the fops table. If the device doesn't provide an ioctl method, the system call returns an error for any request that isn't predefined (-ENOTTY, "No such ioctl for device"). ioctl 系统调用提供了一种发出特定于设备的命令的方法(例如格式化软盘的磁道,既不读取也不写入)。 此外,内核可以识别一些 ioctl 命令,而无需参考 fops 表。 如果设备不提供 ioctl 方法,则系统调用会为任何未预定义的请求返回错误(-ENOTTY,“设备没有此类 ioctl”)。
int (*mmap) (struct file *, struct vm_area_struct *);
mmap is used to request a mapping of device memory to a process's address space. If this method is NULL, the mmap system call returns -ENODEV. mmap 用于请求设备内存到进程地址空间的映射。 如果此方法为 NULL,则 mmap 系统调用返回 -ENODEV。
int (*open) (struct inode *, struct file *);
Though this is always the first operation performed on the device file, the driver is not required to declare a corresponding method. If this entry is NULL, opening the device always succeeds, but your driver isn't notified. 虽然这始终是对设备文件执行的第一个操作,但驱动程序不需要声明相应的方法。 如果此条目为 NULL,则打开设备始终成功,但不会通知您的驱动程序。
int (*flush) (struct file *);
The flush operation is invoked when a process closes its copy of a file descriptor for a device; it should execute (and wait for) any outstanding operations on the device. This must not be confused with the fsync operation requested by user programs. Currently, flush is used in very few drivers; the SCSI tape driver uses it, for example, to ensure that all data written makes it to the tape before the device is closed. If flush is NULL, the kernel simply ignores the user application request. 当进程关闭设备的文件描述符副本时,会调用刷新操作; 它应该在设备上执行(并等待)任何未完成的操作。 这不能与用户程序请求的 fsync 操作相混淆。 目前,flush 用在很少的驱动程序中; 例如,SCSI 磁带驱动程序使用它来确保在关闭设备之前写入的所有数据都进入磁带。 如果 flush 为 NULL,内核将简单地忽略用户应用程序请求。
int (*release) (struct inode *, struct file *);
This operation is invoked when the file structure is being released. Like open, release can be NULL.[5] 释放文件结构时调用此操作。 与 open 一样,release 可以为 NULL。[5]
int (*fsync) (struct file *, struct dentry *, int);
This method is the back end of the fsync system call, which a user calls to flush any pending data. If this pointer is NULL, the system call returns -EINVAL. 这个方法是 fsync 系统调用的后端,用户调用它来刷新任何挂起的数据。 如果此指针为 NULL,则系统调用返回 -EINVAL。
int (*aio_fsync)(struct kiocb *, int);
This is the asynchronous version of the fsync method. 这是 fsync 方法的异步版本。
int (*fasync) (int, struct file *, int);
This operation is used to notify the device of a change in its FASYNC flag. Asynchronous notification is an advanced topic and is described in Chapter 6. The field can be NULL if the driver doesn't support asynchronous notification. 此操作用于通知设备其 FASYNC 标志的变化。 异步通知是一个高级主题,在第 6 章中进行了描述。如果驱动程序不支持异步通知,该字段可以为 NULL。
int (*lock) (struct file *, int, struct file_lock *);
The lock method is used to implement file locking; locking is an indispensable feature for regular files but is almost never implemented by device drivers. lock方法用于实现文件锁定; 锁定是常规文件必不可少的功能,但设备驱动程序几乎从未实现。
ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
These methods implement scatter/gather read and write operations. Applications occasionally need to do a single read or write operation involving multiple memory areas; these system calls allow them to do so without forcing extra copy operations on the data. If these function pointers are left NULL, the read and write methods are called (perhaps more than once) instead. 这些方法实现分散/聚集读取和写入操作。 应用程序偶尔需要执行涉及多个内存区域的单个读取或写入操作; 这些系统调用允许他们这样做,而无需对数据进行额外的复制操作。 如果这些函数指针保留为 NULL,则改为调用 read 和 write 方法(可能不止一次)。
ssize_t (*sendfile)(struct file *, loff_t *, size_t, read_actor_t, void *);
This method implements the read side of the sendfile system call, which moves the data from one file descriptor to another with a minimum of copying. It is used, for example, by a web server that needs to send the contents of a file out a network connection. Device drivers usually leave sendfile NULL. 此方法实现了 sendfile 系统调用的读取端,它将数据从一个文件描述符移动到另一个文件描述符,并且复制最少。 例如,它由需要将文件内容发送到网络连接的 Web 服务器使用。 设备驱动程序通常将 sendfile 保留为 NULL。
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *,int);
sendpage is the other half of sendfile; it is called by the kernel to send data, one page at a time, to the corresponding file. Device drivers do not usually implement sendpage. sendpage 是 sendfile 的另一半; 内核调用它以一次一页的方式将数据发送到相应的文件。 设备驱动程序通常不实现 sendpage。
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
The purpose of this method is to find a suitable location in the process's address space to map in a memory segment on the underlying device. This task is normally performed by the memory management code; this method exists to allow drivers to enforce any alignment requirements a particular device may have. Most drivers can leave this method NULL. 此方法的目的是在进程的地址空间中找到合适的位置,以映射到底层设备的内存段中。 该任务通常由内存管理代码执行; 此方法的存在是为了允许驱动程序强制执行特定设备可能具有的任何对齐要求。 大多数驱动程序可以将此方法保留为 NULL。
int (*check_flags)(int)
This method allows a module to check the flags passed to an fcntl(F_SETFL...) call. 此方法允许模块检查传递给 fcntl(F_SETFL...) 调用的标志。
int (*dir_notify)(struct file *, unsigned long);
This method is invoked when an application uses fcntl to request directory change notifications. It is useful only to filesystems; drivers need not implement dir_notify. 当应用程序使用 fcntl 请求目录更改通知时调用此方法。 它仅对文件系统有用; 驱动程序不需要实现 dir_notify。
The scull device driver implements only the most important device methods. Its file_operations structure is initialized as follows:
struct file_operations scull_fops = {
.owner = THIS_MODULE,
.llseek = scull_llseek,
.read = scull_read,
.write = scull_write,
.ioctl = scull_ioctl,
.open = scull_open,
.release = scull_release,
};
This declaration uses the standard C tagged structure initialization syntax. This syntax is preferred because it makes drivers more portable across changes in the definitions of the structures and, arguably, makes the code more compact and readable. Tagged initialization allows the reordering of structure members; in some cases, substantial performance improvements have been realized by placing pointers to frequently accessed members in the same hardware cache line. 此声明使用标准 C 标记结构初始化语法。 这种语法是首选的,因为它使驱动程序在结构定义的更改中更具可移植性,并且可以说使代码更加紧凑和可读。 标记初始化允许结构成员的重新排序; 在某些情况下,通过将指向经常访问的成员的指针放在同一硬件高速缓存行中,已经实现了显着的性能改进。