基于操作系统的基础IO

news2024/11/28 2:54:55

1.c语言中的文件输入输出

在观看本章节的时候,大家可以去看看之前c语言的文件输入输出的有关博客。

好那么废话不多说之间进入本章正题:

我们都知道c程序默认会打开三个输入输出流:

  • stdin:相当于键盘

  • stdout:相当于显示器

  • stderr:相当于显示器

    这些输入输出流的类型为FILE* ,其由c语言提供,当作文件来处理

    下面我将引进一个概念,其可能在现阶段听起来很突兀,不过随着之后的逐步讲解会越来越感同身受-----一切皆文件法则(脑海中先有这个意识就好)

2.文件的调用接口介绍

在我们介绍之前先回想一下一些我们曾经用过的文件操作细节:

比如fputs函数是向一般文件或者硬件设备写入,其本质是在磁盘上写,由此可推测我们之前所设计到的几乎所有语言的有关文件操作符的底层一点需要使用os的系统调用。

2.1linux下系统调用函数open介绍

open 函数具体使用哪个,和具体应用场景相关,如目标文件不存在,需要open创建,则第三个参数表示创建文件的默认权限,否则,使用两个参数的open。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
pathname: 要打开或创建的目标文件
flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。(其实就是位运算,这些选项可以看出#define 提前设置好的)
参数:
 O_RDONLY: 只读打开
 O_WRONLY: 只写打开
 O_RDWR : 读,写打开
 这三个常量,必须指定一个且只能指定一个
 O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
 O_APPEND: 追加写
  返回值:
 成功:新打开的文件描述符
 失败:-1
2.2write ,read, close, lseek介绍

在Linux下,这几个函数用于文件IO操作:

  • write: 将数据写入文件,语法为write(fd, buf, count)。其中fd是文件描述符,buf是存放数据的缓冲区,count是要写入的字节数。
  • read: 从文件读取数据,语法为read(fd, buf, count)。和write类似,读取fd文件描述符的文件,读取的数据存放在buf缓冲区,读取的最大字节数为count。
  • close: 关闭文件,语法为close(fd)。关闭文件描述符fd对应的文件。
  • lseek: 移动文件读写指针,语法为lseek(fd, offset, whence)。它可以移动fd文件的读写指针,offset是移动的字节数,whence可以是:

SEEK_SET: 相对文件开始处移动
SEEK_CUR: 相对当前位置移动
SEEK_END: 相对文件结束处移动

具体使用例子:

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

int main() {
  // 打开文件
  int fd = open("test.txt", O_RDWR); 
  
  // write写入10个字节
  char buf[10] = "hello";
  write(fd, buf, 10);
  
  // lseek移动5字节
  lseek(fd, 5, SEEK_SET); 
  
  // 读5个字节
  char buf2[5];
  read(fd, buf2, 5); 
  printf("%s\n", buf2); // world
  
  // 关闭文件
  close(fd);
} 
2.3open的返回值

总结一下上面的内容:上面的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数(libc)。

而, open close read write lseek 都属于系统提供的接口,称之为系统调用接口,回顾之前的一张os各部分分层图系统调用接口和库函数的关系,一目了然。

所以,可以认为,f#系列的函数,都是对系统调用的封装,方便二次开发。

从上面open的描述中可以知道,其返回值类型是一个int整形

Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2.

0,1,2对应的物理设备一般是:键盘,显示器,显示器

所以输入输出还可以采用如下方式:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
 char buf[1024];
 ssize_t s = read(0, buf, sizeof(buf));
 if(s > 0){
 buf[s] = 0;
 write(1, buf, strlen(buf));
 write(2, buf, strlen(buf));
 }
 return 0;
}
2.4files_struct

​ 其实所有文件操作本质上是进程去执行一些对应的函数,进程对文件操作时,必须先打开文件,而打开文件的本质是把文件的相关属性加载到内存中。

一个进程往往可以打开多个文件,所以操作系统得去管理,那怎么去管理呢?

我们的老话"先管理,在组织"便派上了用处,原来在每个进程的pcb的结构体中给了一个指针*file指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件

如图:(这里补充说明一点,这个数组当中存的地址实际上是对应文件的虚拟地址)

在这里插入图片描述

2.5从硬件层在来看问题

在这里插入图片描述

实质上不同硬件的读写方式是不一样的,而为了解决这一问题 ,vfs(虚拟文件系统)便诞生了

vfs不关心到底是什么外设,只是创建一个又一个一模一样的结构体(files_struct)以文件模仿硬件,一切皆文件这个概念在这里再次显现!

3.文件描述符的分配规则

​ 规则:给新文件分配的fd,是从fd_array中找一个最小的,没有被使用的分配出去,作为新的fd。(0,1,2默认被打开) -----不管什么情况牢记这个规则就行了

4.重定向

先来看这样的一段代码:

int main()
{
    close(1);
    int fd = open("./log.txt",O_CREAT | O_WRONLY , 00644);
    printf("hello world\n");
}

会观察到这样的一个现象------所有的文件都没有在屏幕上打印出来,而是写入了log.txt文件内部,这其实就是输出重定向的现象。

为什么呢?

在这里插入图片描述

​ 这张图解释的就很好。printf为c语言中的打印本质是向标准输出打印(stdout 类型为FILE* 实质上为语言层上的结构体),可以推测出这个FILE 结构体中一定包含了一个整数,是对应在系统层面的打开这个文件所对应的fd,而我们的close相当于把fd为1的数组里面置换成了nullptr,而此刻在给open打开的文件分配fd的时候,按照文件描述符的分配规则,分配到了1,所以此时printf实际上是在文件当中写入内容。

5.dup2系统调用

上面的例子是在文件未打开的时候用了close并利用文件打开时新分配fd来实现重定向的,那么我们应该如何在文件已经打开后完成重定向呢?

dup系列的函数可以做到这一点。

dup2() 是一个系统调用函数,用于复制文件描述符。它可以将一个已经打开的文件描述符复制到另一个文件描述符,从而使得这两个文件描述符都指向同一个文件、管道或者 socket 等。

dup2() 函数的定义如下:

#include <unistd.h>

int dup2(int oldfd, int newfd);

其中,oldfd 是要被复制的文件描述符,newfd 是要复制到的文件描述符。dup2() 函数返回值为新的文件描述符,如果出现错误则返回 -1。

当 dup2() 调用成功后,newfd 所对应的文件描述符将指向与 oldfd 所对应的文件相同的文件对象。如果 newfd 已经打开,dup2() 函数会先关闭 newfd,然后将其复制为 oldfd。如果 oldfd 和 newfd 相等,则不执行任何操作。

dup2() 函数一般用于重定向标准输入、标准输出和标准错误输出。例如,如果我们想要将命令行程序的标准输出重定向到文件,可以使用 dup2() 函数将标准输出文件描述符(stdout)复制到文件描述符对应的文件上,代码示例如下:

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

int main() {
    int fd = open("output.txt", O_WRONLY | O_CREAT, 0644);
    dup2(fd, STDOUT_FILENO);
    printf("Hello, world!\n"); // 该语句的输出将被重定向到 output.txt 文件中
    close(fd);
    return 0;
}

在上述示例中,我们首先通过 open() 函数打开了一个名为 “output.txt” 的文件,并将其文件描述符保存在变量 fd 中。随后,我们使用 dup2() 函数将标准输出文件描述符(STDOUT_FILENO)复制到 fd 对应的文件上,这样,所有输出语句(如 printf())的输出就会被重定向到 “output.txt” 文件中。最后,我们关闭了文件描述符 fd 并退出程序。

需要注意的是,如果在使用 dup2() 函数时 newfd 所对应的文件描述符已经被打开,那么它将会被先关闭,这意味着对于 newfd 所对应的文件的任何更改都将丢失。因此,在使用 dup2() 函数时,应该确保 newfd 不会对应任何重要的文件描述符,或者在调用 dup2() 函数之前先保存 newfd 的状态。

6.对FILE的进一步理解

6.1概念

其实在我们上文当中就讲过这个问题,其本质就是定义了c语言的缓冲区并且拥有fd的一个由c语言提供的结构体。(在c语言层面上使用类似于fwrite,printf这样的函数时,实际上就是在向文件写入时,这些数据会先放在c语言层面的缓冲区上缓存以提高I/O性能)

当进程退出的时候,会刷新FILE内部的数据到OS的内核缓冲区。

6.2文件内核缓冲区(这里其实是个略讲,后面会有详细讲解)

先看一张图:

在这里插入图片描述

文件内核缓冲区是操作系统内核中用于文件I/O的缓冲区。具体来说,当进行文件读操作时,内核会将文件的数据块读取到内核缓冲区中,然后在刷新到用户缓冲区中。当进行文件写操作时,数据会先被写入用户缓冲区中,然后再被刷新到内核缓冲区中,最后再被写入磁盘或其他设备中(这两种情况实质就是发生系统调用时所发生的)。为了减少数据刷新的次数和性能损失,C 语言提供了多种缓冲区操作函数,如 fread()、fwrite()、fgets()、fputs() 等,它们可以直接访问用户缓冲区,避免了多次数据的多次刷新。

为什么能减少次数呢?这里给大家举个具体例子:
如果要调用write写10次一次写一个字节,那么就得要从用户态切换回内核态10次,如果此时加上了用户级缓冲区,那么这10次写的字节可以先积累在用户态中然后用户级缓冲区里面,在通过一定条件给文件内核缓冲区

6.3缓冲区的刷新策略(用户->OS)
  1. 立即刷新(不缓冲),如进程退出时

  2. 行刷新(行缓冲\n),比如显示器打印

  3. 全缓冲(缓冲区满了才刷新) 当缓冲区满时,标准库会自动将其内容写入文件。

    (默认刷新方式为行缓冲)

6.4缓冲区究竟以一种什么方式存在

在 Linux 下,用户缓冲区和内核缓冲区所分配的内存都是实实在在的物理内存。它们不是虚拟内存,而是直接映射到物理内存中。

当一个进程需要访问文件或设备时,操作系统会将数据从磁盘或设备读取到内核缓冲区中,然后将数据从内核缓冲区复制到用户缓冲区中,最终让进程访问用户缓冲区中的数据。这个过程中,数据的传输是在物理内存中完成的。因此,用户缓冲区和内核缓冲区都是占用物理内存的。

需要注意的是,Linux **中的内存管理机制是基于虚拟内存的。**操作系统会将物理内存分为多个虚拟地址空间,每个进程都有自己的虚拟地址空间。当进程访问内存时,操作系统会将虚拟地址映射到物理地址上,从而让进程能够访问内存。需要补充说明的是,Linux 中的用户缓冲区和内核缓冲区所占用的物理内存是通过操作系统的内存管理机制分配和管理的。在内核中,有专门的内存管理子系统来管理内核空间的内存分配和释放,而在用户空间中,进程的内存分配和释放则由 C 库的 malloc() 和 free() 等内存分配函数来完成。这些函数实际上是通过系统调用 brk() 或 mmap() 来向操作系统请求内存,由操作系统来分配和管理物理内存。因此,用户缓冲区和内核缓冲区所占用的物理内存都是由操作系统来管理的。

6.5几个经典的例子
#include <stdio.h>
#include <string.h>
int main()
{
 const char *msg0="hello printf\n";
 const char *msg1="hello fwrite\n";
 const char *msg2="hello write\n";
 printf("%s", msg0);
 fwrite(msg1, strlen(msg0), 1, stdout);
 write(1, msg2, strlen(msg2));
 fork();
 return 0;
}

打印结果:

hello printf
hello fwrite
hello write

对进程实现输出重定向 ./hello > file(相当于把上面这段代码的运行结果写入了名为"file"的文件当中(代码里面相当于在最前面加个close(1))) , 结果变成了:

hello write
hello printf
hello fwrite
hello printf
hello fwrite

为什么会出现这一现象呢?(ps:以下的缓冲区默认为用户级缓冲区)

  • 一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。

  • printf fwrite 库函数会自带缓冲区(进度条例子就可以说明),当发生重定向到普通文件时,数据

  • 的缓冲方式由行缓冲变成了全缓冲。

  • 而我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后

  • 但是进程退出之后,会统一刷新,写入文件当中。

  • 但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的

  • 一份数据,随即产生两份数据。

  • write 没有变化,说明没有带缓冲区。

7.进一步了解文件系统

在这里插入图片描述

上图为磁盘文件系统图(内核内存映像肯定有所不同),磁盘是典型的块设备,硬盘分区被划分为一个个的block。一个block的大小是由格式化的时候确定的,并且不可以更改。

  • Block Group:ext2文件系统会根据分区的大小划分为数个Block Group。而每个Block Group都有着相

    同的结构组成。政府管理各区的例子

  • 超级块(Super Block):存放文件系统本身的结构信息。记录的信息主要有:bolck 和 inode的总量,

    未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的

    时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个

    文件系统结构就被破坏了

  • GDT,Group Descriptor Table:块组描述符,描述块组属性信息,有兴趣的同学可以在了解一下

  • 块位图(Block Bitmap):Block Bitmap中记录着Data Block中哪个数据块已经被占用,哪个数据块没

    有被占用

  • inode位图(inode Bitmap):每个bit表示一个inode是否空闲可用。

  • i节点表:存放文件属性 如 文件大小,所有者,最近修改时间等

  • 数据区:存放文件内容

    从上图可以看出数据区和超级块(记录相关属性)是分开放置的,那么这两板块的内容是通过什么方式来运作的呢?
    在这里插入图片描述

    以上的图片就做了4个步骤:

  1. 存储属性

内核先找到一个空闲的i节点(这里是263466)。内核把文件信息记录到其中。

  1. 存储数据

该文件需要存储在三个磁盘块,内核找到了三个空闲块:300,500,800。将内核缓冲区的第一块数据

复制到300,下一块复制到500,以此类推。

  1. 记录分配情况

文件内容按顺序300,500,800存放。内核在inode上的磁盘分布区记录了上述块列表。

  1. 添加文件名到目录

新的文件名abc。linux如何在当前的目录中记录这个文件?内核将入口(263466,abc)添加到目录文

件。文件名和inode之间的对应关系将文件名和文件的内容及属性连接起来。

通过上述的理解,下面我们再来理解两个概念:

**硬链接:**我们看到,真正找到磁盘上文件的并不是文件名,而是inode。 其实在linux中可以让多个文件名对应于同一个inode。

**软链接:**硬链接是通过inode引用另外一个文件,软链接是通过名字引用另外一个文件。

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

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

相关文章

【Linux】基本指令(四)

目录 &#x1f348;一.sort指令&#x1f348; &#x1f349;二.uniq指令&#x1f349; &#x1f34a;三.wc指令&#x1f34a; &#x1f34b;四.which指令&#x1f34b; &#x1f34e;五.whereis指令&#x1f34e; &#x1f34f;六.top指令&#x1f34f; &#x1f350;七…

【玩转 Cloud Studio】腾讯Cloud Studio 云端开发环境上手体验

目录 1、Cloud Studio简介 2、功能体验 3、Cloud Studio优点 4、总结 1、Cloud Studio简介 Cloud Studio&#xff08;云端 IDE&#xff09;是基于浏览器的集成式开发环境&#xff0c;为开发者提供了一个稳定快速的云端工作站。用户在使用 Cloud Studio 时无需安装&#xff0c;只…

【LCD应用编程】绘制点、线、矩形框

之前获取LCD屏幕参数信息时了解到&#xff0c;LCD屏是 FrameBuffer 设备&#xff0c;操作 FrameBuffer 设备 其实就是在读写 /dev/fb0 文件。除此之外&#xff0c;LCD屏上包含多个像素点&#xff0c;绘制点、线、矩形框本质是在修改这些像素点的颜色。 目录 1、定义 lcd_color…

VS+Qt — Vistual Studio 2022+Qt6安装教程以及解决Qt Vistual Studio Tools下载慢和VS无法打开.ui进行设计的问题

目录 Vistual Studio 2022下载 Qt下载 Qt Vistual Studio Tools下载 方法1 方法2 方法3 方法4 Vistual Studio 2022配置Qt6 创建Qt项目 若VS无法打开.ui进行设计 Vistual Studio 2022下载 以前因为安装库的关系&#xff0c;已经下载过VS2022了&#xff0c;详细请看这…

C++ 学习 ::【基础篇:05】:C++ 函数重载认识及使用、简单介绍:C++ 支持函数重载的原因

本系列 C 相关文章 仅为笔者学习笔记记录&#xff0c;用自己的理解记录学习&#xff01;C 学习系列将分为三个阶段&#xff1a;基础篇、STL 篇、高阶数据结构与算法篇&#xff0c;相关重点内容如下&#xff1a; 基础篇&#xff1a;类与对象&#xff08;涉及C的三大特性等&#…

CSS之基础扫盲

博主简介&#xff1a;想进大厂的打工人博主主页&#xff1a;xyk:所属专栏: JavaEE初阶 目录 文章目录 二、CSS基础语法 2.1 CSS引入方式 2.1.1 内部样式 2.1.2 外部样式 2.1.3 内联样式 2.2 CSS选择器 2.2.1 标签选择器 2.2.2 类选择器 2.2.3 id选择器 2.2.4 通配符选择器 2.2…

HNU-操作系统OS-实验Lab7

OS_Lab7_Experimental report 湖南大学信息科学与工程学院 计科 210X wolf (学号 202108010XXX) 实验目的 理解操作系统的同步互斥的设计实现;理解底层支撑技术:禁用中断、定时器、等待队列;在ucore中理解信号量(semaphore)机制的具体实现;理解管程机制,在ucore内…

动力电池管理系统(BMS)

BMS技术 目录 BMS技术 一、BMS简介 二、BMS主要功能 1、参数检测 2、剩余电量&#xff08;SOC&#xff09;估计 3、充放电控制 4、热管理 5、均衡控制 6、故障诊断 7、信息监控 8、参数标定 9、CAN总线接口 三、BMS架构组成 1、BMS的拓扑架构 1、1集中式架构的B…

JavaEE进阶(5/27)Spring Boot

目录 1.认识Spring Boot 2.Spring Boot的优点 3.SpringBoot项目创建 4.resource文件夹 和test文件夹 5.使用一个Spring Boot项目 1.认识Spring Boot Spring Boot 中的Boot 是启动引导的意思 如果Spring相比于普通java开发是从走演变到了汽车&#xff0c;那么Spring boot 相比…

【博客历程】比起方法和技巧,我更想谈质量与坚持

【博客历程】比起方法和技巧&#xff0c;我更想谈质量与坚持 文章目录 【博客历程】比起方法和技巧&#xff0c;我更想谈质量与坚持[toc]我的第一篇博客为什么坚持写博客为什么选择CSDN何为质量未来 还没准备好&#xff0c;等我怎样怎样时再 还在犹豫什么时候开始&#xff0c;…

ssm+springboot+java高校图书馆图书借阅座位预约管理系统系统

陕理工图书馆管理系统包括多个功能模块&#xff1a;图书类别管理模块、图书管理模块、读者管理模块、借阅管理模块、预约管理、推荐管理。管理员登入后&#xff0c;维护图书借阅的信息。本文介绍了使用Java技术开发陕理工图书馆管理系统的设计与实现过程&#xff0c;首先对实现…

微信小程序 java+nodejs+vue美食定制个性化点餐推荐系统

社会的发展和科学技术的进步&#xff0c;互联网技术越来越受欢迎。手机也逐渐受到广大人民群众的喜爱&#xff0c;也逐渐进入了每个用户的使用。手机具有便利性&#xff0c;速度快&#xff0c;效率高&#xff0c;成本低等优点。 因此&#xff0c;构建符合自己要求的操作系统是非…

Deathnote

Deathnote 1.主机发现 arp-scan -l2.扫描端口 nmap -Pn -sV -P- -A 192.168.80.132开放了80、22端口 3.访问80端口 修改host文件访问80端口时进行了跳转到deathnote.vuln/wordpress&#xff0c;修改hosts文件&#xff0c;将该域名解析到靶机ip windows hosts文件路径&…

canal server 标准化集群搭建(完结)

4.2. 创建 server 所属集群&#xff1a;选择刚才添加的 “集群名称” server 名称&#xff1a; server_1、server_2、server_3 依次类推 server ip&#xff1a;server 的 ip 地址 admin 端口&#xff1a;canal server 与 canal admin 的通信端口&#xff0c;非生产环境从 2…

Android 12.0Launcher3 电话和短信app图标显示未读短信和未接来电的条数

1.概述 在12.0产品开发中,最近客户有需求要求在电话app图标显示未接来电的条数 在短信app图标上显示未读信息的条数 根据需求首选要在Launcher3的Launcher.java中,启动launcher时,查询未读短信和未接来电 在有未接来电时,更新未接来电的数量 在有未读短信时,更新未读短信的…

基于html+css的图展示95

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

C++内存空间

1.内存空间 在 C 中&#xff0c;内存分为内核空间和用户空间&#xff0c;内核空间由操作系统管理&#xff0c;程序员写的程序在用户空间。 程序运行时&#xff0c;内存主要分成四个区&#xff0c;分别是栈、堆、数据段和代码段。 栈&#xff1a;存储局部变量、函数参数和返回…

今日餐饮美食推荐系统多商家 uniapp+vue微信小程序

此今日美食推荐小程序信息的设计主要有两个用户&#xff0c;会员客户端和管理员服务端两个部分&#xff1a;会员客户主要功能&#xff1b; &#xff08;1&#xff09;会员信息&#xff1a;包括&#xff1a;会员注册、会员登录&#xff0c;包括对个人信息进行修改。 &#xff08…

Redis的常用数据结构之哈希类型

首先这里说的哈希类型针对的是redis中的value的k-v结构 常见的操作命令 hset设置值 hsetnx命令&#xff0c;不存在可以设置&#xff0c;存在设置不成功 hget取值&#xff0c;这里与字符串类型不同是要精确到filed。前面的判断也是基于field来实现的 要是field没有就返回null h…

计算机视觉:卷积步长(Stride)

本文重点 我们前面学习了卷积操作,也学习了填充,本节课程我们学习卷积步长,之前我们使用卷积核进行卷积操作都是在图像的左上角开始,从左到右、从上到下每次移动一步,其实移动多少步是可以变化的,这个移动步数称为步长。 什么是步长 卷积操作中的步长(Stride)是指卷…