java开发系统内核:实现进程自动切换,再现Linus当年辉煌一刻

来源:互联网 发布:des算法 vb 编辑:程序博客网 时间:2024/05/21 11:36

更详细的讲解和代码调试演示过程,请参看视频
Linux kernel Hacker, 从零构建自己的内核

Linux 操作系统内核于1991年10月5日被Linus Benedict Torvalds 所开发,从此后,世界软件史揭开了新的帷幕,我们现在很多伟大的软件项目,都构建在Linux的基础之上,不说用于支撑谷歌,阿里,百度等巨头业务的后台大型服务器,现在风靡世界的安卓操作系统,也是构建在Linux之上的,可以说,没有当年Linux内核诞生的那一刻,就没有现在方兴未艾的移动互联网。

Linus当时完成的内核有什么特点呢?当时的内核功能简单,但最重要的是其具有了进程调度功能,它启动了两个进程,一个进程打印出一串”AAAA…”, 另一个进程打印出一串”BBBB….”

既然我们现在的系统内核已经具备了进程的切换功能,因此,我们更近一步,将现在进程间的主动切换升级为进程间的自动切换,然后每个进程各种打印一个字符,这样就可以再现当时Linus的辉煌一刻了。

如何实现进程的自动切换呢,或许你已经想到了,那就是利用时钟中断。好,让我们来开始实现相应代码,首先要改动的是multi_task.h 和 multi_task.c:

struct TSS32 {    int backlink, esp0, ss0, esp1, ss1, esp2, ss2, cr3;    int eip, eflags, eax, ecx, edx, ebx, esp, ebp, esi, edi;    int es, cs, ss, ds, fs, gs;    int ldtr, iomap;};struct SEGMENT_DESCRIPTOR {    short limit_low, base_low;    char base_mid, access_right;    char limit_high, base_high;};void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base, int ar);#define AR_TSS32        0x0089void mt_init();struct TIMER *mt_timer;void mt_taskswitch();

我们多加了两个函数接口和一个定时器指针,他们将用来实现进程间的自动切换。我们看看具体实现(multi_task.c):

static int mt_tr;void mt_init(void) {    mt_timer = timer_alloc();    timer_settime(mt_timer, 100);    mt_tr = 7*8;    return;}void mt_taskswitch() {    if (mt_tr == 7*8) {        mt_tr = 9*8;    } else {        mt_tr = 7 * 8;    }    timer_settime(mt_timer, 100);    farjmp(0, mt_tr);    return;}

mt_init 首先初始化用于进程调度的时钟对象,然后把mt_tr设置为7*8,也就是CMain函数所对应的任务门描述符的下标。

mt_taskswitch 一旦被调用,那么则把mt_tr在7*8 和 9*8 直接切换,然后通过调用farjmp 跳转到对应的任务门,下标7对应的是CMain的任务门,9对应的是task_b_main函数的任务门,在这两个任务吗间跳转,就可以实现两个进程的相互切换。

我们再看看farjmp的实现,它的实现是在内核的汇编部分:

farjmp:        jmp FAR [esp + 4]        ret

在汇编中,跳转执行分两种情况,一种叫近跳转,如果跳转不涉及到让CPU更改当前加载的代码段描述符,也就是cs寄存器所存储的描述符下标,那么我们就可以使用近跳转,如果要改变cs寄存器的内容,也就是要让CPU加载新的描述符,那么我们可以使用远跳转,就像上面的代码一样,由于我们任务切换时,要从一个任务门描述符切换到另一个任务门描述符,所以我们可以使用远跳转。

如果jmp 指令后面跟着FAR 关键字的话,那么CPU会把当前指定的内存地址处的4字节当做eip的值,然后再连续读入2字节的数据存入cs寄存器。对于调用far(0, 7*8), [esp + 4] 对应的4字节就是数值0,接下来的2字节对应的就是7*8.
所以当我们通过调用farjmp(0, 7*8) 的时候,其效果跟我们以前调用taskswitch7是一样的。

有了进程切换代码后,我们就可以在时钟中断里进行调用,进而实现进程切换了,看timer.c:

void intHandlerForTimer(char *esp) {    io_out8(PIC0_OCW2, 0x20);    timerctl.count++;    char ts = 0;    int i;    for (i = 0; i < MAX_TIMER; i++) {        if (timerctl.timer[i].flags == TIMER_FLAGS_USING) {            timerctl.timer[i].timeout--;            if (timerctl.timer[i].timeout == 0) {                timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;                fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data);                if (&timerctl.timer[i] == mt_timer) {                    ts = 1;                }            }        }        if (ts != 0) {            mt_taskswitch();        }    }    return;}

每次时钟中断发送,上面intHandlerForTimer函数会被调用,该函数先减少每个时钟的时钟片,如果有时钟超时的话,它会看看,超时的时钟是否是mt_timer,如果是的话,表示将变量ts 设置为1,后面如果判断到ts不等于0时,就表示进程切换的时间到了,便调用mt_taskswitch进行进程切换。

再看看内核C语言部分的修改:

void CMain(void) {...    fifo8_init(&timerinfo, 8, timerbuf);    timer = timer_alloc();    timer_init(timer, &timerinfo, 10);    timer_settime(timer, 100);...    for(;;) {       ....       else if (fifo8_status(&timerinfo) != 0) {           io_sti();           int i = fifo8_get(&timerinfo);           if (i == 10) {                showString(shtctl, sht_back, pos, 144, COL8_FFFFFF,                 "A");                 //switch task               //  farjmp(0, 9*8);                timer_settime(timer, 100);                pos += 8;           }       ....    }}

在CMain中,先构造了一个时间片是1秒的时钟,每当时钟超时,就在屏幕上打印一个字符”A”, 然后再次初始化新的时间片为1秒的时钟。我们看看task_b_main的实现:

void task_b_main(void) {   showString(shtctl, sht_back, 0, 160, COL8_FFFFFF, "enter task b");    struct FIFO8 timerinfo_b;    char timerbuf_b[8];    struct TIMER *timer_b = 0;    int i = 0;    fifo8_init(&timerinfo_b, 8, timerbuf_b);    timer_b = timer_alloc();    timer_init(timer_b, &timerinfo_b, 123);    timer_settime(timer_b, 100);    int pos = 0;    for(;;) {       io_cli();        if (fifo8_status(&timerinfo_b) == 0) {            io_sti();        } else {           i = fifo8_get(&timerinfo_b);           io_sti();           if (i == 123) {               showString(shtctl, sht_back, pos, 176, COL8_FFFFFF, "B");              // farjmp(0, 8*8);               timer_settime(timer_b, 100);               pos += 8;           }        }    }}

task_b_main同样构造了一个1秒的时钟,每次时钟超时的时候,就在桌面上打印出字符B,上面的代码编译后,加载到虚拟机,实现的效果如下(请通过视频观看动态效果):
这里写图片描述

字符A和B在没有任何干预的情况下,交替显示出来,当前Linus实现的第一版Linux内核就是这个效果,如今,我们重现了当前激动人心的一刻,不知此时的你是否也能体验到大牛Linus当时喜悦的心情呢?

更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:
这里写图片描述

0 0