进程管理的核心数据结构¶
本节导读¶
本节将会展示在本章节的实验中,我们管理进程、调度进程所用到的数据结构。
进程队列¶
不同于此前遍历进程池的调度方式,在本章节中,我们实现了一个简单的队列,用于存储和调度所有的就绪进程:
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的调度算法。