操作系統課程設計—模擬文件系統
下載地址:模擬操作系統。在 github 上也有源代碼:github 地址
在 Linux 環境下輸入make
編譯,可執行文件是./bin/filesystem
。進入系統后輸入reformat
格式化系統。然后就可以嘗試各種命令了。
一、設計
將一個大文件當作是模擬的硬盤,包括三個區域:superblock, inode, 扇區。
文件 disk.img 共100MB,按照每個扇區 512B 來計算,共有204800個扇區。
二、superblock
記錄inode和扇區的分配情況。占用512(256KB)個扇區,。使用位圖的形式來記錄inode和數據區的分配情況。
256個扇區來記錄inode的分配情況,inode共占用8192個扇區。也就是用了4MB的磁盤來存放inode。
256個扇區來記錄數據扇區的分配情況,數據扇區共131072個扇區。也就是說磁盤中64MB用來存放數據。
類定義及其解析如下:
class superblock{
private:
bool inode_bitmap[INODE_NUM];
bool block_bitmap[BLOCK_NUM];
public:
// 剩余的inode數量
int remain_inode();
// 剩余的扇區數量
int remain_sec();
// 返回未使用的i節點
int get_new_inode();
// 返回未使用的扇區
int get_new_sec();
// 回收i節點
bool recv_inode(int inode_num);
// 回收扇區
bool recv_sec(int sec_num);
// 初始化位圖全都清0
superblock();
// 格式化
bool init();
};
三、inode
記錄文件和目錄的信息,和占用的數據扇區的信息。每個inode節點32字節,一個扇區可以存放16個inode節點。共占用了8192個扇區。
class Inode{
private:
// inode號
int _inode_num;
// 是否是文件
bool _is_file;
// 文件大小,單位為Byte
int _file_size;
// 數據扇區號碼,只指定第一個,后面的通過指針連接
int _sec_beg;
// 占用的數據扇區數量
int _sec_num;
// 填充位,目的是將inode大小控制在32個字節
char _compensate[12];
public:
// 構造函數初始化
Inode();
// 構造函數
Inode(int node_num, bool _is_file, int file_size, int sec_begin);
// 獲得inode號碼
int get_inode_num();
// true->file; false->dir
bool get_type();
// 獲得文件長度
int get_file_size();
// 獲得起始磁盤扇區號
int get_sec_beg();
// 獲得占據的磁盤扇區數量
int get_sec_num();
// 設置inode號
void set_inode_num(int num);
// 返回Inode對應的扇區號
int get_inode_sec_num();
// 從磁盤中讀取inode
bool read_inode_from_disk(int inode_num, Buffer& buffer);
// 將inode寫回到磁盤中
bool write_inode_back_to_disk(Buffer& buffer);
// 重載復制運算符
Inode operator = (const Inode& b) {
}
};
inode包括這樣幾個信息:
- inode號碼,這與目錄中的inode號碼對應
- 文件類型,是目錄還是文件
- 文件大小,文件的具體大小
- 文件起始扇區號
- 文件占用的扇區數量
其中,0號inode是根目錄/
,根據根目錄的數據扇區號,可以找到磁盤中對該文件夾的描述。
四、數據扇區
存放文件和目錄信息,兩者會有比較大的區別。
// 目錄項
struct sector_dir_entry{
// 目錄名
char name[28];
// 目錄項對應的inode號碼
int inode_num;
// 初始化
void init(const char* _name, int _num);
// 構造函數
sector_dir_entry();
// 重載賦值運算符
sector_dir_entry operator = (const sector_dir_entry& dir);
// 和賦值運算符功能一樣
void clone(const sector_dir_entry& dir);
};
// 512 Bytes.最后一項指示接下來的目錄
class sector_dir{
public:
// 構造函數
sector_dir();
// 將文件結構寫回到磁盤中
bool write_back_to_disk(Buffer& buffer, int sec_num);
// 構造函數
sector_dir operator = (const sector_dir& sec_dir);
// 每個扇區有16項目錄信息
sector_dir_entry dirs[16];
// 從磁盤中讀取扇區,通過緩存buffer實現
bool read_dir_from_disk(Buffer& buffer, int sec_num);
};
// 512 Bytes
class sector_file{
public:
// 每個扇區能存放508字節數據
char data[508];
// 指針指向下一個數據節點
int next;
// 構造函數
sector_file();
// 重載賦值運算符
sector_file operator = (const sector_file& sec_file);
// 通過緩存buffer從磁盤中讀取數據
bool read_dir_from_disk(Buffer& buffer, int sec_num);
// 通過緩存將數據寫回磁盤
bool write_back_to_disk(Buffer& buffer, int sec_num);
};
4.1文件
每個扇區的前508個字節存放數據,最后一個字節指向下一個數據扇區,如果沒有就置為-1。
字節 | 0 - 507 | 508 - 511 |
---|---|---|
作用 | 存放數據 | 指向下一個數據扇區 |
4.2目錄
存放該目錄下的文件名稱和對應的文件indoe號碼。例如:
目錄結構示例:
文件或目錄名 | 對應的inode號碼 |
---|---|
. | 1 |
.. | 1 |
bin | 2 |
etc | 3 |
home | 4 |
dev | 5 |
^ | ^ |
next | next_num |
其中要求文件或目錄名不超過28字節,后四位用來保存inode號碼,如果一個扇區不夠,最后一項指向下一個扇區。
根目錄對應的inode為1。類似的home
目錄結構如下:
文件或目錄名 | 對應的inode號碼 |
---|---|
. | 4 |
.. | 1 |
tr | 6 |
^ | ^ |
home自己的inode號為4,home目錄上一層是根目錄,對應inode為1。這個目錄中有一個文件tr,其inode號碼為6。
五、緩存
模擬磁盤的IO全部從這里經過。會動態地檢查節點的狀態,將長時間沒用的緩存寫回磁盤。
/*******************************************
緩存類, 模擬磁盤的IO全部從這里經過。
會動態地檢查節點的狀態,將長時間沒用的緩存寫回磁盤
*******************************************/
// 緩存節點
struct BufferNode{
// 長度
char buffer[SEC_SIZE + 1]; // 長度為512 + 1個字節(為了保存字符串尾0)
int pri; // 緩存的優先級,緩存滿了會將優先級最低的節點寫回磁盤
int sec_num; // 對應的扇區號
// 重載構造函數
BufferNode operator = (const BufferNode& b) {
memcpy(buffer, b.buffer, SEC_SIZE + 1);
pri = b.pri;
sec_num = b.sec_num;
}
BufferNode(){
memset(buffer, 0, SEC_SIZE);
pri = 0;
sec_num = 0;
}
void init(int _sec_num) {
pri = 5;
sec_num = _sec_num;
}
void update(const BufferNode& b) {
memcpy(buffer, b.buffer, SEC_SIZE + 1);
pri = b.pri + 1;
sec_num = b.sec_num;
}
};
class Buffer{
public:
// 構造函數,打開文件
Buffer();
// 析構函數,關閉函數
~Buffer();
// 將 buffer 里面的內容寫入扇區中.單位為512KB。放入緩存隊尾部。
// 如果扇區已經存在於緩存中,則刷新扇區
bool write_disk(const BufferNode& node);
// 將扇區中的內容讀入buffer中,首先會從緩存里找有沒有這個節點。
// 新讀入緩存的節點優先級為5,如果存在於緩存中,則優先級加 1
bool read_disk(int sec_num, BufferNode& node);
private:
// 真正操作文件
bool real_disk_write(const BufferNode& node);
// 真正操作文件
bool real_disk_read(int sec_num, BufferNode& node);
// 檢查緩存中有沒有給定扇區號的緩存
int has_sec(int sec_number);
// 返回優先級最低的緩存號碼,沒滿返回0
int is_full();
// 磁盤緩存,共15個節點,滿了之后會將優先級最低的節點寫回磁盤
vector<BufferNode> cache;
// 靜態的文件指針
fstream disk;
};
六、實現以下文件API
void ls();
int fopen(char* name, int mode);
void fclose(int fd);
int fread(int fd, char* buffer, int length);
int fwrite(int fd, char* buffer, int length);
int flseek(int fd, int position);
int fcreat(char* name, int mode);
int fdelete(char* name);
1. ls命令
當前目錄存放在cur_dir里面,所以只需遍歷一邊就可以輸出目錄
// ls: 列出文件夾下所有文件夾及目錄(cur_dir中所有的信息)
bool my_fs::list_dir() {
for(int i = 2; i < 15; i++) {
cout << cur_dir.dirs[i].name << " ";
}
cout << endl;
return true;
}
1.1 運行結果演示
2. cd命令
進入某個目錄
// cd: 進入某個文件夾
bool my_fs::change_dir(const char* name) {
// 1. 得到子目錄的inode號碼
int dir_inode_num = -1;
for(int i = 0; i < 15; i++) {
if(strncmp(name, cur_dir.dirs[i].name, strlen(name)) == 0) {
dir_inode_num = cur_dir.dirs[i].inode_num;
cout << "目錄inode號碼為:" << dir_inode_num << endl;
break;
}
}
if(dir_inode_num == -1) {
cout << "該目錄不存在" << endl;
return false;
}
// 2. 根據這個inode號碼找到相應的inode
cur_dir_node.read_inode_from_disk(dir_inode_num, my_cache);
// 3. 根據inode中的信息區磁盤中讀取目錄信息
cur_dir.read_dir_from_disk(my_cache, cur_dir_node.get_sec_beg());
return true;
}
2.1 運行演示
可以看到已經進入到home目錄了
3.mkdir命令
創建文件夾
// mkdir: 創建文件夾
bool my_fs::make_dir(const char* name) {
cout << "******************************** 創建新文件夾開始 ********************************" << endl;
// 1. 創建inode
Inode new_dir_inode(sp.get_new_inode(), false, 0, sp.get_new_sec());
// 2. 寫回磁盤
new_dir_inode.write_inode_back_to_disk(my_cache);
// 3. 建立目錄結構
sector_dir new_sec_dir;
new_sec_dir.dirs[0].init(".", new_dir_inode.get_inode_num());
new_sec_dir.dirs[1].init("..", cur_dir_node.get_inode_num());
new_sec_dir.write_back_to_disk(my_cache, new_dir_inode.get_sec_beg());
// 4. 當前目錄中添加一項
int flag = false;
for(int i = 2; i < 15; i++) {
if(cur_dir.dirs[i].inode_num == 0) {
cur_dir.dirs[i].init(name, new_dir_inode.get_inode_num());
flag = true;
break;
}
}
if(flag) {
// 5. 將修改的目錄寫回磁盤
cur_dir.write_back_to_disk(my_cache, cur_dir_node.get_sec_beg());
}
return flag;
cout << "******************************** 創建新文件夾結束 ********************************" << endl;
}
3.1 運行演示
4. touch命令
創建文件命令
// touch: 創建文件
bool my_fs::touch(const char* name) {
cout << "******************************** 創建新文件開始 ********************************" << endl;
// 1. 創建inode
Inode new_file_inode(sp.get_new_inode(), true, 1, sp.get_new_sec());
new_file_inode.write_inode_back_to_disk(my_cache);
// 2. 當前目錄添加一項
int flag = false;
for(int i = 2; i < 15; i++) {
if(cur_dir.dirs[i].inode_num == 0) {
cur_dir.dirs[i].init(name, new_file_inode.get_inode_num());
flag = true;
break;
}
}
if(flag) {
cur_dir.write_back_to_disk(my_cache, new_file_inode.get_sec_beg());
}
return flag;
cout << "******************************** 創建新文件結束 ********************************" << endl;
}
4.1 運行演示
5. rm
刪除文件或文件夾,如果是文件夾就遞歸地進入目錄然后調用自身,刪除所有文件。
// rm: 刪除文件或文件夾
bool my_fs::remove_dir(const char* name) {
// 1. 在當前目錄下尋找這個文件或目錄
int del_inode_num = -1;
for(int i = 0; i < 15; i++) {
if(strncmp(name, cur_dir.dirs[i].name, strlen(name)) == 0) {
del_inode_num = cur_dir.dirs[i].inode_num;
cout << "該文件或目錄inode號碼為:" << del_inode_num << endl;
break;
}
}
if(del_inode_num == -1) {
cout << "不存在這個文件或目錄" << endl;
return false;
}
// 2. 讀取這個inode節點
Inode del_node;
del_node.read_inode_from_disk(del_inode_num, my_cache);
// cout << "inode對應數據扇區號碼為:" << del_node.get_sec_beg() - BLOCK_BEGIN / 512 << endl;
// cout << "inode號為:" << del_node.get_inode_num() << endl;
del_inode(del_node, cur_dir);
return true;
}
// 根據刪除inode
bool my_fs::del_inode(Inode& node, sector_dir& del_dir) {
cout << "delete inode, inode num為" << node.get_inode_num() << endl;
if(node.get_type()) {
// file, 釋放inode,inode對應的sec, 還要從dir中去除這個項目
// 1.刪除sec中的這條記錄
for(int i = 2; i < 15; i++) {
if(del_dir.dirs[i].inode_num == node.get_inode_num()) {
cout << "delate inode,刪除sector中對文件的記錄" << endl;
memset(&del_dir.dirs[i], 0, sizeof(sector_dir_entry));
del_dir.write_back_to_disk(my_cache, node.get_sec_beg());
break;
}
}
// 2. 釋放inode和對應的扇區
sp.recv_sec(node.get_sec_beg() - BLOCK_BEGIN / 512);
sp.recv_inode(node.get_inode_num());
}
else {
// dir
// 1.先刪除當前目錄對這個目錄的記錄
for(int i = 0; i < 15; i++) {
if(node.get_inode_num() == del_dir.dirs[i].inode_num) {
cout << "delate inode,刪除sector中對文件的記錄" << endl;
memset(&del_dir.dirs[i], 0, sizeof(sector_dir_entry));
del_dir.write_back_to_disk(my_cache, node.get_sec_beg());
break;
}
}
// 2. 釋放這個目錄的inode和扇區
sp.recv_sec(node.get_sec_beg() - BLOCK_BEGIN / 512);
sp.recv_inode(node.get_inode_num());
// 3. 切換到要刪除的目錄下
Inode new_node;
new_node = node;
sector_dir new_dir;
new_dir = del_dir;
new_dir.read_dir_from_disk(my_cache, new_node.get_sec_beg());
// 4. delete every files and directories recursively
for(int i = 2; i < 15; i++) {
if(new_dir.dirs[i].inode_num != 0) {
new_node.read_inode_from_disk(new_dir.dirs[i].inode_num, my_cache);
del_inode(new_node, new_dir);
}
}
}
}
6. 將一個文件存入系統
將一張圖片存入系統,圖片如下:
// 將現成文件存入當前目錄中
bool my_fs::move_in() {
/*
* move p1.png into my file system
*/
// 1. get file size, compute needed block number, allocate block
ifstream is(IMG, ifstream::binary);
if(is) {
is.seekg(0, is.end);
int length = is.tellg();
cout << "size of the file:" << length << " bytes" << endl;
// 2. compute needed blocks
int needed_block = length / 508;
if(length % 508 != 0)
needed_block++;
int left = length % 508;
cout << endl << "last node contain " << ((left == 0) ? 508 : left) << "bytes of data" << endl;
cout << "need " << needed_block << " blocks to store data" << endl;
Inode new_file_inode(sp.get_new_inode(), true, length, sp.get_new_sec());
new_file_inode.write_inode_back_to_disk(my_cache);
cout << "img inode info: #inode: " << new_file_inode.get_inode_num() << endl;
cout << "file length " << new_file_inode.get_file_size() << endl;
cout << " #sector begin: " << new_file_inode.get_sec_beg() << endl;
// 3. add new entry in current directory
int flag = false;
for(int i = 2; i < 15; i++) {
if(cur_dir.dirs[i].inode_num == 0) {
cur_dir.dirs[i].init(IMG, new_file_inode.get_inode_num());
flag = true;
break;
}
}
if(flag) {
cur_dir.write_back_to_disk(my_cache, cur_dir_node.get_sec_beg());
}
// 4. store data into file system
is.seekg(0, is.beg);
char buffer[508];
sector_file img_sectors[needed_block];
int sec_numbers[needed_block];
sec_numbers[0] = new_file_inode.get_sec_beg();
for(int i = 0; i < needed_block - 1; i++) {
is.read(buffer, 508);
sec_numbers[i+1] = sp.get_new_sec();
memcpy(img_sectors[i].data, buffer, 508);
img_sectors[i].next = sec_numbers[i+1];
cout << "#next data sector:" << img_sectors[i].next << endl;
}
if(left == 0) {
is.read(buffer, 508);
memcpy(img_sectors[needed_block - 1].data, buffer, 508);
img_sectors[needed_block - 1].next = 0;
}
else {
is.read(buffer, left);
memcpy(img_sectors[needed_block - 1].data, buffer, left);
img_sectors[needed_block - 1].next = 0;
}
cout << "File pointer location" << is.tellg() << endl;
cout << "file sectors info" << endl;
cout << new_file_inode.get_sec_beg();
for(int i = 0; i < needed_block; i++) {
cout << " -> " << img_sectors[i].next;
}
cout << endl;
for(int i = 0; i < needed_block; i++) {
img_sectors[i].write_back_to_disk(my_cache, sec_numbers[i]);
}
is.close();
}
}
6.1 演示
7. mv_out
將圖片移出
// 如果當前目錄有指定文件,就將這個文件從文件系統中讀出
bool my_fs::move_out(string name) {
/*
* move p1.png out of my file system
*/
// 1. search for inode number
int inode_num = -1;
for(int i = 0; i < 15; i++){
if(strncmp(IMG, cur_dir.dirs[i].name, strlen(IMG)) == 0) {
inode_num = cur_dir.dirs[i].inode_num;
cout << "inode of p1.png: " << inode_num << endl;
break;
}
}
if(inode_num == -1) {
cout << "pl.png not exists" << endl;
return false;
}
Inode file_node;
file_node.read_inode_from_disk(inode_num, my_cache);
cout << "file info: #inode " << file_node.get_inode_num() << endl;
cout << "file length: " << file_node.get_file_size() << endl;
cout << "sec number: " << file_node.get_sec_num() << endl;
cout << "sec_begin: " << file_node.get_sec_beg() << endl << endl;
// 2. get data of p1.png from my file system
sector_file data_sec;
data_sec.read_dir_from_disk(my_cache, file_node.get_sec_beg());
string file_name = name + ".png";
fstream os(file_name.c_str(), fstream::in | fstream::out | fstream::app);
char buffer[508];
int next_sec = -1, left = file_node.get_file_size() % 508;
if(os) {
for(int i = 0; i < file_node.get_sec_num() ; i++) {
if(i != file_node.get_sec_num() - 1 || left == 0) {
next_sec = data_sec.next;
memcpy(buffer, data_sec.data, 508);
os.write(buffer, 508);
data_sec.read_dir_from_disk(my_cache, next_sec);
}
else {
memcpy(buffer, data_sec.data, left);
os.write(buffer, left);
}
cout << "size of new file:" << os.tellg() << endl;
}
os.close();
}
return true;
}
7.1 演示
指定文件名為 p1-1
再試一次,這次指定文件名為 p1-2
可以看到目錄下已經有了另外兩張圖片
需要完成的功能:
- 將宿主機的文件存入虛擬磁盤。
- 將虛擬磁盤的文件取出,放在宿主機。要求文件沒有損壞。
- 創建新目錄。
- 刪除已經存在的目錄。
需要編寫的兩個應用程序:
- initialize,用來初始化虛擬磁盤。
- testMyFileSystem,接受用戶輸入的文件操作命令,測試文件系統和API。
8.初始化磁盤
// 初始化文件系統
bool my_fs::format_file_system() {
/*
* 1. 格式化superblock
*/
cout << "******************************** 初始化磁盤開始 ********************************" << endl;
sp.init();
/*
* 2. 申請根目錄的一系列inode。包括根目錄
* 根目錄下面的bin、etc、home、dev
* home目錄下面的tangrui目錄
*/
Inode root_node(sp.get_new_inode(), false, 0, sp.get_new_sec());
Inode bin_node(sp.get_new_inode(), false, 0, sp.get_new_sec());
Inode etc_node(sp.get_new_inode(), false, 0, sp.get_new_sec());
Inode home_node(sp.get_new_inode(), false, 0, sp.get_new_sec());
Inode dev_node(sp.get_new_inode(), false, 0, sp.get_new_sec());
Inode tangrui_node(sp.get_new_inode(), false, 0, sp.get_new_sec());
cout << "1. 申請inode" << endl;
/*
* 3. 將inode寫回到磁盤中
*/
root_node.write_inode_back_to_disk(my_cache);
bin_node.write_inode_back_to_disk(my_cache);
etc_node.write_inode_back_to_disk(my_cache);
home_node.write_inode_back_to_disk(my_cache);
dev_node.write_inode_back_to_disk(my_cache);
tangrui_node.write_inode_back_to_disk(my_cache);
cout << "2. inode寫回磁盤" << endl;
/*
* 4. 建立數據扇區中的目錄結構
*/
sector_dir root_sec_dir;
root_sec_dir.dirs[0].init(".", 1);
root_sec_dir.dirs[1].init("..", 1);
root_sec_dir.dirs[2].init("bin", bin_node.get_inode_num());
root_sec_dir.dirs[3].init("etc", etc_node.get_inode_num());
root_sec_dir.dirs[4].init("home", home_node.get_inode_num());
root_sec_dir.dirs[5].init("dev", dev_node.get_inode_num());
sector_dir bin_sec_dir;
bin_sec_dir.dirs[0].init(".", bin_node.get_inode_num());
bin_sec_dir.dirs[1].init("..", root_node.get_inode_num());
sector_dir etc_sec_dir;
etc_sec_dir.dirs[0].init(".", etc_node.get_inode_num());
etc_sec_dir.dirs[1].init("..", root_node.get_inode_num());
sector_dir home_sec_dir;
home_sec_dir.dirs[0].init(".", home_node.get_inode_num());
home_sec_dir.dirs[1].init("..", root_node.get_inode_num());
home_sec_dir.dirs[2].init("tangrui", tangrui_node.get_inode_num());
sector_dir dev_sec_dir;
dev_sec_dir.dirs[0].init(".", dev_node.get_inode_num());
dev_sec_dir.dirs[1].init("..", root_node.get_inode_num());
sector_dir tangrui_sec_dir;
tangrui_sec_dir.dirs[0].init(".", tangrui_node.get_inode_num());
tangrui_sec_dir.dirs[1].init("..", home_node.get_inode_num());
cout << "3. 目錄創建完成" << endl;
/*
* 5. 將文件夾對應的扇區寫入到磁盤中
*/
root_sec_dir.write_back_to_disk(my_cache, root_node.get_sec_beg());
bin_sec_dir.write_back_to_disk(my_cache, bin_node.get_sec_beg());
etc_sec_dir.write_back_to_disk(my_cache, etc_node.get_sec_beg());
home_sec_dir.write_back_to_disk(my_cache, home_node.get_sec_beg());
dev_sec_dir.write_back_to_disk(my_cache, dev_node.get_sec_beg());
tangrui_sec_dir.write_back_to_disk(my_cache, tangrui_node.get_sec_beg());
cout << "4.目錄創建完成" << endl;
/*
* 6. 修改系統當前目錄位置為根目錄
*/
cur_dir = root_sec_dir;
cur_dir_node = root_node;
cout << "******************************** 初始化磁盤結束 ********************************" << endl;
return true;
}
8.1 演示
再次輸入ls
命令就可以看到磁盤已經格式化成功
9. Makefile
filesystem : ./src/main.cpp control.o buffer.o superblock.o sector.o inode.o
g++ -o ./bin/filesystem ./src/main.cpp ./obj/control.o ./obj/buffer.o \
./obj/superblock.o ./obj/sector.o ./obj/inode.o
inode.o : ./src/inode.cpp
g++ -c ./src/inode.cpp -o ./obj/inode.o
control.o : ./src/control.cpp
g++ -c ./src/control.cpp -o ./obj/control.o
test : buffer.o test.cpp superblock.o
g++ -o test test.cpp ./obj/buffer.o ./obj/superblock.o
sector.o : ./src/sector.cpp
g++ -c ./src/sector.cpp -o ./obj/sector.o
buffer.o : ./src/buffer.cpp
g++ -c ./src/buffer.cpp -o ./obj/buffer.o
superblock.o : ./src/superblock.cpp
g++ -c ./src/superblock.cpp -o ./obj/superblock.o
clean :
rm -f ./bin/filesystem test ./obj/* ./bin/* p1-1.png
七、需要提交的材料
- Makefile 文件
- README.md 文件
- 項目設計報告