【Linux】文件IO基础知识——上篇

目录

 前文

一, 系统级——文件操作接口

a. open

b. close

c. write

d. read

二,接口理解

那文件描述符——fd是什么呢?

三,文件描述符分配规则

原理

四,重定向——dup2

简易shell——重定向

五,回看缓冲区

a, 缓冲区刷新策略 

 b, 缓冲区存在哪儿?? 

c, 尝试手搓一个——缓冲区


 前文

关于C语言文件操作,请看本篇博客,详解文件操作&相关函数(超详细!)_文件操作函数_花果山~~程序猿的博客-CSDN博客

对C语言接口进行复习。

在C语言中,fwrite, fgets, fprintf仅仅是C语言特有的接口,几乎每个编程语言都拥有其自己的输入,输出流接口,其目的也就是保证语言的跨平台性(语言直接调用系统接口,这会导致跨平台性减弱),这些语言级别输入输出流接口,本质上是对操作系统的调用。

接下来我们来学习操作系统级别的输入输出函数:

一, 系统级——文件操作接口

a. open

pathname: 要打开或创建的目标文件
flags : 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行 运算,构成 flags
参数 :  以下参数是经过 宏定义
O_RDONLY  Open Read-Only)   : 只读打开
O_WRONLY  Open Write Only)    : 只写打开
O_RDWR      (Open Read Write) : 读,写打开
这三个常量,必须指定一个且只能指定一个。
O_CREAT (Open Creat):    若文件不存在,则创建它。需要使用 mode 选项,来指明新文件的访问权限
O_APPEND :   追加写
O_TRUNC :   如果文件存在,则清空该文件;不存在则创建该文件。
返回值:
成功:新打开的文件描述符
失败: -1

这些选项可以通过按位或运算进行组合,例如:

int fd = open("file.txt", O_RDWR | O_CREAT | O_TRUNC);

参数flags不是普通的参数,我们知道open()函数用于打开或创建一个文件,并返回一个文件描述符。其中,flags参数用于指定打开文件的方式和权限

那一个一个的传参是否有效率呢?我们看flags如何解决,思想:int 为32(64)个比特位,那么每一个比特位都储存一个参数

    2 #include <iostream>                    4 using namespace std;                    6 #define O_ONE 0x1 // 0000 00017 #define O_two 0x2  // 0000 00108 #define O_Tree 0x4 // 0000 01009                                                             10 int func(int flags)                   11 {                                     12   if (flags & O_ONE) cout << "is O_ONE" << endl;13   if (flags & O_two) cout << "is O_two" << endl;14   if (flags & O_Tree) cout << "is O_Tree" << endl;
W> 15 }                                     16                                       17 int main()                            18 {                                     19   func(O_ONE);                        20   func(O_two);                        21   func(O_ONE | O_two); //  0000 0011, 这样就能同时传入多个标志位         22   func(O_ONE | O_two | O_Tree);       23   return 0;                           24 }

我们开始使用一番:

诺:

  8 int main()9 {10   int ret = open("my_open_t.txt", O_RDWR | O_CREAT, 0666);11   if (ret < 0)12   {13     perror("open fault:");14     exit(-1);15   }16   cout << "success" << endl;                                  17                       18   return 0;           19 }     

 而我们用另一个函数,里面有个mode参数,而那个参数本质上就是初始权限,说到初始权限,我们需要了解文件权限有关知识,请参考本篇博文中文件掩码部分:【Linux】权限管理,谁动了我代码?!_代码权限管理_花果山~~程序猿的博客-CSDN博客

那么我们照冒画虎,C语言中"w", "a", 不就有了?

 int pd = open("my_open_t.txt", O_CREAT | O_WRONLY | O_TRUNC);  // "w"
int pd = open("my_open_t.txt", O_CREAT | O_WRONLY | O_APPEND); // "a"

存在文件直接用2参数open。

b. close

用法:open(open返回值)即可关闭文件流

c. write

d. read

关于read的返回值,返回的是读取的字符数,我们在进程通信再一起讲解。

使用还是挺剪简单的:

二,接口理解

我们尝试着打开多个文件:

int pd1 = open("my_open_t.txt", O_RDWR | O_CREAT, 0666);  30  cout << "my_open pd:" << pd1 << endl;                     31  int pd2 = open("my_open_t.txt", O_RDWR | O_CREAT, 0666);  32  cout << "my_open pd:" << pd2 << endl;                     33  int pd3 = open("my_open_t.txt", O_RDWR | O_CREAT, 0666);  34  cout << "my_open pd:" << pd3 << endl;                     35  int pd4 = open("my_open_t.txt", O_RDWR | O_CREAT, 0666);  36  cout << "my_open pd:" << pd4 << endl;                     37                                                            38   close(pd1);  39   close(pd2);  40   close(pd3);  41   close(pd4);      

其中,打开的文件流为什么是从3开始?? 0,1,2这些去哪儿了呢?  原因是:0,1,2这三个流分别就是我们熟知的 stdin, stdout, stderr

我们在用系统调用接口时,可以直接填0(或1,2)这个可以自己试试水

 38  const char* s1 = "afeng\n";                                           39  write(1, s1, strlen(s1)); // stdout  1

在C语言中,stdin,stdout, stderr中这是FILE指针,系统层面,0,1,2是int类型;C语言中FILE本质上是一个结构体类型,stdin,stdout,stderr是存在于库中已经指向实例化的结构体。那么stdin等结构体中是否存在fd呢 ??? 答案是存在的,就在_fileno中

 40  cout << "stdin :" << stdin->_fileno << endl;41  cout << "stdout :" << stdout->_fileno << endl;                        42  cout << "stderr :" << stderr->_fileno << endl;  

那文件描述符——fd是什么呢?

我们要访问文件,就必须先打开文件。而一般来说一个进程,理论上可以打开N个文件,但文件打开肯定是先被加载在内存中,这会消耗内存。那我打开多个文件那我怎么管理起来呢?答案还是:先描述,后组织。系统内核,怎么管理文件打开的呢? 请看下图:

如你所见,fd(文件描述符)就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进 程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件。

三,文件描述符分配规则

尝试下面代码:

      close(0);                                                            39   int fd = open("my_open_t.txt", O_RDWR | O_APPEND);40   cout << "fd : " << fd << endl;

如你所见,当我关闭stdin后,打开的新文件在0下标处,我们能发现其规则:

files_struct 数组当中,找到当前没有被使用的 最小的一个下标 ,作为新的文件描述符。

原理

以关闭键盘文件为例, 我们通过close(0), 本质上就是对键盘文件描述符置为NULL,然后在打开新文件时,在最小下标内容改为新文件描述符,这本质上也是输出重定向。

可能这个不怎么理解,下面请测试一下下面代码:

      close(1);  39   int fd = open("my_open_t.txt", O_RDWR | O_APPEND);  40   cout << "fd : " << fd << endl;  41   cout << "fd cout_success" << endl;                                   42   printf(" fd printf\n");   

如你所见,关闭stdout后,由于stdout结构体中,文件描述符默认的是1,关闭后并打开新的文件,这就导致输出重定向,数据输出到了打开的文件。输入重定向原理也是如此

那这真的是重定向吗? 答案不是,我们只是利用了打开的新文件把最小下标作为文件描述符这一规则而已,那输入输出重定向的实现到底是如何实现???

答案是系统调用函数

四,重定向——dup2

关于dup2的两个参数是那些?这里用一张图进行表示:

补充知识:问在linux中如何实现面向对象语言中的多态??? 函数指针,通过在结构体中包含函数指针,来实现像类中成员方法。

      

简易shell——重定向

在进程管理中我们制作了一个简单shell, 但当时没有实现重定向功能。

今天我们就通过学习到的dup2()覆盖文件标识符的方法来实现

里面代码重合比较严重,就只展示实现的check_TRUNC和 在创建子进程之前,对重定向的特殊处理,代码如下:

   12 #define OUTPUT 1     // 输出重定向13 #define ADD_OUTPUT 2 // 追加输出重定向14 #define INTPUT 3     // 输入重定向16 17 18 // check_TRUNC的目标:19 // 1. 若是重定向则返回需要重定向的文件名位置20 // 2. 并且确定重定向的类型21   int TRUNC_status = -1;22  char* check_TRUNC(char* ptr)23 {24   assert(ptr);25   char* start = ptr;26   char* end = ptr + strlen(ptr) - 1;27   28   while (start <= end)29   {30     if (*end == '>')31     {32       if (*(end - 1) == '>')33       {                                                            34         // 追加输出重定向35         TRUNC_status = ADD_OUTPUT;36         *(end - 1) = '\0';37           end++;38         break;39       }41       TRUNC_status = OUTPUT;42       *end = '\0';43       end++;44       break;                                                       45     }else if (*end == '<')46     {if (*(end - 1) == '<')33       {                                                            34         // 追加输出重定向35         TRUNC_status = INTPUT;36         *(end - 1) = '\0';37           end++;38         break;39       }55       TRUNC_status = INTPUT;56       *end = '\0';57       end++;58       break;59     }60 61     end--;62     if (start > end)63     {64       end = nullptr;65     }66   }67 68   return end;69 }
..................90     // 拆分前先判断重定向,从而找出重定向的文件91     int fd;92     char* ptr = nullptr;
W> 93     if (ptr = check_TRUNC(instruct))94     {95       // 给出重定向解决方案96       switch(TRUNC_status)97       {98         case ADD_OUTPUT:                                           99           fd = open(ptr, O_WRONLY | O_APPEND | O_CREAT, 0666);100           if (fd < 0)101           {102             perror("pd:");103             exit(-1);104           }105           dup2(fd, 1);106             break;108         case OUTPUT:109           fd = open(ptr, O_WRONLY | O_TRUNC | O_CREAT, 0666);110           if (fd < 0)111           {112             perror("pd:");113             exit(-1);114           115           dup2(fd, 1);116           break;118         case INTPUT:119           fd = open(ptr, O_RDONLY);120           if (fd < 0)121           {122             perror("pd:");123             exit(-1);124           }125           dup2(fd, 0);126           break;130         default:131           printf("what? bug?");132           break;133       }134     }
..........

五,回看缓冲区

首先是为什么需要缓冲区,对数据传输我们有两种模式,一种是写透模式(WT)(说人话是,来一份数据就传递一份),结果就是效率低;另一种是写回模式(WB)(积累一定数据,再传递),效率就比较高。

缓冲区的刷新策略:一般情况 + 特殊情况

a, 缓冲区刷新策略 

一般情况:
1. 立即刷新

2. 行刷新(行缓冲,遇到\n刷新)

3. 全刷新(全缓冲)

特殊情况:
1. 用户强制刷新(fflush函数)

2. 进程退出

一般的设备,都倾向于全缓冲,这样以来就减少了对外设的访问次数,从而提高整机效率。(其中对外设的访问中,外设准备IO操作最废资源,相反数据量并不是主要问题)

但不同使用场景就会有不同的刷新策略:比如:显示器刷新策略,则可以是行刷新。磁盘文件写入 ,刷新策略就会是全刷新。

 b, 缓冲区存在哪儿?? 

我们通过这段代码来引入这个知识,请看下面这段代码:

      printf("%s\n", "C_printf");22   const char* c1 = "const_char_fputs\n";23   fputs(c1, stdout);24   fprintf(stdout, "%s\n", "fprintf");25 26   // 系统接口27   const char* c2 = "系统_write\n";28   write(1, c2, strlen(c2));29 30   fork(); // fork前面的代码虽然被执行完,但并不代表被刷新出来                                                              31   return 0;

结果: 

 

 这里我们可以设想缓冲区是那里储存的呢? 是OS,还是C标准库??   答案是:C标准库,请看下图进行理解

从代码上分析:

1,第一次,我们打印至显示器,我们采用了行刷新。等到执行到了fork函数时,缓冲区已经没有数据了,fork则失去了意义。

2.  第二次, 我们打印至文件中,本质上是向磁盘写入,隐形的刷新策略从行刷新变成全刷新,\n则失去意义。当fork时,前面代码已经执行完毕,C标准库,缓冲区储存着父进程的数据,子进程退出时,触发进程退出的缓冲区刷新机制,向磁盘文件进行写入,此时子进程进行写时拷贝,拷贝缓冲区的数据。

等到父进程退出,至此就有了两份C接口数据。

C语言中有缓冲区,那内核有吗?

诶! 还真有,内核缓冲区的内容我们在网络部分再给大家讲解。

c, 尝试手搓一个——缓冲区

代码也有100行,我们先聊思路。

    #include <stdio.h>2 #include <assert.h>3 #include <string.h>4 #include <stdlib.h>5 #include <unistd.h>6 #include <sys/types.h>7 #include <sys/stat.h>8 #include <fcntl.h>9                                                        10 11 #define NUM 102412 13 struct My_File14 {15   int fd;16   char buffer[NUM];17   int end;  // 记录缓冲区末尾18 };19 20 typedef struct My_File My_File;21 22 My_File* fopen_(const char* path, const char* mode)23 {24   assert(path);25   assert(mode);26   My_File* file = NULL;27 if (strcmp(mode, "w") == 0)29   {30      file = (My_File*)malloc(sizeof (My_File));31      file->fd = open(path, O_WRONLY | O_TRUNC | O_CREAT , 0666);32       if (file->fd < 0)                                33       {34           perror("fail open");35           // 在底层为fopen进行调用36           // 没找到,则要free掉malloc结构体37           free(file);38           return NULL;    39       }40       // 成功打开文件,就要对缓冲区进行处理41       memset(file->buffer, '0', sizeof(file->buffer));42       file->end = 0;43       return file;44   }45   else if (strcmp(mode, "w+") == 0)46   {47 48   }49   else if (strcmp(mode, "r") == 0)50   {52   }53   else if (strcmp(mode, "r+") == 0)54   {55 56   }57   else if (strcmp(mode, "a") == 0)58   {                                                    59 60   }61   else if (strcmp(mode, "a+") == 0)62   {63 64   }else 65   {66     // 都没有匹配67   }68   69   return file;70 }71 72 int fflush_(My_File* file)73 {74   assert(file);75   if (file->end != 0){77   write(file->fd, file->buffer, file->end);78   // 然后调用一个系统级的刷新函数79   syncfs(file->fd);80    file->end = 0;81   } 82   return 0;83 }84 86 int fputs_(const char* str, My_File* file)87 {                                                      88    // 底层还是调用write89    assert(str);90    assert(file);91    // strcpy 一定得跟这end走,不然会出现数据覆盖的问题92    strcpy(file->buffer + file->end, str);93    file->end = file->end + strlen(str);94 95    // 测试一下标准输出96    // file->fd = 1;97 98    if (file->fd == 1)99    {// 标准输出,关于标准输出的策略,我们可以只需给缓冲区,
101      // 其余交给标准输出文件自己的刷新策略。
102      if (file->buffer[file->end - 1] == '\n')
103      {
104        write(1, file->buffer, file->end);
105        file->end = 0;
106      }
107    }else if (file->fd == 2)
108    {                                                   
109      //标准错误输出
111    }
114    return 0;
115 }
116 
117 
118 void fclose_(My_File* file)
119 {
120   assert(file);
121   fflush_(file);
122    free(file);}

六,测试

    int main()  11 {12   close(1);                                                          13   int fd = open("my_ls.txt", O_WRONLY | O_TRUNC | O_CREAT, 0666);14   if (fd < 0)15   {16     cout << "fail open" << endl;17     exit(-1);18   }19  20   printf("hello linux\n");21   close(fd);22   return 0;23 }

请解释一下,为什么在close(1)之后,在显示器和my_ls.txt文件都没有查看到打印的原因??

如图解释:


结语

   本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论,如果给小伙伴带来一些收获请留下你的小赞,你的点赞和关注将会成为博主创作的动力

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【微信小程序】6天精准入门(第3天:小程序flex布局、轮播图组件及mock运用以及综合案例)附源码

一、flex布局 布局的传统解决方案&#xff0c;基于[盒状模型]&#xff0c;依赖display属性 position属性 float属性 1、什么是flex布局&#xff1f; Flex是Flexible Box的缩写&#xff0c;意为”弹性布局”&#xff0c;用来为盒状模型提供最大的灵活性。任何一个容器都可以…

Macos数据库管理:Navicat Premium 中文

Navicat Premium提供了直观且易用的图形用户界面&#xff0c;使得操作更为便捷。Navicat Premium 中文支持多种数据库系统&#xff0c;如MySQL、MariaDB、Oracle、SQLite、PostgreSQL等&#xff0c;可以让用户在同一平台上管理不同类型的数据库。Navicat Premium拥有强大的数据…

3分钟了解 egg.js

Eggjs是什么&#xff1f; Eggjs是一个基于Koajs的框架&#xff0c;所以它应当属于框架之上的框架&#xff0c;它继承了Koajs的高性能优点&#xff0c;同时又加入了一些约束与开发规范&#xff0c;来规避Koajs框架本身的开发自由度太高的问题。 Koajs是一个nodejs中比较基层的…

基于单片机智能汽车仪表设计系统

基于单片机的汽车智能仪表的设计 摘要&#xff1a;汽车的汽车系统。速度测量以及调速是我们这次的设计所要研究的对象&#xff0c;本次设计的基础核心的模块就是单片机&#xff0c;其应用的核心的控制单元就是stc89c52单片机&#xff0c;用到的测速模块是霍尔传感器&#xff0c…

智能垃圾桶丨悦享便捷生活

垃圾桶是人们日常生活所必不可少的必需品&#xff0c;它让生活中所产生的垃圾有了一个正确的存放地方。随着生产技术的迅速发展&#xff0c;垃圾桶也得以更新换代。由最初的简单式的圆筒式垃圾桶&#xff0c;到现在出现的感应式垃圾桶、智能语音控制垃圾桶&#xff0c;垃圾桶也…

JNDI-Injection-Exploit工具安装

从github上下载安装 git clone https://github.com/welk1n/JNDI-Injection-Exploit.git 打开 cd JNDI-Injection-Exploit 编译安装&#xff0c;Maven入门百科_maven中quickstart是什么意思-CSDN博客 mvn clean package -DskipTests 因为提示mvn错误&#xff0c;解决下…

交通目标检测-行人车辆检测流量计数 - 计算机竞赛

文章目录 0 前言1\. 目标检测概况1.1 什么是目标检测&#xff1f;1.2 发展阶段 2\. 行人检测2.1 行人检测简介2.2 行人检测技术难点2.3 行人检测实现效果2.4 关键代码-训练过程 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 毕业设计…

nocos配置中心使用教程(NACOS 1.X版本)

1.下载和安装 进入到官网下载就好了 解压 启动 2.新建cloudalibaba-config-nacos-client3377 2.1 pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://w…

纽交所上市公司埃森哲宣布已收购英国创意管理咨询公司

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 猛兽财经获悉&#xff0c;纽交所上市公司埃森哲(ACN)今日宣布已收购英国创意管理咨询公司The Storytellers。 这笔交易的金额没有披露。 此次收购将增强埃森哲在转型变革方面的能力&#xff0c;并进一步帮助客户阐明和激活…

初识Java 14-1 测试

目录 测试 单元测试 JUnit 测试覆盖率 前置条件 断言 Java提供的断言语法 Guava提供的更方便的断言 契约式设计中的断言 DbC 单元测试 Guava中的前置条件 本笔记参考自&#xff1a; 《On Java 中文版》 测试 ||| 如果没有经过测试&#xff0c;代码就不可能正常工作…

leetCode 5. 最长回文子串 动态规划 + 优化空间 / 中心扩展法 + 双指针

5. 最长回文子串 - 力扣&#xff08;LeetC5. 最长回文子串 - 力扣&#xff08;LeetCode&#xff09;5. 最长回文子串 - 力扣&#xff08;LeetC 给你一个字符串 s&#xff0c;找到 s 中最长的回文子串。如果字符串的反序与原始字符串相同&#xff0c;则该字符串称为回文字符串。…

数据结构----算法--排序算法

数据结构----算法–排序算法 一.冒泡排序&#xff08;BubbleSort&#xff09; 1.冒泡排序的核心思想 相邻两个元素进行大小比较&#xff0c;如果前一个比后一个大&#xff0c;就交换 注意&#xff1a; 在冒泡排序的过程中&#xff0c;促进了大的数往后去&#xff0c;小的数…

Spring事务和事务的传播机制(JavaEE进阶系列7)

目录 前言&#xff1a; 1.为什么需要事务 2.Spring中事务的实现 2.1编程式事务 2.2声明式事务 2.3Transactional的作用范围 2.4Transactional参数说明 2.5Transactional的注意事项 2.6Transactional工作原理 3.事务隔离级别 3.1事务特性的回顾 3.2Spring中设置事务…

区,段,碎片区与表空间结构

区&#xff0c;段&#xff0c;碎片区与表空间结构 结构图 另外在数据库中&#xff0c;还存在着区&#xff08;Extent&#xff09;&#xff0c;段&#xff08;Segment&#xff09;和表空间&#xff08;Tablespace&#xff09;的概念。行&#xff0c;页&#xff0c;区&#xff…

03_51单片机点亮LED灯

51单片机是一种非常常见的单片机型号&#xff0c;广泛应用于各种嵌入式系统和电子设备中。LED灯是一种常见的输出设备&#xff0c;用于显示信息或指示状态。下面是关于51单片机控制LED灯的介绍&#xff1a; 1. 连接LED灯&#xff1a;将LED的正极连接到51单片机的一个I/O引脚&a…

【LeetCode】33. 搜索旋转排序数组

1 问题 整数数组 nums 按升序排列&#xff0c;数组中的值 互不相同 。 在传递给函数之前&#xff0c;nums 在预先未知的某个下标 k&#xff08;0 < k < nums.length&#xff09;上进行了 旋转&#xff0c;使数组变为 [nums[k], nums[k1], …, nums[n-1], nums[0], nums…

【Linux】Ubuntu美化bash【教程】

【Linux】Ubuntu美化bash【教程】 文章目录 【Linux】Ubuntu美化bash【教程】1. 查看当前环境中是否有bash2. 安装Synth-Shell3. 配置Synth-Shell4. 取消greeterReference 1. 查看当前环境中是否有bash 查看当前使用的bash echo $SHELL如下所示 sjhsjhR9000X:~$ echo $SHELL…

在 Android 上恢复已删除音乐的 5 种简单方法

人们经常将重要的音乐文件保存在智能手机上&#xff0c;以方便随时随地收听自己喜欢的曲目。但是&#xff0c;如果这些珍贵的音乐文件因软件故障或硬件故障而被意外删除或丢失怎么办&#xff1f;这将是许多音乐爱好者的噩梦&#xff01; 如果您也是这些人中的一员&#xff0c;…

Linux shell编程学习笔记13:文件测试运算

Linux Shell 脚本编程和其他编程语言一样&#xff0c;支持算数、关系、布尔、逻辑、字符串、文件测试等多种运算。前面几节我们依次研究了 Linux shell编程 中的 字符串运算、算术运算、关系运算、布尔运算 和 逻辑运算&#xff0c;今天我们来研究 Linux shell编程中的文件测…

【设计模式-1】UML和设计原则

说明&#xff1a;设计模式&#xff08;Design Pattern&#xff09;对于软件开发&#xff0c;简单来说&#xff0c;就是软件开发的套路&#xff0c;固定模板。在学习设计模式之前&#xff0c;需要首先学习UML&#xff08;Unified Modeling Language&#xff0c;统一建模语言&…