Linux从0到1——基础IO(上)【文件描述符/重定向/缓冲区】

news2024/9/30 19:35:32

Linux从0到1——基础IO(上)

  • 1. 预备知识
  • 2. 复习一下常见的C语言文件接口
  • 3. 系统调用接口
    • 3.1 函数传参小技巧——标志位
    • 3.2 使用系统调用接口
      • 3.2.1 open
      • 3.2.2 write
      • 3.2.3 read
  • 4. 文件描述符fd
    • 4.1 fd的本质
    • 4.2 理解struct file结构体
    • 4.3 fd的分配规则
  • 5. 重定向
    • 5.1 引入
    • 5.2 一般的重定向写法——配合函数dup2
    • 5.3 stderr的意义
  • 6. 缓冲区
    • 6.1 预备知识
    • 6.2 看一个样例
    • 6.3 用户缓冲区VS内核缓冲区
    • 6.4 验证缓冲区的存在


1. 预备知识


1. 文件 = 内容 + 属性:

  • 所有对文件的操作都可以分为两种:a. 对内容操作 b. 对属性操作;
  • 内容是数据,属性其实也是数据。存储文件,必须即存储内容又存储数据。这里指的文件默认就是在磁盘中的文件;
  • 进程要访问一个文件时,都是要先把这个文件打开的:
    • 打开前:这个文件就是普通的磁盘文件;
    • 打开后:就是将磁盘文件加载到内存。

2. 一个进程可以打开多个文件吗?多个进程可以打开多个文件吗?

  • 一个进程可以打开多个文件,多个进程可以打开多个文件。所以加载到内存中,被打开的文件可能会存在多个。
  • 既然操作系统在运行时,可能会打开多个文件,那么操作系统一定要对这些文件进行管理——先描述,再组织。
  • 我们大胆猜测一下,一个文件要被打开,一定要先在内核中形成被打开的文件对象(结构体),这些对象又可以通过一定的方式链接起来(链表)。

在这里插入图片描述

3. 文件按照是否被打开,分为:被打开的文件、没有被打开的文件

  • 被打开的文件,存在于内存中;
  • 没有被打开的文件,存在于磁盘中。

4. 本次研究文件操作的本质是:研究进程和被打开文件之间的关系。


2. 复习一下常见的C语言文件接口


1. fopen:

在这里插入图片描述
2. fputs:

在这里插入图片描述

3. 代码实践:

#include<stdio.h>

int main()
{
    // "w": 按照写方式打开,如果文件不存在就创建它,并且每次打开都会清空文件内容
    // "r": 按照只读的方式打开,文件不存在直接报错
    // "a": 按照追加方式打开,如果文件不存在就创建它,每次打开不会清空文件内容,会在文件结尾处写入
    FILE *fp = fopen("log.txt", "w");
    if (NULL == fp)
    {
        perror("fopen");
        return 1;
    }

    const char *msg = "hello Linux file\n";
    fputs(msg, fp);     // 像文件中写入字符串

    fclose(fp);

    return 0;
}

3. 系统调用接口

  • 进程打开文件的说法是不准确的,准确的说法应该是,进程通过操作系统打开文件。所以上层的fopenfread等函数在底层一定封装了系统调用接口。

3.1 函数传参小技巧——标志位


1. 先写一段代码:

#include<stdio.h>

#define Print1 1         // 0001
#define Print2 (1<<1)   // 0010
#define Print3 (1<<2)   // 0100
#define Print4 (1<<3)   // 1000

void Print(int flags)
{
    if (flags&Print1) printf("hello 1 ");
    if (flags&Print2) printf("hello 2 ");
    if (flags&Print3) printf("hello 3 ");
    if (flags&Print4) printf("hello 4 ");
    printf("\n");
}

int main()
{
    Print(Print1);
    Print(Print1|Print2);
    Print(Print1|Print2|Print3);
    Print(Print3|Print4);
    Print(Print4);

    return 0;
}

2. 编译并运行:

在这里插入图片描述

3. 解释:

  • 对于Print函数来说,它只有一个参数flags,这个参数是标记位;
  • flags一共有32个比特位,这里我们只使用四个比特位,也就是只有四个选项;
  • 定义了四个宏Print*,也是四个选项,他们对应的二进制信息已在代码中写出,通过|的方式,将这些选项组合起来,传给Print函数;
  • Print函数内部,通过if (flags&Print*)的方式,可以判断对应的选项是否传入(对应比特位是否是1),如果传入了,就执行该条if后的代码;
  • 上面说的选项,也叫标志位。flags就是各种标记位的组合。

3.2 使用系统调用接口


3.2.1 open


1. 查看man手册:

在这里插入图片描述

  • flags参数就是各种标志位的组合;
  • pathname就是文件路径;
  • 返回值是int类型的数据,是打开文件的文件描述符fd,关于这个文件描述符,我们后面再详细讲,这里只需要知道,文件描述符的使用方式和C语言接口中的FILE*文件指针一样即可;
  • open失败时,会返回-1,同时错误码被设置;
  • 可以看到open接口有两个,第二个还有一个参数mode,需要我们以8进制形式传入权限。

2. flags对应选项:

在这里插入图片描述

  • O_WRONLY:以只写方式打开;
  • O_CREAT:以只写方式打开时,如果文件不存在,就创建它;
  • O_TRUNC:以只写方式打开时,清空文件内容;
  • O_APPEND:以只写方式打开时,不清空文件,在文件末尾追加内容。

3. 代码实例:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

int main()
{
    // 打开已经存在的文件,不需要带权限
    // 打开不存在的文件,需要带权限;如果打开不存在的文件,不带权限,那么这个新文件的权限是乱码
    // 权限以8进制方案传入
    int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);
    if (fd < 0)
    {
        perror("open");
        return 1;
    }

    // close也是一个系统调用,用于关闭文件,头文件是unistd.h
    // 参数是fd
    close(fd);  

    return 0;
}
  • 编译并运行:

在这里插入图片描述

  • 问题:为什么我们设置的权限是666,可是创建的log.txt权限却是664?

4. 关于权限:

  • 如果我们打开一个不存在的文件,还不传权限,那么它的文件描述符是乱码。
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

int main()
{
    int fd = open("log.txt", O_WRONLY | O_CREAT);	// 不带权限
    if (fd < 0)
    {
        perror("open");
        return 1;
    }

    close(fd);  

    return 0;
}

在这里插入图片描述

  • 关于3中的问题,答案是有系统默认的权限掩码存在,默认是0002,将对应的权限过滤掉了。我们可以通过umask函数来重新设置权限掩码:

在这里插入图片描述

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

int main()
{
    umask(0);   // 重新设置权限掩码
    int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);
    if (fd < 0)
    {
        perror("open");
        return 1;
    }

    // close也是一个系统调用,用于关闭文件,头文件是unistd.h
    // 参数是fd
    close(fd);  

    return 0;
}

在这里插入图片描述

  • 注意:不建议使用上面这种方式重新设置权限掩码,尽量和系统默认权限掩码保持一致。

5. close接口:

  • 关闭哪个文件,就传对应文件的文件描述符即可。

在这里插入图片描述


3.2.2 write


1. 查看man手册:

在这里插入图片描述

2. 代码实例:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>

int main()
{
    int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);
    if (fd < 0)
    {
        perror("open");
        return 1;
    }

    const char *msg = "hello file system call\n";

    // 操作文件
    write(fd, msg, strlen(msg)); // 要不要传 strlen(msg) + 1,将'\0'也传进去?
    // 第三个参数不要传 strlen(msg) + 1,'\0'只是C语言层面的概念,不是文件层面的概念
    // '\0'传进文件,会出现乱码

    close(fd);  

    return 0;
}
  • O_WRONLY | O_CREAT的方式打开文件,write默认是覆盖式写入。比如文件中原本有内容aaaa,如果再向文件中写入bb,文件内容就会变为bbaa

3. fopen以w方式打开文件的底层:

int main()
{
    int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if (fd < 0)
    {
        perror("open");
        return 1;
    }

    close(fd);  

    return 0;
}

4. fopen以a方式打开文件的底层:

int main()
{
    int fd = open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);
    if (fd < 0)
    {
        perror("open");
        return 1;
    }

    close(fd);  

    return 0;
}

3.2.3 read


1. 查看man手册:

  • fd:要读取文件的文件描述符;
  • buf:用户自定义的一块空间(缓冲区);
  • count:缓冲区总大小;

在这里插入图片描述

2. 代码实例:

int main()
{
    int fd = open("log.txt", O_RDONLY);
    if (fd < 0)
    {
        perror("open");
        return 1;
    }

    char buffer[1024];
    read(fd, buffer, 1024);
    printf("%s\n", buffer);
    close(fd);

    return 0;
}

在这里插入图片描述


4. 文件描述符fd


4.1 fd的本质


1. 文件描述符fd的本质,就是数组下标:

在这里插入图片描述

  • 操作系统会为打开的文件创建一个结构体struct file来描述它,然后通过链表的方式将多个打开的文件组织起来;
  • 进程PCB中会有一个struct files_struct *files指针,指向该进程管理打开文件的结构体struct files_struct。其中有一个成员为struct file *fd_array[]指针数组,每一个位置对应一个打开文件的结构体对象struct file
  • 文件描述符fd,其实就是struct file *fd_array[]数组的下标,所以只要拿到对应的文件描述符(数组下标),就可以找到对应的文件;
  • C/C++程序在运行时,会默认打开三个文件,标准输入流stdin,标准输出流stdout,和标准错误流stderr。这三个文件分别对应的硬件设备为,键盘、显示器、显示器。文件描述符数组的0, 1, 2位置,分别对应这几个文件。

2. FILE*到底是什么?

在这里插入图片描述

  • stdinstdoutstderr的都是FILE*类型的指针。

在这里插入图片描述

  • FILE其实就是C语言中提供的一个结构体类型,我们可以大胆猜测一下,FILE的内部必定封装了文件描述符。

关于FILE现在没有办法讲太多。

3. 代码验证:

int main()
{
    int fda = open("loga.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);
    int fdb = open("logb.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);
    int fdc = open("logc.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);
    int fdd = open("logd.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);

    printf("stdin->fd: %d\n", stdin->_fileno);	// 这个_fileno成员就是文件描述符
    printf("stdout->fd: %d\n", stdout->_fileno);
    printf("stderr->fd:%d \n", stderr->_fileno);
    printf("fda: %d\n", fda);
    printf("fdb: %d\n", fdb);
    printf("fdc: %d\n", fdc);
    printf("fdd: %d\n", fdd);

    close(fda);
    close(fdb);
    close(fdc);
    close(fdd);

    return 0;
}

在这里插入图片描述

  • 可以发现,文件描述符的分配是有一定规律的。

4. 如何理解一切皆文件?

在这里插入图片描述

  • 底层的很多硬件,大多都有两个基本的功能,输入和输出。但是它们的输入和输出方法是不同的;
  • 但是在上层,我们想通过一切皆文件的方式去管理底层不同的硬件,是如何做到的?
  • 比如此时打开一个磁盘文件,OS在上层就会为磁盘文件创建一个struct file对象。里面有两个很重要的内容就是读方法和写方法的指针,指向磁盘文件具体的读写方法;
  • 从此往后,我们再调用磁盘的读写方法时,不用关心底层是如何实现的,直接调用struct file对象中的方法即可,一切皆文件;
  • 这种封装的方式,可以让我们自然联想到C++中的继承和多态。

4.2 理解struct file结构体


在这里插入图片描述

struct file结构体中,必定要存储两个信息:a. 文件的属性 b. 文件的内容。

对文件的操作无非就分为两种,一种是读,一种是写。但是无论读写,都需要先将磁盘中的文件数据加载到文件缓冲区中。

我们在应用层进行的数据读写,本质是将内核缓冲区中的数据进行来回拷贝。


4.3 fd的分配规则


1. 进程默认已经打开了fd为0,1,2的三个文件,我们可以通过0,1,2直接访问:

  • 0,2可以直接使用,从侧面验证了上述结论。
int main()
{
    char buffer[1024];	// 用户自己定义的缓冲区
    ssize_t s = read(0, buffer, 1024); // 从键盘读取
    if (s > 0)
    {
        write(1, buffer, strlen(buffer));	// 向显示器写入
    }
    return 0;
}

2. 文件描述符的分配规则是:从上往下扫描struct file *fd_array[]数组,寻找最小的,没有被使用的位置对应的下标,分配给打开的文件。

  • 关闭0或2,再打开文件,查看分配给新打开文件的fd(先不要关闭1):
int main()
{
    close(0);
    //close(2);
    int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);
    if (fd < 0)
    {
        perror("open");
        return 1;
    }
    
    printf("fd: %d\n", fd);
    
    close(fd);

    return 0;
}
  • 发现显示器输出结果是: fd: 0 或者 fd: 2
  • 可见,文件描述符的分配规则:在struct file *fd_array[]数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。

如果关闭1号文件(显示器),则无法在显示器中看到输出结果。


5. 重定向


5.1 引入


1. 先看代码,观察现象:

int main()
{
    close(1);	// 先关1
    int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);
    if (fd < 0)
    {
        perror("open");
        return 1;
    }
   
    printf("fd: %d\n", fd);
    printf("stdout->fd: %d\n", stdout->_fileno);

    // C语言提供的缓冲区问题,先待定!
    fflush(stdout); // 在close前,刷新缓冲区

    close(fd);

    return 0;
}

在这里插入图片描述

  • printf明明是向显示器打印的,怎么打印到了文件log.txt里?
  • 这种现象叫输出重定向>,常见的重定向有:
    • 输出重定向:>
    • 追加重定向: >>
    • 输入重定向:<

2. 解释:

在这里插入图片描述

  • 首先,根据fd的分配规则,关闭1后,再打开新文件log.txt,新文件的fd就是1。文件描述符数组的1号位置,不再指向显示器,而是文件log.txt
  • printf只认文件描述符1,默认向struct file *fd_array[]数组下标为1的位置所指向的文件打印,所以本该打印到显示器上的内容,打印到了文件log.txt中;
  • 一定要在close前刷新缓冲区,因为printf会先将数据放到C语言提供的缓冲区中,刷新缓冲区,才能让缓冲区中的数据换入到文件中;

这里只是粗力度的解释一下为什么要刷新缓冲区,关于缓冲区更多的细节,我们在后面讲解。

3. 输入重定向(一般不这样写):

int main()
{
    close(0);   
    int fd = open("log.txt", O_RDONLY); // fd == 0
    if (fd < 0)
    {
        perror("open");
        return 1;
    }

    char buffer[1024];	// 用户自己定义的缓冲区,先将数据读到buffer
    fread(buffer, 1, sizeof(buffer), stdin);    // stdin->fd: 0
    printf("%s\n", buffer);
    close(fd);
    
    return 0;
}

在这里插入图片描述

4. 追加重定向:

  • 只需要将1中代码中,openO_TRUNC选项改为O_APPEND即可。
  • 不过一般也不这样写。
int main()
{
    close(1);	// 先关1
    int fd = open("log.txt", O_CREAT | O_WRONLY | O_APPEND, 0666);
    if (fd < 0)
    {
        perror("open");
        return 1;
    }
   
    printf("fd: %d\n", fd);
    printf("stdout->fd: %d\n", stdout->_fileno);

    // C语言提供的缓冲区问题,先待定!
    fflush(stdout); // 在close前,刷新缓冲区

    close(fd);

    return 0;
}

5. 重定向的本质:

  • 上层fd不变,底层fd所指向的内容在改变。

5.2 一般的重定向写法——配合函数dup2


1. dup2函数:

  • oldfd下标指向的内容拷贝给newfd下标指向的内容。

在这里插入图片描述

2. 输出重定向:

int main()
{
    int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if (fd < 0)
    {
        perror("open");
        return 1;
    }

    dup2(fd, 1);

	// 如下内容将打印到文件 log.txt 中
    printf("hello printf\n");
    fprintf(stdout, "hello fprintf\n");
    close(fd);

    return 0;
}

在这里插入图片描述

3. 追加重定向:

int main()
{
    int fd = open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);
    if (fd < 0)
    {
        perror("open");
        return 1;
    }

    dup2(fd, 1);

	// 如下内容将追加到文件 log.txt 中
    printf("hello printf\n");
    fprintf(stdout, "hello fprintf\n");
    close(fd);

    return 0;
}

4. 输入重定向:

int main()
{
    int fd = open("log.txt", O_RDONLY);
    if (fd < 0)
    {
        perror("open");
        return 1;
    }
    dup2(fd, 0);

    char buffer[1024];
    fread(buffer, 1, 1024, stdin);

    printf("%s\n", buffer);
    close(fd);

    return 0;
}

5. 引用计数f_count:

  • 通过重定向的学习,我们知道了,一个打开的文件,可以被多个struct file*指针指向。如图,log.txt就被两个struct file*指针指向。

在这里插入图片描述

  • 我们在调用close接口关闭一个文件时,如果这个文件还被其他的指针指向,该怎么办,这样不是会互相影响吗?
  • 所以struct file结构体内还设计了一个f_count字段,它是引用计数,记录有多少个指针指向自己。如果f_count不为0,则执行一次closef_count就减一。直到f_count等于0时,才释放log.txt的资源。

5.3 stderr的意义


1. 看代码,观察现象:

int main()
{
    fprintf(stdout, "hello stdout\n");
    fprintf(stderr, "hello stderr\n");
    return 0;
}

在这里插入图片描述

  • 这个问题现在很好解释,因为重定向只是将1位置的指针改成指向log.txt了,但是stderr对应的2位置的指针,还是指向显示器文件,所以第二条fprintf语句还是向显示器打印了。

在这里插入图片描述

  • 想让这两条fprintf语句都往log.txt打印,需要将2位置也重定向了。在命令行中可以直接执行指令./myfile > log.txt 2>&1,其中2>&1表示让1位置的指针覆盖2位置的指针,这样1位置和2位置都指向log.txt了。
  • 所以./myfile > log.txt的完整写法应该是./myfile 1>log.txt

2. stderr的实际运用:

  • 比如在一个日志系统中,我们希望将正常信息和错误信息进行分流,将他们放在不同的文件中。这时候就可以使用输出重定向,将1位置和2位置重定向到不同的文件,将错误信息单独储存起来。

在这里插入图片描述


6. 缓冲区


6.1 预备知识


1. 我们理解的缓冲区:

  • 缓冲区其实就是一部分内存,重要的是搞清楚这一部分内存由谁提供。
    • 用户缓冲区:用户自己提供,用户在程序中自己定义的缓冲区,如char buffer[1024]
    • C语言缓冲区:由C语言提供的,定义在C库中;
    • 内核缓冲区:由操作系统提供的,内核级别的缓冲区。

2. 缓冲区存在的意义:

  • 任何缓冲区存在的目的只有一个,就是提高效率。

在这里插入图片描述

  • 举一个生活中的例子:
    • 假如你住在云南,你要给你远在北京的朋友送一个键盘。在快递还没有出现的时候,你需要先自己坐火车跑到北京,把键盘送到朋友手中,然后自己再跑回来,一来一回花了一个月时间,效率低下。
    • 后来快递出现了,你可以先把键盘给楼下的菜鸟驿站,然后由菜鸟驿站完成送键盘的任务,等键盘到了北京,你的朋友再去自己楼下的菜鸟驿站把键盘拿到手。
    • 整个过程中,键盘从云南到送到北京这个时间成本是不可避免的,这个时间成本由菜鸟驿站承担了。但是你就轻松了很多,在你把键盘送到菜鸟驿站的那一刻,你就可以认为你把键盘送出去了,然后你就可以干自己的事情了。
    • 所以菜鸟驿站的存在,节省了使用者的时间。
    • 菜鸟驿站就像缓冲区,进程先将数据放入缓冲区,然后由缓冲区执行向特定位置传输数据的操作,解放进程。所以,缓冲区的主要作用是——提高使用者的效率

3. 缓冲区的刷新策略:

  • 菜鸟驿站在送快递时,肯定也有自己的配送方式。

    • 比如某一个客户要求紧急配送,驿站就派专机专门送这个快递;
    • 不紧急的快递,派专机送成本太高了,会积累到一定的量后,统一配送。
  • 缓冲区的刷新也有自己的策略:

    • 无缓冲(立即刷新);
    • 行缓冲(行刷新);
    • 全缓冲(缓冲区满了,再刷新)。
  • 上面说的都是缓冲区刷新的一般策略,除此之外还有一些特殊情况:

    • 因为某些场景需要,需要强制刷新缓冲区;
    • 进程退出时,一定要进行缓冲区刷新。

4. 磁盘和显示器的刷新策略:

  • 一般对于显示器文件,会进行行刷新;
  • 对于磁盘文件,采取全缓冲策略。

6.2 看一个样例


1. 观察现象:

int main()
{
    fprintf(stdout, "C: hello fprintf\n");
    printf("C: hello printf\n");
    fputs("C: hello fputs\n", stdout);

    const char *str = "system call: hello write\n";
    write(1, str, strlen(str));

    fork();

    return 0;
}
  • 向显示器打印:

在这里插入图片描述

  • 重定向,向log.txt文件中打印:

在这里插入图片描述

2. 理解样例:

  • 当我们直接向显示器打印时,显示器的刷新方式是行刷新,并且我们写的所有打印语句后都有\n\n是一种行刷新策略)。在fork函数执行之前,缓冲区中的数据已经全部刷新,缓冲区为空。(包括系统调用接口write,系统内核级别的缓冲区也为空,这个后面说)
  • 重定向到log.txt文件的本质,是向磁盘文件写入,系统对数据的刷新方式就变成了全缓冲。
  • 全缓冲,意味着实际写入的简单数据,不足以把缓冲区写满,无法达到刷新条件。fork函数执行的时候,数据依旧在缓冲区中。
  • 由于write对应的打印内容正常打印了,所以我们可以得出一个结论:我们目前所谈的“缓冲区”和操作系统没有关系,只和C语言本身有关。
  • C/C++提供的缓冲区,里面保存的一定是用户的数据,属于当前进程在运行时自己的数据。当进程将数据交给操作系统后,这个数据就不属于当前进程了,而是属于操作系统。
  • 当进程退出时,一般要强制刷新缓冲区。缓冲区的刷新,本质上也是一种对当前进程一个变量的清空或“写入”操作。
  • fork后,任意一个进程退出的时候,会强制刷新缓冲区(修改变量),此时就会发生写时拷贝,所以我们看到缓冲区中打印的内容多出一份。
  • write是系统调用,没有使用C语言的缓冲区,它会将数据直接写入操作系统。所以这部分数据不属于进程,也就不会发生写时拷贝。

6.3 用户缓冲区VS内核缓冲区


在这里插入图片描述

1. 用户缓冲区:

  • 我们日常接触的最多的是C/C++提供的语言级别的缓冲区;
  • 我们通常说的刷新,指的是将C语言缓冲区中的数据刷新到操作系统中。

2. 内核缓冲区:

  • C语言缓冲区刷新到操作系统中后,实际上是传给了内核缓冲区,内核缓冲区最后还要将数据刷新到磁盘文件上;
  • 不同的操作系统,内核缓冲区的刷新策略不同。

3. 解释printf(“hello printf\n”);这段代码从执行,到在显示器打印的全过程:

  • 首先,hello printf\n这段数据会先通过printf函数写入到C语言缓冲区中,然后printf函数返回,至此它的任务就完成了;
  • 接着,C语言缓冲区中的数据会根据一定的刷新策略,通过write接口,写入操作系统;
  • 写入操作系统,实际上是先写入了stdout对应的内核缓冲区,然后操作系统再根据自己的刷新策略,将内核缓冲区中的数据刷新到磁盘中。

6.4 验证缓冲区的存在


任何情况下,我们调用C语言文件接口的时候,都要有一个FILE*指针。我们都知道FILE结构体中封装了文件描述符fd,其实FILE结构体中也封装了缓冲区。

在在/usr/include/stdio.h中,有:

typedef struct _IO_FILE FILE;

/usr/include/libio.h中,有:

struct _IO_FILE {
 int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
 
 //缓冲区相关
 /* The following pointers correspond to the C++ streambuf protocol. */
 /* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
 char* _IO_read_ptr; /* Current read pointer */
 char* _IO_read_end; /* End of get area. */
 char* _IO_read_base; /* Start of putback+get area. */
 char* _IO_write_base; /* Start of put area. */
 char* _IO_write_ptr; /* Current put pointer. */
 char* _IO_write_end; /* End of put area. */
 char* _IO_buf_base; /* Start of reserve area. */
 char* _IO_buf_end; /* End of reserve area. */
 /* The following fields are used to support backing up and undo. */
 char *_IO_save_base; /* Pointer to start of non-current get area. */
 char *_IO_backup_base; /* Pointer to first valid character of backup area */
 char *_IO_save_end; /* Pointer to end of non-current get area. */
 
 struct _IO_marker *_markers;
 
 struct _IO_FILE *_chain;
 
 int _fileno; //封装的文件描述符
#if 0
 int _blksize;
#else
 int _flags2;
#endif
 _IO_off_t _old_offset; /* This used to be _offset but it's too small. */
 
#define __HAVE_COLUMN /* temporary */
 /* 1+column number of pbase(); 0 is unknown. */
 unsigned short _cur_column;
 signed char _vtable_offset;
 char _shortbuf[1];
 
 /* char* _save_gptr; char* _save_egptr; */
 
 _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

验证了缓冲区的存在。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1979856.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

学习分享:解析电商 API 接入的技术重难点及解决方案

在当今电商业务迅速发展的时代&#xff0c;接入电商 API 已成为许多企业提升竞争力和拓展业务的重要手段。然而&#xff0c;在这个过程中&#xff0c;往往会遇到一系列的技术重难点。本文将深入解析这些问题&#xff0c;并提供相应的解决方案。 一、电商 API 接入的技术重难点 …

按摩虎口穴位的作用

按摩虎口穴位的作用 虎口穴位是人体手背上的一个重要穴位&#xff0c;它位于手指掌侧第一指骨和第二指骨之间的凹陷处。 按摩虎口穴位有很多益处&#xff0c;包括&#xff1a; 缓解头痛和眼疲劳&#xff1a; 按摩虎口穴位可以缓解头痛和眼疲劳&#xff0c;特别是由于长时间使用…

未授权访问漏洞系列详解①!

Redis未授权访问漏洞 Redis 默认情况下&#xff0c;会绑定在 0.0.0.0:6379 &#xff0c;如果没有进行采用相关的策略&#xff0c;比如添加防火墙规则避免其他非信任来源 ip 访问等&#xff0c;这样将会将 Redis 服务暴露到公网上&#xff0c;如果在没有设置密码认证(一般为空)的…

Golang | Leetcode Golang题解之第322题零钱兑换

题目&#xff1a; 题解&#xff1a; func coinChange(coins []int, amount int) int {var (dfs func(x int) int // x金额 最少硬币个数memo make(map[int]int) // 记忆化)dfs func(x int) int {//边界if x 0 {return 0} else if x < 0 {return math.MaxInt32}//记…

wangpang.xingkong(tou)

目录 client │ ├── client.h/c connect login recv send getcommand pausecommand putscommand │ ├── main.c 登陆&监听 │ ├── str_util.h/c 分割token字符串 conf │ └── server.conf server │ ├── config.h/c 读取文…

Android----Depth Anything尝鲜 小米手机部署

题目要求&#xff1a;了解Depth Anything (以及Depth Anything v2)基本原理&#xff0c;创新点。 Depth Anything 论文&#xff1a;Depth Anything: Unleashing the Power of Large-Scale Unlabeled Data 参考代码&#xff1a;Depth-Anything-Android GitHub 分析&#xff1a; …

深度学习在生物信息学中的应用

一、深度学习概念定义 深度学习&#xff08;Deep Learning&#xff09;是机器学习的一个子领域&#xff0c;它基于人工神经网络&#xff0c;尤其是深度神经网络。深度学习的核心思想是通过学习数据的表示层次和抽象层次&#xff0c;让机器能够具有类似于人类的分析学习能力。深…

lowbit(x)

返回x的最右边的一位1以及后面的所有数 x 1010 lowbit(x) 10x 101000 lowbit(x) 1000 一个整数的负数是补码(取反1) 应用&#xff1a; 求二进制中1的个数 题目 给定一个长度为 n 的数列&#xff0c;请你求出数列中每个数的二进制表示中 1的个数。 输入格式 第一行包…

uvm_config_db 和 uvm_resource_db :

uvm_config_db class my_driver extends uvm_driver;int my_param;function new(string name, uvm_component parent);super.new(name, parent);endfunctionvirtual task run_phase(uvm_phase phase);// 在组件内部获取配置值if (!uvm_config_db#(int)::get(this, ""…

python3 pyside6图形库学习笔记及实践(四)

目录 前言列表控件(QListWidget)创建列表增删插改查添加元素插入元素删除元素修改元素查找元素 常用信号和槽currentItemChangeditemChangedclear 列表排序列表的上下文菜单 图形视图框架简介框架核心图元类(QGraphicsItem)场景类(QGraphicsScene)视图类(QGraphicsView)交互机制…

守护数据安全:有效应对.hmallox勒索病毒的策略

引言 近年来&#xff0c;随着网络技术的飞速发展&#xff0c;勒索病毒成为网络安全领域的一大威胁。其中&#xff0c;.hmallox勒索病毒作为malox勒索软件家族的新变种&#xff0c;给个人和企业带来了极大的数据安全和经济损失风险。本文将对.hmallox勒索病毒进行详细介绍&…

机器学习用python还是R,哪个更好?

机器学习领域中&#xff0c;Python和R都是非常流行的编程语言&#xff0c;它们各有优势和特点&#xff1a; Python: 优势: 拥有丰富的库和框架&#xff0c;如scikit-learn、TensorFlow、PyTorch等&#xff0c;适合各种级别的机器学习任务。语法简洁清晰&#xff0c;易于学习。社…

3DM游戏运行库合集离线安装包2024最新版

3DM游戏运行库合集离线安装包是一款由国内最大的游戏玩家论坛社区3DM推出的集成式游戏运行库合集软件&#xff0c;旨在解决玩家在玩游戏时遇到的运行库缺失或错误问题。该软件包含多种常用的系统运行库组件&#xff0c;支持32位和64位操作系统&#xff0c;能够自动识别系统版本…

LeetCode每日一题_572.另一棵树的子树

解题思路&#xff1a; Step1:首先我们要知道如何判断两颗树相同&#xff0c;思路就是遍历每个节点&#xff0c;然后判断是否均相等&#xff0c;需要用递归来实现。代码如下所示&#xff1a; public static boolean equals(TreeNode t1,TreeNode t2){if(t1null&&t2null…

[Java]面向对象,从浅到深

快速入门 计算机的核心作用就是处理数据, 变量用来存储单个数据, 数组用来储存一批数据, 对象用来存储一类数据 什么是对象: 对象就是一种特殊的数据结构, 在java中万物皆对象 面相对象编程的好处: 更加符合人类思维习惯 类和实例对象 在java中必须先设计类, 才能根据类创…

git学习入门1——下载安装与添加用户标识设置name与Email

想法是这样的&#xff0c;先是自己工作闲暇之余在学习C语言&#xff0c;在跟一个某平台的机构学习C语言的基础知识&#xff0c;空闲之余学习了几天&#xff0c;想起了之前学习过程中某学员提出的git每日提交代码的那个表格记录&#xff0c;忽然想起自己也先学习git的使用。 先是…

三、初识工作流

基础操作 拖动操作&#xff0c;按住鼠标左键可以拖动 放大缩小&#xff0c;可以通过鼠标滚轮操作 节点含义 1、大模型 2、正向与负向提示词(生成图片的文字信息) 3、图片尺寸设定&#xff08;批次大小为每次生产图片数量&#xff09; 4、采样器 5、图片渲染 6、保存图像 设…

Reed-Solomon纠错码——RS(255,251)学习及实现

1、基础知识 1.1 有限域 有限域_百度百科​​​​​​ 伽罗华域&#xff08;Galois Field&#xff09;上的四则运算_模2的伽罗华域乘法-CSDN博客 1.2 RS&#xff08;255,251&#xff09; 里德-所罗门码&#xff08;一种前向错误更正的信道编码&#xff09;_百度百科 本原…

Spring面试篇章——IOC

IOC概念和原理 IOC概念 IOC就是控制反射&#xff0c;把对象创建和对象之间的调用过程&#xff0c;交给Spring进行管理使用IOC的目的&#xff1a;降低耦合度 IOC底层原理 xml解析、工厂模式、反射 图解&#xff1a; 原始模式 耦合度太高了&#xff0c;即当dao改了&#xf…

UWB实操:使用 litepoint 收发UWB信号

使用 litepoint 收发UWB信号 把信号线接到 litepoint 的RF1 和RF2。 注意&#xff1a; RF1 支持 VSG(TX) 和VSA(RX)RF2 只支持 VSG(TX)同一时间只能一个 VSG(TX) 双击 LED STATUS&#xff0c;改变RF1和RF2的模式。 RF1&#xff1a;VSA(RX) RF2&#xff1a;VSG(TX) Techno…