Linux知识点 -- 基础IO(二)

news2024/11/17 5:44:42

Linux知识点 – 基础IO(二)

文章目录

  • Linux知识点 -- 基础IO(二)
  • 一、重定向
    • 1.输出重定向
    • 2.输入重定向
    • 3.追加重定向
    • 4.重定向系统调用
    • 5.minishell支持重定向
    • 6.stdout和stderr的区别
    • 7.常规的重定向操作
    • 8.perror的实现
  • 二、Linux下一切皆文件
  • 三.缓冲区
    • 1.缓冲区
    • 2.关于缓冲区的认识
    • 3.用户缓冲区与内核缓冲区
    • 4.用户缓冲区的位置
    • 5.自己设计用户缓冲区


一、重定向

1.输出重定向

在这里插入图片描述
在上面的代码中,fprintf本来是向stdout中打印的,但是stdout关闭了,实际上fprintf事项fd是1的文件中打印,这里log.txt的fd就是1;
运行结果为:
在这里插入图片描述
这就叫做输出重定向
上面的代码将stdout关闭了,并打开log.txt文件,则log.txt文件的fd就是1;
在系统中,stdout就代表着fd为1,所以默认就会向fd为1的文件中打印,而此时fd为1的文件是log.txt,因此就向该文件中打印了;

2.输入重定向

在这里插入图片描述
运行结果:
在这里插入图片描述

3.追加重定向

在这里插入图片描述
运行结果:
在这里插入图片描述

4.重定向系统调用

在这里插入图片描述
在这里插入图片描述
oldfd copy to the newfd -> 最后要和oldfd一样
在这里插入图片描述
最终重定向的fd要是3,dup2的运行结果是newfd和oldfd一样,因此这里3是oldfd,1是newfd;

  • 使用dup2实现输出重定向:
    在这里插入图片描述
    运行结果:
    在这里插入图片描述
    输出重定向到了log.txt中;

  • 使用dup2实现追加重定向:
    在这里插入图片描述
    在这里插入图片描述

5.minishell支持重定向

在进程控制章节我们自己写了shell程序,这里我们在其中添加重定向功能;
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <unistd.h>
  #include <sys/wait.h>
  #include <sys/types.h>
  #include <fcntl.h>
  #include <sys/stat.h>
  #include <assert.h>
  
  #define NUM 1024
  #define SIZE 32
  #define SEP " "
  
  //保存完整的命令行字符串
  char cmd_line[NUM];
  //保存打散之后的命令行字符串
  char* g_argv[SIZE];
  //用于保存环境变量,使其不被刷新覆盖
  char g_myval[64]; 
  
  #define INPUT_REDIR 1
  #define OUTPUT_REDIR 2
  #define APPEND_REDIR 3
  #define NONE_REDIR 0
  
  int redir_status = NONE_REDIR;
  
  
  char* CheckRedir(char* start)
  {
      assert(start);
      char* end = start + strlen(start) - 1;
      while(end >= start)
      {                                                                   
          if(*end == '>')
          {
              if(*(end - 1) == '>')//重定向类型是>> 追加
              {
                  redir_status = APPEND_REDIR;
                  *(end - 1) = '\0';
                  end++;
                  break;
              }
              redir_status = OUTPUT_REDIR;                                
              *end = '\0';
              end++;
              break;
          }
          else if(*end == '<') 
          {
              redir_status = INPUT_REDIR;
              *end = '\0';
              end++;
              break;
          }
          else 
          {
              end--;
          }
      }
  
      if(end >= start)
      {
          //有重定向
          return end;//返回要打开的文件
      }
      else 
      {
          return NULL;
      }
  }
  
  //shell运行原理,让子进程执行命令,父进程等待&&解析命令
  int main()
  {
      extern char** environ;//使用父进程的环境变量,可以通过main函数的参数  ,也可以导入environ指针
        //命令行解释器:一定是一个常驻内存的进程,不退出
      while(1)                                                            
      {
          //1.打印出提示信息 [lmx@localhost myshell]#
          printf("[lmx@localhost myshell]# ");
          fflush(stdout);//由于printf没有加\n,不刷新缓冲区,使用fflush刷>  新
          memset(cmd_line, '\0', sizeof cmd_line);//sizeof可以不使用括号
  
          //2.获取用户的键盘输入,输入的是各种指令和选项:"ls -a -l"
          if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL)
          {
              continue;
          }
          cmd_line[strlen(cmd_line) - 1] = '\0';//去掉输入时的\n
          //2.1 分析是否有重定向
          //"ls -a -l\n\0"
          char* sep = CheckRedir(cmd_line);
          //原理:从后往前检查命令字符串,发现有">, <, >>"的,就将该字符所  在位置变为\0,能够将命令分为两段,左边是命令, 右边是文件
          
          //3.命令行字符串解析:"ls -a -l" -> "ls" "-a" "-l"
          g_argv[0] = strtok(cmd_line, SEP);//第一次调用,要传入原始字符串
          int index = 1;
          if(strcmp(g_argv[0], "ls") == 0)
          {
             g_argv[index++] = "--color=auto";
          }
          if(strcmp(g_argv[0], "ll") == 0)
          {
              g_argv[0] = "ls";
              g_argv[index++] = "-l";
              g_argv[index++] = "--color=auto";
          }
  
          while(g_argv[index++] = strtok(NULL, SEP));//第二次,如果还要继>  续解析原始字符,传入NULL
          //导入环境变量                                                  
          if(strcmp(g_argv[0], "export") == 0 && g_argv[1] != NULL)
          {
              strcpy(g_myval, g_argv[1]);//将环境变量保存到全新字符串中,>  不让它被下一个指令刷新,以至于子进程拿不到环境变量
              int ret = putenv(g_myval);
              if(ret == 0)
              {
                  printf("%s export success\n", g_argv[1]);
              }
              continue;
          }
  
          //4.TODO:内置命令,让父进程(shell)自己执行的命令,叫做内置命令  ,内建命令
          //内置命令本质是shell中的一个函数调用
          if(strcmp(g_argv[0], "cd") == 0)//如果命令是cd,改变工作目录,需  要在父进程实现
                                          //子进程的cd变换的只是子进程的路  径,父进程不会变
          {
              if(g_argv[1] != NULL)
              {
                  chdir(g_argv[1]);//改变工作目录
              }
              continue;
          }
  
          //5.fork()
          pid_t id = fork();
          if(id == 0)//child
          {
              if(sep != NULL)
              {
                  int fd = -1;
                  switch(redir_status)                                    
                  {
                      case INPUT_REDIR:
                          fd = open(sep, O_RDONLY);
                          dup2(fd, 0);
                          break;
                      case OUTPUT_REDIR:
                          fd = open(sep, O_WRONLY | O_TRUNC | O_CREAT, 066  6);
                          dup2(fd, 1);
                          break;
                      case APPEND_REDIR:
                          fd = open(sep, O_WRONLY | O_CREAT | O_APPEND, 06  66);
                          dup2(fd, 0);
                          break;
                      default:
                          printf("BUG?\n");
                          break;
                  }
              }
             // printf("child, MYVAL: %s\n", getenv("MYVAL"));
             // printf("child, PATH: %s\n", getenv("PATH"));
              execvp(g_argv[0], g_argv);
              exit(1);
          }
  
          //father
          int status = 0;
          pid_t ret = waitpid(id, &status, 0);//阻塞等待
          if(ret > 0)
          {
              printf("exit code: %d\n", WEXITSTATUS(status));
          }
      }
      return 0;
  }

6.stdout和stderr的区别

在这里插入图片描述
上面的代码分别向stdout和stderr文件写入了字符;

  • 直接运行的结果是所有字符全部打印到显示器上,说明stdout和stderr都对应的是显示器文件;
    在这里插入图片描述
  • 如果将打印的结果重定向到log.txt中,结果会发生变化,并不是所有的字符都写入了log.txt;
    在这里插入图片描述
    可以看出:重定向过后,只有向1号fd中写的内容被重定向写入到文件中,2号fd的内容依然打印在显示器上;

这是因为:1和2号fd对应的都是显示器文件,但是是不同的,可以认为是同一个显示器文件被打开了两次;
因此重定向后只有stdout的内容写入了log.txt,而stderr的内容依然打印到了屏幕上;
在这里插入图片描述

7.常规的重定向操作

  • 上面的代码中2号fd 的文件无法重定向到普通文件中,经过如下操作:
    在这里插入图片描述
    执行了这条语句后,1号和2号fd的文件内容都重定向到了log.txt中;
    其中2>&1的意思是把1的地址拷贝给2,则2也指向1的显示器文件了,1和2指向的是同一个显示器文件;

  • 文件拷贝
    在这里插入图片描述
    这条指令的意思是先将log.txt的内容重定向输入给cat打印出来,再将打印的结果重定向到back.txt,就相当于把log.txt的内容拷贝给back.txt;

8.perror的实现

在这里插入图片描述
在这里插入图片描述
perror是会打印出错误信息的,这是因为函数中使用了strerror接口,来打印错误信息;

二、Linux下一切皆文件

在这里插入图片描述
所有的Linux文件结构体中都会有读函数和写函数的指针;在这里插入图片描述
虽然底层不同的硬件,一定对应的是不同的操作方法;
但是上面的设备都是外设,每一个设备的核心访问函数都可以是read、write,每一个文件中的读写函数指针都可以指向这两个函数;
读写代码的实现是不一样的,但是在操作系统看来,都是读写,没有任何硬件的差别了;
因此,Linux下一切皆文件;

三.缓冲区

1.缓冲区

  • 由上可知:使用dup2进行输出重定向时,运行程序后,使用cat指令打印log.txt能够直接打印出来;
  • 如果使用系统指令进行重定向:
    在这里插入图片描述
    在这里插入图片描述
    直接打印是打印不出来的;
    如果在输出重定向后加上fflush刷新缓冲区,就可以将内容输出到log.txt了:
    在这里插入图片描述
    在这里插入图片描述

这种现象与缓冲区有关;

  • 缓冲区:就是一段内存空间;
  • 缓冲区的存在主要是为了提高整机效率,提高用户的响应速度;
  • 缓冲区的刷新策略主要有:
    (1)立即刷新;
    (2)行刷新(行缓冲 \n)
    (3)满刷新(全缓冲)
    特殊情况:
    (1)用户强制刷新(fflush)
    (2)进程退出

2.关于缓冲区的认识

一般而言:

  • 行缓冲的设备文件 – 显示器
  • 全缓冲的设备文件 – 磁盘文件

所有的设备,永远都倾向于全缓冲;缓冲区满了,才刷新,这样就需要更少次数的IO操作,更少次的外设访问,能够提高效率;
和外部设备IO的时候,数据量的大小不是主要矛盾,和外设预备IO的过程是最耗费时间的;
显示器,是要给用户看的,一方面要照顾效率,一方面还要照顾用户体验;

3.用户缓冲区与内核缓冲区

下面一段代码:
在这里插入图片描述

  • 正常打印:
    在这里插入图片描述
    打印出4条;
  • 重定向到log.txt打印:
    在这里插入图片描述
    就会打印出7条;
    在这里插入图片描述
    我们是在最后调用的fork,创建子进程之前,上面的语句已经被执行完了;
    向显示器打印时,只打印出了4行文本;
    而向普通文件(磁盘上)打印时,就变成了7行:
    C语言的IO接口是打印了两次;
    系统接口只打印了一次;

上面的代码,并不影响系统接口,如果有缓冲区,那这个缓冲区一定是由C标准库维护的,因为如果是由OS维护的,那上面的代码应该都是一样的效果;

  • 用户缓冲区:
    C标准库为我们提供了用户级的缓冲区,我们平常使用的就是这个,在执行IO操作时,我们先将数据写入用户缓冲区中,再调用系统的IO接口(read、write等)将数据从用户缓冲区写入到内核缓冲区中,而不是直接写入到文件中;
    一旦拷贝完成,该数据就属于内核数据了,再由OS写入文件;
  • 内核缓冲区
    操作系统中也有内核级的缓冲区,用来接收用户缓冲区的数据,并写入到文件中
    在这里插入图片描述
  • 解释现象:
    如果向显示器中打印,刷新策略是行刷新,那么最后执行fork的时候,一定是函数执行完了,且数据已经被刷新了;
    如果对应的程序进行了重定向,要向磁盘文件中打印,隐形的将刷新策略变成了全缓冲,那么字符串最后的\n就没有意义了,这是用来进行行缓冲的;
    因此,在fork的时候,一定是函数已经执行完了,但是数据还没有刷新,还在当前进程对应的C标准库的缓冲区中,这部分数据就是父进程的数据
    而fork一旦执行,创建子进程时发生了写时拷贝,父进程的数据拷贝给了子进程,代码结束后刷新缓冲区,就会将C接口的数据打印两份给磁盘文件;

4.用户缓冲区的位置

FILE结构体中不仅封装了文件描述符fd,也封装了该文件fd对应的语言层缓冲区结构;

  • 如果在fork之前强制刷新:
    在这里插入图片描述
    就会变成打印4条:
    在这里插入图片描述
  • fflush只需传入stdout就能够将数据刷新到缓冲区,就是因为我们打开的文件在进程中的FILE结构体中封装了用户缓冲区的结构;
    在这里插入图片描述

C语言中打开的FILE文件流,必须包含:

  • 文件描述符fd;
  • 缓冲区buffer;

5.自己设计用户缓冲区

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

#define NUM 1024

struct MyFILE_
{
    int fd;//文件描述符
    char buffer[NUM];//缓冲区
    int end;//当前缓冲区的结尾
};

typedef struct MyFILE_ MyFILE;

MyFILE* fopen_(const char* pathname, const char* mode)
{
    assert(pathname);
    assert(mode);

    MyFILE* fp = NULL;

    if(strcmp(mode, "r") == 0)
    {

    }
    else if(strcmp(mode, "r+") == 0)
    {

    }                                                                     
    else if(strcmp(mode, "w") == 0)
    {
        int fd = open(pathname, O_WRONLY | O_TRUNC | O_CREAT, 0666);
        if(fd  >= 0)
        {
            fp = (MyFILE*)malloc(sizeof(MyFILE));                         
            memset(fp, 0, sizeof(MyFILE));
            fp->fd = fd;
        }
    }
    else if(strcmp(mode, "w+") == 0)
    {

    }
    else if(strcmp(mode, "a") == 0)
    {

    }
    else if(strcmp(mode, "a+") == 0)
    {

    }
    else 
    {

    }

    return fp;
}

void fputs_(const char* message, MyFILE* fp)
{
    assert(message);
    assert(fp);

    strcpy(fp->buffer + fp->end, message); 
    fp->end += strlen(message);

    //暂时没有刷新,刷新策略是用户通过执行C标准库中的代码逻辑,来完成刷新>动作
    //这里效率提高,因为C提供了缓冲区,我们可以通过刷新策略,较少了IO的执>行次数
    
    if(fp->fd == 0)
    {
        //标注输入                                                        
    }
    else if(fp->fd == 1)
    {
        //标准输出
        if(fp->buffer[fp->end - 1] == '\n')//如果缓冲区数据最后以\n结尾,>就立即刷新
        {
            write(fp->fd, fp->buffer, fp->end);
            fp->end = 0;
        }
    }
    else if(fp->fd == 2)
    {
        //标准错误
    }
    else 
    {
        //其他文件
    }
}

void fflush_(MyFILE* fp)
{
    assert(fp);

    if(fp->end != 0)
    {
        write(fp->fd, fp->buffer, fp->end);//将数据写入内核
        syncfs(fp->fd);//将输入写入磁盘
        fp->end = 0;
    }
}

void fclose_(MyFILE* fp)
{                                                                         
    assert(fp);
    fflush_(fp);
    close(fp->fd);
    free(fp);
}

int main()
{
    MyFILE* fp = fopen_("./log.txt", "w");
    if(fp == NULL)
    {
        perror("open file error");
        return 1;
    }

    fputs_("lmx uio", fp);

    fork();

    fclose_(fp);


    return 0;
}

运行结果:
在这里插入图片描述

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

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

相关文章

【福利活动】深度体验OpenHarmony对接华为云IoT

本文主要介绍基于OpenHarmony 3.0来接入IoTDA&#xff0c;以BearPi-HM_Nano开发板为例&#xff0c;使用huaweicloud_iot_link SDK对接华为云物联网平台的简单流程。文末为大家提供了的福利&#xff0c;最高可得HUAWEI WATCH FIT手表、华为手环7、HUAWEI FreeBuds SE 无线耳机、…

OSI七层模型——物理层

OSI模型的物理层位于协议栈的底部。它是 TCP/IP 模型的网络接入层的一部分。如果没有物理层&#xff0c;就没有网络。本模块详细介绍了连接到物理层的三种方法。 1 物理层的用途 1.1 物理连接 不管是在家连接本地打印机还是将其连接到另一国家/地区的网站上&#xff0c;在进…

vue 文件扩展名中 esm 、common 、global 以及 mini 、 dev 、prod 、runtime 的含义

vue 文件扩展名中 esm 、common 、global 以及 mini 、 dev 、prod 、runtime 的含义 vue.js 直接用在 script 标签中的完整版本&#xff08;同时包含编译器 compiler 和运行时 runtime&#xff09;&#xff0c;可以看到源码&#xff0c;适用于开发环境。 这个版本视图可以写在…

vue3+ts+elementui-plus二次封装树形表格实现不同层级展开收起的功能

一、TableTreeLevel组件 <template><div classmain><div class"btns"><el-button type"primary" click"expandLevel(1)">展开一级</el-button><el-button type"primary" click"expandLevel(2…

管理类联考——写作——素材篇——论说文——企业管理故事

文章目录 经典管理案例——武装经管知识&#xff0c;让考官刮目相看分槽喂马金蝉脱壳欲取先与窃符救赵尺蠖huo求伸声东击西借尸还魂以静制动釜底抽薪围师必阙撒豆成兵不入虎穴焉得虎子八坛七盖 经典管理案例 ——武装经管知识&#xff0c;让考官刮目相看 纵使是世界顶级的管理…

音视频入门之音频采集、编码、播放

作者&#xff1a;花海blog 今天我们学习音频的采集、编码、生成文件、转码等操作&#xff0c;我们生成三种格式的文件格式&#xff0c;pcm、wav、aac 三种格式&#xff0c;并且我们用 AudioStack 来播放音频&#xff0c;最后我们播放这个音频。 使用 AudioRecord 实现录音生成…

【Chat GPT】用 ChatGPT 运行 Python

前言 ChatGPT 是一个基于 GPT-2 模型的人工智能聊天机器人&#xff0c;它可以进行智能对话&#xff0c;同时还支持 Python 编程语言的运行&#xff0c;可以通过 API 接口进行调用。本文将介绍如何使用 ChatGPT 运行 Python 代码&#xff0c;并提供一个实际代码案例。 ChatGPT …

苹果mac电脑好用的pdf编辑工具PDF Expert 最新中文 v3.2.2

PDF Expert提供了丰富的PDF编辑功能&#xff0c;包括添加、删除、移动、旋转、缩放、裁剪等操作&#xff0c;以及文本、图像、链接、表格、注释等元素的添加和修改。 行云如水&#xff01;阅读PDF文档非常流畅&#xff0c;不管你的文件有多大。编辑PDF文档 以简单快速度编辑PD…

兴达易控 ETHERCAT转PROFIBUS从站网关配置方式

ETHERCAT转PROFIBUS&#xff08;XD-ECPBS20&#xff09;是一款PROFIBUS从站功能的通讯网关。ETHERCAT转PROFIBUS从站网关主要功能是将ETHERCAT设备接入到PROFIBUS网络中。 本网关连接到PROFIBUS总线中做为从站使用&#xff0c;连接到ETHERCAT总线中做为从站使用。 1、配置网关…

基于RWEQ模型的土壤风蚀模数估算及其变化归因分析教程

详情点击公众号链接&#xff1a;基于RWEQ模型的土壤风蚀模数估算及其变化归因分析 前沿 土壤风蚀是一个全球性的环境问题。中国是世界上受土壤风蚀危害最严重的国家之一&#xff0c;土壤风蚀是中国干旱、半干旱及部分湿润地区土地荒漠化的首要过程。中国风蚀荒漠化面积达160…

学习笔记20 Java Collections Framework概览

一、Lists, Sets, and Maps list按位置对元素排序&#xff0c;元素x在列表中的位置&#xff0c;也称为其索引。一个列表允许重复的元素。list通过其索引来区分相同的对象。 set是无序无重复集合。与列表不同&#xff0c;set没有其元素的位置概念。集合的实现通常针对搜索进行…

C++-----stack和queue

本期我们来学习stack和queue 目录 stack介绍 栈的使用 栈的模拟实现 queue介绍 队列的使用 队列的模拟实现 deque 优先级队列 模拟实现 仿函数 全部代码 stack介绍 1. stack 是一种容器适配器&#xff0c;专门用在具有后进先出操作的上下文环境中&#xff0c;其删除…

亚马逊测评自养号:如何正确选择学习对象与获取可靠技术知识?

亚马逊是一家知名的跨境电商平台&#xff0c;吸引了越来越多的人涉足这个领域。随着商家数量的增加&#xff0c;亚马逊的竞争力也在不断提高。在亚马逊平台上&#xff0c;产品评价对于卖家账号的评估以及产品曝光量、销量等方面具有直接影响。因此&#xff0c;对于任何一个希望…

单路 PWM 控制的高调光比 LED 降压恒流控制器

概述 OC5401 是一款单路 PWM 控制的高调光比降压恒流驱动控制器&#xff0c;PWM 调光比最高可达 10000&#xff1a;1。 OC5401 支持 16-60V 输入电压范围。 OC5401 采用电流滞环控制方式&#xff0c;无需环路补偿。 OC5401 可通过外接电阻设置 LED 输出电流&#xff0c;最…

数据库中的事务处理

MySQL的事务处理&#xff1a;只支持 lnnoDB 和BDB数据表类型 1.事务就是将一组SQL语句放在同一批次内去执行 2.如果一个SQL语句出错&#xff0c;则该批次内的所有SQL都将被取消执行 MySQL的事务实现方法一&#xff1a; select autocommit 查询当前事务提交模式 set a…

【第二套】Java面试题

第二套&#xff1a; 一、JavaScript前端开发 1、下列的代码输出什么&#xff1f; var y 1; if(function f(){}){y typeof f; } console.log(y);正确的答案应该是 1undefined。 JavaScript中if语句求值其实使用eval函数&#xff0c;eval(function f(){}) 返回 function f()…

Web前端 常用布局

Flex布局&#xff08;弹性布局&#xff09; 参考&#xff1a;滑动验证页面 space-between与space-around的区别

Python编程很简单,四步菜鸟到高手(文末送书5本)

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

别在找git报错的解决方案啦,多达20条git错误解决方案助你学习工作

1. 找不到Git命令 $ sudo apt-get update $ sudo apt-get install git2. 无法克隆远程仓库 $ git clone https://github.com/username/repo.git3. 无法拉取或推送到远程仓库 $ git pull origin master $ git add . $ git commit -m "Resolve conflicts" $ git pus…

java.lang.ClassNotFoundException: sun.misc.BASE64Decoder

有一个新的应用服务&#xff0c;idea启动应用应用服务时&#xff0c;突然报错java.lang.ClassNotFoundException: sun.misc.BASE64Decoder &#xff0c;然后在网上搜索&#xff0c;说是建议使用apache包&#xff0c;该类新的JRE已经废弃&#xff0c;并从rt.jar包中移除。但是该…