)
1.FILE(1)是什么FILE就是c标准库用于储存文件属性和用户级缓冲区的地方这个结构体空间在fopen这类c标准库中用于打开文件的函数内部创建(动态开辟)也就是用c标准库打开的文件都有其自己的用户级缓冲区往这些文件中写入数据时会先存进FILE内部指向的缓冲区中。(2)意义系统调用是有成本的:OS是很忙的频繁调用系统会造成程序效率低下。因此先将用户级缓冲区存满再一次性的调用OS接口就可以提高c标准库函数的效率。(3)用户级缓冲区刷新条件(1)立即刷新-无缓冲-写透模式(WT)(2)缓冲区满了-全缓冲(效率最高,常用于普通文件的写入)(3)行刷新-行缓冲(如c库就是遇到了\n就刷新常用于显示器的写入以方便用户读)OS缓冲区的刷新条件由OS自主决定用户无法得知(但有对应的接口)因此可以认为将数据交给了OS就相当于给到了对应的硬件或文件了。计算机内部的数据流动方式全部都是拷贝。2.重定向与缓冲区重定向还会更改缓冲区的刷新方式。例:原本向显示器写入的刷新条件为行刷新重定向为向普通文件写入时刷新条件就改为全缓冲了此时当缓冲区还没有满就开始子进程时子进程会将父进程的用户级缓冲区中的数据也会拷贝一份导致最后重定向文件中会有部分重复的数据。例:#include stdio.h #include string.h int main() { const char *msg0hello printf\n; const char *msg1hello fwrite\n; const char *msg2hello write\n; printf(%s, msg0); fwrite(msg1, strlen(msg0), 1, stdout); write(1, msg2, strlen(msg2)); //子进程中的缓冲区还会有着printf和fwrite中的数据 //当进程结束后会把相同的数据也写入重定向的文件中 fork(); return 0; }c标准库管理FILE的方式也是先描述再组织。3.glibc的模拟实现(1)glibc.h#pragma once #define SIZE 1024 //刷新条件WT行刷新全刷新 #define FLUSH_NONE 0 #define FLUSH_LINE 1 #define FLUSH_FULL 2 typedef struct IO_FILE { //flag是刷新方式 int flag; //是fd int fileno; //是用户级缓冲区 char outbuffer[SIZE]; //是outbuffer的最大的大小 int cap; //是当前outbuffer的存储量 int size; }mFILE; mFILE *mfopen(const char *filename, const char *mode); int mfwrite(const void *ptr, int num, mFILE *stream); void mfflush(mFILE *stream); void mfclose(mFILE *stream);(2)glibc.c(补充)fsyncint fsync(int fd); //将fd对应的文件内核缓冲区数据直接刷新到硬件中#include my_stdio.h #include string.h #include stdlib.h #include sys/stat.h #include sys/types.h #include fcntl.h #include unistd.h //open初始化 mFILE *mfopen(const char *filename, const char *mode) { int fd -1; //根据mode运行对应的open模式 if(strcmp(mode, r) 0) { fd open(filename, O_RDONLY); } else if(strcmp(mode, w) 0) { fd open(filename, O_CREAT|O_WRONLY|O_TRUNC, 0666); } else if(strcmp(mode, a) 0) { fd open(filename, O_CREAT|O_WRONLY|O_APPEND, 0666); } if(fd 0) return NULL; //初始化 mFILE *mf (mFILE*)malloc(sizeof(mFILE)); mf-fileno fd; //此处是默认向显示器写入不同情况应该要用条件判断的 mf-flag FLUSH_LINE; mf-size 0; mf-cap SIZE; return mf; } void mfflush(mFILE *stream) { if(stream-size 0) { //写入的是写入到文件缓冲区中 write(stream-fileno, stream-outbuffer, stream-size); //强制刷新 fsync(stream-fileno); //size清零即可以初始化了 stream-size 0; } } int mfwrite(const void *ptr, int num, mFILE *stream) { //从最后空的地方开始拷贝 memcpy(stream-outbufferstream-size, ptr, num); stream-size num; //检测此时的刷新条件并分析是否满足该条件 if(stream-flag FLUSH_LINE stream-size 0 stream-outbuffer[stream-size-1] \n) { mfflush(stream); } return num; } //关闭文件前先刷新一下 void mfclose(mFILE *stream) { if(stream-size 0) { mfflush(stream); } close(stream-fileno); }题外话:(1)linux中的函数声明与定义处是可以允许参数只写类型不写新参名的。(2)数据库就是磁盘上的一个文件因此调用数据库的本质就是开一个新进程也就是说对数据库的增删查改是在内存中进行的然后通过文件缓冲区写进磁盘中。这种将数据写进持久化文件中的操作叫落盘。(3)C不允许const char*的变量传进void*的形参中因为这里传的时候会进行隐式丢弃const限定符但C不允许这么做。4.文件系统所有的文件是由目录结构组织的目录结构是树状的由路径来寻找。从硬件角度理解磁盘磁盘简化上来讲就是由很多个小磁铁构成因此往磁盘上写入就是更改磁盘上的那些小磁铁的南北极(北是0南是1)磁盘由几个盘片组成一个盘片中由很多个同心圆形状的磁道组成每一个磁道由多个有间隙的扇区组成每一个扇区能存512字节。磁盘的存储单位是扇区也就是对磁盘进行读写时数据的拷贝无论修改多少都要以扇区的大小进行拷贝。盘片的两面都是独立的也就是两面都可以用每一面都有其独立的磁头。磁头在传动臂的带动下共进退。(即所有的磁头都绑在同一个传动臂上)在同一时间下的所有磁头指向的所有磁道的整体在几何上叫柱面。传动比的旋转是为了调整指向的磁道(柱面)盘片自身的旋转只为调整指向的扇区。1.CHS当我们知道了柱面磁头(第几个磁头)扇区的位置后就可以定位磁盘中的任意一格扇区这种定位扇区的方式就叫CHS。对一个文件数据的储存就是存进一个或多个扇区里。2.LBA简化上来说我们可以将一个磁道视为一个线性结构基本单元是扇区从这个角度看磁道就变成一个一维数组了。将一个柱面剪开能发现其本质就是一个二维数组而整个磁盘是由多个柱面组成说白了就是一个三维数组这个三维数组的读取数值就叫LBA。LBA与CHS之间的相互转换从表面看磁盘只认识LBA但其在内容上还是会将LBA转换为CHS来寻址(由磁盘自己操作转换)。LBA其实是一个值但其能拆分出CHS的三个地址(柱面磁头扇区的展现就是一个地址)。LBA转CHS(/默认取整)---(我们只要明白%的结构就是/的余数即可)C LBA / 一个柱面的扇区总数(停在第几个柱面的意思余数就是在一个柱面中的第几个扇区中)H LBA % 一个柱面的扇区总数 / 一个磁道扇区总数(停在一个柱面的第几个磁道中余数为一个磁道中的第几个扇区中)S LBA % 每一个磁道中的扇区数 1(停留在一个磁道中的第几个扇区中)CHS转LBALBA C * 一个柱面的扇区总数 H * 一个磁道扇区总数 S - 1