进程管理的核心数据结构

本节导读

本节将会展示在本章节的实验中,我们管理进程、调度进程所用到的数据结构。

进程队列

不同于此前遍历进程池的调度方式,在本章节中,我们实现了一个简单的队列,用于存储和调度所有的就绪进程:

 1// os/queue.h
 2
 3struct queue {
 4        int data[QUEUE_SIZE];
 5        int front;
 6        int tail;
 7        int empty;
 8};
 9
10void init_queue(struct queue *);
11void push_queue(struct queue *, int);
12int pop_queue(struct queue *);

队列的实现非常简单,大小为1024,具体的实现大家可以查看queue.c进行查看。我们将在后面的部分展示我们要如何使用这一数据结构

进程的调度

进程的调度主要体现在proc.c的scheduler函数中:

 1// os/proc.c
 2
 3void scheduler()
 4{
 5        struct proc *p;
 6        for (;;) {
 7                /*int has_proc = 0;
 8                for (p = pool; p < &pool[NPROC]; p++) {
 9                        if (p->state == RUNNABLE) {
10                                has_proc = 1;
11                            tracef("swtich to proc %d", p - pool);
12                            p->state = RUNNING;
13                            current_proc = p;
14                            swtch(&idle.context, &p->context);
15                    }
16            }
17                if(has_proc == 0) {
18                        panic("all app are over!\n");
19            }*/
20            p = fetch_task();
21                if (p == NULL) {
22                        panic("all app are over!\n");
23            }
24            tracef("swtich to proc %d", p - pool);
25                p->state = RUNNING;
26                current_proc = p;
27                swtch(&idle.context, &p->context);
28        }
29}

可以看到,我们移除了原来遍历进程池,选出其中就绪状态的进程来运行的这种朴素调度方式,而是直接使用了fetch_task函数从队列中获取应当调度的进程,再进行相应的切换;而对于已经运行结束或时间片耗尽的进程,则将其push进入队列之中。这种调度方式,相比之前提高了调度的效率,可以在常数时间复杂度下完成一次调度。由于使用的是队列,因此大家也会发现,我们的框架代码所使用的FIFO的调度算法。