操作系统实验:μC/OS-II 中利用信号量实现经典问题算法

操作系统实验三

任务一 生产者-消费者问题

1.1 实验过程分析
函数及数据声明

任务实现过程中需要三个信号量:full、empty、mutex

#define N 2
OS_EVENT *full;
OS_EVENT *empty;
OS_EVENT *mutex; 

其中,full用来记录充满的缓冲区数目,empty记录空的缓冲区数目,mutex用来确保生产者和消费者不会同时访问缓冲区。full的初始值为0,empty初始值为缓冲区中槽的数目,mutex初始值为1。

full = OSSemCreate (0);
empty = OSSemCreate (N);
mutex = OSSemCreate (1); 

文件开始处声明了生产者和消费者任务所使用的栈和函数

static OS_STK producer_stk[TASK_STK_SIZE]; 
static OS_STK consumer_stk[TASK_STK_SIZE]; 
void producer(void *p_arg);
void consumer(void *p_arg);
生产者和消费者函数

生产者首先执行P(empty)操作,将空槽数量减一,然后执行P(mutex)操作,进入临界区,进入临界区后将数据放入缓冲区,此过程用打印字符串表示,然后执行V(mutex)操作离开临界区,P(full)将满槽的数目加一。

static void producer(void *p_arg) 
{
	int i;
	int x_temp = (int) p_arg; 
	for (;;)
	{
		print_str("Producer!\n");
		// your code
		OSSemPend(empty,0,0);
		OSSemPend(mutex,0,0);
		
		print_str("The producer ");
    	char str[40]="x puts the data into the buffer\n";
		str[0]=(char)(x_temp+48);
		print_str(str);
		
		OSSemPost(mutex);
		OSSemPost(full);
		
		OSTimeDly (100);
		for(i=0;i<=10000;i++);
	} 
} 

消费者首先执行P(full)操作将满槽数目减一,然后P(mutex)进入临界区,取出数据,然后V(mutex)操作离开临界区,V(empty)操作将空槽数量加一。

static void consumer(void *p_arg) 
{
	int i;
	int x_temp =(int)p_arg;
	for (;;)
	{
		print_str("Consumer!\n");
		// your code
		OSSemPend(full,0,0);
		OSSemPend(mutex,0,0);
		
		print_str("The consumer ");
		char str[40]="x fetches the data from the buffer\n";
		str[0]=(char)(x_temp+48);
		print_str(str);
		
		OSSemPost(mutex);
		OSSemPost(empty);
		
	  OSTimeDly (100);
		for(i=0;i<=10000;i++);
	} 
} 
1.2实验结果展示
程序运行结果1:

在这里插入图片描述

当生产者生产一个数据并放入缓冲区后,任务挂起等待,随后消费者从缓冲区中取出数据,由于任务等待时间相同,所以生产者生产一个数据就被消费掉了,如此循环往复。

若将生产者(producer)挂起时间增加,做如下修改:

OSTimeDly (100);  ->   OSTimeDly (200);
程序运行结果2:

在这里插入图片描述

生产者挂起时间翻倍,当生产者生产一个数据后,消费者随后取出一个数据并将其消费掉,此时生产者还处于挂起状态,程序轮至消费者,输出Consumer!后,缓冲区为空,不能进入临界区,于是等待生产者生产数据。

任务二 哲学家就餐问题

2.1 实验过程分析
函数及数据声明

每个叉子不能被同时拿起,因此叉子为互斥访问量,将其作为信号量。设置哲学家数量为5,并设置新的优先级与其他任务区分,为每个哲学家任务创建栈:

#define N 5
#define PHI_TASK_PRIO 3
OS_STK TaskStk[N][TASK_STK_SIZE];  
OS_EVENT *forks[N];

通过动态分配内存为每个哲学家任务创建了一个PhilosopherParams结构体对象,并在任务创建时传递给任务函数。这样可以确保每个任务都有自己独立的参数对象,避免共享同一个指针的问题。创建哲学家任务函数。

typedef struct {
    INT8U philosopher_id;
} PhilosopherParams;

void philosopher(void *p_arg);

主函数任务创建,为每个哲学家分配栈空间并赋予不同优先级:

int main(void)
{
    int i;
    OSInit();
    systick_init(); 
    
    for (i = 0; i < N; i++) {
        forks[i] = OSSemCreate(1);
        PhilosopherParams *params = (PhilosopherParams *)malloc(sizeof(PhilosopherParams));
        params->philosopher_id = i;
        OSTaskCreate(philosopher, (void *)params, &TaskStk[i][TASK_STK_SIZE - 1], PHI_TASK_PRIO+i);
    }
    OSStart();
    return 0;
}
哲学家函数:

首先让哲学家进入饥饿状态,哲学家试图拿起左边和右边的叉子,如果同时拿起了两边的叉子,则可以开始进食,使用时间延迟模拟哲学家进食过程,哲学家进食完毕后,放下左边和右边的叉子,离开临界区开始思考,使用时间延迟函数模拟思考过程。

void philosopher(void *p_arg)
{
	
	INT8U i, j;
    i = *((INT8U *)p_arg);
    j = (i + 1) % N;
    
    for (;;)
    {
        print_str("Philosopher ");
        char str[20] = "x is hungry...\n";
        str[0] = (char)(i + 48);
        print_str(str);
        OSSemPend(forks[i], 0, 0);
        OSSemPend(forks[j], 0, 0);
        
        print_str("Philosopher ");
        char str1[20] = "x is eating...\n";
        str1[0] = (char)(i + 48);
        print_str(str1);
        OSTimeDly(500);
			
        print_str("Philosopher ");
        char str2[40] = "x is done eating and begin thinking...\n";
        str2[0] = (char)(i + 48);
        print_str(str2);
        
			
		OSSemPost(forks[i]);
        OSSemPost(forks[j]);
        OSTimeDly(500);
    }
}
2.2实验结果展示
程序运行结果:

在这里插入图片描述

结果分析:

程序运行至哲学家0号,开始饥饿,此时0号左右叉子均可用,于是开始就餐;轮到哲学家1号,由于0号正在就餐,有一个叉子不可用,所以任务挂起;轮至哲学家2号,左右均可用,开始就餐;3号哲学家由于2号正在就餐所以任务挂起;4号哲学家由于1号正在就餐所以任务挂起。随后1号和2号哲学家就餐完毕开始思考,1号左右叉子释放,1号开始就餐;4号被1号占用的叉子释放,开始就餐。0号又开始饥饿,随后1号就餐完毕,2号开始饥饿,4号就餐完毕,3号和0号左右叉子释放,开始就餐…

任务三 读者写者问题

3.1 实验过程分析
函数及数据声明

定义读者数量N,RC为正在读或者即将读的进程数目,db控制对数据库的访问,信号量mutex控制对RC的访问

#define N 2
OS_EVENT *mutex;
OS_EVENT *db;
INT32U RC = 0;

为读者和写者分配栈空间和声明函数

static OS_STK reader_stk[N][TASK_STK_SIZE]; 
static OS_STK writer_stk[TASK_STK_SIZE]; 

void reader(void *p_arg);
void writer(void *p_arg);

主函数创建读者写者任务,这里创建了2个读者1个写者,写者优先级最高。


int main(void) 
{ 
	
	OSInit();   
	systick_init(); 
	OSTaskCreate(writer, (void *)0, 
                   &writer_stk[TASK_STK_SIZE-1], TASK1_PRIO); 
	OSTaskCreate(reader, (void *)1, 
                   &reader_stk[0][TASK_STK_SIZE-1], TASK2_PRIO);    
	OSTaskCreate(reader, (void *)1, 
                   &reader_stk[1][TASK_STK_SIZE-1], TASK3_PRIO); 
	//ASM_Switch_To_Unprivileged();
	
	
	db=OSSemCreate(1);
	mutex=OSSemCreate(1);
	OSStart(); 
	return 0; 
} 
读者写者函数

读者首先通过P(mutex)操作获得对RC的互斥访问权,然后RC++,表示又多了一个读者,如果是第一个读者就执行P(db)操作获得对数据库的访问,然后通过V(mutex)操作释放对RC的互斥访问,访问数据然后通过P(mutex)获得对RC的互斥访问,RC–读者数减一,如果是最后一个读者就进行V(db)操作释放对数据库的互斥访问,接着释放对RC的互斥访问。

static void reader(void *p_arg) 
{
	int i;
	for (;;)
	{
		OSSemPend(mutex, 0, 0);
		RC++;
		if(RC==1)
		{
			OSSemPend(db,0,0);
		}
		OSSemPost(mutex);
		char str[20]="x is reading...\n";
		str[0]=(char)(RC+48);
		print_str(str);
		OSTimeDly(100);
		OSSemPend(mutex,0,0);
		RC--;
		if(RC==0)
		{
			OSSemPost(db);
		}
		OSSemPost(mutex);
		
		OSTimeDly (200);
		for(i=0;i<=10000;i++);
	} 
} 

写者的操作比较简单,首先通过P(db)操作获取对数据库的互斥访问权,更新数据后通过V(db)操作释放该访问权。

static void writer(void *p_arg) 
{
	int i;
	for (;;)
	{
		OSSemPend(db, 0, 0);
		print_str("The writer is writting...\n");
		OSSemPost(db);
	    OSTimeDly (200);
		for(i=0;i<=10000;i++);
	} 
} 
3.2 实验结果展示

程序运行结果:
在这里插入图片描述

首先写者写入数据,然后读者1和读者2轮流读取数据,由于任务挂起时间相同,所以一直如此循环。

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值