Linux 一切皆文件

我们在Linux中所熟知的一句话“一切皆文件”,所以在Linux操作系统中,所有的研究实际上就是研究文件,所以我们也有必要了解文件系统。

系统文件相关接口

我们在学C的时候,学过文件相关的接口,这些都是C标准库中的函数,称为库函数。而我们现在要学的接口是系统提供接口,称之为系统调用接口。我们用这些接口对文件进行操作。

那么系统调用接口有哪些呢?
open、write、iseek
上述接口具体用法,可以查看man手册,非常清楚。

此处我们主要来看看open函数:
int open(const char* filename,int flags,mode_t mode);
filename要打开文件的名称
flags以什么属性打开文件
O_RDONLY:以只读的形式打开文件
O_WRONLY:以只写的形式打开文件
O_RDWR:以可读可写形式打开文件
上述三个属性必须有一个。
O_CREAT:若文件不存在则创建。(此时就需要mode来指明新文件权限)
O_APPEND:以可追加的形式打开文件。
mode打开文件后给文件设置的默认权限。
返回值
成功:新打开文件的文件描述符
失败:-1

我大家可能有疑问了,什么是文件描述符?下面我们来介绍一下文件描述符fd。

文件描述符

Linux内核利用文件描述符来访问文件,当我们打开一个新的文件就会给我们返回一个对应文件描述符(一个非负的小整数)。
Linux进程中会默认打开三个文件描述符,分别是标准输入0,标准输出1,标准错误2。
这三个文件描述符0,1,2分别对应的物理设备是:键盘,显示器,显示器。

我们用图再来理解一下:
在这里插入图片描述
通过上图我们发现在PCB中有一个结构体指针files,指向一个file_struct结构体,在这个结构体中有一个指针数组fd_array,里面保存的指针就是打开的文件指针,数组的下标就是我们获得的文件描述符。所以拿到文件描述符就可以找到对应的文件,从而访问文件的信息。

那么文件描述符是怎么分配的呢?
我们知道在Linux下系统会默认打开三个文件,也就是0,1,2。当我们打开一个文件时,会在file_struct数组当中找一个没有被使用的最小下标,来作为我们打开文件的描述符。

重定向

看一段代码

#include<stdio.h>
#include<iostream>
#include<unistd.h>
int main()
{
	close(1);
	int fd =open("my",O_WRONLY|O_CREAT,0777);
	printf("fd:%d\n",fd);
	fflush(stdout);
	close(fd);
	return 0;
}

在这里插入图片描述
通过结果我们发现本应该输出在屏幕上的内容,没有输出,而是输出到了文件中。这种现象就是重定向,这是因为我们将标识符1也就是标准输出关闭了,当我们创建一个新文件,根据标识符的分配规则,打开的文件的标识符就是1,所以我们本该输出的内容,写到了文件内,而没有输出到屏幕上。

画个图理解一下
在这里插入图片描述
这里介绍一个函数:

int dup2(int oldfd ,int newfd)
功能:重定向
newfd:会关闭这个文件描述符,并将newfd拷贝到oldfd上。
oldfd:要重定向到哪个文件描述符上。

FILE

1.因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd访问的。
2.因此C库中的FILE结构体,必定封装了fd。

文件系统

当我们输入ls -l 命令时,我们会看到如下图:
ls -l是读取存储在磁盘上的文件信息。
在这里插入图片描述
从左往右依次是:文件类型-》权限-》硬链接数-》文件拥有者-》文件拥有者组-》大小-》最后修改时间-》文件名。

当我们输入stat 文件名就可以获得文件的更多信息
在这里插入图片描述

我们发现图中有一个Inode,那么这个Inode是干什么的呢?
Inode号码就是区分文件的,因为我们的文件名重复的有很多,所以每一个文件有自己的Inode号码,那么Inode号码是怎么来的呢?这就需要了解一下文件系统。

在这里插入图片描述

上图大概是我们的文件管理系统,我们主要观察的是:
超级块(Super Block):存储的是文件系统本身信息。比如:Block和Inode总量。
块位图(Block Bitmap):记录着DataBlock中哪个数据块没有被占用,哪个块被占用了。
Inode位图(inode Bitmap):每一个比特表示一个Inode号码是否可以用。
Inode结点表(inode Table):保存文件的信息。如:大小、所有者、最近修改时间。
数据区(Data Block):存放文件数据。

当我们创建一个文件,首先要在块位图中找有没有空闲的块,将待存储的文件数据分成不同的块放在数据区,此时需要用inode结点来存储文件的信息,所以就会去inode位图里找空闲的inode结点,将文件信息存储到inode结点里,并将结点放在inode结点表里。最后将文件名称和inode结点当做目录的目录项存储起来。
此时文件和inode结点之间的对应关系就可以让操作系统将文件名与文件内容相联系。

但我们查找一个文件时,先找到文件对应的inode结点,通过inode的结点找到数据区中存储文件数据对的块,将这些快合并在一起就是文件数据了。

硬链接

通过上述知识我们发现,其实找一个文件并不是找一个文件的文件名,而是找到其对应的inode结点。然而在Linux下可以让多个文件对应同一个inode结点。也就说多个文件名可以访问到同一个inode结点对应的内容,这就叫硬链接。
如何建立硬链接呢?

ln [源文件][链接出来的文件名称]
在这里插入图片描述
hello.c是我们创建出来的硬链接。我们发现hello.c和test.c有相同的inode码,所以他们可以访问同一片区域。尽管此时将删除一个硬链接,而另一个仍可以使用,这种一般用作来备份重要文件。

软连接

软连接相当于创建了一个新的文件,这个文件里存储的是源文件的路径,通过这个路径找到源文件的数据。
如何建立软链接呢?
ln -s [源文件][链接出来的文件名称]
在这里插入图片描述
hh使我们软链接创建出来的,我们发hh与test的inode码不同,说明他们有自己独立的inode结点,内容不同。hh保存的是test的路径,所以我们执行hh的时候,就相当于执行了test。

<think>我们正在讨论Linux系统中“一切文件”的实现原理。根据之前的引用讨论,我们可以总结出以下几点关键机制: 1. **统一的文件操作接口**:Linux通过虚拟文件系统(VFS)提供统一的文件操作接口(如open, read, write, close等),这些接口以系统调用的形式提供给用户空间程序。 2. **file_operations结构体**:在内核中,每个打开文件(包括设备、管道、套接字等)都关联一个`struct file`结构体,其中包含一个指向`file_operations`结构体的指针。这个`file_operations`结构体是一个函数指针表,里面定义了针对该文件的具体操作函数(如read, write等)。 3. **设备驱动注册**:对于设备(如键盘、鼠标、磁盘等),设备驱动会实现自己的`file_operations`函数集,并在初始化时将这些操作函数注册到内核中。当用户空间程序对设备文件进行操作时,内核会找到对应的操作函数并调用。 4. **文件描述符(fd)**:每个进程打开一个文件时,内核会返回一个文件描述符(fd),它是一个整数,实际上是该进程文件描述符表的索引。通过fd,进程可以找到对应的`struct file`结构体,从而进行文件操作。 因此,“一切文件”的实现原理在于:将各种不同的资源(普通文件、目录、设备、管道、套接字等)都抽象成一个文件,并提供相同的操作接口(read, write等),但在底层,每个资源类型都有自己特定的操作函数实现。 下面我们用数学公式代码结构来更清晰地说明: 设一个文件操作请求为$O$(例如读操作),用户调用系统调用: $$ \text{read}(fd, buffer, size) $$ 内核处理过程: 1. 根据当前进程进程控制块(PCB)找到文件描述符表,通过$fd$索引到对应的`struct file *file`。 2. 从`file`中取出`file_operations`指针,记为$fop$。 3. 调用$fop \rightarrow \text{read}(file, buffer, size)$。 这里,$fop \rightarrow \text{read}$指向的函数是由具体资源(比如设备驱动)提供的。例如: - 对于普通文件,它指向文件系统(如ext4)提供的读写函数。 - 对于字符设备(如键盘),它指向设备驱动中实现的读函数(可能从设备缓冲区读取数据)。 - 对于网络套接字,它指向网络协议栈实现的读函数。 因此,虽然用户使用相同的系统调用接口,但底层操作函数不同,这就是“一切文件”的核心机制。 另外,引用[2][4]都提到,通过`struct file`下的`file_operations`中的各种函数回调,让开发者能够用相同的文件操作接口(如文件描述符)来操作绝大部分资源。 下面我们用一个简单的伪代码示例说明设备驱动如何实现并注册自己的文件操作: ```c // 设备驱动定义自己的file_operations static const struct file_operations mydev_fops = { .owner = THIS_MODULE, .read = mydev_read, .write = mydev_write, .open = mydev_open, .release = mydev_release, }; // 在初始化函数中注册设备 static int __init mydev_init(void) { // 创建设备节点,关联fops register_chrdev(MY_MAJOR, "mydev", &mydev_fops); ... } ``` 当用户程序打开设备文件(比如`/dev/mydev`)时,内核会创建一个`struct file`结构体,并将其`f_op`指向`mydev_fops`。之后,用户程序对该文件描述符进行`read`操作,就会调用到`mydev_read`函数。 总结: Linux中“一切文件”的实现依赖于: 1. **抽象接口**:统一的文件操作接口(VFS层)。 2. **多态机制**:通过函数指针表(file_operations)实现不同资源的不同操作。 3. **文件描述符**:作为用户内核之间文件操作的句柄。 这样,用户空间程序无需关心底层资源的具体细节,只需使用文件操作接口即可。 §§相关问题§§ 1. 文件描述符FILE结构体之间有什么关系? 2. 如何创建一个新的设备文件并实现其文件操作? 3. Linux中的管道(pipe)是如何实现“一切文件”的? 4. 除了设备文件,还有哪些资源被抽象成了文件? </think>### Linux系统中“一切文件”的实现原理 Linux通过**统一抽象层**实现“一切文件”理念,其核心是**虚拟文件系统(VFS)** **文件操作函数表**机制。关键技术原理如下: #### 1. **统一对象模型** 所有实体(文件、设备、管道等)在内核中都表示为: - `inode`:描述对象的**元数据**(权限、大小等) - `dentry`:目录项缓存,加速路径查找 - `file`:对象操作接口的载体 数学表示文件查找过程: $$ \text{路径名} \xrightarrow{\text{dentry缓存}} \text{inode} \xrightarrow{\text{file_operations}} \text{操作函数} $$ #### 2. **函数回调机制** 内核定义`struct file_operations`函数表(引用[2][4]): ```c struct file_operations { ssize_t (*read)(struct file *, char __user *, size_t, loff_t *); ssize_t (*write)(struct file *, const char __user *, size_t, loff_t *); int (*open)(struct inode *, struct file *); // 其他操作函数... }; ``` **实现原理**: 1. 设备/资源驱动实现自己的函数表(如键盘实现`.read=keyboard_read`) 2. 内核创建`file`结构体时绑定对应函数表 3. 用户调用`read(fd)`时,内核通过文件描述符`fd`找到`file->f_op->read()` #### 3. **文件描述符抽象** - 进程打开对象时获得整数`fd`(文件描述符) - `fd`实际指向进程级**文件描述符表**的索引: $$ \begin{array}{c|c} \text{索引} & \text{内容} \\ \hline 0 & \text{stdin (键盘)} \\ 1 & \text{stdout (屏幕)} \\ 2 & \text{stderr (错误输出)} \\ 3 & \text{普通文件} \\ 4 & \text{网络套接字} \\ \end{array} $$ #### 4. **缓冲区管理** 通过`FILE`结构体(用户层)实现缓冲(引用[3]): ```c typedef struct { int _fileno; // 底层fd char* _buffer; // 用户缓冲区 size_t _bufsize; // 缓冲区大小 } FILE; ``` 数据流动: $$ \text{应用数据} \rightarrow \text{FILE缓冲区} \xrightarrow{\text{系统调用}} \text{内核} \xrightarrow{\text{file_operations}} \text{设备} $$ #### 5. **设备文件化示例** 以键盘为例: 1. 驱动程序实现`keyboard_read()`函数 2. 创建`/dev/input/event0`设备文件 3. 用户程序`open("/dev/input/event0")`获得`fd` 4. `read(fd)`实际调用`keyboard_read()` > 这种抽象使开发者只需使用`open/read/write`等通用接口,无需关心底层硬件差异[^1][^2][^4]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值