嵌入式Linux:文件I/O和标准I/O库

2024-05-26 10:43:23 浏览数 (1)

文件 I/O (Input/Output)和标准 I/O 库是用于在 C 语言中进行文件操作的两种不同的方法。

1、文件I/O

文件 I/O(Input/Output)是指程序与文件之间进行数据交换的过程。在计算机编程中,文件 I/O 是通过读取和写入文件来实现数据的输入和输出操作。文件 I/O 主要涉及打开文件、读取文件内容、写入文件内容和关闭文件等操作。

常见的文件 I/O 操作包括使用系统调用(如 open()、read()、write()、close())来进行文件操作。通过文件 I/O,程序可以从文件中读取数据,对数据进行处理,然后将结果写入文件中,实现数据的持久化存储和处理。

在Linux系统中,一切皆文件是其核心设计理念之一,因此文件I/O操作在Linux系统中显得尤为重要。

1.1、文件描述符

文件描述符是操作系统中用于标识打开文件的整数值。它是进程与文件之间的桥梁,允许进程对文件进行读取、写入和其他操作。在Linux系统中,每个打开的文件都与一个文件描述符相关联,这个文件描述符是一个非负整数,通常是从0开始递增的。

文件描述符直接与操作系统的文件表项相关联,是操作系统提供的抽象。

举例来说,假设我们有一个C语言程序,打开了一个名为“example.txt”的文本文件进行读取。在这个程序中,文件描述符是用于表示这个打开的文件的整数值。当程序调用open函数打开文件时,操作系统会分配一个文件描述符,并将其返回给程序。程序可以使用这个文件描述符执行读取操作,如读取文件内容并将其输出到终端上。

代码语言:javascript复制
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
    int fd; // 文件描述符
    char buf[1024]; // 用于存储读取的数据

    // 打开文件 example.txt
    fd = open("example.txt", O_RDONLY);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    // 读取文件内容并输出到终端上
    ssize_t bytes_read;
    while ((bytes_read = read(fd, buf, sizeof(buf))) > 0) {
        write(STDOUT_FILENO, buf, bytes_read);
    }

    // 关闭文件
    close(fd);

    return 0;
}

在这个示例中,open函数打开文件example.txt并返回一个文件描述符,然后read函数使用这个文件描述符来从文件中读取数据。最后,close函数关闭文件,并释放对应的文件描述符。

1.2、open打开文件

在Linux系统中,操作文件需要先打开它以获取文件描述符,然后进行读写或其他操作,最后关闭文件。open函数可用于打开现有文件或创建新文件。函数原型如下所示:

代码语言:javascript复制
#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:字符串类型,用于标识需要打开或创建的文件。它可以包含路径信息,可以是绝对路径或相对路径,例如:"./src_file"(当前目录下的 src_file 文件)或 "/home/dengtao/hello.c"。如果 pathname 是一个符号链接,open 函数会对其进行解引用。
  • flags:调用 open 函数时需要提供的标志,包括文件访问模式标志以及其他文件相关标志。这些标志使用宏定义进行描述,并都是常量。open 函数提供了丰富的标志选项,我们可以单独使用某一个标志,也可以通过位或运算(|)将多个标志进行组合。
  • mode:用于指定新建文件的访问权限,仅在flags参数中包含O_CREATO_TMPFILE标志时有效。在Linux系统中,权限对于文件是一个重要的属性。我们可以使用touch命令在Linux系统中创建一个文件,此时文件会有默认的权限。如果需要修改文件权限,可以使用chmod命令进行修改。例如,在Linux系统下,我们可以使用ls -l命令查看文件对应的权限。
  • 返回值:成功将返回文件描述符,文件描述符是一个非负整数;失败将返回-1。

open函数的flags参数用于指定打开文件时的行为和权限。下面是一些常用的flags参数值:

  • O_RDONLY:只读方式打开文件。
  • O_WRONLY:只写方式打开文件。
  • O_RDWR:读写方式打开文件。
  • O_CREAT:如果文件不存在,则创建文件。
  • O_EXCL:与O_CREAT一同使用,如果文件已经存在,则返回错误。
  • O_TRUNC:如果文件存在且为只写或读写打开,则将其长度截断为0。
  • O_APPEND:追加方式打开文件,在写入数据时追加到文件末尾。
  • O_NONBLOCK:非阻塞方式打开文件,在没有数据可读取时不阻塞。
  • O_SYNC:同步写入方式打开文件,对写入文件的每个操作进行同步。
  • O_DIRECT:直接IO方式打开文件,绕过系统缓存,数据直接读写到磁盘。
  • O_TMPFILE:创建一个临时文件,文件在关闭时自动删除。

open函数的常用的mode参数:

  • S_IRUSR:文件所有者读权限。
  • S_IWUSR:文件所有者写权限。
  • S_IXUSR:文件所有者执行权限。
  • S_IRGRP:文件组用户读权限。
  • S_IWGRP:文件组用户写权限。
  • S_IXGRP:文件组用户执行权限。
  • S_IROTH:其他用户读权限。
  • S_IWOTH:其他用户写权限。
  • S_IXOTH:其他用户执行权限。

在应用程序中使用 open 函数时,需要包含 3 个头文件“#include <sys/types.h>”、“#include <sys/stat.h>”、“#include <fcntl.h>”。

下面是一个使用 open 函数的简单示例:

代码语言:javascript复制
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    const char *filename = "example.txt";
    int fd;

    // 使用 open 函数打开文件,如果文件不存在,则创建
    fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    // 向文件写入内容
    if (write(fd, "Hello, World!", 13) == -1) {
        perror("write");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // 关闭文件
    if (close(fd) == -1) {
        perror("close");
        exit(EXIT_FAILURE);
    }

    printf("File created and written successfully.n");

    return 0;
}

在这个示例中:

  • 我们首先定义了一个文件名 example.txt
  • 使用 open 函数打开文件,使用 O_WRONLY 标志表示以只写方式打开文件,O_CREAT 标志表示如果文件不存在则创建,O_TRUNC 标志表示如果文件存在则将其截断为空文件,最后一个参数 S_IRUSR | S_IWUSR 指定了新创建文件的权限为用户可读可写。
  • 如果 open 函数调用失败,会打印错误消息并退出程序。
  • 使用 write 函数向文件中写入内容。
  • 最后使用 close 函数关闭文件。

1.3、write写文件

write 函数用于将数据写入文件。其函数原型如下:

代码语言:javascript复制
#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);

函数的参数和返回值含义如下:

  • fd:文件描述符,代表要写入数据的文件。需要将要写入数据的文件对应的文件描述符传递给 write 函数。
  • buf:指定要写入数据的缓冲区。
  • count:指定要写入的字节数。
  • 返回值:成功时返回写入的字节数(0 表示未写入任何字节)。如果返回值小于 count 参数,这不一定是错误,例如磁盘空间已满可能导致未写入所有字节。如果写入出错,则返回 -1。

对于普通文件,无论是读取还是写入,一个关键问题是确定从文件的哪个位置开始进行操作。即所谓的I/O操作位置偏移量。读写操作都从文件的当前位置偏移量开始。默认情况下,当前位置偏移量通常是0,即指向文件的起始位置。随着read、write函数的调用,当前位置偏移量也会相应移动。例如,如果当前位置偏移量为1000字节,调用write()写入或read()读取500字节后,当前位置偏移量将移动到1500字节处。

使用 write 函数需要先包含 unistd.h 头文件。

下面是一个示例代码,将字符串写入文件:

代码语言:javascript复制
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>

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

    const char *message = "Hello, world!";
    ssize_t bytes_written = write(fd, message, strlen(message));
    if (bytes_written == -1) {
        perror("write");
        close(fd);
        return 1;
    }

    close(fd);
    printf("Data written successfully.n");
    return 0;
}

在此示例中,我们首先打开了一个文件 example.txt 以供写入,然后使用 write 函数将字符串 "Hello, world!" 写入文件中。

1.4、read读文件

调用 read 函数可从打开的文件中读取数据,其函数原型如下所示:

代码语言:javascript复制
#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);

函数参数和返回值含义如下:

  • fd:文件描述符,用于标识要读取的文件。
  • buf:用于存储读取数据的缓冲区。
  • count:需要读取的字节数。
  • 返回值:如果读取成功,返回读取到的字节数。实际读取到的字节数可能小于请求的字节数,也可能为0,例如当文件已到达末尾时。

使用 read 函数需要先包含 unistd.h 头文件。

例如,下面是一个简单的示例,从文件中读取数据:

代码语言:javascript复制
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

#define BUF_SIZE 1024

int main() {
    int fd;
    ssize_t bytes_read;
    char buffer[BUF_SIZE];

    // 打开文件
    fd = open("example.txt", O_RDONLY);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    // 读取文件内容
    bytes_read = read(fd, buffer, BUF_SIZE);
    if (bytes_read == -1) {
        perror("read");
        exit(EXIT_FAILURE);
    }

    // 输出读取的内容
    write(STDOUT_FILENO, buffer, bytes_read);

    // 关闭文件
    close(fd);

    return 0;
}

这个示例打开一个名为example.txt的文件,从中读取数据并将其写入标准输出。

1.5、close关闭文件

close 函数用于关闭一个已经打开的文件描述符,释放对应的资源。在Linux系统中,文件描述符是有限资源,因此在不再需要使用文件时,应该及时关闭,以释放资源并避免资源泄漏。

函数原型如下所示:

代码语言:javascript复制
#include <unistd.h>

int close(int fd);

函数参数和返回值含义如下:

  • fd:文件描述符,需要关闭的文件所对应的文件描述符。
  • 返回值:如果成功返回 0,如果失败则返回-1。

使用 close 函数需要先包含 <unistd.h> 头文件。

以下是一个简单的示例,演示如何使用 close 函数关闭文件:

代码语言:javascript复制
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

int main() {
    // 打开一个文件,获取文件描述符
    int fd = open("example.txt", O_RDONLY);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    // 关闭文件
    if (close(fd) == -1) {
        perror("close");
        exit(EXIT_FAILURE);
    }

    printf("File closed successfully.n");

    return 0;
}

在这个例子中,首先通过 open 函数打开了一个文件,然后使用 close 函数关闭了文件描述符。perror 函数用于打印出发生错误的详细信息。

除了使用 close 函数显式关闭文件之外,在 Linux 系统中,当一个进程终止时,内核会自动关闭它打开的所有文件。这意味着如果一个程序在退出时没有关闭已打开的文件,内核会代为关闭这些文件。许多程序都依赖于这一特性,因此没有显式地使用 close 函数来关闭文件。

然而,显式关闭不再需要的文件描述符通常是良好的编程习惯。这样做可以提高代码的可读性和可靠性,并确保在后续修改时代码的行为符合预期。此外,释放不再需要的文件描述符可以有效地管理有限的系统资源。

2、标准I/O库

标准I/O库是C语言中用于进行输入和输出操作的标准库之一。它提供了一组函数和数据结构,用于与文件、终端设备、管道等进行交互,使得程序可以方便地进行输入和输出操作,而无需直接操作文件描述符。

标准I/O库函数构建在文件I/O系统调用(如 open()read()write()lseek()close() 等)之上。例如,fopen() 利用 open() 系统调用打开文件,fread() 利用 read() 系统调用读取文件,fwrite() 利用 write() 系统调用写入文件等。

尽管标准I/O和文件I/O都是C语言函数,但它们有明显区别:

  • 标准 I/O 是标准 C 库函数,而文件 I/O 是 Linux 系统调用;
  • 标准 I/O 是文件 I/O 的封装,实际上调用文件 I/O 完成操作;
  • 可移植性方面,标准 I/O 更优,因为不同操作系统的系统调用接口不同,而标准 I/O 接口几乎相同;
  • 在性能和效率方面,标准 I/O 由于维护自己的缓冲区,性能更高,而文件 I/O 在用户空间无缓存。

标准I/O库通常包含在C标准库中,其函数和数据结构被定义在<stdio.h>头文件中。一些常用的标准I/O函数包括fopenfclosefreadfwritefprintffscanf等。所以使用时候需要在程序源码中包含<stdio.h>头文件。

标准I/O库的主要特点包括:

  • 缓冲机制:标准I/O库通常使用缓冲区来提高性能。例如,在输出时,数据首先写入到缓冲区,然后在适当的时机才会被刷新到实际的输出设备上,从而减少了系统调用的次数,提高了效率。
  • 格式化输入输出:标准I/O库提供了格式化输入输出的功能,例如printfscanf函数允许以特定格式输出和输入数据,使得数据的处理更加方便。
  • 文件操作:标准I/O库提供了一系列函数用于文件的打开、关闭、读取、写入等操作,例如fopenfclosefreadfwrite等。
  • 错误处理:标准I/O库提供了一套错误处理机制,允许程序员检测和处理输入输出操作中可能出现的错误情况。

使用标准I/O库可以使得程序更加可移植,因为它们提供了对底层系统调用的封装,使得程序不依赖于特定的操作系统或文件系统。因此,标准I/O库是C语言中进行文件操作和输入输出的主要方式之一。

2.1、FILE指针

标准I/O库函数操作围绕FILE指针展开。调用标准I/O库函数打开或创建文件时,返回一个指向FILE类型对象的指针(FILE *),该指针与被打开或创建的文件相关联,用于后续的标准I/O操作。因此,FILE指针在标准I/O库中扮演了与文件描述符类似的角色,但用于更高级别的操作。

FILE结构体包含了标准I/O库函数所需的所有文件管理信息,如文件描述符、文件缓冲区指针、缓冲区长度、当前缓冲区字节数以及出错标志等。

当使用标准I/O库函数打开或创建文件时,会返回一个指向FILE类型对象的指针,该指针与被打开或创建的文件相关联。下面是一个简单的示例:

代码语言:javascript复制
#include <stdio.h>

int main() {
    FILE *file_ptr;

    // 使用标准I/O库函数打开文件
    file_ptr = fopen("example.txt", "w");
    if (file_ptr == NULL) {
        printf("Failed to open file.n");
        return 1;
    }

    // 使用 FILE 指针进行写入操作
    fprintf(file_ptr, "Hello, world!n");

    // 关闭文件
    fclose(file_ptr);

    return 0;
}

在这个示例中,file_ptr指针与文件相关联,用于后续的标准I/O操作(写入操作)。通过FILE指针,我们可以方便地进行文件的读写操作,而不必直接操作文件描述符和底层的文件系统。

2.2、fopen打开文件

fopen() 是C语言标准库中用于打开文件的函数之一。它的原型如下:

代码语言:javascript复制
FILE *fopen(const char *filename, const char *mode);

函数参数和返回值含义如下:

  • path:参数 path 是一个指向文件路径的指针,可以是文件的绝对路径或相对路径。这个路径指定了要打开或创建的文件的位置和名称。
  • mode:参数 mode 是一个字符串,指定了对文件的读写权限。它描述了打开或创建文件时所需的操作类型。常见的模式包括:
    • "r":只读模式,用于打开一个已存在的文本文件,文件必须存在。
    • "w":写入模式,用于创建一个新的空文本文件,如果文件已存在,则删除其内容。
    • "a":追加模式,用于打开一个文本文件以便写入,如果文件不存在,则创建文件,文件指针被放在文件的末尾。
    • "r ":读写模式,用于打开一个文本文件用于读取和写入,文件必须存在。
    • "w ":读写模式,用于创建一个新的空文本文件用于读取和写入,如果文件已存在,则删除其内容。
    • "a ":读写模式,用于打开一个文本文件用于读取和写入,如果文件不存在,则创建文件,文件指针被放在文件的末尾。
  • 返回值:函数调用成功时,返回一个指向 FILE 类型对象的指针(FILE *),该指针与打开或创建的文件相关联。后续的标准 I/O 操作将围绕这个 FILE 指针进行。如果函数调用失败,则返回 NULL,并设置 errno 以指示错误原因。

以下是一个简单的示例,演示了如何使用 fopen() 打开文件:

代码语言:javascript复制
#include <stdio.h>

int main() {
    FILE *file_ptr;

    // 以只读模式打开文件
    file_ptr = fopen("example.txt", "r");
    if (file_ptr == NULL) {
        printf("Failed to open file.n");
        return 1;
    }

    // 在这里可以进行文件读取操作

    // 关闭文件
    fclose(file_ptr);

    return 0;
}

在这个示例中,我们尝试以只读模式打开名为 example.txt 的文件。如果文件打开失败,则会打印一条消息并退出程序。否则,我们可以在之后的代码中对文件进行读取操作。最后,我们使用 fclose() 函数关闭文件,释放资源。

2.3、fwrite写文件

fwrite() 是C语言标准库中用于向文件写入数据的函数之一。它的原型如下:

代码语言:javascript复制
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

函数参数和返回值含义如下:

  • ptr:参数 ptr 是一个指向缓冲区的指针,该缓冲区中存储了要写入到文件中的数据。函数将会把这个缓冲区中的数据写入到文件中。
  • size:参数 size 指定了每个数据项的字节大小,即每次写入的数据的大小。
  • nmemb:参数 nmemb 指定了写入的数据项的个数,即要写入到文件中的数据项的数量。
  • stream:参数 stream 是一个指向 FILE 结构的指针,它标识了要写入数据的文件。
  • 返回值:调用成功时,fwrite() 函数返回实际成功写入到文件中的数据项的数目。如果发生错误,则返回值可能小于参数 nmemb(或者等于 0)。

fwrite() 函数返回成功写入的数据项数目,如果返回值与 nmemb 不同,则表示写入出现了错误。

以下是一个简单的示例,演示了如何使用 fwrite() 向文件写入数据:

代码语言:javascript复制
#include <stdio.h>

int main() {
    FILE *file_ptr;
    char buffer[] = "Hello, world!";

    // 打开文件以便写入
    file_ptr = fopen("example.txt", "w");
    if (file_ptr == NULL) {
        printf("Failed to open file.n");
        return 1;
    }

    // 向文件写入数据
    size_t num_written = fwrite(buffer, sizeof(char), sizeof(buffer), file_ptr);
    if (num_written != sizeof(buffer)) {
        printf("Failed to write to file.n");
        fclose(file_ptr);
        return 1;
    }

    // 关闭文件
    fclose(file_ptr);

    return 0;
}

在这个示例中,我们向文件 "example.txt" 写入了字符串 "Hello, world!"。首先我们打开文件以便写入,然后使用 fwrite() 函数将数据写入文件,最后关闭文件。

2.4、fread读文件

fread() 是C语言标准库中用于从文件读取数据的函数之一。它的原型如下:

代码语言:javascript复制
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

函数参数和返回值含义如下:

  • ptrfread() 函数将读取到的数据存放在参数 ptr 指向的缓冲区中。这个缓冲区是用来存储从文件中读取的数据的。
  • sizefread() 函数从文件读取 nmemb 个数据项,每个数据项的大小为 size 个字节。因此,总共读取的数据大小为 nmemb * size 个字节。
  • nmemb:参数 nmemb 指定了要读取的数据项的个数。
  • stream:参数 stream 是一个指向 FILE 结构的指针,它标识了要从中读取数据的文件。
  • 返回值:调用成功时,fread() 函数返回成功读取到的数据项的数目。如果发生错误或到达文件末尾,则返回值可能小于参数 nmemb。由于 fread() 无法区分文件结尾和错误,返回值小于 nmemb 时,可以使用 ferror()feof() 函数来进一步判断是发生了错误还是已经到达了文件末尾。

fread() 函数返回成功读取的数据项数目,如果返回值与 nmemb 不同,则表示读取出现了错误。

以下是一个简单的示例,演示了如何使用 fread() 从文件中读取数据:

代码语言:javascript复制
#include <stdio.h>

int main() {
    FILE *file_ptr;
    char buffer[100]; // 缓冲区用于存储读取的数据

    // 打开文件以便读取
    file_ptr = fopen("example.txt", "r");
    if (file_ptr == NULL) {
        printf("Failed to open file.n");
        return 1;
    }

    // 从文件读取数据
    size_t num_read = fread(buffer, sizeof(char), sizeof(buffer), file_ptr);
    if (num_read == 0) {
        printf("Failed to read from file.n");
        fclose(file_ptr);
        return 1;
    }

    // 输出读取的数据
    printf("Read from file: %sn", buffer);

    // 关闭文件
    fclose(file_ptr);

    return 0;
}

在这个示例中,我们打开了一个名为 "example.txt" 的文件以便读取。我们使用 fread() 函数从文件中读取数据,并将其存储在名为 buffer 的缓冲区中。最后,我们打印出读取到的数据,并关闭文件。

2.5、fclose关闭文件

fclose() 是C语言标准库中用于关闭文件的函数之一。它的原型如下:

代码语言:javascript复制
int fclose(FILE *stream);

函数参数和返回值含义如下:

  • stream:指向 FILE 结构的指针,标识要关闭的文件。
  • 返回值:调用成功返回 0;失败将返回 EOF(也就是-1)。

以下是一个示例,演示了如何使用 fclose() 关闭文件:

代码语言:javascript复制
#include <stdio.h>

int main() {
    FILE *file_ptr;

    // 打开文件以便读取
    file_ptr = fopen("example.txt", "r");
    if (file_ptr == NULL) {
        printf("Failed to open file.n");
        return 1;
    }

    // 在这里可以进行文件读取操作...

    // 关闭文件
    if (fclose(file_ptr) != 0) {
        printf("Failed to close file.n");
        return 1;
    }

    return 0;
}

在这个示例中,我们打开了一个名为 "example.txt" 的文件以便读取。在文件读取操作完成后,我们使用 fclose() 函数关闭了文件。如果关闭文件失败,则会打印一条错误消息并退出程序。

0 人点赞