Linux:一切皆文件
**文件描述符**:它是一种特殊的索引,本质上是进程中`file_struct`结构体成员`fd_array`数组的下标。在Linux等系统中,文件描述符是一个非负整数,用于标识打开的文件,是内核为了高效管理已被打开的文件所创建的索引。通过文件描述符,进程可以对相应文件进行读写等操作。
- **文件打开模式**:分为主模式和副模式。
- **主模式**:
- `O_RDONLY`:以只读模式打开文件,即只能从文件中读取数据,不能进行写入操作。
- `O_WRONLY`:以只写模式打开文件,只能向文件写入数据,无法读取。
- `O_RDWR`:以读写模式打开文件,既可以读取数据也可以写入数据。
- **副模式**:
- `O_CREAT`:当指定文件不存在时,会自动创建该文件。
- `O_APPEND`:追加模式,写入数据时会将数据添加到文件末尾,而不是覆盖原有内容。
- `O_DIRECT`:直接I/O模式,绕过系统缓存,数据直接在用户空间和磁盘之间传输,通常用于对I/O性能有较高要求的场景。
- `O_SYNC`:同步模式,保证每次写入操作都将数据同步到磁盘,确保数据的持久性,但可能会降低写入性能。
- `O_NONBLOCK`:非阻塞模式,在进行I/O操作时,不会使进程阻塞等待,而是立即返回,常用于需要同时处理多个I/O操作的场景。
系统I/O编程
另外一个当然是标准I/O编程。这个后面再说。
(Linux 文件体系结构)
- open
- write
- read
- Iseek
- close
- 伪代码示例:
int fd;
fd = open(filename, flags, mode);
lseek(fd, offset, whence)
write(fd, buf, write_len);
read(fd, buf, readlen);
close(fd);
- 文件描述符:一个特殊的索引,用于标识被打开的文件或其他输入/输出资源。
- 进程:是计算机中已运行的程序实例。
open/close函数
// OPEN函数所需的头文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
// OPEN函数原型
// 当文件存在时
int open(const char* pathname, int flags);
// 当文件不存在,需要创建文件时
int open(const char* pathname, int flags, int perms);
// 返回值
// 成功:返回文件描述符(一个非负整数)
// 失败:返回-1
// CLOSE函数所需的头文件
#include <unistd.h>
完整例子:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main() {
int fd; // 声明文件描述符变量
fd = open("./a.txt", O_RDONLY); // 以只读方式打开文件
if (fd < 0) { // 如果打开文件失败
printf("open error!!\n"); // 输出错误信息
} else {
// 这里可以添加代码来读取文件内容或进行其他操作
}
// 如果需要在文件打开成功后输出空行,可以在else分支中添加printf("\n");
close(fd)
return 0; // 程序正常结束
}
read和write函数
一、函数定义
- read函数
- 功能:从文件描述符指向的文件中读取数据。
- 原型:
ssize_t read(int fd, void *buf, size_t count);
- 参数:
fd
:文件描述符,标识要读取的文件。buf
:指向存储读取数据的缓冲区的指针。count
:要读取的字节数。
- 返回值:成功时返回读取的字节数(可能小于
count
),失败时返回-1。
- write函数
- 功能:向文件描述符指向的文件中写入数据。
- 原型:
ssize_t write(int fd, const void *buf, size_t count);
- 参数:
fd
:文件描述符,标识要写入的文件。buf
:指向包含要写入数据的缓冲区的指针。count
:要写入的字节数。
- 返回值:成功时返回写入的字节数(可能小于
count
),失败时返回-1。
二、使用方法
- 在使用read和write函数之前,需要打开文件并获得文件描述符。
- 调用read函数时,应确保缓冲区足够大以存储读取的数据。
- 调用write函数时,应确保缓冲区中的数据是要写入文件的正确数据。
- 读取或写入操作完成后,应检查返回值以确定操作是否成功。
三、注意事项
- 当读取或写入的数据量较大时,可能需要多次调用read或write函数。
- 在读取或写入过程中,如果遇到错误(如文件结束、磁盘满等),read或write函数将返回-1,并设置errno以指示错误类型。
- 在使用完文件后,应关闭文件描述符以释放资源。
复制普通文件实验步骤:
- 打开要复制的文件:
- 使用适当的文件打开函数或方法,以只读模式打开源文件。
- 创建新的文件:
- 使用适当的文件创建函数或方法,以写模式创建一个新的目标文件。如果文件已存在,可以选择覆盖或报错。
- 把源文件内容读到缓冲区,把缓冲区内容写入新文件:
- 分配一个缓冲区来存储从源文件中读取的数据。
- 使用文件读取函数或方法,从源文件中读取数据到缓冲区。
- 使用文件写入函数或方法,将缓冲区中的数据写入到目标文件中。
- 循环执行第三步,直到读取的字节数量为0,退出循环:
- 重复执行第三步,直到文件读取函数或方法返回0,表示已经到达源文件末尾,没有更多的数据可以读取。
- 关闭打开的文件:
- 使用文件关闭函数或方法,关闭源文件和目标文件,释放资源。
代码:
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#define BUFFER_SIZE 512
int main(int argc, char *argv[]) {
int fd1, fd2;
ssize_t read_size;
char buffer[BUFFER_SIZE];
// 检查命令行参数数量
if (argc != 3) {
fprintf(stderr, "Usage: %s <source_file> <destination_file>\n", argv[0]);
return EXIT_FAILURE;
}
// 打开源文件(只读)
fd1 = open(argv[1], O_RDONLY);
if (fd1 == -1) {
perror("Error opening source file");
return EXIT_FAILURE;
}
// 打开目标文件(写,如果文件不存在则创建)
fd2 = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd2 == -1) {
perror("Error opening destination file");
close(fd1);
return EXIT_FAILURE;
}
// 从源文件读取数据并写入目标文件
while ((read_size = read(fd1, buffer, BUFFER_SIZE)) > 0) {
if (write(fd2, buffer, read_size) != read_size) {
perror("Error writing to destination file");
close(fd1);
close(fd2);
return EXIT_FAILURE;
}
}
// 检查read函数是否因为错误而返回-1
if (read_size == -1) {
perror("Error reading from source file");
}
// 关闭文件描述符
close(fd1);
close(fd2);
return EXIT_SUCCESS;
}
标准I/O :
### C标准库实现了一个I/O缓存区 C标准库为了提高文件输入输出操作的效率,实现了一个I/O缓存区。在对文件进行读写操作时,数据不会立即写入磁盘或从磁盘读取,而是先在缓存区中进行处理。比如进行多次写入操作时,数据先存放在缓存区,等缓存区满或调用特定函数时才真正写入磁盘,这减少了磁盘I/O的次数,从而提升了性能。 ### 常见标准I/O函数 - **fopen**:用于打开一个文件,返回一个指向文件的指针(`FILE*`类型),它需要指定文件名和打开模式(如"r" 只读、"w" 只写、"a" 追加等)。例如 `FILE *fp = fopen("test.txt", "r");` 尝试以只读模式打开名为"test.txt"的文件。 - **fclose**:用于关闭一个已打开的文件,它接受一个文件指针作为参数,在文件操作完成后应及时调用该函数关闭文件,释放相关资源,如 `fclose(fp);`。 - **fread**:从文件流中读取数据到指定的内存区域。它接受目标内存地址、每个数据项的大小、数据项的数量以及文件指针作为参数,返回实际读取的数据项数量。例如 `size_t count = fread(buffer, sizeof(char), 100, fp);` 尝试从文件指针 `fp` 指向的文件中读取100个字符到 `buffer` 中。 - **fwrite**:将数据从指定的内存区域写入到文件流中。参数与 `fread` 类似,接受源内存地址、每个数据项的大小、数据项的数量以及文件指针作为参数,返回实际写入的数据项数量。 - **fseek**:用于改变文件流的读写位置指针。它接受文件指针、偏移量以及起始位置(如 `SEEK_SET` 从文件开头、`SEEK_CUR` 从当前位置、`SEEK_END` 从文件末尾)作为参数。例如 `fseek(fp, 0, SEEK_END);` 将文件指针移动到文件末尾。 - **fflush**:强制把I/O缓存区中的数据写入到页缓存区。当需要确保数据及时写入文件时,可调用此函数,比如在一些日志记录场景中,希望数据尽快落盘就可以使用 `fflush` 。