随笔-80  评论-24  文章-0  trackbacks-0
该文件是进程实现以及启动第一个进程的主要实现部分。
还是从文件开头说起,先看代码:

 1 #include <string.h>
 2 #include <asm/system.h>
 3 #include <winixj/mm.h>
 4 #include <winixj/int.h>
 5 #include <winixj/mailbox.h>
 6 #include <winixj/process.h>
 7 #include <winixj/schedule.h>
 8 
 9 //所有进程所拥有的内核态堆栈都放到一起
10 //进程的内核态堆栈的作用主要是用来保存
11 //进程被打断时的现场信息
12 KSTACK kstack_list[NR_PROCS];
13 
14 /*********************************************
15  * 这只是权宜之计,因为我们还没有实现内存管理
16  * 所以暂时使每个进程都有一个独立的用户态堆栈
17  *********************************************/
18 //所有进程所拥有的用户太堆栈也都放到一起
19 USTACK ustack_list[NR_PROCS];
20 
21 //进程链表,保存所有的进程控制块信息
22 //这是全局变量
23 PROCESS proc_list[NR_PROCS];
24 
25 PROCESS *current; //指向当前正在运行的进程
26 

先看几个变量的含义:
首先是kstack_list和ustack_list,在该博文中已经说到了,每个进程都有自己单独的用户态栈和核心态栈,在这里的WinixJ的实现目前比较幼稚,就是申请一个数组,将所有的用户态栈放到一起成为数组,将所有的核心态放到一起组成数组,这样做是有严重问题的:每个进程的数据段和堆栈段是分开放置的,数据段在进程地址空间中,而堆栈段则在内核空间中,这样极不利于进程之间的隔离与保护,但是谁让我们是miniOS呢,这样实现至少现在也能用,等实现了分页机制以及内存管理后再改进也不迟。
然后是proc_list数组,该数组是整个进程的核心,它其中的每一项都是一个进程的心脏---进程控制块。先看进程控制块的定义,看它有哪些字段组成:

 1 //目前的进程控制块结构不妨尽量简单
 2 //因为我们要实现的是简陋的进程,请忍受这一点
 3 typedef struct task_struct
 4 {
 5     uint32    state;                    //进程状态
 6     uint32    priority;                //进程优先级
 7     uint32    time_slices;            //进程的剩余时间片
 8     uint32    pid;                    //进程的pid
 9     uint32    ppid;                    //父进程的pid
10 #define PROC_NAME_LEN    32            //进程名的最大长度为32字节
11     char    name[PROC_NAME_LEN];
12     uint32    *kstack;                //指向内核态堆栈顶
13     uint32    *ustack;                //指向用户态堆栈顶
14     uint32    running_time;            //进程一共运行了的时间
15     struct seg_struct ldt[3];        //进程的局部描述符表一项为空、一项为cs、一项为ds和ss
16     TSS tss;
17 } PROCESS;
18 

可以看到,我们所熟知的字段都包含了,有:进程状态、进程优先级、进程运行时间片、进程pid、父进程pid、进程名等等,还有我们刚才提到的该进程的用户态栈和核心态栈指针,以及该进程运行了多长时间(该参数目前版本的WinixJ还没有用到),还有ldt、tss。下面依次对其进行介绍:
1、state,有如下几种状态:

1 #define PROC_RUNNING        1
2 #define PROC_INTERRUPTIBLE    2
3 #define PROC_UNINTERRUPTIBLE    3
4 #define PROC_ZOMBIE        4
5 #define PROC_STOPPED        5
6 

这些定义符合大部分UNIX类系统的规定。不过在WinixJ中第2、3、4种状态目前没有用到,进程只有正在运行、就绪两种状态,而RUNNING则代表了这两种状态,STOPPED是进程控制块中的初始状态,代表进程不存在(这里与UNIX类系统有些冲突,不过可以很容易修正),目前还不支持进程的死亡退出以及僵尸进程。
2、priority,进程的优先级,WinixJ目前采用的是时间片轮转调度,因此priority和进程的初始时间片相同。
3、time_slices,进程时间片,该值越大则进程获得运行的时间越长。
4、pid,进程pid。
5、ppid,父进程pid。
6、name[32],进程名。
7、kstack,核心态堆栈顶,前面介绍过了。
8、ustack,用户态堆栈顶,核心态堆栈和用户态堆栈都是1KB大小,见下面的定义:

1 //进程所拥有的内核态以及用户态堆栈
2 //大小为1KB,短期内应该够用
3 typedef struct kstack
4 {
5     uint8 res[1024];
6 } KSTACK, USTACK;
7 

9、running_time,进程已经运行了多长时间,目前未使用该变量。
10、ldt[3],局部描述符表,定义和GDT相同,如下:

 1 //定义段描述符结构,每个描述符占用8个字节
 2 typedef struct seg_struct
 3 {
 4     uint16    seg_limit_low16;
 5     uint16    seg_base_low16;
 6     uint8    seg_base_mid8;
 7     uint8    attr1;
 8     uint8    attr2_limit_high4;
 9     uint8    seg_base_high8;
10 };
11 

ldt是进程间隔离的核心部件之一,每个进程都有自己的ldt,它的含义和gdt项没有什么不同,只不过它仅包含三项,第一项和GDT的第一项一样未空,不使用,第二项为局部CS段描述符,第三项为局部DS和SS段描述符。而这个ldt又如何使用呢?暂时不需要管,只需要知道,当发生进程切换的时候,进程的CS段和DS段基地址是从该进程的ldt中获得的。
11、tss,这是TSS段,在进行任务切换的时候用于进程现场的保护和恢复。

再继续往下看代码:

 1 extern void init();
 2 extern void sys();
 3 
 4 static void set_tss_seg(int n, void *addr)
 5 {
 6     gdt[n].seg_limit_low16        = 0x0068//tss段长为104个字节,不能多也不能少
 7     gdt[n].seg_base_low16        = (uint16)(((uint32)addr) & 0xffff); //段地址的低16位
 8     gdt[n].seg_base_mid8        = (uint8)((((uint32)addr) >> 16& 0xff); //段地址的中间8位
 9     gdt[n].attr1                = 0x89//该段在内存中存在,DPL=0,是TSS描述符
10     gdt[n].attr2_limit_high4    = 0x40//段界限粒度是字节
11     gdt[n].seg_base_high8        = (uint8)((((uint32)addr) >> 24& 0xff); //段地址的高8位
12 }
13 
14 static void set_ldt_seg(int n, void *addr)
15 {
16     gdt[n].seg_limit_low16        = 0x0018//所有进程的ldt均只包含三个描述符,因此ldt段长为24个字节
17     gdt[n].seg_base_low16        = (uint16)(((uint32)addr) & 0xffff); //段地址的低16位
18     gdt[n].seg_base_mid8        = (uint8)((((uint32)addr) >> 16& 0xff); //段地址的中间8位
19     gdt[n].attr1                = 0x82//该段在内存中存在,DPL=0,是LDT描述符
20     gdt[n].attr2_limit_high4    = 0x40//段界限粒度是字节
21     gdt[n].seg_base_high8        = (uint8)((((uint32)addr) >> 24& 0xff); //段地址的高8位
22 }
23 

这两个函数不长,代码也很相似,他们完成的功能如下:
set_tss_seg()函数用于设置GDT中的某一描述符表项为指向进程控制块中的tss的起始地址。
set_ldt_seg()函数用于设置GDT中的某一描述符表项为指向进程控制块中的ldt的起始地址。
我们可以先预览一下WinixJ的GDT表的样子:
DUMMY
kernel-cs
kernel-ds
unused
process-0-tss
process-0-ldt
process-1-tss
process-1-ldt
process-2-tss
process-2-ldt
...
...
process-124-tss
process-124-ldt
unused
unused
其中每个GDT表项占用8字节,每个进程在gdt表中占用2项---1项是该进程的tss,1项是该进程的ldt。
ldtr寄存器中的值是ldt选择子,即相对于GDT起始地址的偏移,据此它能索引到对应的ldt,进而取得对应的段基地址。
posted on 2012-02-14 17:30 myjfm 阅读(434) 评论(0)  编辑 收藏 引用 所属分类: 操作系统

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理