前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >嵌入式Linux:文件I/O和标准I/O库

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

作者头像
不脱发的程序猿
发布2024-05-26 10:43:23
1140
发布2024-05-26 10:43:23
举报
文章被收录于专栏:AIoT技术交流、分享

文件 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: %s\n", 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() 函数关闭了文件。如果关闭文件失败,则会打印一条错误消息并退出程序。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-05-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、文件I/O
    • 1.1、文件描述符
      • 1.2、open打开文件
        • 1.3、write写文件
          • 1.4、read读文件
            • 1.5、close关闭文件
            • 2、标准I/O库
              • 2.1、FILE指针
                • 2.2、fopen打开文件
                  • 2.3、fwrite写文件
                    • 2.4、fread读文件
                      • 2.5、fclose关闭文件
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档