IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> 《操作系统真象还原》第十四章 ----实现文件系统 任务繁多 饭得一口口吃路得一步步走啊(上) -> 正文阅读

[系统运维]《操作系统真象还原》第十四章 ----实现文件系统 任务繁多 饭得一口口吃路得一步步走啊(上)


相关查阅博客链接


linux 中“.”和“…”代表什么意思
C语言中extern的用法


本书中错误勘误



闲聊时刻


各位看官 又来到了每章必到的闲聊时刻了哈 哈哈
这次我想想又想聊点什么呢 感觉刚刚有很多话想说 自己编辑的时候的又写不出来什么

就是聊一下最近写这个《操作系统真象还原》相关的章节博客嘛 其实说实话 因为每一章其实我都是自己定了时间大概多久就必须完成 但其实往往每一章花的时间要比我预计的时间还是要多个一天多

除此之外呢 其实我真的觉得写这个还是相当的不轻松的 当然书籍作者是最不轻松的 不仅要把代码写完 还要以各种方式把整个实现给呈现出来
不轻松的原因就是 我还是特别希望整个操作系统十五章 每一章的博客我都尽量写的细致一点 当然最主要的原因是 写操作系统的代码确实太不好写了

细节多 而且我是属于全部都自己能思考清楚之后 我再大部分看了代码 自己再写一遍 而不是属于很纯粹的copy 你让那些copy代码的人来说一下 各种地方的实现原理 我相信是根本说不出来的 而且这个写操作系统纯copy代码的人而言 是真的我不相信有几个能写下来 哈哈

为什么我会这样说呢 因为我自己的情况就是 比如写一章花的时间是12小时 3-5小时敲代码 + 6-7小时调试各种问题 + 2-3个小时写博客 这里当然是抛开了思考时间 看书的时间
不清楚思路 不清楚代码为什么这样写的人 写出来的代码出现了问题 你让他去debug 哈哈 根本就是痴人说梦了 而且书中时不时的还会出现一些错误 不带着思考审视的眼光去看 去写代码 很多地方就会被卡死

现在也来到了倒数第二章了 我看了看内容 这十四章的实现思路总的来说呢 还是挺清晰的 后面我应该会先写思路写出来 再给代码

写了那么多 其实我当时就想说 写这个操作系统 真的得连续花时间一鼓作气写下去 如果心里面不沉淀下来 很多人中途就会放弃 而且其实写这个的这段时间 我总是感觉有一个沉沉的担子在身上 就是感觉心里面装了事情 一点都不轻松 可能到最后操作系统写完的那一天 我把最后的博客发了 并且把代码先全贴出来 看能不能放到github上面 最后再贴个链接 可能要给自己放个一天的假了 哈哈哈哈 到时候再说吧 先一步步做着走 那下面再见


部分缩写熟知


fs file system文件系统
ft file type文件类型

由于我没怎么用过. ..经常用 所以下面提一嘴
Linux.是当前目录 ..是到上一级目录


实现文件系统的原理


这个的话 还是内容挺多的 刚刚和我的好homie聊了会天 聊了个把小时 一直没心思写这部分的内容 现在静下来了 我就来写一下

我们话说在前面
我们不聊FAT文件系统 FAT文件系统不好的地方 就在于每个文件节点的末尾都用链表链接 每次寻找文件的话都只能通过链表遍历一遍才能找到

好了 说了不聊还是聊了一下 哈哈 回到我们的重点


inode构建原理


我们采用Linux文件系统的思路 什么是核心呢 inodeinode起到什么作用呢 inode基本上可以代表着文件 这个话说的有点武断 但是我们可以通过inode寻找到文件所在的磁盘位置 这是怎么寻找到的呢 我们先介绍这个 待会在介绍上一层的目录

我们对硬盘进行了分区 当然可以不分 先不聊分区 linux 中的inode 有多少inode就有多少文件 inode中有个15的整型数组 其中0-12是直接的lba块所在直接编号 这够直接吧 对的 我们通过访问inode中的数组就能直接找到文件所在的块位置 那一个块大小挺小的 15个整型数组怎么存放的到那么多块呢 比如一个文件2GB 我们怎么存放呢 对了 可能有聪明的hxd就想到了 我们的第13号可以指定一个区域的块 里面存放着块信息 这样是不是就扩大了呢 哈哈 那怎么扩的更大呢 我们第14号就指定的那个区域 里面的区域全是存放着另一个区域的块号 而那个另一个区域里面再负责指定精准块号不就行了吗 哈哈 这样其实就相当多了 依次类推即可

对了 再额外提一嘴 inode里面除了存放文件具体存储在哪些块 还存放着 文件日期 访问权限 大小 当然还有编号 除了大小 编号 其他的都是比较成熟的文件系统构建的 我们的小型系统就不构建这些啦 哈哈

inode文件并不关心里面装的是什么类型 并且里面也没有说明 但是这和我们平时的认识就冲突了 我们的文件有各种类型 比如在windos下的 mp4 jpg docx 那我们访问的时候怎么知道文件是什么类型呢 哈哈 而且我们平时访问文件的时候也根本没有出现inode 我们又是怎么访问到的呢 这又要引出下面一个概念了 目录 各位看官往下看


目录构建原理


站在inode的上一层 我的理解就是目录 比如我们的windows 我们与文件的交互 都是利用图标与文件名 对吧 不存在什么inode编号 如果我们访问每个文件都是采用输入inode编号来访问的 那也是真的太烦人了吧 哈哈

目录是干什么的呢 我们与文件交互都是利用文件名 而且我们也会给文件命名 文件系统是以一种树的类型建立起来的 我们访问一个文件 如果目录项里面的文件名和我们的文件名匹配 并且目录项里面存放着的有该文件的inode编号 以及文件类型 我们不是可以把相关文件交给相关文件处理文件处理啦 那我们上面的问题不是迎刃而解了 是吧

注意 inode可不管你文件类型是什么 这叫给目录项来管理 但是我们访问目录找到指定的文件 是一级一级的寻找 我们是不是必须要把根目录给固定下来 找inode首先需要先找目录 找到对应的inode编号 再通过inode编号找到具体的inode 再通过具体的inode访问到对应的块

那找目录 我们从哪里开始找呢 对了 就是根目录 我们必须要把根目录固定下来 不然每次我们都不知道在哪里找目录 而目录文件也是文件类型的一种 找文件的话又要通过inode找 找inode又要通过目录项来找 这样的话进入死循环了 怎么办

就如我说的那样 固定 固定下来 我们必须要把最底层的东西固定下来 我们的第一个inode固定成根目录的专属inode 每一次找根目录 直接找固定下来的根目录位置 那我们怎么找固定根目录位置呢 哈哈 这又要引入一个下面的一个概念了 超级块


超级块构建思路


书上我记得有一句话说的特别好 就是其实很多在上面的东西 都必须要依靠最底层最固定的东西 仔细想了一想是这个样子的
都写到接近尾声了 我现在都还能记得 我们第一步写mbr的时候 cpu读入的时候 不都是确定了必须是硬盘的第一块扇区吗 就像mbr也必须是确定要512字节的 很多东西都是必须要确定下来 后面的东西才能依次开展

超级块 是干什么的呢 我们既然要分区 那我们很多地方的起始位置 或者 存放位置是不是得固定下来 比如inode数组的位置 inode数组由位图来管理 空闲块也由位图来管理 这些位图位置是不是得放好了之后得存在超级块里面呢

那当然 超级块的位置肯定是确定好了 我们放在哪里呢 哈哈 这又得说 我们的引导块是放在除去第一个扇区后面的 我把书上的原图拍下来是给弄在这里 方便各位学习

哈哈 是不是特别贴心

在这里插入图片描述


然后超级块里面 也是存放着该分区的各种信息 很多很多 这里也可以贴一下 我就懒得说那么多了 哈哈 当然这是我们的小型操作系统所要用的 其他的都很少

在这里插入图片描述


总得来说就是那么多了 代码里面我可是满满的注释 超级详细 哈哈 直接上代码咯


创建文件系统


编写完的super_block.h


路径fs/super_block.h

#ifndef __FS_SUPER_BLOCK_H
#define __FS_SUPER_BLOCK_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;	//块位图本身起始扇区lba地址
    uint32_t block_bitmap_sects;	//块位图所占的扇区数
    
    uint32_t inode_bitmap_lba;	//inode位图本身起始扇区lba地址
    uint32_t inode_bitmap_sects;	//inode位图所占的扇区数
    
    uint32_t inode_table_lba;		//inode数组本身起始扇区lba地址
    uint32_t inode_table_sects;	//inode数组所占扇区数
    
    uint32_t data_start_lba;		//存放数据区开始的第一个扇区号
    uint32_t root_inode_no;		//根目录所在的inode号
    uint32_t dir_entry_size;		//目录项大小
    
    uint8_t pad[460];			//13 * 4 + 460 = 512 为了后面位图起始位置一些列方便
}; __attribute__ ((packed));

#endif

编写完的inode.h


路径fs/inode.h

#ifndef __FS_INODE_H
#define __FS_INODE_H

#include "list.h"

struct inode
{
    uint32_t i_no;	     		//inode 编号
    uint32_t i_size;	     		//文件大小 或者 目录项总大小 inode不管是什么文件的
    
    uint32_t i_open_cnts;   		//记录此文件被打开的次数
    bool write_deny;	     		//写文件不能并行
    
    uint32_t i_sectors[13]; 		//这里只实现了一级简介块 12为一级简介块指针 0-11直接是inode编号
    struct list_elem inode_tag;	//从硬盘读取速率太慢 此list做缓冲用 当第二次使用时如果list中有
    					//直接通过list_elem得到inode而不用再读取硬盘
};

#endif

编写完的dir.h


#ifndef __FS_DIR_H
#define __FS_DIR_H

#define MAX_FILE_NAME_LEN	16 //最长16个字
struct dir			//在内存中用的目录结构
{
    struct inode* inode;	//指向已经打开的inode指针
    uint32_t dir_pos;		//目录偏移位置
    uint8_t  dir_buf[512];	//目录的数据缓冲
};

struct dir_entry			//目录项
{
    char filename[MAX_FILE_NAME_LEN];	//16个字的名字
    uint32_t i_no;			//inode编号
    enum file_types f_type;		//文件类型 由此看是目录还是文件类型
};

#endif

编写完的fs.h


路径fs/fs.h

#ifndef __FS_FS_H
#define __FS_FS_H
#include "ide.h"


#define MAX_FILES_PER_PART     4096  //每个扇区最大支持文件数
#define BITS_PER_SECTOR	4096  //每扇区的位数 512字节 * 8
#define SECTOR_SIZE		512   //每扇区的字节数 512字节
#define BLOCK_SIZE	SECTOR_SIZE   //块字节大小 我们这里一块 == 一扇区

enum file_types 
{
    FT_UNKNOWN,	//未知文件类型
    FT_REGULAR,	//普通文件类型
    FT_DIRECTORY	//目录文件类型
};

void partition_format(struct disk* hd,struct partition* part);
bool mount_partition(struct list_elem* pelem,int arg);
void filesys_init(void);

#endif

编写完的fs.c


#include "fs.h"
#include "stdint.h"
#include "global.h"
#include "../device/ide.h"
#include "inode.h"
#include "dir.h"
#include "super_block.h"
#include "stdio-kernel.h"
#include "string.h"
#include "debug.h"
#include "list.h"

struct partition* cur_part;	//默认操作分区

void partition_format(struct disk* hd,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);  //inode位图占的块数
    										       //inode数组所占的块数
    uint32_t inode_table_sects = DIV_ROUND_UP((sizeof(struct inode) * MAX_FILES_PER_PART),SECTOR_SIZE);
    
    //注意这里的used_sects 肯定是不准确 差了那么一点点的 因为还没有包含block_bitmap_sects 但是为了简单处理 要先得到free_sects才能推  所以到后面block_bitmap_sects 要除两次
    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 = DIV_ROUND_UP(free_sects,BITS_PER_SECTOR);	//一位一块
    uint32_t block_bitmap_bit_len = free_sects - block_bitmap_sects;	//再减去block_bitmap的
    block_bitmap_sects = DIV_ROUND_UP(block_bitmap_bit_len,BITS_PER_SECTOR);
    
    struct super_block sb;	        		//利用栈来初始化超级块 我们的栈此刻在
    sb.magic         = 0x23333333;			//魔数
    sb.sec_cnt       = part->sec_cnt; 		//该分区总扇区数
    sb.inode_cnt     = MAX_FILES_PER_PART;		//该分区总inode数
    sb.part_lba_base = part->start_lba;		//该分区lba起始扇区位置
    
    // 引导块 超级块 空闲块位图 inode位图 inode数组 根目录 空闲块区域
    //挨着挨着顺序赋值即可
    sb.block_bitmap_lba   = part->start_lba + boot_sector_sects + super_block_sects;
    sb.block_bitmap_sects = block_bitmap_sects;
    
    sb.inode_bitmap_lba   = sb.block_bitmap_lba + block_bitmap_sects;
    sb.inode_bitmap_sects = inode_bitmap_sects;
    
    sb.inode_table_lba    = sb.inode_bitmap_lba + inode_bitmap_sects;
    sb.inode_table_sects  = inode_table_sects;
    
    sb.data_start_lba     = sb.inode_table_lba + inode_table_sects;
    sb.root_inode_no	   = 0;			//根目录inode起始编号 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);   
    
    //把元信息挨个挨个写进硬盘
    ide_write(hd,part->start_lba + boot_sector_sects,&sb,super_block_sects);
    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 >= inode_table_sects) ? buf_size : inode_table_sects) * SECTOR_SIZE;
    //申请缓冲空间 给元信息腾空间 设置成uint8_t* 原因是 先弄块位图的初始化
    uint8_t* buf = (uint8_t*)sys_malloc(buf_size);
    
    /* 初始化块位图了 */
    buf[0] |= 0;
    uint8_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); //先除余数 算出来多少字节空的
    
    //处理字节 把可能多的一字节全部置成1 这几步处理的很细节阿
    memset(&buf[block_bitmap_last_byte],0xff,last_size);	 //全部置1 保证不会被使用
    
    //处理最后的位 有效位变成0 用~来处理 真的很妙
    uint8_t bit_idx = 0;
    while(bit_idx <= block_bitmap_last_bit)
    	buf[block_bitmap_last_byte] &= ~(1 << (bit_idx++));	//有效位
    
    //把位图元信息给写到硬盘中 块位图的部分就结束了 还有inode位图 inode数组等着我们
    ide_write(hd,sb.block_bitmap_lba,buf,sb.block_bitmap_sects);
    
    /*初始化inode位图了*/
    memset(buf,0,buf_size);
    buf[0] |= 0x1;	                                          //第一个inode用于存根目录
    ide_write(hd,sb.inode_bitmap_lba,buf,sb.inode_bitmap_sects); //第一个inode初始化在后面
    
    /*初始化inode数组了*/
    memset(buf,0,buf_size);
    struct inode* i = (struct inode*)buf;			//先初始化第一个inode 根目录所在的
    i->i_size = sb.dir_entry_size * 2;			//. 和 .. 
    i->i_no   = 0;
    i->i_sectors[0]  = sb.data_start_lba;			//根目录所在扇区就是最开始的第一个扇区
    
    ide_write(hd,sb.inode_table_lba,buf,sb.inode_table_sects);
    
    /*写根目录文件进入 第一个扇区了*/
    memset(buf,0,buf_size);
    struct dir_entry* p_de = (struct dir_entry*)buf;
    
    memcpy(p_de->filename,",",1);				//名称
    p_de->i_no = 0;						//根目录. inode仍然是自己
    p_de->f_type = FT_DIRECTORY;
    p_de++;							//移动到下一条目录项
    
    p_de->i_no = 0;						//根目录的父目录仍然是自己 因为自己是固定好的 根基
    p_de->f_type = FT_DIRECTORY;
    
    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);						//临时借用的 现在得还回去了 
}

//除了挂载 还需要在内存中把超级块指针 块位图 i结点位图 i结点指针给初始化赋值了 方便使用
bool mount_partition(struct list_elem* pelem,int arg)
{
    char* part_name = (char*)arg;
    struct partition* part = elem2entry(struct partition,part_tag,pelem);//得到分区指针 partition*
    if(!strcmp(part->name,part_name))					   //字符串相匹配
    {
    	cur_part = part;						   //赋值指针
    	struct disk* hd = cur_part->my_disk;
    	
    	struct super_block* sb_buf = (struct super_block*)sys_malloc(SECTOR_SIZE);
    	if(sb_buf == NULL)
    	    PANIC("alloc memory failed!");
    	
    	memset(sb_buf,0,SECTOR_SIZE);
    	ide_read(hd,cur_part->start_lba + 1,sb_buf,1);
    	
    	cur_part->sb = sb_buf;
    	
    	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;
    	ide_read(hd,sb_buf->block_bitmap_lba,cur_part->block_bitmap.bits,sb_buf->block_bitmap_sects);
    	
        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;
    	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);
    	return true;	//停止循环
    	
    }
    return false;	//继续循环
}

//文件系统初始化 磁盘上搜索 如果没有则格式化分区 并创建文件系统
void filesys_init(void)
{
    uint8_t channel_no = 0,dev_no,part_idx = 0;
    struct super_block* sb_buf = (struct super_block*)sys_malloc(SECTOR_SIZE);
    
    if(sb_buf == NULL)	PANIC("alloc memory failed!");
    printk("searching filesysteam......\n");
    while(channel_no < channel_cnt)
    {
    	dev_no = 1;
    	while(dev_no < 2)
    	{
    	    if(!dev_no)	//跳过hd60M.img主盘
    	    {
    	    	++dev_no;	
    	    	continue;
    	    }
    	    struct disk* hd = &channels[0].devices[1];		//得到硬盘指针
    	    struct partition* part = hd->prim_parts;				//先为主区创建文件系统
    	    while(part_idx < 12)		//4个主区 + 8个逻辑分区
    	    {
    	    	if(part_idx == 4)
    	    	    part = hd->logic_parts;	
    	    	if(part->sec_cnt != 0)		//分区存在 如果没有初始化 即所有成员都为0
    	    	{
    	    	    memset(sb_buf,0,SECTOR_SIZE);
    	    	    ide_read(hd,part->start_lba +1,sb_buf,1);	//读取超级块的扇区
    	    	    
    	    	    if(sb_buf->magic != 0x23333333)			//还没有创建文件系统
    	    	    {
    	    	    	printk("formatting %s's partition %s......\n",\
    	    	    	hd->name,part->name);
    	    	    	partition_format(hd,part);
    	    	    }
    	    	    else
    	    	    	printk("%s has filesystem\n",part->name);
    	    	}
    	    	++part_idx;
    	    	++part;	//到下一个分区看
    	    }
    	    ++dev_no;		//切换盘号
    	}
    	++channel_no;		//增加ide通道号
    }
    sys_free(sb_buf);
    char default_part[8] = "sdb1";	//参数为int 4字节字符串指针传的进去
    list_traversal(&partition_list,mount_partition,(int)default_part);
}

修改后的ide.h


增加下面三条

extern uint8_t channel_cnt;
extern struct ide_channel channels[2];
extern struct list partition_list;

修改后的makefile


BUILD_DIR = ./build
ENTRY_POINT = 0xc0001500
AS = nasm
CC = gcc
LD = ld
LIB = -I lib/ -I lib/kernel/ -I lib/user/ -I kernel/ -I device/ 
ASFLAGS = -f elf
CFLAGS = -Wall -m32 -fno-stack-protector $(LIB) -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes
LDFLAGS =  -m elf_i386 -Ttext $(ENTRY_POINT) -e main -Map $(BUILD_DIR)/kernel.map
OBJS = $(BUILD_DIR)/main.o $(BUILD_DIR)/init.o $(BUILD_DIR)/interrupt.o \
      $(BUILD_DIR)/timer.o $(BUILD_DIR)/kernel.o $(BUILD_DIR)/print.o $(BUILD_DIR)/switch.o \
      $(BUILD_DIR)/debug.o $(BUILD_DIR)/string.o $(BUILD_DIR)/memory.o \
      $(BUILD_DIR)/bitmap.o $(BUILD_DIR)/thread.o $(BUILD_DIR)/list.o \
      $(BUILD_DIR)/sync.o $(BUILD_DIR)/console.o $(BUILD_DIR)/keyboard.o \
      $(BUILD_DIR)/ioqueue.o $(BUILD_DIR)/tss.o $(BUILD_DIR)/process.o \
      $(BUILD_DIR)/syscall-init.o $(BUILD_DIR)/syscall.o $(BUILD_DIR)/stdio.o \
      $(BUILD_DIR)/stdio-kernel.o $(BUILD_DIR)/ide.o $(BUILD_DIR)/fs.o
      
##############     c代码编译     ###############
$(BUILD_DIR)/main.o: kernel/main.c lib/kernel/print.h \
        lib/stdint.h kernel/init.h lib/string.h kernel/memory.h \
        thread/thread.h kernel/interrupt.h device/console.h \
        device/keyboard.h device/ioqueue.h userprog/process.h \
        lib/user/syscall.h userprog/syscall-init.h lib/stdio.h \
        lib/kernel/stdio-kernel.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/init.o: kernel/init.c kernel/init.h lib/kernel/print.h \
        lib/stdint.h kernel/interrupt.h device/timer.h kernel/memory.h \
        thread/thread.h device/console.h device/keyboard.h userprog/tss.h \
        userprog/syscall-init.h device/ide.h fs/fs.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/interrupt.o: kernel/interrupt.c kernel/interrupt.h \
        lib/stdint.h kernel/global.h lib/kernel/io.h lib/kernel/print.h \
        kernel/kernel.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/timer.o: device/timer.c device/timer.h lib/kernel/io.h lib/kernel/print.h \
        kernel/interrupt.h thread/thread.h kernel/debug.h kernel/global.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/debug.o: kernel/debug.c kernel/debug.h \
        lib/kernel/print.h lib/stdint.h kernel/interrupt.h
	$(CC) $(CFLAGS) $< -o $@
	
$(BUILD_DIR)/string.o: lib/string.c lib/string.h \
	kernel/debug.h kernel/global.h
	$(CC) $(CFLAGS) $< -o $@
	
$(BUILD_DIR)/memory.o: kernel/memory.c kernel/memory.h \
	lib/stdint.h lib/kernel/bitmap.h kernel/debug.h lib/string.h kernel/global.h \
	thread/sync.h thread/thread.h lib/kernel/list.h kernel/interrupt.h
	$(CC) $(CFLAGS) $< -o $@
	
$(BUILD_DIR)/bitmap.o: lib/kernel/bitmap.c lib/kernel/bitmap.h kernel/global.h \
	lib/string.h kernel/interrupt.h lib/kernel/print.h kernel/debug.h
	$(CC) $(CFLAGS) $< -o $@
	
$(BUILD_DIR)/thread.o: thread/thread.c thread/thread.h \
	lib/stdint.h lib/string.h kernel/global.h kernel/memory.h \
	kernel/debug.h kernel/interrupt.h lib/kernel/print.h \
	userprog/process.h thread/sync.h
	$(CC) $(CFLAGS) $< -o $@
	
$(BUILD_DIR)/list.o: lib/kernel/list.c lib/kernel/list.h \
	kernel/interrupt.h lib/stdint.h kernel/debug.h
	$(CC) $(CFLAGS) $< -o $@
	
$(BUILD_DIR)/sync.o: thread/sync.c thread/sync.h \
	lib/stdint.h thread/thread.h kernel/debug.h kernel/interrupt.h
	$(CC) $(CFLAGS) $< -o $@
	
$(BUILD_DIR)/console.o: device/console.c device/console.h \
	lib/kernel/print.h thread/sync.h
	$(CC) $(CFLAGS) $< -o $@
	
$(BUILD_DIR)/keyboard.o: device/keyboard.c device/keyboard.h \
	lib/kernel/print.h lib/kernel/io.h kernel/interrupt.h \
	kernel/global.h lib/stdint.h device/ioqueue.h
	$(CC) $(CFLAGS) $< -o $@
	
$(BUILD_DIR)/ioqueue.o: device/ioqueue.c device/ioqueue.h \
	kernel/interrupt.h kernel/global.h kernel/debug.h
	$(CC) $(CFLAGS) $< -o $@
	
$(BUILD_DIR)/tss.o: userprog/tss.c userprog/tss.h \
	kernel/global.h thread/thread.h lib/kernel/print.h
	$(CC) $(CFLAGS) $< -o $@
	
$(BUILD_DIR)/process.o: userprog/process.c userprog/process.h \
	lib/string.h kernel/global.h kernel/memory.h lib/kernel/print.h \
	thread/thread.h kernel/interrupt.h kernel/debug.h device/console.h
	$(CC) $(CFLAGS) $< -o $@
	
$(BUILD_DIR)/syscall-init.o: userprog/syscall-init.c userprog/syscall-init.h \
	lib/user/syscall.h lib/stdint.h lib/kernel/print.h kernel/interrupt.h thread/thread.h \
	kernel/memory.h
	$(CC) $(CFLAGS) $< -o $@
	
$(BUILD_DIR)/syscall.o: lib/user/syscall.c lib/user/syscall.h 
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/stdio.o: lib/stdio.c lib/stdio.h lib/stdint.h lib/string.h lib/user/syscall.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/stdio-kernel.o: lib/kernel/stdio-kernel.c lib/kernel/stdio-kernel.h \
	lib/stdio.h device/console.h
	$(CC) $(CFLAGS) $< -o $@
	
$(BUILD_DIR)/ide.o: device/ide.c device/ide.h lib/stdint.h kernel/debug.h \
	lib/kernel/stdio-kernel.h lib/stdio.h kernel/global.h thread/sync.h \
	lib/kernel/io.h device/timer.h kernel/interrupt.h lib/kernel/list.h fs/fs.h
	$(CC) $(CFLAGS) $< -o $@
	
$(BUILD_DIR)/fs.o: fs/fs.c fs/fs.h lib/stdint.h kernel/global.h device/ide.h fs/inode.h fs/dir.h \
	fs/super_block.h lib/kernel/stdio-kernel.h lib/string.h kernel/debug.h lib/kernel/list.h
	$(CC) $(CFLAGS) $< -o $@

##############    汇编代码编译    ###############
$(BUILD_DIR)/kernel.o: kernel/kernel.S
	$(AS) $(ASFLAGS) $< -o $@

$(BUILD_DIR)/print.o: lib/kernel/print.S
	$(AS) $(ASFLAGS) $< -o $@

$(BUILD_DIR)/switch.o: thread/switch.S
	$(AS) $(ASFLAGS) $< -o $@

##############    链接所有目标文件    #############
$(BUILD_DIR)/kernel.bin: $(OBJS)
	$(LD) $(LDFLAGS) $^ -o $@

.PHONY : mk_dir hd clean all

mk_dir:
	if [ ! -d $(BUILD_DIR) ]; then mkdir $(BUILD_DIR); fi

hd:
	dd if=$(BUILD_DIR)/kernel.bin \
           of=/home/cooiboi/bochs/hd60M.img \
           bs=512 count=200 seek=9 conv=notrunc

clean:
	cd $(BUILD_DIR) && rm -f  ./*

build: $(BUILD_DIR)/kernel.bin

all: mk_dir build hd

修改后的init.c


#include "init.h"
#include "print.h"
#include "interrupt.h"
#include "../device/timer.h"
#include "memory.h"
#include "../thread/thread.h"
#include "../device/console.h"
#include "../device/keyboard.h"
#include "../userprog/tss.h"
#include "../userprog/syscall-init.h"
#include "../device/ide.h"
#include "../fs/fs.h"

/*负责初始化所有模块 */
void init_all() {
   put_str("init_all\n");
   idt_init();	     // 初始化中断
   mem_init();
   timer_init();
   thread_init();
   console_init();
   keyboard_init();
   tss_init();
   syscall_init();
   ide_init();
   filesys_init();
}

make all 验证成果


这里分两个个阶段 因为写入硬盘就直接写入了 和内存不一样
所以的话 最后一次重复的时候我们的文件系统已经创建完成 所以就直接出现sda1 has filesystem
sda5 has filesystem
sda6 has filesystem
sda7 has filesystem
sda8 has filesystem
sda9 has filesystem


第一个make all阶段(第一次给分区装载文件系统)


这里的话 我当时把filesys_init放到了ide_init的最后了 就导致最后一句是ide_init done 后面就单独弄出来了
在这里插入图片描述


第二个make all阶段(第二次给分区装载文件系统+挂载)


第二次的时候不仅装好了文件系统 还弄好了挂载 如下图所示

在这里插入图片描述


创建文件系统的debug心路历程


这部分与其说是创建文件系统的debug心路历程 还不如说是悲惨史 哈哈
真的还是挺惨的 感觉表面上我博客代码写的那么多 内容写的那么充实 其实背后我写代码 make all编译不知道实现一部分功能我就需要调试编译几十次 如果出现大功能 整体做完出错了 我可能需要纠错 编译上百次 + 修改一些最基本的语法错误 总之还是真的很累人的

这次在创建文件系统最后的读取超级块的时候发生了错误
当时的时候 我心里面还没什么感觉 就觉得哪里肯定写了一点错误 但在排查了一个小时 用while(1); 断点尝试之后 发现就是在ide_read()函数出了错

我之后仔细思考了一下 如果到后面的filesys_init才出错的话
前面的ide_init也是用了函数filesys_init 但我们的读取的内容都是对的
怎么到filesys_init就出错了 此时我在想是不是线程切换出了错 此时我就倒吸了一口冷气 线程切换这个出了错那是相当不好调试的 啊 怎么办

没办法 只能一行一行尝试 断点尝试while(1) 并尝试printk一些数据 看一下是不是哪里访问出错了 在从上午的11点到下午的2点 终于有了突破 发现错误在于ide_read函数中的read_from_sector();函数

我刚开始认为是不是中断在那几次读过了之后就被屏蔽了 但在尝试了几次后 就发现没有问题 之后又想了想 不会是内存分配的问题吧 毕竟我们的内存都是malloc出来的 我一直觉得malloc那个地方细节太多了 真的调试起来难度相当相当大 最后在尝试了几次malloc都发现挺合情合理的

那问题在哪里呢 在我摸不着头脑时 我就把ide_read函数放在了sema_down的前面 结果就发现不出错了 我当时冷汗更是直冒 问题出在sema_down啊 但我事后仔细想了想 sema_down的核心就在于阻塞 而核心应该是schedule调度函数 一次偶然的尝试下 我看了看我们ide_read前我们的页表 竟然发现我们的页表 我们分配的虚拟地址竟然不在页表中 我真的服了

之后又想了想 在schedule前后 schedule前可以ide_read schedule后出错 后面进行了两次断点 发现如下图的页表
我们分配的虚拟地址是在0xc0101c0c 分配了512字节 最高地址在0xc0101e0c 还没有到0xc0101ffff

没有schedule前的页表
在这里插入图片描述
schedule后的页表
在这里插入图片描述


厚米 我是相当无语 我想了想schedule唯一和页表有关系的
只有page_dir_activate函数中的
asm volatile ("movl %0,%%cr3" : : "r"(pagedir_phy_addr) : "memory");

我之后仔细想了哈 不对啊 我们的初始化都在内核啊 只是重新装载了相同的物理地址 页表应该是相同的啊 怎么会出现不一样的页表 而且内核区域的页表一直都是相同的物理地址0x100000

之后我就很明确感觉到 问题肯定在memory.c 中的sys_mallocsys_free
因为那里面的函数 很多涉及页表位置的修改 页表项的移除与还原

于是在我一行一行的代码的对比纠察下 功夫不负有心人 终于找到了凶手
就是在sys_free的最后两行代码 少写了两行代码 其中的赋值后面还画蛇添足的加了个++
在修改后 终于一切都正常的运转起来了
这就是一次 我debug的全程的想法和心路历程 相当的折磨

其实之前写的每一章博客 基本上每一章都有这样类似的bug出现 每一次我都是心力劳损的一点点慢慢的修改 这样类似的情况还有很多很多次 只是这次我想着已经快接近尾声了 还是写点有意思的东西吧
不然表面上看上去写操作系统很简单的样子 哈哈 其实真的挺难的 各种问题和意想不到的事情发生 都需要耐住性子一点点去解决 一点点去磨

哈哈 这部分就先写到这里 毕竟是事后了 现在的心情肯定是舒畅的 那个时候肯定是比较难受的 哈哈

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2021-08-08 11:55:18  更:2021-08-08 11:56:27 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/28 3:45:51-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计