20211406张顺扬

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

统计

学习笔记2

9.1~9.2 系统调用和I/O库函数

在操作系统中,进程以两种不同的方式运行:内核模式(Kmode)和用户模式(Umode)。用户模式的权限有限,某些特殊权限的操作需要在内核模式下进行。系统调用(System Call)机制允许进程进入内核模式,执行更高权限的操作。

系统调用和I/O库函数不同,但I/O库函数是基于系统调用构建的。例如,许多I/O库函数依赖于底层的系统调用,如fopen()依赖于open(),fread()依赖于read()。

常见系统调用及功能

  1. open():用于打开文件或创建新文件。可以指定文件名、访问模式(读、写、执行等)、文件权限等参数。返回一个文件描述符,用于后续文件操作。

  2. read():用于从已打开的文件中读取数据。需要提供文件描述符、读取位置和读取字节数等参数。

  3. write():用于向已打开的文件中写入数据。需要提供文件描述符、写入位置和写入字节数等参数。

  4. lseek():用于移动文件读写指针的位置。可以在文件中随机访问数据。

  5. close():用于关闭已打开的文件,释放文件描述符以及与文件相关的资源。

这些系统调用提供了对文件操作的底层支持。在其上,I/O库函数被构建,以提供更高级别的文件操作接口,使程序员更容易使用这些系统调用来进行文件处理。

例如,C标准库的文件函数,如fopen()fread()fwrite(),以及fclose(),都是建立在系统调用的基础上的。fopen()函数实际上使用open()系统调用来打开文件,而fread()fwrite()则使用read()write()系统调用来进行数据读写操作。

通过系统调用,用户程序可以与操作系统内核进行通信,并获得对底层资源的访问权限,这是编写系统级应用程序和操作系统的关键部分。

9.3 I/O库函数的算法

I/O库函数的算法主要包括fread算法、fwrite算法、以及fclose算法。

9.3.1 fread算法

1. 第一次调用fread()

  • 在第一次调用fread()时,FILE结构体的缓冲区是空的。
  • fread()使用保存的文件描述符fd发出一个n = read(fd, fbuffer, BLKSIZE)系统调用,用数据块填充内部的fbuf[]。
  • 然后,它会初始化fbuf[]的指针、计数器和状态变量,以表明内部缓冲区中有一个数据块。

2. 随后的fread()调用

  • 在随后的每次fread()调用中,它都尝试满足来自FILE结构体内部缓冲区的调用。
  • 当缓冲区变为空时,它就会再次发出read()系统调用来重新填充内部缓冲区。

9.3.2 fwrite()算法

  • fwrite()算法与fread()算法相似,不同之处在于数据传输方向不同。
  • 开始的时候,FILE结构体的内部缓冲区是空的。
  • 在每次调用fwrite()时,它将数据写入内部缓冲区,并调整缓冲区的指针、计数器和状态变量,以跟踪缓冲区中的字节数。
  • 如果缓冲区已满,则发出write()系统调用,将整个缓冲区写入操作系统内核。

9.3.3 fclose()算法

  • 若文件以写的方式被打开,fclose()会先关闭文件流的局部缓冲区。
  • 然后,它会发出一个close(fd)系统调用来关闭FILE结构体中的文件描述符。
  • 最后,它会释放FILE结构体,并将FILE指针重置为NULL。

这些算法描述了I/O库函数在内部如何操作,以及它们如何与底层系统调用交互。这对于理解文件读写的底层机制非常重要,尤其是在编写需要高效I/O操作的程序时。

9.4~9.5 使用I/O库函数、I/O库模式

对于以BLKSIZE为单位的读/写数据,使用系统调用比I/O库函数更高效。

I/O库模式中的字符模式

  • r:只读模式,文件必须存在,否则打开失败。
  • w:只写模式,若文件存在,则清除原文件内容后写入;否则,新建文件后写入。
  • a:追加模式。若文件存在,则位置指针移到文件末尾,在文件尾部追加写入。若文件不存在,则打开失败。
  • r+:表示读/写,不会截断文件。
  • w+:表示读/写,但是先截断文件,如果文件不存在,就先创建文件。
  • a+:表示通过追加的方式读/写;如果文件不存在就创建文件。

I/O库模式中的二进制模式

  • rb:只读模式,文件必须存在,否则打开失败。
  • wb:只写模式,若文件存在,则清除原文件内容后写入;否则,新建文件后写入。
  • ab:追加模式。若文件存在,则位置指针移到文件末尾,在文件尾部追加写入。若文件不存在,则打开失败。
  • rb+:表示读/写,不会截断文件。
  • wb+:表示读/写,但是先截断文件,如果文件不存在,就先创建文件。
  • ab+:表示通过追加的方式读/写;如果文件不存在就创建文件。

字符模式I/O

字符模式I/O以字符为单位存取,文件在流中。

  • 从文件指针中读取一个字符:int fgetc(FILE *fp);
  • 向文件流中退回一个字符: int ungetc(int c, FILE *fp);
  • 向文件流中存放一个字符:int fputc(int c, FILE *fp);

注意:读取到文件结束时,会返回一个值-1,将它与文件流中其他字符区分。同时应注意返回值的类型是整数。

行模式I/O

行模式I/O以文本文件的行为单位进行存取。

  • 读取一行:char *fgets(char *buf,int size, FILE *fp);
  • 写入一行:int fputs(char *buf, FILE *fp);

格式化I/O

格式化输入和格式化输出就是我们C语言课常用的scanf和printf类型,scanf和printf指定输入输出流为stdin和stdout,而fscanf和fprintf就可以指定输入流和输出流了。

格式化输入:

  • 从stdin输入:scanf(char *FMT,&items);
  • 从指定流输入:fscanf(fp,char *FMT, &items);

格式化输出:

  • 输出到stdout: printf(char *FMT,&items);
  • 输出到指定流:fprintf(fp,char *FMT, &items);

9.6 文件流缓冲

每个文件流都有一个FILE结构体,其中包含一个内部缓冲区,对文件流进行读写要遍历FILE缓冲区,文件缓冲可以使用三种缓冲方案的一种。

  • 无缓冲:尽快单独传输,如stderr就是通常无缓冲的。
  • 行缓冲:遇到换行符时进行缓冲,逐行输入输出,如stdout。
  • 全缓冲:以块大小传输到文件或者从文件传输,文件流常用的就是全缓冲。

9.7 变参函数

在学习C语言时,我们发现printf、scanf函数在传入参数时,参数的个数是不确定的,在学习本章后发现,原来它们都是变参函数。变参函数使用至少一个参数声明,后面跟三个点。如:int function(int m, int n . . . );

在函数内部,可以通过C语言库宏访问参数。







问题与解决思路

在进行文件I/O操作时,常常会面临各种问题,如文件不存在、权限问题、编码错误、内存消耗过大等。

文件不存在问题:

问题描述: 尝试打开或读取一个不存在的文件,导致FileNotFoundError。

解决方式:

  1. 在打开文件前,确保文件存在,可以使用os.path.exists()来检查。

  2. 使用try...except块来捕获FileNotFoundError并进行适当的错误处理。


import os

file_path = "example.txt"

if os.path.exists(file_path):
    with open(file_path, 'r') as file:
        content = file.read()
else:
    print(f"File '{file_path}' does not exist.")

实践过程

创建并写入文件:


 #include <stdio.h>

int main() {
    // 创建文件并打开以写入模式
    FILE *file = fopen("pro.txt", "w");

    // 检查文件是否成功打开
    if (file == NULL) {
        perror("Error opening file");
        return 1;
    }

    // 写入数据到文件
    fprintf(file, "Hello, world!\n");
    fprintf(file, "C program\n");

    // 关闭文件
    fclose(file);

    printf("已经成功写入\n");

    return 0;
}

读取文件内容:


 #include <stdio.h>

int main() {
    // 打开文件以读取模式
    FILE *file = fopen("pro.txt", "r");

    // 检查文件是否成功打开
    if (file == NULL) {
        perror("Error opening file");
        return 1;
    }

    // 读取文件内容并打印
    char buffer[100]; // 缓冲区
    while (fgets(buffer, sizeof(buffer), file) != NULL) {
        printf("%s", buffer); // 不打印多余的换行符
    }

    // 关闭文件
    fclose(file);

    return 0;
}

3. 追加内容到文件:

#include <stdio.h>

int main() {
    // 打开文件以追加模式
    FILE *file = fopen("pro.txt", "a");

    // 检查文件是否成功打开
    if (file == NULL) {
        perror("Error opening file");
        return 1;
    }

    // 写入数据到文件
    fprintf(file, "This line is appended.\n");
    printf("print successfully");
    // 关闭文件
    fclose(file);

    return 0;
}


posted on   20211406张顺扬  阅读(14)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示