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

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

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     sti
181 
182     push    0
183     call    clock_handler;注意这句
184     add esp, 4
185 
186     cli
187 
188     mov esp, [p_proc_ready] ; 离开内核栈;下面的几句,虽然与之前没有代码上的改变,但是由于clock_hander内部使用了全局变量,所以下面几句的实际数值发生了改变
189     lldt    [esp + P_LDT_SEL];改变ldtr
190     lea eax, [esp + P_STACKTOP]
191     mov dword [tss + TSS3_S_SP0], eax
192 
193 .re_enter:  ; 如果(k_reenter != 0),会跳转到这里
194     dec dword [k_reenter]   ; k_reenter--;


运行代码,结果如下:


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

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



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值