嵌入式软件linux开发(第二课_文件I/O)

C语言文件操作与系统调用学习笔记

1. C标准I/O库函数回顾

1.1 打开/关闭文件

1.1.1 fopen函数

fopen函数用于打开文件,其原型为:

FILE *fopen(const char *restrict filename, const char *restrict mode);
  • 参数

    • filename:要打开文件的路径和名称。
    • mode:访问模式,包括:
      • "r":只读模式,文件必须存在。
      • "w":只写模式,文件存在则清空,不存在则创建。
      • "a":只追加写模式,文件不存在则创建。
      • "r+":读写模式,文件必须存在。
      • "w+":读写模式,文件存在则清空,不存在则创建。
      • "a+":读写追加模式,文件不存在则创建。
  • 返回值:成功返回FILE结构体指针,失败返回NULL

示例代码

#include <stdio.h>

int main() {
    char *filename = "io.txt";
    FILE *ioFile = fopen(filename, "a+");
    if (ioFile == NULL) {
        printf("FAILED,a+不能打开不存在的文件\n");
    } else {
        printf("SUCCESS,a+能打开不存在的文件\n");
    }
}
1.1.2 fclose函数

fclose函数用于关闭文件,其原型为:

int fclose(FILE *stream);
  • 参数stream,需要关闭的文件。

  • 返回值:成功返回0,失败返回EOF(负数)。

示例代码

#include <stdio.h>

int main() {
    char *filename = "io1.txt";
    FILE *ioFile = fopen(filename, "r");
    if (ioFile == NULL) {
        printf("r不能打开不存在的文件\n");
    } else {
        printf("r能打开不存在的文件\n");
    }

    int result = fclose(ioFile);
    if (result != 0) {
        printf("关闭文件失败");
        return 1;
    }

    return 0;
}

1.2 向文件中写入数据

1.2.1 fputc函数

fputc函数用于向文件写入单个字符,其原型为:

int fputc(int c, FILE *stream);
  • 参数

    • c:要写入的字符(按ASCII值)。
    • stream:要写入的文件。
  • 返回值:成功返回字符的值,失败返回EOF

示例代码

#include <stdio.h>

int main() {
    char *filename = "io.txt";
    FILE *ioFile = fopen(filename, "a+");
    if (ioFile == NULL) {
        printf("a+不能打开不存在的文件\n");
    } else {
        printf("a+能打开不存在的文件\n");
    }

    int putcR = fputc(97, ioFile);
    if (putcR == EOF) {
        printf("写入字符失败\n");
    } else {
        printf("写入字符成功:%c\n", putcR);
    }

    int result = fclose(ioFile);
    if (result != 0) {
        printf("关闭文件失败");
        return 1;
    }

    return 0;
}

1.2.2 fputs函数

fputs函数用于向文件写入字符串,其原型为:

int fputs(const char *restrict s, FILE *restrict stream);
  • 参数

    • s:需要写入的字符串。
    • stream:要写入的文件。
  • 返回值:成功返回非负整数(一般是0或1),失败返回EOF

示例代码

#include <stdio.h>

int main() {
    char *filename = "io.txt";
    FILE *ioFile = fopen(filename, "a+");
    if (ioFile == NULL) {
        printf("a+不能打开不存在的文件\n");
    } else {
        printf("a+能打开不存在的文件\n");
    }

    int putsR = fputs(" love letter\n", ioFile);
    if (putsR == EOF) {
        printf("写入字符串失败\n");
    } else {
        printf("写入字符串成功:%d\n", putsR);
    }

    int result = fclose(ioFile);
    if (result != 0) {
        printf("关闭文件失败");
        return 1;
    }

    return 0;
}

1.2.3 fprintf函数

fprintf函数用于格式化输出到文件,其原型为:

int fprintf(FILE *restrict stream, const char *restrict format, ...);
  • 参数

    • stream:要写入的文件。
    • format:格式化字符串。
    • ...:变长参数列表,用于匹配格式化字符串中的占位符。
  • 返回值:成功返回写入字符总数(不包含换行符),失败返回EOF

示例代码

#include <stdio.h>

int main() {
    char *filename = "io.txt";
    FILE *ioFile = fopen(filename, "a+");
    if (ioFile == NULL) {
        printf("a+不能打开不存在的文件\n");
    } else {
        printf("a+能打开不存在的文件\n");
    }

    char *name = "大海";
    int fprintfR = fprintf(ioFile, "哎呀,那边窗户透出了什么光?\n那是东方,而你则是太阳!\n升起吧,骄阳,去让忌妒的月黯然失色!\n\t\t%s", name);
    if (fprintfR == EOF) {
        printf("写入字符串失败");
    } else {
        printf("写入字符串成功:%d\n", fprintfR);
    }

    int result = fclose(ioFile);
    if (result != 0) {
        printf("关闭文件失败");
        fprintf(stderr, "%s\n", filename);
        return 1;
    }

    return 0;
}

1.3 从文件中读取数据

1.3.1 fgetc函数

fgetc函数用于从文件读取单个字符,其原型为:

int fgetc(FILE *stream);
  • 参数stream,需要读取的文件。

  • 返回值:读取的一个字节,到文件结尾或出错返回EOF

示例代码

#include <stdio.h>

int main() {
    FILE *ioFile = fopen("io.txt", "r");
    if (ioFile == NULL) {
        printf("不能读不存在的文件");
    }

    char c = fgetc(ioFile);
    while (c != EOF) {
        printf("%c", c);
        c = fgetc(ioFile);
    }

    int result = fclose(ioFile);
    if (result != 0) {
        printf("关闭文件失败");
        return 1;
    }

    return 0;
}
1.3.2 fgets函数

fgets函数用于从文件读取字符串,其原型为:

char *fgets(char *restrict s, int n, FILE *restrict stream);
  • 参数

    • s:接收读取的数据字符串。
    • n:能够接收数据的长度。
    • stream:需要读取的文件。
  • 返回值:成功返回字符串,失败返回NULL(可以直接用于while循环)。

示例代码

#include <stdio.h>

int main() {
    FILE *ioFile = fopen("io.txt", "r");
    if (ioFile == NULL) {
        printf("不能读不存在的文件");
    }

    char buffer[100];
    while (fgets(buffer, sizeof(buffer), ioFile)) {
        printf("%s", buffer);
    }

    int result = fclose(ioFile);
    if (result != 0) {
        printf("关闭文件失败");
        return 1;
    }

    return 0;
}
1.3.3 fscanf函数

fscanf函数用于从文件读取格式化数据,其原型为:

int fscanf(FILE *restrict stream, const char *restrict format, ...);
  • 参数

    • stream:读取的文件。
    • format:读取的匹配表达式。
    • ...:变长参数列表,用于接收匹配的数据。
  • 返回值:成功返回参数的个数,失败返回0,报错或结束返回EOF

示例代码

#include <stdio.h>

int main() {
    FILE *userFile = fopen("user.txt", "r");
    if (userFile == NULL) {
        printf("不能打开不存在的文件");
    }

    char name[50];
    int age;
    char wife[50];
    int scanfR;
    while (scanfR = fscanf(userFile, "%s %d %s\n", name, &age, wife) != EOF) {
        printf("%s在%d岁爱上了%s\n", name, age, wife);
    }

    int result = fclose(userFile);
    if (result != 0) {
        printf("关闭文件失败");
        return 1;
    }

    return 0;
}

1.4 标准输入/输出/错误

1.4.1 标准输入/输出/错误简介

在C语言中,标准输入(stdin)、标准输出(stdout)和标准错误(stderr)是三种特殊的文件流,分别用于从控制台读取输入、向控制台输出数据和发送错误消息。

  • stdin:标准输入文件流,文件描述符为0。
  • stdout:标准输出文件流,文件描述符为1。
  • stderr:标准错误文件流,文件描述符为2。

1.4.2 示例代码

stdin_out_err_test.c

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char const *argv[]) {
    char *ch = malloc(100);
    fgets(ch, 100, stdin);
    printf("你好:%s", ch);
    fputs(ch, stdout);
    fputs(ch, stderr);
    free(ch);
    return 0;
}

在这个示例中,程序从标准输入读取一行数据,然后分别通过标准输出和标准错误输出这行数据。

1.5 系统调用

1.5.1 系统调用简介

系统调用是操作系统内核提供给应用程序的接口,使得应用程序能够间接访问硬件资源。以下是一些常见的系统调用:

1. open

open系统调用用于打开一个文件,并返回一个文件描述符。

int open(const char *__path, int __oflag, ...);
  • 参数

    • __path:文件路径。
    • __oflag:打开文件的方式,可以是只读、只写、读写等模式的组合。
    • ...:可选参数,如文件权限位(仅在创建新文件时使用)。
  • 返回值:成功返回非负的文件描述符,失败返回-1。

2. read

read系统调用用于从文件描述符读取数据。

ssize_t read(int __fd, void *__buf, size_t __nbytes);
  • 参数

    • __fd:文件描述符。
    • __buf:缓冲区指针,用于存放读取的数据。
    • __nbytes:要读取的最大字节数。
  • 返回值:成功返回实际读取的字节数,失败返回-1。

3. write

write系统调用用于向文件描述符写入数据。

ssize_t write(int __fd, const void *__buf, size_t __n);
  • 参数

    • __fd:文件描述符。
    • __buf:缓冲区指针,存放要写入的数据。
    • __n:要写入的字节数。
  • 返回值:成功返回实际写入的字节数,失败返回-1。

4. close

close系统调用用于关闭文件描述符。

int close(int __fd);
  • 参数__fd,要关闭的文件描述符。

  • 返回值:成功关闭返回0,失败返回-1。

5. exit和_exit
  • _exit:立即终止当前进程,不执行任何清理操作。
  • exit:终止当前进程,并执行清理操作,如刷新I/O缓冲区、关闭打开的文件等。

1.5.2 综合案例

system_call_test.c

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

int main(int argc, char const *argv[]) {
    int fd = open("io.txt", O_RDONLY);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }
    char buffer[1024];
    ssize_t bytes_read;
    while ((bytes_read = read(fd, buffer, sizeof(buffer))) > 0) {
        write(STDOUT_FILENO, buffer, bytes_read);
    }
    if (bytes_read == -1) {
        perror("read");
        close(fd);
        exit(EXIT_FAILURE);
    }
    close(fd);
    return 0;
}

在这个综合案例中,程序使用open系统调用打开一个文件,然后使用read系统调读取数据,并使用write系统调用将数据写入标准输出。最后,使用close系统调用关闭文件描述符。

1.6 文件描述符

1.6.1 文件描述符定义

在Linux系统中,文件描述符(File Descriptor,FD)是一个非负整数,用于标识打开的文件(或套接字)。它是操作系统为应用程序操作底层资源所提供的引用或“句柄”。

1.6.2 文件描述符的特殊含义

在Linux中,文件描述符0、1、2具有特殊含义:

  • 0:标准输入(stdin)
  • 1:标准输出(stdout)
  • 2:标准错误(stderr)

1.6.3 文件描述符关联的数据结构

struct file

每个文件描述符都关联到内核的一个struct file类型的结构体数据,记录了与文件相关的所有信息。

  • 关键字段
    • f_count:引用计数,管理文件对象的生命周期。
    • f_pos:当前文件位置(读写位置)。
    • f_path:记录文件路径。
    • f_inode:指向与文件相关联的inode对象的指针,用于维护文件元数据。
    • f_op:指向文件操作函数表的指针,定义了文件支持的操作。
struct path

struct path结构体包含了文件路径的信息,由struct vfsmountstruct dentry组成。

  • struct vfsmount:虚拟文件系统挂载点的表示。
  • struct dentry:目录项结构体,代表了文件系统中的一个目录项。
struct inode

struct inode结构体记录了文件的元数据,如文件类型、访问权限等。

  • 关键字段
    • i_mode:文件类型和权限。
    • i_uid:文件的用户ID。
    • i_gid:文件的组ID。
    • i_ino:inode编号,文件系统中文件的唯一标识。
    • i_size:文件大小。

1.6.4 文件描述符表关联的数据结构

struct files_struct

用于维护一个进程中所有打开文件信息。

  • 关键字段
    • fdt:指向当前使用的文件描述符表(fdtable)。
    • next_fd:存储下一个可用的最小文件描述符编号。
    • fd_array:struct file指针的数组,用于快速访问。
struct fdtable

打开文件描述符表底层的数据结构。

  • 关键字段
    • max_fds:文件描述符数组的容量。
    • fd:指向struct file指针数组的指针。
    • close_on_execopen_fdsfull_fds_bits:管理文件描述符的状态。

1.6.5 文件描述符和fd或fd_array的关系

文件描述符的值实际上是其关联的struct filefd指向的数组或fd_array中的下标。

1.7 总结与实际应用

1.7.1 总结

通过本文档的学习,我们深入了解了C语言中文件操作的基本概念和系统调用的工作原理。我们学习了如何使用标准I/O库函数进行文件的打开、关闭、读写操作,以及如何使用系统调用来直接操作文件描述符。此外,我们还探讨了文件描述符背后的数据结构,包括struct filestruct pathstruct inode以及文件描述符表struct files_structstruct fdtable

1.7.2 实际应用示例

示例1:使用系统调用来读取文件内容

以下是一个使用系统调用来读取文件内容并打印到标准输出的示例代码:

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

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

    char buffer[1024];
    ssize_t bytesRead;
    while ((bytesRead = read(fd, buffer, sizeof(buffer))) > 0) {
        write(STDOUT_FILENO, buffer, bytesRead);
    }

    if (bytesRead == -1) {
        perror("Failed to read file");
        close(fd);
        return EXIT_FAILURE;
    }

    close(fd);
    return EXIT_SUCCESS;
}

在这个示例中,我们使用open系统调打开文件,read系统调读取文件内容,并使用write系统调将内容写入标准输出。最后,我们使用close系统调关闭文件描述符。

示例2:使用标准I/O库函数处理文件

以下是一个使用标准I/O库函数来复制文件内容的示例代码:

#include <stdio.h>
#include <stdlib.h>

int main() {
    FILE *srcFile = fopen("source.txt", "r");
    FILE *destFile = fopen("destination.txt", "w");
    if (srcFile == NULL || destFile == NULL) {
        perror("Failed to open files");
        return EXIT_FAILURE;
    }

    char buffer[1024];
    size_t bytesRead;
    while ((bytesRead = fread(buffer, 1, sizeof(buffer), srcFile)) > 0) {
        fwrite(buffer, 1, bytesRead, destFile);
    }

    if (ferror(srcFile) || ferror(destFile)) {
        perror("Error reading or writing files");
        fclose(srcFile);
        fclose(destFile);
        return EXIT_FAILURE;
    }

    fclose(srcFile);
    fclose(destFile);
    return EXIT_SUCCESS;
}

在这个示例中,我们使用fopen函数打开源文件和目标文件,fread函数从源文件读取数据,并使用fwrite函数将数据写入目标文件。最后,我们使用fclose函数关闭文件。

1.7.3 进一步学习

对于想要进一步深入学习文件操作和系统调用的读者,建议阅读以下资源:

  • 《UNIX环境高级编程》(Advanced Programming in the UNIX Environment):这本书详细介绍了UNIX系统下的文件操作和系统调用。
  • Linux内核源码:直接查看Linux内核源码,了解文件描述符和相关数据结构的实现细节。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值