Linux--基础IO(2)

1. 缓冲区

1.1 概念

执行下列代码:

int main()
{
    close(1);//重定向
    int fd = open("log.txt",O_CREAT|O_WRONLY,0644);
    printf("hello\n");
    return 0;
}

查看log.txt发现里面打印了hello,但是如果把fd关闭:

int main()
{
    close(1);重定向
    int fd = open("log.txt",O_CREAT|O_WRONLY,0644);
    printf("hello\n");
    close(fd);
    return 0;
}

会发现log.txt里面已经没有内容。这是为什么?

这就要涉及到缓冲区的问题:

1.2 C缓冲区

在这里插入图片描述

平时我们输入的内容其实都是输入到了C缓冲区里,而不是直接刷新。

而这个刷新的时间,就是进程退出的时候,会将C缓冲区的数据到OS缓冲区。

所以对于上面的例子来说,所谓的打印不过是把数据放到了C缓冲区中;随后再通过系统调用接口刷新到内核缓冲区,最后再刷新到外设中。

  • 对于这个过程有几点值得提出:
  1. 这个所谓的C缓冲区在哪里?

存在于FILE指针结构体中。

之前说到FILE结构体里封装了fd,用于描述文件位置;其实里面还封装了有关维护C缓冲区的内容

在这里插入图片描述

  1. 这个C缓冲区刷新到OS缓冲区的过程是如何实现的?

这其实相当于对一个文件进行写入,那么就肯定要用到fd,并且这个刷新过程是由固定的刷新策略决定的。

1.3 刷新策略

1.3.1 三种刷新方式
  1. 立即刷新(不缓冲,有数据就马上刷新);
  2. 行缓冲(通过\n区分,比如显示器打印);
  3. 全缓冲(缓冲区满了才刷新,比如磁盘)。

这几种方式对于OS缓冲区和硬件都适用。

而对于重定向:显示器重定向到文件中,其实就是说刷新策略由行缓冲变成全缓冲。

  • 解析

而对于一开始的现象,可以这么解释:

  1. 如果有重定向

能显示出来是因为重定向到了log.txt文件内,策略变成了全缓冲那么数据刷新过程就是先刷新到了C缓冲区,进程结束后(这是条件)就会刷新到OS缓冲区然后显示在文件内;这是没有close(fd)的时候;

但是有close(fd)以后,内容还在C缓冲区,由于全缓冲,还未刷新到OS缓冲区内(进程未结束),就没有显示内容。

  1. 如果没有重定向

如果没有重定向,无论有没有close(fd)都会输出,因为没有重定向就是打印到显示器,采用行刷新,只要C缓冲区识别到了\n就可以直接刷新。

而对于无法显示内容,我们可以采用刷新的方式将内容从C缓冲区刷新到OS缓冲区:

fflush(stdout);

运行下列代码:

int main()
{
    const char* a = "hello a\n";
    write(1,a,strlen(a));
    printf("hello printf\n");
    fprintf("hello fprintf\n");
    close(fd);
    return 0;
}

将可执行文件重定向到任一文件中,会发现:只有"hello a"被打印在了文件中,而其他都没有。

都是一起重定向到文件中,为什么其他两个没有显示?

这是close(1)的问题:由于重定向到文件中,那就是全缓冲策略,和前面一样,由于进程还没关闭就关掉了标准输出流1,导致内容在C缓冲区中刷新不出来。

那为什么"hello a"可以?

因为write是写方式,这是系统调用,不需要通过C缓冲区,自然也就不存在“缓冲区还未满,等待进程结束再刷新到OS缓冲区”的过程。

从上面的例子可以知道,平时我们打开文件以后需要关闭对应的文件描述符,就是因为不这样做数据刷新不出来。

1.3.2 系统调用的问题

观察下列代码

int main()
{
    const char* a = "hello a\n";
    write(1,a,strlen(a));
    printf("hello printf\n");
    fprintf("hello fprintf\n");
    fork();//创建子进程
    return 0;
}

可以看见最后创建了子进程。正常运行没有问题,都打印在了显示器;但是将其可执行程序重定向到任一文件后,发现不仅有"hello a",还有两倍的"hello printf"和"hello fprintf"。这又是为什么?

这是因为C函数打印本质上是在向stdout打印,而stdout是FILE*类型,会找到FILE结构体,从而找到其封装的对应缓冲区信息再找到对应缓冲区buffer,然后进行写入。

又因为这是父进程的缓冲区(子进程一模一样拷贝了一份),这个缓冲区是用户层提供,也就是C缓冲区,并且是重定向到文件里,刷新策略是全缓冲,所以等到进程结束后就会刷新到文件里。
在这里插入图片描述

但是这个过程相当于子进程对共有的内容进行了修改,就会引发写时拷贝,那么子进程也会刷新一次,导致父子进程都刷新一次就打印了两次。

本质还是因为刷新策略改变了,所以同样的采用强制刷新的方式可以只打印一次,这是因为直接将内容刷新到OS缓冲区,进程结束后C缓冲区里已经没有内容,也就不会引发写时拷贝,最后只打印OS缓冲区里的一次。

可是为什么write只有一份?

这就验证了平时我们说的缓冲区其实是用户层的C缓冲区,而不是存在于操作系统内核里,否则的话全部都会刷新两次。

  • 类比

C++打印中的std::endl其实也是行缓冲的道理,endl作用就是刷新缓冲区的数据到显示器中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

久菜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值