?文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-413006.html
?
文章目錄
- 前言
- 一、模擬C庫(kù)文件操作
- 二、磁盤文件
- 總結(jié)
?
前言
經(jīng)過(guò)我們上一篇對(duì)linux系統(tǒng)文件操作的學(xué)習(xí)想必我們已經(jīng)會(huì)使用系統(tǒng)文件接口了,今天我們就用系統(tǒng)文件接口來(lái)封裝一個(gè)像C語(yǔ)言庫(kù)那樣的文件操作函數(shù)的函數(shù)來(lái)加深我們對(duì)文件操作的學(xué)習(xí)。
?
一、模擬C庫(kù)文件操作
首先我們創(chuàng)建相應(yīng)的.c? ?.h?以及main.c頭文件,然后我們寫一個(gè)makefile:
?接下來(lái)我們完善.h里面的代碼:
#pragma once
#include <stdio.h>
#define NUM 1024
#define BUFF_NONE 0x1
#define BUFF_LINE 0x2
#define BUFF_ALL 0x4
typedef struct _MY_FILE
{
int fd;
char outputbuffer[NUM];
int flags; //刷新策略
}MY_FILE;
MY_FILE* my_fopen(const char* path,const char* mode);
size_t my_fwrite(const void* ptr,size_t size,size_t nmemb,MY_FILE* stream);
int my_fclose(MY_FILE* fp);
?首先有一個(gè)文件結(jié)構(gòu)體,在這個(gè)結(jié)構(gòu)體中有文件描述符,有一個(gè)1024大小的緩沖區(qū),還有控制刷新的標(biāo)志,我們?yōu)榱朔奖銓⒔Y(jié)構(gòu)體重命名為MY_FILE,既然有刷新策略那么我們就#define3個(gè)刷新策略,分別是無(wú)緩沖,行緩沖,全緩沖。然后我們實(shí)現(xiàn)三個(gè)函數(shù),分別是打開(kāi)文件,寫入文件和關(guān)閉文件,這些函數(shù)我們都是通過(guò)系統(tǒng)接口調(diào)用所以參數(shù)與系統(tǒng)接口一致,下面我們進(jìn)行函數(shù)主體的編寫:
#include "mystdio.h"
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <malloc.h>
#include <assert.h>
#include <unistd.h>
MY_FILE* my_fopen(const char* path,const char* mode)
{
//.識(shí)別標(biāo)識(shí)位
int flag = 0;
if (strcmp(mode,"r")==0)
{
flag|=O_RDONLY;
}
else if(strcmp(mode,"w")==0)
{
flag|=(O_CREAT | O_WRONLY | O_TRUNC);
}
else if(strcmp(mode,"a")==0)
{
flag|=(O_CREAT | O_WRONLY | O_APPEND);
}
else
{
}
//2.嘗試打開(kāi)文件
mode_t m = 0666;
int fd = 0;
if (flag & O_CREAT)
{
fd = open(path,flag,m);
}
else
{
fd = open(path,flag);
}
if (fd<0)
{
return NULL;
}
//給用戶返回MY_FILE對(duì)象,需要先進(jìn)行構(gòu)建
MY_FILE* mf = (MY_FILE*)malloc(sizeof(MY_FILE));
if (mf==NULL)
{
close(fd);
return NULL;
}
//4.初始化MY_FILE對(duì)象
mf->fd = fd;
mf->flags = BUFF_LINE;
memset(mf->outputbuffer,'\0',sizeof(mf->outputbuffer));
//my_outputbuffer[0] = 0; //初始化緩沖區(qū)
//5.返回打開(kāi)的文件
return mf;
}
int my_fflush(MY_FILE* fp)
{
return 0;
}
size_t my_fwrite(const void* ptr,size_t size,size_t nmemb,MY_FILE* stream)
{
return 0;
}
int my_fclose(MY_FILE* fp)
{
assert(fp);
//1.沖刷緩沖區(qū)
if (fp->current >0)
{
my_fflush(fp);
}
//2.關(guān)閉文件
close(fp->fd);
//3.釋放堆空間
free(fp);
//4.指針置NULL --- 可以設(shè)置可以不設(shè)置
fp = NULL;
return 0;
}
?下面我們一個(gè)函數(shù)一個(gè)函數(shù)的進(jìn)行講解,首先是fopen函數(shù):
MY_FILE* my_fopen(const char* path,const char* mode)
{
//.識(shí)別標(biāo)識(shí)位
int flag = 0;
if (strcmp(mode,"r")==0)
{
flag|=O_RDONLY;
}
else if(strcmp(mode,"w")==0)
{
flag|=(O_CREAT | O_WRONLY | O_TRUNC);
}
else if(strcmp(mode,"a")==0)
{
flag|=(O_CREAT | O_WRONLY | O_APPEND);
}
else
{
}
}
?對(duì)于打開(kāi)文件的函數(shù)我們要判斷用戶是什么模式下的打開(kāi)文件,我們?cè)O(shè)置標(biāo)志位為0,如果用戶輸入的方式是r只讀模式,就讓標(biāo)志位或上只讀選項(xiàng),如果是w寫模式,這個(gè)時(shí)候要有三種實(shí)現(xiàn),文件不存在就創(chuàng)建文件,只寫文件,寫之前先清空文件,搞定w模式后我們完成追加模式,對(duì)于a來(lái)講也有三種情況,文件不存在就創(chuàng)建文件,只寫文件,追加文件不進(jìn)行清空,搞定第一步后我們完成第二步打開(kāi)文件:
//2.嘗試打開(kāi)文件
mode_t m = 0666;
int fd = 0;
if (flag & O_CREAT)
{
fd = open(path,flag,m);
}
else
{
fd = open(path,flag);
}
if (fd<0)
{
return NULL;
}
?要打開(kāi)文件我們先創(chuàng)建一個(gè)默認(rèn)的文件權(quán)限0666,并且創(chuàng)建一個(gè)文件標(biāo)識(shí)符變量,判斷文件是否存在,如果存在就用不需要權(quán)限參數(shù)的open函數(shù)代開(kāi)文件,如果不存在則需要用三個(gè)參數(shù)的open函數(shù)打開(kāi)文件。如果返回值小于0說(shuō)明打開(kāi)文件失敗返回空指針。
//3.給用戶返回MY_FILE對(duì)象,需要先進(jìn)行構(gòu)建
MY_FILE* mf = (MY_FILE*)malloc(sizeof(MY_FILE));
if (mf==NULL)
{
close(fd);
return NULL;
}
//4.初始化MY_FILE對(duì)象
mf->fd = fd;
mf->flags = BUFF_LINE;
memset(mf->outputbuffer,'\0',sizeof(mf->outputbuffer));
//my_outputbuffer[0] = 0; //初始化緩沖區(qū)
//5.返回打開(kāi)的文件
return mf;
?第三步給用戶返回一個(gè)文件對(duì)象,先創(chuàng)建一個(gè)指針用來(lái)開(kāi)一個(gè)MY_FILE的結(jié)構(gòu)體,然后判斷是否創(chuàng)建成功如果不成功我們就將文件關(guān)閉并且返回NULL,如果成功我們就初始化MY_struct中的參數(shù),將文件標(biāo)識(shí)符傳過(guò)去,因?yàn)镃語(yǔ)言是行緩沖所以我們直接設(shè)為行緩沖,然后給結(jié)構(gòu)體中的緩沖區(qū)初始化。這些過(guò)程都結(jié)束了就返回打開(kāi)的文件。
int my_fclose(MY_FILE* fp)
{
assert(fp);
//1.沖刷緩沖區(qū)
if (fp->current >0)
{
my_fflush(fp);
}
//2.關(guān)閉文件
close(fp->fd);
//3.釋放堆空間
free(fp);
//4.指針置NULL --- 可以設(shè)置可以不設(shè)置
fp = NULL;
return 0;
}
關(guān)閉文件的函數(shù)很簡(jiǎn)單,因?yàn)殛P(guān)閉文件前需要先刷新緩沖區(qū)所以我們?cè)趕truct中添加一個(gè)變量current來(lái)判斷是否需要刷新緩沖區(qū),沖刷完緩沖區(qū)后將文件關(guān)閉,最后將malloc出來(lái)的struct結(jié)構(gòu)體空間釋放掉,為了安全起見(jiàn)可以將fp指針置為NULL。
#include "mystdio.h"
#define MYFILE "log.txt"
int main()
{
MY_FILE* fp = my_fopen(MYFILE,"w");
if (fp==NULL)
{
return 1;
}
my_fclose(fp);
return 0;
}
?下面我們演示一下我們的文件操作成功與否:
?通過(guò)上圖我們可以看到我們寫的文件操作函數(shù)是成功的,只是有些功能我們還沒(méi)有去完善,如果都完善了可以替代C語(yǔ)言庫(kù)用我們自己的庫(kù)。
下面我們將fwrite接口補(bǔ)充完整:
//我們今天返回的就是一次實(shí)際寫入的字節(jié)數(shù),我們就不返回個(gè)數(shù)了
size_t my_fwrite(const void* ptr,size_t size,size_t nmemb,MY_FILE* stream)
{
//1.緩沖區(qū)如果已經(jīng)滿了,就直接寫入
if (stream->current==NUM)
{
my_fflush(stream);
}
//2.根據(jù)緩沖區(qū)剩余情況,進(jìn)行數(shù)據(jù)拷貝即可
size_t user_size = size*nmemb;
size_t my_size = NUM-stream->current;
size_t writen = 0;
if (my_size>=user_size)
{
memcpy(stream->outputbuffer+stream->current,ptr,user_size);
//3.更新計(jì)數(shù)器字段
stream->current+=user_size;
writen = user_size;
}
else
{
memcpy(stream->outputbuffer+stream->current,ptr,my_size);
stream->current+=my_size;
writen = my_size;
}
//4.開(kāi)始計(jì)劃刷新
if (stream->flags & BUFF_ALL)
{
if (stream->current==NUM)
{
my_fflush(stream);
}
}
else if(stream->flags & BUFF_LINE)
{
if (stream->outputbuffer[stream->current-1]=='\n')
{
my_fflush(stream);
}
}
else
{
}
return writen;
}
?我們寫入的第一件事就是判斷緩沖區(qū)是否有空間,如果我們的緩沖區(qū)已經(jīng)滿了我們就直接把數(shù)據(jù)刷新出去。如果我們期望寫入4096字節(jié),但是緩沖區(qū)只有1024字節(jié),所以最后緩沖區(qū)只能輸出1024字節(jié),所以下一步我們要根據(jù)緩沖區(qū)剩余情況進(jìn)行數(shù)據(jù)拷貝即可。current是當(dāng)前緩沖區(qū)有多少字符,并且current也是下一次緩沖區(qū)繼續(xù)寫入的下標(biāo)。接下來(lái)我們計(jì)算用戶傳的緩沖區(qū)有多大,再計(jì)算我們自己的緩沖區(qū)還剩下多少,如果我們的剩余空間是大于用戶的數(shù)據(jù)的,說(shuō)明我們的空間是足夠的,直接把用戶的緩沖區(qū)的數(shù)據(jù)拷貝到我們自己的緩沖區(qū)即可,在這里要注意不能直接拷貝到stream->outbuffer,因?yàn)楫?dāng)前緩沖區(qū)很可能是有數(shù)據(jù)的我們不能覆蓋,只能從current的位置繼續(xù)拷貝,拷貝完成后記得更新我們緩沖區(qū)的位置,由于fwrite函數(shù)寫入成功會(huì)返回當(dāng)前寫入多少字節(jié),所以我們用一個(gè)變量記錄當(dāng)前寫入了多少字節(jié),如果我們當(dāng)前的空間不足以容納用戶傳來(lái)的空間,我們就只能拷貝我們剩余大小的空間,然后接下來(lái)我們計(jì)劃開(kāi)始刷新緩沖區(qū),如果是全緩沖就等下標(biāo)等于NUM的時(shí)候刷新,如果是行刷新我們直接判斷current-1的位置是不是\n,如果是\n我們就進(jìn)行行刷新。最后返回寫入了多少字節(jié)就完成了,接下來(lái)我們完成刷新函數(shù):
int my_fflush(MY_FILE* fp)
{
assert(fp);
write(fp->fd,fp->outputbuffer,fp->current);
return 0;
}
?首先判斷fp是否為空指針,如果不是我們就進(jìn)行刷新,刷新我們直接用write函數(shù),第一個(gè)參數(shù)是文件描述符,第二個(gè)參數(shù)是緩沖區(qū),第三個(gè)參數(shù)是寫入多少的大小,總體就是向文件描述符中寫入特定的緩沖區(qū)中的特定大小,接下來(lái)我們?cè)趍ain函數(shù)中試試我們的fwrite函數(shù)。
#include "mystdio.h"
#define MYFILE "log.txt"
#include <string.h>
#include <unistd.h>
int main()
{
MY_FILE* fp = my_fopen(MYFILE,"w");
if (fp==NULL)
{
return 1;
}
const char* str = "hello my fwrite";
int cnt = 5;
while (cnt)
{
char buffer[1024];
snprintf(buffer,sizeof(buffer),"%s:%d\n",str,cnt--);
size_t size = my_fwrite(buffer,strlen(buffer),1,fp);
sleep(1);
printf("當(dāng)前成功寫入:%lu個(gè)字節(jié)\n",size);
}
my_fclose(fp);
return 0;
}
?接下來(lái)我們運(yùn)行一下看看效果:
?當(dāng)我們運(yùn)行后發(fā)現(xiàn),其他的都沒(méi)問(wèn)題但是為什么多打印了那么多?因?yàn)槲覀兠看未蛴『蠖家匦滤⑿乱幌戮彌_區(qū)再進(jìn)行打印,否則每次都帶有之前舊的數(shù)據(jù)被打印出來(lái),下面我們將緩沖區(qū)函數(shù)改一下:
int my_fflush(MY_FILE* fp)
{
assert(fp);
write(fp->fd,fp->outputbuffer,fp->current);
//刷新后清理緩沖區(qū)
fp->current = 0;
return 0;
}
?通過(guò)上圖我們可以看到這次的程序是成功完成。這就是我們模擬的文件操作,通過(guò)這樣的實(shí)現(xiàn)我們更深刻的理解了系統(tǒng)文件操作。
二、磁盤文件
一個(gè)文件在不被打開(kāi)的時(shí)候是存儲(chǔ)在磁盤中的,那么這個(gè)文件如何在磁盤中合理的存儲(chǔ)呢?要了解文件如何在磁盤中存儲(chǔ),我們要先了解磁盤,如下圖所示:
?磁盤有幾個(gè)重要的接口,分別是馬達(dá),盤片,磁頭,一個(gè)磁頭可以讀取一個(gè)面的數(shù)據(jù)。磁盤中每個(gè)數(shù)據(jù)都是0 1,,01?的表示有很多種,波峰波谷,南極北極等,并且磁頭和盤面是不接觸的,只不過(guò)他們離的距離很近,下面是盤面的結(jié)構(gòu):
在盤面上有扇區(qū)和磁道,一般的磁盤所有的扇區(qū)都是512字節(jié),那么如何在硬件上定位一個(gè)扇區(qū)呢?要定位扇區(qū)首先要定位哪一個(gè)盤面,而盤面又是通過(guò)磁頭確定的,因?yàn)榇蓬^有相應(yīng)的編號(hào),通過(guò)磁頭的編號(hào)就能找到哪個(gè)面了,找到面后我們要找扇區(qū)需要先定位在哪一個(gè)磁道,而磁道是通過(guò)半徑?jīng)Q定的,在確定在該磁道在哪一個(gè)扇區(qū),根據(jù)扇區(qū)的編號(hào)定位一個(gè)扇區(qū)。理解了以上的知識(shí),我們就能談文件是如何在磁盤中存儲(chǔ)的了,一個(gè)普通文件(屬性+數(shù)據(jù))都是數(shù)據(jù)0,1,對(duì)于0 1這樣的數(shù)據(jù)無(wú)非就是占用一個(gè)或者多個(gè)扇區(qū),來(lái)進(jìn)行自己的數(shù)據(jù)存儲(chǔ)的,我們既然能夠用CHS定位任意一個(gè)扇區(qū),我們就能定位任意多個(gè)扇區(qū),從而將文件從硬件角度,進(jìn)行讀取或者寫入。
下面我們根據(jù)操作系統(tǒng)和磁盤的關(guān)系分析一下磁盤:
根據(jù)我們上面的描述,如果操作系統(tǒng)能夠得知任意一個(gè)CHS地址,就能訪問(wèn)任意一個(gè)扇區(qū),那么操作系統(tǒng)內(nèi)部是不是直接使用的CHS地址呢?答案其實(shí)不是,因?yàn)椴僮飨到y(tǒng)是軟件,磁盤是硬件,硬件定位一個(gè)地址,如果操作系統(tǒng)直接用了這個(gè)地址,玩意硬件變了呢?這樣的話操作系統(tǒng)也要發(fā)生變化,操作系統(tǒng)要和硬件做好解耦工作所以不能直接用硬件上的地址。并且扇區(qū)才512字節(jié),操作系統(tǒng)實(shí)際向外設(shè)寫入的基本單位是4KB,與扇區(qū)并不符合,所以操作系統(tǒng)需要有一套新的地址來(lái)進(jìn)行塊級(jí)別的訪問(wèn)?,F(xiàn)在我們畫一張圖表示一下磁道中的扇區(qū)結(jié)構(gòu):
?每一個(gè)扇區(qū)都是512,我們從頭開(kāi)始數(shù)512就找到了第一個(gè)扇區(qū)。這就印證了計(jì)算機(jī)常規(guī)的訪問(wèn)方式:起始地址 +?偏移量的方式,也就是說(shuō)我們只需要知道數(shù)據(jù)塊的起始地址(第一個(gè)扇區(qū)的下標(biāo)地址)+4KB(塊的類型)如下圖所示:
?所以塊的地址,本質(zhì)就是數(shù)組的一個(gè)下標(biāo)N,以后我們表示一個(gè)塊,我們可以采用線性下標(biāo)N的方式定位任何一個(gè)塊了。OS->N->LBA->邏輯塊地址,前面我們說(shuō)了,磁盤只認(rèn)CHS方式,所以要讓這兩個(gè)聯(lián)系起來(lái)就需要LBA到CHS互相轉(zhuǎn)化。就拿我們的電腦而言,我們進(jìn)入我的電腦發(fā)現(xiàn)有ABCD 4個(gè)盤或者幾個(gè)盤,而實(shí)際只有一個(gè)盤,是因?yàn)閷⑦@一個(gè)盤分區(qū)管理得到的四個(gè)盤,就如下圖所示:
?如上圖所示,我們將500G的磁盤分為150G一組,那么在這個(gè)150G的盤中都有什么呢?其實(shí)上圖已經(jīng)給出答案了,Boot Block主要保存與操作系統(tǒng)啟動(dòng)相關(guān)的內(nèi)容,比如分區(qū)表,操作系統(tǒng)鏡像的地址等,所以一般分區(qū)會(huì)在0號(hào)盤面0號(hào)磁道1號(hào)扇區(qū)保存的,對(duì)應(yīng)的在C盤的某個(gè)區(qū)域存儲(chǔ),而這個(gè)塊又被稱為啟動(dòng)塊,如果這個(gè)塊壞了就不能開(kāi)機(jī)了。文件=?內(nèi)容+屬性,linux下是將內(nèi)容和屬性分離的。
1.SuperBlock保存的是文件系統(tǒng)的所有屬性信息,比如文件系統(tǒng)的類型,整個(gè)分組的情況。而SuperBlock在各個(gè)分組里面可能都存在,而且是統(tǒng)一更新的,這樣做的原因是防止SuperBlock區(qū)域壞掉,如果出現(xiàn)故障,整個(gè)分區(qū)不可以再被使用。?
2.GroupDescriptorTable:組描述符,改組內(nèi)的詳細(xì)統(tǒng)計(jì)等屬性信息。
3.inodeTable:一個(gè)文件內(nèi)部所有屬性的集合,inode節(jié)點(diǎn)(128字節(jié)),一個(gè)文件一個(gè)inode,其中即便是一個(gè)分區(qū),內(nèi)部也會(huì)存在大量的文件即會(huì)存在大量的inode節(jié)點(diǎn),一個(gè)group需要有一個(gè)區(qū)域,專門保存該group內(nèi)的所有文件的inode節(jié)點(diǎn)。分組內(nèi)部,可能存在多個(gè)inode,需要將inode區(qū)分開(kāi)來(lái),每一個(gè)inode都有自己的inode編號(hào),inode編號(hào)也屬于對(duì)應(yīng)文件的屬性id。
4.DateBlocks:文件的內(nèi)容是變化的,我們是用數(shù)據(jù)塊來(lái)進(jìn)行文件的內(nèi)容的保存的,所以一個(gè)有效文件要保存內(nèi)容,就需要[1,n]數(shù)據(jù)塊,那么如果是多個(gè)文件所以就需要更多的數(shù)據(jù)塊,而DateBlock就是保存這些數(shù)據(jù)塊的。而linux查找文件是要根據(jù)inode編號(hào)來(lái)進(jìn)行文件查找的,包括讀取內(nèi)容,一個(gè)inode對(duì)應(yīng)一個(gè)文件,而改文件inode屬性和改文件對(duì)應(yīng)的數(shù)據(jù)塊是有映射關(guān)系的。
5.塊位圖BlockBitmap:BlockBitmap中記錄著DateBlock中哪個(gè)數(shù)據(jù)塊已經(jīng)被占用,哪個(gè)數(shù)據(jù)塊沒(méi)有被占用。
6.inode位圖inodeBitmap:每個(gè)bit表示一個(gè)inode是否空閑可用
總結(jié)
本篇文章的主要內(nèi)容是在上一篇文件操作的基礎(chǔ)上再次深刻理解系統(tǒng)文件操作,然后我們又大致講解了磁盤的物理空間是什么樣的是如何存儲(chǔ)數(shù)據(jù)的,通過(guò)軟硬件共同學(xué)習(xí)我們能知道文件的各個(gè)屬性在磁盤中是如何存儲(chǔ)的,每個(gè)分區(qū)又有什么塊的知識(shí),下一篇文章我們將詳細(xì)介紹文件的軟硬鏈接。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-413006.html
?
到了這里,關(guān)于【linux】:模擬文件基本操作以及文件在磁盤中如何存儲(chǔ)的學(xué)習(xí)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!