Bootstrap

【Linux探索学习】第二十弹——基础IO:深入理解C语言文件I/O与Linux操作系统中的文件操作

Linux学习笔记:

https://blog.csdn.net/2301_80220607/category_12805278.html?spm=1001.2014.3001.5482

前言:

文件I/O(输入输出)操作是现代计算机系统中的重要组成部分,几乎所有的程序都需要与文件进行交互。无论是读取配置文件、写入日志文件,还是处理用户数据,文件操作都是不可避免的。而在操作系统层面,文件操作的实现往往是通过文件描述符和系统调用接口来完成的。在Linux操作系统中,文件描述符是处理文件的关键,而通过系统调用接口,程序能够直接与操作系统交互,实现文件的打开、读取、写入和关闭等操作。

C语言作为一种底层编程语言,提供了多种文件操作函数,同时,Linux操作系统提供了底层的系统调用,帮助程序实现更精细的文件控制。在这篇博客中,我们将深入探讨C语言文件I/O操作的基础,重点讨论操作系统中的文件操作机制,详细讲解Linux中的文件描述符和文件操作系统调用接口。通过这篇文章,你将能够理解文件操作的基本概念、系统调用的工作原理以及如何在C语言中实现高效的文件操作。

目录

第一部分:C语言文件I/O操作基础

1.1 打开文件:fopen()

1.2 读取文件:fread()

1.3 写入文件:fwrite()

1.4 关闭文件:fclose()

1.5 错误处理

第二部分:Linux操作系统中的文件操作

2.1 文件描述符(File Descriptor)

2.2 系统调用接口

2.2.1 open() 系统调用

2.2.2 read() 系统调用

2.2.3 write() 系统调用

2.2.4 close() 系统调用

第三部分:文件描述符与FILE *的区别

3.1 FILE *与文件描述符的区别

结论


首先我们先来讲解一下C语言中的文件I/O操作,这个在我们前面C语言的讲解中已经提到过,今天我们结合操作系统的IO操作再来回顾一下

第一部分:C语言文件I/O操作基础

在C语言中,文件I/O操作主要是通过标准库提供的FILE *指针和一系列文件操作函数来实现的。这些函数为开发人员提供了更高层的文件操作接口,使得文件读写变得简单和方便。

1.1 打开文件:fopen()

C语言通过fopen()函数打开文件,并返回一个FILE *类型的指针,用于后续的文件读写操作。fopen()的语法如下:

FILE *fopen(const char *filename, const char *mode);
  • filename:表示文件的路径,文件可以是相对路径或绝对路径。
  • mode:文件打开的模式,决定文件的读写方式。

常见的mode有:

模式描述
"r"以只读模式打开文件,文件必须存在。
"w"以写模式打开文件,若文件已存在则会被清空。
"a"以追加模式打开文件,若文件不存在则会创建文件。
"rb"以二进制模式只读打开文件,文件必须存在。
"wb"以二进制模式写入文件,若文件已存在则会被清空。

示例:

#include <stdio.h>

int main() {
    FILE *file = fopen("example.txt", "w");
    if (file == NULL) {
        perror("Error opening file");
        return -1;
    }

    fprintf(file, "Hello, World!\n");
    fclose(file);
    return 0;
}

运行结果:

1.2 读取文件:fread()

fread()函数从文件中读取数据,并将其存储到内存中。其语法如下:

size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
  • ptr:指向内存的指针,用于存储读取的数据。
  • size:每个数据元素的大小(单位:字节)。
  • count:读取的元素个数。
  • stream:文件指针,指定从哪个文件读取数据。

示例:

#include <stdio.h>

int main() {
    FILE *file = fopen("example.txt", "r");
    if (file == NULL) {
        perror("Error opening file");
        return -1;
    }

    char buffer[100];
    size_t bytesRead = fread(buffer, sizeof(char), 100, file);
    buffer[bytesRead] = '\0';  // Add null terminator
    printf("File content: %s\n", buffer);
    
    fclose(file);
    return 0;
}

运行结果:

1.3 写入文件:fwrite()

fwrite()函数用于将数据从内存写入到文件中。其语法如下:

size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
  • ptr:指向内存中数据的指针。
  • size:每个数据元素的大小(单位:字节)。
  • count:写入的元素个数。
  • stream:文件指针,指定将数据写入哪个文件。

示例:

#include <stdio.h>

int main() {
    FILE *file = fopen("example.txt", "w");
    if (file == NULL) {
        perror("Error opening file");
        return -1;
    }

    const char *message = "Hello from C!\n";
    fwrite(message, sizeof(char), 16, file);
    
    fclose(file);
    return 0;
}

运行结果:

1.4 关闭文件:fclose()

文件操作完成后,应该通过fclose()函数关闭文件。其语法如下:

int fclose(FILE *stream);
  • stream:文件指针,指向已经打开的文件。

关闭文件后,程序无法再通过该文件指针进行任何操作。

示例:

#include <stdio.h>

int main() {
    FILE *file = fopen("example.txt", "r");
    if (file == NULL) {
        perror("Error opening file");
        return -1;
    }

    // Perform file operations...

    fclose(file);
    return 0;
}

1.5 错误处理

在文件操作中,错误处理非常重要。C语言提供了两个函数来帮助开发者检测错误:ferror()feof()

  • ferror(FILE *stream):判断是否发生了文件I/O错误。
  • feof(FILE *stream):判断文件是否到达了末尾。

示例:

#include <stdio.h>

int main() {
    FILE *file = fopen("example.txt", "r");
    if (file == NULL) {
        perror("Error opening file");
        return -1;
    }

    char buffer[100];
    if (fread(buffer, sizeof(char), 100, file) == 0) {
        if (ferror(file)) {
            perror("Error reading file");
        } else if (feof(file)) {
            printf("End of file reached.\n");
        }
    }

    fclose(file);
    return 0;
}

第二部分:Linux操作系统中的文件操作

在Linux中,文件操作是通过系统调用接口和文件描述符来完成的。文件描述符是一个整数,它表示一个打开的文件,而系统调用接口提供了底层文件操作的功能,如打开、读写和关闭文件。理解Linux中的文件描述符和系统调用接口对于深入了解文件I/O非常重要。

2.1 文件描述符(File Descriptor)

在Linux中,每个打开的文件都会被分配一个文件描述符。文件描述符是一个非负整数,操作系统内核通过它来跟踪进程与文件之间的关联。文件描述符提供了与文件进行交互的通道,可以通过它执行各种文件操作。

文件描述符描述
0标准输入(stdin)
1标准输出(stdout)
2标准错误(stderr)

除了标准输入、输出和错误,程序还可以使用open()系统调用打开文件并获得其他文件描述符。

2.2 系统调用接口

Linux提供了多个系统调用来执行文件操作,常见的系统调用包括:open()read()write()close()等。通过这些系统调用,程序能够直接与内核进行交互,完成文件的操作。

2.2.1 open() 系统调用

open()系统调用用于打开一个文件并返回文件描述符。其语法如下:

int open(const char *pathname, int flags, mode_t mode);
  • pathname:要打开的文件的路径。
  • flags:文件打开模式,决定文件的访问方式。
  • mode:文件权限,通常在文件创建时使用。

常见的flags参数包括:

标志描述
O_RDONLY以只读模式打开文件
O_WRONLY以只写模式打开文件
O_RDWR以读写模式打开文件
O_CREAT文件不存在时创建文件
O_TRUNC如果文件已存在,清空文件内容
O_APPEND以追加模式打开文件

示例:

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    int fd = open("example.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd == -1) {
        perror("Error opening file");
        return -1;
    }

    const char *data = "Hello from Linux File Descriptor!";
    write(fd, data, 31);  // 写入数据到文件
    close(fd);  // 关闭文件描述符
    return 0;
}

运行结果:

2.2.2 read() 系统调用

read()系统调用用于从文件中读取数据,并将数据存储到内存中。其语法如下:

ssize_t read(int fd, void *buf, size_t count);
  • fd:文件描述符。
  • buf:指向内存的指针,用于存储读取的数据。
  • count:要读取的字节数。

示例:

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    int fd = open("example.txt", O_RDONLY);
    if (fd == -1) {
        perror("Error opening file");
        return -1;
    }

    char buffer[100];
    ssize_t bytesRead = read(fd, buffer, sizeof(buffer) - 1);
    if (bytesRead == -1) {
        perror("Error reading file");
        close(fd);
        return -1;
    }

    buffer[bytesRead] = '\0';  // Null-terminate the string
    printf("File content: %s\n", buffer);

    close(fd);
    return 0;
}

运行结果:

2.2.3 write() 系统调用

write()系统调用用于将数据写入文件。其语法如下:

ssize_t write(int fd, const void *buf, size_t count);
  • fd:文件描述符。
  • buf:指向要写入数据的内存地址。
  • count:要写入的字节数。

示例:

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    int fd = open("example.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd == -1) {
        perror("Error opening file");
        return -1;
    }

    const char *message = "Hello from Linux File Descriptor!\n";
    write(fd, message, 31);  // Write data to file
    close(fd);  // Close file descriptor
    return 0;
}

运行结果:

2.2.4 close() 系统调用

close()系统调用用于关闭文件描述符。其语法如下:

int close(int fd);
  • fd:要关闭的文件描述符。

示例:

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    int fd = open("example.txt", O_RDONLY);
    if (fd == -1) {
        perror("Error opening file");
        return -1;
    }

    // Perform file operations...

    close(fd);  // Close the file descriptor
    return 0;
}

第三部分:文件描述符与FILE *的区别

虽然C语言提供了FILE *类型和相关的标准库函数来处理文件操作,但底层实际上是通过文件描述符来进行管理的。理解FILE *与文件描述符的区别对于深入理解文件I/O非常重要。

3.1 FILE *与文件描述符的区别

特性FILE *(C标准库)文件描述符(Linux操作系统)
类型由C标准库提供的类型操作系统内核使用整数值标识
管理者由C标准库管理由操作系统内核管理
主要用途提供更高级别的文件操作接口提供更低级别的文件操作接口
缓冲区管理提供缓冲区管理,提高效率不提供缓冲区管理
数据访问方式适用于文本文件的高级操作适用于二进制文件和直接内存映射操作

结论

通过本文的详细讲解,您应该已经对C语言的文件I/O操作以及Linux操作系统中文件描述符和系统调用有了更深刻的理解。在C语言中,文件I/O操作主要通过FILE *指针和标准库函数来实现,而在Linux操作系统中,文件操作通过文件描述符和底层的系统调用接口进行。通过这些系统调用,程序能够直接与操作系统交互,完成文件的打开、读写和关闭等操作。

理解FILE *与文件描述符的区别、系统调用的工作原理,以及如何高效地进行文件操作,将有助于你在编程过程中处理更复杂的文件任务,并提高程序的性能。

本篇笔记:


感谢各位大佬支持,创作不易,还请各位大佬点赞支持!!!

;