printf 重新实现put_【虚拟机系列】实现Fork!

7ae4ce764dc966c1edb9bb6e21b34c13.png

目前的预览图如下,已初步成型(两个月来的成果了):

724dc10a052c9bd09fc99178ae1f38f6.gif
动画演示

实验目的

实现C语言虚拟机,将代码实时编译成二进制文件,并按自定义PE格式将文件加载到虚拟机内存当中,并由虚拟机执行。

为何做这个?——陈安:【构建操作系统】Hello world!
之前所做的C语言写操作系统的代码,到后面由于某种诡异的原因会kernel panic,自己解决不了,所以呵呵,我自己做套编译器、指令集、虚拟机玩玩,向MiniOS说拜拜

虚拟机实现了:

  • 基于寄存器ax的设计
  • 多任务
  • 模拟系统调用:exec、wait、fork(可实现异步)
  • 与UI的交互
  • 用户进程空间隔离(自己不能访问其他进程的内存)
  • 代码段禁止修改
  • 由自定义interrupt指令为入口进行系统调用
  • 一切的一切基于内存池,对内存申请操作有近乎苛刻的要求,因此无论多少进程被kill了,都不会让程序内存出现多大的波动(除了AST用了shared_ptr外,其他内存在一开始就分配好了,后期不会重新分配)

特点:

  • 除了调用标准库、使用OpenGL作为渲染以外,无其他依赖,因此代码量约一万行
  • 没有使用LLVM作为语法分析工具,而是用了一套LR分析系统
  • 如果不需要OpenGL的话,那么bajdcc/clibparser可以作为一套小型的、无依赖的C语言虚拟机使用
  • 终极目标:实现编译原理、操作系统、图形学的究极融合,这也是本专栏的三大方向

动画演示中的示例代码

除了interrupt语句,其他皆符合C语言文法。

系统入口:https://github.com/bajdcc/clibparser/blob/master/code/sys/entry.cpp

enum INTR_TABLE {
    INTR_PUT_CHAR = 0,
    INTR_PUT_NUMBER = 1,
    INTR_INPUT_LOCK = 10,
    INTR_INPUT_CHAR = 11,
    INTR_RESIZE = 20,
    INTR_PUT_EXEC_CHAR = 50,
    INTR_PUT_EXEC_FILE = 51,
    INTR_PUT_EXEC_WAIT = 52,
    INTR_PUT_FORK = 55,
    INTR_SLEEP_RECORD = 100,
    INTR_SLEEP_REACH = 101,
};
// SYSTEM CALL
int put_char(char c) {
    c;
    interrupt 0;
}
int put_string(char *text) {
    while (put_char(*text++));
}
int put_int(int number) {
    number;
    interrupt 1;
}
int exec_char(char c) {
    c;
    interrupt 50;
}
int exec(char *path) {
    while (exec_char(*path++));
    interrupt 51;
}
int wait() {
    interrupt 52;
}
int sleep(int ms) {
    ms;
    interrupt 100;
    interrupt 101;
}
// WELCOME
int welcome() {
    put_string(" ________  ________        ___  ________  ________  ________     n");
    put_string("|   __  |   __        |  |   ___ |   ____|   ____    n");
    put_string("   | /   |             _|    ___|   ___|    n");
    put_string("     __      __    ___                         n");
    put_string("     |        |  _     _    ____   _____n");
    put_string("    _______ __ __ ________ _______ _______ _______n");
    put_string("    |_______||__||__||________||_______||_______||_______|n");
    put_string("nn");
    put_string("Welcome to @clibos system by bajdcc!");
    put_string("nn");
}
int main(int argc, char **argv) {
    welcome();
    exec("/usr/test_rec");    wait();
    exec("/usr/test_fork");   wait();
    exec("/usr/test_input");  wait();
    exec("/usr/test_resize"); wait();
    return 0;
}

入口代码通过调用exec函数去执行usr目录下的四大程序。

示例#1:递归测试

https://github.com/bajdcc/clibparser/blob/master/code/usr/test_rec.cpp

int put_char(char c) {
    c;
    interrupt 0;
}
int put_string(char *text) {
    while (put_char(*text++));
}
int put_int(int number) {
    number;
    interrupt 1;
}
int sleep(int ms) {
    ms;
    interrupt 100;
    interrupt 101;
}
// TEST
int fib(int i) {
    if (i > 2)
        return fib(i - 1) + fib(i - 2);
    else
        return 1;
}
int fib2(int i) {
    return i > 2 ? fib(i - 1) + fib(i - 2) : 1;
}
int fib3(int i) {
    int a[3], j;
    a[0] = a[1] = 1;
    for (j = 1; j < i; ++j)
        a[2] = a[0] + a[1], a[0] = a[1], a[1] = a[2];
    return a[0];
}
int sum(int i) {
    int s = 0;
    while (i > 0) {
        s += i--;
    }
    return s;
}
int sum2(int n) {
    int i, s;
    for (i = 1, s = 0; i <= n ; ++i) {
        s += i;
    }
    return s;
}
int sum3(int i) {
    int s = 0;
    do {
        s += i--;
    } while (i > 0);
    return s;
}
enum TEST {
    TEST_IF,
    TEST_TRIOP,
    TEST_ARRAY,
    TEST_WHILE,
    TEST_FOR,
    TEST_DO,
};
int test(int i) {
    switch (i) {
        case TEST_IF:
            put_string("fib(10):   "); put_int(fib(10));
            break;
        case TEST_TRIOP:
            put_string("fib2(10):  "); put_int(fib2(10));
            break;
        case TEST_ARRAY:
            put_string("fib3(10):  "); put_int(fib3(10));
            break;
        case TEST_WHILE:
            put_string("sum(100):  "); put_int(sum(100));
            break;
        case TEST_FOR:
            put_string("sum2(100): "); put_int(sum2(100));
            break;
        case TEST_DO:
            put_string("sum3(100): "); put_int(sum3(100));
            break;
        default:
            put_string("undefined task");
            break;
    }
    put_string("n"); sleep(100);
}
int main(int argc, char **argv) {
    int i;
    put_string("========== [#1 TEST REC] ==========n");
    put_string("Command:");
    for (i = 0; i < argc; ++i) {
        put_string(" ");
        put_string(argv[i]);
    }
    put_string("n");
    for (i = TEST_IF; i <= TEST_DO; ++i)
        test(i);
    put_string("========== [#1 TEST REC] ==========n");
    return 0;
}

主要测试循环语句,switch,数组。

代码#2:测试Fork

https://github.com/bajdcc/clibparser/blob/master/code/usr/test_fork.cpp

int fork() {
    interrupt 55;
}
int put_char(char c) {
    c;
    interrupt 0;
}
int put_string(char *text) {
    while (put_char(*text++));
}
int put_int(int number) {
    number;
    interrupt 1;
}
int wait() {
    interrupt 52;
}
int sleep(int ms) {
    ms;
    interrupt 100;
    interrupt 101;
}
int main(int argc, char **argv) {
    int i;
    put_string("========== [#2 TEST FORK] ==========n");
    put_string("Command:");
    for (i = 0; i < argc; ++i) {
        put_string(" ");
        put_string(argv[i]);
    }
    put_string("n");
    i = fork();
    if (i == -1) {
        put_string("Child: fork return "); put_int(i); put_string("n");
    } else {
        wait();
        sleep(500);
        put_string("Parent: fork return "); put_int(i); put_string("n");
        put_string("========== [#2 TEST FORK] ==========n");
    }
    return 0;
}

fork的实现在interrupt处理程序中,过程跟minios的fork一样:

  1. 新建task
  2. 拷贝四大段:代码段、数据段、栈、堆
  3. 拷贝寄存器:ax、bp、sp等
  4. 拷贝其他内容

代码#3:测试UI输入(尚未完成)

https://github.com/bajdcc/clibparser/blob/master/code/usr/test_input.cpp

int put_char(char c) {
    c;
    interrupt 0;
}
int put_string(char *text) {
    while (put_char(*text++));
}
int put_int(int number) {
    number;
    interrupt 1;
}
int input_char() {
    interrupt 11;
}
int input(char *text, int len) {
    int i, c;
    interrupt 10;
    for (i = 0; i < len && ((c = input_char()) != -1); ++i)
        text[i] = c;
    text[i++] = '0';
    return i - 1;
}
int main(int argc, char **argv) {
    int i;
    put_string("========== [#3 TEST INPUT] ==========n");
    put_string("Command:");
    for (i = 0; i < argc; ++i) {
        put_string(" ");
        put_string(argv[i]);
    }
    put_string("n");
    put_string("Input: ");
    char text[100];
    input((char *) &text, 100);
    put_string("Output: ");
    put_string((char *) &text);
    put_string("n");
    return 0;
}

代码#4:改变屏幕缓冲区大小

https://github.com/bajdcc/clibparser/blob/master/code/usr/test_resize.cpp

int resize(int rows, int cols) {
    (rows << 16) | cols;
    interrupt 20;
}
int put_char(char c) {
    c;
    interrupt 0;
}
int put_string(char *text) {
    while (put_char(*text++));
}
int put_int(int number) {
    number;
    interrupt 1;
}
int sleep(int ms) {
    ms;
    interrupt 100;
    interrupt 101;
}
int main(int argc, char **argv) {
    int i;
    put_string("========== [#4 TEST RESIZE] ==========n");
    put_string("Command:");
    for (i = 0; i < argc; ++i) {
        put_string(" ");
        put_string(argv[i]);
    }
    put_string("n");
    resize(30, 120);
    sleep(1000);
    resize(20, 20);
    sleep(1000);
    resize(30, 84);
    put_string("========== [#4 TEST RESIZE] ==========n");
    return 0;
}

Fork的实现

int cvm::fork() {
        if (available_tasks >= TASK_NUM) { //超过进程最大数量
            error("max process num!");
        }
        auto old_ctx = ctx;
        auto end = TASK_NUM + ids;
        for (int i = ids; i < end; ++i) {
            auto j = i % TASK_NUM;
            if (!(tasks[j].flag & CTX_VALID)) { //寻找空闲的PID
                tasks[j].flag |= CTX_VALID;
                ctx = &tasks[j];
                ids = (j + 1) % TASK_NUM;
                ctx->id = i;
                ctx->file = old_ctx->file;
                break;
            }
        }
#if LOG_SYSTEM
        printf("[SYSTEM] PROC | Fork: Parent= #%d, Child= #%dn", old_ctx->id, ctx->id);
#endif
        PE *pe = (PE *) ctx->file.data();//获取加载的PE文件
        // TODO: VALID PE FILE
        uint32_t pa;
        ctx->poolsize = PAGE_SIZE;
        ctx->mask = ((uint) (ctx->id << 16) & 0x00ff0000);//段掩码
        ctx->entry = old_ctx->entry;//入口地址
        ctx->stack = old_ctx->stack | ctx->mask;//栈
        ctx->data = old_ctx->data | ctx->mask;//数据段
        ctx->base = old_ctx->base | ctx->mask;//代码段
        ctx->heap = old_ctx->heap | ctx->mask;//堆
        ctx->pool = std::make_unique<memory_pool<HEAP_MEM>>();//堆内存
        ctx->flag |= CTX_KERNEL;//下面为内核级操作(地址不会重定向)
        ctx->state = CTS_RUNNING;
        /* 映射4KB的代码空间 */
        {
            auto size = PAGE_SIZE / sizeof(int);
            auto text_size = pe->text_len / sizeof(int);
            for (uint32_t i = 0, start = 0; start < text_size; ++i, start += size) {
                auto new_page = (uint32_t) pmm_alloc();
                std::copy((byte *)(old_ctx->text_mem[i]),
                          (byte *)(old_ctx->text_mem[i]) + PAGE_SIZE,
                          (byte *)new_page);//拷贝代码段数据
                ctx->text_mem.push_back(new_page);
                vmm_map(ctx->base + PAGE_SIZE * i, new_page, PTE_U | PTE_P | PTE_R); // 用户代码空间
                if (!vmm_ismap(ctx->base + PAGE_SIZE * i, &pa)) {
                    destroy(ctx->id);
                    error("fork: text segment copy failed");
                }
            }
        }
        /* 映射4KB的数据空间 */
        {
            auto size = PAGE_SIZE;
            auto data_size = pe->data_len;
            auto data_start = (char *) &pe->data;
            for (uint32_t i = 0, start = 0; start < data_size; ++i, start += size) {
                auto new_page = (uint32_t) pmm_alloc();
                ctx->data_mem.push_back(new_page);
                std::copy((byte *)(old_ctx->data_mem[i]),
                          (byte *)(old_ctx->data_mem[i]) + PAGE_SIZE,
                          (byte *)new_page);//拷贝数据段数据
                vmm_map(ctx->data + PAGE_SIZE * i, new_page, PTE_U | PTE_P | PTE_R); // 用户数据空间
                if (!vmm_ismap(ctx->data + PAGE_SIZE * i, &pa)) {
                    destroy(ctx->id);
                    error("fork: data segment copy failed");
                }
            }
        }
        /* 映射4KB的栈空间 */
        {
            auto new_page = (uint32_t) pmm_alloc();
            ctx->stack_mem.push_back(new_page);
            std::copy((byte *)(old_ctx->stack_mem[0]),
                      (byte *)(old_ctx->stack_mem[0]) + PAGE_SIZE,
                      (byte *)new_page);//拷贝栈
            vmm_map(ctx->stack, new_page, PTE_U | PTE_P | PTE_R); // 用户栈空间
            if (!vmm_ismap(ctx->stack, &pa)) {
                destroy(ctx->id);
                error("fork: stack segment copy failed");
            }
        }
        /* 映射16KB的堆空间 */
        {
            auto head = ctx->pool->alloc_array<byte>(PAGE_SIZE * (HEAP_SIZE + 2));
#if 0
            printf("HEAP> ALLOC=%pn", head);
#endif
            ctx->heapHead = head; // 得到内存池起始地址
            ctx->pool->free_array(ctx->heapHead);
            ctx->heapHead = (byte *) PAGE_ALIGN_UP((uint32_t) head);
#if 0
            printf("HEAP> HEAD=%pn", heapHead);
#endif
            memset(ctx->heapHead, 0, PAGE_SIZE * HEAP_SIZE);
            for (int i = 0; i < HEAP_SIZE; ++i) {
                auto new_page = (uint32_t) ctx->heapHead + PAGE_SIZE * i;
                ctx->heap_mem.push_back(new_page);
                std::copy((byte *)(old_ctx->heap_mem[i]),
                          (byte *)(old_ctx->heap_mem[i]) + PAGE_SIZE,
                          (byte *)new_page);拷贝堆
                vmm_map(ctx->heap + PAGE_SIZE * i, new_page, PTE_U | PTE_P | PTE_R);
                if (!vmm_ismap(ctx->heap + PAGE_SIZE * i, &pa)) {
                    destroy(ctx->id);
                    error("fork: heap segment copy failed");
                }
            }
        }
        ctx->flag &= ~CTX_KERNEL;//解除内核状态
        {
            ctx->sp = old_ctx->sp;//下面是拷贝一些寄存器
            ctx->stack = old_ctx->stack;
            ctx->data = old_ctx->data;
            ctx->base = old_ctx->base;
            ctx->heap = old_ctx->heap;
            ctx->pc = old_ctx->pc;
            ctx->ax = -1;//子进程返回-1
            ctx->bx = 0;
            ctx->bp = old_ctx->bp;
            ctx->log = old_ctx->log;
        }
        available_tasks++;
        auto pid = ctx->id;
        ctx = old_ctx;
        return pid;
    }

后续计划

  1. 模拟inode建立内存文件系统,供虚拟机调用
  2. 支持更多语法,如结构体、二维数组寻址,支持浮点运算,支持隐式类型转换,一些编译优化
  3. 完成UI输入功能,做一个shell
  4. 尝试更强大的图形界面,摆脱命令行(但可能响应有延迟),能否做成OS.js那样?
  5. 本系列的思路大部分为bajdcc/jMiniLang的C++实现
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值