【Linux】:重定向和缓冲区

朋友们、伙计们,我们又见面了,本期来给大家带来关于重定向和缓冲区的相关知识点,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成!

C 语 言 专 栏:C语言:从入门到精通

数据结构专栏:数据结构

个  人  主  页 :stackY、

C + + 专 栏   :C++

Linux 专 栏  :Linux

目录

1. 重定向

1.1 输出重定向 

1.2 追加重定向

1.3 输入重定向

1.4 重定向系统调用接口

2. 标准错误stderr

3. 缓冲区

3.1 缓冲区存在的价值

3.2 缓冲区的刷新方式

3.3 分析样例 

3.4 用户缓冲区和内核缓冲区

3.5 何为刷新

3.6 FILE结构体


1. 重定向

重定向这个概念在前面Linux常见指令章节就介绍过它的指令以及用法,那么本节来一起深入了解一下重定向:

1.1 输出重定向 

echo 字符串 > 文件 :将本来输出在显示器文件(标准输出)上的字符串输出至指定的文件。

标准输出对应的文件fd是1。

下面用代码来实现一下重定向的功能:
 

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

#define FILE_NAME "log.txt"

int main()
{
    // 关闭标准输出
    close(1);
    int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_TRUNC, 0666);
    printf("fd: %d\n", fd);
    fprintf(stdout, "stdout->fd: %d\n", stdout->_fileno);

    // 刷新
    fflush(stdout);
    close(fd);
}

先看结果,再分析代码:

我们都知道文件fd的分配规则,是寻找最小的未被使用的fd进行分配,所以我们先把1号文件描述符关闭,然后再打开文件时,1号文件描述符就被新打开的文件分配走了,这些C语言打印函数,默认是往1号文件描述符对应的文件中打印,简单的说就是它们只认识1这个数字,并不会在乎这个文件到底是不是显示器文件,所以才会把数据打印到新打开的文件中。

至于这里为什么要加这个fflush用来刷新缓冲区在后续会详细介绍。

1.2 追加重定向

追加重定向直接把打开文件时的方式从清空改为追加即可:

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

#define FILE_NAME "log.txt"

int main()
{
    // 关闭标准输出
    close(1);
    //int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_TRUNC, 0666);
    int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_APPEND, 0666);
    printf("fd: %d\n", fd);
    fprintf(stdout, "stdout->fd: %d\n", stdout->_fileno);

    // 刷新
    fflush(stdout);
    close(fd);
}


1.3 输入重定向

cat指令默认是从标准输入键盘文件中读取数据;

cat < 文件:本来从键盘读取数据,但是重定向为从指定的文件读取数据。

标准输入对应的文件fd是0。

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

#define FILE_NAME "log.txt"

int main()
{
    // 关闭标准输入
    close(0);

    int fd = open(FILE_NAME, O_RDONLY);

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

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

当我们把标准输入文件fd关闭后,根据文件fd的分配规则,新创建的文件就被分配到了0号文件fd,C语言的这些读取接口只认识0号文件fd,只认识0这个数组,所以就直接从0号fd对应的文件中直接读取。

重定向之后上层的fd不变,但是底层fd的指向在变化,所以重定向的本质是修改特定文件fd的下标内容。 

1.4 重定向系统调用接口

#include <unistd.h>
int dup2(int oldfd, int newfd);

代码演示:

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

#define FILE_NAME "log.txt"

int main()
{
    int fd = open(FILE_NAME, O_RDONLY);
    // 重定向
    dup2(fd, 0);
    char buffer[1024];
    fread(buffer, 1, sizeof(buffer), stdin);

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

dpu2接口其实是文件描述符级别的数组内容的拷贝!

注意:

程序替换是不影响曾经的重定向;

程序替换没有创建新的进程,它更改的是物理到虚拟的转化以及对应的页表,并不会影响进程PCB,所以程序替换不会影响重定向。 

2. 标准错误stderr

打印结果分为错误打印和正确打印,正确打印对应的是stdin,错误打印对应的是stderr,它们两者都是打印在显示器上的。

有了stderr之后,就可以将正确打印和错误打印的数据分别存储在两个不同的文件,最主要的是为了查错,当程序出错时,直接去存储错误结果文件查找错误原因。

我们在命令行使用的重定向都是简写,完整的写法是:

./exe 1 > log.txt  // 输出重定向至log.txt
./exe 1 > log.txt 2 > &1 // 将标准输出和标准错误都重定向至log.txt
./exe 1 > log.txt 2 > log.txt.error // 标准输出重定向至log.txt,标准错误重定向至log.txt.error

3. 缓冲区

在前面的文件fd章节提到过,读写数据的本质是将内核缓冲区中的数据来回拷贝。

那么我们所理解的缓冲区其实就是一块由操作系统提供的内存空间。

3.1 缓冲区存在的价值

举一个现实中的例子来理解缓冲区:

小明居住在西安,他的好朋友居住在苏州,小华和小明每年都要过生日,双方都会在彼此过生日的时候挑选好生日礼物,小明等到小华过生日的前两个月,直接骑着骑行车从西安历经两个月到了苏州,刚好把他给小华准备的生日礼物送到,小华在小明过生日的时候也一样,都是骑两个月自行车去送礼物,就这样持续了好几年,某一天小明和小华家楼下都开了一家快递公司,每小华过生日的小明直接把礼物交给快递公司,让快递公司托运给小华,当小明把礼物给快递公司时,站在小明的视角礼物已经送走了,但是站在小华的视角,礼物当前还没收到,需要时间,但这个时间肯定比小明骑着自行车送过来要更快。

在这个例子中,这个快递公司扮演的角色就类似于缓冲区,正是有了快递公司的存在,大大提升了小明送礼物的效率。

所以缓冲区的存在可以提高使用者的效率,正是因为有了缓冲区的存在,我们可以积累一部分数据再统一发送,减小了发送成本,提升了发送效率。 

3.2 缓冲区的刷新方式

因为缓冲区可以暂存数据,所以它必须要有对应的刷新策略;
一般策略:

  • 1. 无缓冲(有数据立即刷新)
  • 2. 行缓冲(按行为单位进行刷新)
  • 3. 全缓冲(等到数据写满缓冲区再刷新)

特殊策略:

  • 1. 强制刷新
  • 2. 进程退出的时候,一般要进行刷新缓冲区

对于显示器文件,一般使用的是行刷新策略;

对于磁盘文件,一般使用的是全缓冲策略。

3.3 分析样例 

下面以缓冲区这个概念为基础,分析一下下面这段代码:

#include <stdio.h>
#include <string.h>
#include <unistd.h>

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(); // 注意fork的位置!
    return 0;
}

命令行运行结果:

当我们直接运行是,和预期一致,都是打印在显示器上的,没有任何问题,但是一旦我们重定向至文件,此时就很奇怪了,接下来我们一步一步分析: 

  • 1. 当我们直接向显示器打印,显示器文件的刷新方式是行刷新,我们打印的字符串都有'\n',在fork创建子进程之前,数据已经被刷新完毕,所以三条C接口消息和一条系统调用接口消息。
  • 2.  当我们将内容重定向至文件log.txt,本质就是向磁盘文件进行写入,我们的系统对于数据的刷新策略从行缓冲变成了全缓冲!
  • 3. 全缓冲的策略意味着缓冲区变大,我们写入的简单数据不足以把缓冲区写满,所以在fork执行的时候,数据依旧停留在缓冲区中。
  • 4. 当进程退出的时候,一般要刷新缓冲区,即使数据没有满足刷新条件!
  • 5. 观察文件中的写入结果发现C接口写入的数据是双倍的,但是系统调用接口写入的数据只有一个,所以这里的缓冲区和和操作系统没有关系,只和C语言本身有关!
  • 6. C/C++提供给我们的缓冲区,里面一定保存的是用户的数据,属于当前进程在运行时自己的数据,但是,当我们把数据交给了OS,此时该数据就属于OS,不属于用户了。
  • 7. 刷新缓冲区的这个操作就是把进程的数据写入到操作系统,刷新的操作属于清空、写入,所以,在fork之后,OS检测到了父子进程任意一方要对数据进行写入、清空,此时就发生了写时拷贝,父子进程各有一份数据,所以才会C语言调用的接口写入数据时才会写入两次。
  • 8. 系统调用接口是直接写入到操作系统,不属于进程数据,所以不发生写时拷贝,只会有一份数据。

3.4 用户缓冲区和内核缓冲区

用户缓冲区就是我们使用的C/C++提供的语言级别的缓冲区。

内核缓冲区是由OS提供的一块内存空间。

3.5 何为刷新

我们使用C语言的接口写入数据时首先是要把数据写入到C语言提供的缓冲区的,那么C语言的缓冲区就有对应的刷新策略(行缓冲、全缓冲等),当数据满足刷新策略时,就会将数据写入到内核缓冲区,所以从用户缓冲区写入到内核缓冲区的这个工作就叫做刷新。

内核缓冲区刷新也有它对应的刷新策略。

C/C++语言提供的缓冲区也是为了提高函数调用(printf、fprintf等)的效率。 

3.6 FILE结构体

前面说过FILE结构体中包含了文件描述符,现在来看它里面也必定也包含了C缓冲区

在Linux在命令行输入:vim /usr/include/libio.h +246 就可以查看对应的FILE结构体对象了。

朋友们、伙计们,美好的时光总是短暂的,我们本期的的分享就到此结束,欲知后事如何,请听下回分解~,最后看完别忘了留下你们弥足珍贵的三连喔,感谢大家的支持!    

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

海事无人机解决方案

海事巡察 海事巡察现状 巡查效率低下&#xff0c;存在视野盲区&#xff0c;耗时长&#xff0c;人力成本高。 海事的职能 统一管理水上交通安全和防治船舶污染。 管理通航秩序、通航环境。负责水域的划定和监督管理&#xff0c;维护水 上交通秩序&#xff1b;核定船舶靠泊安…

Spring Boot集成groovy快速入门Demo

1.什么是groovy&#xff1f; Groovy 是构建在 JVM 上的一个轻量级却强大的动态语言&#xff0c;它结合了 Python、Ruby 和 Smalltalk 的许多强大的特性。 Groovy 就是用 Java 写的&#xff0c;Groovy 语法与 Java 语法类似&#xff0c;Groovy 代码能够与 Java 代码很好地结合&…

QQ频道导航退出

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/140413538 长沙红胖子Qt&#xff08;长沙创微智科&#xff09;博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV…

C#中的MD5摘要算法与哈希算法

文章目录 一、哈希算法基础二、MD5 算法原理三、MD5摘要算法四、哈希算法五、C#实现示例MD5算法示例哈希算法示例字符串MD5值对比 六、总结 一、哈希算法基础 哈希算法是一种单向密码体制&#xff0c;它将任意长度的数据转换成固定长度的字符串。这种转换是不可逆的&#xff0…

Java二十三种设计模式-工厂方法模式(2/23)

工厂方法模式&#xff1a;设计模式中的瑞士军刀 引言 在软件开发中&#xff0c;工厂方法模式是一种常用的创建型设计模式&#xff0c;它用于处理对象的创建&#xff0c;将对象的实例化推迟到子类中进行。这种模式不仅简化了对象的创建过程&#xff0c;还提高了代码的可维护性…

WordPress:无法创建新文章?创建新帖子时候页面空白

wordPress中我们新建文章的时候&#xff0c;会遇到页面空白&#xff0c;这个问题是怎么导致呢&#xff1f;我们可以打开F12开发者模式看下报错信息&#xff0c;这是一个警告信息 Warning: Creating default object from empty value in /pub 到数据库 wp_posts中查看生成了很…

SpringBoot新手快速入门系列教程十一:自动生成API文档,Springboot3.x集成SpringDoc

本次项目我们用Maven来做&#xff0c;最近发现gradle其实很多项目的支持比较差&#xff0c;所以项目还是用Maven来新建项目。对比了市面上的几种API生成第三方库&#xff0c;只有springdoc 是能够按照文档就能部署出来的。 官网&#xff1a; OpenAPI 3 Library for spring-bo…

Mac电脑下运行java命令行出现:错误: 找不到或无法加载主类

mac 电脑 问题复现 随手写了一个main方法&#xff0c;想用命令行操作 进入 BlockDemo.java 所在目录&#xff1a; wnwangnandeMBP wn % cd /Users/wn/IdeaProjects/test/JianZhiOffer/src/main/java/com/io/wn wnwangnandeMBP wn % ls -l total 16 -rw-r--r-- 1 wangnan …

Qt文件下载工具

在Qt中实现文件下载功能&#xff0c;通常可以通过多种方式来完成&#xff0c;包括使用 QNetworkAccessManager 和 QNetworkReply 类&#xff0c;或者使用更高级别的 QHttpMultiPart 类。以下是两种常见的实现方法&#xff1a; 方法1&#xff1a;使用 QNetworkAccessManager 和…

芋道框架万字详解(前后端分离)、若依框架、yudao-cloud保姆级攻略

♥️作者&#xff1a;小宋1021 &#x1f935;‍♂️个人主页&#xff1a;小宋1021主页 ♥️坚持分析平时学习到的项目以及学习到的软件开发知识&#xff0c;和大家一起努力呀&#xff01;&#xff01;&#xff01; &#x1f388;&#x1f388;加油&#xff01; 加油&#xff01…

tessy 集成测试:小白入门指导手册

目录 1,创建集成测试模块且分析源文件 2,设置测试环境 3,TIE界面设置相关函数 4,SCE界面增加用例 5,编辑数据 6,用例所对应的测试函数序列 7,添加 work task 函数 8,为测试场景添加函数 9,为函数赋值 10,编辑时间序列的数值 11,执行用例 12,其他注意事项…

Linux 下使用Docker安装redis

redis&#xff1a; 是一个高性能的&#xff0c;键值对的&#xff0c;将数据存储到内存中的非关系型数据库&#xff08;nosql数据库 not only sql&#xff09; 高性能&#xff1a;数据存在内存中&#xff0c;直接访问内存 键值对&#xff1a;新闻id&#xff08;键&#xff09…

C语言 | Leetcode C语言题解之第235题二叉搜索树的最近公共祖先

题目&#xff1a; 题解&#xff1a; struct TreeNode* lowestCommonAncestor(struct TreeNode* root, struct TreeNode* p, struct TreeNode* q) {struct TreeNode* ancestor root;while (true) {if (p->val < ancestor->val && q->val < ancestor-&g…

51单片机6(P0P1P2P3结构框架图)

一、GPIO结构框架图与工作原理 1、接下来我们介绍一下这个GPIO结构框图和工作原理&#xff0c;我们使用51单片机的GPIO分为了P0&#xff0c;P1&#xff0c;P2&#xff0c;P3这四组端口&#xff0c;下面我们就分别来介绍这四组端口它的一个内部结构&#xff0c;只有了解了内部的…

windows环境下基于3DSlicer 源代码编译搭建工程开发环境详细操作过程和中间关键错误解决方法说明

说明: 该文档适用于  首次/重新 搭建3D-Slicer工程环境  Clean up(非增量) 编译生成 1. 3D-slicer 软件介绍 (1)3D Slicer为处理MRI\CT等图像数据软件,可以实行基于MRI图像数据的目标分割、标记测量、坐标变换及三维重建等功能,其源于3D slicer 4.13.0-2022-01-19开…

好用的文献翻译软件

自从我开始使用七星文献翻译阅读器&#xff08;https://lestore.lenovo.com/detail/L116395&#xff09;&#xff0c;我的学术世界仿佛被打开了一扇全新的大门。这款软件不仅承载了让知识无国界的宏伟使命&#xff0c;更以其实惠的价格和强大的功能&#xff0c;成为了我学习和研…

【操作系统】进程管理——死锁(个人笔记)

学习日期&#xff1a;2024.7.13 内容摘要&#xff1a;死锁的概念和三大处理策略 目录 死锁 死锁的概念 死锁、饥饿和死循环的区别 死锁产生的必要条件 死锁的处理策略&#xff1a;预防、避免和解除 预防死锁 破坏互斥条件 破坏不剥夺条件 破坏请求和保持条件 破坏循…

【从0到1进阶Redis】主从复制 — 主从机宕机测试

上一篇&#xff1a;【从0到1进阶Redis】主从复制 测试&#xff1a;主机断开连接&#xff0c;从机依旧连接到主机的&#xff0c;但是没有写操作&#xff0c;这个时候&#xff0c;主机如果回来了&#xff0c;从机依旧可以直接获取到主机写的信息。 如果是使用命令行&#xff0c;来…

STM32MP135裸机编程:唯一ID(UID)、设备标识号、设备版本

0 资料准备 1.STM32MP13xx参考手册1 唯一ID&#xff08;UID&#xff09;、设备标识号、设备版本 1.1 寄存器说明 &#xff08;1&#xff09;唯一ID 唯一ID可以用于生成USB序列号或者为其它应用所使用&#xff08;例如程序加密&#xff09;。 &#xff08;2&#xff09;设备…

笔记 1 : 课本前 2 章

现在开始跟着彭老师学习 arm 。把重要的知识点归拢一下&#xff0c;便于复习。早日学有所成&#xff0c;为国为家为己&#xff0c;更幸福些。 &#xff08;1&#xff09;冯诺依曼架构与哈弗架构&#xff0c;与混合架构&#xff1a; 以及&#xff1a; &#xff08;2&#xff0…