基于dos的多任务系统实现

一.程序的设计思想

该程序主要是分5大块内容:线程的创建和撤销,线程的调度,线程的同步与互斥,线程的阻塞与唤醒,利用消息缓冲队列的线程间的通信。由这五大块功能来完成的基于DOS的多任务系统的实现。在这个系统中,首先先由main函数进行一些初始化工作,然后直接创建0#线程对应于main函数,再由0#线程调用create创建1#,2#线程分别对应与函数f1(),f2(),最后将系统的中断服务程序设置为new_int8,并把控制交给1#线程,启动多个线程的并发执行。
0#线程是一个比较特殊的线程,它在创建的时候没有使用create来创建,而是在系统初始化后直接创建的,因它对应的程序段为main函数中的一段,所以也直接使用整个系统的堆栈,而不再创建时为私有堆栈分配额外的空间;同样,撤销的时也不需要释放私有堆栈的空间,所以也没有over()函数而是直接撤销,从这方面来看,它是一个系统线程。
此外,在启动多个线程并发执行过程后,0#线程将系统控制权转交出去,直至系统中其他进程都不具备执行条件时,它才有可能重新得到CPU,从这方面看,0#线程相当于是一个空转线程,最后,0#线程还担负着一个特别的使命:等待系统中所有其他的线程的完成,此时,它将直接撤销自己并恢复原来的时钟中断服务程序,从此终止整个多任务系统。


二.线程的创建

一.主要函数设计
1. create函数
(1)函数申明原型: typedef int (far codeptr)(void); /定义了一个函数指针类型*/
Int create(char *name,codeptr code,int stck) ;
(2)函数功能描述:在main()函数中调用,创建一个新线程,让其执行code开始的代码。
(3)输入:
name:新创建线程的外部标识符;
code:新创建线程要执行的代码的入口地址,此处用函数名作为传入地址;
stck: 新创建线程的私有堆栈的长度。
(4)输出:新创建线程的内部标识符,若创建失败,返回-1。
(5)线程创建的主要设计思路
i, 为新线程分配一空闲的线程控制块
ii, 为新线程的私有堆栈分配内存空间
iii, 初始化新线程的私有堆栈,即按CPU调度时现场信息的保存格式布置堆栈。为了方便堆栈的初始化工作,我们可以按照堆栈中的内容设计一个以下的数据结构:
struct int_regs {
unsigned bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,flags,off,seg;
};然后用一个指向该数据结构的指针给堆栈赋值。
iv, 初始化线程控制块,即填入线程的外部标识符,设置好线程私有堆栈的始址,段址和栈顶指针,将线程的状态置为就绪状态。如图a所示。
v, 最后哦返回新线程的内部标识符
vi, 线程的内存映像如下:


三.线程的撤销

一.主要函数设计
1. destory函数

(1)函数申明原型:void destroy(int id);
(2)功能:撤销内部标识符为id的指定线程。
(3)输入:id:将要被撤销的线程的内部标识符。
(4)输出:无
撤销线程所要完成的工作比较简单,主要是将线程所占据的资源归还给系统。主要工作是:
①将线程的私有堆栈所占的内存空间归还给系统;
②将线程控制块TCB的各成员变量进行初始化操作。


四.线程的阻塞

一.主要函数设计
1. block函数

(1)函数申明原型:void block(struct TCB **qp);
(2)功能:实现线程的阻塞。
(3)输入:qp:指向TCB链表的二级指针。
(4)输出:无

我们将所有处于阻塞状态的线程的TCB按阻塞的不同原因排成多个队列。为此,需在TCB中增加一链接字段next,以方便TCB的排队。
一个正在执行的线程因某种原因而阻塞时,必须给出因该原因而阻塞的阻塞队列的队首信息,阻塞原语应完成以下主要工作:将线程的状态改成阻塞态;将线程插入指定的阻塞队列末尾;重新进行CPU调度。


五.线程的唤醒

一.主要函数设计
1.wakeup函数

唤醒原语应完成以下主要工作:把阻塞队列头上的第一个线程的TCB取下来,并将其状态改为就绪态,如果系统设置了就绪队列,还应插入就像队列。
(1)函数申明原型:void wakeup_first(struct TCB **qp);
(2)功能:实现线程的阻塞。
(3)输入:qp:指向TCB链表的二级指针。
(4)输出:无


六.线程的调度

一.主要设计思想
引起CPU调度原因主要是有三种情况:时间片到时,线程执行完毕或正在执行的线程因等待某种事件而不能继续执行。由这些原因,调度程序可以通过两个函数分别处理不同原因引起的调度:New_int8()函数主要是处理因时间片到时引起的调度该调度可以通过截取时钟中断(int 08)来完成;Swtch()函数主要是处理因其他原因引起的调度;New_int8()函数因为是通过截取时钟中断来实现,可以知道其是属于系统调度,由于涉及到系统调度的函数 都是需要对DOS状态进行判断,以防止出现系统数据混乱等情况的发生(从Dos的不可重入性来得出),而Swtch()函数是处理因其他原因引起的调度,所以它所涉及到的仅仅是用户级的函数调度,没有涉及到系统级的函数调度,因此Swtch()函数不需要对Dos状态进行判断。
二.主要函数设计
1. new_int8函数
(1)函数申明原型: void interrupt new_int8(void)
(2)功能:系统调度,即时间中断到达后,判断时间片到后才运行,调用老的时钟中断
(3)输入:无
(4)输出:无
(5)程序设计流程图

  1. my_swtch函数
    (1)函数申明原型: void interrupt my_swtch(void)
    (2)功能:由于它是解决由其他因素所引起的CPU调度,在这个实现过程,只需要判断线程的执行状态即可,其他阻塞等状态不需要进行判断,或者可以直接对当前线程的现场进行保护,然后寻找就绪线程,分配CPU以及现场进行执行
    (3)输入:无
    (4)输出:无
    (5)程序设计流程图

  2. find函数
    (1)函数申明原型:int Find(void)
    (2)功能:为了寻找就绪线程而且是优先权大的线程(根据优先数越大,优先权越小的思想,在TCB设置以优先数,然后进行选择)
    (3)输入:无
    (4)输出:就绪线程而且是优先权大的线程
    三.调度算法设计
    基于优先权的时间片轮转调度算法,即当前线程时间片到时,应暂停当前线程运行,重新选择一个优先权最高的就绪线程参加运行。在Tcb中增加一有限数字段(优先数越大优先权较低),可以按照等待时间的长度动态改变优先数(当一个线程在就绪队列中等待一个时间片),则将它的优先数减去某个值a,以提高它的优先权而避免长期等待;一个线程执行完一个时间片,则将它的优先数加上某个值b,以降低它的优先权。


七.线程的同步与互斥

一.主要变量
1.选择记录型信号量机制来作为线程的同步机制
记录型信号量的数据结构定义如下:
typedef struct{
int value;
struct TCB *wq;
} semaphore;
2.定义三个信号量
(1)mutex(初值为1)——–实现线程对缓冲池的互斥使用
(2)empty(初值为缓冲区的个数n)———–表示缓冲池中空缓冲区的数量
(3)full(初值为0)————-满缓冲区的数量

二.主要函数设计
1. wait函数
(1)函数申明原型: void wait(semaphore *sem)
(2)功能:申请一个空的缓冲区或者说是申请缓冲池的使用权
(3)输入:无
(4)输出:无
2. signal函数
(1)函数申明原型: void signal(semaphore *sem)
(2)功能:释放缓冲池使用权或者说是释放一个满缓冲区
(3)输入:无
(4)输出:无


八.利用消息缓冲队列通信机制实现线程间通信

一.主要变量
1.在消息缓冲队列通信机制中,主要用到的数据结构是消息缓冲区,它可描述如下:
struct buffer
{
int sender; /* 消息发送者的内部标识 */
int size; /* 消息长度 <=NTEXT 个字节 */
char text[NTEXT]; /* 消息正文 */
struct buffer next; / 指向下一个消息缓冲区的指针 */
};

2.在利用消息缓冲队列进行线程间的通信时,除了需要设置消息缓冲区外,还需对线程控制块TCB进行扩充,在原来的TCB的基础上再增加以下字段:
struct buffer mq; / 接收线程的消息队列队首指针 */
semaphore mutex; /* 接收线程的消息队列的互斥信号量 */
semaphore sm; /* 接收线程的消息队列的计数信号量,用于实现同步 */
二.主要函数设计
1. 获取空闲缓冲区函数getbuf()
(1)函数申明原型:struct buffer *getbuf(void)
(2)功能:从空闲消息缓冲队列头上取下一空闲消息缓冲区
(3)输入:无
(4)输出:指向所获得的空闲消息缓冲区的指针
2.插入缓冲区到缓冲队列函数insert()
(1)函数申明原型:void insert(struct buffer **mq,struct buffer *buff)
(2)功能:将buff所指的缓冲区插到*mq所指的缓冲队列末尾。
(3)输入:
mq:将要插入的缓冲队列的队首指针的指针;
buff:消息缓冲区指针。
(4)输出:无
3.发送原语send()
在发送消息时,消息的发送者必须提供接收者的标识符、消息长度及消息正文的起始地址等信息。然后在发送原语里申请一空闲的消息缓冲区,用相应的信息来装配该消息缓冲区,并将它插入到接受者的消息队列中去。
(1)函数申明原型:void send(char *receiver,char *a,int size)
(2)功能:将地址a开始的size个字节发送给外部标识符为receiver的线程。
(3)输入:
receiver:接收线程的外部标识符的指针;
a:要发送的消息正文的指针;
size:要发送的消息正文的长度。
(4)输出:无
4.获取消息缓冲区函数remov()
(1)函数申明原型:struct buffer *remov(struct buffer **mq,int sender)
(2)功能:接收线程从它自己的消息队列中取下sender发送给它的消息缓冲区。
(3)输入:
mq:接收线程的消息队列的队首指针的指针;
sender:发送线程的内部标识符;
(4)输出:所获得的消息缓冲区的指针
(5)函数实现的算法描述:
该函数需要完成的工作比较简单,首先在mq消息队列中查找发送线程标识符为sender的消息缓冲区,若找到则从mq所指示的消息缓冲队列中移除该消息缓冲区并返回该缓冲区,否则返回空。程序实现请同学们自己完成。
5.接收原语receive()
(1)函数申明原型:int receive(char *sender,char *b)
(2)功能:从接收线程的消息缓冲队列中取一个从sender发送过来消息并复制到b中。
(3)输入:
sender:发送线程的外部标识符;
b:接收线程内部的接收区,用来存放接收到的消息正文。
(4)输出:接收到的消息的长度
(5)函数实现的算法描述:
该函数要完成的主要工作是:首先检测指定的sender是否存在,如果不存在则直接返回;接着判断当前线程消息队列是否空闲,如果空闲,则调用remov()从消息队列取得sender发给它的消息;读取消息到b中;最后调用insert()将取完消息后的消息缓冲区插入到系统空闲消息缓冲队列中。


代码借鉴了一下网上和老师所给的


#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=
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值