ビデオを使用すると学習体験がさらに向上します。
セクション A のパート A: https://www.bilibili.com/video/BV1Uh4y1U7G6/?vd_source=701807c4f8684b13e922d0a8b116af31
セクション A のパート 2: https://www.bilibili.com/video/BV1Lj41127Bc/?vd_source=701807c4f8684b13e92 2 d0a8b116af31
セクションb: https://www.bilibili.com/video/BV1Pp4y1J7pN/?vd_source=701807c4f8684b13e922d0a8b116af31
セクションの一部 c: https://www.bilibili.com/video/BV1gp4y1N7GG/?vd_source=701807c4f8684b13e922d0a 8b116 af31
サブセクション c パート 2: https ://www.bilibili .com/video/BV11N411v7s5/?vd_source=701807c4f8684b13e922d0a8b116af31
セクション c のパート 3: https://www.bilibili.com/video/BV1uu411P7yV/?vd_source=701807c4f8684b13e922d0a8 b116af3
サブセクション 1 c の 4 つの部分: https: //www.bilibili.com/video /BV1YH4y1Q769/?vd_source=701807c4f8684b13e922d0a8b116af31
サブセクション c の 5 つの部分: https://www.bilibili.com/video/BV1UH4y1Q7kT/?vd_source=701807c4f8684b13e922d0a8b116af31
サブセクション c の 6 つの部分: https://www.bilibili.com/video/BV1594y1x7NH/?vd_source=701807c4f8684b 13e922 d0a8b116af31
セクション d: https://www.bilibili.com/video/BV1Fz4y1j71L/?vd_source=701807c4f8684b13e922d0a8b116af31
セクション e: https://www.bilibili.com/video/BV1Rh4y1P7HG/?vd_source=701807c4f8684 b13e922d0a8b116 af31
サブセクション f: https:/ /www.bilibili.com /video/BV1MV411P7kL/?vd_source=701807c4f8684b13e922d0a8b116af31
セクション g: https://www.bilibili.com/video/BV148411i7se/?vd_source=701807c4f8684b13e922d0a8b116af31セクション
h: https://www.bilibili.com/video /BV18u4y1k7oR/?vd_source= 701807c4f8684b13e922d0a8b116af31
セクション i: https://www.bilibili.com/video/BV1ph4y1Y7j9/?vd_source=701807c4f8684b13e922d0a8b116af31
セクション j: https://www.bilibili.com/video/BV1Ah4y1A7Hf/?vd_source=701807c4f8684b13e922d0a8b116af31
セクション k: https://www.bilibili.com/video/BV1Gk4y1w7Yb/?vd_source=701807c4f8684b13e922d0a8 b1 16af31
セクション 1: https:// www .bilibili.com/video/BV1a14y1r7y6/?vd_source=701807c4f8684b13e922d0a8b116af31
セクション m: https://www.bilibili.com/video/BV1aF411D7kZ/?vd_source=701807c4f8684b13e922d0a8b116afセクション 31
: https://www.bilibili.com/video/ BV1Kw411i79c /?vd_source=701807c4f8684b13e922d0a8b116af31
コードリポジトリ: https://github.com/xukanshan/the_truth_of_operationg_system
ファイル システムにおける中心的な概念は、inode、ディレクトリ (ディレクトリもファイルです)、スーパー ブロックの 3 つです。ファイル システムはパーティションに基づいています。つまり、各パーティションには独自のファイル システムがあります。
- iノード:
- i ノードは、ファイルとディスクの場所のマッピングを記録します。
- 各ファイルは i ノードに対応します。このファイルに対応する i ノードが見つかると、そのファイルがディスク上のどこに保存されているかがわかります。
- 各ディスク パーティションのすべての i ノードがアレイを形成します。ファイルの i ノード配列の添字を使用すると、配列内の対応する i ノード情報を見つけることができます。
- ディレクトリ:
- ディレクトリは実際には特別なファイルです。
- ディレクトリは、ファイル名と i ノードのマッピングを記録する多くのディレクトリ エントリで構成されます。ディレクトリが
/Desktop
フォルダーを管理する場合、このディレクトリ内の多くのディレクトリ エントリは、/Desktop
管理フォルダーの下のさまざまなファイルおよびサブディレクトリです。
- スーパーブロック:
- スーパーブロックには、inode、ディレクトリ、およびその他のファイル システム メタデータに関する情報が含まれています。
- 通常、これは各ディスク パーティションの 2 番目のセクターにあり、固定の場所になります。
- i ノードの場合、ディスク上の i ノード配列の開始位置が記録され、ルート ディレクトリの場合、スーパー ブロックはその i ノード ラベルも記録します。
ファイルを検索するプロセスの例:
探している場合/home/test.c
、ファイル システムはどのように機能するのでしょうか?
- まず、スーパーブロックの場所は固定されているため、ディスク上で直接アクセスできます。
- スーパーブロックから、ルート ディレクトリの i ノード ラベルと i ノード配列の場所を知ることができます。
- ルート ディレクトリの i ノード ラベルと i ノード配列の位置を使用して、ルート ディレクトリ ファイルに対応する i ノードを見つけ、ディスク上のルート ディレクトリ ファイルを見つけることができます。
home
ルート ディレクトリ ファイルで、という名前のディレクトリ エントリを探し、home
そこから i ノード配列ラベルを取得します。- i ノード ラベルを使用して
home
、再度 i ノード配列にアクセスし、home
ディスク上のディレクトリ ファイルの実際の場所を見つけます。 - 最後に、
home
ディレクトリ ファイル内で、test.c
という名前のディレクトリ エントリを検索し、test.c
そこから i ノード配列ラベルを取得して、test.c
ディスク上の場所を見つけます。
ディスク上の一般的なファイル システム メタデータ構造の場所を図
セクション a に示します。
ファイル システムを作成する関数を記述するだけpartition_format
です。つまり、ファイル システムのメタデータ (スーパー ブロック、フリー ブロック ビットマップ、inode ビットマップ、inode 配列、ルート ディレクトリ) を作成します。
次に、すべてのパーティションを走査する関数を作成しますfilesys_init
。パーティションにファイル システムがない場合は、その関数を呼び出してpartition_format
ファイル システムを作成します。
まずはデータ構造を準備しましょう
スーパーブロック ( myos/fs/super_block.h )
#ifndef __FS_SUPER_BLOCK_H
#define __FS_SUPER_BLOCK_H
#include "stdint.h"
/* 超级块 */
struct super_block {
uint32_t magic; // 用来标识文件系统类型,支持多文件系统的操作系统通过此标志来识别文件系统类型
uint32_t sec_cnt; // 本分区总共的扇区数
uint32_t inode_cnt; // 本分区中inode数量
uint32_t part_lba_base; // 本分区的起始lba地址
uint32_t block_bitmap_lba; // 块位图本身起始扇区地址
uint32_t block_bitmap_sects; // 扇区位图本身占用的扇区数量
uint32_t inode_bitmap_lba; // i结点位图起始扇区lba地址
uint32_t inode_bitmap_sects; // i结点位图占用的扇区数量
uint32_t inode_table_lba; // i结点表起始扇区lba地址
uint32_t inode_table_sects; // i结点表占用的扇区数量
uint32_t data_start_lba; // 数据区开始的第一个扇区号
uint32_t root_inode_no; // 根目录所在的I结点号
uint32_t dir_entry_size; // 目录项大小
uint8_t pad[460]; // 加上460字节,凑够512字节1扇区大小
} __attribute__ ((packed));
#endif
i ノード(myos/fs/inode.h)
#ifndef __FS_INODE_H
#define __FS_INODE_H
#include "stdint.h"
#include "list.h"
/* inode结构 */
struct inode {
uint32_t i_no; // inode编号
/* 当此inode是文件时,i_size是指文件大小,
若此inode是目录,i_size是指该目录下所有目录项大小之和*/
uint32_t i_size;
uint32_t i_open_cnts; // 记录此文件被打开的次数
bool write_deny; // 写文件不能并行,进程写文件前检查此标识
/* i_sectors[0-11]是直接块, i_sectors[12]用来存储一级间接块指针 */
uint32_t i_sectors[13];
struct list_elem inode_tag;
};
#endif
ディレクトリとディレクトリ エントリ ( myos/fs/dir.hstruct dir
)ディレクトリ ファイルの操作 (ディレクトリ ファイルを開くなど) を管理するため、ディレクトリのデータ構造はメモリ内にのみ存在することに注意してください。このような構造をメモリ内に作成します)
#ifndef __FS_DIR_H
#define __FS_DIR_H
#include "stdint.h"
#include "inode.h"
#define MAX_FILE_NAME_LEN 16 // 最大文件名长度
/* 目录结构 */
struct dir {
struct inode* inode;
uint32_t dir_pos; // 记录在目录内的偏移
uint8_t dir_buf[512]; // 目录的数据缓存
};
/* 目录项结构 */
struct dir_entry {
char filename[MAX_FILE_NAME_LEN]; // 普通文件或目录名称
uint32_t i_no; // 普通文件或目录对应的inode编号
enum file_types f_type; // 文件类型
};
#endif
ファイルタイプ定義: ( myos/fs/fs.h )
#ifndef __FS_FS_H
#define __FS_FS_H
#include "stdint.h"
#define MAX_FILES_PER_PART 4096 // 每个分区所支持最大创建的文件数
#define BITS_PER_SECTOR 4096 // 每扇区的位数
#define SECTOR_SIZE 512 // 扇区字节大小
#define BLOCK_SIZE SECTOR_SIZE // 块字节大小
/* 文件类型 */
enum file_types {
FT_UNKNOWN, // 不支持的文件类型
FT_REGULAR, // 普通文件
FT_DIRECTORY // 目录
};
#endif
次に、ファイル システムを作成する関数を記述しますpartition_format
。つまり、ファイル システムのメタデータ (スーパー ブロック、フリー ブロック ビットマップ、inode ビットマップ、inode 配列、ルート ディレクトリ) を作成します。
(myos/fs/fs.c)
#include "stdint.h"
#include "fs.h"
#include "inode.h"
#include "ide.h"
#include "memory.h"
#include "super_block.h"
#include "dir.h"
#include "stdio-kernel.h"
#include "string.h"
/* 格式化分区,也就是初始化分区的元信息,创建文件系统 */
static void partition_format(struct partition* part) {
/* 为方便实现,一个块大小是一扇区 */
uint32_t boot_sector_sects = 1;
uint32_t super_block_sects = 1;
uint32_t inode_bitmap_sects = DIV_ROUND_UP(MAX_FILES_PER_PART, BITS_PER_SECTOR); // I结点位图占用的扇区数.最多支持4096个文件
uint32_t inode_table_sects = DIV_ROUND_UP(((sizeof(struct inode) * MAX_FILES_PER_PART)), SECTOR_SIZE);
uint32_t used_sects = boot_sector_sects + super_block_sects + inode_bitmap_sects + inode_table_sects;
uint32_t free_sects = part->sec_cnt - used_sects;
/************** 简单处理块位图占据的扇区数 ***************/
uint32_t block_bitmap_sects;
block_bitmap_sects = DIV_ROUND_UP(free_sects, BITS_PER_SECTOR);
/* block_bitmap_bit_len是位图中位的长度,也是可用块的数量 */
uint32_t block_bitmap_bit_len = free_sects - block_bitmap_sects;
block_bitmap_sects = DIV_ROUND_UP(block_bitmap_bit_len, BITS_PER_SECTOR);
/*********************************************************/
/* 超级块初始化 */
struct super_block sb;
sb.magic = 0x19590318;
sb.sec_cnt = part->sec_cnt;
sb.inode_cnt = MAX_FILES_PER_PART;
sb.part_lba_base = part->start_lba;
sb.block_bitmap_lba = sb.part_lba_base + 2; // 第0块是引导块,第1块是超级块
sb.block_bitmap_sects = block_bitmap_sects;
sb.inode_bitmap_lba = sb.block_bitmap_lba + sb.block_bitmap_sects;
sb.inode_bitmap_sects = inode_bitmap_sects;
sb.inode_table_lba = sb.inode_bitmap_lba + sb.inode_bitmap_sects;
sb.inode_table_sects = inode_table_sects;
sb.data_start_lba = sb.inode_table_lba + sb.inode_table_sects; //数据区的起始就是inode数组的结束
sb.root_inode_no = 0;
sb.dir_entry_size = sizeof(struct dir_entry);
printk("%s info:\n", part->name);
printk(" magic:0x%x\n part_lba_base:0x%x\n all_sectors:0x%x\n inode_cnt:0x%x\n block_bitmap_lba:0x%x\n block_bitmap_sectors:0x%x\n inode_bitmap_lba:0x%x\n inode_bitmap_sectors:0x%x\n inode_table_lba:0x%x\n inode_table_sectors:0x%x\n data_start_lba:0x%x\n", sb.magic, sb.part_lba_base, sb.sec_cnt, sb.inode_cnt, sb.block_bitmap_lba, sb.block_bitmap_sects, sb.inode_bitmap_lba, sb.inode_bitmap_sects, sb.inode_table_lba, sb.inode_table_sects, sb.data_start_lba);
struct disk* hd = part->my_disk;
/*******************************
* 1 将超级块写入本分区的1扇区 *
******************************/
ide_write(hd, part->start_lba + 1, &sb, 1);
printk(" super_block_lba:0x%x\n", part->start_lba + 1);
/* 找出数据量最大的元信息,用其尺寸做存储缓冲区*/
uint32_t buf_size = (sb.block_bitmap_sects >= sb.inode_bitmap_sects ? sb.block_bitmap_sects : sb.inode_bitmap_sects);
buf_size = (buf_size >= sb.inode_table_sects ? buf_size : sb.inode_table_sects) * SECTOR_SIZE;
uint8_t* buf = (uint8_t*)sys_malloc(buf_size); // 申请的内存由内存管理系统清0后返回
/**************************************
* 2 将块位图初始化并写入sb.block_bitmap_lba *
*************************************/
/* 初始化块位图block_bitmap */
buf[0] |= 0x01; // 第0个块预留给根目录,位图中先占位
uint32_t block_bitmap_last_byte = block_bitmap_bit_len / 8; //计算出块位图最后一字节的偏移
uint8_t block_bitmap_last_bit = block_bitmap_bit_len % 8; //计算出块位图最后一位的偏移
uint32_t last_size = SECTOR_SIZE - (block_bitmap_last_byte % SECTOR_SIZE); // last_size是位图所在最后一个扇区中,不足一扇区的其余部分
/* 1 先将位图最后一字节到其所在的扇区的结束全置为1,即超出实际块数的部分直接置为已占用*/
memset(&buf[block_bitmap_last_byte], 0xff, last_size);
/* 2 再将上一步中覆盖的最后一字节内的有效位重新置0 */
uint8_t bit_idx = 0;
while (bit_idx <= block_bitmap_last_bit) {
buf[block_bitmap_last_byte] &= ~(1 << bit_idx++);
}
ide_write(hd, sb.block_bitmap_lba, buf, sb.block_bitmap_sects);
/***************************************
* 3 将inode位图初始化并写入sb.inode_bitmap_lba *
***************************************/
/* 先清空缓冲区*/
memset(buf, 0, buf_size);
buf[0] |= 0x1; // 第0个inode分给了根目录
/* 由于inode_table中共4096个inode,位图inode_bitmap正好占用1扇区,
* 即inode_bitmap_sects等于1, 所以位图中的位全都代表inode_table中的inode,
* 无须再像block_bitmap那样单独处理最后一扇区的剩余部分,
* inode_bitmap所在的扇区中没有多余的无效位 */
ide_write(hd, sb.inode_bitmap_lba, buf, sb.inode_bitmap_sects);
/***************************************
* 4 将inode数组初始化并写入sb.inode_table_lba *
***************************************/
/* 准备写inode_table中的第0项,即根目录所在的inode */
memset(buf, 0, buf_size); // 先清空缓冲区buf
struct inode* i = (struct inode*)buf;
i->i_size = sb.dir_entry_size * 2; // .和..
i->i_no = 0; // 根目录占inode数组中第0个inode
i->i_sectors[0] = sb.data_start_lba; // 由于上面的memset,i_sectors数组的其它元素都初始化为0
ide_write(hd, sb.inode_table_lba, buf, sb.inode_table_sects);
/***************************************
* 5 将根目录初始化并写入sb.data_start_lba
***************************************/
/* 写入根目录的两个目录项.和.. */
memset(buf, 0, buf_size);
struct dir_entry* p_de = (struct dir_entry*)buf;
/* 初始化当前目录"." */
memcpy(p_de->filename, ".", 1);
p_de->i_no = 0;
p_de->f_type = FT_DIRECTORY;
p_de++;
/* 初始化当前目录父目录".." */
memcpy(p_de->filename, "..", 2);
p_de->i_no = 0; // 根目录的父目录依然是根目录自己
p_de->f_type = FT_DIRECTORY;
/* sb.data_start_lba已经分配给了根目录,里面是根目录的目录项 */
ide_write(hd, sb.data_start_lba, buf, 1);
printk(" root_dir_lba:0x%x\n", sb.data_start_lba);
printk("%s format done\n", part->name);
sys_free(buf);
}
作成者の 2 行のコードは 2 回しか反復していませんが、これは正しくありません。値が安定するまでは無限反復を使用する必要があります。このコードが実際に解決する問題は、合計量は変わらないが、利用可能なリソースと管理リソースはどれだけあるのか、ということです。
例 1:
次の例を考えてみましょう。会社には 12 人がいます。1 人が 5 人を管理できると仮定します。反復プロセスは次のようになります。
- 最初の反復: 3 人が管理し、9 人が作業します。
- 2 回目の反復: 2 人が管理し、10 人が作業します。
明らかに、正しい答えが得られました。
例 2:
別の例を考えてみましょう。合計 100 人がいて、1 人が 3 人を管理できます。反復プロセスは次のようになります。
- 最初の反復: 34 人が管理し、66 人が作業しました。
- 2 回目の反復: 22 人が管理し、78 人が作業します。
- 3 回目: 26 人が管理し、74 人が作業します。
- 4 回目の反復: 25 人が管理し、75 人が作業します。
- 5 回目の反復: 25 人が管理し、75 人が作業します。
最終的には安定した値に到達します。
1 人が少数の人々を管理する場合、非常に多くの反復が必要になると結論付けることができます。ただし、1 人が多数のユーザーを管理する場合、必要な反復回数はわずかです。1 つのセクターで 4096 セクターを管理できるため、作成者のコードはほとんどの場合正常に動作します。
変更 ( myos/fs/fs.c/partition_format )
/************** 简单处理块位图占据的扇区数 ***************/
uint32_t block_bitmap_sects;
block_bitmap_sects = DIV_ROUND_UP(free_sects, BITS_PER_SECTOR);
/* block_bitmap_bit_len是位图中位的长度,也是可用块的数量 */
uint32_t block_bitmap_bit_len = free_sects - block_bitmap_sects;
block_bitmap_sects = DIV_ROUND_UP(block_bitmap_bit_len, BITS_PER_SECTOR);
/*********************************************************/
のために
/************** 简单处理块位图占据的扇区数 ***************/
uint32_t now_total_free_sects = free_sects; // 定义一个现在总的可用扇区数
uint32_t prev_block_bitmap_sects = 0; // 之前的块位图扇区数
uint32_t block_bitmap_sects = DIV_ROUND_UP(now_total_free_sects, BITS_PER_SECTOR); // 初始估算
uint32_t block_bitmap_bit_len;
while (block_bitmap_sects != prev_block_bitmap_sects) {
prev_block_bitmap_sects = block_bitmap_sects;
/* block_bitmap_bit_len是位图中位的长度,也是可用块的数量 */
block_bitmap_bit_len = now_total_free_sects - block_bitmap_sects;
block_bitmap_sects = DIV_ROUND_UP(block_bitmap_bit_len, BITS_PER_SECTOR);
}
/*********************************************************/
block_bitmap_last_bit の実際の意味はブロック ビットマップの最後のバイトの有効なビットの数であり、bit_idx は 0 から始まるため、次の while ループはもう一度ループするため、変更する必要があります: (myos/fs/fs.c/パーティション形式)
while (bit_idx <= block_bitmap_last_bit) {
buf[block_bitmap_last_byte] &= ~(1 << bit_idx++);
}
のために
while (bit_idx < block_bitmap_last_bit) {
buf[block_bitmap_last_byte] &= ~(1 << bit_idx++);
}
次に、すべてのパーティションを走査する関数を作成しますfilesys_init
。パーティションにファイル システムがない場合は、その関数を呼び出してpartition_format
ファイル システムを作成します。
変更 ( myos/fs/fs.c )
#include "debug.h"
/* 在磁盘上搜索文件系统,若没有则格式化分区创建文件系统 */
void filesys_init() {
uint8_t channel_no = 0, dev_no, part_idx = 0;
/* sb_buf用来存储从硬盘上读入的超级块 */
struct super_block* sb_buf = (struct super_block*)sys_malloc(SECTOR_SIZE);
if (sb_buf == NULL) {
PANIC("alloc memory failed!");
}
printk("searching filesystem......\n");
while (channel_no < channel_cnt) {
//遍历两个通道
dev_no = 0;
while(dev_no < 2) {
//遍历通道下1主1从两个硬盘
if (dev_no == 0) {
// 跨过裸盘hd60M.img
dev_no++;
continue;
}
struct disk* hd = &channels[channel_no].devices[dev_no];
struct partition* part = hd->prim_parts;
while(part_idx < 12) {
// 遍历硬盘的分区,4个主分区+8个逻辑
if (part_idx == 4) {
// 开始处理逻辑分区
part = hd->logic_parts;
}
/* channels数组是全局变量,默认值为0,disk属于其嵌套结构,
* partition又为disk的嵌套结构,因此partition中的成员默认也为0.
* 若partition未初始化,则partition中的成员仍为0.
* 下面处理存在的分区. */
if (part->sec_cnt != 0) {
// 如果分区存在
memset(sb_buf, 0, SECTOR_SIZE);
/* 读出分区的超级块,根据魔数是否正确来判断是否存在文件系统 */
ide_read(hd, part->start_lba + 1, sb_buf, 1);
/* 只支持自己的文件系统.若磁盘上已经有文件系统就不再格式化了 */
if (sb_buf->magic == 0x19590318) {
printk("%s has filesystem\n", part->name);
}
else {
// 其它文件系统不支持,一律按无文件系统处理
printk("formatting %s`s partition %s......\n", hd->name, part->name);
partition_format(part);
}
}
part_idx++;
part++; // 下一分区
}
dev_no++; // 下一磁盘
}
channel_no++; // 下一通道
}
sys_free(sb_buf);
}
関数宣言の変更 ( myos/fs/fs.h )
void filesys_init(void);
ファイルシステムを初期化するために( myos/kernel/init.c ) を変更します。
#include "fs.h"
/*负责初始化所有模块 */
void init_all() {
put_str("init_all\n");
idt_init(); // 初始化中断
mem_init(); // 初始化内存管理系统
thread_init(); // 初始化线程相关结构
timer_init(); // 初始化PIT
console_init(); // 控制台初始化最好放在开中断之前
keyboard_init(); // 键盘初始化
tss_init(); // tss初始化
syscall_init(); // 初始化系统调用
intr_enable(); // 后面的ide_init需要打开中断
ide_init(); // 初始化硬盘
filesys_init(); // 初始化文件系统
}
テストコード ( myos/kernel/main.c )
#include "print.h"
#include "init.h"
#include "thread.h"
#include "interrupt.h"
#include "console.h"
#include "process.h"
#include "syscall-init.h"
#include "syscall.h"
#include "stdio.h"
#include "memory.h"
void k_thread_a(void*);
void k_thread_b(void*);
void u_prog_a(void);
void u_prog_b(void);
int main(void) {
put_str("I am kernel\n");
init_all();
while(1);
process_execute(u_prog_a, "u_prog_a");
process_execute(u_prog_b, "u_prog_b");
thread_start("k_thread_a", 31, k_thread_a, "I am thread_a");
thread_start("k_thread_b", 31, k_thread_b, "I am thread_b");
while(1);
return 0;
}
/* 在线程中运行的函数 */
void k_thread_a(void* arg) {
void* addr1 = sys_malloc(256);
void* addr2 = sys_malloc(255);
void* addr3 = sys_malloc(254);
console_put_str(" thread_a malloc addr:0x");
console_put_int((int)addr1);
console_put_char(',');
console_put_int((int)addr2);
console_put_char(',');
console_put_int((int)addr3);
console_put_char('\n');
int cpu_delay = 100000;
while(cpu_delay-- > 0);
sys_free(addr1);
sys_free(addr2);
sys_free(addr3);
while(1);
}
/* 在线程中运行的函数 */
void k_thread_b(void* arg) {
void* addr1 = sys_malloc(256);
void* addr2 = sys_malloc(255);
void* addr3 = sys_malloc(254);
console_put_str(" thread_b malloc addr:0x");
console_put_int((int)addr1);
console_put_char(',');
console_put_int((int)addr2);
console_put_char(',');
console_put_int((int)addr3);
console_put_char('\n');
int cpu_delay = 100000;
while(cpu_delay-- > 0);
sys_free(addr1);
sys_free(addr2);
sys_free(addr3);
while(1);
}
/* 测试用户进程 */
void u_prog_a(void) {
void* addr1 = malloc(256);
void* addr2 = malloc(255);
void* addr3 = malloc(254);
printf(" prog_a malloc addr:0x%x,0x%x,0x%x\n", (int)addr1, (int)addr2, (int)addr3);
int cpu_delay = 100000;
while(cpu_delay-- > 0);
free(addr1);
free(addr2);
free(addr3);
while(1);
}
/* 测试用户进程 */
void u_prog_b(void) {
void* addr1 = malloc(256);
void* addr2 = malloc(255);
void* addr3 = malloc(254);
printf(" prog_b malloc addr:0x%x,0x%x,0x%x\n", (int)addr1, (int)addr2, (int)addr3);
int cpu_delay = 100000;
while(cpu_delay-- > 0);
free(addr1);
free(addr2);
free(addr3);
while(1);
}
fs.c をコンパイルするためのルールを追加することに加えて、Makefile にも以下を追加する必要があります。-I fs/
LIB= -I lib/ -I lib/kernel/ -I lib/user/ -I kernel/ -I device/ -I thread/ -I userprog/ -I fs/
を実行する前にバックアップすることをお勧めしますhd80M.img
。そうでないと、何か問題が発生した場合に意味のないデータが書き込まれる可能性があります。注文:cp hd80M.img hd80M.img-beifen
セクション b:
以前は、ファイル システムのメタデータを作成し、それをディスクに書き込む関数を作成しました。実際に特定のパーティションのデータを使用したい場合は、パーティションをマウントする必要があります。つまり、ファイル システムのメタデータ (スーパー ブロック、ブロック ビットマップ、inode ビットマップ、inode 配列は大きすぎて読み込むことができないため) を読み取る必要があります。 ). メモリへ、) メモリへ、そしてこれらのメタデータへの変更は適時にディスクに同期される必要があります。次に、パーティションをマウントするこの関数を作成しましょうmount_partition
。この関数がlist_traversal
呼び出されます。
変更 ( myos/fs/fs.c )
struct partition* cur_part; // 默认情况下操作的是哪个分区
/* 在分区链表中找到名为part_name的分区,并将其指针赋值给cur_part */
static bool mount_partition(struct list_elem* pelem, int arg) {
char* part_name = (char*)arg;
struct partition* part = elem2entry(struct partition, part_tag, pelem);
if (!strcmp(part->name, part_name)) {
cur_part = part;
struct disk* hd = cur_part->my_disk;
/* sb_buf用来存储从硬盘上读入的超级块 */
struct super_block* sb_buf = (struct super_block*)sys_malloc(SECTOR_SIZE);
/* 在内存中创建分区cur_part的超级块 */
cur_part->sb = (struct super_block*)sys_malloc(sizeof(struct super_block));
if (cur_part->sb == NULL) {
PANIC("alloc memory failed!");
}
/* 读入超级块 */
memset(sb_buf, 0, SECTOR_SIZE);
ide_read(hd, cur_part->start_lba + 1, sb_buf, 1);
/* 把sb_buf中超级块的信息复制到分区的超级块sb中。*/
memcpy(cur_part->sb, sb_buf, sizeof(struct super_block));
/********** 将硬盘上的块位图读入到内存 ****************/
cur_part->block_bitmap.bits = (uint8_t*)sys_malloc(sb_buf->block_bitmap_sects * SECTOR_SIZE);
if (cur_part->block_bitmap.bits == NULL) {
PANIC("alloc memory failed!");
}
cur_part->block_bitmap.btmp_bytes_len = sb_buf->block_bitmap_sects * SECTOR_SIZE;
/* 从硬盘上读入块位图到分区的block_bitmap.bits */
ide_read(hd, sb_buf->block_bitmap_lba, cur_part->block_bitmap.bits, sb_buf->block_bitmap_sects);
/*************************************************************/
/********** 将硬盘上的inode位图读入到内存 ************/
cur_part->inode_bitmap.bits = (uint8_t*)sys_malloc(sb_buf->inode_bitmap_sects * SECTOR_SIZE);
if (cur_part->inode_bitmap.bits == NULL) {
PANIC("alloc memory failed!");
}
cur_part->inode_bitmap.btmp_bytes_len = sb_buf->inode_bitmap_sects * SECTOR_SIZE;
/* 从硬盘上读入inode位图到分区的inode_bitmap.bits */
ide_read(hd, sb_buf->inode_bitmap_lba, cur_part->inode_bitmap.bits, sb_buf->inode_bitmap_sects);
/*************************************************************/
list_init(&cur_part->open_inodes);
printk("mount %s done!\n", part->name);
/* 此处返回true是为了迎合主调函数list_traversal的实现,与函数本身功能无关。
只有返回true时list_traversal才会停止遍历,减少了后面元素无意义的遍历.*/
return true;
}
return false; // 使list_traversal继续遍历
}
( myos/fs/fs.c )を変更し、呼び出しを使用して指定されたパーティションのファイル システムをマウントするコードをfilesys_init
追加します。list_traversal
mount_partition
/* 在磁盘上搜索文件系统,若没有则格式化分区创建文件系统 */
void filesys_init() {
uint8_t channel_no = 0, dev_no, part_idx = 0;
/* sb_buf用来存储从硬盘上读入的超级块 */
struct super_block* sb_buf = (struct super_block*)sys_malloc(SECTOR_SIZE);
if (sb_buf == NULL) {
PANIC("alloc memory failed!");
}
printk("searching filesystem......\n");
while (channel_no < channel_cnt) {
//遍历两个通道
dev_no = 0;
while(dev_no < 2) {
//遍历通道下1主1从两个硬盘
if (dev_no == 0) {
// 跨过裸盘hd60M.img
dev_no++;
continue;
}
struct disk* hd = &channels[channel_no].devices[dev_no];
struct partition* part = hd->prim_parts;
while(part_idx < 12) {
// 遍历硬盘的分区,4个主分区+8个逻辑
if (part_idx == 4) {
// 开始处理逻辑分区
part = hd->logic_parts;
}
/* channels数组是全局变量,默认值为0,disk属于其嵌套结构,
* partition又为disk的嵌套结构,因此partition中的成员默认也为0.
* 若partition未初始化,则partition中的成员仍为0.
* 下面处理存在的分区. */
if (part->sec_cnt != 0) {
// 如果分区存在
memset(sb_buf, 0, SECTOR_SIZE);
/* 读出分区的超级块,根据魔数是否正确来判断是否存在文件系统 */
ide_read(hd, part->start_lba + 1, sb_buf, 1);
/* 只支持自己的文件系统.若磁盘上已经有文件系统就不再格式化了 */
if (sb_buf->magic == 0x19590318) {
printk("%s has filesystem\n", part->name);
}
else {
// 其它文件系统不支持,一律按无文件系统处理
printk("formatting %s`s partition %s......\n", hd->name, part->name);
partition_format(part);
}
}
part_idx++;
part++; // 下一分区
}
dev_no++; // 下一磁盘
}
channel_no++; // 下一通道
}
sys_free(sb_buf);
/* 确定默认操作的分区 */
char default_part[8] = "sdb1";
/* 挂载分区 */
list_traversal(&partition_list, mount_partition, (int)default_part);
}
サポートコード
変更 ( myos/device/ide.h )
(このパーティションのリンク リストは、ide_init
マウント ディスク関数で呼び出すpartition_scan
ときに作成されます。そのため、前のセクションでパーティション ファイル システムを作成しました。実際、このリンク リストをたどることができます)
extern struct list partition_list;
mount_partition
中身はsb_buf
公開されません!メモリリークの原因になります!
変更 ( myos/fs/fs.c/mountpartition )
list_init(&cur_part->open_inodes);
printk("mount %s done!\n", part->name);
/* 此处返回true是为了迎合主调函数list_traversal的实现,与函数本身功能无关。
只有返回true时list_traversal才会停止遍历,减少了后面元素无意义的遍历.*/
return true;
}
return false; // 使list_traversal继续遍历
のために
list_init(&cur_part->open_inodes);
printk("mount %s done!\n", part->name);
/* 此处返回true是为了迎合主调函数list_traversal的实现,与函数本身功能无关。
只有返回true时list_traversal才会停止遍历,减少了后面元素无意义的遍历.*/
sys_free(sb_buf);
return true;
}
return false; // 使list_traversal继续遍历
セクション c:
ファイル システムの中核的な機能は、ファイル (inode) とディスク上のそれらの保存場所の間のマッピング関係を管理することです。ただし、実際のファイル操作 (開く、読み取り、書き込みなど) について話す場合、これらの操作は常に特定のプロセスまたはスレッドに基づいて実行されます。したがって、プロセスまたはスレッドのファイル (inode) へのマッピングを確立するメカニズムが必要であり、これがファイル記述子が行うことです。ファイル記述子は「ファイル構造」に基づいて実装されます。
ファイル構造は、アプリケーション プロセスによるファイルの現在の操作ステータスを詳細に記述します。
- アプリケーション プロセスがファイルを操作する (たとえば、開く) と、オペレーティング システムは対応するグローバル ファイル構造を作成し、それをグローバル ファイル構造配列 (ファイル テーブルとも呼ばれる) に配置して、ファイルに対するプロセスの操作ステータスを追跡します。
- ファイル構造には、
fd_pos
現在のプロセス操作の場所を表すフィールドが含まれています。たとえば、ファイルを開くときtest.txt
、fd_pos
通常、初期値はファイルの先頭です。 - ファイルに対して実際の読み取りおよび書き込み操作を実行すると、
fd_pos
ファイルは操作の場所に更新されます。たとえば、ファイルの途中でデータの書き込みを開始した場合、fd_pos
データはその位置に更新され、書き込み操作が発生するにつれて前方に進み続けます。編集操作を実行すると、入力内容はまずメモリの入力バッファに保存されます。を押すとctrl + s
、オペレーティング システムはファイル構造の開始点として使用しfd_pos
、バッファの内容をディスク ファイルに書き込み、fd_pos
書き込みが完了した後の位置に更新します。 fd_inode
ファイル構造には、対応するファイルの i ノードへのポインタであるフィールドも含まれています。このfd_inode
フィールドにより、オペレーティング システムはディスク上のファイルの場所を簡単に見つけて、メモリ入力バッファの内容をディスクに書き込むことができます。
ファイル構造は特定のアプリケーションと密接に関連しているため、ファイル記述子配列という新しい概念を導入する必要があります。
-
ファイル記述子配列は、プロセスのプロセス制御ブロック (PCB) に格納される配列です。各要素は、ファイル構造のグローバル配列内の特定のエントリを指すファイル記述子 (単なる数字) です。
-
プロセスがファイルを開くと、オペレーティング システムはそのファイルのファイル構造を作成し、それをグローバル ファイル構造配列に配置します。次に、オペレーティング システムは、グローバル配列内のファイル構造の位置 (またはインデックス) をプロセスのファイル記述子配列内の空き要素に割り当てます。
たとえば、プロセスが 2 つのファイルを開き、グローバル ファイル構造配列内のそれらの位置はそれぞれ 32 と 58 であるとします。次に、プロセスの PCB 内のファイル記述子配列の 0 番目の要素には値 32 が割り当てられ、最初の要素には値 58 が割り当てられます。
-
PCB 内のファイル記述子配列のサイズにより、プロセスが同時に開くことができるファイルの最大数が制限されます。プロセスが新しいファイルを開いたにもかかわらず、ファイル記述子配列がいっぱいの場合、操作は失敗します。
-
異なるファイルを開くと、当然ながらファイル記述子が消費されることに注意してください。しかし、ファイル構造 (つまり
fd_pos
) にファイル操作の場所の概念を導入したため、プロセスが同じファイルを複数回開く場合 (つまり、開くたびに独自の読み取りおよび書き込み場所がある)、開くたびに新しいファイル記述子が消費されます。
ファイル システムの章には多くの機能が含まれていますが、実際の中心となるのは、スーパー ブロック、inode、ディレクトリ、ファイル構造の 4 つの概念間の相互作用を扱うことです。
ファイル記述子をサポートするには、task_struct
構造の定義を変更する必要があります。( もparent_id
追加cwd_inode_nr
)
変更 ( myos/thread/thread.h )
#define MAX_FILES_OPEN_PER_PROC 8
/* 进程或线程的pcb,程序控制块, 此结构体用于存储线程的管理信息*/
struct task_struct
{
uint32_t *self_kstack; // 用于存储线程的栈顶位置,栈顶放着线程要用到的运行信息
pid_t pid;
enum task_status status;
uint8_t priority; // 线程优先级
char name[16]; // 用于存储自己的线程的名字
uint8_t ticks; // 线程允许上处理器运行还剩下的滴答值,因为priority不能改变,所以要在其之外另行定义一个值来倒计时
uint32_t elapsed_ticks; // 此任务自上cpu运行后至今占用了多少cpu嘀嗒数, 也就是此任务执行了多久*/
struct list_elem general_tag; // general_tag的作用是用于线程在一般的队列(如就绪队列或者等待队列)中的结点
struct list_elem all_list_tag; // all_list_tag的作用是用于线程队列thread_all_list(这个队列用于管理所有线程)中的结点
uint32_t *pgdir; // 进程自己页表的虚拟地址
struct virtual_addr userprog_vaddr; // 用户进程的虚拟地址
int32_t fd_table[MAX_FILES_OPEN_PER_PROC]; // 已打开文件数组
uint32_t cwd_inode_nr; // 进程所在的工作目录的inode编号
int16_t parent_pid; // 父进程pid
struct mem_block_desc u_block_desc[DESC_CNT]; // 用户进程内存块描述符
uint32_t stack_magic; // 如果线程的栈无限生长,总会覆盖地pcb的信息,那么需要定义个边界数来检测是否栈已经到了PCB的边界
};
プロセス/スレッドを作成する初期化 PCB リンクに、ファイル記述子配列とparent_id
初期化cwd_inode_nr
コードを追加します。
変更 ( myos/thread/thread.c )
/* 初始化线程基本信息 , pcb中存储的是线程的管理信息,此函数用于根据传入的pcb的地址,线程的名字等来初始化线程的管理信息*/
void init_thread(struct task_struct *pthread, char *name, int prio)
{
memset(pthread, 0, sizeof(*pthread)); // 把pcb初始化为0
pthread->pid = allocate_pid();
strcpy(pthread->name, name); // 将传入的线程的名字填入线程的pcb中
if (pthread == main_thread)
{
pthread->status = TASK_RUNNING; // 由于把main函数也封装成一个线程,并且它一直是运行的,故将其直接设为TASK_RUNNING */
}
else
{
pthread->status = TASK_READY;
}
pthread->priority = prio;
/* self_kstack是线程自己在内核态下使用的栈顶地址 */
pthread->ticks = prio;
pthread->elapsed_ticks = 0;
pthread->pgdir = NULL; // 线程没有自己的地址空间,进程的pcb这一项才有用,指向自己的页表虚拟地址
pthread->self_kstack = (uint32_t *)((uint32_t)pthread + PG_SIZE); // 本操作系统比较简单,线程不会太大,就将线程栈顶定义为pcb地址
//+4096的地方,这样就留了一页给线程的信息(包含管理信息与运行信息)空间
/* 标准输入输出先空出来 */
pthread->fd_table[0] = 0;
pthread->fd_table[1] = 1;
pthread->fd_table[2] = 2;
/* 其余的全置为-1 */
uint8_t fd_idx = 3;
while (fd_idx < MAX_FILES_OPEN_PER_PROC)
{
pthread->fd_table[fd_idx] = -1;
fd_idx++;
}
pthread->cwd_inode_nr = 0; // 以根目录做为默认工作路径
pthread->parent_pid = -1; // -1表示没有父进程
pthread->stack_magic = 0x19870916; // 定义的边界数字,随便选的数字来判断线程的栈是否已经生长到覆盖pcb信息了
}
ファイルやディレクトリに関連する操作は、inode の操作と切り離すことができません。inode を通じてファイルの保存場所を知る必要があるため、ファイルの操作は常にファイルの i ノードを見つけることを意味します。次に、一連の i ノード処理関数を実装します。ディスク内の i ノードの場所の検索、i ノードの初期化、メモリへの i ノードのロード、メモリ内の i ノードの変更とディスクへの同期、メモリからの i ノードの削除が含まれます。
inode_locate
inode 配列内の指定された i ノードのインデックスを渡すことにより、inode が配置されているセクターとセクター内のオフセットを取得するために使用されます。原則: inode 配列内の i ノードのインデックスが渡され、inode 配列の開始セクターがスーパー ブロックに記録されているため (ファイル システムがマウントされた後、スーパー ブロックはメモリ内にあります)。したがって、各 i ノードのサイズにインデックスを付けることで、inode が配置されているセクターとセクター内のオフセットを計算できます。
( myos/fs/inode.c )
#include "stdint.h"
#include "ide.h"
#include "inode.h"
#include "debug.h"
#include "super_block.h"
/* 用来存储inode位置 */
struct inode_position {
bool two_sec; // inode是否跨扇区
uint32_t sec_lba; // inode所在的扇区号
uint32_t off_size; // inode在扇区内的字节偏移量
};
/* 获取inode所在的扇区和扇区内的偏移量 */
static void inode_locate(struct partition *part, uint32_t inode_no, struct inode_position *inode_pos)
{
/* inode_table在硬盘上是连续的 */
ASSERT(inode_no < 4096);
uint32_t inode_table_lba = part->sb->inode_table_lba;
uint32_t inode_size = sizeof(struct inode);
uint32_t off_size = inode_no * inode_size; // 第inode_no号I结点相对于inode_table_lba的字节偏移量
uint32_t off_sec = off_size / 512; // 第inode_no号I结点相对于inode_table_lba的扇区偏移量
uint32_t off_size_in_sec = off_size % 512; // 待查找的inode所在扇区中的起始地址
/* 判断此i结点是否跨越2个扇区 */
uint32_t left_in_sec = 512 - off_size_in_sec;
if (left_in_sec < inode_size)
{
// 若扇区内剩下的空间不足以容纳一个inode,必然是I结点跨越了2个扇区
inode_pos->two_sec = true;
}
else
{
// 否则,所查找的inode未跨扇区
inode_pos->two_sec = false;
}
inode_pos->sec_lba = inode_table_lba + off_sec;
inode_pos->off_size = off_size_in_sec;
}
inode_sync
inode 配列に対応するディスクの場所に i ノードを書き込むために使用されます。原則: を呼び出してinode_locate
ディスク上の i ノードの位置を解析し、次に i ノードが配置されているブロック全体をメモリ バッファに読み取り、次に i ノードをバッファ内の対応する位置に書き込み、次にブロック全体をメモリ バッファに書き込みます。ディスク。
変更 ( myos/fs/inode.c )
#include "string.h"
/* 将inode写入到分区part */
void inode_sync(struct partition *part, struct inode *inode, void *io_buf)
{
// io_buf是用于硬盘io的缓冲区
uint8_t inode_no = inode->i_no;
struct inode_position inode_pos;
inode_locate(part, inode_no, &inode_pos); // inode位置信息会存入inode_pos
ASSERT(inode_pos.sec_lba <= (part->start_lba + part->sec_cnt));
/* 硬盘中的inode中的成员inode_tag和i_open_cnts是不需要的,
* 它们只在内存中记录链表位置和被多少进程共享 */
struct inode pure_inode;
memcpy(&pure_inode, inode, sizeof(struct inode));
/* 以下inode的三个成员只存在于内存中,现在将inode同步到硬盘,清掉这三项即可 */
pure_inode.i_open_cnts = 0;
pure_inode.write_deny = false; // 置为false,以保证在硬盘中读出时为可写
pure_inode.inode_tag.prev = pure_inode.inode_tag.next = NULL;
char *inode_buf = (char *)io_buf;
if (inode_pos.two_sec)
{
// 若是跨了两个扇区,就要读出两个扇区再写入两个扇区
/* 读写硬盘是以扇区为单位,若写入的数据小于一扇区,要将原硬盘上的内容先读出来再和新数据拼成一扇区后再写入 */
ide_read(part->my_disk, inode_pos.sec_lba, inode_buf, 2); // inode在format中写入硬盘时是连续写入的,所以读入2块扇区
/* 开始将待写入的inode拼入到这2个扇区中的相应位置 */
memcpy((inode_buf + inode_pos.off_size), &pure_inode, sizeof(struct inode));
/* 将拼接好的数据再写入磁盘 */
ide_write(part->my_disk, inode_pos.sec_lba, inode_buf, 2);
}
else
{
// 若只是一个扇区
ide_read(part->my_disk, inode_pos.sec_lba, inode_buf, 1);
memcpy((inode_buf + inode_pos.off_size), &pure_inode, sizeof(struct inode));
ide_write(part->my_disk, inode_pos.sec_lba, inode_buf, 1);
}
}
inode_open
i ノードを開くために使用されます。つまり、渡された i ノード配列インデックスに従って i ノードを検索し、メモリにロードします。このパーティション内で開いているファイルの inode リンク リストは、パーティションを管理する struct パーティションに保持されているため、最初にこのリンク リストを検索します (このリンク リストはメモリ内にあります)。見つからない場合は、ディスク内を検索します。つまり、inode_locate
ディスク上の位置を Parse を呼び出してメモリに読み取ります。オープン I ノードのリンク リストはすべてのプロセスで共有する必要があるため、i ノードを保存するすべてのメモリはカーネル ヒープ領域に適用する必要があります (すべてのプロセスがカーネルにアクセスできるため)。
変更 ( myos/fs/inode.c )
/* 根据i结点号返回相应的i结点 */
struct inode *inode_open(struct partition *part, uint32_t inode_no)
{
/* 先在已打开inode链表中找inode,此链表是为提速创建的缓冲区 */
struct list_elem *elem = part->open_inodes.head.next;
struct inode *inode_found;
while (elem != &part->open_inodes.tail)
{
inode_found = elem2entry(struct inode, inode_tag, elem);
if (inode_found->i_no == inode_no)
{
inode_found->i_open_cnts++;
return inode_found;
}
elem = elem->next;
}
/*由于open_inodes链表中找不到,下面从硬盘上读入此inode并加入到此链表 */
struct inode_position inode_pos;
/* inode位置信息会存入inode_pos, 包括inode所在扇区地址和扇区内的字节偏移量 */
inode_locate(part, inode_no, &inode_pos);
/* 为使通过sys_malloc创建的新inode被所有任务共享,
* 需要将inode置于内核空间,故需要临时
* 将cur_pbc->pgdir置为NULL */
struct task_struct *cur = running_thread();
uint32_t *cur_pagedir_bak = cur->pgdir;
cur->pgdir = NULL;
/* 以上三行代码完成后下面分配的内存将位于内核区 */
inode_found = (struct inode *)sys_malloc(sizeof(struct inode));
/* 恢复pgdir */
cur->pgdir = cur_pagedir_bak;
char *inode_buf;
if (inode_pos.two_sec)
{
// 考虑跨扇区的情况
inode_buf = (char *)sys_malloc(1024);
/* i结点表是被partition_format函数连续写入扇区的,
* 所以下面可以连续读出来 */
ide_read(part->my_disk, inode_pos.sec_lba, inode_buf, 2);
}
else
{
// 否则,所查找的inode未跨扇区,一个扇区大小的缓冲区足够
inode_buf = (char *)sys_malloc(512);
ide_read(part->my_disk, inode_pos.sec_lba, inode_buf, 1);
}
memcpy(inode_found, inode_buf + inode_pos.off_size, sizeof(struct inode));
/* 因为一会很可能要用到此inode,故将其插入到队首便于提前检索到 */
list_push(&part->open_inodes, &inode_found->inode_tag);
inode_found->i_open_cnts = 1;
sys_free(inode_buf);
return inode_found;
}
inode_close
i ノードを閉じる、つまりメモリから i ノードを削除するために使用されます。i ノードは複数回開くことができるため、現時点で i ノードを開いているプロセス/スレッドがまだ存在するかどうかを確認し、それを実際に削除するかどうかを決定する必要があります。削除された i ノードによって占有されるメモリ領域は、カーネルのヒープ領域です。
変更 ( myos/fs/inode.c )
#include "interrupt.h"
/* 关闭inode或减少inode的打开数 */
void inode_close(struct inode *inode)
{
/* 若没有进程再打开此文件,将此inode去掉并释放空间 */
enum intr_status old_status = intr_disable();
if (--inode->i_open_cnts == 0)
{
list_remove(&inode->inode_tag); // 将I结点从part->open_inodes中去掉
/* inode_open时为实现inode被所有进程共享,
* 已经在sys_malloc为inode分配了内核空间,
* 释放inode时也要确保释放的是内核内存池 */
struct task_struct *cur = running_thread();
uint32_t *cur_pagedir_bak = cur->pgdir;
cur->pgdir = NULL;
sys_free(inode);
cur->pgdir = cur_pagedir_bak;
}
intr_set_status(old_status);
}
inode_init
i ノード番号を渡して i ノードを初期化します。
変更 ( myos/fs/inode.c )
/* 初始化new_inode */
void inode_init(uint32_t inode_no, struct inode *new_inode)
{
new_inode->i_no = inode_no;
new_inode->i_size = 0;
new_inode->i_open_cnts = 0;
new_inode->write_deny = false;
/* 初始化块索引数组i_sector */
uint8_t sec_idx = 0;
while (sec_idx < 13)
{
/* i_sectors[12]为一级间接块地址 */
new_inode->i_sectors[sec_idx] = 0;
sec_idx++;
}
}
関数宣言: 変更 ( myos/fs/fs.h )
void inode_sync(struct partition* part, struct inode* inode, void* io_buf);
struct inode *inode_open(struct partition *part, uint32_t inode_no);
void inode_close(struct inode *inode);
void inode_init(uint32_t inode_no, struct inode *new_inode);
次に、ディレクトリの開閉、ディレクトリ ファイル内の指定されたディレクトリ エントリの検索、ディレクトリ エントリの初期化、親ディレクトリへのディレクトリ エントリの書き込みなど、ディレクトリ関連の一連の関数を実装します。
open_root_dir
受信パーティションに従ってinode_open
ルート ディレクトリ ファイルの i ノードをメモリに呼び出し、struct root_dir
グローバル変数の i ノード ポインタがルート ディレクトリ ファイルの i ノードを指すように使用されます。
( myos/fs/dir.c )
#include "dir.h"
#include "inode.h"
#include "super_block.h"
struct dir root_dir; // 根目录
/* 打开根目录 */
void open_root_dir(struct partition *part)
{
root_dir.inode = inode_open(part, part->sb->root_inode_no);
root_dir.dir_pos = 0;
}
dir_open
受信パーティションと i ノード オフセットに基づいてディレクトリを保存するメモリ領域を申請するために使用され、inode_open
このディレクトリに対応する i ノードを呼び出してメモリにロードし、ディレクトリ内の i ノード ポインタがこの i ノードを指すようにします。これらの関数はopen_root_dir
似ていますが、1 つはグローバル変数を直接変更し、もう 1 つは自分でメモリを適用する必要がある点が異なります。
変更 ( myos/fs/dir.c )
/* 在分区part上打开i结点为inode_no的目录并返回目录指针 */
struct dir *dir_open(struct partition *part, uint32_t inode_no)
{
struct dir *pdir = (struct dir *)sys_malloc(sizeof(struct dir));
pdir->inode = inode_open(part, inode_no);
pdir->dir_pos = 0;
return pdir;
}
したがって、ディレクトリを開くということは、ディレクトリ (struct dir 構造) と i ノードの間の関係を確立することであることがわかります。なぜ?なぜなら、ディレクトリはメモリ内の概念ですが、それ自体はディスク上のファイルだからです。メモリからディスクへの関係を確立するには、struct dir と inode の間の関係を確立する必要があります。したがって、ディレクトリを開くと、当然のことながら、struct dir 構造がメモリ内に存在する必要があります。さらに考えると、ディレクトリを開くということは、(フォルダーを開いてフォルダーの下にあるファイルを見つけるのと同じように) このディレクトリ内で何かを見つけることを意味し、ディスク上のこのディレクトリ ファイルの場所を記録するのは inode だけです。 、ディレクトリ ファイルは、このディレクトリの内容を記録するディレクトリ エントリのコレクションです。したがって、ディレクトリ内にあるものを見つけるには、その i ノードを見つけてメモリに転送し、この 2 つの間の関係を確立する必要があります。
search_dir_entry
ディレクトリ内で指定された名前を持つディレクトリ エントリを検索するために使用されます。基本的な原理は、ディレクトリ構造 struct dir には、独自の i ノードを指すメンバーがあるということです。このメンバーは、ディレクトリがディスク上に保存されている場所 (inode の i_sectors[ ]) を記録します。これに基づいてディレクトリ ファイルを見つけることができ、次に、その中のディレクトリエントリを走査します。つまり、
変更 ( myos/fs/dir.c )
#include "stdio-kernel.h"
#include "string.h"
/* 在part分区内的pdir目录内寻找名为name的文件或目录,
* 找到后返回true并将其目录项存入dir_e,否则返回false */
bool search_dir_entry(struct partition *part, struct dir *pdir, const char *name, struct dir_entry *dir_e)
{
uint32_t block_cnt = 140; // 12个直接块+128个一级间接块=140块
/* 12个直接块大小+128个间接块,共560字节 */
uint32_t *all_blocks = (uint32_t *)sys_malloc(48 + 512);
if (all_blocks == NULL)
{
printk("search_dir_entry: sys_malloc for all_blocks failed");
return false;
}
uint32_t block_idx = 0;
while (block_idx < 12)
{
all_blocks[block_idx] = pdir->inode->i_sectors[block_idx];
block_idx++;
}
block_idx = 0;
if (pdir->inode->i_sectors[12] != 0)
{
// 若含有一级间接块表
ide_read(part->my_disk, pdir->inode->i_sectors[12], all_blocks + 12, 1);
}
/* 至此,all_blocks存储的是该文件或目录的所有扇区地址 */
/* 写目录项的时候已保证目录项不跨扇区,
* 这样读目录项时容易处理, 只申请容纳1个扇区的内存 */
uint8_t *buf = (uint8_t *)sys_malloc(SECTOR_SIZE);
struct dir_entry *p_de = (struct dir_entry *)buf; // p_de为指向目录项的指针,值为buf起始地址
uint32_t dir_entry_size = part->sb->dir_entry_size;
uint32_t dir_entry_cnt = SECTOR_SIZE / dir_entry_size; // 1扇区内可容纳的目录项个数
/* 开始在所有块中查找目录项 */
while (block_idx < block_cnt)
{
/* 块地址为0时表示该块中无数据,继续在其它块中找 */
if (all_blocks[block_idx] == 0)
{
block_idx++;
continue;
}
ide_read(part->my_disk, all_blocks[block_idx], buf, 1);
uint32_t dir_entry_idx = 0;
/* 遍历扇区中所有目录项 */
while (dir_entry_idx < dir_entry_cnt)
{
/* 若找到了,就直接复制整个目录项 */
if (!strcmp(p_de->filename, name))
{
memcpy(dir_e, p_de, dir_entry_size);
sys_free(buf);
sys_free(all_blocks);
return true;
}
dir_entry_idx++;
p_de++;
}
block_idx++;
p_de = (struct dir_entry *)buf; // 此时p_de已经指向扇区内最后一个完整目录项了,需要恢复p_de指向为buf
memset(buf, 0, SECTOR_SIZE); // 将buf清0,下次再用
}
sys_free(buf);
sys_free(all_blocks);
return false;
}
dir_close
inode_close
ディレクトリを閉じるために使用され、その本質は、ディレクトリの i ノードによって占有されているメモリをメモリから解放するために呼び出すことと、ディレクトリによって占有されているメモリを解放すること、つまり、struct dir と i ノードの関係のバインドを解除することです。なお、ルートディレクトリは繰り返し使用するため、解放できません。
変更 ( myos/fs/dir.c )
/* 关闭目录 */
void dir_close(struct dir *dir)
{
/************* 根目录不能关闭 ***************
*1 根目录自打开后就不应该关闭,否则还需要再次open_root_dir();
*2 root_dir所在的内存是低端1M之内,并非在堆中,free会出问题 */
if (dir == &root_dir)
{
/* 不做任何处理直接返回*/
return;
}
inode_close(dir->inode);
sys_free(dir);
}
create_dir_entry
ディレクトリ エントリを初期化するために使用されます。つまり、ディレクトリ エントリが指すファイルに名前と i ノード インデックスを与えます。
変更 ( myos/fs/dir.c )
#include "debug.h"
/* 在内存中初始化目录项p_de */
void create_dir_entry(char *filename, uint32_t inode_no, uint8_t file_type, struct dir_entry *p_de)
{
ASSERT(strlen(filename) <= MAX_FILE_NAME_LEN);
/* 初始化目录项 */
memcpy(p_de->filename, filename, strlen(filename));
p_de->i_no = inode_no;
p_de->f_type = file_type;
}
sync_dir_entry
ディレクトリ エントリを親ディレクトリに書き込むために使用されます。中心的な原則は次のとおりです: 親ディレクトリ構造 struct dir には、独自の i ノードを指すメンバーがあります。このメンバーは、親ディレクトリ ファイルがディスク上に保存されている場所 (inode の i_sectors[]) を記録します。ディレクトリ ファイルは、次の情報に基づいて見つけることができます。ファイル内の空のスペースを見つけて、その空のスペースにディレクトリ エントリを挿入します。問題は、inode の i_sectors[] 要素が空かどうかを判断する必要があることです。空の場合は、ディレクトリ ファイルをホストするブロックも申請する必要があります。
変更 ( myos/fs/dir.c )
#include "file.h"
/* 将目录项p_de写入父目录parent_dir中,io_buf由主调函数提供 */
bool sync_dir_entry(struct dir *parent_dir, struct dir_entry *p_de, void *io_buf)
{
struct inode *dir_inode = parent_dir->inode;
uint32_t dir_size = dir_inode->i_size;
uint32_t dir_entry_size = cur_part->sb->dir_entry_size;
ASSERT(dir_size % dir_entry_size == 0); // dir_size应该是dir_entry_size的整数倍
uint32_t dir_entrys_per_sec = (512 / dir_entry_size); // 每扇区最大的目录项数目
int32_t block_lba = -1;
/* 将该目录的所有扇区地址(12个直接块+ 128个间接块)存入all_blocks */
uint8_t block_idx = 0;
uint32_t all_blocks[140] = {
0}; // all_blocks保存目录所有的块
/* 将12个直接块存入all_blocks */
while (block_idx < 12)
{
all_blocks[block_idx] = dir_inode->i_sectors[block_idx];
block_idx++;
}
struct dir_entry *dir_e = (struct dir_entry *)io_buf; // dir_e用来在io_buf中遍历目录项
int32_t block_bitmap_idx = -1;
/* 开始遍历所有块以寻找目录项空位,若已有扇区中没有空闲位,
* 在不超过文件大小的情况下申请新扇区来存储新目录项 */
block_idx = 0;
while (block_idx < 140)
{
// 文件(包括目录)最大支持12个直接块+128个间接块=140个块
block_bitmap_idx = -1;
if (all_blocks[block_idx] == 0)
{
// 在三种情况下分配块
block_lba = block_bitmap_alloc(cur_part);
if (block_lba == -1)
{
printk("alloc block bitmap for sync_dir_entry failed\n");
return false;
}
/* 每分配一个块就同步一次block_bitmap */
block_bitmap_idx = block_lba - cur_part->sb->data_start_lba;
ASSERT(block_bitmap_idx != -1);
bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);
block_bitmap_idx = -1;
if (block_idx < 12)
{
// 若是直接块
dir_inode->i_sectors[block_idx] = all_blocks[block_idx] = block_lba;
}
else if (block_idx == 12)
{
// 若是尚未分配一级间接块表(block_idx等于12表示第0个间接块地址为0)
dir_inode->i_sectors[12] = block_lba; // 将上面分配的块做为一级间接块表地址
block_lba = -1;
block_lba = block_bitmap_alloc(cur_part); // 再分配一个块做为第0个间接块
if (block_lba == -1)
{
block_bitmap_idx = dir_inode->i_sectors[12] - cur_part->sb->data_start_lba;
bitmap_set(&cur_part->block_bitmap, block_bitmap_idx, 0);
dir_inode->i_sectors[12] = 0;
printk("alloc block bitmap for sync_dir_entry failed\n");
return false;
}
/* 每分配一个块就同步一次block_bitmap */
block_bitmap_idx = block_lba - cur_part->sb->data_start_lba;
ASSERT(block_bitmap_idx != -1);
bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);
all_blocks[12] = block_lba;
/* 把新分配的第0个间接块地址写入一级间接块表 */
ide_write(cur_part->my_disk, dir_inode->i_sectors[12], all_blocks + 12, 1);
}
else
{
// 若是间接块未分配
all_blocks[block_idx] = block_lba;
/* 把新分配的第(block_idx-12)个间接块地址写入一级间接块表 */
ide_write(cur_part->my_disk, dir_inode->i_sectors[12], all_blocks + 12, 1);
}
/* 再将新目录项p_de写入新分配的间接块 */
memset(io_buf, 0, 512);
memcpy(io_buf, p_de, dir_entry_size);
ide_write(cur_part->my_disk, all_blocks[block_idx], io_buf, 1);
dir_inode->i_size += dir_entry_size;
return true;
}
/* 若第block_idx块已存在,将其读进内存,然后在该块中查找空目录项 */
ide_read(cur_part->my_disk, all_blocks[block_idx], io_buf, 1);
/* 在扇区内查找空目录项 */
uint8_t dir_entry_idx = 0;
while (dir_entry_idx < dir_entrys_per_sec)
{
if ((dir_e + dir_entry_idx)->f_type == FT_UNKNOWN)
{
// FT_UNKNOWN为0,无论是初始化或是删除文件后,都会将f_type置为FT_UNKNOWN.
memcpy(dir_e + dir_entry_idx, p_de, dir_entry_size);
ide_write(cur_part->my_disk, all_blocks[block_idx], io_buf, 1);
dir_inode->i_size += dir_entry_size;
return true;
}
dir_entry_idx++;
}
block_idx++;
}
printk("directory is full!\n");
return false;
}
サポートコード:
変更 ( myos/fs/fs.h )
extern struct partition* cur_part;
この 2 行のコードの作成者は、
( myos/fs/fs.c/sync_dir_entry )
/* 将12个直接块存入all_blocks */
while (block_idx < 12) {
all_blocks[block_idx] = dir_inode->i_sectors[block_idx];
block_idx++;
}
最初の 12 個の直接ブロックのみがディレクトリ ファイルから読み取られるため、all_blocks 配列の最初の 12 個の項目のみがゼロ以外の値になります。ディレクトリ ファイル全体、最初の 12 の直接ブロックが指すブロックがディレクトリ エントリでいっぱいであると仮定すると、新しいディレクトリ エントリは、ディレクトリ ファイルが指すブロックのアドレスの 1 つが指すディレクトリ ファイルにのみ格納できます。第 1 レベルの間接ブロック (i_sectors[12]) 中央。ただし、コード ロジックは必然的に all_blocks[12] == 0 と判断し、block_idx == 12 を入力します。ブロックが第 1 レベルの間接ブロックとして機能するように再適用され、元の第 1 レベルの間接ブロックが上書きされます。ブロック。これにより、以前に間接ブロックに格納されていたすべてのディレクトリ エントリへの参照が失われ、データの整合性に関する重大な問題が発生します。
次のように変更します。
/* 将12个直接块存入all_blocks */
while (block_idx < 12) {
all_blocks[block_idx] = dir_inode->i_sectors[block_idx];
block_idx++;
}
if (dir_inode->i_sectors[12] != 0) {
// 若含有一级间接块表
ide_read(cur_part->my_disk, dir_inode->i_sectors[12], all_blocks + 12, 1);
}
関数宣言:
変更 ( myos/fs/dir.h )
void open_root_dir(struct partition *part);
struct dir *dir_open(struct partition *part, uint32_t inode_no);
bool search_dir_entry(struct partition *part, struct dir *pdir, const char *name, struct dir_entry *dir_e);
void dir_close(struct dir *dir);
void create_dir_entry(char *filename, uint32_t inode_no, uint8_t file_type, struct dir_entry *p_de);
bool sync_dir_entry(struct dir *parent_dir, struct dir_entry *p_de, void *io_buf);
次に、パス解析関数を実装する必要があります。たとえば、ファイルを見つけたい場合は、パスを指定する/home/kanshan/Desktop/test.c
と、システムがそのファイルを見つけます。パス解析の中心原理については、この章の冒頭で説明しました。これは、/home/test.c
ファイル システムがどのように機能するかを示す例です。
path_parse
各呼び出しは最上位のパス名を解析し、name_store
解析されたパス名をバッファに保存します。ルート ディレクトリはすべてのパスの最上位であるため、解析に参加する必要はありません (たとえば、ディレクトリ階層/home/test.c
は/
、home
, test.c
, しかし、/
これはすべてのディレクトリの最上位にあります。 , これは無視できるため、/home/test.c
ディレクトリ階層は , と考えることができますhome
。test.c
) たとえば、この関数を解析すると/home/kanshan/Desktop/test.c
、最初の呼び出しが返され/kanshan/Desktop/test.c
、name_store
保存されますhome
。もう一度コールリターンして/Desktop/test.c
、...name_store
に保存してください。kanshan
変更 ( myos/fs/fs.c )
/* 将最上层路径名称解析出来 */
static char *path_parse(char *pathname, char *name_store)
{
if (pathname[0] == '/')
{
// 根目录不需要单独解析
/* 路径中出现1个或多个连续的字符'/',将这些'/'跳过,如"///a/b" */
while (*(++pathname) == '/')
;
}
/* 开始一般的路径解析 */
while (*pathname != '/' && *pathname != 0)
{
*name_store++ = *pathname++;
}
if (pathname[0] == 0)
{
// 若路径字符串为空则返回NULL
return NULL;
}
return pathname;
}
path_depth_cnt
パスの深さを返すために使用されます。たとえば、/home/kanshan/Desktop/test.c
パスの深さは 4 です。原則は、ループ内で呼び出しpath_parse
、戻り値が空かどうかを確認し、呼び出しを続行するかどうかを決定することです。path_parse
変更 ( myos/fs/fs.c )
/* 返回路径深度,比如/a/b/c,深度为3 */
int32_t path_depth_cnt(char *pathname)
{
ASSERT(pathname != NULL);
char *p = pathname;
char name[MAX_FILE_NAME_LEN]; // 用于path_parse的参数做路径解析
uint32_t depth = 0;
/* 解析路径,从中拆分出各级名称 */
p = path_parse(p, name);
while (name[0])
{
depth++;
memset(name, 0, MAX_FILE_NAME_LEN);
if (p)
{
// 如果p不等于NULL,继续分析路径
p = path_parse(p, name);
}
}
return depth;
}
関数宣言
変更 ( myos/fs/fs.h )
int32_t path_depth_cnt(char *pathname);
次にファイル取得機能を実装します。
search_file
ファイル パスを提供し、inode インデックスを返すために使用されます。中心となる原理は、継続的にループすることです: 1.path_parse
アドレス解決を実行してルート ディレクトリを除く最上位ディレクトリの名前を取得する呼び出しを実行します; 2. 次に、検索ディレクトリsearch_dir_entry
から 1 を見つけるために呼び出します (この関数が最初に呼び出されたとき、検索はディレクトリは当然ルート ディレクトリです)名前; 3、次に次の検索ディレクトリ (つまり、2 で見つかったディレクトリ エントリ) を更新します; 解析を続行し、検索を続行し、検索ディレクトリの更新を続行します...
( myos/fs/fs.h ) を変更して、検索パスを記録するための構造を追加します。
#define MAX_PATH_LEN 512 // 路径最大长度
/* 用来记录查找文件过程中已找到的上级路径,也就是查找文件过程中"走过的地方" */
struct path_search_record
{
char searched_path[MAX_PATH_LEN]; // 查找过程中的父路径
struct dir *parent_dir; // 文件或目录所在的直接父目录
enum file_types file_type; // 找到的是普通文件还是目录,找不到将为未知类型(FT_UNKNOWN)
};
変更 ( myos/fs/fs.c )
/* 搜索文件pathname,若找到则返回其inode号,否则返回-1 */
static int search_file(const char *pathname, struct path_search_record *searched_record)
{
/* 如果待查找的是根目录,为避免下面无用的查找,直接返回已知根目录信息 */
if (!strcmp(pathname, "/") || !strcmp(pathname, "/.") || !strcmp(pathname, "/.."))
{
searched_record->parent_dir = &root_dir;
searched_record->file_type = FT_DIRECTORY;
searched_record->searched_path[0] = 0; // 搜索路径置空
return 0;
}
uint32_t path_len = strlen(pathname);
/* 保证pathname至少是这样的路径/x且小于最大长度 */
ASSERT(pathname[0] == '/' && path_len > 1 && path_len < MAX_PATH_LEN);
char *sub_path = (char *)pathname;
struct dir *parent_dir = &root_dir;
struct dir_entry dir_e;
/* 记录路径解析出来的各级名称,如路径"/a/b/c",
* 数组name每次的值分别是"a","b","c" */
char name[MAX_FILE_NAME_LEN] = {
0};
searched_record->parent_dir = parent_dir;
searched_record->file_type = FT_UNKNOWN;
uint32_t parent_inode_no = 0; // 父目录的inode号
sub_path = path_parse(sub_path, name);
while (name[0])
{
// 若第一个字符就是结束符,结束循环
/* 记录查找过的路径,但不能超过searched_path的长度512字节 */
ASSERT(strlen(searched_record->searched_path) < 512);
/* 记录已存在的父目录 */
strcat(searched_record->searched_path, "/");
strcat(searched_record->searched_path, name);
/* 在所给的目录中查找文件 */
if (search_dir_entry(cur_part, parent_dir, name, &dir_e))
{
memset(name, 0, MAX_FILE_NAME_LEN);
/* 若sub_path不等于NULL,也就是未结束时继续拆分路径 */
if (sub_path)
{
sub_path = path_parse(sub_path, name);
}
if (FT_DIRECTORY == dir_e.f_type)
{
// 如果被打开的是目录
parent_inode_no = parent_dir->inode->i_no;
dir_close(parent_dir);
parent_dir = dir_open(cur_part, dir_e.i_no); // 更新父目录
searched_record->parent_dir = parent_dir;
continue;
}
else if (FT_REGULAR == dir_e.f_type)
{
// 若是普通文件
searched_record->file_type = FT_REGULAR;
return dir_e.i_no;
}
}
else
{
// 若找不到,则返回-1
/* 找不到目录项时,要留着parent_dir不要关闭,
* 若是创建新文件的话需要在parent_dir中创建 */
return -1;
}
}
/* 执行到此,必然是遍历了完整路径并且查找的文件或目录只有同名目录存在 */
dir_close(searched_record->parent_dir);
/* 保存被查找目录的直接父目录 */
searched_record->parent_dir = dir_open(cur_part, parent_inode_no);
searched_record->file_type = FT_DIRECTORY;
return dir_e.i_no;
}
サポートコード:
変更 ( myos/fs/dir.h )
extern struct dir root_dir;
次にファイル作成機能を実装します
file_create
ファイルの作成に使用され、成功するとファイル記述子が返されます。主要な手順: 1. 新しいファイルの i ノードを作成します (これには i ノード ビットマップの変更と i ノードの初期化が含まれます); 2. ファイル構造の作成 (ファイル構造はファイル上のプロセスまたはスレッドの動作ステータスを記述しており、作成は当然のことながら次のものに属するため)この稼働状況。これには、グローバルなオープン ファイル構造配列の変更、3. 対応するディレクトリ エントリの作成、4. 新しいファイルのディレクトリ エントリの親ディレクトリへの同期、親ディレクトリの i ノードと親ノードの i ノードの同期が含まれます。これには、inode->i_size が関係します。 、inode->i_sectors [ ] (ファイルのディレクトリ エントリは親ディレクトリ ファイルに存在するため、新しいブロック リクエストが含まれる可能性があります)。i ノードを同期すると、ディスク内の i ノード配列も同期されます。ファイルの i ノードを作成します。inode ビットマップ。5. 新しいファイルの i ノードをオープンリンクリストに追加します。6. プロセスの PCB にファイル記述子をインストールします。ファイルを作成するスレッド。
ファイル作成の核心は、inode、ディレクトリ、およびファイル構造の間の関係を扱うことであることがわかります。
変更 ( myos/fs/file.c )
#include "string.h"
/* 创建文件,若成功则返回文件描述符,否则返回-1 */
int32_t file_create(struct dir *parent_dir, char *filename, uint8_t flag)
{
/* 后续操作的公共缓冲区 */
void *io_buf = sys_malloc(1024);
if (io_buf == NULL)
{
printk("in file_creat: sys_malloc for io_buf failed\n");
return -1;
}
uint8_t rollback_step = 0; // 用于操作失败时回滚各资源状态
/* 为新文件分配inode */
int32_t inode_no = inode_bitmap_alloc(cur_part);
if (inode_no == -1)
{
printk("in file_creat: allocate inode failed\n");
return -1;
}
/* 此inode要从堆中申请内存,不可生成局部变量(函数退出时会释放)
* 因为file_table数组中的文件描述符的inode指针要指向它.*/
struct inode *new_file_inode = (struct inode *)sys_malloc(sizeof(struct inode));
if (new_file_inode == NULL)
{
printk("file_create: sys_malloc for inode failded\n");
rollback_step = 1;
goto rollback;
}
inode_init(inode_no, new_file_inode); // 初始化i结点
/* 返回的是file_table数组的下标 */
int fd_idx = get_free_slot_in_global();
if (fd_idx == -1)
{
printk("exceed max open files\n");
rollback_step = 2;
goto rollback;
}
file_table[fd_idx].fd_inode = new_file_inode;
file_table[fd_idx].fd_pos = 0;
file_table[fd_idx].fd_flag = flag;
file_table[fd_idx].fd_inode->write_deny = false;
struct dir_entry new_dir_entry;
memset(&new_dir_entry, 0, sizeof(struct dir_entry));
create_dir_entry(filename, inode_no, FT_REGULAR, &new_dir_entry); // create_dir_entry只是内存操作不出意外,不会返回失败
/* 同步内存数据到硬盘 */
/* a 在目录parent_dir下安装目录项new_dir_entry, 写入硬盘后返回true,否则false */
if (!sync_dir_entry(parent_dir, &new_dir_entry, io_buf))
{
printk("sync dir_entry to disk failed\n");
rollback_step = 3;
goto rollback;
}
memset(io_buf, 0, 1024);
/* b 将父目录i结点的内容同步到硬盘 */
inode_sync(cur_part, parent_dir->inode, io_buf);
memset(io_buf, 0, 1024);
/* c 将新创建文件的i结点内容同步到硬盘 */
inode_sync(cur_part, new_file_inode, io_buf);
/* d 将inode_bitmap位图同步到硬盘 */
bitmap_sync(cur_part, inode_no, INODE_BITMAP);
/* e 将创建的文件i结点添加到open_inodes链表 */
list_push(&cur_part->open_inodes, &new_file_inode->inode_tag);
new_file_inode->i_open_cnts = 1;
sys_free(io_buf);
return pcb_fd_install(fd_idx);
/*创建文件需要创建相关的多个资源,若某步失败则会执行到下面的回滚步骤 */
rollback:
switch (rollback_step)
{
case 3:
/* 失败时,将file_table中的相应位清空 */
memset(&file_table[fd_idx], 0, sizeof(struct file));
case 2:
sys_free(new_file_inode);
case 1:
/* 如果新文件的i结点创建失败,之前位图中分配的inode_no也要恢复 */
bitmap_set(&cur_part->inode_bitmap, inode_no, 0);
break;
}
sys_free(io_buf);
return -1;
}
コードブロック
/* 为新文件分配inode */
int32_t inode_no = inode_bitmap_alloc(cur_part);
if (inode_no == -1) {
printk("in file_creat: allocate inode failed\n");
return -1;
}
に変更されました
/* 为新文件分配inode */
int32_t inode_no = inode_bitmap_alloc(cur_part);
if (inode_no == -1) {
printk("in file_creat: allocate inode failed\n");
goto rollback;
}
関数宣言:
変更 ( myos/fs/file.h )
#include "dir.h"
int32_t file_create(struct dir *parent_dir, char *filename, uint8_t flag);
sys_open
渡されたパスに基づいてファイルを開くか作成し、ファイル記述子を返すために使用されます。現在は最初のバージョンを作成しているため、作成関数のみを実装します。基本的な原則は、最初に呼び出してsearch_file
指定されたパスにファイルが存在するかどうかを確認し、存在しない場合は直接呼び出してfile_create
ファイルを作成するということです。存在の検索から最終的な作成まで、次の状況を除外する必要があります: 1. 見つかったが、それはディレクトリだった; 2. 検索プロセス中に中間パスが存在しなかった。呼び出した後、このディレクトリが存在しないことが/home/kanshan/test.c
わかりました; 3. 最後のパスでは見つかりませんでしたが、ファイルの作成を示すフラグが渡されていませんでした; 4. 作成されるファイルはすでに存在しています。search_file
/kanshan
変更 ( myos/fs/fs.h )
/* 打开文件的选项 */
enum oflags
{
O_RDONLY, // 只读
O_WRONLY, // 只写
O_RDWR, // 读写
O_CREAT = 4 // 创建
};
変更 ( myos/fs/fs.c )
#include "file.h"
/* 打开或创建文件成功后,返回文件描述符,否则返回-1 */
int32_t sys_open(const char *pathname, uint8_t flags)
{
/* 对目录要用dir_open,这里只有open文件 */
if (pathname[strlen(pathname) - 1] == '/')
{
printk("can`t open a directory %s\n", pathname);
return -1;
}
ASSERT(flags <= 7);
int32_t fd = -1; // 默认为找不到
struct path_search_record searched_record;
memset(&searched_record, 0, sizeof(struct path_search_record));
/* 记录目录深度.帮助判断中间某个目录不存在的情况 */
uint32_t pathname_depth = path_depth_cnt((char *)pathname);
/* 先检查文件是否存在 */
int inode_no = search_file(pathname, &searched_record);
bool found = inode_no != -1 ? true : false;
if (searched_record.file_type == FT_DIRECTORY)
{
printk("can`t open a direcotry with open(), use opendir() to instead\n");
dir_close(searched_record.parent_dir);
return -1;
}
uint32_t path_searched_depth = path_depth_cnt(searched_record.searched_path);
/* 先判断是否把pathname的各层目录都访问到了,即是否在某个中间目录就失败了 */
if (pathname_depth != path_searched_depth)
{
// 说明并没有访问到全部的路径,某个中间目录是不存在的
printk("cannot access %s: Not a directory, subpath %s is`t exist\n",
pathname, searched_record.searched_path);
dir_close(searched_record.parent_dir);
return -1;
}
/* 若是在最后一个路径上没找到,并且并不是要创建文件,直接返回-1 */
if (!found && !(flags & O_CREAT))
{
printk("in path %s, file %s is`t exist\n",
searched_record.searched_path,
(strrchr(searched_record.searched_path, '/') + 1));
dir_close(searched_record.parent_dir);
return -1;
}
else if (found && flags & O_CREAT)
{
// 若要创建的文件已存在
printk("%s has already exist!\n", pathname);
dir_close(searched_record.parent_dir);
return -1;
}
switch (flags & O_CREAT)
{
case O_CREAT:
printk("creating file\n");
fd = file_create(searched_record.parent_dir, (strrchr(pathname, '/') + 1), flags);
dir_close(searched_record.parent_dir);
/* 此fd是指任务pcb->fd_table数组中的元素下标,
* 并不是指全局file_table中的下标 */
}
return fd;
}
関数宣言:
変更 ( myos/fs/fs.h )
int32_t sys_open(const char *pathname, uint8_t flags);
filesys_init
、list_traversal
パーティションをマウントするためにパーティションをポーリングするときに呼び出されますmount_partition
。次に、 を呼び出してopen_root_dir
現在開いているパーティションのルート ディレクトリに参加し、グローバルなオープン ファイル構造配列を初期化します。
変更 ( myos/fs/fs.c/filesys_init )
sys_free(sb_buf);
/* 确定默认操作的分区 */
char default_part[8] = "sdb1";
/* 挂载分区 */
list_traversal(&partition_list, mount_partition, (int)default_part);
}
のために
sys_free(sb_buf);
/* 确定默认操作的分区 */
char default_part[8] = "sdb1";
/* 挂载分区 */
list_traversal(&partition_list, mount_partition, (int)default_part);
/* 将当前分区的根目录打开 */
open_root_dir(cur_part);
/* 初始化文件表 */
uint32_t fd_idx = 0;
while (fd_idx < MAX_FILE_OPEN)
{
file_table[fd_idx++].fd_inode = NULL;
}
}
サポートコード、修正 ( myos/fs/file.h )
extern struct file file_table[MAX_FILE_OPEN];
コードのテスト、変更 ( myos/kernel/main.c )
#include "print.h"
#include "init.h"
#include "fs.h"
int main(void) {
put_str("I am kernel\n");
init_all();
sys_open("/file1", O_CREAT);
while(1);
return 0;
}
デバッグを容易にするために、( myos/fs/fs.c/mount_partition ) を変更します。デバッグ後、追加されたコードを削除する必要があります。
printk("mount %s done!\n", part->name);
/* 此处返回true是为了迎合主调函数list_traversal的实现,与函数本身功能无关。
只有返回true时list_traversal才会停止遍历,减少了后面元素无意义的遍历.*/
sys_free(sb_buf);
のために
printk("mount %s done!\n", part->name);
printk("sb1 data_start_lba: %x\n", cur_part->sb->data_start_lba);
/* 此处返回true是为了迎合主调函数list_traversal的实现,与函数本身功能无关。
只有返回true时list_traversal才会停止遍历,减少了后面元素无意义的遍历.*/
sys_free(sb_buf);
仮想マシンが起動すると、sdb 1 のデータ領域の開始セクターが表示されます (私は A6B です。私をフォローしている場合は、これであるはずです)。ルート ディレクトリ ファイルの最初のブロックは次のとおりです。データ領域の最初のセクターに配置されるため、このデータを取得した後、それを *512 に追加し、次のコマンドを入力して hd80M.img データ領域の最初の 512 バイトのデータを表示します。書籍内のデータ 効果
xxd -s 1365504 -l 512 hd80M.img
xxd
これは、主にファイルの内容を 16 進形式で表示するために使用されるコマンド ライン ツールで、パラメータは-s
表示するデータ バイト オフセットを指定し、-l
パラメータは表示バイト数を指定します。
セクション d:
ファイルの開閉
file_open
: ファイルを開くために使用されます。本質は、ファイル構造がメモリ内の i ノードを指すようにすることです。原則: 開く必要があるファイルの i ノード配列インデックスを渡し、get_free_slot_in_global
グローバルに開いているファイル構造配列内の空の位置を見つけるために呼び出し、inode_open
その i ノードをメモリに転送するために呼び出し、その後、ファイル構造がこの i ノードを指すようにします。最後に呼び出してpcb_fd_install
ファイル記述子をインストールします。同期のため、書き込み用にオープンしたファイルは書き込み中のファイルではなく、ファイルの対応する i ノード内の書き込みフラグを確認する必要があります。
変更 ( myos/fs/file.c )
#include "interrupt.h"
/* 打开编号为inode_no的inode对应的文件,若成功则返回文件描述符,否则返回-1 */
int32_t file_open(uint32_t inode_no, uint8_t flag)
{
int fd_idx = get_free_slot_in_global();
if (fd_idx == -1)
{
printk("exceed max open files\n");
return -1;
}
file_table[fd_idx].fd_inode = inode_open(cur_part, inode_no);
file_table[fd_idx].fd_pos = 0; // 每次打开文件,要将fd_pos还原为0,即让文件内的指针指向开头
file_table[fd_idx].fd_flag = flag;
bool *write_deny = &file_table[fd_idx].fd_inode->write_deny;
if (flag & O_WRONLY || flag & O_RDWR)
{
// 只要是关于写文件,判断是否有其它进程正写此文件
// 若是读文件,不考虑write_deny
/* 以下进入临界区前先关中断 */
enum intr_status old_status = intr_disable();
if (!(*write_deny))
{
// 若当前没有其它进程写该文件,将其占用.
*write_deny = true; // 置为true,避免多个进程同时写此文件
intr_set_status(old_status); // 恢复中断
}
else
{
// 直接失败返回
intr_set_status(old_status);
printk("file can`t be write now, try again later\n");
return -1;
}
} // 若是读文件或创建文件,不用理会write_deny,保持默认
return pcb_fd_install(fd_idx);
}
ファイルは書き込み関連の方法で開かれますが、他のスレッドまたはプロセスがファイルに書き込んでいるとき、作成者のコードはファイル構造を解放しません。それで、修正してください
else
{
// 直接失败返回
intr_set_status(old_status);
printk("file can`t be write now, try again later\n");
return -1;
}
のために
else
{
// 直接失败返回
intr_set_status(old_status);
file_table[fd_idx].fd_inode = NULL;
printk("file can`t be write now, try again later\n");
return -1;
}
sys_open
以前はファイルを作成する機能のみを実装していましたが、今度はfile_open
それをカプセル化して、sys_open
ファイルを開く機能を持たせます。
変更 ( myos/fs/fs.c/sys_open )
switch (flags & O_CREAT)
{
case O_CREAT:
printk("creating file\n");
fd = file_create(searched_record.parent_dir, (strrchr(pathname, '/') + 1), flags);
dir_close(searched_record.parent_dir);
/* 此fd是指任务pcb->fd_table数组中的元素下标,
* 并不是指全局file_table中的下标 */
}
return fd;
のために
switch (flags & O_CREAT)
{
case O_CREAT:
printk("creating file\n");
fd = file_create(searched_record.parent_dir, (strrchr(pathname, '/') + 1), flags);
dir_close(searched_record.parent_dir);
break;
default:
/* 其余情况均为打开已存在文件:
* O_RDONLY,O_WRONLY,O_RDWR */
fd = file_open(inode_no, flags);
}
/* 此fd是指任务pcb->fd_table数组中的元素下标,
* 并不是指全局file_table中的下标 */
return fd;
file_close
渡されたファイル構造ポインタを使用してファイルを閉じます。手順: ファイル書き込みフラグをオフにし、呼び出してinode_close
メモリ内の i ノードを削除し、ファイル構造と i ノードの関連付けを解除します。
変更 ( myos/fs/file.c )
/* 关闭文件 */
int32_t file_close(struct file *file)
{
if (file == NULL)
{
return -1;
}
file->fd_inode->write_deny = false;
inode_close(file->fd_inode);
file->fd_inode = NULL; // 使文件结构可用
return 0;
}
ここで、ファイルの開閉がファイル構造と i ノードの関係を扱っていることがわかります。
関数宣言、変更( myos/fs/file.h )
int32_t file_open(uint32_t inode_no, uint8_t flag);
int32_t file_close(struct file *file);
fd_local2global
, ファイル記述子をグローバルなオープン ファイル構造の配列添字に変換するために使用されます。原理は、現在実行中のプロセスまたはスレッドの PCB 内の fd_table 配列内のファイル記述子に対応する要素を見つけることです。ここに保存されるのはグローバルですファイルを開く構造体配列の添字。
変更 ( myos/fs/fs.c )
/* 将文件描述符转化为文件表的下标 */
static uint32_t fd_local2global(uint32_t local_fd)
{
struct task_struct *cur = running_thread();
int32_t global_fd = cur->fd_table[local_fd];
ASSERT(global_fd >= 0 && global_fd < MAX_FILE_OPEN);
return (uint32_t)global_fd;
}
sys_close
受信したファイル記述子を使用してファイルを閉じます。原則: を呼び出してfd_close2global
ファイル記述子をグローバルなオープン ファイル構造配列添字に変換し、次に を呼び出してfile_close
このファイル構造に対応するファイルを閉じ、最後に PCB 内のファイル記述子ビットをクリアします。
変更 ( myos/fs/fs.c )
/* 关闭文件描述符fd指向的文件,成功返回0,否则返回-1 */
int32_t sys_close(int32_t fd)
{
int32_t ret = -1; // 返回值默认为-1,即失败
if (fd > 2)
{
uint32_t _fd = fd_local2global(fd);
ret = file_close(&file_table[_fd]);
running_thread()->fd_table[fd] = -1; // 使该文件描述符位可用
}
return ret;
}
関数宣言、変更( myos/fs/fs.h )
int32_t sys_close(int32_t fd);
コードのテスト、変更 ( myos/kernel/main.c )
#include "print.h"
#include "init.h"
#include "fs.h"
#include "stdio.h"
int main(void) {
put_str("I am kernel\n");
init_all();
uint32_t fd = sys_open("/file1", O_RDONLY);
printf("fd:%d\n", fd);
sys_close(fd);
printf("%d closed now\n", fd);
while(1);
return 0;
}
セクション e:
file_write
、buf 内の count バイトをファイルに書き込むために使用されます。基本原理: 関数に渡されるファイル構造ポインタは、struct file
動作ファイルの i ノードへのポインタを持ち、この i ノードの i_size と i_sectors[] を通じて、ファイル サイズと保存場所の情報を正常に知ることができます。まず、ファイル内のデータの最後のブロックが読み取られ、バッファーに書き込まれるデータと結合されて完全なブロックが形成され、ディスクに書き込まれます。残りのデータはブロック単位でディスクに書き込むことができます。
変更 ( myos/fs/file.c )
/* 把buf中的count个字节写入file,成功则返回写入的字节数,失败则返回-1 */
int32_t file_write(struct file *file, const void *buf, uint32_t count)
{
if ((file->fd_inode->i_size + count) > (BLOCK_SIZE * 140))
{
// 文件目前最大只支持512*140=71680字节
printk("exceed max file_size 71680 bytes, write file failed\n");
return -1;
}
uint8_t *io_buf = sys_malloc(BLOCK_SIZE);
if (io_buf == NULL)
{
printk("file_write: sys_malloc for io_buf failed\n");
return -1;
}
uint32_t *all_blocks = (uint32_t *)sys_malloc(BLOCK_SIZE + 48); // 用来记录文件所有的块地址
if (all_blocks == NULL)
{
printk("file_write: sys_malloc for all_blocks failed\n");
return -1;
}
const uint8_t *src = buf; // 用src指向buf中待写入的数据
uint32_t bytes_written = 0; // 用来记录已写入数据大小
uint32_t size_left = count; // 用来记录未写入数据大小
int32_t block_lba = -1; // 块地址
uint32_t block_bitmap_idx = 0; // 用来记录block对应于block_bitmap中的索引,做为参数传给bitmap_sync
uint32_t sec_idx; // 用来索引扇区
uint32_t sec_lba; // 扇区地址
uint32_t sec_off_bytes; // 扇区内字节偏移量
uint32_t sec_left_bytes; // 扇区内剩余字节量
uint32_t chunk_size; // 每次写入硬盘的数据块大小
int32_t indirect_block_table; // 用来获取一级间接表地址
uint32_t block_idx; // 块索引
/* 判断文件是否是第一次写,如果是,先为其分配一个块 */
if (file->fd_inode->i_sectors[0] == 0)
{
block_lba = block_bitmap_alloc(cur_part);
if (block_lba == -1)
{
printk("file_write: block_bitmap_alloc failed\n");
return -1;
}
file->fd_inode->i_sectors[0] = block_lba;
/* 每分配一个块就将位图同步到硬盘 */
block_bitmap_idx = block_lba - cur_part->sb->data_start_lba;
ASSERT(block_bitmap_idx != 0);
bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);
}
/* 写入count个字节前,该文件已经占用的块数 */
uint32_t file_has_used_blocks = file->fd_inode->i_size / BLOCK_SIZE + 1;
/* 存储count字节后该文件将占用的块数 */
uint32_t file_will_use_blocks = (file->fd_inode->i_size + count) / BLOCK_SIZE + 1;
ASSERT(file_will_use_blocks <= 140);
/* 通过此增量判断是否需要分配扇区,如增量为0,表示原扇区够用 */
uint32_t add_blocks = file_will_use_blocks - file_has_used_blocks;
/* 开始将文件所有块地址收集到all_blocks,(系统中块大小等于扇区大小)
* 后面都统一在all_blocks中获取写入扇区地址 */
if (add_blocks == 0)
{
/* 在同一扇区内写入数据,不涉及到分配新扇区 */
if (file_has_used_blocks <= 12)
{
// 文件数据量将在12块之内
block_idx = file_has_used_blocks - 1; // 指向最后一个已有数据的扇区
all_blocks[block_idx] = file->fd_inode->i_sectors[block_idx];
}
else
{
/* 未写入新数据之前已经占用了间接块,需要将间接块地址读进来 */
ASSERT(file->fd_inode->i_sectors[12] != 0);
indirect_block_table = file->fd_inode->i_sectors[12];
ide_read(cur_part->my_disk, indirect_block_table, all_blocks + 12, 1);
}
}
else
{
/* 若有增量,便涉及到分配新扇区及是否分配一级间接块表,下面要分三种情况处理 */
/* 第一种情况:12个直接块够用*/
if (file_will_use_blocks <= 12)
{
/* 先将有剩余空间的可继续用的扇区地址写入all_blocks */
block_idx = file_has_used_blocks - 1;
ASSERT(file->fd_inode->i_sectors[block_idx] != 0);
all_blocks[block_idx] = file->fd_inode->i_sectors[block_idx];
/* 再将未来要用的扇区分配好后写入all_blocks */
block_idx = file_has_used_blocks; // 指向第一个要分配的新扇区
while (block_idx < file_will_use_blocks)
{
block_lba = block_bitmap_alloc(cur_part);
if (block_lba == -1)
{
printk("file_write: block_bitmap_alloc for situation 1 failed\n");
return -1;
}
/* 写文件时,不应该存在块未使用但已经分配扇区的情况,当文件删除时,就会把块地址清0 */
ASSERT(file->fd_inode->i_sectors[block_idx] == 0); // 确保尚未分配扇区地址
file->fd_inode->i_sectors[block_idx] = all_blocks[block_idx] = block_lba;
/* 每分配一个块就将位图同步到硬盘 */
block_bitmap_idx = block_lba - cur_part->sb->data_start_lba;
bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);
block_idx++; // 下一个分配的新扇区
}
}
else if (file_has_used_blocks <= 12 && file_will_use_blocks > 12)
{
/* 第二种情况: 旧数据在12个直接块内,新数据将使用间接块*/
/* 先将有剩余空间的可继续用的扇区地址收集到all_blocks */
block_idx = file_has_used_blocks - 1; // 指向旧数据所在的最后一个扇区
all_blocks[block_idx] = file->fd_inode->i_sectors[block_idx];
/* 创建一级间接块表 */
block_lba = block_bitmap_alloc(cur_part);
if (block_lba == -1)
{
printk("file_write: block_bitmap_alloc for situation 2 failed\n");
return -1;
}
ASSERT(file->fd_inode->i_sectors[12] == 0); // 确保一级间接块表未分配
/* 分配一级间接块索引表 */
indirect_block_table = file->fd_inode->i_sectors[12] = block_lba;
block_idx = file_has_used_blocks; // 第一个未使用的块,即本文件最后一个已经使用的直接块的下一块
while (block_idx < file_will_use_blocks)
{
block_lba = block_bitmap_alloc(cur_part);
if (block_lba == -1)
{
printk("file_write: block_bitmap_alloc for situation 2 failed\n");
return -1;
}
if (block_idx < 12)
{
// 新创建的0~11块直接存入all_blocks数组
ASSERT(file->fd_inode->i_sectors[block_idx] == 0); // 确保尚未分配扇区地址
file->fd_inode->i_sectors[block_idx] = all_blocks[block_idx] = block_lba;
}
else
{
// 间接块只写入到all_block数组中,待全部分配完成后一次性同步到硬盘
all_blocks[block_idx] = block_lba;
}
/* 每分配一个块就将位图同步到硬盘 */
block_bitmap_idx = block_lba - cur_part->sb->data_start_lba;
bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);
block_idx++; // 下一个新扇区
}
ide_write(cur_part->my_disk, indirect_block_table, all_blocks + 12, 1); // 同步一级间接块表到硬盘
}
else if (file_has_used_blocks > 12)
{
/* 第三种情况:新数据占据间接块*/
ASSERT(file->fd_inode->i_sectors[12] != 0); // 已经具备了一级间接块表
indirect_block_table = file->fd_inode->i_sectors[12]; // 获取一级间接表地址
/* 已使用的间接块也将被读入all_blocks,无须单独收录 */
ide_read(cur_part->my_disk, indirect_block_table, all_blocks + 12, 1); // 获取所有间接块地址
block_idx = file_has_used_blocks; // 第一个未使用的间接块,即已经使用的间接块的下一块
while (block_idx < file_will_use_blocks)
{
block_lba = block_bitmap_alloc(cur_part);
if (block_lba == -1)
{
printk("file_write: block_bitmap_alloc for situation 3 failed\n");
return -1;
}
all_blocks[block_idx++] = block_lba;
/* 每分配一个块就将位图同步到硬盘 */
block_bitmap_idx = block_lba - cur_part->sb->data_start_lba;
bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);
}
ide_write(cur_part->my_disk, indirect_block_table, all_blocks + 12, 1); // 同步一级间接块表到硬盘
}
}
bool first_write_block = true; // 含有剩余空间的扇区标识
/* 块地址已经收集到all_blocks中,下面开始写数据 */
file->fd_pos = file->fd_inode->i_size - 1; // 置fd_pos为文件大小-1,下面在写数据时随时更新
while (bytes_written < count)
{
// 直到写完所有数据
memset(io_buf, 0, BLOCK_SIZE);
sec_idx = file->fd_inode->i_size / BLOCK_SIZE;
sec_lba = all_blocks[sec_idx];
sec_off_bytes = file->fd_inode->i_size % BLOCK_SIZE;
sec_left_bytes = BLOCK_SIZE - sec_off_bytes;
/* 判断此次写入硬盘的数据大小 */
chunk_size = size_left < sec_left_bytes ? size_left : sec_left_bytes;
if (first_write_block)
{
ide_read(cur_part->my_disk, sec_lba, io_buf, 1);
first_write_block = false;
}
memcpy(io_buf + sec_off_bytes, src, chunk_size);
ide_write(cur_part->my_disk, sec_lba, io_buf, 1);
printk("file write at lba 0x%x\n", sec_lba); // 调试,完成后去掉
src += chunk_size; // 将指针推移到下个新数据
file->fd_inode->i_size += chunk_size; // 更新文件大小
file->fd_pos += chunk_size;
bytes_written += chunk_size;
size_left -= chunk_size;
}
inode_sync(cur_part, file->fd_inode, io_buf);
sys_free(all_blocks);
sys_free(io_buf);
return bytes_written;
}
関数宣言、変更( myos/fs/file.h )
int32_t file_write(struct file *file, const void *buf, uint32_t count);
第 12 章で printf を実装したときに sys_write を実装しましたが、当時はファイル システムがなかったため、単に console_put_str を呼び出して文字列を出力するだけでした。
sys_write
buf 内の連続 count バイトをファイル記述子 fd に書き込むために使用されます。受信 fd が標準出力を表す場合は、console_put_str を呼び出して印刷するだけです。そうでない場合は、直接呼び出してfile_write
ください
変更 ( myos/fs/fs.c )
#include "console.h"
/* 将buf中连续count个字节写入文件描述符fd,成功则返回写入的字节数,失败返回-1 */
int32_t sys_write(int32_t fd, const void *buf, uint32_t count)
{
if (fd < 0)
{
printk("sys_write: fd error\n");
return -1;
}
if (fd == stdout_no)
{
char tmp_buf[1024] = {
0};
memcpy(tmp_buf, buf, count);
console_put_str(tmp_buf);
return count;
}
uint32_t _fd = fd_local2global(fd);
struct file *wr_file = &file_table[_fd];
if (wr_file->fd_flag & O_WRONLY || wr_file->fd_flag & O_RDWR)
{
uint32_t bytes_written = file_write(wr_file, buf, count);
return bytes_written;
}
else
{
console_put_str("sys_write: not allowed to write file without flag O_RDWR or O_WRONLY\n");
return -1;
}
}
サポートコード、修正 ( myos/fs/file.h )
/* 标准输入输出描述符 */
enum std_fd {
stdin_no, // 0 标准输入
stdout_no, // 1 标准输出
stderr_no // 2 标准错误
};
関数宣言、変更( myos/fs/fs.h )
int32_t sys_write(int32_t fd, const void *buf, uint32_t count)
( myos/userprog/syscall-init.c ) を変更し、元のsys_write
定義を削除します
/* 打印字符串str(未实现文件系统前的版本) */
uint32_t sys_write(char* str) {
console_put_str(str);
return strlen(str);
}
次に ( myos/userprog/syscall-init.c )fs.h
ヘッダー ファイルを追加します。
#include "fs.h"
最後にステートメント( myos/userprog/syscall-init.c )を削除します。sys_write
uint32_t sys_write(char* str);
次に、ユーザーモードエントリを変更しますwrite
変更 ( myos/fs/syscall.c )
/* 打印字符串str */
uint32_t write(char* str) {
return _syscall1(SYS_WRITE, str);
}
のために
/* 把buf中count个字符写入文件描述符fd */
uint32_t write(int32_t fd, const void *buf, uint32_t count)
{
return _syscall3(SYS_WRITE, fd, buf, count);
}
関数宣言を変更する
変更 ( myos/lib/user/syscall.h )
uint32_t write(char* str);
のために
uint32_t write(int32_t fd, const void *buf, uint32_t count)
最後の変更は、printf
置換された文字列を標準出力に書き込むことです。
変更 ( myos/lib/stdio.c )
/* 格式化输出字符串format */
uint32_t printf(const char* format, ...) {
va_list args;
va_start(args, format); // 使args指向format
char buf[1024] = {
0}; // 用于存储拼接后的字符串
vsprintf(buf, format, args);
va_end(args);
return write(buf);
}
のために
/* 格式化输出字符串format */
uint32_t printf(const char *format, ...)
{
va_list args;
va_start(args, format); // 使args指向format
char buf[1024] = {
0}; // 用于存储拼接后的字符串
vsprintf(buf, format, args);
va_end(args);
return write(1, buf, strlen(buf));
}
テストコード ( myos/kernel/main.c )
#include "print.h"
#include "init.h"
#include "fs.h"
#include "stdio.h"
int main(void) {
put_str("I am kernel\n");
init_all();
uint32_t fd = sys_open("/file1", O_RDWR);
printf("fd:%d\n", fd);
sys_write(fd, "hello,world\n", 12);
sys_close(fd);
printf("%d closed now\n", fd);
while(1);
return 0;
}
(注: 作成者はコードのこの部分を 2 回実行しました。つまり、合計 2 回書かれたことになりますhello,world\n
)
xxd -s 屏幕显示写入地址 *512 -l 512 hd80M.img
ディスク上のファイルを表示する
セクション f:
file_read
ファイル file から buf に count バイトを読み取ります。基本原則:ファイル構造には現在の操作を表すファイル コンテンツの位置があります。実際、これはファイル内で読み取られるコンテンツの開始位置です。たとえば、1KB のテキスト ファイル、fd_pos = 500、struct file
その後fd_pos
これは、ファイル内のオフセット 500 バイトのこの文字で始まる現在のコンテンツを読み取ることを意味します。関数 と にstruct file
渡すことによって、ファイル全体に対する相対的な読み取り対象のバイト オフセットを決定できます。次に、操作ファイルの i ノードへのポインタがあり、この i ノードの i_size と i_sectors[] を通じて、ファイルの保存場所の情報を知ることができます。したがって、読み取られるコンテンツがディスク上でどこにあるかを知るのは当然です。演算単位はブロックなので、開始位置の場合はブロックの前の無駄なデータを破棄し、終了位置の場合はブロック以降の無駄なデータを破棄する必要があります。fd_pos
count
struct file
変更 ( myos/fs/file.c )
/* 从文件file中读取count个字节写入buf, 返回读出的字节数,若到文件尾则返回-1 */
int32_t file_read(struct file *file, void *buf, uint32_t count)
{
uint8_t *buf_dst = (uint8_t *)buf;
uint32_t size = count, size_left = size;
/* 若要读取的字节数超过了文件可读的剩余量, 就用剩余量做为待读取的字节数 */
if ((file->fd_pos + count) > file->fd_inode->i_size)
{
size = file->fd_inode->i_size - file->fd_pos;
size_left = size;
if (size == 0)
{
// 若到文件尾则返回-1
return -1;
}
}
uint8_t *io_buf = sys_malloc(BLOCK_SIZE);
if (io_buf == NULL)
{
printk("file_read: sys_malloc for io_buf failed\n");
}
uint32_t *all_blocks = (uint32_t *)sys_malloc(BLOCK_SIZE + 48); // 用来记录文件所有的块地址
if (all_blocks == NULL)
{
printk("file_read: sys_malloc for all_blocks failed\n");
return -1;
}
uint32_t block_read_start_idx = file->fd_pos / BLOCK_SIZE; // 数据所在块的起始地址
uint32_t block_read_end_idx = (file->fd_pos + size) / BLOCK_SIZE; // 数据所在块的终止地址
uint32_t read_blocks = block_read_start_idx - block_read_end_idx; // 如增量为0,表示数据在同一扇区
ASSERT(block_read_start_idx < 139 && block_read_end_idx < 139);
int32_t indirect_block_table; // 用来获取一级间接表地址
uint32_t block_idx; // 获取待读的块地址
/* 以下开始构建all_blocks块地址数组,专门存储用到的块地址(本程序中块大小同扇区大小) */
if (read_blocks == 0)
{
// 在同一扇区内读数据,不涉及到跨扇区读取
ASSERT(block_read_end_idx == block_read_start_idx);
if (block_read_end_idx < 12)
{
// 待读的数据在12个直接块之内
block_idx = block_read_end_idx;
all_blocks[block_idx] = file->fd_inode->i_sectors[block_idx];
}
else
{
// 若用到了一级间接块表,需要将表中间接块读进来
indirect_block_table = file->fd_inode->i_sectors[12];
ide_read(cur_part->my_disk, indirect_block_table, all_blocks + 12, 1);
}
}
else
{
// 若要读多个块
/* 第一种情况: 起始块和终止块属于直接块*/
if (block_read_end_idx < 12)
{
// 数据结束所在的块属于直接块
block_idx = block_read_start_idx;
while (block_idx <= block_read_end_idx)
{
all_blocks[block_idx] = file->fd_inode->i_sectors[block_idx];
block_idx++;
}
}
else if (block_read_start_idx < 12 && block_read_end_idx >= 12)
{
/* 第二种情况: 待读入的数据跨越直接块和间接块两类*/
/* 先将直接块地址写入all_blocks */
block_idx = block_read_start_idx;
while (block_idx < 12)
{
all_blocks[block_idx] = file->fd_inode->i_sectors[block_idx];
block_idx++;
}
ASSERT(file->fd_inode->i_sectors[12] != 0); // 确保已经分配了一级间接块表
/* 再将间接块地址写入all_blocks */
indirect_block_table = file->fd_inode->i_sectors[12];
ide_read(cur_part->my_disk, indirect_block_table, all_blocks + 12, 1); // 将一级间接块表读进来写入到第13个块的位置之后
}
else
{
/* 第三种情况: 数据在间接块中*/
ASSERT(file->fd_inode->i_sectors[12] != 0); // 确保已经分配了一级间接块表
indirect_block_table = file->fd_inode->i_sectors[12]; // 获取一级间接表地址
ide_read(cur_part->my_disk, indirect_block_table, all_blocks + 12, 1); // 将一级间接块表读进来写入到第13个块的位置之后
}
}
/* 用到的块地址已经收集到all_blocks中,下面开始读数据 */
uint32_t sec_idx, sec_lba, sec_off_bytes, sec_left_bytes, chunk_size;
uint32_t bytes_read = 0;
while (bytes_read < size)
{
// 直到读完为止
sec_idx = file->fd_pos / BLOCK_SIZE;
sec_lba = all_blocks[sec_idx];
sec_off_bytes = file->fd_pos % BLOCK_SIZE;
sec_left_bytes = BLOCK_SIZE - sec_off_bytes;
chunk_size = size_left < sec_left_bytes ? size_left : sec_left_bytes; // 待读入的数据大小
memset(io_buf, 0, BLOCK_SIZE);
ide_read(cur_part->my_disk, sec_lba, io_buf, 1);
memcpy(buf_dst, io_buf + sec_off_bytes, chunk_size);
buf_dst += chunk_size;
file->fd_pos += chunk_size;
bytes_read += chunk_size;
size_left -= chunk_size;
}
sys_free(all_blocks);
sys_free(io_buf);
return bytes_read;
}
関数宣言、変更( myos/fs/file.h )
int32_t file_read(struct file *file, void *buf, uint32_t count);
sys_read
受信したファイル記述子に従って、 を呼び出してfd_local2global
ファイル記述子を指定されたグローバルなオープン ファイル構造配列インデックスに変換し、その後 を呼び出してfile_read
count バイトを読み取ってそれらを格納しますbuf
。
変更 ( myos/fs/fs.c )
/* 从文件描述符fd指向的文件中读取count个字节到buf,若成功则返回读出的字节数,到文件尾则返回-1 */
int32_t sys_read(int32_t fd, void *buf, uint32_t count)
{
if (fd < 0)
{
printk("sys_read: fd error\n");
return -1;
}
ASSERT(buf != NULL);
uint32_t _fd = fd_local2global(fd);
return file_read(&file_table[_fd], buf, count);
}
関数宣言、変更( myos/fs/fs.h )
int32_t sys_read(int32_t fd, void *buf, uint32_t count);
テストコード ( myos/kernel/main.c )
#include "print.h"
#include "init.h"
#include "fs.h"
#include "stdio.h"
int main(void) {
put_str("I am kernel\n");
init_all();
uint32_t fd = sys_open("/file1", O_RDWR);
printf("open /file1, fd:%d\n", fd);
char buf[64] = {
0};
int read_bytes = sys_read(fd, buf, 18);
printf("1_ read %d bytes:\n%s\n", read_bytes, buf);
memset(buf, 0, 64);
read_bytes = sys_read(fd, buf, 6);
printf("2_ read %d bytes:\n%s", read_bytes, buf);
memset(buf, 0, 64);
read_bytes = sys_read(fd, buf, 6);
printf("3_ read %d bytes:\n%s", read_bytes, buf);
printf("________ close file1 and reopen ________\n");
sys_close(fd);
fd = sys_open("/file1", O_RDWR);
memset(buf, 0, 64);
read_bytes = sys_read(fd, buf, 24);
printf("4_ read %d bytes:\n%s", read_bytes, buf);
sys_close(fd);
while(1);
return 0;
}
セクション g:
sys_lseek
受信参照オブジェクトとオフセットに基づいて、受信ファイル記述子に対応するグローバルなオープン ファイル構造をリセットするために使用されますfd_pos
。基本原則: 受信ファイル記述子に従って、呼び出しはfd_local2global
ファイル記述子を指定されたグローバルなオープン ファイル構造配列インデックスに変換し、次にスイッチ ケースが受信参照オブジェクトを選択してfd_pos
別の処理を実行します。参照オブジェクトが であるSEET_SET
場合、新しいfd_pos
オブジェクトは受信オフセットと等しくなります。参照オブジェクトが であるSEET_CUR
場合、新しいオブジェクトはfd_pos
= オリジナルfd_pos
+ 受信オフセットです。参照オブジェクトが である場合SEET_END
、新しいオブジェクトfd_pos
は元のファイル サイズ + オフセット (時点)今回はオフセットです。それ以外の場合は負の数です)
参照場所の定義と変更 ( myos/fs/fs.h )
/* 文件读写位置偏移量 */
enum whence
{
SEEK_SET = 1,
SEEK_CUR,
SEEK_END
};
関数sys_lseek
、変更済み ( myos/fs/fs.c )
/* 重置用于文件读写操作的偏移指针,成功时返回新的偏移量,出错时返回-1 */
int32_t sys_lseek(int32_t fd, int32_t offset, uint8_t whence)
{
if (fd < 0)
{
printk("sys_lseek: fd error\n");
return -1;
}
ASSERT(whence > 0 && whence < 4);
uint32_t _fd = fd_local2global(fd);
struct file *pf = &file_table[_fd];
int32_t new_pos = 0; // 新的偏移量必须位于文件大小之内
int32_t file_size = (int32_t)pf->fd_inode->i_size;
switch (whence)
{
/* SEEK_SET 新的读写位置是相对于文件开头再增加offset个位移量 */
case SEEK_SET:
new_pos = offset;
break;
/* SEEK_CUR 新的读写位置是相对于当前的位置增加offset个位移量 */
case SEEK_CUR: // offse可正可负
new_pos = (int32_t)pf->fd_pos + offset;
break;
/* SEEK_END 新的读写位置是相对于文件尺寸再增加offset个位移量 */
case SEEK_END: // 此情况下,offset应该为负值
new_pos = file_size + offset;
}
if (new_pos < 0 || new_pos > (file_size - 1))
{
return -1;
}
pf->fd_pos = new_pos;
return pf->fd_pos;
}
関数宣言 ( myos/fs/fs.h )
int32_t sys_lseek(int32_t fd, int32_t offset, uint8_t whence);
テストコード ( myso/kernel/main.c )
#include "print.h"
#include "init.h"
#include "fs.h"
#include "stdio.h"
#include "string.h"
int main(void)
{
put_str("I am kernel\n");
init_all();
uint32_t fd = sys_open("/file1", O_RDWR);
printf("open /file1, fd:%d\n", fd);
char buf[64] = {
0};
int read_bytes = sys_read(fd, buf, 18);
printf("1_ read %d bytes:\n%s\n", read_bytes, buf);
memset(buf, 0, 64);
read_bytes = sys_read(fd, buf, 6);
printf("2_ read %d bytes:\n%s", read_bytes, buf);
memset(buf, 0, 64);
read_bytes = sys_read(fd, buf, 6);
printf("3_ read %d bytes:\n%s", read_bytes, buf);
printf("________ SEEK_SET 0 ________\n");
sys_lseek(fd, 0, SEEK_SET);
memset(buf, 0, 64);
read_bytes = sys_read(fd, buf, 24);
printf("4_ read %d bytes:\n%s", read_bytes, buf);
sys_close(fd);
while (1)
;
return 0;
}
セクション h:
次に、ファイル作成の逆の処理であるファイル削除機能を実装します。i ノードとディレクトリのエントリが含まれます。
1 つ目は i ノードを削除することです。
inode_delete
、削除する i ノードのインデックスを i ノード配列で渡し、ディスク内の i ノードを削除します。inode の割り当ては inode ビットマップに依存して完了するため、この機能は不要であり、inode をリサイクルする場合は、inode ビットマップ内のビットのみをリサイクルする必要があります。次回 i ノード ビットが割り当てられた後、新しい i ノード データが古い i ノード データを上書きするため、不必要なディスクの読み取りと書き込みも回避できます。機能原理: この呼び出しにより、inode_locate
inode 配列インデックスをディスク上のこの i ノードの開始セクターとバイト オフセットに変換できます。i ノードが配置されているセクター全体をメモリ バッファに読み取り、次にメモリ バッファ内の i ノードをクリアして、メモリ バッファ内のデータをディスクに書き込みます。
変更 ( myos/fs/inode.c )
/* 将硬盘分区part上的inode清空 */
void inode_delete(struct partition *part, uint32_t inode_no, void *io_buf)
{
ASSERT(inode_no < 4096);
struct inode_position inode_pos;
inode_locate(part, inode_no, &inode_pos); // inode位置信息会存入inode_pos
ASSERT(inode_pos.sec_lba <= (part->start_lba + part->sec_cnt));
char *inode_buf = (char *)io_buf;
if (inode_pos.two_sec)
{
// inode跨扇区,读入2个扇区
/* 将原硬盘上的内容先读出来 */
ide_read(part->my_disk, inode_pos.sec_lba, inode_buf, 2);
/* 将inode_buf清0 */
memset((inode_buf + inode_pos.off_size), 0, sizeof(struct inode));
/* 用清0的内存数据覆盖磁盘 */
ide_write(part->my_disk, inode_pos.sec_lba, inode_buf, 2);
}
else
{
// 未跨扇区,只读入1个扇区就好
/* 将原硬盘上的内容先读出来 */
ide_read(part->my_disk, inode_pos.sec_lba, inode_buf, 1);
/* 将inode_buf清0 */
memset((inode_buf + inode_pos.off_size), 0, sizeof(struct inode));
/* 用清0的内存数据覆盖磁盘 */
ide_write(part->my_disk, inode_pos.sec_lba, inode_buf, 1);
}
}
inode_release
ファイルを削除します。これには、ディスク上の i ノードを削除し、ファイルが占有しているブロックを再利用します。1. ファイルによって占有されているすべてのブロックをリサイクルします。inode の i_sectors[] を通じてどのブロックが占有されているかを知ることができ、対応するブロック ビットマップ内のビットをクリアします。つまり、ファイルは実際には削除されません。データ; 2. i ノードをリサイクルし、最初に i ノード ビットマップ内の i ノードの対応するビットをクリアしてから、呼び出してinode_delete
ディスク内の i ノードを削除します。
変更 ( myos/fs/inode.c )
#include "file.h"
/* 回收inode的数据块和inode本身 */
void inode_release(struct partition *part, uint32_t inode_no)
{
struct inode *inode_to_del = inode_open(part, inode_no);
ASSERT(inode_to_del->i_no == inode_no);
/* 1 回收inode占用的所有块 */
uint8_t block_idx = 0, block_cnt = 12;
uint32_t block_bitmap_idx;
uint32_t all_blocks[140] = {
0}; // 12个直接块+128个间接块
/* a 先将前12个直接块存入all_blocks */
while (block_idx < 12)
{
all_blocks[block_idx] = inode_to_del->i_sectors[block_idx];
block_idx++;
}
/* b 如果一级间接块表存在,将其128个间接块读到all_blocks[12~], 并释放一级间接块表所占的扇区 */
if (inode_to_del->i_sectors[12] != 0)
{
ide_read(part->my_disk, inode_to_del->i_sectors[12], all_blocks + 12, 1);
block_cnt = 140;
/* 回收一级间接块表占用的扇区 */
block_bitmap_idx = inode_to_del->i_sectors[12] - part->sb->data_start_lba;
ASSERT(block_bitmap_idx > 0);
bitmap_set(&part->block_bitmap, block_bitmap_idx, 0);
bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);
}
/* c inode所有的块地址已经收集到all_blocks中,下面逐个回收 */
block_idx = 0;
while (block_idx < block_cnt)
{
if (all_blocks[block_idx] != 0)
{
block_bitmap_idx = 0;
block_bitmap_idx = all_blocks[block_idx] - part->sb->data_start_lba;
ASSERT(block_bitmap_idx > 0);
bitmap_set(&part->block_bitmap, block_bitmap_idx, 0);
bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);
}
block_idx++;
}
/*2 回收该inode所占用的inode */
bitmap_set(&part->inode_bitmap, inode_no, 0);
bitmap_sync(cur_part, inode_no, INODE_BITMAP);
/****** 以下inode_delete是调试用的 ******
* 此函数会在inode_table中将此inode清0,
* 但实际上是不需要的,inode分配是由inode位图控制的,
* 硬盘上的数据不需要清0,可以直接覆盖*/
void *io_buf = sys_malloc(1024);
inode_delete(part, inode_no, io_buf);
sys_free(io_buf);
/***********************************************/
inode_close(inode_to_del);
}
関数宣言、変更( myos/fs/inode.h )
void inode_delete(struct partition *part, uint32_t inode_no, void *io_buf);
void inode_release(struct partition *part, uint32_t inode_no);
次に、ディレクトリ エントリを削除します。
delete_dir_entry
指定されたファイルに対応するディスク上のディレクトリ エントリを削除します。基本原則: 削除されるファイルの親ディレクトリ構造の渡された struct dir ポインタには i ノード メンバーがあります。この i ノードには、このディレクトリ ファイルの保存場所を記録する i_sectors[] があります。当然、親ディレクトリがディスクからブロック単位で読み取られ、ディレクトリ ファイルがバッファに読み込まれ、削除するディレクトリ エントリを見つけるために走査され、バッファ内の対応するディレクトリ エントリが削除されて、バッファ データが書き戻されます。
このプロセスでは: ディレクトリ エントリが存在するブロック (. が存在するブロックではない) に削除対象のディレクトリ エントリのみがあることが判明した場合は、ブロックをリサイクルする方法 (ブロック ビットマップ内のビットを削除する) が使用されます。 、その後ブロック ビットマップを同期する) 方式が採用され、ディレクトリ エントリをクリアします。このブロックをリサイクルする場合には、このブロックが第 1 レベルの間接ブロックの中で唯一であるかどうかも判断する必要があり、そうであれば、第 1 レベルの間接ブロックもリサイクルする必要があります。
変更 ( myos/fs/dir.c )
/* 把分区part目录pdir中编号为inode_no的目录项删除 */
bool delete_dir_entry(struct partition *part, struct dir *pdir, uint32_t inode_no, void *io_buf)
{
struct inode *dir_inode = pdir->inode;
uint32_t block_idx = 0, all_blocks[140] = {
0};
/* 收集目录全部块地址 */
while (block_idx < 12)
{
all_blocks[block_idx] = dir_inode->i_sectors[block_idx];
block_idx++;
}
if (dir_inode->i_sectors[12])
{
ide_read(part->my_disk, dir_inode->i_sectors[12], all_blocks + 12, 1);
}
/* 目录项在存储时保证不会跨扇区 */
uint32_t dir_entry_size = part->sb->dir_entry_size;
uint32_t dir_entrys_per_sec = (SECTOR_SIZE / dir_entry_size); // 每扇区最大的目录项数目
struct dir_entry *dir_e = (struct dir_entry *)io_buf;
struct dir_entry *dir_entry_found = NULL;
uint8_t dir_entry_idx, dir_entry_cnt;
bool is_dir_first_block = false; // 目录的第1个块
/* 遍历所有块,寻找目录项 */
block_idx = 0;
while (block_idx < 140)
{
is_dir_first_block = false;
if (all_blocks[block_idx] == 0)
{
block_idx++;
continue;
}
dir_entry_idx = dir_entry_cnt = 0;
memset(io_buf, 0, SECTOR_SIZE);
/* 读取扇区,获得目录项 */
ide_read(part->my_disk, all_blocks[block_idx], io_buf, 1);
/* 遍历所有的目录项,统计该扇区的目录项数量及是否有待删除的目录项 */
while (dir_entry_idx < dir_entrys_per_sec)
{
if ((dir_e + dir_entry_idx)->f_type != FT_UNKNOWN)
{
if (!strcmp((dir_e + dir_entry_idx)->filename, "."))
{
is_dir_first_block = true;
}
else if (strcmp((dir_e + dir_entry_idx)->filename, ".") &&
strcmp((dir_e + dir_entry_idx)->filename, ".."))
{
dir_entry_cnt++; // 统计此扇区内的目录项个数,用来判断删除目录项后是否回收该扇区
if ((dir_e + dir_entry_idx)->i_no == inode_no)
{
// 如果找到此i结点,就将其记录在dir_entry_found
ASSERT(dir_entry_found == NULL); // 确保目录中只有一个编号为inode_no的inode,找到一次后dir_entry_found就不再是NULL
dir_entry_found = dir_e + dir_entry_idx;
/* 找到后也继续遍历,统计总共的目录项数 */
}
}
}
dir_entry_idx++;
}
/* 若此扇区未找到该目录项,继续在下个扇区中找 */
if (dir_entry_found == NULL)
{
block_idx++;
continue;
}
/* 在此扇区中找到目录项后,清除该目录项并判断是否回收扇区,随后退出循环直接返回 */
ASSERT(dir_entry_cnt >= 1);
/* 除目录第1个扇区外,若该扇区上只有该目录项自己,则将整个扇区回收 */
if (dir_entry_cnt == 1 && !is_dir_first_block)
{
/* a 在块位图中回收该块 */
uint32_t block_bitmap_idx = all_blocks[block_idx] - part->sb->data_start_lba;
bitmap_set(&part->block_bitmap, block_bitmap_idx, 0);
bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);
/* b 将块地址从数组i_sectors或索引表中去掉 */
if (block_idx < 12)
{
dir_inode->i_sectors[block_idx] = 0;
}
else
{
// 在一级间接索引表中擦除该间接块地址
/*先判断一级间接索引表中间接块的数量,如果仅有这1个间接块,连同间接索引表所在的块一同回收 */
uint32_t indirect_blocks = 0;
uint32_t indirect_block_idx = 12;
while (indirect_block_idx < 140)
{
if (all_blocks[indirect_block_idx] != 0)
{
indirect_blocks++;
}
}
ASSERT(indirect_blocks >= 1); // 包括当前间接块
if (indirect_blocks > 1)
{
// 间接索引表中还包括其它间接块,仅在索引表中擦除当前这个间接块地址
all_blocks[block_idx] = 0;
ide_write(part->my_disk, dir_inode->i_sectors[12], all_blocks + 12, 1);
}
else
{
// 间接索引表中就当前这1个间接块,直接把间接索引表所在的块回收,然后擦除间接索引表块地址
/* 回收间接索引表所在的块 */
block_bitmap_idx = dir_inode->i_sectors[12] - part->sb->data_start_lba;
bitmap_set(&part->block_bitmap, block_bitmap_idx, 0);
bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);
/* 将间接索引表地址清0 */
dir_inode->i_sectors[12] = 0;
}
}
}
else
{
// 仅将该目录项清空
memset(dir_entry_found, 0, dir_entry_size);
ide_write(part->my_disk, all_blocks[block_idx], io_buf, 1);
}
/* 更新i结点信息并同步到硬盘 */
ASSERT(dir_inode->i_size >= dir_entry_size);
dir_inode->i_size -= dir_entry_size;
memset(io_buf, 0, SECTOR_SIZE * 2);
inode_sync(part, dir_inode, io_buf);
return true;
}
/* 所有块中未找到则返回false,若出现这种情况应该是serarch_file出错了 */
return false;
}
関数宣言、変更( myos/fs/dir.h )
bool delete_dir_entry(struct partition *part, struct dir *pdir, uint32_t inode_no, void *io_buf);
sys_unlink
受信パスに基づいてディレクトリ以外のファイルを削除するために使用されます。原則: まず、 search_file
検索パスを呼び出してファイルの i ノードを返し、その i ノードがオープン グローバル ファイル構造に対応するかどうかを確認します。対応する場合、そのファイルは使用中であるため、削除すべきではありません。そうでない場合は、 を呼び出して delete_dir_entry
ディスク上のこのファイルのディレクトリ エントリを削除し、 を呼び出してinode_release
i ノードに対応するファイルを削除します。これで削除が完了します。
変更 ( myos/fs/fs.c )
/* 删除文件(非目录),成功返回0,失败返回-1 */
int32_t sys_unlink(const char *pathname)
{
ASSERT(strlen(pathname) < MAX_PATH_LEN);
/* 先检查待删除的文件是否存在 */
struct path_search_record searched_record;
memset(&searched_record, 0, sizeof(struct path_search_record));
int inode_no = search_file(pathname, &searched_record);
ASSERT(inode_no != 0);
if (inode_no == -1)
{
printk("file %s not found!\n", pathname);
dir_close(searched_record.parent_dir);
return -1;
}
if (searched_record.file_type == FT_DIRECTORY)
{
printk("can`t delete a direcotry with unlink(), use rmdir() to instead\n");
dir_close(searched_record.parent_dir);
return -1;
}
/* 检查是否在已打开文件列表(文件表)中 */
uint32_t file_idx = 0;
while (file_idx < MAX_FILE_OPEN)
{
if (file_table[file_idx].fd_inode != NULL && (uint32_t)inode_no == file_table[file_idx].fd_inode->i_no)
{
break;
}
file_idx++;
}
if (file_idx < MAX_FILE_OPEN)
{
dir_close(searched_record.parent_dir);
printk("file %s is in use, not allow to delete!\n", pathname);
return -1;
}
ASSERT(file_idx == MAX_FILE_OPEN);
/* 为delete_dir_entry申请缓冲区 */
void *io_buf = sys_malloc(SECTOR_SIZE + SECTOR_SIZE);
if (io_buf == NULL)
{
dir_close(searched_record.parent_dir);
printk("sys_unlink: malloc for io_buf failed\n");
return -1;
}
struct dir *parent_dir = searched_record.parent_dir;
delete_dir_entry(cur_part, parent_dir, inode_no, io_buf);
inode_release(cur_part, inode_no);
sys_free(io_buf);
dir_close(searched_record.parent_dir);
return 0; // 成功删除文件
}
関数宣言、変更( myos/fs/fs.h )
int32_t sys_unlink(const char *pathname);
テストコード ( myos/kernel/main.c )
#include "print.h"
#include "init.h"
#include "fs.h"
#include "stdio.h"
#include "string.h"
int main(void) {
put_str("I am kernel\n");
init_all();
printf("/file1 delete %s!\n", sys_unlink("/file1") == 0 ? "done" : "fail");
while(1);
return 0;
}
デバッグを容易にするために、( myos/fs/fs.c/mount_partition ) を変更します。デバッグ後、追加されたコードを削除する必要があります。
printk("mount %s done!\n", part->name);
/* 此处返回true是为了迎合主调函数list_traversal的实现,与函数本身功能无关。
只有返回true时list_traversal才会停止遍历,减少了后面元素无意义的遍历.*/
sys_free(sb_buf);
のために
printk("mount %s done!\n", part->name);
printk("sdb1's block_bitmap_lba: %x\n", sb_buf->block_bitmap_lba);
printk("sdb1's inode_bitmap_lba: %x\n", sb_buf->inode_bitmap_lba);
printk("sdb1's inode_table_lba: %x\n", sb_buf->inode_table_lba);
printk("sdb1's data_start_lba: %x\n", sb_buf->data_start_lba);
/* 此处返回true是为了迎合主调函数list_traversal的实现,与函数本身功能无关。
只有返回true时list_traversal才会停止遍历,减少了后面元素无意义的遍历.*/
sys_free(sb_buf);
セクション i:
ディレクトリ ファイルの作成に伴う作業には、実際には i ノードとディレクトリ エントリが含まれます。
-
作成する新しいディレクトリがファイル システム上に存在しないことを確認してください。
-
新しいディレクトリの i ノードを作成します。
-
ディレクトリ ファイル内のディレクトリ エントリを保存するために、新しいディレクトリに 1 ブロックを割り当てます。
-
新しいディレクトリに 2 つのディレクトリ エントリ「...」と「.」を作成します。これらの 2 つのディレクトリ エントリは、すべてのディレクトリに存在する必要があります。
-
新しいディレクトリの親ディレクトリに、新しいディレクトリのディレクトリ エントリを追加します。
-
上記のリソースへの変更をディスクに同期します。
sys_mkdir
渡されたパスに基づいてディレクトリ ファイルを作成するために使用されます。基本原理: 1.search_file
作成する新しいディレクトリ ファイルがファイル システム上に存在しないことを確認するために呼び出します。 2. 新しいディレクトリの i ノード インデックスを割り当てるために inode_bitmap_alloc を呼び出し、inode を初期化するために inode_init を呼び出します。 3. block_bitmap_alloc を呼び出します。ディレクトリをホストするディレクトリ ファイルで使用するブロックを割り当てます。同時に、inode の i_sectors[0] を設定し、bitmap_sync 同期ブロックのビットマップを呼び出します; 4. バッファをクリアし、2 つのディレクトリ エントリを書き込みます。そして...、バッファの内容をディレクトリ ファイルに書き込みます。これで完了です。... を使用して 2 つのディレクトリ エントリを作成します。5. 独自のディレクトリ エントリを作成して設定し、sync_dir_entry を呼び出してディレクトリ エントリを親ディレクトリに同期します。6. 親ディレクトリの i ノードを独自の i ノードと同期します。 inode と inode_sync、bitmap_sync と同期する inode ビットマップ
変更 ( myos/fs/fs.c )
/* 创建目录pathname,成功返回0,失败返回-1 */
int32_t sys_mkdir(const char *pathname)
{
uint8_t rollback_step = 0; // 用于操作失败时回滚各资源状态
void *io_buf = sys_malloc(SECTOR_SIZE * 2);
if (io_buf == NULL)
{
printk("sys_mkdir: sys_malloc for io_buf failed\n");
return -1;
}
struct path_search_record searched_record;
memset(&searched_record, 0, sizeof(struct path_search_record));
int inode_no = -1;
inode_no = search_file(pathname, &searched_record);
if (inode_no != -1)
{
// 如果找到了同名目录或文件,失败返回
printk("sys_mkdir: file or directory %s exist!\n", pathname);
rollback_step = 1;
goto rollback;
}
else
{
// 若未找到,也要判断是在最终目录没找到还是某个中间目录不存在
uint32_t pathname_depth = path_depth_cnt((char *)pathname);
uint32_t path_searched_depth = path_depth_cnt(searched_record.searched_path);
/* 先判断是否把pathname的各层目录都访问到了,即是否在某个中间目录就失败了 */
if (pathname_depth != path_searched_depth)
{
// 说明并没有访问到全部的路径,某个中间目录是不存在的
printk("sys_mkdir: can`t access %s, subpath %s is`t exist\n", pathname, searched_record.searched_path);
rollback_step = 1;
goto rollback;
}
}
struct dir *parent_dir = searched_record.parent_dir;
/* 目录名称后可能会有字符'/',所以最好直接用searched_record.searched_path,无'/' */
char *dirname = strrchr(searched_record.searched_path, '/') + 1;
inode_no = inode_bitmap_alloc(cur_part);
if (inode_no == -1)
{
printk("sys_mkdir: allocate inode failed\n");
rollback_step = 1;
goto rollback;
}
struct inode new_dir_inode;
inode_init(inode_no, &new_dir_inode); // 初始化i结点
uint32_t block_bitmap_idx = 0; // 用来记录block对应于block_bitmap中的索引
int32_t block_lba = -1;
/* 为目录分配一个块,用来写入目录.和.. */
block_lba = block_bitmap_alloc(cur_part);
if (block_lba == -1)
{
printk("sys_mkdir: block_bitmap_alloc for create directory failed\n");
rollback_step = 2;
goto rollback;
}
new_dir_inode.i_sectors[0] = block_lba;
/* 每分配一个块就将位图同步到硬盘 */
block_bitmap_idx = block_lba - cur_part->sb->data_start_lba;
ASSERT(block_bitmap_idx != 0);
bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);
/* 将当前目录的目录项'.'和'..'写入目录 */
memset(io_buf, 0, SECTOR_SIZE * 2); // 清空io_buf
struct dir_entry *p_de = (struct dir_entry *)io_buf;
/* 初始化当前目录"." */
memcpy(p_de->filename, ".", 1);
p_de->i_no = inode_no;
p_de->f_type = FT_DIRECTORY;
p_de++;
/* 初始化当前目录".." */
memcpy(p_de->filename, "..", 2);
p_de->i_no = parent_dir->inode->i_no;
p_de->f_type = FT_DIRECTORY;
ide_write(cur_part->my_disk, new_dir_inode.i_sectors[0], io_buf, 1);
new_dir_inode.i_size = 2 * cur_part->sb->dir_entry_size;
/* 在父目录中添加自己的目录项 */
struct dir_entry new_dir_entry;
memset(&new_dir_entry, 0, sizeof(struct dir_entry));
create_dir_entry(dirname, inode_no, FT_DIRECTORY, &new_dir_entry);
memset(io_buf, 0, SECTOR_SIZE * 2); // 清空io_buf
if (!sync_dir_entry(parent_dir, &new_dir_entry, io_buf))
{
// sync_dir_entry中将block_bitmap通过bitmap_sync同步到硬盘
printk("sys_mkdir: sync_dir_entry to disk failed!\n");
rollback_step = 2;
goto rollback;
}
/* 父目录的inode同步到硬盘 */
memset(io_buf, 0, SECTOR_SIZE * 2);
inode_sync(cur_part, parent_dir->inode, io_buf);
/* 将新创建目录的inode同步到硬盘 */
memset(io_buf, 0, SECTOR_SIZE * 2);
inode_sync(cur_part, &new_dir_inode, io_buf);
/* 将inode位图同步到硬盘 */
bitmap_sync(cur_part, inode_no, INODE_BITMAP);
sys_free(io_buf);
/* 关闭所创建目录的父目录 */
dir_close(searched_record.parent_dir);
return 0;
/*创建文件或目录需要创建相关的多个资源,若某步失败则会执行到下面的回滚步骤 */
rollback: // 因为某步骤操作失败而回滚
switch (rollback_step)
{
case 2:
bitmap_set(&cur_part->inode_bitmap, inode_no, 0); // 如果新文件的inode创建失败,之前位图中分配的inode_no也要恢复
case 1:
/* 关闭所创建目录的父目录 */
dir_close(searched_record.parent_dir);
break;
}
sys_free(io_buf);
return -1;
}
関数宣言、変更( myos/fs/fs.h )
int32_t sys_mkdir(const char *pathname);
テストコード ( myos/kernel/main.c )
#include "print.h"
#include "init.h"
#include "fs.h"
#include "stdio.h"
#include "string.h"
int main(void) {
put_str("I am kernel\n");
init_all();
printf("/dir1/subdir1 create %s!\n", sys_mkdir("/dir1/subdir1") == 0 ? "done" : "fail");
printf("/dir1 create %s!\n", sys_mkdir("/dir1") == 0 ? "done" : "fail");
printf("now, /dir1/subdir1 create %s!\n", sys_mkdir("/dir1/subdir1") == 0 ? "done" : "fail");
int fd = sys_open("/dir1/subdir1/file2", O_CREAT|O_RDWR);
if (fd != -1) {
printf("/dir1/subdir1/file2 create done!\n");
sys_write(fd, "Catch me if you can!\n", 21);
sys_lseek(fd, 0, SEEK_SET);
char buf[32] = {
0};
sys_read(fd, buf, 21);
printf("/dir1/subdir1/file2 says:\n%s", buf);
sys_close(fd);
}
while(1);
return 0;
}
セクション j:
sys_opendir
渡されたパスに基づいてディレクトリを開くために使用されます。原則: を呼び出してsearch_file
パスを i ノード インデックスに変換し、次に呼び出してdir_open
ディレクトリ構造を作成し、対応する i ノードをメモリにロードします。
変更 ( myos/fs/fs.c )
/* 目录打开成功后返回目录指针,失败返回NULL */
struct dir *sys_opendir(const char *name)
{
ASSERT(strlen(name) < MAX_PATH_LEN);
/* 如果是根目录'/',直接返回&root_dir */
if (name[0] == '/' && (name[1] == 0 || name[0] == '.'))
{
return &root_dir;
}
/* 先检查待打开的目录是否存在 */
struct path_search_record searched_record;
memset(&searched_record, 0, sizeof(struct path_search_record));
int inode_no = search_file(name, &searched_record);
struct dir *ret = NULL;
if (inode_no == -1)
{
// 如果找不到目录,提示不存在的路径
printk("In %s, sub path %s not exist\n", name, searched_record.searched_path);
}
else
{
if (searched_record.file_type == FT_REGULAR)
{
printk("%s is regular file!\n", name);
}
else if (searched_record.file_type == FT_DIRECTORY)
{
ret = dir_open(cur_part, inode_no);
}
}
dir_close(searched_record.parent_dir);
return ret;
}
sys_closedir
それがdir_close
パッケージです
変更 ( myos/fs/fs.c )
/* 成功关闭目录dir返回0,失败返回-1 */
int32_t sys_closedir(struct dir *dir)
{
int32_t ret = -1;
if (dir != NULL)
{
dir_close(dir);
ret = 0;
}
return ret;
}
関数宣言 ( myos/fs/fs.h )
struct dir *sys_opendir(const char *name);
int32_t sys_closedir(struct dir *dir);
テストコード ( myos/kernel/main.c )
#include "print.h"
#include "init.h"
#include "fs.h"
#include "stdio.h"
#include "string.h"
int main(void)
{
put_str("I am kernel\n");
init_all();
struct dir *p_dir = sys_opendir("/dir1/subdir1");
if (p_dir)
{
printf("/dir1/subdir1 open done!\n");
if (sys_closedir(p_dir) == 0)
{
printf("/dir1/subdir1 close done!\n");
}
else
{
printf("/dir1/subdir1 close fail!\n");
}
}
else
{
printf("/dir1/subdir1 open fail!\n");
}
while (1)
;
return 0;
}
セクション k:
dir_read
渡されたディレクトリ ポインタに従って、一度に 1 つのディレクトリ アイテムを返すために使用されます。たとえば、ディレクトリ ファイルの下のディレクトリ アイテムの分布: a、空、b、空、c。最初の呼び出しでは a が返され、2 番目の呼び出しでは a が返されます。 b を返す 3 回目の呼び出しでは c を返す 原則: ディレクトリ内に独自の i ノードへのポインタがあります i ノード内に i_sectors[] があるため、ディレクトリ ファイルはディスク上で見つかり、バッファに読み込まれますトラバーサル ルールを設定するだけです。
コードの実際の意味はdir_pos
、返されたディレクトリ エントリの合計サイズであり、cur_dir_entry_pos
その意味は、この呼び出しのトラバーサル中にスキャンされた空でないディレクトリ エントリの合計サイズです。空でないディレクトリエントリがスキャンされるとき、この時点でcur_dir_entry_pos
と がdir_pos
等しい場合、当然、この空ではないディレクトリエントリを返す必要があると判断できます。
変更 ( myos/fs/dir.c )
/* 读取目录,成功返回1个目录项,失败返回NULL */
struct dir_entry *dir_read(struct dir *dir)
{
struct dir_entry *dir_e = (struct dir_entry *)dir->dir_buf;
struct inode *dir_inode = dir->inode;
uint32_t all_blocks[140] = {
0}, block_cnt = 12;
uint32_t block_idx = 0, dir_entry_idx = 0;
while (block_idx < 12)
{
all_blocks[block_idx] = dir_inode->i_sectors[block_idx];
block_idx++;
}
if (dir_inode->i_sectors[12] != 0)
{
// 若含有一级间接块表
ide_read(cur_part->my_disk, dir_inode->i_sectors[12], all_blocks + 12, 1);
block_cnt = 140;
}
block_idx = 0;
uint32_t cur_dir_entry_pos = 0; // 当前目录项的偏移,此项用来判断是否是之前已经返回过的目录项
uint32_t dir_entry_size = cur_part->sb->dir_entry_size;
uint32_t dir_entrys_per_sec = SECTOR_SIZE / dir_entry_size; // 1扇区内可容纳的目录项个数
/* 因为此目录内可能删除了某些文件或子目录,所以要遍历所有块 */
while (block_idx < block_cnt)
{
if (dir->dir_pos >= dir_inode->i_size)
{
return NULL;
}
if (all_blocks[block_idx] == 0)
{
// 如果此块地址为0,即空块,继续读出下一块
block_idx++;
continue;
}
memset(dir_e, 0, SECTOR_SIZE);
ide_read(cur_part->my_disk, all_blocks[block_idx], dir_e, 1);
dir_entry_idx = 0;
/* 遍历扇区内所有目录项 */
while (dir_entry_idx < dir_entrys_per_sec)
{
if ((dir_e + dir_entry_idx)->f_type)
{
// 如果f_type不等于0,即不等于FT_UNKNOWN
/* 判断是不是最新的目录项,避免返回曾经已经返回过的目录项 */
if (cur_dir_entry_pos < dir->dir_pos)
{
cur_dir_entry_pos += dir_entry_size;
dir_entry_idx++;
continue;
}
ASSERT(cur_dir_entry_pos == dir->dir_pos);
dir->dir_pos += dir_entry_size; // 更新为新位置,即下一个返回的目录项地址
return dir_e + dir_entry_idx;
}
dir_entry_idx++;
}
block_idx++;
}
return NULL;
}
関数宣言、変更( myos/fs/dir.h )
struct dir_entry *dir_read(struct dir *dir);
sys_readdir
、つまりdir_read
パッケージ
sys_rewinddir
ディレクトリの dir_pos を 0 に設定します
変更 ( myos/fs/fs.c )
/* 读取目录dir的1个目录项,成功后返回其目录项地址,到目录尾时或出错时返回NULL */
struct dir_entry *sys_readdir(struct dir *dir)
{
ASSERT(dir != NULL);
return dir_read(dir);
}
/* 把目录dir的指针dir_pos置0 */
void sys_rewinddir(struct dir *dir)
{
dir->dir_pos = 0;
}
関数宣言、変更( myos/fs/fs.h )
struct dir_entry *sys_readdir(struct dir *dir);
void sys_rewinddir(struct dir *dir);
テストコード ( myos/kernel/main.c )
#include "print.h"
#include "init.h"
#include "fs.h"
#include "stdio.h"
#include "string.h"
#include "dir.h"
int main(void)
{
put_str("I am kernel\n");
init_all();
/******** 测试代码 ********/
struct dir *p_dir = sys_opendir("/dir1/subdir1");
if (p_dir)
{
printf("/dir1/subdir1 open done!\ncontent:\n");
char *type = NULL;
struct dir_entry *dir_e = NULL;
while ((dir_e = sys_readdir(p_dir)))
{
if (dir_e->f_type == FT_REGULAR)
{
type = "regular";
}
else
{
type = "directory";
}
printf(" %s %s\n", type, dir_e->filename);
}
if (sys_closedir(p_dir) == 0)
{
printf("/dir1/subdir1 close done!\n");
}
else
{
printf("/dir1/subdir1 close fail!\n");
}
}
else
{
printf("/dir1/subdir1 open fail!\n");
}
/******** 测试代码 ********/
while (1)
;
return 0;
}
セクション 1:
dir_is_empty
ディレクトリが空かどうかを確認します。原則: 空のディレクトリに対応するディレクトリ ファイルには、. と.... の 2 つのディレクトリ エントリのみがあります。したがって、ディレクトリに対応する i ノードの i_size が 2 つのディレクトリ エントリのサイズと等しいかどうかを直接判断できます。
dir_remove
親ディレクトリとサブディレクトリのポインタを渡し、指定された親ディレクトリ内のサブディレクトリを削除します。原則: サブディレクトリが空かどうかを確認し、次に呼び出してdelete_dir_entry
親ディレクトリのディレクトリ ファイル内のサブディレクトリ ディレクトリ エントリを削除し、最後に呼び出してinode_release
サブディレクトリのディレクトリ ファイルを削除します (サブディレクトリには . と... のみが存在する必要があります)ディレクトリファイル)
変更 ( myso/fs/dir.c )
/* 判断目录是否为空 */
bool dir_is_empty(struct dir *dir)
{
struct inode *dir_inode = dir->inode;
/* 若目录下只有.和..这两个目录项则目录为空 */
return (dir_inode->i_size == cur_part->sb->dir_entry_size * 2);
}
/* 在父目录parent_dir中删除child_dir */
int32_t dir_remove(struct dir *parent_dir, struct dir *child_dir)
{
struct inode *child_dir_inode = child_dir->inode;
/* 空目录只在inode->i_sectors[0]中有扇区,其它扇区都应该为空 */
int32_t block_idx = 1;
while (block_idx < 13)
{
ASSERT(child_dir_inode->i_sectors[block_idx] == 0);
block_idx++;
}
void *io_buf = sys_malloc(SECTOR_SIZE * 2);
if (io_buf == NULL)
{
printk("dir_remove: malloc for io_buf failed\n");
return -1;
}
/* 在父目录parent_dir中删除子目录child_dir对应的目录项 */
delete_dir_entry(cur_part, parent_dir, child_dir_inode->i_no, io_buf);
/* 回收inode中i_secotrs中所占用的扇区,并同步inode_bitmap和block_bitmap */
inode_release(cur_part, child_dir_inode->i_no);
sys_free(io_buf);
return 0;
}
関数宣言、変更( myos/fs/dir.h )
bool dir_is_empty(struct dir *dir);
int32_t dir_remove(struct dir *parent_dir, struct dir *child_dir);
sys_rmdir
受信パスに基づいて空のディレクトリを削除するために使用されます。原則: 最初に呼び出して、search_file
このパスに対応するディレクトリの i ノードを見つけます。それが存在し、その i ノードがディレクトリ ファイルに対応する場合は、呼び出してdir_open
i ノードをメモリに転送し、対応する構造体 dir を作成します。呼び出してdir_is_empty
ディレクトリであることを確認します。は空であり、最後にdir_remove
i ノードを削除するために呼び出します。
変更 ( myos/fs/fs.c )
/* 删除空目录,成功时返回0,失败时返回-1*/
int32_t sys_rmdir(const char *pathname)
{
/* 先检查待删除的文件是否存在 */
struct path_search_record searched_record;
memset(&searched_record, 0, sizeof(struct path_search_record));
int inode_no = search_file(pathname, &searched_record);
ASSERT(inode_no != 0);
int retval = -1; // 默认返回值
if (inode_no == -1)
{
printk("In %s, sub path %s not exist\n", pathname, searched_record.searched_path);
}
else
{
if (searched_record.file_type == FT_REGULAR)
{
printk("%s is regular file!\n", pathname);
}
else
{
struct dir *dir = dir_open(cur_part, inode_no);
if (!dir_is_empty(dir))
{
// 非空目录不可删除
printk("dir %s is not empty, it is not allowed to delete a nonempty directory!\n", pathname);
}
else
{
if (!dir_remove(searched_record.parent_dir, dir))
{
retval = 0;
}
}
dir_close(dir);
}
}
dir_close(searched_record.parent_dir);
return retval;
}
関数宣言、変更( myos/fs/fs.h )
int32_t sys_rmdir(const char *pathname);
テストコード ( myos/kernel/main.c )
#include "print.h"
#include "init.h"
#include "fs.h"
#include "stdio.h"
#include "string.h"
#include "dir.h"
int main(void)
{
put_str("I am kernel\n");
init_all();
/******** 测试代码 ********/
printf("/dir1 content before delete /dir1/subdir1:\n");
struct dir *dir = sys_opendir("/dir1/");
char *type = NULL;
struct dir_entry *dir_e = NULL;
while ((dir_e = sys_readdir(dir)))
{
if (dir_e->f_type == FT_REGULAR)
{
type = "regular";
}
else
{
type = "directory";
}
printf(" %s %s\n", type, dir_e->filename);
}
printf("try to delete nonempty directory /dir1/subdir1\n");
if (sys_rmdir("/dir1/subdir1") == -1)
{
printf("sys_rmdir: /dir1/subdir1 delete fail!\n");
}
printf("try to delete /dir1/subdir1/file2\n");
if (sys_rmdir("/dir1/subdir1/file2") == -1)
{
printf("sys_rmdir: /dir1/subdir1/file2 delete fail!\n");
}
if (sys_unlink("/dir1/subdir1/file2") == 0)
{
printf("sys_unlink: /dir1/subdir1/file2 delete done\n");
}
printf("try to delete directory /dir1/subdir1 again\n");
if (sys_rmdir("/dir1/subdir1") == 0)
{
printf("/dir1/subdir1 delete done!\n");
}
printf("/dir1 content after delete /dir1/subdir1:\n");
sys_rewinddir(dir);
while ((dir_e = sys_readdir(dir)))
{
if (dir_e->f_type == FT_REGULAR)
{
type = "regular";
}
else
{
type = "directory";
}
printf(" %s %s\n", type, dir_e->filename);
}
/******** 测试代码 ********/
while (1)
;
return 0;
}
セクション m:
タスクの作業ディレクトリ
get_parent_dir_inode_nr
サブディレクトリの i ノード インデックスを渡すと、親ディレクトリの i ノード インデックスが返されます。原則: inode_open を呼び出して、サブディレクトリに対応する i ノードをメモリにロードし、inode の i_sectors[0] アドレスを取り出し、このディレクトリ ファイルをメモリにロードし、... に対応するディレクトリ エントリを見つけ、inode を取り出します親ディレクトリに対応するインデックスを取得して返します。
変更 ( myos/fs/fs.c )
/* 获得父目录的inode编号 */
static uint32_t get_parent_dir_inode_nr(uint32_t child_inode_nr, void *io_buf)
{
struct inode *child_dir_inode = inode_open(cur_part, child_inode_nr);
/* 目录中的目录项".."中包括父目录inode编号,".."位于目录的第0块 */
uint32_t block_lba = child_dir_inode->i_sectors[0];
ASSERT(block_lba >= cur_part->sb->data_start_lba);
inode_close(child_dir_inode);
ide_read(cur_part->my_disk, block_lba, io_buf, 1);
struct dir_entry *dir_e = (struct dir_entry *)io_buf;
/* 第0个目录项是".",第1个目录项是".." */
ASSERT(dir_e[1].i_no < 4096 && dir_e[1].f_type == FT_DIRECTORY);
return dir_e[1].i_no; // 返回..即父目录的inode编号
}
get_child_dir_name
親ディレクトリの i ノード インデックスと渡されたサブディレクトリの i ノード インデックスを通じてサブディレクトリの名前を返します。原理:inode_open
親ディレクトリの i ノードをメモリにロードする呼び出しを行い、次に inode の i_sectors[] を通じて親ディレクトリのディレクトリ ファイルをメモリにロードし、ディレクトリ エントリを走査し、ディレクトリ エントリの i_nr をその i_nr と比較します。走査プロセス中のサブディレクトリの inode インデックスが等しい場合は、その名前をバッファにコピーします。
変更 ( myos/fs/fs.c )
/* 在inode编号为p_inode_nr的目录中查找inode编号为c_inode_nr的子目录的名字,
* 将名字存入缓冲区path.成功返回0,失败返-1 */
static int get_child_dir_name(uint32_t p_inode_nr, uint32_t c_inode_nr, char *path, void *io_buf)
{
struct inode *parent_dir_inode = inode_open(cur_part, p_inode_nr);
/* 填充all_blocks,将该目录的所占扇区地址全部写入all_blocks */
uint8_t block_idx = 0;
uint32_t all_blocks[140] = {
0}, block_cnt = 12;
while (block_idx < 12)
{
all_blocks[block_idx] = parent_dir_inode->i_sectors[block_idx];
block_idx++;
}
if (parent_dir_inode->i_sectors[12])
{
// 若包含了一级间接块表,将共读入all_blocks.
ide_read(cur_part->my_disk, parent_dir_inode->i_sectors[12], all_blocks + 12, 1);
block_cnt = 140;
}
inode_close(parent_dir_inode);
struct dir_entry *dir_e = (struct dir_entry *)io_buf;
uint32_t dir_entry_size = cur_part->sb->dir_entry_size;
uint32_t dir_entrys_per_sec = (512 / dir_entry_size);
block_idx = 0;
/* 遍历所有块 */
while (block_idx < block_cnt)
{
if (all_blocks[block_idx])
{
// 如果相应块不为空则读入相应块
ide_read(cur_part->my_disk, all_blocks[block_idx], io_buf, 1);
uint8_t dir_e_idx = 0;
/* 遍历每个目录项 */
while (dir_e_idx < dir_entrys_per_sec)
{
if ((dir_e + dir_e_idx)->i_no == c_inode_nr)
{
strcat(path, "/");
strcat(path, (dir_e + dir_e_idx)->filename);
return 0;
}
dir_e_idx++;
}
}
block_idx++;
}
return -1;
}
sys_getcwd
現在実行中のプロセスまたはスレッドの絶対作業パスを解決するために使用されます。原則: タスクの作業ディレクトリの i ノード インデックスを表すために、以前に cwd_inode_nr を PCB に追加しました。i ノード インデックスは現在正しいと想定されています。まず、 を呼び出してget_parent_dir_inode_nr
親ディレクトリの i ノード インデックスを取得します。このとき、元の cwd_inode_nr がサブディレクトリの i ノード インデックスになります。次に、 を呼び出してサブディレクトリ名を取得し、親ディレクトリの iget_child_dir_name
ノード インデックスを新しいサブディレクトリ インデックスに変換してから、get_parent_dir_inode_nr
親ディレクトリのインデックス... このループでは、反転された絶対パスがバッファに保存されます。たとえば、プロセスが /home/kanshan/test で動作する場合、バッファは /test/kanshan/home に保存されるため、最終的にこのパスを逆にすることができます。
変更 ( myos/fs/fs.c )
/* 把当前工作目录绝对路径写入buf, size是buf的大小.
当buf为NULL时,由操作系统分配存储工作路径的空间并返回地址
失败则返回NULL */
char *sys_getcwd(char *buf, uint32_t size)
{
/* 确保buf不为空,若用户进程提供的buf为NULL,
系统调用getcwd中要为用户进程通过malloc分配内存 */
ASSERT(buf != NULL);
void *io_buf = sys_malloc(SECTOR_SIZE);
if (io_buf == NULL)
{
return NULL;
}
struct task_struct *cur_thread = running_thread();
int32_t parent_inode_nr = 0;
int32_t child_inode_nr = cur_thread->cwd_inode_nr;
ASSERT(child_inode_nr >= 0 && child_inode_nr < 4096); // 最大支持4096个inode
/* 若当前目录是根目录,直接返回'/' */
if (child_inode_nr == 0)
{
buf[0] = '/';
buf[1] = 0;
return buf;
}
memset(buf, 0, size);
char full_path_reverse[MAX_PATH_LEN] = {
0}; // 用来做全路径缓冲区
/* 从下往上逐层找父目录,直到找到根目录为止.
* 当child_inode_nr为根目录的inode编号(0)时停止,
* 即已经查看完根目录中的目录项 */
while ((child_inode_nr))
{
parent_inode_nr = get_parent_dir_inode_nr(child_inode_nr, io_buf);
if (get_child_dir_name(parent_inode_nr, child_inode_nr, full_path_reverse, io_buf) == -1)
{
// 或未找到名字,失败退出
sys_free(io_buf);
return NULL;
}
child_inode_nr = parent_inode_nr;
}
ASSERT(strlen(full_path_reverse) <= size);
/* 至此full_path_reverse中的路径是反着的,
* 即子目录在前(左),父目录在后(右) ,
* 现将full_path_reverse中的路径反置 */
char *last_slash; // 用于记录字符串中最后一个斜杠地址
while ((last_slash = strrchr(full_path_reverse, '/')))
{
uint16_t len = strlen(buf);
strcpy(buf + len, last_slash);
/* 在full_path_reverse中添加结束字符,做为下一次执行strcpy中last_slash的边界 */
*last_slash = 0;
}
sys_free(io_buf);
return buf;
}
sys_chdir
ディレクトリへのパスを渡し、現在実行中のプロセスまたはスレッドの作業ディレクトリのインデックスを変更します。原則: この呼び出しは、search_file
受信パスを対応する i ノード インデックスに解析し、現在のタスク PCB の cwd_inode_nr を以前に返された i ノード インデックスに直接変更します。
/* 更改当前工作目录为绝对路径path,成功则返回0,失败返回-1 */
int32_t sys_chdir(const char *path)
{
int32_t ret = -1;
struct path_search_record searched_record;
memset(&searched_record, 0, sizeof(struct path_search_record));
int inode_no = search_file(path, &searched_record);
if (inode_no != -1)
{
if (searched_record.file_type == FT_DIRECTORY)
{
running_thread()->cwd_inode_nr = inode_no;
ret = 0;
}
else
{
printk("sys_chdir: %s is regular file or other!\n", path);
}
}
dir_close(searched_record.parent_dir);
return ret;
}
関数宣言、変更( myos/fs/fs.h )
char *sys_getcwd(char *buf, uint32_t size);
int32_t sys_chdir(const char *path);
テストコード ( myos/kernle/main.c )
#include "print.h"
#include "init.h"
#include "fs.h"
#include "stdio.h"
#include "string.h"
#include "dir.h"
int main(void) {
put_str("I am kernel\n");
init_all();
/******** 测试代码 ********/
char cwd_buf[32] = {
0};
sys_getcwd(cwd_buf, 32);
printf("cwd:%s\n", cwd_buf);
sys_chdir("/dir1");
printf("change cwd now\n");
sys_getcwd(cwd_buf, 32);
printf("cwd:%s\n", cwd_buf);
/******** 测试代码 ********/
while(1);
return 0;
}
セクションn:
ファイル属性を取得する
( myos/fs/fs.h ) を変更し、記録ファイル属性の構造定義を追加します
/* 文件属性结构体 */
struct stat
{
uint32_t st_ino; // inode编号
uint32_t st_size; // 尺寸
enum file_types st_filetype; // 文件类型
};
sys_stat
パスを渡し、search_file を呼び出してパスを解析し、パスに対応する i ノード インデックスを取得してから、inode_open
メモリに転送する i ノードを呼び出して、struct stat の対応するメンバーを割り当てます。
変更 ( myos/fs/fs.c )
/* 在buf中填充文件结构相关信息,成功时返回0,失败返回-1 */
int32_t sys_stat(const char *path, struct stat *buf)
{
/* 若直接查看根目录'/' */
if (!strcmp(path, "/") || !strcmp(path, "/.") || !strcmp(path, "/.."))
{
buf->st_filetype = FT_DIRECTORY;
buf->st_ino = 0;
buf->st_size = root_dir.inode->i_size;
return 0;
}
int32_t ret = -1; // 默认返回值
struct path_search_record searched_record;
memset(&searched_record, 0, sizeof(struct path_search_record)); // 记得初始化或清0,否则栈中信息不知道是什么
int inode_no = search_file(path, &searched_record);
if (inode_no != -1)
{
struct inode *obj_inode = inode_open(cur_part, inode_no); // 只为获得文件大小
buf->st_size = obj_inode->i_size;
inode_close(obj_inode);
buf->st_filetype = searched_record.file_type;
buf->st_ino = inode_no;
ret = 0;
}
else
{
printk("sys_stat: %s not found\n", path);
}
dir_close(searched_record.parent_dir);
return ret;
}
関数宣言、変更( myos/fs/fs.h )
int32_t sys_stat(const char *path, struct stat *buf);
テストコード ( myos/kernle/main.c )
#include "print.h"
#include "init.h"
#include "fs.h"
#include "stdio.h"
int main(void)
{
put_str("I am kernel\n");
init_all();
/******** 测试代码 ********/
struct stat obj_stat;
sys_stat("/", &obj_stat);
printf("/`s info\n i_no:%d\n size:%d\n filetype:%s\n",
obj_stat.st_ino, obj_stat.st_size,
obj_stat.st_filetype == 2 ? "directory" : "regular");
sys_stat("/dir1", &obj_stat);
printf("/dir1`s info\n i_no:%d\n size:%d\n filetype:%s\n",
obj_stat.st_ino, obj_stat.st_size,
obj_stat.st_filetype == 2 ? "directory" : "regular");
/******** 测试代码 ********/
while (1)
;
return 0;
}