文件操作和文件系统
1. C语言文件接操作
为了更好地理解文件系统和文件描述符,让我们回顾一下C文件接口的基础知识。我们通过一个简单的文件写入和读取的例子来展示。
FILE *fopen(const char *filename, const char *mode);
filename:要打开的文件的路径或名称。
mode:打开文件的模式,包括读("r")、写("w")、追加("a")等。
关闭文件:
int fclose(FILE *stream);
stream:要关闭的文件指针。
读取文件:
size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
ptr:指向存储读取数据的内存块的指针。
size:每个数据项的字节数。
count:要读取的数据项的数量。
stream:文件指针。
写入文件:
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
ptr:指向要写入的数据的内存块的指针。
size:每个数据项的字节数。
count:要写入的数据项的数量。
stream:文件指针。
2. 系统调用与文件操作
系统调用是操作系统提供给应用程序的接口,通过这些接口,应用程序可以请求操作系统执行特定的操作。文件操作通常涉及到一系列系统调用,以便应用程序能够在磁盘或其他存储介质上读取和写入数据。以下是文件操作的系统调用:
2.1 open 系统调用
open 系统调用用于打开或创建文件,其原型如下:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
pathname:要打开或创建的目标文件的路径。
flags:打开文件时的参数选项,比如 O_RDONLY、O_WRONLY、O_RDWR 等。
mode:如果使用 O_CREAT 选项,需要指定新文件的访问权限。
open:返回一个新打开的文件描述符,如果失败则返回 -1。
#include <fcntl.h>
int fd = open("example.txt", O_WRONLY | O_CREAT, 0644);
if (fd < 0) {
perror("open");
return 1;
}
上述例子中,我们使用 open 打开(或创建)一个名为 example.txt 的文件,以只写方式打开,如果文件不存在则创建,权限设置为 0644。
2.2 read 系统调用
read 系统调用用于从文件描述符中读取数据:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
fd:文件描述符,表示从哪个文件或设备读取数据。
buf:指向存放读取数据的缓冲区。
count:欲读取的字节数。
read: 返回值为读取的字节数,如果返回 -1 则表示出错。
#include <unistd.h>
int fd = open("example.txt", O_RDONLY);
if (fd < 0) {
perror("open");
return 1;
}
char buffer[1024];
ssize_t bytesRead = read(fd, buffer, sizeof(buffer));
if (bytesRead > 0) {
buffer[bytesRead] = '\0';
printf("Read from file: %s", buffer);
}
close(fd);
在这个例子中,我们使用 read 从文件中读取数据到缓冲区 buffer。
2.3 write 系统调用
write 系统调用用于向文件描述符中写入数据:
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
fd:文件描述符,表示写入数据到哪个文件或设备。
buf:指向待写入数据的缓冲区。
count:欲写入的字节数。
write: 返回值为实际写入的字节数,如果返回 -1 则表示出错。
#include <fcntl.h>
int fd = open("output.txt", O_WRONLY | O_CREAT, 0644);
if (fd < 0) {
perror("open");
return 1;
}
const char *message = "Hello, File!";
ssize_t bytesWritten = write(fd, message, strlen(message));
if (bytesWritten > 0) {
printf("Write to file successful!\n");
}
close(fd);
在这个例子中,我们使用 write 将字符串写入到文件中。
2.4 close 系统调用
close 系统调用用于关闭文件描述符:
#include <unistd.h>
int close(int fd);
fd:待关闭的文件描述符。
close 返回值为 0 表示成功,-1 表示失败。
#include <fcntl.h>
int fd = open("example.txt", O_RDONLY);
if (fd < 0) {
perror("open");
return 1;
}
// 文件操作
close(fd);
在这个例子中,我们打开文件后执行一些文件操作,最后使用 close 关闭文件描述符。
系统调用在文件操作中扮演着重要的角色,提供了对文件的底层访问。通过 open、read、write 和 close 等系统调用,程序可以打开、读取、写入和关闭文件。这些系统调用是文件 I/O 的基础,也是理解文件系统的关键。
3. 文件描述符
进程管理: 操作系统通过管理进程的方式,将每个正在执行的进程抽象为 task_struct(PCB),然后通过链表等方式将这些结构体组织起来,以实现对进程的有序调度和管理。
文件管理: 一个进程可以打开多个文件,为了对这些文件进行读写操作,操作系统将文件抽象成 file 结构体,并将这些结构体组织起来。文件结构体内部存储了文件的大部分属性。
文件描述符: 文件描述符是操作系统为每个进程分配的整数,用于标识其所打开的文件。进程通过文件描述符来管理打开的文件,通过数组指针的方式组织了打开的文件的 file 结构体,通常有三个默认的文件描述符:0(标准输入stdin)、1(标准输出stdout)、2(标准错误stderr)。文件描述符表是一个数组,通过数组下标与具体文件关联。
文件描述符表:是操作系统用于跟踪和管理一个进程打开的文件的数据结构。每个进程都有一个独立的文件描述符表,该表记录了该进程所打开的所有文件的信息。
文件描述符的分配规则: 文件描述符是从数组下标的最低处开始分配的。新打开的文件描述符会分配给数组中未被使用的最小的下标位置。
程序证明
4. 重定向
重定向是计算机操作系统中的一种机制,允许用户将一个程序的输入或输出从默认的位置(通常是终端或命令行窗口)切换到其他位置,比如文件或者另一个程序。这样的操作使得用户能够更灵活地控制数据的流向。
-
标准输入重定向(<):从文件中读取数据作为程序的标准输入。例如,command < input.txt 将文件 input.txt 的内容作为 command 程序的输入。
-
标准输出重定向(>):将程序的标准输出保存到文件中。例如,command > output.txt 将 command 程序的输出写入到文件 output.txt。
-
追加标准输出重定向(>>):类似于标准输出重定向,但是会追加内容到指定文件而不是覆盖它。例如,command >> output.txt 会将 command 程序的输出追加到文件 output.txt 的末尾。
重定向的原理
重定向的本质是通过改变程序的输入或输出目标,将数据流从默认位置切换到其他位置。标准输出重定向的例子中,原本应该输出到标准输出设备(通常是显示器),但通过重定向操作,输出被发送到了指定的文件中。这是通过关闭标准输出,然后重新打开一个文件,将新的文件描述符用于输出的方式实现的。
重定向输出程序例子:
在这个程序中,通过close(1) 关闭标准输出,然后通过 open(“log.txt”, O_CREAT | O_TRUNC | O_WRONLY, 0666) 打开一个文件(“log.txt”),并获得新的文件描述符(1)。接下来,printf 输出的内容被写入到这个新的文件描述符,而不是默认的标准输出。
重要的是要注意,虽然程序使用 printf 输出,但实际上由于我们关闭了标准输出,printf 写入的是通过 open 打开的文件描述符(“log.txt”)。这就是重定向的本质:改变数据流的方向,使得数据不再流向默认的设备,而是流向了我们指定的目标,即文件 “log.txt”。这样,原本应该在屏幕上显示的内容就被重定向到了文件中。
5. dup2 系统调用
dup2 是一个用于复制文件描述符的系统调用。它的原型如下:
#include <unistd.h>
int dup2(int oldfd, int newfd);
oldfd: 要复制的文件描述符。这是源文件描述符,它将被复制到新的文件描述符。
newfd: 要复制到的目标文件描述符。如果 newfd 已经打开,它将被关闭并且会被 oldfd 的副本所替代。如果 oldfd 和 newfd 相等,dup2 不会关闭 newf。
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
int file = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
// 将标准输出重定向到文件,使用整数值 1 代替 STDOUT_FILENO
if (dup2(file, 1) == -1) {
perror("dup2");
return 1;
}
// 现在,printf 将输出到文件而不是标准输出
printf("This will be written to the file.\n");
close(file); // 关闭文件描述符,但由于 dup2 复制了它,标准输出仍然指向文件
return 0;
}
这个系统调用的作用是将文件描述符 oldfd 复制到 newfd,如果 newfd 已经打开,它会先关闭 newfd。如果 oldfd 和 newfd 相等,dup2 不会关闭 newfd,并返回 newfd。如果复制成功,dup2 返回新的文件描述符,如果失败,则返回 -1,并设置 errno 来指示错误类型。