Nanos-操作系统oslab1(2)---sleep和wakeup

来源:互联网 发布:淘宝买官换机哪家靠谱 编辑:程序博客网 时间:2024/06/05 16:26

目标:

这次要为进程实现sleep功能和wakeup功能,所以我们需要添加两个队列,一个是运行队列runningList,还有一个则是等待队列waitingList。

链表的使用

提供的链表节点结构如下:

typedef struct ListHead {ListHead *prev;ListHead *next;}ListHead;
如何使用这个链表节点呢?毕竟平常创建的链表节点,里边可是会在节点定义里边添加value值的啊。
我们可以使用PCB和ListHead来新创建一个节点结构如下:

typedef struct PCBList{PCB *pcb;        ListHead listNode;}PCBList;
关于这个链表,它提供了以下几个函数,

链表节点的初始化,链表节点的加入

void list_init(ListHead *list){assert(list != NULL);list->prev = list;list->next = list;list->value = 0;}void list_add(ListHead *prev, ListHead *next, ListHead *data){assert(data != NULL);data->prev = prev;data->next = next;if (NULL != prev){prev->next = data;}if (NULL != next){next->prev = data;}}void list_add_before(ListHead *list, ListHead *data){assert(list != NULL);list_add(list->prev, list, data);}
我看到这个双向链表节点的加入感觉好懵懵懂懂啊委屈,新节点加入之后,链表结构到底是怎么变化的呢?

比如我首先创建一个头结点ListHead head,之后初始化它,那么我们的双向链表就是如下图所示:



加入一个节点之后呢?

ListHead node1;

list_init(node1);

list_add_before(&head, &node1);

我们知道会有这样的操作:

list_add(head->prev, &head, &node1)

也即node1->prev = head->prev;

node1->next = &head;

head->prev->next = node1;

head->prev = node1;

感觉好混乱啊尴尬,画个图来看看吧:

首先head的prev和next都是指向自己的,为了表示更加清晰,我们使用一个单独的head->prev节点,当然了,此时head->prev == head。

接着node1节点加入,然后就是按照上面写的修改指向,也就是此时的中间图。

因为head->prev和head节点时同一个节点,所以合并之后就变成第三幅图了。



那么我再加入一个节点node2情况会怎么样呢?

node2->prev = head->prev;

node2->next = &head;

head->prev->next = node2;

head->prev = node2;

初始情况如最左图,中间图显示指向关系的变化,蓝色为变化的指向,黑色为没有改变的指向,之后将head->prev节点与node1节点合起来就得到了最右图所示结果。


整个链表的遍历可以按照如下代码所示:

void list_traversal(){cout << "traverse in order:\t"; ListHead *node = head.next;while (node != &head){cout << node->value << "\t";node = node->next;}cout << endl;cout << "traverse in reverse order:"; node = head.prev;while (node != &head){cout << node->value << "\t";node = node->prev;}cout << endl;}
所以对于2个节点和3个节点的示例,
ListHead node1, node2;list_init(&head);list_init(&node1);list_init(&node2);node1.value = 1;node2.value = 2;list_add_before(&head, &node1);list_traversal();list_add_before(&head, &node2);list_traversal();
结果为:



两个队列的设计

按照实验要求,我们需要设计一个运行队列和一个等待队列,运行队列中存储现在可以调度的线程,等待队列则存储处于等待态,等待其他线程唤醒的线程。
我是用两个链表头结点来记录这两个链表:
ListHead runningHead;
ListHead waitingHead;

测试用例

一共会生成4个线程,初始只有A是在运行队列中,其余三个线程都在等待队列中,之后线程依次唤醒下一个线程并沉睡自己:
void A () {     int x = 0;    while(1) {        if(x % 100000 == 0) {            printk("a");            wakeup(PCB_of_thread_B);            sleep();        }        x ++;    }}void B () {     int x = 0;    while(1) {        if(x % 100000 == 0) {            printk("b");            wakeup(PCB_of_thread_C);            sleep();        }        x ++;    }}void C () {     int x = 0;    while(1) {        if(x % 100000 == 0) {            printk("c");            wakeup(PCB_of_thread_D);            sleep();        }        x ++;    }}void D () {     int x = 0;    while(1) {        if(x % 100000 == 0) {            printk("d");            wakeup(PCB_of_thread_A);            sleep();        }        x ++;    }}

根据create_kthread我们可以得到这4个函数的PCB,sleep()函数可以对宏current操作,而wakeup函数则需要对相应的PCB操作,所以我们定义4个全局变量PCB *来记录生成的四个PCB。
PCB *pPcbA = NULL;PCB *pPcbB = NULL;PCB *pPcbC = NULL;PCB *pPcbD = NULL;
我们首先不考虑等待队列情况,首先将所有的4个线程都加入运行队列,然后测试看看效果如何。
那我们要做些什么呢?
  • 生成4个线程的PCB
  • 将这4个线程加入runningHead中
  • 调度函数中轮询runningHead中的线程

生成4个线程的PCB

首先初始化链表头结点,初始化进程的pcbPool等。
之后如下生成4个线程的PCB,之后加入运行队列中

加入运行队列

很严重的问题1

最初我是按照下面的函数来将线程加入队列中,不过出了一个很严重的问题哦!!
PCBList是在这个函数中创建的局部变量!!!!!
所在在调度函数中遍历队列的时候,就出现了奇怪的情况,原来设定好的链表结构完全被破坏掉了,就因为这里是局部变量,所以到调度函数中时,这些节点都是不存在的!!!
因为这里不能够动态申请,毕竟是裸机嘛,连malloc的头文件都是不能包含的哦,所以我们要预习定义好全局PCBList数组,就和之前的pcbPool一样的啦!
PCBList定义如下:
m_ppcb用于存放指向PCB的指针,m_listNode则是用于连接队列的双向链表节点,used则是标记是否使用啦。
 
最后修改为如下:
       

队列添加好了,我们来看看添加后的效果,要做好单元测试嘛,所以写了一个遍历runningHead的函数来显示下运行队列的情况,从后遍历一次,然后再从前遍历一次。


结果如下,看来添加正确啦!吐舌头


调度函数

调度函数的功能就是修改current指向我们想要运行的线程的PCB。
会遇到一些什么样的调度情况呢?
  • 运行队列为空
  • 运行队列不为空,但是current宏指向idle线程
  • 运行队列不为空,current不指向idle
运行队列为空的话,那就没的说,current指向idle吧
运行队列不空,根据轮询策略,current要指向当前运行线程的下一个线程。
那如果current此时为idle呢?我们则把current指向运行队列的第一个线程就好啦,否则,遍历runningHead队列,找到当前线程的双向队列节点,然后就可以找到它的下一个线程节点了啦。
不过我为了避免遍历操作,所以又加了个全局变量ListHead *current_pcbNode = NULL来记录当前运行线程的双向队列节点。
要注意的一个地方是,根据pNode节点来得到PCBList的地址,它们之间隔了一个PCB *,也就是sizeof(PCB *)个字节。

很严重的问题2

看我下面写的这句话:
PCBList * pPcbList = (PCBList *)(pNode - sizeof(PCB *)),这句话问题出在哪里呢?
pNode没有转化为char *!所以实际上pNode - sizeof(PCB*)相当于减了sizeof(PCB*)*sizeof(ListHead)个字节!!这样计算就出问题啦!!
所以在做指针地址加减的时候,注意要转为char *,这样加减才会用字节来做单位!

根据pNode得到PCB *

因为等待队列和运行队列都是用ListHead结点记录,可是我们需要的是PCB啊!
那么如何根据pNode得到PCB *呢?下面做个小实验


具体如如下图所示

所以调度函数可以写成这样


运行一下,看,交替输出了吧!

wakeup的设计

wakeup做的事情是什么呢?
也就是把PCB* pcb指向的线程从等待队列中找出来,然后加入到运行队列就好啦!




sleep的设计

把当前运行的线程加入到等待队列,之后呢?还要做什么?
注意啦,还需要通过int 0x80调用一下系统调用,这样就会进入中断,从而调用schedule,立即更新current以及运行进程。
注意哦,调用完sleep之后current_pcbNode要指向哪一个节点呢?当然是pPrevNode啦,这样在schedule中才会顺利跑到pNextNode节点上去哦!

最后的运行结果:



0 0
原创粉丝点击