C++手写操作系统学习笔记(四)—— GUI,多任务和内存管理

1.GUI

通过操作vga显卡,我们实现了PutPixel方法,这意味着我们具备了绘制图形界面的能力,可以在完全抽象的层面上完成我们的图形用户界面(GUI)了。不过,绘制GUI与操作系统内核的关系不大,本课程没有对窗口和桌面的实现进行深入探索,仅仅完成逻辑层面的一些简单操作。

1.GUI框架

所有图形界面都可以抽象出一个统一的基类Widget(小组件),他可能是一个窗口,一个图标或整个桌面。同时,可能同时存在多个widget,不同widiget间可能存在继承关系,所以定义CompositeWidget(混合小组件),统一管理所有Widget。
首先定义graphicscontext,图形界面上下文:
include/common/graphicscontext.h

...()
typedef myos::drivers::VideoGraphicsArray GraphicsContext;

为简便处理,仅仅将vga重命名为图形上下文(graphicscontext),因为在gui中一定要调用vga绘制(Draw函数)图形,重新命名以适应上下文情况,使代码更清晰。

include/gui/widget.h

#ifndef __MYOS__GUI__WIDGET_H
#define __MYOS__GUI__WIDGET_H

#include <common/types.h>
#include <common/graphicscontext.h>
#include <drivers/keyboard.h>
#include <drivers/mouse.h>
namespace myos
{
   namespace gui
   {
       
class Widget : public myos::drivers::KeyboardEventHandler,myos::drivers::MouseEventHandler,
{//widgit继承键盘驱动类,就无需重写键盘操作的函数
protected:
    Widget* parent;
    common::int32_t x, y, w, h;
    common::uint8_t r, g, b;
    bool Focussable;//是否可以被focus:如桌面只有一个不能被选中,而窗口可能有多个,每次只能有一个作为focus
    //硬编码的方式记录所有小组件的共有特点:大小,颜色,继承关系等
public:

    Widget(Widget* parent,
           common::int32_t x, common::int32_t y, common::int32_t w, common::int32_t h,
           common::uint8_t r, common::uint8_t g, common::uint8_t b);//构造函数传入基本属性
    ~Widget();
    
    virtual void GetFocus(Widget* widget);//
    virtual void ModelToScreen(common::int32_t &x, common::int32_t &y);//统一坐标
    virtual bool ContainsCoordinate(common::int32_t x, common::int32_t y);
    //两个组件是否是包含关系:每个组件有各自的鼠标键盘操作
    
    virtual void Draw(common::GraphicsContext* gc); 
    //画出(显示)Widget
    
    virtual void OnMouseDown(common::int32_t x, common::int32_t y, common::uint8_t button);
    virtual void OnMouseUp(common::int32_t x, common::int32_t y, common::uint8_t button);
    virtual void OnMouseMove(common::int32_t oldx, common::int32_t oldy, common::int32_t newx, common::int32_t newy);
//每个Widget对鼠标与键盘的响应效果都不同,所以在特定类中重写鼠标键盘操作。
};


class CompositeWidget : public Widget//混合组件
{
private:
    Widget* children[100];//定义所有组件
    int numChildren;
    Widget* focussedChild;//定义当前使用的(focesed)组件
    
public:
    CompositeWidget(Widget* parent,
           common::int32_t x, common::int32_t y, common::int32_t w, common::int32_t h,
           common::uint8_t r, common::uint8_t g, common::uint8_t b);
    ~CompositeWidget();            
    
    virtual void GetFocus(Widget* widget);
    virtual bool AddChild(Widget* child);//增加组件
    
    virtual void Draw(common::GraphicsContext* gc);
    
    virtual void OnMouseDown(common::int32_t x, common::int32_t y, common::uint8_t button);
    virtual void OnMouseUp(common::int32_t x, common::int32_t y, common::uint8_t button);
    virtual void OnMouseMove(common::int32_t oldx, common::int32_t oldy, common::int32_t newx, common::int32_t newy);
    
    virtual void OnKeyDown(char);
    virtual void OnKeyUp(char);
};
       
   }
}
#endif

src/gui/widget.cpp

 
#include <gui/widget.h>

using namespace myos::common;
using namespace myos::gui;


Widget::Widget(Widget* parent, int32_t x, int32_t y, int32_t w, int32_t h,
                               uint8_t r, uint8_t g, uint8_t b)
: parent(parent), x(x), y(y), w(w), h(h), r(r), g(g), b(b), Focussable(true){}

Widget::~Widget(){}
            
void Widget::GetFocus(Widget* widget)
{
    if(parent != 0)
        parent->GetFocus(widget);
}//递归获取focus

void Widget::ModelToScreen(int32_t &x, int32_t& y)
{
    if(parent != 0)
        parent->ModelToScreen(x,y);//递归的不断加上父widget的相对坐标
    x += this->x;
    y += this->y;
}//由于每个Widget都有自己的坐标,当执行draw等操作时需要统一到screen坐标上
            
void Widget::Draw(GraphicsContext* gc)
{
    int X = 0;
    int Y = 0;
    ModelToScreen(X,Y);//统一坐标
    gc->FillRectangle(X,Y,w,h, r,g,b);//画出代表这个widget的矩形
}

void Widget::OnMouseDown(int32_t x, int32_t y, uint8_t button)
{
    if(Focussable)
        GetFocus(this);
}//点击鼠标以获得focus

bool Widget::ContainsCoordinate(int32_t x, int32_t y)
{
    return this->x <= x && x < this->x + this->w
        && this->y <= y && y < this->y + this->h;
}//判断是否为包含关系

void Widget::OnMouseUp(int32_t x, int32_t y, uint8_t button){}

void Widget::OnMouseMove(int32_t oldx, int32_t oldy, int32_t newx, int32_t newy){}

//CompositeWidget
CompositeWidget::CompositeWidget(Widget* parent,
                   int32_t x, int32_t y, int32_t w, int32_t h,
                   uint8_t r, uint8_t g, uint8_t b)
: Widget(parent, x,y,w,h, r,g,b)//每次实例化一个CompositgeWidget都要初始化当前foucs的widget
{
    focussedChild = 0;
    numChildren = 0;
}//focussedChild记录被focus的widget的坐标

CompositeWidget::~CompositeWidget(){}
            
void CompositeWidget::GetFocus(Widget* widget)
{
    this->focussedChild = widget;
    if(parent != 0)
        parent->GetFocus(this);
}

bool CompositeWidget::AddChild(Widget* child)
{
    if(numChildren >= 100)
        return false;
    children[numChildren++] = child;
    return true;
}//加入一个widget

void CompositeWidget::Draw(GraphicsContext* gc)
{
    Widget::Draw(gc);
    for(int i = numChildren-1; i >= 0; --i)
        children[i]->Draw(gc);
}//绘制所有widget


void CompositeWidget::OnMouseDown(int32_t x, int32_t y, uint8_t button)
{
    for(int i = 0; i < numChildren; ++i)
        if(children[i]->ContainsCoordinate(x - this->x, y - this->y)){
            children[i]->OnMouseDown(x - this->x, y - this->y, button);
            break;
        }
}

void CompositeWidget::OnMouseUp(int32_t x, int32_t y, uint8_t button)
{
    for(int i = 0; i < numChildren; ++i)
        if(children[i]->ContainsCoordinate(x - this->x, y - this->y)){
            children[i]->OnMouseUp(x - this->x, y - this->y, button);
            break;
        }
}

void CompositeWidget::OnMouseMove(int32_t oldx, int32_t oldy, int32_t newx, int32_t newy)
{
    int firstchild = -1;
    for(int i = 0; i < numChildren; ++i)
        if(children[i]->ContainsCoordinate(oldx - this->x, oldy - this->y)){
            children[i]->OnMouseMove(oldx - this->x, oldy - this->y, newx - this->x, newy - this->y);
            firstchild = i;
            break;
        }
    for(int i = 0; i < numChildren; ++i)
        if(children[i]->ContainsCoordinate(newx - this->x, newy - this->y)){
            if(firstchild != i)
                children[i]->OnMouseMove(oldx - this->x, oldy - this->y, newx - this->x, newy - this->y);
            break;
        }
}
//在所有有关鼠标的操作中由于widget有可能重叠,要首先判断是对哪个widget的操作:首先对所有childer判断:是否有包含关系,以递归的方式找到最终要操作的widget

void CompositeWidget::OnKeyDown(char str)
{
    if(focussedChild != 0)
        focussedChild->OnKeyDown(str);
}

void CompositeWidget::OnKeyUp(char str)
{
    if(focussedChild != 0)
        focussedChild->OnKeyUp(str);    
}


2.桌面和窗口

接下来编写桌面和窗口的逻辑,他们都是继承自CompositeWidget的类,逻辑比较清晰,省略库文件部分:
src/gui/desktop.cpp

#include <gui/desktop.h>
using namespace myos;
using namespace myos::common;
using namespace myos::gui;

Desktop::Desktop(int32_t w, int32_t h,  uint8_t r, uint8_t g, uint8_t b)
:   CompositeWidget(0,0,0, w,h,r,g,b),//从原点初始化桌面
    MouseEventHandler()
{
    MouseX = w/2;
    MouseY = h/2;
}//初始化鼠标的位置

Desktop::~Desktop(){}

void Desktop::Draw(GraphicsContext* gc)
{
    CompositeWidget::Draw(gc);//画出整个桌面
    for(int i = 0; i < 4; i++){
        gc -> PutPixel(MouseX-i, MouseY, 0xFF, 0xFF, 0xFF);
        gc -> PutPixel(MouseX+i, MouseY, 0xFF, 0xFF, 0xFF);
        gc -> PutPixel(MouseX, MouseY-i, 0xFF, 0xFF, 0xFF);
        gc -> PutPixel(MouseX, MouseY+i, 0xFF, 0xFF, 0xFF);
    }//(画出)模拟鼠标:将鼠标硬编码为desktop的一部分是不太好打方法
}
            
void Desktop::OnMouseDown(uint8_t button)
{
    CompositeWidget::OnMouseDown(MouseX, MouseY, button);
}

void Desktop::OnMouseUp(uint8_t button)
{
    CompositeWidget::OnMouseUp(MouseX, MouseY, button);
}

void Desktop::OnMouseMove(int x, int y)
{
    x /= 4; y /= 4;
    //降低灵敏度
    int32_t newMouseX = MouseX + x;
    if(newMouseX < 0) newMouseX = 0;
    if(newMouseX >= w) newMouseX = w - 1
    int32_t newMouseY = MouseY + y;
    if(newMouseY < 0) newMouseY = 0;
    if(newMouseY >= h) newMouseY = h - 1;
    //同mouse.cpp中一样,处理鼠标移动时要防止鼠标移出屏幕
    
    CompositeWidget::OnMouseMove(MouseX, MouseY, newMouseX, newMouseY);
    //计算鼠标的位置并作为参数传给基类的OnMouseMove
    MouseX = newMouseX;
    MouseY = newMouseY;
}

至于window并没有实现一个真正的窗口,只是逻辑上的,仅添加一个功能:拖拽。
src/gui/window.cpp

#include <gui/window.h>

using namespace myos::common;
using namespace myos::gui;

Window::Window(Widget* parent,
            int32_t x, int32_t y, int32_t w, int32_t h,
            uint8_t r, uint8_t g, uint8_t b)
: CompositeWidget(parent, x,y,w,h, r,g,b)
{
    Dragging = false;//拖拽
}

Window::~Window(){}

void Window::OnMouseDown(int32_t x, int32_t y, uint8_t button)
{
    Dragging = button == 1;
    CompositeWidget::OnMouseDown(x,y,button);
}

void Window::OnMouseUp(int32_t x, int32_t y, uint8_t button)
{
    Dragging = false;
    CompositeWidget::OnMouseUp(x,y,button);
}

void Window::OnMouseMove(int32_t oldx, int32_t oldy, int32_t newx, int32_t newy)
{
    if(Dragging){
        this->x += newx-oldx;
        this->y += newy-oldy;
    }//如果被拖拽更新window的xy位置属性。
    CompositeWidget::OnMouseMove(oldx,oldy,newx, newy);
} 

最后,在kernelmain中运行我们的图形界面:

... ...

    Desktop desktop(320, 200,0x00,0x00,0xa8);
	//启动桌面
    DriverManager drvManager;
    KeyBoardDriver keyboard(&Interrupts,&desktop);
    drvManager.AddDriver(&keyboard);
    MouseDriver mouse(&Interrupts,&desktop);
    drvManager.AddDriver(&mouse);
    //初始化鼠标键盘
    PCI_ConnectController PCIController;
    PCIController.SelectDrivers(&drvManager,&Interrupts);
    VideoGraphicsArray vga;
    drvManager.ActivateAll();
    
    vga.SetMode(320,200,8);//设置vga模式(只有这一种模式)
    Window w1(&desktop, 10,10,20,20,0xa8,0x00,0x00);
    desktop.AddChild(&w1);
    Window w2(&desktop, 40,15,30,30,0x00,0xa8,0x00);
    desktop.AddChild(&w2);
    //初始化w1和w2两个窗口
    Interrupts.Activate();

    while(1){
        desktop.Draw(&vga);//绘制desktop
    }

可以看到,虽然屏幕一直闪烁,但确实出现了两个“窗口”,且可以“拖拽”。这就是本项目所有有关图形的内容,后续也不在启用此功能。
可以看到,desktop.Draw需要写到while循环中,意味着屏幕一直在刷新绘制desktop,导致运行非常慢而且一直闪烁,总之正如viktor所说这是非常“垃圾”的图形界面。据我观察,一般也没有人使用这种方式(越过bios)写图形界面。

2.多任务(multitasking)

多任务是相对操作系统而言的,指提供一个 CPU 来一次执行多个任务。是操作系统非常基本也非常重要的功能。多任务处理通常涉及任务之间的 CPU 切换,以便用户可以与每个程序一起协作
多任务其实就是多进程,是线程出现以前比较原始的一次执行多个任务的方式。与多线程不同,在多任务处理中,进程共享单独的内存和资源。由于多任务处理涉及任务之间的 CPU 快速切换,因此从一个用户切换到下一个用户需要更少的时间。

1.多任务实现

include/multitasking.h

...()
    struct CPUState//记录某一任务的寄存器情况,使得可以正确返回到该任务
    {
        common::uint32_t eax, ebx, ecx, edx, esi, edi, ebp;//这些值由中断处理程序处理
        /*
        common::uint32_t gs, fs, es, ds;
        */
        common::uint32_t error, eip, cs, eflags, esp, ss;//这些值由processer处理
       
    } __attribute__((packed));
    
    
    class Task//定义每一个任务
    {
    friend class TaskManager;
    private:
        common::uint8_t stack[4096]; // 4 KB,记录任务需要的寄存器中的内容
        CPUState* cpustate;//每个任务都有自己的cpustate
    public:
        Task(GDT *gdt, void entrypoint());//函数指针
        ~Task();
    };
    
    class TaskManager
    {
    private:
        Task* tasks[256];
        int numTasks;
        int currentTask;//定义当前任务,任务数,总任务数组
    public:
        TaskManager();
        ~TaskManager();
        bool AddTask(Task* task);
        CPUState* Schedule(CPUState* cpustate);//任务调度的函数
    };
}
#endif

src/multitasking.cpp

...()
Task::Task(GDT *gdt, void entrypoint())
{
    cpustate = (CPUState*)(stack + 4096 - sizeof(CPUState));
    
    cpustate -> eax = 0;
    cpustate -> ebx = 0;
    cpustate -> ecx = 0;
    cpustate -> edx = 0;

    cpustate -> esi = 0;
    cpustate -> edi = 0;
    cpustate -> ebp = 0;  
    /*
    cpustate -> gs = 0;
    cpustate -> fs = 0;
    cpustate -> es = 0;
    cpustate -> ds = 0;
    */
    // cpustate -> error = 0;    
    // cpustate -> esp = ;
    
    cpustate -> eip = (uint32_t)entrypoint;
    cpustate -> cs = gdt->CodeSegmentSelector();//cs记录代码选择子
    // cpustate -> ss = ;
    cpustate -> eflags = 0x202;
}//初始化一个任务
Task::~Task(){}

TaskManager::TaskManager(){
    numTasks = 0;
    currentTask = -1;
}

TaskManager::~TaskManager(){}

bool TaskManager::AddTask(Task* task){
    if(numTasks >= 256)
        return false;
    tasks[numTasks++] = task;
    return true;
}//添加新任务

CPUState* TaskManager::Schedule(CPUState* cpustate){
    if(numTasks <= 0)
        return cpustate;
    
    if(currentTask >= 0)
        tasks[currentTask]->cpustate = cpustate;
    
    if(++currentTask >= numTasks)
        currentTask %= numTasks;
    return tasks[currentTask]->cpustate;
}//任务调度函数:遍历依次返回task列表中每一个task都cpustate,实现多任务的效果。

2.改写中断处理程序

由于通过中断的方式实现多任务:即每次任务切换都是由中断方式实现的,要重写有关中断的内容使其可以与multitasking相关内容关联:

1.修改intruptmanager:

为了中断处理可以同样处理多任务的情况,在intterruptmanager中加入taskmanager(在interrupts.h中同步修改)
interrupts.cpp

...
InterruptManager::InterruptManager(uint16_t hardwareInterruptOffset, GDT* gdt, TaskManager* taskManager)
:picMasterCommand(0x20),
picMasterData(0x21),//可编程中断控制器
picSlaveCommand(0xA0),
picSlaveData(0xA1)
{
    this -> taskManager = taskManager;
 ... ...
uint32_t InterruptManager::DoHandleInterrupt(uint8_t interruptNumber, uint32_t esp)
{
	... ...
    if(interruptNumber == hardwareInterruptOffset){
        esp = (uint32_t)taskManager -> Schedule((CPUState*)esp);
    }//即在没有中断信号时,调用taskManager的Schedule函数获取栈指针:实现多任务
    ... ...
    return esp;
}

2.修改中断处理汇编程序:

通过中断的方式实现任务切换,故同样需要保存现场。而原本中断处理程序保存现场方式(见C++手写操作系统学习笔记(二))与每个task中的cpustate不相符,要使用统一的方式保存现场:
interruptstub.s

...
.macro HandleInterruptRequest num
.global __ZN4myos21hardwarecommunication16InterruptManager26HandleInterruptRequest\num\()Ev
__ZN4myos21hardwarecommunication16InterruptManager26HandleInterruptRequest\num\()Ev:
    movb $\num + IRQ_BASE, (interruptnumber)
    push $0 ;这里执行InterruptManager的构造函数时,由于多了一个error,故在每次执行中断时需要输入32b占位
    jmp int_bottom
.endm
... ...
int_bottom:
    pushl %ebp
    pushl %edi
    pushl %esi

    pushl %edx
    pushl %ecx
    pushl %ebx
    pushl %eax
    
    ; pushl %ds
    ; pushl %es
    ; pushl %fs
    ; pushl %gs

    pushl %esp
    push (interruptnumber)
    call __ZN4myos21hardwarecommunication16InterruptManager15handleInterruptEhj

    movl %eax, %esp

    popl %eax
    popl %ebx
    popl %ecx
    popl %edx

    popl %esi
    popl %edi
    popl %ebp
    ; popl %gs
    ; popl %fs
    ; popl %es
    ; popl %ds
    add $4,%esp;更新栈针
    ;统一保存现场和恢复现场的操作

3.测试效果

在cernel.cpp中定义两个任务,测试多任务能否正常执行:
kernel.cpp

void taskA(){
    while(true){
        printf("A");
    }
}
void taskB(){
    while(true){
        printf("B");
    }
}
... ...
extern "C" void kernelMain(void* multiboot_structrue,uint32_t magicnumber){
    GDT gdt;

    TaskManager taskManager;
    Task task1(&gdt, taskA);
    Task task2(&gdt, taskB);
    taskManager.AddTask(&task1);
    taskManager.AddTask(&task2);

	InterruptManager Interrupts(0x20,&gdt,&taskManager);//中断处理传入所有任务对象
... ...

make run后可以看到,操作系统交替执行taskA和taskB(交替打印大量A和B)。
注意: 实际上,这里由于某种未知原因,我没有得到想要的效果,出现了类似蓝屏的情况。只能copy源代码运行才成功()。原作者也提到类似的问题,可能与cpustate中cs的取值有关。

3.动态内存管理

1.动态分区分配

内存管理是现代操作系统设计中最重要和最复杂的内容之一:即对内存的划分与动态分配。现代操作系统一般采用基本分页分段以及请求分页分段(虚拟内存)的方式进行内存管理。由于我们的操作系统甚至还没有硬盘,故这里只是实现非常简单的功能。即通过双链表的方式实现一个类似动态分区分配首次适应算法的内存管理方式,主要功能为:

  • 内存空间的分配与回收
  • 存储保护:保证各进程在各自空间运行。

动态分区分配是一种连续分配管理方式,是指在进程装入内存时,根据进程实际大小动态分配内存,这里分配规则使用首次适应算法:从链首开始顺序查找,找到大小能满足要求的第一个空闲分区进行分配。

具体方法为:将整个可分配内存空间视为一个双链表,将每个内存块作为一个表项,每个表项又分为memorytrunk(记录块信息及指针的区域)和实际存储区域。初始时只有整个未分配的内存,即只有一项。每次分配和回收时,都会根据双链表增减表项的规则,相应分离或合并出新的表项。
作者选用最直接,最简单的方法实现上述过程,缺点是:每次分配/回收内存都需要搜索整个链表(时间复杂度为n),如果链表项很多会大幅降低运行速度。

2.动态分区分配首次适应算法

首先在types.h中加入存储字长的数据结构,由于是32位系统,故存储字长为32位:
types.h

...
	typedef uint32_t size_t;
...

include/memorymanagement.h

#ifndef __MYOS__MEMORYMANAGEMENT_H
#define __MYOS__MEMORYMANAGEMENT_H

#include <common/types.h>
namespace myos
{
    struct MemoryChunk
    {
        MemoryChunk *next;
        MemoryChunk *prev;
        bool allocated;
        common::size_t size;
    };//双链表存储每一个内存块,memorychunk是真正的双链表表项,记录前后指针,是否分配,以及块大小的信息
     
    class MemoryManager{   
    protected:
        MemoryChunk* first;//定义指向第一个内存块的memorychunk的指针
    public:
        static MemoryManager *activeMemoryManager;
        
        MemoryManager(common::size_t first, common::size_t size);//这里参数就对应起始块即整个可分配内存的首地址和大小
        ~MemoryManager();
        
        void* malloc(common::size_t size);
        void free(void* ptr);//内存管理中最重要的分配与回收内存函数
    };
}
//operator:运算符重载这里重新定义new和new[]
//注意:在重载 new 或 new[] 时,无论是作为成员函数还是作为全局函数,它的第一个参数必须是 size_t 类型。size_t 表示的是要分配空间的大小,对于 new[] 
//的重载函数而言,size_t 则表示所需要分配的所有空间的总和。重载函数也可以有其他参数,但都必须有默认值,并且第一个参数的类型必须是 size_t。(unsigned)
void* operator new(unsigned size);
//void* operator new[](unsigned size);

// placement new:new的特殊用法:即指定位置new一块空间
void* operator new(unsigned size, void* ptr);
//void* operator new[](unsigned size, void* ptr);

void operator delete(void* ptr);
//void operator delete[](void* ptr);

#endif

src/memorymanagement.cpp

#include <memorymanagement.h>

using namespace myos;
using namespace myos::common;

MemoryManager* MemoryManager::activeMemoryManager = 0;
MemoryManager::MemoryManager(size_t start, size_t size)
{
    activeMemoryManager = this;
    if(size < sizeof(MemoryChunk)){
        first = 0;
    }//如果空间小于
    else{
        first = (MemoryChunk*)start;
        first -> allocated = false;
        first -> prev = 0;
        first -> next = 0;
        first -> size = size - sizeof(MemoryChunk);
    }
}//构造函数初始化第一个内存块,分配大小,前后指针等信息

MemoryManager::~MemoryManager()
{
    if(activeMemoryManager == this)
        activeMemoryManager = 0;
}
        
void* MemoryManager::malloc(size_t size)
{
    MemoryChunk *result = 0;
    
    for(MemoryChunk* chunk = first; chunk != 0 && result == 0; chunk = chunk->next)
        if(chunk->size > size && !chunk->allocated)
            result = chunk;
    //首次适应算法
        
    if(result == 0) return 0;//如果没有匹配的返回0
    
    if(result->size >= size + sizeof(MemoryChunk) + 1){
        MemoryChunk* temp = (MemoryChunk*)((size_t)result + sizeof(MemoryChunk) + size);
        //如果找到的块大于新请求的块大小(至少要差一个字节),就分裂出新的内存块,其首地址就是上一块首地址+memorychunk+新分配出去的地址大小
        temp->allocated = false;
        temp->size = result->size - size - sizeof(MemoryChunk);
        temp->prev = result;
        temp->next = result->next;
        if(temp->next != 0)
            temp->next->prev = temp;
     	//更新分裂出的新表项的信息
        result->size = size;
        result->next = temp;
        //更新新分配的表项的信息
    }
    
    result->allocated = true;
    return (void*)(((size_t)result) + sizeof(MemoryChunk));//返回值为新分配内存块的末尾地址
}

void MemoryManager::free(void* ptr)//void*指针:可以是任何类型的指针/void*指针只有进行强制类型转换才可以正常取值
{
    MemoryChunk* chunk = (MemoryChunk*)((size_t)ptr - sizeof(MemoryChunk));
    //由此看出ptr是要free内存块的首地址
    chunk -> allocated = false;
    
    if(chunk->prev != 0 && !chunk->prev->allocated){
        chunk->prev->next = chunk->next;
        chunk->prev->size += chunk->size + sizeof(MemoryChunk);
        if(chunk->next != 0)
            chunk->next->prev = chunk->prev;
  
        chunk = chunk->prev;
    }//若前面还有节点,进行删除节点的相应操作
    
    if(chunk->next != 0 && !chunk->next->allocated){
        chunk->size += chunk->next->size + sizeof(MemoryChunk);//更新新节点大小
        chunk->next = chunk->next->next;
        if(chunk->next != 0)
            chunk->next->prev = chunk;
    }//若后面还有节点,进行删除节点的相应
    
}
void* operator new(unsigned size)
{
    if(myos::MemoryManager::activeMemoryManager == 0)
        return 0;
    return myos::MemoryManager::activeMemoryManager->malloc(size);
}

void* operator new(unsigned size, void* ptr){
    return ptr;
}

void operator delete(void* ptr){
    if(myos::MemoryManager::activeMemoryManager != 0)
        myos::MemoryManager::activeMemoryManager->free(ptr);
}//这里new和delete就是调用malloc和free函数,没有其他操作

3.测试效果

kernel.cpp

... ...
    GlobalDescriptorTable gdt;
    
    uint32_t* memupper = (uint32_t*)(((size_t)multiboot_structure) + 8);
    //这里根据grub引导程序的规则,从参数multiboot_structure中获取可分配内存的大小(单位kB)
    size_t heap = 10*1024*1024;//以硬编码的方式设定可分配内存的起始地址
    MemoryManager memoryManager(heap, (*memupper)*1024 - heap - 10*1024);
    
    printf("heap: 0x");
    printfHex((heap >> 24) & 0xFF);
    printfHex((heap >> 16) & 0xFF);
    printfHex((heap >> 8 ) & 0xFF);
    printfHex((heap      ) & 0xFF);
    //这里打印了heap(即可分配内存)的首地址,由于printfHex每次只能打印两个16进制数所以要执行4次
    void* allocated = memoryManager.malloc(1024);
    printf("\nallocated: 0x");
    printfHex(((size_t)allocated >> 24) & 0xFF);
    printfHex(((size_t)allocated >> 16) & 0xFF);
    printfHex(((size_t)allocated >> 8 ) & 0xFF);
    printfHex(((size_t)allocated      ) & 0xFF);
    printf("\n");
    //分配1024空间后,打印未被分配空间的地址。
... ...

make run后可以看到正确输出了heap与nalloated的值(相差1024)。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值