基于线程的多任务调度系统的设计与实现实验报告
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
基于线程的多任务调度系统的设计与实现实验报告
姓名陈振辉
学号 12224506
班级 5班
1 实验要求
(1)线程的创建、撤消和CPU切换。
掌握线程的定义和特征,线程的基本状态,线程的私有堆栈,线程控制块TCB,理
解线程与进程的区别,实现线程的创建、撤消和CPU切换。
(2)时间片轮转调度
理解各种调度算法、调度的原因,完成时钟中断的截取,具体实现调度程序。
(3)最高优先权优先调度
理解优先权的概念,并实现最高优先权优先调度策略。
(4)利用记录型信号量实现线程的同步
理解同步的相关概念,掌握记录型信号量的概念及应用,并用记录型信号量实现
生产者和消费者问题。
(5)消息缓冲队列通信机制
理解进程(线程)通信的基本概念,并用消息缓冲队列实现线程间的通信。
功能设计
1 线程的创建和撤销
线程的创建过程关键就是对私有堆栈和TCB初始化的过程,其过程如下:
i,为新线程分配一空闲的线程控制块
ii,为新线程的私有堆栈分配内存空间(因为对等线程共享程序段和数据段空间,所以创建线程时不必像创建进程那样再为程序段和数据段分配内存空间)iii,初始化新线程的私有堆栈,即按CPU调度时现场信息的保存格式布置堆栈。
初始化线程控制块,即填入线程的外部标识符,设置好线程私有堆栈的始址,段址和栈顶指
针,将线程的状态置为就绪状态
2 线程的调度
引起CPU调度原因主要是有三种情况:时间片到时,线程执行完毕或正在执行的线程因等待某种事件而不能继续执行。
由这些原因,调度程序可以通过两个函数分别处理不同原因引起的调度:New_int8()函数主要是处理因时间片到时引起的调度该调度可以通过截取时钟中断(int 08)来完成;
Swtch()函数主要是处理因其他原因引起的调度;
New_int8()函数因为是通过截取时钟中断来实现,可以知道其是属于系统调度,由于涉及到系统调度的函数都是需要对DOS状态进行判断,以防止出现系统数据混乱等情况的发生(从Dos的不可重入性来得出),而Swtch()函数是处理因其他原因引起的调度,所以它所涉及到的仅仅是用户级的函数调度,没有涉及到系统级的函数调度,因此Swtch()函数不需要对Dos 状态进行判断。
3 线程的阻塞与唤醒
线程的阻塞:主要是当某一线程需要阻塞的时候,将其插入阻塞队列中,等待唤醒进程唤醒,所以其过程为:首先,将线程的状态置为阻塞态,然后将线程插入指定的阻塞队列末尾,并重新进行CPU调度。
线程的唤醒:主要是唤醒阻塞队列里面的线程,所以其过程是:把阻塞队列头上的第一个线程的TCB取下来,并将其状态改为就绪状态,等待CPU调度
4 线程的同步与互斥
在这个系统中是采用记录型信号量机制来实现同步与互斥的,实现的方法:
采用P ,V操作,设置两个信号量:一个为互斥信号量,一个为临界资源数目;
5利用消息缓冲队列的线程间通信
线程间的通信,关键采用send()与receive()来实现,通过发送一个文本信息来显示通信的过程,其过程为:
send()函数:消息的发送者需要提供接收者的标识符,消息的长度以及消息正文的起始地址等信息,然后在发送原语里申请一空闲的消息缓冲区,用相应的信息来装配该消息缓冲区,并把它插入到接收者的消息队列中去。
Receive()函数:消息的接受者必须给出发送者的标识符,接受区的起始地址等信息,然后从自己的消息队列中取得相应的发送者发送来的消息缓冲区,将消息正文复制到接受区中,并释放相应的消息缓冲区。
源代码及解析如下
#include <stdlib.h>
#include <stdio.h>
#include <dos.h>
#define GET_INDOS 0x34
#define GET_CRIT_ERR 0x5d06
/*定义四个状态*/
#define finished 0
#define running 1
#define ready 2
#define blocked 3
#define TL 3 /*设置TL(时间片)时间为3*/
#define NTCB 10 /*NTCB是系统允许的最多任务数也就是进程数*/
#define NBUF 5
#define NTEXT 30
/**********************声明变量********************/
char far *indos_ptr=0;
char far *crit_err_ptr=0;
int current; /*全部变量,始终等于正在执行的线程的内部标识符*/
int timecount=0; /*全局变量,等于上次调度至今的时间,在每次时钟中断发生时,timecount+1,通过它与TL课判断时间片是否到时,从而决定是否进行CPU调度*/
/********************定义数据结构********************/
typedef int (far *codeptr)(void);/*定义codeptr函数指针*/
/*定义记录型信号量的数据结构*/
typedef struct
{
int value;
struct TCB *wq;
}semaphore;
semaphore mutexfb={1,NULL}; /*互斥信号量*/
semaphore sfb={NBUF,NULL}; /*空闲缓冲队列的计数信号量*/
/*消息缓冲区的数据结构*/
struct buffer
{
int sender; /*消息发送者的标识数*/ int size;
/*消息长度<=NTEXT个字节*/ char text[NTEXT]; struct buffer *next; /指向下一个消息缓冲区的指针*/
};
struct buffer *freebuf; /*空闲消息缓冲队列,是临界资源,由NBUF个空闲的消息缓冲区组成*/
/*定义TCB数据结构*/
struct TCB{
unsigned char *stack; /*堆栈的起始地址*/
unsigned ss; /*堆栈的段址*/
unsigned sp; /*堆栈的栈指针*
char state; /*线程的状态*/
char name[10]; /*线程的外部标示符*/
struct TCB* next; /*链接字段,把所有就绪的线程按某种方式排成一显式队列,如优先权从高到底的队列*/
struct buffer *mq; /*消息队列队首指针*/
semaphore mutex; /*消息队列的互斥信号量*/
semaphore sm; /*消息队列计数信号量*/
int value;
} tcb[NTCB]; /*NTCB是系统允许的最多任务数*/
/*现场保护和恢复中要用到的一个数据结构*/
struct int_regs{
unsigned bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,flags,off,seg;
};
/**************************声明函数*************************/
int DosBusy(void);
void InitInDos(void);
void InitTcb(void); /*对TCB的初始化*/
int create(char *name,codeptr code,int stacklen);
void over(void); /*撤销线程,归还所占资源*/
void interrupt(*old_int8)(void); /*原来的时间中断程序,需要先声明*/
void interrupt new_int8(void); /*因时间片到时而引起的调度由new_int8()函数来完成*/
void interrupt swtch(void); /*其他原因引起的CPU调度由函数swtch()完成*/
void tcb_state(void); /*输出所有线程的状态信息*/
int all_finished(void);
void p(semaphore *sem); /*信号量P操作*/
void v(semaphore *sem); /*信号量V操作*/
/*********************函数的实现*********************/
/*******InitInDos函数的实现********/
void InitInDos(void){
union REGS regs;
struct SREGS segregs;
/*获得INDOS flag 的地址*/
regs.h.ah=GET_INDOS;
intdosx(®s,®s,&segregs),
indos_ptr=MK_FP(segregs.es,regs.x.bx);
/*get the address of CRIT_ERR flag*/
if(_osmajor<3)
crit_err_ptr=indos_ptr+1;
else if(_osmajor==3 && _osminor==0)
crit_err_ptr=indos_ptr-1;
else{
regs.x.ax=GET_CRIT_ERR,
intdosx(®s,®s,&segregs);
crit_err_ptr=MK_FP(segregs.ds,regs.x.si);
}
}
/*************DosBusy函数的实现************/
int DosBusy(void){
if(indos_ptr&&crit_err_ptr)
return (*indos_ptr|| *crit_err_ptr);
else
return (-1);
}
/************InitTcb函数的实现*************/
/*对TCB进行初始化*/
void InitTcb(void){
int i;
for(i=1;i<NTCB;i++){
tcb[i].stack=NULL;
tcb[i].state=finished;
strcpy(tcb[i].name,'\0');
tcb[i].mq=NULL;
tcb[i].sm.value=0; /*消息队列计数信号量*/
tcb[i].mutex.value=1; /*缓冲区的互斥信号量*/
}
}
/*************create函数的实现****************/
/*创建一对应于函数name(外部标识符)的线程*/
int create(char *name,codeptr code, int stacklen){
int i;
char *p;
struct int_regs *pt;
/*第一步:寻找空白的TCB*/
for(i=1;i<NTCB;i++){
if(tcb[i].state==finished)
break;
}
/*第二步:申请线程的私有堆栈内存空间,分配stacklen个字节长度的内存空间,利用malloc函数返回内存地址指针指向该内存空间,
所返回的值是该内存空间的起始地址*/
p=(char *)malloc(stacklen*sizeof(char));
/*获得堆栈的内存空间的高地址指针*/
p=p+stacklen
pt=(struct int_regs*)p;
pt--;
pt->flags=0x200; /*flags寄存器的允许中断位*/
pt->cs=FP_SEG(code); /*代码段的段地址*/
pt->ip=FP_OFF(code); /*代码段的段内偏移地址*/
pt->ds=_DS; /*数据段的段地址*/
pt->es=_ES; /*附加数据段的段地址*/
pt->off=FP_OFF(over); /*撤销线程代码的偏移地址*/
pt->seg=FP_SEG(over); /*撤销线程代码的段址*/
/*第四步:初始化线程的控制块TCB*/
strcpy(tcb[i].name,name); /*填入线程的外部标识符*/
tcb[i].state=ready; /*将线程的状态置成就绪态*/
tcb[i].stack=p-stacklen; /*私有堆栈的起始地址*/
tcb[i].ss=FP_SEG(pt); /*当前线程的段地址*/
tcb[i].sp=FP_OFF(pt); /*当前线程的栈顶指针*/
return i; /*返回线程的内部标示符*/
}
/************new_int8函数的实现***************/
/*系统调度,即时间中断到达后,判断时间片到后才运行,调用老的时钟中断*/
void interrupt new_int8(void){
int i;
(*old_int8)(); /*调用原来的时钟中断服务程序*/
timecount++; /*每次发生中断时加1*/
if(timecount>=TL){ /*时间片到时*/
if(DosBusy()) /*如果Dos忙*/
return;
disable(); /*关中*/
/*保护正在执行的线程current的现场,暂停它的执行*/
tcb[current].ss=_SS;
tcb[current].sp=_SP;
if(tcb[current].state==running) /*将执行状态变为就绪状态,暂停执行*/ tcb[current].state=ready;
/*找到以新的就绪线程*/
for(i=1;i<NTCB;i++){
if(tcb[i].state==ready && i!=current) /*找到除了当前线程的其他就绪线程*/
break;
}
/*如果没有找到就绪线程,那么就回复当前线程,继续执行*/
if(i>=NTCB){
if(tcb[current].state==ready)
tcb[current].state=running;
enable();
return; /*如果超出了NTCB则恢复现场然后返回*/
}
/*如果找到就绪线程,那么恢复线程i的现场,把CPU分配给它*/
_SS=tcb[i].ss;
_SP=tcb[i].sp;
tcb[i].state=running;
/*置线程i为现有线程,并且重新开始计时*/
current=i;
timecount=0;
enable(); /*开中*/
}
return;
}
/*********swtch函数的实现************/
/*针对Swtch()函数的实现:由于它是解决由其他因素所引起的CPU调度,在这个实现过程,只需要判断线程的执行状态即可,其他阻塞等状态不需要进行判断,或者可以直接对当前线程的现场进行保护,然后寻找就绪线程,分配CPU以及现场进行执行*/
/*Find()函数是为了寻找就绪线程而且是优先权大的线程(根据优先数越大,优先权越小的思想,在TCB设置以优先数,然后进行选择)*/
int Find()
{
int i,j;
for(i=0;i<NTCB;i++)
if(tcb[i].state==ready&&i!=current)
break;
if(i==NTCB)
return -1;
for(j=i+1;j<NTCB;j++)
{
if(tcb[j].state==ready&&j!=current)
if(tcb[j].value>tcb[i].value)
i=j;
}
return i;
}
/*swtch()调度,手工调度才能运行,处理因其他因素引起的中断*/
void interrupt swtch(void)
{
int i;
i=Find();
if(i<0)
i=0;
disable();
tcb[current].ss=_SS;
tcb[current].sp=_SP;
if(tcb[current].state==running)
tcb[current].state=ready;
_SS=tcb[i].ss;
_SP=tcb[i].sp;
tcb[i].state=running;
current=i;
enable();
}
/****************线程的阻塞和唤醒的实现****************/
/**(阻塞)block函数的实现**/
void block(struct TCB **qp){
struct TCB *tp;
disable();
tp=*qp;
tcb[current].state=blocked; /*首先要将当前线程的状态置为阻塞状态*/ /*需要将线程插入到指定的阻塞队列未尾,并重新进行CPU调度*/
(*qp)->next=NULL;
if(tp==NULL)
tp=&tcb[current]; /*由于tp是一个指针,所以操作的是指针*/ else{
while(tp->next!=NULL)
tp=tp->next;
tp->next=&tcb[current]; /*将阻塞线程插入到队尾*/
}
enable();
swtch(); /*并重新进行CPU调度*/
}
/**(唤醒)wakeup_first函数的实现**/
void wakeup_first(struct TCB **qp){
int i;
struct TCB *tp;
disable();
tp=*qp;
/*寻找阻塞线程,因为线程状态的改变需要在TCB中修改,所以需要知道阻塞队列里面需要唤醒的线程对应TCB数组里面的哪一个*/
for(i=1;i<NTCB;i++){
if(strcmp(tcb[i].name,(*tp->next).name)==0){ /*如果两个外部标示符一样说明找到需要唤醒的线程*/
break;
}
tcb[i].state=ready; /*将其状态改为就绪状态*/
enable();
}
}
/***************线程的同步和互斥的实现****************/
/*用记录型信号量机制实现同步与互斥*/
/**对信号量的P操作**/
void p(semaphore *sem){
struct TCB **qp; /*设置一个指向TCB链表的二级指针*/
disable(); /*关中断*/
sem->value=sem->value-1; /*记录型信号量的value值减1*/
if(sem->value<0){ /*如果记录型信号量的值小于0*/
qp=&(sem->wq); /*那么将qp指针指向sem信号量的阻塞队列*/
block(qp); /*阻塞相应进程到阻塞队列*/
}
enable();
}
/**对信号量的V操作**/
void v(semaphore *sem){
struct TCB **qp;
disable();
qp=&(sem->wq);
sem->value=sem->value+1;
if(sem->value<=0){
wakeup_first(qp);
}
enable();
}
/***************消息缓冲队列的线程间的通信*************/
/**初始化消息队列**/
void InitBuf(void){
int i;
struct buffer *freebuf,*temp;
freebuf=(struct buffer*)malloc(sizeof(struct buffer)); /*申请空间*/ temp=freebuf;
for(i=1;i<=NBUF;i++){
temp=(struct buffer*)malloc(sizeof(struct buffer));
temp=temp->next;
}
}
/**从空闲消息缓冲队列头上取下一缓冲区,返回指向该缓冲区的指针**/
struct buffer *getbuf(void){
struct buffer *buff;
buff=freebuf; /*空闲消息缓冲头*/
freebuf=freebuf->next;
return buff;
}
/**将buff所指的缓冲区插到*mq所指的缓冲队列尾**/
void insert (struct buffer **mq,struct buffer *buff){
struct buffer *temp;
if(buff==NULL)
return;
buff->next=NULL;
if(*mq==NULL)
*mq=buff;
else{
temp=*mq;
while(temp->next!=NULL)
temp=temp->next;
temp->next=buff;
}
}
/***将地址a开始的size个字节发送给外部标示符为receiver的线程***/
void send(char *receiver,char *a,int size){
struct buffer *buff;
int i,id=-1;
disable(); /*原语要关中断*/
/*首先需要进行搜索接受进程*/
for(i=0;i<NTCB;i++){
。