文件想大家都不陌生吧,计算机中所有的数据都存在文件中,但是前面是一个普通人对文件的理解,那么现在我就带你看看程序员眼中的文件
1 语言层面上的文件(C语言)
1.2 C语言接口介绍
在C语言中想要对文件进行操作那么需要用到一下的接口(fopen,fclose,fwrite,fred)对文件进行读写操作
如图所示 : >
代码实现
写形式打开文件
int main()
{
FILE * fd=fopen("log.txt","w");
const char *str="hello word";
fwrite(str,1,strlen(str),fd);
fclose(fd);
return 0;
}
结果:
写/读形式读打开文件
代码:
int main()
{
FILE * fd=fopen("log.txt","w");
FILE * fd=fopen("log.txt","r");
char str[1024];
size_t ret=fread((char*)str,1,1024,fd);
str[ret]='\0';
printf("%s\n",str);
fclose(fd);
return 0;
}
读结果
写结果
追加追加形式写文件
代码
int main()
{
FILE * fd=fopen("log.txt","a");
const char *str="hahahaha~";
fwrite(str,1,strlen(str),fd);
fclose(fd);
return 0;
}
追加结果:
1.2 系统接口介绍
不知大家是否记得,就是用户是无法访问硬件的(要访问则一定要贯穿整个计算机体系结构),一般系统会暴露接口,则库为了方便使用会进行封装,上述介绍的接口,但其实我们也可以直接调用这些系统接口
如图所示 : >
写的形式打开,不存在则创建文件
int mian()
{
int fd = open("log.txt",O_CREAT|O_WRONLY,0x644);
char * str="hello word";
write(fd,str,strlen(str));
close(fd);
}
结果
读形式打开,不存在则创建(读一般情况都在吧)
int main()
{
int fd = open("log.txt",O_CREAT|O_RDONLY,0x644);
char str[1024];
ssize_t ret=read(fd,str,1024);
str[ret]='\0';
printf("%s \n",str);
close(fd);
}
结果
追加形式打开
int main()
{
int fd = open("log.txt",O_WRONLY|O_APPEND);
char * str="hahaha~~~";
write(fd,str,strlen(str));
}
结果
仔细观察系统接口与C语言的接口大致相同,那以后使用C的接口时你就会想到他的底层是如何运作的。但是他们还是有所区别,系统调用返回的是 一个整形(文件描述符) 而C语言是一个FILE * 的指针,后文介绍文件描述符
1.3 三个默认打开输入输出流
不知道大家还记不记得三个标准输入输出流,分别是标准输入,标准输出,标准错误 (stdin,stdout,stderr)所对应的硬件是键盘,显示器,显示器
问
那么为啥要默认打开这三个输入输出流?
答
都有输入输出的要求,且方便操作(如人生第一个程序hello world和你说输入要打开标准输入,打印时要打开标准输出,从入门到入土)
c语言就有对应的接口fprintf,fsanf他们就需自己指定的流输入输出: 如图所示 : >
计算体系结构图 : >
2 系统层面上理解文件
一个文件其实可以划分成俩个板块
- 属性
- 内容(数据)
问
创建一个文件不写入任何数据,会占用内存吗?
答
占用,上述说过一个文件分为俩部分属性与内容 ,而创建文件时显然属性也一定创建了
2.1如何理解linux一切皆文件
大家或多或少动听过linux下一切皆文件,那么这个要如何理解呢?其实只需要理解这六个字先描述在组织
问
当一个进程执行时,是否会打开各种文件,硬件(驱动)?
答
当然会啊,那么那么如果文件和硬件(驱动)打开的多,操作系统(作用: 文件、驱动、进程、内存管理)则需要讲他们管理起来,则管理的本质就是 先描述在组织
问
对文件的操作是??
答
emmm对文件的操作其实就是读写
问
各种硬件不是有不同的读写方式吗?如键盘只有读,我从来没看到过写数据到键盘上的,那么如何组织,如何如题所说一切皆文件呢?
答
确实各种硬件都有不同的读写方式,但是我们可以先描述在组织用 ,那么操作系统管理硬件,那么就可以用相同的方式操作 如图所示 : > stdin对应的是键盘,stdout,stderr对应显示器
2.2 文件描述符 与 FILE
用系统的接口调用返回的一个整数也就是文件描述符,那是如何根据文件描述符来找对对应的文件呢?
代码
int main()
int fd1=open("log1.txt",O_CREAT|O_WRONLY,0x666);
int fd2=open("log2.txt",O_CREAT|O_WRONLY,0x666);
int fd3=open("log3.txt",O_CREAT|O_WRONLY,0x666);
int fd4=open("log4.txt",O_CREAT|O_WRONLY,0x666);
int fd5=open("log5.txt",O_CREAT|O_WRONLY,0x666);
int fd6=open("log6.txt",O_CREAT|O_WRONLY,0x666);
int fd7=open("log7.txt",O_CREAT|O_WRONLY,0x666);
int fd8=open("log8.txt",O_CREAT|O_WRONLY,0x666);
printf("fd1 %d\n",fd1);
printf("fd2 %d\n",fd2);
printf("fd3 %d\n",fd3);
printf("fd4 %d\n",fd4);
printf("fd5 %d\n",fd5);
printf("fd6 %d\n",fd6);
printf("fd7 %d\n",fd7);
printf("fd8 %d\n",fd8);
return 0;
}
结果:
问
嗯居然是连续的,他们不会放在一起吧?为啥第一个文件描述符是从3开始的?
答
连续下标想到了啥?数组呀,没错这些文件底层其实是放在数组中(客观表示),文件描述符为啥从三开始,上述说过编译器会默认打开三个流(输入、输出、错误),自然后面创建的文件描述符只能从3开始
上述说用统一的方式看待硬件实现一切皆文件,但是文件一多就需要管理 ,并用一种数据结构组织起来 ,这里用的数据结构就是数组 如图所示 : >
问
那么C是如何操作的呢?他的返回值不是一个FILE*吗?
答
其实FILE底层是一个结构体并且里面有俩个重要的部分1:C语言缓冲区 2:file_on(fd) 如图所示 : >
验证_filen 是否是fd
int main()
{
printf("%d\n",stdin->_fileno);
printf("%d\n",stdout->_fileno);
printf("%d\n",stderr->_fileno);
int fd=open("log.txt",O_CREAT|O_WRONLY,0x666);
printf("%d\n",fd->_fileno);
}
结果
那么C语言中底层调用文件我们大致就可以猜想出来了(fopen调用open,open返回fd给 fopen ,fopen再把fd写入到 FILE*对象中的_fileon,后面通过接口对文件读写 )
如图所示 : >
2.3 文件描述符的分配规则
文件描述符的分配规则其实很简单,就是数组中最小空余的下标 ,上述例子已经可以看出(标准输入、输出、错误占用下标0,1,2,新开文件就在3下标处)
问
标准输入输出错误流可以关闭吗?
答
既然他是默认打开的,注意默认打开,那么也就是说是被打开的,那么就可以被关闭
代码:
int main()
{
close(0);
int fd=open("log.txt",O_CREAT|O_WRONLY,0x666);
printf("%d\n",fd);
}
结果
问
如果问吧标准输给关闭了,是否会打印呢?应该会吧,标准错误也是显示器
答
其实是不会打印的,虽然stderr也是显示器,但是底层要把数据输显示器上,还是通过stdout去输出 ,但是stdout中的fileon还是2 ,但files_strcuct中的fd 数组下标为2的数据已经被 “抹除” ,那么就找不到显示器文件
如图所示 : >
2.3.2 重定向
重定向:把本该输出到固定位置的数据输出到别出简称重定向 上述说,可以把stdout关闭,但stdout的_fileson还是2 ,且文件描述符分配规则是没使用且下标最小的位置 ,那么我新打开一个文件 ,在输出数据不就是到文件中 了吗,这不就是重定向吗??
验证代码:
int main()
{
close(1);
int fd = open("log.txt",O_WRONLY);
printf("hello world");
return 0;
}
结果:
正常情况下我们不会像上面的操作来完成重定向,但是我们确实需要重定向的需求,C语言中有个接口可以帮助我们实现重定向dup2
接口介绍
接口使用
int main()
{
int fd = open("log.txt",O_WRONLY);
dup2(fd,1);
printf("hello world");
return 0;
}
结果
2.4 创建一个进程的新理解
其实上述中所画的图是有一点错误的,其实FILE*其实是在代码也就是数据,他是在内存中的,根本不可能直接访问files_struct,他们其实用是PCB进行互相访问
如图所示 : >
2.5 缓冲区的理解
上述在介绍文件描述符与FILE*时提到过FILE 是一个结构体,它里面包含了俩个重要的部分 1. 缓冲区 2. files_on, 缓冲区(对IO数据的临时存储的区域 )想必大家已经耳熟能详了吧!
怪异现象(程序结束后调用fork)
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();
}
输出结果 : >
那么把输出的结果重定向到文件中的时候 : >
问
这个应该就是所谓的怪异现象吧,C语言的接口所输出的数据重定向后输出了俩份,这个是为啥呢?
答
确实这就是所谓的怪异现象,其实这都是看似没有用处的fork 与 C语言缓冲区导致的
- 都知道C语言的缓冲区是遇到
\n、\r、fflush、程序结束、缓冲区满了 才会看时机吧数据刷新给内核中的缓冲区,且C缓冲区对显示器是行刷新,对普通文件是全刷新 fork后父子数据是共享 的,那么fork前父进程输出的数据没有刷新,那么就留在C缓冲区 (FILE 结构体,也就是数据),当父亲将数据刷新时,那么就会写实拷贝
这就是为啥输出到屏幕上是正常的,但是重定向后却变得怪异起来
解决怪异现象(提前全刷新,防止写实拷贝)
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));
fflush(stdout);
fork();
}
结果
问
为啥要存在C语言缓冲区 ? 不是有内核系统自带的缓冲区吗?直接刷新的系统的不香吗?
答
我觉得是提高效率,听个故事,现在是早上,你爹现在每5分钟给你2元,并会给你5次,2元只可以买一个包子,那么你是会拿到钱直接去买包子再回来等,还是等钱全部到手再去统一一次性买呢?
刷新策略如图所示 : >
2.6 文件系统
上述介绍的都是打开的文件,那么下面来介绍一下没有打开呆在磁盘上的文件吧!!!
这里有一个问题就是打开的文件要管理,那么没打开的文件需要进行管理吗?
那一定要管理呀,不然你如果需要打开文件去哪里找?搜索整个磁盘吗?
硬件部分: 机械硬盘,固态硬盘(ssd),光盘,磁带。现在磁带光盘应该已经退出时代的舞台了,现在基本用的都是机械硬盘或者是固态硬盘。由于ssd的工艺太高,那么我就介绍一下机械硬盘吧!
如图所示 : >
问
那如何吧数据写入固态硬盘中?open(“盘片/磁道/扇区/文件名”,……)?
答
你不觉得这个很麻烦吗?且不通用吧,假如你有钱了换个存储器呢?ssd那不是又要改。所以一切都需要抽象一下,直接把底层看成一个线性结构,像磁带一样只是卷在一起了 (数组等),一个盘对应一个数组,可以把他抽象为多维数组(in t arr[盘面][磁道][扇区] ,如何存储器都是如此),底层维护这种关系的是对应存储器的驱动
补充1 :
都知道,需要执行一个程序一定要把程序的加载到内存中,那么程序底层不过就是代码数据,那这些都是存在磁盘中的。内存IO大小是4字节,且他也是有自己基础单位是1字节
补充2 :
当存储器用统一的方式看待后,管理又是一个难题,假设存储器的大小为2个T,这时就是一个巨大的数组。分区不知道大家是否听过 (分区:把大空间的盘分为多个小空间的盘,方便操作系统使用和管理 ),没错这里就要进行分区。
//stata 文件 的截图
2. 6.2 文件系统
LINUX文件系统
- EXT2
- EXT3
- fs
- usb-fs
- sysfs、proc
- ……
每一个分区所使用的管理系统不一定是一样 的这个要看OS
EXT2文件系统介绍
inode
每一个文件,目录,都有一个Inode,且Inode中存放的是文件的属性信息,但是不包括文件名,查看inode 在ls后面+i选项
问
通过上面的EXT2文件系统现在可以知道一个文件在磁盘上是如何存放的 (inode table 存放文件的属性,Data blokcks 存放数据 ),但是他们是如何联系的呢?
答
创建一个文件那么就会申请Inode存文件的属性信息,在申请blocks存放数据内容,他们如何管理,每个inode里面有一个数组存的是blocks的编号 (存放内容的block) 如图所示 : >
问
我好奇Block bitmap 和 Inode bitmap 是计数让我们知道Inode和blocks的使用情况?用一个整形来计数吗?
答
其实计数也不是不行但是,这里用了却更加妙,这里使用的位图 (用一个bit位来标识状态),相比计数他可以很快的找到那个Inode和blocks使用或者没有使用 如图所示 : >
那么现在要操作一个文件我是如何找他的呢?
不知道大家是否还记得,目录结构其实是一个树状结构 的,且上面也说过目录也是有自己的Inode(目录也是文件,也有自己的属性),那么他的内容是啥 呢?当然是里面文件的文件喽(与文件对应的Inode) ,毕竟一个当父亲(目录)不可能不记得自己儿子(文件)的名字(文件名)和生日(inode)
树状结构如图所示 :> 目录Inode与blocks与文件的关系 如图所示 :>
问
目录也是文件,那我要找当前目录不是要找上级目录………
答
没错正是如此,他会一直一直的往上找知道根目录,执行过一次后就会缓存路径,现在可以解释一个现象了,就是当你第一次开机执行命令的时候是不是都比平时慢一点(自己验证下吧)?
问
文件名是否是一个文件的标识?理解ls后就可以看到存在磁盘上文件的属性信息?
答
当然不是表示一个文件的是Inode。 ls后,直接区当前目录找文件,并通inode返回文件的属性
现在理解一下创建一个文件需要的操作
1.inode bitmap 和 blocks bitmap申请所需大小个数,2. 存储到inode中的block组中当前文件内容所需block的编号 3. 把文件名和inode的映射关系存到目录的block中 如图所示 :>
2.6.3 软硬链接
想要进行软硬链接就需要用到命令 ln
软硬链接的理解
如果用C++来理解软连接是指针而硬连接是引用 ,也就是说软连接有自己的空间,而硬链接就是本体 如图so是软ha是硬 : >你会发现硬的inode和文件是一样的,而软的inode是不同的,那么就说inode也有对应的block,他的block中存的就是文件的路径(类似windows中的快捷方式)
怪异现象(创建文件硬链接是1,创建目录确实2)
是否还记得ls的一个选项就是-a显示全部包括隐藏的文件?你会发现有一个 .的文件 他就是代表当前文件夹,..的文件 代表上级目录。当查看 .文件的inode 时发现居然是一样的
完结🎉🎉🎉🎉
上面就是博主总结的IO博客,如果对你有所帮助,请给我点个赞吧,点赞,点赞,点赞,给大家跳个舞!!!
|