inode结构体
inode结构体概述
Linux打开一个文件中的核心点是inode结构体,所以我们就从inode结构体开始讲这个问题。
inode应该来源于“index node”的缩写,所以发音为 [aɪ noʊd]
在Linux系统中,一个文件通常对应一个inode(索引节点),特殊情况下可能有多个文件名与一个inode对应,或者一个文件涉及多个inode,特殊情况我们不作考虑,在这里,我们只需要知道,在Linux系统中,一个文件通常对应一个inode。
inode(索引节点)是文件系统中的数据结构,存储文件的元信息(例如文件大小、权限、所有者、时间戳等),但不包含文件名。
每个文件在文件系统中都会分配一个唯一的inode。
具体来说,inode
在 Linux 内核中是一个结构体,定义在内核的源代码中,具体在头文件 include/linux/fs.h
中。它是文件系统的核心数据结构之一,用于表示文件的元数据。
struct inode
的基本结构
inode
结构体的定义包含许多字段,负责描述文件的各种属性和操作。例如:
struct inode {
umode_t i_mode; /* 文件类型和权限 */
unsigned short i_opflags; /* 操作相关标志 */
kuid_t i_uid; /* 文件所有者的用户ID */
kgid_t i_gid; /* 文件所有者的组ID */
unsigned int i_flags; /* 文件标志 */
const struct inode_operations *i_op; /* 文件操作方法 */
struct super_block *i_sb; /* 所属超级块 */
struct address_space *i_mapping; /* 地址空间 */
loff_t i_size; /* 文件大小 */
struct timespec64 i_atime; /* 最后访问时间 */
struct timespec64 i_mtime; /* 最后修改时间 */
struct timespec64 i_ctime; /* 状态更改时间 */
unsigned int i_nlink; /* 硬链接计数 */
ino_t i_ino; /* inode编号 */
dev_t i_rdev; /* 主设备号和次设备号(如果是设备文件) */
const struct file_operations *i_fop; /* 文件操作结构体,存储着诸如open,read,write等函数 */
/* ...更多字段,用于缓存、锁、权限检查等... */
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
关键字段说明
-
i_mode
:- 保存文件的类型(如普通文件、目录、设备文件)和权限信息。
- 通过掩码可以检查文件类型,例如
S_ISREG
检查是否是普通文件。
-
i_uid
和i_gid
:- 文件的所有者用户ID和组ID。
-
i_size
:- 文件的大小(以字节为单位)。
-
i_atime
/i_mtime
/i_ctime
:- 分别表示最后访问时间、最后修改时间和元数据更改时间。
-
i_nlink
:- 硬链接计数,指向同一个inode的目录项数量。当计数降为0时,文件数据会被释放。
-
i_op
:- 指向一个
inode_operations
结构体,用于定义文件系统对该inode支持的操作(例如创建、删除)。
- 指向一个
-
i_mapping
:- 表示文件的地址空间,通常用来管理文件数据的缓存。
-
i_ino
:- inode编号,文件系统中每个文件的唯一标识。
-
i_rdev
:- 里面存储着主设备号和次设备号信息。
-
i_fop
:- 文件操作结构体,它实际上就是链接着文件操作结构体,比如驱动程序中的文件操作结构体,里面定义着文件的底层打开函数、读函数、写函数等。
inode 的核心作用
-
存储元信息:
- 保存文件的基本属性,比如权限、大小、时间戳等。
-
连接文件数据:
inode
不直接保存文件数据,而是通过其他结构体(如块地址映射表)指向数据块。
-
支持文件操作:
- 内核通过
struct inode
来调度文件系统操作。
- 内核通过
小结:
inode
是 Linux 文件系统中用于描述文件的核心结构体,它包含了文件的所有元信息以及与文件数据的关联。虽然用户空间程序看不到 inode
结构体的细节,但文件系统操作(如open
、read
)背后都依赖于它。
当我们获得了文件的inode后,就可以借由inode来进一步获取文件中的信息。
文件描述符(fd)及与文件描述符直接关联的结构体struct file
文件描述符(fd)
如果我们要访问一个文件的内容,通常的流程是这样的:
文件系统首先通过文件路径和文件名在目录中查找对应的inode,并根据inode中的元信息定位文件的存储位置。
接着,文件系统打开文件并返回一个文件描述符(通常用fd表示),文件描述符是进程内的一个整数标识符,在这个进程中可以用来进一步访问文件内容,而不用每次都再去打开文件。
请注意文件描述符只是进程内的一个整数标识,它对应的文件信息其实是存储在结构体struct file
中的,所以我们需要详细了解下结构体struct file
。
结构体struct file
在 Linux 内核中,打开文件时会涉及到另一个重要的结构体 struct file
,它与 struct inode
一起协同工作,负责文件的实际操作和管理。
struct file
的作用
struct file
表示一个已打开的文件,即文件打开后的状态。- 它与用户空间的文件描述符(
file descriptor
)直接关联。 - 每次调用
open
系统调用时,内核会为该文件创建一个新的struct file
实例,用来管理这个打开的文件。
struct file
和 struct inode
的区别
struct inode | struct file |
---|---|
表示文件在文件系统中的元信息和属性 | 表示一个已打开的文件实例(与进程相关) |
一个文件在文件系统中只有一个 inode | 每次打开文件会创建一个新的 struct file |
包含文件权限、大小、时间戳等信息 | 包含文件的当前偏移量、模式、操作等信息 |
共享于多个文件描述符(通过硬链接等) | 文件描述符对应唯一的 struct file 实例 |
struct file
的关键字段
struct file
定义在内核头文件 include/linux/fs.h
中,主要字段包括:
struct file {
const struct file_operations *f_op; /* 文件操作方法 */
struct inode *f_inode; /* 指向对应的 inode */
void *private_data; /* 私有数据(通常是驱动使用) */
loff_t f_pos; /* 文件的当前偏移量 */
unsigned int f_flags; /* 打开文件的标志(如读写模式) */
struct path f_path; /* 文件路径信息 */
atomic_long_t f_count; /* 引用计数 */
/* 其他字段省略 */
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
主要字段解释:
-
f_op
:- 指向
struct file_operations
,定义了文件支持的操作(如read
、write
、ioctl
等)。 - 不同的文件类型(如普通文件、设备文件)会提供不同的操作实现。
- 指向
-
f_inode
:- 指向文件对应的
struct inode
,通过它可以访问文件的元信息。
- 指向文件对应的
-
f_pos
:- 文件的当前读写位置(偏移量)。
-
f_flags
:- 打开文件时的标志(如
O_RDONLY
,O_WRONLY
,O_RDWR
)。
- 打开文件时的标志(如
-
private_data
:- 文件的私有数据,常用于驱动程序中存储设备相关信息。
-
f_path
:- 表示文件的路径信息(包括文件的
dentry
和vfsmount
)。
- 表示文件的路径信息(包括文件的
打开文件的核心流程
当调用 open
系统调用时,内核的处理流程如下:
-
路径解析:
- 根据文件路径解析到
inode
,找到对应的struct inode
。
- 根据文件路径解析到
-
创建
struct file
:- 为每个打开的文件分配一个新的
struct file
,并初始化其字段。 struct file
的f_inode
字段会指向文件的inode
。
- 为每个打开的文件分配一个新的
-
文件描述符与
struct file
的关联:- 将
struct file
注册到当前进程的文件表中,并分配一个文件描述符(整数值)。 - 文件描述符用于用户空间访问,而
struct file
是内核的实际表示。
- 将
struct file
在文件操作中的作用
struct file
主要用于管理打开文件的状态和操作。例如:
- 读写操作:
- 当调用
read(fd)
或write(fd)
时,内核会根据文件描述符找到对应的struct file
,并调用其f_op
字段中定义的操作函数。
- 当调用
- 偏移量管理:
f_pos
保存了文件的当前偏移量,支持顺序读写操作。
小结
在 Linux 内核中:
struct inode
是文件的静态表示,存储文件的元信息。struct file
是打开文件的动态表示,描述了文件打开后的状态(如偏移量、模式等)。- 两者协作完成文件系统的读写和管理操作。每次打开文件,都会生成一个新的
struct file
,但它们可能共享同一个struct inode
(例如通过硬链接)。
访问一个文件的内容,通常的流程
流程概述
当调用系统函数 open
打开一个文件时,内核的处理流程如下:
-
路径解析:
- 根据文件路径解析到
inode
,找到对应的struct inode
。
- 根据文件路径解析到
-
创建
struct file
:- 为每个打开的文件分配一个新的
struct file
,并初始化其字段。 struct file
的f_inode
字段会指向文件的inode
。
- 为每个打开的文件分配一个新的
-
文件描述符与
struct file
的关联:- 将
struct file
注册到当前进程的文件表中,并分配一个文件描述符(整数值)。 - 文件描述符用于用户空间访问,而
struct file
是内核的实际表示。
- 将
调用系统函数 open
打开一个文件后,最终返回了文件描述符,文件描述符是进程内的一个整数标识符,它与一个结构体 struct file
相关联,里面存储着文件及对文件操作的各种上下文信息,从而,在这个进程后续的操作中,可以用文件描述符内进一步访问文件内容或向文件写内容。
一个打开并操作普通文件的例子
一个操作普通文件的例子如下:
#include
#include
#include
int main() {
int fd = open("/home/user/file.txt", O_RDONLY);
if (fd == -1) {
perror("open");
return 1;
}
char buffer[1024];
ssize_t bytesRead = read(fd, buffer, sizeof(buffer) - 1);
if (bytesRead > 0) {
buffer[bytesRead] = '\0';
printf("File content: %s\n", buffer);
} else {
perror("read");
}
close(fd);
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
在上面的例子中,通过文件路径/home/user/file.txt
找到文件file.txt
对应的inode
,找到文件file.txt
的inode
后,我们就知道了文件file.txt
的存储位置,如果inode
中的信息显示这个文件有可以打开权限,那么系统的open()函数便能打开它,并且返回属于这个进程的文件描述fd
,相关代码如下:
int fd = open("/home/user/file.txt", O_RDONLY);
if (fd == -1) {
perror("open");
return 1;
}
- 1
- 2
- 3
- 4
- 5
借由这个文件描述符,那么在该进程内我们需要再次操作这个文件时,便不需要再去打开操作了。
比如上面的示例中,代码:
ssize_t bytesRead = read(fd, buffer, sizeof(buffer) - 1);
- 1
便是进程内借由文件描述符fd
去读取文件内容。之所以要少读一个,原因见 http://iyenn.com/rec/1709343.html
写文件的流程这里就再叙述了,理解了inode和文件描述符的概念后,再配合一个读的示例,这个问题基本就搞清了。
一个打开驱动程序的设备文件的例子
假设驱动程序的文件操作结构体file_operations如下定义:
static struct file_operations hello_drv_fops = {
.owner = THIS_MODULE,
.open = hello_drv_open,
.release = hello_drv_close,
};
- 1
- 2
- 3
- 4
- 5
设备底层打开函数hello_drv_open
的代码如下:
static int hello_drv_open(struct inode *node, struct file *file)
{
dev_t dev = node->i_rdev;
int major = MAJOR(dev);
int minor = MINOR(dev);
printk("Opening device: major=%d, minor=%d\n", major, minor);
// 为每次打开操作分配私有数据
file->private_data = kmalloc(sizeof(int), GFP_KERNEL);
if (!file->private_data) {
printk("Failed to allocate memory for private data\n");
return -ENOMEM;
}
*(int *)file->private_data = 42; // 示例数据
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
这个函数的两个参数便使用到了结构体inode
和结构体file
,
当系统函数open尝度打开设备文件/dev/hello
时,即下面的代码:
// 打开设备文件
fd = open("/dev/hello", O_RDWR);
if (fd < 0) {
perror("Failed to open device");
return errno;
}
- 1
- 2
- 3
- 4
- 5
- 6
系统会根据设备文件路径/dev/hello
找到文件的inode实例,然后准备利用inode结构体打开文件。
打开文件前首先会为准备找开的文件分配一个 struct file
实例,这个结构体在上文中已经讲清楚了,这里不再多述。
然后正式开始打开文件,打开文件时找到inode结构体的成员i_fop,其定义如下:
const struct file_operations *i_fop; /* 文件操作结构体,存储着诸如open,read,write等函数 */
- 1
它实际上指向着我们在驱动程序中定义的设备结构体,代码如下:
static struct file_operations hello_drv_fops = {
.owner = THIS_MODULE,
.open = hello_drv_open,
.release = hello_drv_close,
};
- 1
- 2
- 3
- 4
- 5
这个结构体成员中便存储着设备底层的打开函数,然后通过它获得设备的底层打开函数,在这里就是函数hello_drv_open()
,并且把系统自动生成的结构体struct inode 和结构体 struct file的实例作为参数传递给函数hello_drv_open()
,函数hello_drv_open()
最终利用这两个参数完成设备文件的打开操作,当然,函数hello_drv_open()
的函数其实并不一定非得用到这两个参数,不用也可以,只是系统的open()函数会把这两个参数传递给设备结构体hello_drv_fops
中的打开函数hello_drv_open()
。在我们这里给出的例子中,用到了这两个参数,我们利用参数struct inode *node
打印出了设备号,利用参数struct file *file
记录了一些私有数据,这些数据可以根据我们的需要用作不同的用途。
如果一切正常,系统的open()函数会为此次设备文件的打开分配一个文件描述符(fd),并将文件描述符(fd)的值记录到结构体 struct file
的实例中。在进程的后续操作中,便可利用这个文件文件描述符(fd)对文件进行一系列操作。
以上便是一个打开驱动程序的设备文件的例子的详细流程。



评论记录:
回复评论: