《自己动手写操作系统 》第六章 多进程

时间:2022-08-09 20:27:14
摘要:为了实现进程切换,我们需要实现多进程。进程管理是操作系统中最重要的内容之一,在本节中,我们将实现多个进程,从而为以后的进程调度打下基础。

1.添加一个进程体


 85 void TestB() 86 {
87 int i = 0x1000;
88 while(1){
89 disp_str("B");
90 disp_int(i++);
91 disp_str(".");
92 delay(1);
93 }
94 }

在proc.h中进行申明

2.相关的变量和宏定义


显然,我们现在需要关注的是进程控制块的初始化问题,如果我们对每个进程单独都写一段代码,就没法重用初始化PCB中通用的部分。于是,我们将初始化PCB时候的特异性特征提取出来:“进程入口、堆栈、进程名称”等,将这些定义为一个结构task,用for循环来读取即可。
/include/proc.h
 42 typedef struct s_task { 43     t_pf_task   initial_eip;函数指针,指向进程入口 
44 int stacksize;
45 char name[32];
46 }TASK;
在global.c中增加进程信息数组的定义,然后在global.h中进行申明
20 PUBLIC char task_stack[STACK_SIZE_TOTAL];注意,task_stack是char数组
21
22 PUBLIC TASK task_table[NR_TASKS] = {{TestA, STACK_SIZE_TESTA, "TestA"},
23 {TestB, STACK_SIZE_TESTB, "TestB"},
24 {TestC, STACK_SIZE_TESTC, "TestC"}};我们需要在proc.h中添加对stack_size_testA等的定义,另外增加NR_TASKS


3.PCB数组初始化

 20 PUBLIC int tinix_main()
21 {
22 disp_str("-----\"tinix_main\" begins-----\n");
23
24 TASK* p_task = task_table;
25 PROCESS* p_proc = proc_table;
26 char* p_task_stack = task_stack + STACK_SIZE_TOTAL;
27 t_16 selector_ldt = SELECTOR_LDT_FIRST;
28 int i;
29 for(i=0;i<NR_TASKS;i++){
30 strcpy(p_proc->p_name, p_task->name); // name of the process
31 p_proc->pid = i; // pid
32
33 p_proc->ldt_sel = selector_ldt;
34 memcpy(&p_proc->ldts[0], &gdt[SELECTOR_KERNEL_CS >> 3], sizeof(DESCRIPTOR));
35 p_proc->ldts[0].attr1 = DA_C | PRIVILEGE_TASK << 5; // change the DPL
36 memcpy(&p_proc->ldts[1], &gdt[SELECTOR_KERNEL_DS >> 3], sizeof(DESCRIPTOR));
37 p_proc->ldts[1].attr1 = DA_DRW | PRIVILEGE_TASK << 5; // change the DPL
38 p_proc->regs.cs = ((8 * 0) & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_TASK;
39 p_proc->regs.ds = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_TASK;
40 p_proc->regs.es = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_TASK;
41 p_proc->regs.fs = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_TASK;
42 p_proc->regs.ss = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_TASK;
43 p_proc->regs.gs = (SELECTOR_KERNEL_GS & SA_RPL_MASK) | RPL_TASK;
44 p_proc->regs.eip = (t_32)p_task->initial_eip;
45 p_proc->regs.esp = (t_32)p_task_stack;
46 p_proc->regs.eflags = 0x1202; // IF=1, IOPL=1, bit 2 is always 1.
47
48 p_task_stack -= p_task->stacksize;
49 p_proc++;
50 p_task++;
51 selector_ldt += 1 << 3;
52 }
53
54 k_reenter = 0;
55
56 p_proc_ready = proc_table;
57
58 put_irq_handler(CLOCK_IRQ, clock_handler); /* 设定时钟中断处理程序 */
59 enable_irq(CLOCK_IRQ); /* 让8259A可以接收时钟中断 */
60
61 restart();
62
63
64 while(1){}
65 }

这个过程相对比较简单,不提

4.LDT

每个进程都会在GDT中拥有自己的描述符,我们在PCB的初始化中初始化了selector,但是并没有在GDT中对LDT进行描述。现在,我们在init_prot()中解决这个问题:初始化GDT中的LDT描述符。
108     /* 填充 GDT 中每个进程的 LDT 的描述符 */109     int i;
110 PROCESS* p_proc = proc_table;
111 t_16 selector_ldt = INDEX_LDT_FIRST << 3;
112 for(i=0;i<NR_TASKS;i++){
113 init_descriptor(&gdt[selector_ldt>>3],
114 vir2phys(seg2phys(SELECTOR_KERNEL_DS), proc_table[i].ldts),
115 LDT_SIZE * sizeof(DESCRIPTOR) - 1,
116 DA_LDT);
117 p_proc++;
118 selector_ldt += 1 << 3;
119 }

5.修改中断处理程序

如何将一个进程从睡眠状态转化成运行状态,无非是将esp指向PCB的开始处,然后在执行lldt指令之后恢复进程上下文。

离开内核栈,将esp指向PCB: mov esp,[p_proc_ready]

    p_proc_ready是一个指针,我们仅仅需要改变这个指针就ok。

    现在,我们创建一个中断服务程序(此处仅仅是处理服务部分,不包括上下文切换和中断控制部分)
///kernel/clock.c 20 PUBLIC void clock_handler(int irq)
21 {
22 disp_str("#");
28
29 p_proc_ready++;
30
31 if (p_proc_ready >= proc_table + NR_TASKS) {
32 p_proc_ready = proc_table;
33 }
34 }

这样,我们去修改中断处理过程:
179 180     sti181 182     push    0183     call    clock_handler;注意这句184     add esp, 4185 186     cli187 188     mov esp, [p_proc_ready] ; 离开内核栈;下面的几句,虽然与之前没有代码上的改变,但是由于clock_hander内部使用了全局变量,所以下面几句的实际数值发生了改变189     lldt    [esp + P_LDT_SEL];改变ldtr190     lea eax, [esp + P_STACKTOP]191     mov dword [tss + TSS3_S_SP0], eax192 193 .re_enter:  ; 如果(k_reenter != 0),会跳转到这里194     dec dword [k_reenter]   ; k_reenter--;


运行代码,结果如下:


6.添加一个任务(进程)的步骤

1)添加进程体
2)添加任务表task_table
3)修改proc.h中关于进程堆栈和数目的信息,将进程体函数放在其中