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

news2024/11/24 12:02:59

前言

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

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

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

相关文章

【网站项目】基于jsp的199旅游景点管理系统

&#x1f64a;作者简介&#xff1a;多年一线开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

最安全的飞行器——飞行汽车

飞行汽车采用旋翼机飞行方式&#xff0c;稳定可靠&#xff0c;保证人身安全&#xff0c;可以垂直起降。旋翼机的稳定性在所有航空器中最高的&#xff0c;旋翼机被国际航空界公认为最安全的飞行器&#xff01;增程器采用斯特林发电机。飞行汽车3D。 固定翼飞机在起飞的时候&…

二叉树 - 堆 | 数据结构中的小技巧大作用

&#x1f4f7; 江池俊&#xff1a; 个人主页 &#x1f525;个人专栏&#xff1a; ✅数据结构冒险记 ✅C语言进阶之路 &#x1f305; 有航道的人&#xff0c;再渺小也不会迷途。 文章目录 一、堆的概念及介绍二、结构图示三、堆的代码实现&#xff08;图解&#xff09;3.1 创…

【RT-DETR有效改进】华为 | GhostnetV2移动端的特征提取网络效果完爆MobileNet系列

前言 大家好&#xff0c;这里是RT-DETR有效涨点专栏。 本专栏的内容为根据ultralytics版本的RT-DETR进行改进&#xff0c;内容持续更新&#xff0c;每周更新文章数量3-10篇。 专栏以ResNet18、ResNet50为基础修改版本&#xff0c;同时修改内容也支持ResNet32、ResNet101和PP…

【RT-DETR有效改进】Google | EfficientNetV1一种超轻量又高效的网络 (附代码 + 添加教程)

前言 大家好&#xff0c;我是Snu77&#xff0c;这里是RT-DETR有效涨点专栏。 本专栏的内容为根据ultralytics版本的RT-DETR进行改进&#xff0c;内容持续更新&#xff0c;每周更新文章数量3-10篇。 专栏以ResNet18、ResNet50为基础修改版本&#xff0c;同时修改内容也支持Re…

2024首更---Web Service 教程

Web Services 简介 Web Services 可使您的应用程序成为 Web 应用程序。 Web Services 通过 Web 进行发布、查找和使用。 您应当具备的基础知识 在继续学习之前&#xff0c;您需要对下面的知识有基本的了解&#xff1a; HTMLXML 如果您希望首先学习这些项目&#xff0c;请在…

蓝桥杯-dfs(一)

&#x1f4d1;前言 本文主要是【算法】——dfs使用的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是听风与他&#x1f947; ☁️博客首页&#xff1a;CSDN主页听风与他 &#x1f304;每日一句&#xff1…

RLC如何通过改变频率实现输出稳压

当开关频率工作在容性区域时&#xff0c;容抗抵消完感抗还有剩余&#xff0c;所以容抗感抗可以近似为一个容抗Cr,但加上频率的改变&#xff0c;容抗又可以近似为一个可调电阻 那又改如何控制频率&#xff0c;保持输出稳压&#xff1f; 当输入与输出电压不变时&#xff0c;Rac变…

Oracle 经典练习题 50 题

文章目录 一 CreateTable二 练习题1 查询"01"课程比"02"课程成绩高的学生的信息及课程分数2 查询"01"课程比"02"课程成绩低的学生的信息及课程分数3 查询平均成绩大于等于60分的同学的学生编号和学生姓名和平均成绩4 查询平均成绩小于…

[小程序]API、数据与事件

一、API ①事件监听API 以on开头&#xff0c;用来监听事件的触发&#xff08;如wx.inWindowResize&#xff09; ②同步API 以Sync结尾&#xff0c;且可以通过函数返回值获取&#xff0c;执行错误会抛出异常&#xff08;如wx.setStorageSync&#xff09; ③异步API 类似网页中的…

yum下载源,vim使用

文章目录 yum本地配置lzrsz命令行互传scp(远程拷贝)vim yum本地配置 [rootiZf8z3j2ckkap6ypn717msZ ~]# pwd /root [rootiZf8z3j2ckkap6ypn717msZ ~]# ls /etc/yum.repos.d CentOS-Base.repo epel.repo //本地配置源yum会根据/etc/yum.repo.d路径下的配置文件来构成自己的下载…

pip安装之后还是无法使用问题处理

最近由于需要使用到Python 相关功能&#xff0c; 记录下一些入门小技巧 1 python 下载安装 在window10 环境下载免安装版本&#xff0c; 并解压 安装包下载地址&#xff1a; https://www.python.org/ftp/python/3.12.1/python-3.12.1-embed-amd64.zip 2. 安装pip, 由于是内嵌…

基于无锁循环队列的线程池的实现

目录 出处&#xff1a;B站码出名企路 应用场景 设计实现 等待策略模块 晚绑定 C 中的 override关键字 C中的 default 关键字 C中的 delete 关键字 C中的 explicit 关键字 C中 using 别名技巧 sleep 和 yield的区别 noexcept关键字 volatile关键字 无锁循环队列的…

【计算机网络】TCP握手与挥手:三步奏和四步曲

这里写目录标题 前言三次握手四次挥手三次握手和四次挥手的作用TCP三次握手的作用建立连接防止已失效的连接请求建立连接防止重复连接 TCP四次挥手的作用&#xff1a;安全关闭连接避免数据丢失避免半开连接 总结&#xff1a; 总结 前言 TCP&#xff08;传输控制协议&#xff09…

《游戏-02_2D-开发》

基于《游戏-01_2D-开发》&#xff0c; 继续制作游戏&#xff1a; 首先给人物添加一个2D重力效果 在编辑的项目设置中&#xff0c; 可以看出unity默认给的2D重力数值是-9.81&#xff0c;模拟现实社会中的重力效果 下方可以设置帧率 而Gravity Scale代表 这个数值会 * 重力 还…

MySQL---多表等级查询综合练习

创建emp表 CREATE TABLE emp( empno INT(4) NOT NULL COMMENT 员工编号, ename VARCHAR(10) COMMENT 员工名字, job VARCHAR(10) COMMENT 职位, mgr INT(4) COMMENT 上司, hiredate DATE COMMENT 入职时间, sal INT(7) COMMENT 基本工资, comm INT(7) COMMENT 补贴, deptno INT…

【cucumber】cluecumber-report-plugin生成测试报告

cluecumber为生成测试报告的第三方插件&#xff0c;可以生成html测报&#xff0c;该测报生成需以本地json测报的生成为基础。 所以需要在测试开始主文件标签CucumberOptions中&#xff0c;写入生成json报告。 2. pom xml文件中加入插件 <!-- 根据 cucumber json文件 美化测…

使用docker配置semantic slam

一.Docker环境配置 1.拉取Docker镜像 sudo docker pull ubuntu:16.04拉取的为ununtu16版本镜像&#xff0c;环境十分干净&#xff0c;可以通过以下命令查看容器列表 sudo docker images 如果想删除多余的docker image&#xff0c;可以使用指令 sudo docker rmi -f <id&g…

【深度学习目标检测】十七、基于深度学习的洋葱检测系统-含GUI和源码(python,yolov8)

使用AI实现洋葱检测对农业具有以下意义&#xff1a; 提高效率&#xff1a;AI技术可以快速、准确地检测出洋葱中的缺陷和问题&#xff0c;从而提高了检测效率&#xff0c;减少了人工检测的时间和人力成本。提高准确性&#xff1a;AI技术通过大量的数据学习和分析&#xff0c;能够…

【面试】java并发编程面试题

java并发编程面试题 何为进程?何为线程?JVM拓展为什么程序计数器、虚拟机栈和本地方法栈是线程私有的呢&#xff1f;为什么堆和方法区是线程共享的呢虚拟机栈和本地方法栈为什么是私有的?一句话简单了解堆和方法区单核 CPU 上运行多个线程效率一定会高吗&#xff1f;创建线程…