目前的预览图如下,已初步成型(两个月来的成果了):
实验目的
实现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一样:
- 新建task
- 拷贝四大段:代码段、数据段、栈、堆
- 拷贝寄存器:ax、bp、sp等
- 拷贝其他内容
代码#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;
}
后续计划
- 模拟inode建立内存文件系统,供虚拟机调用
- 支持更多语法,如结构体、二维数组寻址,支持浮点运算,支持隐式类型转换,一些编译优化
- 完成UI输入功能,做一个shell
- 尝试更强大的图形界面,摆脱命令行(但可能响应有延迟),能否做成OS.js那样?
- 本系列的思路大部分为bajdcc/jMiniLang的C++实现