【操作系统和计网从入门到深入】(四)基础IO和文件系统

前言

在这里插入图片描述
这个专栏其实是博主在复习操作系统和计算机网络时候的笔记,所以如果是博主比较熟悉的知识点,博主可能就直接跳过了,但是所有重要的知识点,在这个专栏里面都会提到!而且我也一定会保证这个专栏知识点的完整性,大家可以放心订阅~# 基础IO

1. 文件描述符预备工作

Linux系统下一切皆文件

1.1 复习C文件接口相关细节

#include <stdio.h>
#include <stdlib.h>
// 复习C语言文件接口
int main()
{
    FILE *fp = fopen("log.txt", "w");
    if (fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    // 文件操作
    fclose(fp);
    return 0;
}

这个文件在哪里创建?

我们实验发现,程序在哪里被执行,log.txt就会在哪里被创建,log.txt是相对路径。

就是在工作目录下创建。

1.2 用C语言相关文件接口模拟实现一个cat命令

// 模拟实现一个cat命令
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        printf("args error!\n");
        return 1;
    }
    FILE *fp = fopen(argv[1], "r"); // 打开这个文件
    if (fp == NULL)
    {
        perror("fopen");
        return 2;
    }
    // 读取文件里面的内容
    char line[64];
    while (fgets(line, sizeof(line), fp) != NULL) // 按照行读取
    {
        fprintf(stdout, "%s", line);
    }
    fclose(fp);
    return 0;
}

三个自动打开的文件描述符,很熟悉了,不再赘述。

1.3 学习系统调用

open

如果打开成功 — 返回文件描述符,如果打开失败,返回-1。

O_WRONLY只负责写,如果没有这个文件,是打不开的!

我们带上O_CREAT就能创建了

但是我们发现, 创建出来的这个文件的权限怎么是个奇怪的东西呢?所以,不像我们C接口创建出来的那么整齐

所以,光光创建是不够的!

一般涉及到文件的创建的时候,我们会传递第三个参数,表示权限。

如果这个文件已经有了

我们就使用两个参数的open就行了 不需要三个参数的,带上O_RDONLY选项 — read only

关闭文件:fclose

int close(int fd);

现在想要往里面写东西了。

write函数!

如果我们往已经有东西的文件里面,再写入一个短一点的字符串。

所以这个是不会帮我们清空文件的。

想要系统帮我们清空还要带上一个选项O_TRUNC

int fd = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);

这样才会帮我们清空。

那如果我想要往文件中追加呢?

O_TRUNC换成O_APPEND

现在,我们来认识一下 读文件的接口 read

read 的返回值我们到进程通信再说 现在我们先不关心

read是不会给我们加 \0 的

1.4 文件描述符这个intFILE*的关系

FILE是一个结构体,是C语言提供的。

C中文件相关库函数内部一定会调用系统调用! 那么在系统角度,认FILE,还是认 fd ? 系统只认fd

FILE结构体里面必定封装了fd!

// 文件描述符和FILE*
int main()
{
    printf("stdin: %d\n", stdin->_fileno);
    printf("stdout: %d\n", stdout->_fileno);
    printf("stderr: %d\n", stderr->_fileno);
    return 0;
}
yufc@ALiCentos7:~/Src/Review/operatingSys/Unit4$ ./test
stdin: 0
stdout: 1
stderr: 2
yufc@ALiCentos7:~/Src/Review/operatingSys/Unit4$ 

同样!先描述再组织!在内核中,OS内部要为了管理每一个被打开的文件,构建struct file{}

1.5 struct file{}

用双链表组织起来。

struct file
{
  	struct file* next;
  	struct file* prev;
  	// 后面的字段 ...
  	// 包含了一个被打开的文件的几乎所有的内容, 不仅仅包含属性
};

所以本质是存在一个数组的!

struct file* array[32]

所以fd的本质就是一个数组下标。

2. 正式开始学习文件描述符

fd的分配规则是,最小的,没有被占用的文件描述符。

然后012是被打开的,这个很熟,所以下一个打开的文件就是3。

2.1 输入重定向和输出重定向

如果我把1文件描述符关了,然后打开一个文件,那么这个新打开的文件的fd就是1,这个很好理解。

所以原本要打印到stdout的东西会打印到新打开的文件中去。

// 输出重定向
int main()
{
    close(1);
    int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC);
    assert(fd >= 0);
    printf("fd: %d\n", fd);
    printf("hello world!\n");
    fflush(stdout); // 不加这个是没有输出的
    close(fd);
    return 0;
}

这里fflush(stdout)其实没有刷新屏幕,其实刷新的是log.txt,因为里面文件描述符是1,而现在1不是显示器,而是log.txt

至于这个代码里面,为什么如果不加fflush(stdout);,会没有输出

因为重定向到文件里面,磁盘文件是全缓冲的(第三节复习缓冲区的时候会讲),所以printf之后在缓冲区里面,所以没有输出,然后按道理来说,程序结束会自动刷新,但是你都close了,肯定就刷新不了了。

所以要不不加close,不用fflush也能有结果

加了close,那么fflush也要加,不然结果被close清理掉了

输入重定向也是一个道理。

当然,重定向不是这样实现的!我们这种方式仅仅只是利用了文件描述符的特点而已。有没有一种方式,可以让我们的不用关闭别人的,也能完成重定向呢?肯定是有的!

2.2 dup2

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

记住:最终想要输出到哪里,哪里的fd就是第一个参数。

  • 如果oldfd不是有效的文件描述符,则调用失败,并且newfd未关闭
  • 如果oldfd是有效的文件描述符,而newfd的值与oldfd相同,则dup2()不执行任何操作,并且
    返回newfd

至于为什么,我们上面那种先close的重定向方法,最后close之后,就不能成功重定向,而左边代码的方法 没问题。
这其实是dup2的一个特性,涉及到缓冲区的概念。

2.3 如何理解一切皆文件(VFS)

3. 缓冲区

缓冲区在哪里?我们写一个代码看看。

这个代码分别调用了C语言的输出函数和系统的输出函数,打印一句话。

// 缓冲区
int main()
{
    // C语言
    printf("hello printf\n");
    fprintf(stdout, "hello fprintf\n");
    const char *s = "hello fputs\n";
    fputs(s, stdout);
    // 系统调用
    const char *ss = "hello write\n";
    write(1, ss, strlen(ss)); // 写到fd=1的文件上->stdout
    fork();
    return 0;
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们先把现象解释一下:

首先

write 只打印了一次 其他的打印了两次

为什么?我们下节课再讲!讲清楚之后,我们可以也可以回答一些尚未解答的现象了!

为什么会这样,我们现在来解释!

关于缓冲区的认识:

  • 一般而言,行缓冲的设备文件 – 显示器

  • 一般而言,全换从的设备文件 – 磁盘文件

  • 所有设备,永远都倾向于全缓冲! 缓冲区满了才刷新->需要更少的IO操作->更少次的外设访问->提高效率!

当和外部设备进行IO的时候,数据量的大小不是主要矛盾, 和外设预备IO的过程才是最耗费时间的

其他刷新策略是,结合具体情况做的妥协!

为什么fork()之后拷贝一份?

  1. 如果向显示器打印,刷新策略是行刷新,那么最后执行 fork的时候,一定一定是函数执行完了 && 数据已经刷新了

  2. 如果对应的程序做了重定向,本质是向磁盘文件打印 — 隐性的刷新策略变成了全缓冲!此时代码的已经没有意

    义了,所以fork的时候,函数执行完了,但是数据没有刷新!现在数据在,当前进程的C标准库中!

  3. 这部分数据,属不属于父进程的数据?肯定是的!fork之后,父子各自执行自己的退出。进程退出是需要刷新缓冲区的!

  4. 那么现在的一个问题,刷新这个动作,算不算“写”?算的,从缓冲区刷新出去,相当于写到显示器里

  5. 此时会有写时拷贝!

  6. 所以!C的接口会出现两份的数据!

4. 文件系统和inode

4.1 背景知识1

int main()
{
    // C
    printf("hello printf\n");
    fprintf(stdout, "hello fprintf\n");
    perror("hello perror"); // stderr
    // 系统调用
    const char* s1 = "hello write(stdout)\n";
    const char* s2 = "hello write(stderr)\n";
    write(1, s1, strlen(s1));
    write(2, s2, strlen(s2));
    // C++
    std::cout << "hello cout" << std::endl;
    std::cerr << "hello cerr" << std::endl;
    return 0;
}

这个代码直接运行,肯定是这样的。

但是如果重定向一下呢?

1,2都是显示器文件,但是他们 两个是不同的显示器文件! 我们可以认为,同一个显示器文 件,被打开了两次!

一般而言,如果程序运行有可能 有问题的话,建议使用stderr来打 印! 如果是常规打印,建议用stdout 打印。

然后区分之后,我们可以这么运行,可以把正确的和错误的分开打印到文件里面去。

可以理解成,把fd为2的放到err.txt里面去。

另外cat还有一个用法:

cat < log.txt > back.txt

这个表示,把log.txt的内容交给cat,cat准备向显示器打印,但是此时再次重定向到back.txt上,所以最终就是,log.txt的内容完成一次拷贝到back.txt上!

4.2 背景知识2

学习文件系统要掌握的背景知识:

  1. 我们以前学习的都是被打开的文件,那们有没有没有被打开的文件?当然存在,在磁盘里 2. 我们学习磁盘级别的文件,我们侧重点在哪里呢?

  2. 单个文件的角度 — 这个文件在哪里?这个文件多大?这个文件的其他属性是什么? 站在系统的角度, 一共有多少个文件?各自属性在哪里?如何快速找到?我还可以存储多少 个文件?如何快速找到制定的文件?

  3. 如何进行对磁盘文件进行分门别类的存储,又来支持更好的存取?

所以,我们先要了解磁盘

磁盘具体构造,寻址方式,可以看看以前的课件/ppt。

一个重要概念:虽然磁盘的基本单位是扇区(512字节) 但是操作系统(文件系统)和磁盘进行IO的基本单位是:4kb

为什么?

  1. 太小了,有可能会导致多次的IO,进而导致效率降低
  2. 如果OS使用和磁盘一样的大小,万一磁盘基本大小变了的话,OS的源代码要不要改呢? 所以硬件和软件(OS)进行解耦。

4.3 文件系统构造

4.4 如果文件特别大怎么办

一个block放不下怎么办?

在data block中,不是所有的datablock只能存文件数据,也可以存其他块的块号!

我们通过索引找到一个块之后,可以通过这个块继续找到下面的块这样就解决了要存大文件的问题

找到一个文件的步骤:inode 编号 -> 分区特定的bg -> inode -> 属性 -> 内容

现在的问题是,inode编号是怎么得到的?

在Linux文件属性中国呢,是没有文件名这个东西的。

  1. 在一个目录下,可以保存很多文件,但是这些文件名是不能重复的!
  2. 目录是文件吗?是 -> 所以目录也有自己的inode,也有自己的datablock!

一个文件的文件名,是存在datablock里面存的!

datablock里面存了:文件名和inode编号的映射关系!

下面我们要回答三个问题:

  1. 创建文件,系统做了什么
  2. 删除文件,系统做了什么?
  3. 查看文件,系统做了什么?

为什么删除总比拷贝快很多?因为删除的时候,不用把内容这部分东西真的删掉,只需要把位图标记改了就行了。

所以,删了的东西能恢复吗?肯定是可以的,只是我们不会而已 我们只要找到原来的inode,找到磁盘的位置(删除日志)只要它还没被覆盖就一定能找到。

一道面试题:

为什么还有空间,但是一直不能创建文件呢?可能就是因为inode申请不下来文件无法创建。

如果创建出来,也只有个文件名没有inode这个时候一写就会失败,一写就会失败的,无法写入。

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

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

相关文章

Vue2 - keep-alive 作用和原理

目录 1&#xff0c;介绍和作用2&#xff0c;原理3&#xff0c;使用场景3.1&#xff0c;效果展示3.2&#xff0c;实现思路 1&#xff0c;介绍和作用 <!-- 非活跃的组件将会被缓存&#xff01; --> <keep-alive><component :is"activeComponent" />…

将AWS iot消息数据发送Kinesis Firehose Stream存向S3

观看此文章之前&#xff0c;请先学习AWS iot的数据收集&#xff1a; 使用Linux SDK客户端向AWS Iot发送数据-CSDN博客 1、工作原理&#xff1a; 1.1 规则 规则可让您的设备与 AWS 服务进行交互。分析规则并根据物品发送的消息执行操作。您可以使用规则来支持任务&#xff0…

前端开发提高效率的两大工具

一、浏览器中的开发者工具 怎么启动开发者工具&#xff1f; 在浏览器中按下F12或者鼠标右键点击检查 怎么利用&#xff08;常用的几点&#xff09;&#xff1f; 1、元素 点击标红的图标可以用于在页面选择元素&#xff0c;同时右侧会找到元素在前端代码中的位置 点击下方红…

2023 IoTDB Summit:中核武汉核电运行技术股份有限公司主管工程师方华建《IoTDB在核电数字化转型过程的应用实践》...

12 月 3 日&#xff0c;2023 IoTDB 用户大会在北京成功举行&#xff0c;收获强烈反响。本次峰会汇集了超 20 位大咖嘉宾带来工业互联网行业、技术、应用方向的精彩议题&#xff0c;多位学术泰斗、企业代表、开发者&#xff0c;深度分享了工业物联网时序数据库 IoTDB 的技术创新…

Pycharm终端显示PS而不显示虚拟环境venv

PS表示当前使用的是powershell.exe&#xff0c;如果你要显示虚拟环境名&#xff0c;则要改为cmd.exe 解决办法&#xff1a; 打开File-settings-Tools-Terminal-shell path 在文件中找到设置&#xff0c;在工具中找到终端 把第四个Shell路径设置为cmd.exe 3. 点击确定&#xf…

c#算法(10)——求点到直线的距离

前言 在上位机软件开发领域,特别是机器视觉领域,经常会遇到尺寸测量的场景,比如让我们求一个点到一条直线的距离,我们已知了直线上的两个点的坐标,然后又已知了直线外的一个点的坐标,那么如何求出该直线外的一点到直线的距离呢?本文就是来讲解如何求点到直线的距离的,…

uniapp page宽度设置为750rpx,子元素宽度100%,大小不一致

uniapp page宽度设置为750rpx&#xff0c;子元素宽度100%&#xff0c;大小不一致。 原因是我在page加了margin: 0 auto;去掉就正常了&#xff08;但是如果在超大屏幕还是会出现&#xff0c;我猜是使用rpx导致的&#xff0c;rpx渲染成页面时会转成精确到一个小数点几位数的rem&a…

【jetson笔记】vscode远程调试

vscode安装插件 vscode安装远程插件Remote-SSH 安装完毕点击左侧远程资源管理器 打开SSH配置文件 添加如下内容&#xff0c;Hostname为jetson IP&#xff0c;User为登录用户名需替换为自己的 Host aliasHostName 192.168.219.57User jetson配置好点击连接&#xff0c;控制台输…

详细Nginx和PHP-FPM的进程间通信使用

工作中考虑到PHP-FPM效率&#xff0c;发现PHP-FPM和NGINX的进程通信不止配置端口这一种方式:bowtie: Nginx和PHP-FPM的进程间通信有两种方式,一种是TCP,一种是UNIX Domain Socket. 其中TCP是IP加端口,可以跨服务器.而UNIX Domain Socket不经过网络,只能用于Nginx跟PHP-FPM都在同…

递归和尾递归(用C语言解斐波那契和阶乘问题)

很多人都对递归有了解&#xff0c;但是为尾递归很少&#xff0c;所以这次来专门讲一讲关于尾递归的一些问题。 什么是尾递归 如果一个函数中所有递归形式的调用都出现在函数的末尾&#xff0c;我们称这个递归函数是尾递归的。因为在一些题目的做法中&#xff0c;我们可以发现…

uniapp scroll-view用法[下拉刷新,触底事件等等...](4)

前言:可滚动视图区域。用于区域滚动 话不多说 直接上官网属性 官网示例 讲一下常用的几个 scroll 滚动时触发 scrolltoupper 滚动到顶部或左边&#xff0c;会触发 scrolltoupper 事件 scrolltolower 滚动到底部或右边&#xff0c;会触发 scrolltolower 事件 1.纵向滚动…

面向对象、封装、继承、多态、JavaBean

二、面向对象 什么是对象 什么是对象&#xff1f;之前我们讲过&#xff0c;对象就是计算机中的虚拟物体。例如 System.out&#xff0c;System.in 等等。然而&#xff0c;要开发自己的应用程序&#xff0c;只有这些现成的对象还远远不够。需要我们自己来创建新的对象。 1. 抽…

fpga外置flash程序烧录流程

Fpga外置FLASH程序烧录流程&#xff1a; step1&#xff1a; 打开vivado2019.2软件&#xff0c;找到hardware manager选项&#xff0c;进入该功能界面&#xff1b; Step2&#xff1a; 确定连接状态&#xff0c;当JTAG正确连接到板卡的调试插针后&#xff0c;会在状态窗口显示…

【嵌入式学习】网络通信基础-项目篇:简单UDP聊天室

源码已在GitHub开源&#xff1a;0clock/LearnEmbed-projects/chat 实现的功能 客户端功能&#xff1a; 上线发送登录的用户名[yes] 发送消息和接收消息[yes] quit退出 服务器端功能&#xff1a; 统计用户上线信息&#xff0c;放入链表中[yes] 接收用户信息并给其他用户发送消…

模型选择实战

我们现在可以通过多项式拟合来探索这些概念。 import math import numpy as np import torch from torch import nn from d2l import torch as d2l生成数据集 给定x&#xff0c;我们将使用以下三阶多项式来生成训练和测试数据的标签&#xff1a; max_degree 20 # 多项式的最…

Redis中BigKey的分析与优化

Redis中BigKey的分析与优化 Redis以其出色的性能和易用性&#xff0c;在互联网技术栈中占据了重要的地位。 但是&#xff0c;高效的工具使用不当也会成为性能瓶颈。在Redis中&#xff0c;BigKey是常见的性能杀手之一&#xff0c;它们会消耗过多的内存&#xff0c;导致网络拥塞…

4 课程分类查询

4 课程分类查询 4.1 需求分析 下边根据内容管理模块的业务流程&#xff0c;下一步要实现新增课程&#xff0c;在新增课程界面&#xff0c;有三处信息需要选择&#xff0c;如下图&#xff1a; 课程等级、课程类型来源于数据字典表&#xff0c;此部分的信息前端已从系统管理服…

将vue组件发布成npm包

文章目录 前言一、环境准备1.首先最基本的需要安装nodejs&#xff0c;版本推荐 v10 以上&#xff0c;因为需要安装vue-cli2.安装vue-cli 二、初始化项目1.构建项目2.开发组件/加入组件3. 修改配置文件 三、调试1、执行打包命令2、发布本地连接包3、测试项目 四、发布使用1、注册…

从零开始训练 YOLOv8最新8.1版本教程说明(包含Mac、Windows、Linux端 )同之前的项目版本代码有区别

从零开始训练 YOLOv8 - 最新8.1版本教程说明 本文适用Windows/Linux/Mac:从零开始使用Windows/Linux/Mac训练 YOLOv8 算法项目 《芒果 YOLOv8 目标检测算法 改进》 适用于芒果专栏改进 YOLOv8 算法 文章目录 官方 YOLOv8 算法介绍改进网络代码汇总第一步 配置环境1.1 系列配…

【GitHub项目推荐--不错的 Go 学习项目】【转载】

开源实时性能分析平台 Pyroscope 是基于 Go 的开源实时性能分析平台&#xff0c;在源码中添加几行代码 pyroscope 就能帮你找出源代码中的性能问题和瓶颈、CPU 利用率过高的原因&#xff0c;调用树展示帮助你理解程序&#xff0c;支持 Go、Python、Ruby 语言。 Pyroscope 可以…