攻破:重定向 缓冲区

news2024/9/19 10:34:21

文章目录

  • 前言:
  • 认识读文件read
  • 认识重定向&&缓冲区
    • 重定向现象及分析:
      • dup2的介绍:
    • 缓冲区的引入:
    • 缓冲区的理解:

前言:

​ 从上一章开始,我们进入了文件IO的学习,认识了文件描述符是个什么,以及在操作系统内部是如何管理那么多文件的,最终我们还解释了为什么说“linux一切皆文件”这个概念。下面我们继续往下学习,认识输入输出重定向以及了解缓冲区的概念。

认识读文件read

​ 首先我们需要知道:文件 = 属性 + 内容。
​ 因此,对文件操作本质就是对文件的属性或文件的内容进行操作。
​ 对于文件的内容操作,不论我们是使用系统调用接口还是说语言层面的函数,我们都可以实现对文件的内容的操作,但是对于属性我们暂时无法添加相对应的属性,但我们可以通过一个函数来实现文件属性的查找:

int stat(const char* path, struct stat* buf);
  • struct stat这个结构体包含了文件的各种信息,比如文件大小。而buf就是我们的输出型参数。

  • 而对于read的操作,我们只需要先将open里的宏替换成O_RDONLY,然后再实现read的代码:

    ssize read
    (
    	int fd, 		// 从文件描述符 fd 指定的文件中读取内容
    	void* buf, 		// 将读取到的内容放到 buf 中
    	size_t count	// 指明希望从文件中读取的字节数
    );	
    

所以我们便可以先利用write在log.txt里写入“hello linux file" 再通过stat获取文件的大小,最后通过read读取数据存放至buffer数组中,便可以实现文件的读取,代码如下:

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

int main()
{
    struct stat st;
    int n = stat("log.txt", &st);
    
    printf("file size: %lu\n", st.st_size);

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

    char* file_buffer = (char*)malloc(st.st_size);
    if(file_buffer == NULL)
    {
        perror("malloc");
        return 2;
    }
    
    n = read(fd, file_buffer, st.st_size);
    if(n < 0)
    {
        perror("read");
        return 3;
    }

    printf("file_buffer: %s\n", file_buffer);

    return 0;
}

image-20240824123612178

认识重定向&&缓冲区

重定向现象及分析:

  • 先看代码:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

int main()
{
    close(1); // 先关闭默认的stdout,也就是关闭了显示屏
    int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }
    printf("fd: %d\n", fd);
    fprintf(stdout, "%s\n", "this is fprintf");
    
    const char* msg = "This is write\n";
    int n = write(fd, msg, strlen(msg));
    if(n < 0)
    {
        perror("write");
        return 2;
    }
    return 0;
}

image-20240824125401360

image-20240824131025786

问题出现了!我明明在代码中printf了我的文件描述符fd,为什么没有显示呢?
为什么我明明直接向stdout用fprintf打印了一句话,最后也没有在显示屏显示呢?
还有,为什么在问log.txt的文件中,新创建的文件描述符变成了1?1不是显示器文件吗???

  • 分析现象:

image-20240824130922446

image-20240824131741123

image-20240824132227300

因此,printf和fprintf原本是向1号对应的显示屏打印的,但是由于先关闭了一号,导致发生了文件的重定向,log.txt的文件描述符就变为了1号。因此最终其实是向log.txt中打印数据!

我们也能得知文件描述符的分配规则
——先查自己的文件描述符表(就是上图的struct file* arrat[ ]数组),分配最小的且没有退出的文件描述符fd。

dup2的介绍:

​ 现在我差不多知道什么叫重定向了,不就是原本是往显示器文件打印数据,变成了往普通文件打印数据吗。但是实现这个却要先close掉显示器文件,可不可以不关掉直接发生重定向呢?对此,我们就要引入一个函数:

int dup2(int oldfd, int newfd);

​ 对于这个函数,它的本质其实是 文件描述符下标所对应的内容的拷贝,将老的文件描述符往新的文件描述符进行拷贝。
​ 例如我想实现上述那样,将原本的3号文件描述符拷贝至1号文件描述符(当然这里一定是对内容的拷贝而不是下标!!!)我们就可以这样写:dup2(fd, 1);(意思是将fd的内容拷贝至1号文件描述符当中)

  • 代码演示:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

int main()
{
    int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }
    
    dup2(fd, 1);
    
    // C
    printf("fd: %d\n", fd);
    fprintf(stdout, "%s\n", "this is fprintf");
    
    // system call
    const char* msg = "This is write\n";
    int n = write(fd, msg, strlen(msg));
    if(n < 0)
    {
        perror("write");
        return 2;
    }
    
    return 0;
}

image-20240824170845832

这个时候我们会发现通过dup2实现的重定向,原本是3号文件描述符并不会发生改变,原因如下:

image-20240825124405263

缓冲区的引入:

​ 其实对于上述的代码,我们并没有写完全。按道理来说,在使用系统调用open打开一个文件,最后是需要close掉的,可是我们并没有close。接下来我们使用先关close(1)而实现重定向的代码来分析缓冲区:

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

int main()
{
    close(1); // 先关闭默认的stdout,也就是关闭了显示屏
    int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }
    printf("fd: %d\n", fd);
    fprintf(stdout, "%s\n", "this is fprintf");
    
    const char* msg = "This is write\n";
    int n = write(fd, msg, strlen(msg));
    if(n < 0)
    {
        perror("write");
        return 2;
    }
    
    close(fd); // 不同的在这里,我最后把fd关了
    return 0;
}

image-20240825125508753

​ 最后结果运行结果也是没有在显示屏上显示,原理我们知道了发生了重定向。但我们发现log.txt文件里的数据缺少了很多,没有printf / fprintf打印的数据,只有系统调用的数据,这是什么原因呢?
image-20240825133133893

image-20240825141538273

但如果我们在close(1)之前刷新缓冲区,就可以显示数据了:
使用函数fflush(stdout);

image-20240825141948835

至此我们就发现数据写入进去了。

缓冲区的理解:

​ 从上述的理解来看,缓冲区分为 “用户级缓冲区” “内核级缓冲区”。
​ 存在两大好处:
​ 1、解耦(不必在意底层是如何实现)
​ 2、提高效率

  • 为什么提高效率?

    ——目的是提高用户的效率。
    首先调用系统调用,是有成本的!
    少调用成本就低了,效率就高了。
    先暂存,最后再刷新(提高了刷新IO效率)

  • 是什么?

    是一段内存空间(每个文件都有自己的缓冲区)

  • 为什么?

    给上层提供高效的IO体验,间接提高整体效率。

  • 怎么办?

    1. 立即刷新(无缓存,直接释放缓冲区) —> fflush(stdout), int sync(int fd);
    2. 行刷新 —> 显示器(照顾用户的习惯)
    3. 全缓冲 —> 缓冲区写满,才刷新(针对的是普通文件)
    4. 进程退出,系统会自动刷新,强制刷新

来看看代码:

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

int main()
{
    printf("hello printf\n");
    fprintf(stdout, "hello fprintf\n");
    
    const char* msg = "hello write\n";
    write(1, msg, strlen(msg));

    fork();

    return 0;
}

image-20240825154730193

​ 代码这一看是没啥问题的,因为加了\n就会自动刷新,从语言层缓冲区刷新至内核级缓冲区此时我们使用重定向—— ./myfile > log.txt
image-20240825155608467

​ 这时我们就会发现一个神奇的现象,关于语言层的输出最终会被打印两次。造成这种现象的就是fork( )导致的。

​ 当运行程序不发生重定向时,stdout是面向的显示器的,也就是1号文件描述符对应的是显示器,针对于显示器,语言级的缓冲区是按照 “行刷新” 的方式进行刷新缓冲区,所以我们就会看到程序通过’\n’一行一行的输出至屏幕上。但是由于执行了 ./myfile > log.txt,导致stdout面向的是普通文件,针对于普通文件,语言级的缓冲区是按照 “全缓冲” 的方式进行刷新的,只有当语言级缓冲区满了或者进程退出时,才会刷新缓冲区。

​ 问题就出现在进程上,当面向文件时执行的全刷新,当fork( )创建子进程后,会导致在同一个代码结束时,造成两次刷新语言级缓冲区,从而导致打印了两次。
​ 下面我们通过进程等待,来看看缓冲区被释放的过程:

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

int main()
{
    printf("hello printf/ ");
    fprintf(stdout, "hello fprintf/ ");
    
    const char* msg = "hello write/ ";
    write(1, msg, strlen(msg));
    

    pid_t id = fork();
    if(id == 0)
    {
        // child
        sleep(3);
        printf("<- child process quit ->/ ");
        return 0;
    }
 
    // father
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    
    sleep(3);
    printf("<- father process quit ->/ ");
    return 0;
}

image-20240825162811240
这个代码下来可以验证下,因为我有用sleep函数进行时间控制

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

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

相关文章

浅谈C# RabbitMQ

一、基本介绍 RabbitMQ——Rabbit Message Queue的简写&#xff0c;但不能仅仅理解其为消息队列&#xff0c;消息代理更合适。 RabbitMQ 是一个由 Erlang 语言开发的AMQP&#xff08;高级消息队列协议&#xff09;的开源实现&#xff0c;其内部结构如下&#xff1a; RabbitMQ作…

今年秋招太吓人了。(20届,在得物做Java开发)

有个学弟来问我诉苦最近好忙好累&#xff0c;说竞争压力特别大&#xff0c;让我给点建议&#xff0c;要不要放弃实习闷头搞秋招&#xff0c;我才意识到时间太快了&#xff0c;想想我都毕业几年了&#xff0c;感慨颇深&#xff0c;整理一下我的求职经验和目前的心得吧&#xff0…

SpingBoot集成kafka-发送读取消息示例

SpingBoot集成kafka开发 kafka的几个常见概念 1、springboot和kafka对应版本&#xff08;重要&#xff09;2、创建springboot项目&#xff0c;引入kafka依赖2.1、生产者EventProducer2.2、消费者EventConsumer2.3、启动生产者的方法SpringBoot01KafkaBaseApplication2.4、appli…

监控电脑屏幕的软件叫什么?6款电脑屏幕监控软件分享!

监控电脑屏幕的软件可以帮助企业和家长监控电脑的使用情况&#xff0c;确保工作和学习的效率与安全。 以下是六款常用的电脑屏幕监控软件及其特点&#xff1a; 1. Keylogger 特点&#xff1a;专注于企业数据安全和员工上网行为管理。 功能&#xff1a;全面的屏幕监控、上网…

Redis持久化(RDB、AOF、混合持久化)

目录 1、持久化机制 &#xff08;1&#xff09;RDB &#xff08;2&#xff09;AOF 2、混合持久化 3、总结 ❓为什么需要持久化&#xff1f; Redis 是一个基于内存的键值存储系统&#xff0c;它提供了非常快的数据访问速度&#xff0c;因为它不需要像传统的磁盘存储那样进…

竞猜足球核心算法源码

需要实现的功能如下&#xff1a; 仅用于学习 竞猜足球核心算法源码 package com.lotterysource.portsadmin.service; import com.aliyun.oss.common.utils.DateUtil; import com.fasterxml.jackson.core.type.TypeReference; import com.lotterysource.portsadmin.dbprovid…

进存销系统

摘 要 伴随着我国全面推动信息化的趋势&#xff0c;我国的很多行业都在朝着互联网的方向进发。商品销售行业也有很多挑战。这次论文介绍的进存销系统就是为了能够解决当前传统商品进存销存在的问题&#xff0c;使得商品进存销能够更加有效率。电商智能化管理必不可少的帮手有进…

功能安全实战系列02-RamTst(RamTest)开发介绍

本文框架 前言1. What(RamTst相关概念)1.1 后台检测1.2 前台检测1.3 RamTst对应状态机2.How?2.1 接口调用2.2 配置开发2.3 测试模式选择前言 在本系列笔者将结合工作中对功能安全实战部分的开发经验进一步介绍常用,包括Memory(Flash,Ram)失效检测,程序运行时序时间检测,及…

数字模拟IC设计前端、后端、前仿、后仿新版虚拟机

虚拟化平台&#xff1a;VMware Workstation 15 Pro以上版本 操作系统&#xff1a;CentOS Linux release 7.9.2009 (Core) 一、射频模拟IC设计必备软件 Cadence IC06.18.350/IC23.10.080&#xff08;virtuoso&#xff09; Cadence SPECTRE23.10.538-isr10 Cadence ASSURA04.…

Python优化算法15——麻雀搜索算法(SSA)

科研里面优化算法都用的多&#xff0c;尤其是各种动物园里面的智能仿生优化算法&#xff0c;但是目前都是MATLAB的代码多&#xff0c;python几乎没有什么包&#xff0c;这次把优化算法系列的代码都从底层手写开始。 需要看以前的优化算法文章可以参考&#xff1a;Python优化算…

Mozilla为本地音频到文本翻译开发Whisperfile引擎

Mozilla Ocho 小组正进行 Mozilla 的"创新和实验"。Llamafile 用于将大型语言模型以单个文件的形式发布&#xff0c;以便在不同的硬件/软件间轻松执行。Whisperfile 是一项将音频轻松转化为文本的新引擎。 正如其名称所暗示的&#xff0c;Whisperfile 是围绕 OpenAI…

嵌入式UI开发-lvgl+wsl2+vscode系列:10、控件(Widgets)(三)

1、scale&#xff08;标尺&#xff09; 示例1 #include "../../lv_examples.h" #if LV_USE_SCALE && LV_BUILD_EXAMPLES/*** 简单的水平标尺*/ void lv_example_scale_1(void) {lv_obj_t * scale lv_scale_create(lv_screen_active());lv_obj_set_size(sca…

MyBatis源码(6)拦截器

1、目标 本文的主要目标是学习MyBatis拦截器的源码&#xff0c;本文将以插入操作为例debug拦截器相关的源码 2、拦截器源码分析 调用mapper接口的insert插入记录方法&#xff0c;会调用SqlSession对象的insert方法 SqlSession执行insert方法 Spring容器会创建SqlSessionTemp…

Python画笔案例-011 绘制草帽

1、绘制草帽 通过 python 的turtle 库绘制一个草帽的图案&#xff0c;如下图&#xff1a; 2、实现代码 绘制以上草帽的图案&#xff0c;代码如下&#xff1a; """草帽.py """ import turtle # 导入海龟模块turtle.delay(20) …

多动症的孩子有哪些症状表现?

在星启帆自闭症儿童康复机构&#xff0c;我们不仅关注自闭症儿童的成长与康复&#xff0c;也深刻认识到多动症对儿童日常生活、学习和社交的深远影响。多动症&#xff0c;全称注意缺陷多动障碍&#xff0c;是一种常见于儿童时期的神经发育性疾病&#xff0c;其症状表现多种多样…

Python优化算法16——鲸鱼优化算法(WOA)

科研里面优化算法都用的多&#xff0c;尤其是各种动物园里面的智能仿生优化算法&#xff0c;但是目前都是MATLAB的代码多&#xff0c;python几乎没有什么包&#xff0c;这次把优化算法系列的代码都从底层手写开始。 需要看以前的优化算法文章可以参考&#xff1a;Python优化算…

ChatGPT不同模型在论文写作中的优势和应用

学境思源&#xff0c;一键生成论文初稿&#xff1a; AcademicIdeas - 学境思源AI论文写作 ChatGPT在论文写作中的应用日益广泛。作为OpenAI开发的先进语言模型&#xff0c;ChatGPT有多个版本&#xff0c;包括GPT-3.5、GPT-4.0和GPT-4.0-mini&#xff0c;每个版本在性能和应用方…

如何解决Docker启动时报Status: unknown flag: --graph问题

最近在进行Docker环境迁移时&#xff0c;用二制对Docker进行了重新安装&#xff0c;一切配置好之后&#xff0c;启动Docker时&#xff0c;服务启动不起来&#xff0c;使用journalctl -xe命令查看&#xff0c;报出以下错误&#xff1a; [rootapp docker]# journalctl -xe 8月 2…

【css】伪元素实现图片悬停文字聚焦效果

实现重点&#xff1a; 文字覆盖在图片上&#xff1a; 通过使用 position: absolute 将 .box 文字盒子定位在图片上方。父容器 .img-wrap 使用了 position: relative 确保子元素的绝对定位在父容器的边界内生效。 创建悬停效果&#xff1a; 通过使用 &::before 和 &::…

Android PopupWindow弹窗动态显示在View的上下方,

序、周末不加班&#xff0c; 效果图如下。 我们要弹出的PopupWindow在View的下方&#xff0c;如果下方区域不够&#xff0c;则弹出在上方。 实现方案思路 我们在显示的时候&#xff0c;首先去计算一下弹窗高度。使用屏幕的高 - popupwind的高并且和popup的高做对比&#xff0…