杭州电子科技大学操作系统课程设计课程设计题目:基于DOS的多任务系统实现姓名:徐凯辉班级:12052315学号:12224679学院:计算机学院专业:计算机科学与技术负责老师:贾刚勇报告完成日期:2014.12.25基于DOS的多任务系统的实现一,课程设计的目的通过对线程(和进程)的创建和撤销,CPU的调度,同步机制,通信机制的实现,以达到一下目的:1,加深对线程和进程概念的理解,明确进程和程序的区别。2,加深对CPU调度过程(现场保护,CPU的分派和现场的恢复)的理解。3,进一步认识并执行的概念,明确顺序执行和并发执行的区别。4,加深对临界资源,临界区,信号量以及同步机制的理解。5,加深对消息缓冲通信的理解。二,设计要求1,完成线程的创建和撤销,并按优先权加时间片轮转算法对多线程进行调度。2,改变时间片的大小,观察结果的变化,3,假设两个线程共用同一软件资源(如某以变量,或者某以数据结构),请用记录型信号量来实现对它的互斥访问。4,假设有两个线程共享一个可以存放5个整数的缓冲,一线程不停地计算1至50的平方,并将结构放入缓冲中,另一个线程不断地从缓冲中取出结果,并将它们打印出来,请用记录型信号量实现这一生产者和消费者的同步问题。5,实现消息缓冲通信,并与3,4中的简单通信进行比较。三,程序设计思想以及总流程图1,程序的设计思想:该程序主要是分5大块内容:线程的创建和撤销,线程的调度,线程的同步与互斥,线程的阻塞与唤醒,利用消息缓冲队列的线程间的通信。由这五大块功能来完成的基于DOS的多任务系统的实现。在这个系统中,首先先由main函数进行一些初始化工作,然后直接创建0#线程对应于main函数,再由0#线程调用create创建1#,2#线程分别对应与函数f1(),f2(),最后将系统的中断服务程序设置为new_int8,并把控制交给1#线程,启动多个线程的并发执行。0#线程是一个比较特殊的线程,它在创建的时候没有使用create来创建,而是在系统初始化后直接创建的,因它对应的程序段为main函数中的一段,所以也直接使用整个系统的堆栈,而不再创建时为私有堆栈分配额外的空间;同样,撤销的时也不需要释放私有堆栈的空间,所以也没有over()函数而是直接撤销,从这方面来看,它是一个系统线程。此外,在启动多个线程并发执行过程后,0#线程将系统控制权转交出去,直至系统中其他进程都不具备执行条件时,它才有可能重新得到CPU,从这方面看,0#线程相当于是一个空转线程,最后,0#线程还担负着一个特别的使命:等待系统中所有其他的线程的完成,此时,它将直接撤销自己并恢复原来的时钟中断服务程序,从此终止整个多任务系统。2,系统的总流程图开始系统初始化创建0#号线程创建1#,2#线程截取时钟中断,启动线程的并发执行,使控制转向1#号线程重复输出字符’b’,并撤销自己2#1#0#重复输出字符’a’,并撤销自己其他线程以完YNCPU调度程序终止整个多任务系统输出结束信息结束四,系统各个功能的实现思想1,线程的创建和撤销线程的创建过程关键就是对私有堆栈和TCB初始化的过程,其过程如下:i,为新线程分配一空闲的线程控制块ii,为新线程的私有堆栈分配内存空间(因为对等线程共享程序段和数据段空间,所以创建线程时不必像创建进程那样再为程序段和数据段分配内存空间)iii,初始化新线程的私有堆栈,即按CPU调度时现场信息的保存格式布置堆栈。iv,初始化线程控制块,即填入线程的外部标识符,设置好线程私有堆栈的始址,段址和栈顶指针,将线程的状态置为就绪状态。v,最后哦返回新线程的内部标识符vi,线程的内存映像如下:Tcb[0]........Tcb[i]:Stack:63eSs:59baSp:a22State:readyName:”f1”........Tcb[NTCB-1]TCB集P---(低地址)59a:63e初始化现场信息:BPDISIDS:59baES:59baDXCXBXAXIP:879CS:57f7Flags:200Off:4b6Seg:57f7Sp--------59ba:a22F1()的返回地址:59ba:a3e线程的私有堆栈空间F1()Over()57f7:87957f7:4b6线程的撤销过程中,一个关键的地方是在初始化线程私有堆栈时需要将over()的入口地址压入线程的私有堆栈中,这样做的好处是:当线程所对应的函数正常结束时,over()函数的入口地址将最为函数的返回地址被弹出至CS,IP寄存器,那么控制将自动转向over(),从而使对应的线程被自动撤销,并重新进行CPU调度。intcreate(char*name,codeptrfunc,intstack_len){inti;unsignedchar*fp;structint_regs*regs;for(i=0;iNTCB;i++){if(tcb[i].state==FINISHED){/*创建新线程*/fp=(unsignedchar*)malloc(stack_len*sizeof(unsignedchar));//为新线程的私有堆栈分配内存空间regs=(structint_regs*)(fp+stack_len);regs--;/*对私有堆栈进行初始化*/regs-seg=FP_SEG(over);regs-off=FP_OFF(over);regs-flags=0x200;regs-cs=FP_SEG(func);/*代码段的段地址*/regs-ip=FP_OFF(func);/*获取代码段的段内偏移地址*/regs-es=FP_SEG(regs);/*附加数据段的段地址*/regs-ds=FP_SEG(regs);/*获取数据段的段地址*//*对TCB进行初始化*/tcb[i].stack=fp;strcpy(tcb[i].name,name);tcb[i].ss=FP_SEG(regs);tcb[i].sp=FP_OFF(regs);tcb[i].state=READY;returni;}}return-1;}/*线程撤销函数*//*id:线程内部标识符*//*释放动态申请的线程私有堆栈*//*将线程的状态置为FINISHED*/voiddestroy(intid){structbuffer*buff;buff=NULL;free(tcb[id].stack);tcb[id].stack=NULL;tcb[id].state=FINISHED;tcb[id].ss=0;tcb[id].sp=0;strcpy(tcb[id].name,'\0');tcb[id].mutex.value=1;//接收线程的消息队列的互斥信号量tcb[id].mutex.wq=NULL;//等待进程tcb[id].sm.value=0;//计数信号量tcb[id].sm.wq=NULL;/*将mq中的消息缓冲区全部回收到空闲消息缓冲队列中*/if(tcb[id].mq==NULL)return;buff=tcb[id].mq;while(buff!=NULL){/*回收*/p(&mutexfb);insert(&freebuf,buff);v(&mutexfb);v(&sfb);tcb[id].mq=tcb[id].mq-next;buff=tcb[id].mq;}}2,线程的调度引起CPU调度原因主要是有三种情况:时间片到时,线程执行完毕或正在执行的线程因等待某种事件而不能继续执行。由这些原因,调度程序可以通过两个函数分别处理不同原因引起的调度:New_int8()函数主要是处理因时间片到时引起的调度该调度可以通过截取时钟中断(int08)来完成;Swtch()函数主要是处理因其他原因引起的调度;New_int8()函数因为是通过截取时钟中断来实现,可以知道其是属于系统调度,由于涉及到系统调度的函数都是需要对DOS状态进行判断,以防止出现系统数据混乱等情况的发生(从Dos的不可重入性来得出),而Swtch()函数是处理因其他原因引起的调度,所以它所涉及到的仅仅是用户级的函数调度,没有涉及到系统级的函数调度,因此Swtch()函数不需要对Dos状态进行判断。对于线程的两种调度函数的过程,因其相似,给出New_int8()函数的执行过程图,如下:调用原来的时钟中断服务程序(*old_int8)()进行计时:timecount++时间片到时?DOS忙?Y关中N保护正在执行的线程(current)的现场信息,暂时它的执行Tcb[current].ss=_SS;Tcb[current].sp=_SP;If(tcb[current].state==running)Tcb[current].state=ready;找到一新的就绪线程恢复线程i的现场,把CPU分配给它:_SS=tcb[i].ss;_SP=tcb[i].sp;Tcb[i].state=running;置线程i为现有线程:current=I;重新开始计时:timecount=0;开中返回NY需要主要的是:新的时钟中断处理程序不能太长,否则系统效率将大大下降甚至使系统无法正常工作;在新的时钟中断处理程序必须调用系统原来的INT08H,否则将影响磁盘马达的关闭和系统的计时,另外,我们还主要依赖原来的INT08H向中断控制器发中断结束指令(EOI);voidinterruptmy_swtch(){intt_id;/*找到一个新的就绪线程*/t_id=find();if(t_id0){/*没有就绪线程了*/printf(NotFindReadyThread.\n);t_id=0;}disable();/*关中断*//*保护正在执行的线程current的现场,暂停它的执行*/tcb[current].ss=_SS;tcb[current].sp=_SP;if(tcb[current].state==RUNNING){tcb[current].state=READY;}/*恢复线程i的现场,把CPU分派给它*/_SS=tcb[t_id].ss;_SP=tcb[t_id].sp;tcb[t_id].state=RUNNING;current=t_id;timecount=0;//重新开始计时enable();/*开中断*/}3,线程的阻塞与唤醒线程的阻塞:主要是当某一线程需要阻塞的时候,将其插入阻塞队列中,等待唤醒进程唤醒,所以其过程为:首先,将线程的状态置为阻塞态,然后将线程插入指定的阻塞队列末尾,并重新进行CPU调度。线程的唤醒:主要是唤醒阻塞队列里面的线程,所以其过程是:把阻塞队列头上的第一个线程的TCB取下来,并将其状态改为就绪状态,等待CPU调度/*阻塞函数*/voidblock(structTCB**qp){structTCB*p;/*1.将线程的状态改成阻塞态*/tcb[current].state=BLOCKED;/*2.将线程插入到指定的阻塞队列末尾*/tcb[current].next=NULL;if((*qp)==NULL){/*阻塞队列为空时*/(*qp)=&tcb[current];}else{p=*qp;while(p-next!=NULL)p=p-next;p-next=&tcb[current];}/*printf(\n%sisBLOCKED!\n,tcb[current].name);*//*3.重新进行CPU调度*/my_swtch();}/*唤醒函数*/voidwakeup_first(structTCB**qp){structTCB*tp;if((*qp)==NULL)/*如果阻塞队列为空*/return;/*1.把阻塞队列头上的第一个线程的TCB取下*/tp=*qp;*qp=(*qp)-next;tp-next=NULL;/*2.将其状态改为就绪态*/tp-state=READY;/*printf(\n%siswakeup!\n,tp-name);*/}4,线程的同步与互斥在这个系统