Norlit OS —— 自制操作系统 第7章 进程调度

7  进程管理

7.1         千呼万唤始出来(不是翔出来)

千等万等,终于等到这一刻了!一般来说操作系统方面的书都会很开始就讲进程了,而我们编号都编到7了才等到这一刻。

我们创建一个process目录,添加proc.c文件和proc.asm,然后把entry.asm中的跳转至ring3的代码删除。接下来才是关键!

#include "typedef.h"

#include "proto.h"

#include "const.h"

 

typedef struct{

    u_addr gs;

    u_addr fs;

    u_addr es;

    u_addr ds;

    u_addr di;

    u_addr si;

    u_addr bp;

    u_addr ignored;

    u_addr bx;

    u_addr dx;

    u_addr cx;

    u_addr ax;

    u_addr ip;

    u_addr cs;

    u_addr flags;

    u_addr sp;

    u_addr ss;

}STACK_FRAME;

 

typedef struct{

    STACK_FRAME regs;

}PROCESS;

 

PROCESS* current_proc;

 

ASMLINKAGE void testA(){

    u8 i=0;

    u_addr tmr;

    while(1){

       for(tmr=0;tmr<10000000;tmr++)MEMORY_BARRIER();

       dispByte(i++);

       puts(".");

    }

}

 

FASTCALL PROCESS* new_process(u_addr ip, u_addr flags){

    PROCESS* proc=malloc(sizeof(PROCESS));

    proc->regs.cs=SEL_FLAT_C_USR|SSA_RPL3;

proc->regs.ds=proc->regs.es=proc->regs.fs=proc->regs.ss=proc->regs.gs=SEL_FLAT_D_USR|SSA_RPL3;

    proc->regs.ip=ip;

    proc->regs.sp=(u_addr)memory_alloc(12)+(1<<12);

    proc->regs.flags=flags;

    return proc;

}

 

FASTCALL void init_proc(){

    PROCESS* proc=new_process((u_addr)testA, 0x3202);

    current_proc=proc;

    restart();

}

代码 7.1.1 第一个进程(chapter7/a/kernel/process/proc.c)

很简单,我们首先写了一个存放寄存器的数据结构,然后我们再new_process中初始化好关键数据,最后调用restartRestart又是何方神圣呢?

restart:

    mov    esp, [current_proc]

    pop    gs

    pop    fs

    pop    es

    pop    ds

    popad

    iretd

代码 7.1.2 没有啥技术含量的restart(chapter7/a/kernel/process/proc.asm)

         的确没啥技术含量。。。和我们的STACK_FRAME比着看就会发现全部凑好了,刚好pop完就可以iretd。我们来make一下。

7.1.1 第一个进程

 

7.2         内核栈

之前也说过,操作系统和内核不可能使用同一个栈,所以我们势必需要初始化一个内核栈。我们给每个进程初始化一个内核栈,让整个PROCESS结构大小刚好达到4KB。这样一来,我们还得设置tss.esp0。现在常数越来越多,我们要千万小心。

typedef struct{

    u8 stack[KERNEL_STACK_SIZE-sizeof(STACK_FRAME)-sizeof(u_addr)];

    STACK_FRAME regs;

}PROCESS;

 

FASTCALL void init_proc(){

    PROCESS* proc=new_process((u_addr)testA, 0x3202);

    current_proc=proc;

    tss_esp0=(u_addr)current_proc+sizeof(PROCESS);

    restart();

}

代码 7.2.1 第一次启动的初始化(chapter7/a/kernel/process/proc.c)

 

restart:

    mov    ebp, [current_proc]

    lea    esp, [ebp+0x1000-4-(4+8+1+5)*4]

    pop    gs

    pop    fs

    pop    es

    pop    ds

    popad

    iretd

 

save:

    pushad

    push   ds

    push   es

    push   fs

    push   gs

    mov    ax, ss

    mov    ds, ax

    mov    es, ax

 

    push   restart

    ret

代码 7.2.2 save函数(chapter7/a/kernel/process/proc.asm)

 

可以看到,我们把整个PROCESS当作了一个内核栈。这样下来,PROCESS的总大小为KERNEL_STACK_SIZE-0x4,算上malloc用掉的4字节刚好占一个页。

我们来分析一下,当进程被中断的时候,内核栈被设置成PROCESS的最高地址,刚好是STACK_FRAME结构。CPU自动压栈sp,ss, flags, cs, ip,然后save函数压栈所有寄存器,接下来转移到restart运行,pop出所有寄存器然后iretd

现在问题就来了,save函数肯定是有中断处理程序调用的,怎么可以不返回呢!所以我们call save而不是jmp save。所以这时候,我们必须在STACK_FRAME中预留一个位子。

    u_addr ax;

=>  u_addr retaddr;

    u_addr ip;

         这样一来,我们只要在restart里面加上add esp, 4就好了。然后处理save的返回这件事。在save的末尾把ret改为

    jmp    [esp+(4+8+1)*4]

       ; 4 Seg Regs, 8 GPRs, 1 `restart`

就完事了。我们来做个实验,首先按照以前的方法打开键盘中断。在hwint01里面加入处理语句:

call       save

ret

接下来,如果一切顺利的话,按下键盘不会有任何反应。Make一下,成功了!我们的内核栈也成功的被设置了!对了,笔者发现lib.asm中的注释有一个失误,改正过来了。笔者一番心里斗争之后,还是觉得抛弃lib.asm换用C语言来增强可读性。我顺便加入了更多控制字符的支持,然后改了一下testA的进程体。

ASMLINKAGE void testA(){

    u8 i=0;

    u_addr tmr;

    puts("  ");

    while(1){

       for(tmr=0;tmr<10000000;tmr++)MEMORY_BARRIER();

       puts("\b\b");

       dispByte(i++);

    }

}

代码 7.2.3 计数器!(chapter7/a/kernel/process/proc.c)

 

7.2.1计数器

7.3         时钟中断

由于设置控制时钟中断的芯片需要操作端口,于是我们来定义一个宏。

#define io_out8(port,value) do{__asm____volatile__("outb%%al,%0"::"n"(port),"a"(value));}while(0)

         这个宏使用了gcc的内联汇编来达到读写端口的效果。由于读写端口号基本上是固定的,所以我们使用n(立即数)来修饰port。如果涉及到可变端口的话,需要额外写一个内联汇编。我们创建timer.c,并写一个初始化函数。

FASTCALL void init_timer(){

    io_out8(TIMER_MODE_PORT, RATE_GENERATOR);

    io_out8(TIMER0_PORT, (u8)(TIMER_FREQ/HZ));

    io_out8(TIMER0_PORT, (u8)((TIMER_FREQ/HZ)>>8));

}

代码 7.3.1 初始化时间(chapter7/a/kernel/process/timer.c)

其中,宏被定义在const.h里面。

#define TIMER_FREQ 1193182L

#define HZ 100

#define TIMER0_PORT 0x40

#define TIMER_MODE_PORT 0x43

#define RATE_GENERATOR 0b00110100

代码 7.3.2 (chapter7/a/kernel/include/const.h)

为什么要这么定义宏呢?我们要先来看一下PIT(可编程间隔定时器)

        每个PC机中都有一个PIT,以通过IRQ0产生周期性的时钟中断信号,作为系统定时器。当前使用最普遍的是Intel 8254 PIT芯片,它的I/O端口地址是0x40~0x43 
Intel 8254 PIT
3个计时通道,每个通道都有其不同的用途: 
1 通道0用来负责更新系统时钟。每当一个时钟滴答过去时,它就会通过IRQ0       系统 产生一次时钟中断。 (端口0x40
2 通道1通常用于控制DMACRAM的刷新。 (端口0x41
3 通道2被连接到PC机的扬声器,以产生方波信号。 (端口0x42
    
每个通道都有一个向下减小的计数器,8254 PIT的输入时钟信号的频率是1.193181MHZ,也即一秒钟输入1193181clock-cycle。每输入一个clock-cycle其时间通道的计数器就向下减1,一直减到0值。因此对于通道0而言,当计数器减到0时,PIT就向系统产生一次时钟中断,表示一个时钟滴答已经过去了。计数器是16位的,因此所能表示的最大值是65535,一秒内发生的滴答数是:1193181/(65535+1)=18.206482

我们需要的通道毫无疑问是通道0,所以我们首先写模式选择寄存器。这个寄存器的第0位控制计数器是用2进制还是用BCD码。我们当然用2进制。然后1-3位是计数器的模式选择。模式有好几种,我们选择RateGenerator模式(010)。第45位决定我们写计数器的那一个部分。计数器是16位的,如果这两位为00,则锁住计数器。如果这两位为01,则只读写高位。如果这两位为10,则只读写低位。我们选择11,表示先读写低8位,然后读写高8位。67位是计数器选择位。我们选择通道0,也就是00。这样下来,这个寄存器的值应该为00-11-010-0

         然后按照我们设置的,先写低位再写高位,就形成了我们init_timer的代码。在OCW

启用IRQ0,然后设置中断服务程序:

hwint00:

    call   save

    mov    al, EOI

    out    INT_MASTER_CTL, al

    call   irq0_handler

    ret

代码 7.3.3 hwint00(chapter7/a/kernel/interrupt.asm)

然后irq0_handler被定义在timer.c里面,仅仅自增系统时钟。

u64 wall_clock=0;

FASTCALL void irq0_handler(){

    wall_clock+=1000/HZ;

}

代码 7.3.4 好水的时钟中断(chapter7/a/kernel/process/timer.c)

我们写了这么久了,还没有一点实用的东西。我们把testA改造成计时器吧。

ASMLINKAGE void testA(){

    u8 i=0;

    u_addr tmr;

    puts("Already           seconds after boot.\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b");

    while(1){

       puts("\b\b\b\b\b\b\b\b\b\b");

       dispInt((u_addr)wall_clock/1000);

       for(tmr=0;tmr<1000000;tmr++)MEMORY_BARRIER();

    }

}

代码 7.3.5 向着实用迈出第一步(chapter7/a/kernel/process/proc.c)

 

7.4         独立页表

我们初始化了分页机制,就是为了让每个程序有独立的页表。我们在结构中加入cr3

typedefstruct{

=>  u_addr cr3;

    u_addr gs;

然后修改restart,添加切换cr3的代码。

restart:

    mov    ebp, [current_proc]

    lea    esp, [ebp+0x1000-4-(4+8+1+1+5)*4]

    pop    edx

    mov    eax, cr3

    cmp    edx, eax

    jne    .chgcr3

.nochg:

    pop    gs

    pop    fs

    pop    es

    pop    ds

    popad

    add    esp, 4

    iretd

.chgcr3:

    mov    cr3, edx

    jmp    .nochg

代码 7.4.1 看起来复杂了好多呀(chapter7/a/kernel/process/proc.asm)

我们判断了cr3是否发生了变化。如果没有发生变化的话我们就不修改cr3的值(因为cr3的修改会刷新TLB,造成CPU时间的浪费)。而且大概只有不到25%的中断会引起cr3的变化,所以我们加入了一个判断以节约时间。

    u_addr* pde=ALLOC_PAGE();

    memcpy(pde, kernel_pde, 0x1000);

proc->regs.cr3=va2pa(pde);

代码 7.4.2 初始化cr3(chapter7/a/kernel/process/proc.c)

memcpy定义在了lib.c里面,就是拷贝了一份内核页表。其实这只是为了测试能否正确切换页表,把这三句话改为一句

proc->regs.cr3=va2pa(kernel_pde)

也就是直接使用内核页表,也是没有问题的。

 

7.5         一些杂乱的小事

你肯定不希望那些初始化用的代码继续残留在内存里毒害你的内存空间。于是乎笔者想了个办法,改一下链接脚本,然后再把所有初始化用的代码写入init_text段。等我们的操作系统成型以后直接把这个段free掉就行了。说做就做,我们先定义INITIALIZER修饰符。

#define INITIALIZER __attribute__ ((section (".init_text")))

然后修改链接脚本

SECTIONS

{

. = 0x0;

.text : { *(.text) }

. = ALIGN(0x8);

.data : { *(.data) }

. = ALIGN(0x8);

.rodata : {

    *(.rodata*)

}

. = ALIGN(0x8);

.bss : {

    *(.bss)

    *(COMMON)

}

. = ALIGN(0x8);

.init_text : {

    init_text_seg_start = . ;

    *(.init_text)

}

/DISCARD/ : { *.* }

}

代码 7.5.1 链接脚本(chapter7/b/ldscript.lds)

然后给所有的初始化用的函数套上INITIALIZER修饰符。笔者还把entry.asminterrupt.asm合并,把汇编的初始化代码也放在了init_text段里面。这样下来,我们到时候只要释放init_text_seg_startKERNEL_SIZE+0x100000之间的所有空间就可以回收这部分代码了。对了,我们之前辛苦的得到的内存分布表得利用起来。

INITIALIZER FASTCALL void init_memory(){

    BootParam* bp=(BootParam*)(0x500+PAGE_OFFSET);

    {

       u32 pgFlag=bp->pgFlag;

       if(pgFlag&0b1)puts("PSE ");

       if(pgFlag&0b10)puts("PAE ");

       if(pgFlag&0b100)puts("PGE ");

       if(pgFlag&0b1000)puts("PAT ");

       if(pgFlag&0b10000)puts("PSE-36 ");

       if(pgFlag&0b100000)puts("SMEP ");

       if(pgFlag&0b1000000)puts("NX ");

       puts("\r\n");

    }

    u32 bplen=bp->len;

    puts("Total Items: ");dispInt(bp->len);

    puts("\r\nBase:     Limit:     Type:\r\n");

    ARDSItem* ai=bp->items;

    u64 max=0;

    for(;bplen>0;bplen--,ai++){

       dispInt(ai->base);putc(' ');

       dispInt(ai->limit);putc(' ');

       switch(ai->type){

       case 5:puts("Unusable\r\n");continue;

       case 6:puts("Disabled\r\n");continue;

       case 1:{

           puts("Avaliable\r\n");

           if(ai->base<LOADER_MAPPED){

              u_addrbase=ai->base,limit=ai->limit;

              if(base<KERNEL_OFFSET-PAGE_OFFSET+KERNEL_SIZE){

                  limit+=base;

                  if(limit<=KERNEL_OFFSET-PAGE_OFFSET+KERNEL_SIZE)break;

                  base=KERNEL_OFFSET-PAGE_OFFSET+KERNEL_SIZE;

                  limit-=base;

              }

              if(base+limit>LOADER_MAPPED){

                  limit=LOADER_MAPPED-base;

              }

              memory_free_nocheck(PAGE_OFFSET+base,limit);

           }

           break;

       }

       case 3:puts("ACPI\r\n");break;

       case 4:puts("NVS\r\n");break;

       default:puts("Reserved\r\n");continue;

       }

       max=ai->base+ai->limit;

    }

    puts("The Size of Your Memory: ");dispInt(max/0x100000);puts("MB\r\n");

    if(max<0x1000000){

       puts(":-( You need 16MBor higher memory.");

       io_clihlt();

    }

    if(!PAE_ENABLE&&max>0xFFFFFFFF){

       puts("Your memory isover 4GB. Please Turn on PAE if you want to use them.");

    }

    init_paging(max);

    for(bplen=bp->len,ai=bp->items;bplen>0;bplen--,ai++){

       if(ai->type==1&&ai->base<MAX_MAPPING){

           u_addrbase=ai->base,limit=ai->limit;

           if(base<LOADER_MAPPED){

              limit+=base;

              if(limit<=LOADER_MAPPED)continue;

              base=LOADER_MAPPED;

              limit-=base;

           }

           if(base+limit>MAX_MAPPING){

              limit=MAX_MAPPING-base;

           }

           memory_free_nocheck(PAGE_OFFSET+base,limit);

       }

    }

}

代码 7.5.2 复杂的函数(chapter7/b/kernel/memory/memory.c)

这部分代码用来回收可用内存,而跳过不可用的部分。在调试VMware的时候我发现VMware把内存分得比较散,于是会造成8MB不足的情况,于是我用loader里的初始页表划出了16MB的初始可用空间,然后修改了init_memory把最小内存大小改为了16MB

 

7.6         多任务

因为多任务肯定涉及到一个进程表的问题,进程肯定是分散的,我们用链表的方式来组织。我们首先写一个链表用的头文件。这个文件你可以在linux的源代码中找到相似的版本。不过在这里,我基本上使用内联函数而不是宏。这样的话可以获得强类型检查,而且还可以防止宏的多次展开导致的一些问题。

#pragma once

 

#include "typedef.h"

 

typedef struct list_head{

    struct list_head *prev, *next;

}LINKED_LIST;

 

/**

* LIST_HEAD - initial a linked list

* @name:       the name of thelist head.

* */

#define LIST_HEAD(name) struct list_head name = {&(name), &(name) }

 

/**

* list_empty - judge the linked list is empty or not

* @head:        the listhead to judge with.

* */

static INLINE int list_empty(const structlist_head *head)

{

    return head->next == head;

}

 

/**

* init_list - clean the list and make it to be a empty list

* @ptr:         thepointer of the list to empty.

* */

static INLINE void init_list(struct list_head *ptr)

{

    ptr->next=ptr;

    ptr->prev=ptr;

}

 

/**

* list_insert - insert a node into a linked list.

* @newl:        the nodeto be inserted.

* @prev:       the node before.

* @next:       the node after.

* */

static INLINE void list_insert(struct list_head *newl, structlist_head *prev, struct list_head *next)

{

    prev->next=newl;

    newl->prev=prev;

    next->prev=newl;

    newl->next=next;

}

 

/**

* list_add_(head|tail) - insert a new node to the (head|tail)of the list

* @newl:        the nodeto be inserted.

* @head:       the list tocontain the node.

* */

static INLINE void list_add_head(struct list_head *newl, structlist_head *head){list_insert(newl,head,head->next);}

static INLINE void list_add_tail(struct list_head *newl, structlist_head *head){list_insert(newl,head->prev,head);}

 

/**

* list_hide - remove a node from a list.

* @entry:      the node to beremoved or to be hide.

* */

static INLINE void list_hide(struct list_head *entry)

{

    entry->prev->next=entry->next;

    entry->next->prev=entry->prev;

}

 

/**

* list_del - remove a node from a list and empty the node.

* @entry:      the node to beremoved and empty.

* */

static INLINE void list_del(struct list_head *entry){list_hide(entry);init_list(entry);}

 

/**

* list_move_(head|tail) - move a node from the original listto the (head|tail) of the newl list

* @entry:      the node to be moved.

* @head:       the newl list tocontain the node.

* */

static INLINE void list_move_head(struct list_head *entry, structlist_head *head){list_hide(entry);list_add_head(entry,head);}

static INLINE void list_move_tail(struct list_head *entry, structlist_head *head){list_hide(entry);list_add_tail(entry,head);}

 

static INLINE void list_concat(struct list_head *dest,struct list_head *src){

    dest->prev->next=src->next;

    src->next->prev=dest->prev;

    dest->prev=src->prev;

    src->prev->next=dest;

}

 

static INLINE void list_slice(struct list_head *left,struct list_head *right){

    left->prev->next=right->next;

    right->next->prev=left->prev;

    left->prev=right;

    right->next=left;

}

 

 

/**

* container_of - cast a member of a structure out to thecontaining structure

* @ptr:        the pointerto the member.

* @type:       the type of thecontainer struct this is embedded in.

* @member:     the name of the memberwithin the struct.

* */

#define container_of(ptr, type, member)({             \

         consttypeof( ((type *)0)->member ) *__mptr = (ptr);     \

         (type *)((char *)__mptr - offsetof(type,member) );})

 

/**

* list_entry - get the struct which containing list_head

* @ptr:        pointer tothe list_head struct.

* @type:       the parentstruct type.

* @member:     the name of the list_headfield inside the struct.

* */

#define list_entry(ptr, type, member) container_of(ptr, type,member)

 

/**

* list_for_each - list every node inside a list. The usage isthe same as for inside the block.

* @entry:      the node.

* @prefetch:   the node prefetched to keep it safe.

* @list:       the list head.

* Warning:     this list is safe if andonly if you do not remove node from the list or only remove the current node orthe node previous. The safety is not guaranteed if you remove the next entry.

* */

#define list_for_each(entry, prefetch, list) \

for (entry = (list)->next, prefetch = entry->next;entry != (list); \

entry = prefetch, prefetch = entry->next)

代码 7.6.1 链表(chapter7/b/kernel/include/list.h)

对了,笔者还发现之前的malloc有个致命错误,真是对不住,这个错误会导致用free释放内存的时候出错

*((u_addr*)getit)=1<<(bit+2);   /* Store the allocated size */

去掉红色部分就行了。我还稍稍修改了一下memory_block_free使其变成公有函数。我们还把分配和释放页面的代码写成宏:

#define ALLOC_PAGE() memory_alloc(12)

#define FREE_PAGE(addr) memory_block_free((u_addr)(addr), 12)

#define ALLOC_PAGES(num) memory_alloc(12+(num))  //  Allocate 2^num

#define FREE_PAGES(addr, num)memory_block_free((u_addr)(addr), 12+(num)) //  Free2^num

         以后用起来就方便多了。

我们来看一下支持多进程以后的代码。

typedef struct{

    LINKED_LIST list;

    u_addr pid;

    u8 name[PROCESS_NAME_LENGTH];

    u8 stack[PAGE_SIZE*(1<<PROCESS_STRUCT_SIZE)-

       sizeof(STACK_FRAME)-sizeof(LINKED_LIST)-

       sizeof(u_addr)-sizeof(u8[PROCESS_NAME_LENGTH])];

    STACK_FRAME regs;

}PROCESS;

 

PROCESS* current_proc;

LINKED_LIST* process_table;

 

ASMLINKAGE void testA(){

    u_addr tmr=0;

    while(1){

       puts(current_proc->name);

       for(tmr=0;tmr<1000000;tmr++)MEMORY_BARRIER();

    }

}

 

 

FASTCALL PROCESS* new_process(u_addr ip, u_addr flags, u8 *name){

    PROCESS* proc=ALLOC_PAGES(PROCESS_STRUCT_SIZE);

    u_addr* pde=ALLOC_PAGE();

    memcpy(pde, kernel_pde, PAGE_SIZE);

    proc->regs.cr3=va2pa(pde);

    proc->regs.cs=SEL_FLAT_C_USR|SSA_RPL3;

    proc->regs.ds=proc->regs.es=

       proc->regs.fs=proc->regs.ss=

       proc->regs.gs=SEL_FLAT_D_USR|SSA_RPL3;

    proc->regs.ip=ip;

    proc->regs.sp=(u_addr)ALLOC_PAGE()+PAGE_SIZE;//TODO Alloc them in user zone instead of kernel zone in thefuture

    proc->regs.flags=flags;

    {

       LINKED_LIST *entry, *prefetch;

       PROCESS *pentry;

       u_addr pid=1;

       list_for_each(entry, prefetch, process_table){

           pentry=list_entry(entry, PROCESS, list);

           if(pentry->pid!=pid)break;

           pid++;

       }

       proc->pid=pid;

       list_add_tail(&proc->list, entry);

    }

    memcpy(proc->name, name, PROCESS_NAME_LENGTH);

    return proc;

}

 

FASTCALL void dispose_process(PROCESS* proc){

    list_hide(&proc->list);

    FREE_PAGE(proc->regs.sp-PAGE_SIZE);

    FREE_PAGE(pa2va(proc->regs.cr3));

    FREE_PAGES(proc,PROCESS_STRUCT_SIZE);

}

 

ASMLINKAGE void Init(){

    new_process((u_addr)testA, 0x3202, "A");

    new_process((u_addr)testA, 0x3202, "B");

    new_process((u_addr)testA, 0x3202, "C");

    u_addr tmr=0;

    while(1){

       (*((u8*)0xC00B8000))++;

       for(tmr=0;tmr<1000000;tmr++)MEMORY_BARRIER();

    }

}

 

extern u_addr tss_esp0;

 

FASTCALL void schedule(){

    LINKED_LIST* ll=current_proc->list.next;

    current_proc=list_entry(ll,PROCESS,list);

    tss_esp0=(u_addr)current_proc+sizeof(PROCESS);

}

 

INITIALIZER FASTCALL void init_proc(){

    PROCESS* init;

    {

       init=ALLOC_PAGES(PROCESS_STRUCT_SIZE);

       init->regs.cr3=va2pa(kernel_pde);

       init->regs.cs=SEL_FLAT_C_USR|SSA_RPL3;

       init->regs.ds=init->regs.es=

           init->regs.fs=init->regs.ss=

           init->regs.gs=SEL_FLAT_D_USR|SSA_RPL3;

       init->regs.ip=(u_addr)Init;

       init->regs.sp=(u_addr)ALLOC_PAGE()+PAGE_SIZE;

       init->regs.flags=0x3202;

       init->list.prev=&init->list;

       init->list.next=&init->list;

       init->pid=0;

       memcpy(init->name,"Init",5);

       process_table=&init->list;

    }

    current_proc=init;

    tss_esp0=(u_addr)current_proc+sizeof(PROCESS);

    init_timer();

    restart();

}

代码 7.6.2 多进程(chapter7/b/kernel/process/proc.c)

我们把常数基本上都定义在了const.c里面。由于我们之前的proc.asm考虑地比较周到,proc.asm都不用作修改(除了我们改PROCESS结构导致的偏移差,只要去掉-4就行了)。这段代码其实也不难,我们使用一个双向链表来组织进程表,而且根据PID排列。Init进程(相当于Windows的系统空闲进程)是重点,这个进程是始终存在于内存中的(不能被释放)。我们的进程表的起点实际上永远是这个Init进程。

我们还增加了name成员,用来表示进程的名字。我们还把启动其余进程的任务放在了Init函数里面。事实上,这个举措有一定的危险性,因为有可能在添加进程的过程中发生了任务切换。不过,我们首先带着这个风险来运行一下。

其实这个风险用一下io_cliio_sti就可以解决了。

接下来只要在timer.c里面加上一句

schedule();

就行了。

7.6.1 多进程

 

7.7         基于优先级的调度

不可能所有的进程都运行相同的时间。所以我们要采取优先级调度的策略。

我们不需要多么复杂的优先级调度,笔者认为,在现阶段,优先级高的进程只要能有更多的时间允许就好了。说干就干。

u_addrticks;

u_addrpriority;

在进程表里面增加这两个成员(不要忘记改stack大小哦),然后在schedule函数的开头写上一句:

current_proc->ticks=current_proc->priority;

         好了,现在时间片的初始化完成了,接下来要消耗这些时间片。我们在irq_handler0里面消耗这些时间片:

extern PROCESS* current_proc;

 

u64 wall_clock=0;

FASTCALL void irq0_handler(){

    wall_clock+=1000/HZ;

    if((--current_proc->ticks)>0)return;

    schedule();

}

7.7.1 IRQ处理(chapter7/b/kernel/process/timer.c)

         好,全部完成了。现在只要初始化的时候给进程初始化tickspriority就好了。我们不需要华丽的优先级调度,这个应该够用了。(不够用再改嘛!有什么关系!)

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值