The file Structure
struct file, defined in
The file structure represents an open file. (It is not specific to device drivers; every open file in the system has an associated struct file in kernel space.) It is created by the kernel on open and is passed to any function that operates on the file, until the last close. After all instances of the file are closed, the kernel releases the data structure. 文件结构代表一个打开的文件。 (它不特定于设备驱动程序;系统中的每个打开文件在内核空间中都有一个关联的结构文件。)它由内核在打开时创建,并传递给对该文件进行操作的任何函数,直到最后一次关闭。 在文件的所有实例都关闭后,内核释放数据结构。
In the kernel sources, a pointer to struct file is usually called either file or filp ("file pointer"). We'll consistently call the pointer filp to prevent ambiguities with the structure itself. Thus, file refers to the structure and filp to a pointer to the structure. 在内核源代码中,指向结构文件的指针通常称为 file 或 filp(“文件指针”)。 我们将始终调用指针 filp 以防止结构本身出现歧义。 因此,file 指的是结构,而 filp 指的是指向该结构的指针。
The most important fields of struct file are shown here. As in the previous section, the list can be skipped on a first reading. However, later in this chapter, when we face some real C code, we'll discuss the fields in more detail. 结构文件最重要的字段显示在这里。 与上一节一样,第一次阅读时可以跳过该列表。 然而,在本章后面,当我们面对一些真正的 C 代码时,我们将更详细地讨论这些字段。
mode_t f_mode;
The file mode identifies the file as either readable or writable (or both), by means of the bits FMODE_READ and FMODE_WRITE. You might want to check this field for read/write permission in your open or ioctl function, but you don't need to check permissions for read and write, because the kernel checks before invoking your method. An attempt to read or write when the file has not been opened for that type of access is rejected without the driver even knowing about it. 文件模式通过位 FMODE_READ 和 FMODE_WRITE 将文件标识为可读或可写(或两者)。 您可能希望在 open 或 ioctl 函数中检查该字段的读/写权限,但您不需要检查读写权限,因为内核会在调用您的方法之前进行检查。 当文件没有被打开以进行那种类型的访问时,在驱动程序甚至不知道的情况下,尝试读取或写入都会被拒绝。
loff_t f_pos;
The current reading or writing position. loff_t is a 64-bit value on all platforms (long long in gcc terminology). The driver can read this value if it needs to know the current position in the file but should not normally change it; read and write should update a position using the pointer they receive as the last argument instead of acting on filp->f_pos directly. The one exception to this rule is in the llseek method, the purpose of which is to change the file position. 当前读取或写入位置。 loff_t 是所有平台上的 64 位值(gcc 术语中的 long long )。 如果驱动程序需要知道文件中的当前位置但通常不应该改变它,它可以读取这个值; read 和 write 应该使用作为最后一个参数接收的指针来更新位置,而不是直接作用于 filp->f_pos。 此规则的一个例外是 llseek 方法,其目的是更改文件位置。
unsigned int f_flags;
These are the file flags, such as O_RDONLY, O_NONBLOCK, and O_SYNC. A driver should check the O_NONBLOCK flag to see if nonblocking operation has been requested (we discuss nonblocking I/O in Section 6.2.3); the other flags are seldom used. In particular, read/write permission should be checked using f_mode rather than f_flags. All the flags are defined in the header
struct file_operations *f_op;
The operations associated with the file. The kernel assigns the pointer as part of its implementation of open and then reads it when it needs to dispatch any operations. The value in filp->f_op is never saved by the kernel for later reference; this means that you can change the file operations associated with your file, and the new methods will be effective after you return to the caller. For example, the code for open associated with major number 1 (/dev/null, /dev/zero, and so on) substitutes the operations in filp->f_op depending on the minor number being opened. This practice allows the implementation of several behaviors under the same major number without introducing overhead at each system call. The ability to replace the file operations is the kernel equivalent of "method overriding" in object-oriented programming. 与文件关联的操作。 内核分配指针作为其 open 实现的一部分,然后在需要调度任何操作时读取它。 filp->f_op 中的值永远不会被内核保存以供以后参考; 这意味着您可以更改与您的文件关联的文件操作,并且新方法将在您返回调用方后生效。 例如,与主编号 1 相关联的 open 代码(/dev/null、/dev/zero 等)根据被打开的次编号替换 filp->f_op 中的操作。 这种做法允许在同一主编号下实现多个行为,而不会在每个系统调用中引入开销。 替换文件操作的能力是面向对象编程中“方法覆盖”的内核等价物。
void *private_data;
The open system call sets this pointer to NULL before calling the open method for the driver. You are free to make its own use of the field or to ignore it; you can use the field to point to allocated data, but then you must remember to free that memory in the release method before the file structure is destroyed by the kernel. private_data is a useful resource for preserving state information across system calls and is used by most of our sample modules. open 系统调用在调用驱动程序的 open 方法之前将此指针设置为 NULL。 您可以自由地使用该字段或忽略它; 您可以使用该字段指向分配的数据,但是您必须记住在内核破坏文件结构之前在释放方法中释放该内存。 private_data 是跨系统调用保存状态信息的有用资源,我们的大多数示例模块都使用它。
struct dentry *f_dentry;
The directory entry (dentry) structure associated with the file. Device driver writers normally need not concern themselves with dentry structures, other than to access the inode structure as filp->f_dentry->d_inode. 与文件关联的目录条目(dentry)结构。 设备驱动程序编写者通常不需要关心 dentry 结构,只需像 filp->f_dentry->d_inode 一样访问 inode 结构。
The real structure has a few more fields, but they aren't useful to device drivers. We can safely ignore those fields, because drivers never create file structures; they only access structures created elsewhere. 真正的结构有更多字段,但它们对设备驱动程序没有用处。 我们可以放心地忽略这些字段,因为驱动程序从不创建文件结构; 他们只能访问在其他地方创建的结构。
The inode Structure
The inode structure is used by the kernel internally to represent files. Therefore, it is different from the file structure that represents an open file descriptor. There can be numerous file structures representing multiple open descriptors on a single file, but they all point to a single inode structure. 内核内部使用 inode 结构来表示文件。 因此,它不同于表示打开文件描述符的文件结构。 可以有许多文件结构表示单个文件上的多个打开描述符,但它们都指向单个 inode 结构。
The inode structure contains a great deal of information about the file. As a general rule, only two fields of this structure are of interest for writing driver code: inode 结构包含有关文件的大量信息。 作为一般规则,该结构中只有两个字段对编写驱动程序代码有用:
dev_t i_rdev;
For inodes that represent device files, this field contains the actual device number. 对于表示设备文件的 inode,此字段包含实际的设备编号。
struct cdev *i_cdev;
struct cdev is the kernel's internal structure that represents char devices; this field contains a pointer to that structure when the inode refers to a char device file. struct cdev 是内核的内部结构,表示字符设备; 当 inode 引用 char 设备文件时,此字段包含指向该结构的指针。
The type of i_rdev changed over the course of the 2.5 development series, breaking a lot of drivers. As a way of encouraging more portable programming, the kernel developers have added two macros that can be used to obtain the major and minor number from an inode: i_rdev 的类型在 2.5 开发系列的过程中发生了变化,破坏了很多驱动程序。 作为鼓励更多可移植编程的一种方式,内核开发人员添加了两个可用于从 inode 获取主要和次要编号的宏:
unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);
In the interest of not being caught by the next change, these macros should be used instead of manipulating i_rdev directly. 为了不被下一个变化抓住,应该使用这些宏而不是直接操作 i_rdev。
Char Device Registration
As we mentioned, the kernel uses structures of type struct cdev to represent char devices internally. Before the kernel invokes your device's operations, you must allocate and register one or more of these structures.[6] To do so, your code should include
There are two ways of allocating and initializing one of these structures. If you wish to obtain a standalone cdev structure at runtime, you may do so with code such as: 有两种分配和初始化这些结构之一的方法。 如果您希望在运行时获得独立的 cdev 结构,您可以使用以下代码来实现:
struct cdev *my_cdev = cdev_alloc( );
my_cdev->ops = &my_fops;
Chances are, however, that you will want to embed the cdev structure within a device-specific structure of your own; that is what scull does. In that case, you should initialize the structure that you have already allocated with: 但是,您可能希望将 cdev 结构嵌入到您自己的特定于设备的结构中; 这就是 scull 所做的。 在这种情况下,您应该初始化已经分配的结构:
void cdev_init(struct cdev *cdev, struct file_operations *fops);
Either way, there is one other struct cdev field that you need to initialize. Like the file_operations structure, struct cdev has an owner field that should be set to THIS_MODULE. 无论哪种方式,您都需要初始化另一个 struct cdev 字段。 与 file_operations 结构一样,struct cdev 有一个应设置为 THIS_MODULE 的所有者字段。
Once the cdev structure is set up, the final step is to tell the kernel about it with a call to: 一旦建立了 cdev 结构,最后一步是通过调用以下命令告诉内核:
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
Here, dev is the cdev structure, num is the first device number to which this device responds, and count is the number of device numbers that should be associated with the device. Often count is one, but there are situations where it makes sense to have more than one device number correspond to a specific device. Consider, for example, the SCSI tape driver, which allows user space to select operating modes (such as density) by assigning multiple minor numbers to each physical device. 这里,dev 是 cdev 结构,num 是该设备响应的第一个设备号,count 是应该与该设备关联的设备号的数量。 计数通常是一,但在某些情况下,让多个设备编号对应于特定设备是有意义的。 例如,考虑 SCSI 磁带驱动程序,它允许用户空间通过为每个物理设备分配多个次要编号来选择操作模式(例如密度)。
There are a couple of important things to keep in mind when using cdev_add. The first is that this call can fail. If it returns a negative error code, your device has not been added to the system. It almost always succeeds, however, and that brings up the other point: as soon as cdev_add returns, your device is "live" and its operations can be called by the kernel. You should not call cdev_add until your driver is completely ready to handle operations on the device. 使用 cdev_add 时需要牢记几件重要的事情。 首先是这个调用可能会失败。 如果它返回负错误代码,则您的设备尚未添加到系统中。 然而,它几乎总是成功,这就引出了另一点:只要 cdev_add 返回,您的设备就“活动”了,内核可以调用它的操作。 在您的驱动程序完全准备好处理设备上的操作之前,您不应调用 cdev_add。
To remove a char device from the system, call: 要从系统中删除 char 设备,请调用:
void cdev_del(struct cdev *dev);
Clearly, you should not access the cdev structure after passing it to cdev_del. 显然,在将 cdev 结构传递给 cdev_del 之后,您不应该访问它。