Linux 系统中的文件 I/O(输入/输出)涉及多种操作和系统调用,用于读写文件和管理文件描述符。
文件 I/O 基础
1. 文件描述符(File Descriptors):
- 文件描述符是非负整数,用于标识一个已打开的文件、设备或管道。
- 文件描述符 0、1 和 2 分别表示标准输入(stdin)、标准输出(stdout)和标准错误(stderr)。
2. 文件 I/O 操作:
- 文件 I/O 包括打开、关闭、读取、写入和管理文件。
常用系统调用
1. open:
- 用于打开文件或设备,并返回一个文件描述符。
- 原型: `int open(const char *pathname, int flags, mode_t mode);`
- 示例:
int fd = open("example.txt", O_RDONLY);
2. close:
- 关闭一个打开的文件描述符。
- 原型: `int close(int fd);`
- 示例:
close(fd);
3. read:
- 从文件描述符中读取数据。
- 原型: `ssize_t read(int fd, void *buf, size_t count);`
- 示例:
char buffer[100];
ssize_t bytesRead = read(fd, buffer, sizeof(buffer));
4. write:
- 向文件描述符写入数据。
- 原型: `ssize_t write(int fd, const void *buf, size_t count);`
- 示例:
const char *msg = "Hello, World!";
ssize_t bytesWritten = write(fd, msg, strlen(msg));
5. lseek:
- 设置或获取文件描述符的偏移量。
- 原型: `off_t lseek(int fd, off_t offset, int whence);`
- 示例:
off_t newPos = lseek(fd, 0, SEEK_END);
6. ftruncate:
- 截断文件到指定长度。
- 原型: `int ftruncate(int fd, off_t length);`
- 示例:
ftruncate(fd, 1024);
7. fopen, fclose, fread, fwrite:
- 标准 C 库函数,用于文件操作,提供更高级的文件 I/O。
- 示例:
FILE *file = fopen("example.txt", "r");
char buffer[100];
fread(buffer, sizeof(char), 100, file);
fclose(file);
文件 I/O 操作示例
1. 打开文件并读取数据
#include
#include
#include
int main() {
int fd = open("example.txt", O_RDONLY);
if (fd < 0) {
perror("open");
return 1;
}
char buffer[100];
ssize_t bytesRead = read(fd, buffer, sizeof(buffer) - 1);
if (bytesRead < 0) {
perror("read");
close(fd);
return 1;
}
buffer[bytesRead] = '\0'; // Null-terminate the buffer
printf("Read data: %s\n", buffer);
close(fd);
return 0;
}
2. 写入文件
#include
#include
#include
int main() {
int fd = open("example.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) {
perror("open");
return 1;
}
const char *msg = "Hello, World!";
ssize_t bytesWritten = write(fd, msg, strlen(msg));
if (bytesWritten < 0) {
perror("write");
close(fd);
return 1;
}
close(fd);
return 0;
}
高级文件 I/O 操作
1. mmap:
- 将文件或设备映射到内存,以实现更高效的 I/O 操作。
- 原型: `void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);`
- 示例:
#include
#include
int fd = open("example.txt", O_RDONLY);
off_t length = lseek(fd, 0, SEEK_END);
void *mapped = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0);
if (mapped == MAP_FAILED) {
perror("mmap");
close(fd);
return 1;
}
// Use mapped memory...
munmap(mapped, length);
close(fd);
2. ioctl:
- 执行设备特定的输入/输出控制操作。
- 原型: `int ioctl(int fd, unsigned long request, ...);`
- 示例:
#include
int fd = open("/dev/some_device", O_RDWR);
int result = ioctl(fd, SOME_IOCTL_COMMAND, ¶meter);
文件 I/O 的注意事项
1. 错误处理:
- 总是检查系统调用的返回值,处理错误并打印适当的错误信息。
2. 资源管理:
- 确保文件描述符在完成操作后被关闭,以防资源泄漏。
3. 文件权限:
- 使用 open 时指定正确的权限,并确保程序有权访问指定的文件或设备。
4. 同步和缓存:
- 文件 I/O 操作可能涉及缓存和同步操作,了解 fsync 和 fdatasync 等函数可以帮助确保数据被正确写入磁盘。
在 C 语言中,文件 I/O 和标准 I/O 是两种不同的输入/输出操作方式,各自有不同的接口和功能。
文件 I/O(系统调用)
文件 I/O 是基于系统调用的低级 I/O 操作方式,直接与操作系统交互。
主要函数
1. open: 打开一个文件,返回文件描述符。
int fd = open(const char *pathname, int flags, mode_t mode);
2. close: 关闭一个文件描述符。
int close(int fd);
3. read: 从文件描述符中读取数据。
ssize_t read(int fd, void *buf, size_t count);
4. write: 向文件描述符写入数据。
ssize_t write(int fd, const void *buf, size_t count);
5. lseek: 设置或获取文件描述符的偏移量。
off_t lseek(int fd, off_t offset, int whence);
6. ftruncate: 截断文件到指定长度。
int ftruncate(int fd, off_t length);
特点
- 低级操作: 直接操作文件描述符,提供对文件的原始访问。
- 无缓冲: 直接与内核交互,通常需要手动管理缓存和同步。
- 灵活性: 可以用于处理各种设备(如管道、网络套接字)和文件类型。
- 效率: 可以进行高效的 I/O 操作,尤其是在处理大文件时。
标准 I/O(C 标准库)
标准 I/O 是 C 标准库提供的高级 I/O 操作方式,基于文件流的抽象。
主要函数
1. fopen: 打开一个文件流。
FILE *fopen(const char *filename, const char *mode);
2. fclose: 关闭一个文件流。
int fclose(FILE *stream);
3. fread: 从文件流中读取数据。
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
4. fwrite: 向文件流写入数据。
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
5. fseek: 设置文件流的读写位置。
int fseek(FILE *stream, long offset, int whence);
6. ftell: 获取文件流的当前读写位置。
long ftell(FILE *stream);
7. fprintf`/`fscanf: 格式化写入/读取。
int fprintf(FILE *stream, const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
特点
- 高级抽象: 提供对文件的更高层次的抽象,操作文件流而不是文件描述符。
- 缓冲: 内置缓冲机制(如 `FILE` 结构),通常提供更高的性能和易用性。
- 便利性: 更易于使用,适合大多数文件处理任务。
- 安全性: 自动处理缓冲区、格式化等,减少了许多底层错误的可能性。
比较
1. 级别:
- 文件 I/O: 低级操作,直接操作文件描述符。
- 标准 I/O: 高级操作,基于文件流的抽象。
2. 缓冲:
- 文件 I/O: 不自动缓冲,需要手动管理。
- 标准 I/O: 自动缓冲,通常更高效。
3. 易用性:
- 文件 I/O: 更复杂,适合需要精细控制的情况。
- 标准 I/O: 更简单,适合大多数应用程序。
4. 性能:
- 文件 I/O: 在需要低级控制或处理大量数据时可能更高效。
- 标准 I/O: 对于大多数普通操作,性能通常足够,并且提供了额外的便利性。
5. 灵活性:
- 文件 I/O: 可以操作各种文件和设备类型,如管道、套接字等。
- 标准 I/O: 主要用于文件操作,虽然可以与标准输入输出设备一起使用,但不适用于所有类型的设备。
示例代码
文件 I/O 示例:
#include
#include
#include
int main() {
int fd = open("example.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1) {
perror("open");
return 1;
}
const char *msg = "Hello, World!";
write(fd, msg, strlen(msg));
close(fd);
return 0;
}
标准 I/O 示例:
#include
int main() {
FILE *file = fopen("example.txt", "w");
if (file == NULL) {
perror("fopen");
return 1;
}
fprintf(file, "Hello, World!");
fclose(file);
return 0;
}
总结来说,文件 I/O 和标准 I/O 各有优缺点,根据需要选择合适的方式进行文件操作。如果你需要更底层的控制或者处理特殊设备,文件 I/O 更合适;而对于大多数常规的文件操作,标准 I/O 更加方便和高效。