实验5-信号量的实现和应用

对比标准POSIX信号量,只考虑有名信号量:使用 Posix IPC 名字标识,可用于进程或线程间的同步。

一个进程可以在信号量上执行三种操作:

创建。要求调用者指定初始值,对于二值信号量来说, 它通常是1,但也可以是0。
等待。该操作会测试会这个信号量的值,若其值 <= 0,那就等待(阻塞),一旦其值变为 >0 就将它减 1。测试和减 1 必须要是原子操作。
挂出。挂出一个信号量,会将信号量的值加 1,若某个进程阻塞等待该信号量的值变为大于 0,其现在就可能被唤醒。同时挂出操作也必须是原子的。

(先新建sem.c)中实现如下系统调用函数

sem_t *sys_sem_open(const char *name, unsigned int value);
int sys_sem_wait(sem_t *sem);
int sys_sem_post(sem_t *sem);
int sys_sem_unlink(const char *name);

挂载虚拟机,在(~/oslab/hdc/usr/include/linux)中创建sem.h,定义数据类型sem_t:

#ifndef _SEM_H_
#define _SEM_H_

#include <linux/sched.h> //用到了task_struct

/* 定义因某种信号量而阻塞的队列 */
#define QUE_LEN 16
struct semaphore_queue {
	int front;
	int rear;
	struct task_struct *wait_tasks[QUE_LEN]; //信号量表征资源的使用情况,进程因缺少资源而阻塞,挂在该信号量的等待队列上;
};
typedef struct semaphore_queue sem_queue;

/* 定义sem_t */
struct semaphore_t {
	int value;  //信号量的值
	int occupied; //是否被使用了
	char name[32];   //信号量的名字
	struct semaphore_queue wait_queue;
};

typedef struct semaphore_t sem_t;

#endif

kernel/sem.c的源码及注释

//#include <unistd.h> //包括了sem_t的定义等
#include <linux/sem.h> //sem_t的定义的等
#include <linux/sched.h> //使用了schedule函数
#include <linux/kernel.h> //使用了printk函数
#include <asm/system.h> //cli(), sti(), 实现原子操作
#include <asm/segment.h> //使用了get_fs_byte函数

#define NAME_SIZE 32

/* 信号量本质就是一种结构体,所以要消耗内存资源,故是有限的 */
//用数组来管理
#define SEM_COUNT 32
sem_t semaphores[SEM_COUNT];

/* 判断某信号量的等待队列是否已满 */
//循环队列:空1个位置,以判断队满
int is_full(sem_queue *q) {
	if((q->rear+1) % QUE_LEN == q->front) 
		return 1; //1表示满了
	else return 0;
}

/* 进程被阻塞,挂到某个信号量的等待队列中;*/
int insert_task(struct task_struct *p, sem_queue *q) {
	if(is_full(q)) {
		printk("Queue is full!\n");
		return -1; //插入失败
	}
	else {
		//入队操作
		q->wait_tasks[q->rear] = p;
		q->rear = (q->rear + 1) % QUE_LEN;
		return 1;  //插入成功
	}
}

/* 判断某信号量的等待队列是否为空 */
int is_empty(sem_queue *q) {
	return q->rear == q->front ? 1 : 0; //1表示为空
}


/* 获得循环队列的第1个任务 */
struct task_struct *get_task(sem_queue *q) {
	if(is_empty(q)) {
		printk("Queue is empty!\n");
		return NULL;
	}
	else {
		struct task_struct *tmp = q->wait_tasks[q->front];
		q->front = (q->front + 1) % QUE_LEN;
		return tmp;
	}
}

/* 判断信号量是否已经打开了 */
//信号量的名字是唯一的,所以根据名字来判断
int is_sem_opened(const char *name, int name_size) {
	int i;
	for(i = 0; i < SEM_COUNT; i++) {
			if(semaphores[i].occupied == 1) {
				int j;
				int flag = 1; //表示名字匹配上了
				for(j = 0; j < name_size; j++) {
					if(name[j] != semaphores[i].name[j]) {
						flag = 0;
						break;
					}
				}
				if(flag) return i;
			}
	}
	return -1; //没打开。
}

/* 初始化信号量的等待队列 */
void init_queue(sem_queue *q) {
	q->front = q->rear = 0;
}

/*
sem_open()的功能是创建一个信号量,或打开一个已经存在的信号量。
*/
sem_t *sys_sem_open(const char *name, unsigned int value) {
	char sem_name[NAME_SIZE];
	int i;
	for(i = 0; i < NAME_SIZE; i++) {
		char c = get_fs_byte(name + i);
		sem_name[i] = c;
		if(c == '\0') break;
	}
	if(i == NAME_SIZE) {
		printk("The name of semaphore is too long!\n");
		return NULL;
	}
	int id;
	if((id = is_sem_opened(sem_name, i)) != -1) {
		//打开了
		return &semaphores[id];
	}
	/* 没打开,则打开 */
	int j;
	for(j = 0; j < SEM_COUNT; j++) {
		if(semaphores[j].occupied == 0) {
			/* 复制名字 */
			int tmp;
			for(tmp = 0; tmp <= i; tmp++) {
				semaphores[j].name[tmp] = sem_name[tmp];
			}

			semaphores[j].occupied = 1;
			semaphores[j].value = value;
			
			//初始化该信号量的等待队列
			init_queue(&(semaphores[j].wait_queue));
			return &semaphores[j];
		}
	}

	/* 信号量数组已经满了 */
	printk("Numbers of semaphores are limited!\n");
	return NULL;
}

/* 释放信号量资源 */
int sys_sem_unlink(const char *name) {
	char sem_name[NAME_SIZE];
	int i;
	for(i = 0; i < NAME_SIZE; i++) {
		char c = get_fs_byte(name + i);
		sem_name[i] = c;
		if(c == '\0') break;
	}
	if(i == NAME_SIZE) {
		printk("The name of semaphore is too long!\n");
		return -1;
	}
	
	/* 判断是否打开了 */
	int id;
	if((id = is_sem_opened(sem_name, i)) != -1) {
		//打开了,才释放
		semaphores[id].occupied = 0;
		return 0;
	}
	return -1;
}

/*
关闭一个信号量并没有将它从系统中删除。
Posix 有名信号量至少是随内核持续:即使当前没有进程打开着某个信号量,它的值仍然保持。
*/

/* P操作 */
int sys_sem_wait(sem_t *sem) {
	cli();
	sem->value--;
	if(sem->value < 0) {
		current->state = TASK_UNINTERRUPTIBLE;
		insert_task(current, &(sem->wait_queue));
		schedule();
	}
	sti();
	return 0;
}

/* V操作 */
int sys_sem_post(sem_t *sem) {
	cli();
	sem->value++;
	if(sem->value <= 0) {
		struct task_struct *p = get_task(&(sem->wait_queue));
		if(p != NULL) {
			p->state = TASK_RUNNING;
		}
	}
	sti();
	return 0;
}


添加系统调用

unistd.h添加宏定义
在这里插入图片描述

system_call.s 修改数量

在这里插入图片描述

在include/linux/sys.h中添加系统调用的定义:

/* ... */
extern int sys_setregid();
/* 添加的系统调用定义 */
#include<linux/sem.h>
extern sem_t * sys_sem_open();
extern int sys_sem_wait();
extern int sys_sem_post();
extern int sys_sem_unlink();

/* 在sys_call_table数组中添加系统调用的引用: */
fn_ptr sys_call_table[] = 
{ sys_setup, sys_exit, sys_fork, sys_read,......, sys_sem_open, sys_sem_wait, sys_sem_post, sys_sem_unlink}

修改 kernel/Makefile

/* 第一处 */
OBJS  = sched.o system_call.o traps.o asm.o fork.o \
        panic.o printk.o vsprintf.o sys.o exit.o \
        signal.o mktime.o sem.o
       
/* 第二处 */
### Dependencies:
sem.s sem.o: sem.c ../include/linux/kernel.h ../include/unistd.h \
  ../include/linux/sem.h ../include/linux/sched.h
exit.s exit.o: exit.c ../include/errno.h ../include/signal.h \
  ../include/sys/types.h ../include/sys/wait.h ../include/linux/sched.h \
  ../include/linux/head.h ../include/linux/fs.h ../include/linux/mm.h \
  ../include/linux/kernel.h ../include/linux/tty.h ../include/termios.h \
  ../include/asm/segment.h

至此,我们就完成了系统内核sem.c编写,并修改了系统文件让四个函数能正常运行。

使用make all指令重新编译linux0.11内核,如果有错误出现的话,按照系统提示依次修改,直至编译成功。

接下来,我们继续编写用户态的pc.c

用户态pc.c

/*
建立一个生产者进程,N个消费者进程(N>1);
用文件建立一个共享缓冲区;
生产者进程依次向缓冲区写入整数0,1,2,...,M,M>=500;
消费者进程从缓冲区读数,每次读一个,并将读出的数字从缓冲区删除,然后将本进程ID和数字输出到标准输出;
缓冲区同时最多只能保存10个数。
提示:建议直接使用系统调用进行文件操作,使用标准C的fopen等文件读写函数需要额外的操作,稍显麻烦
*/
#define __LIBRARY__
#include <stdio.h>
#include <linux/sem.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

_syscall2(sem_t *, sem_open, const char *, name, unsigned int, value);
_syscall1(int, sem_wait, sem_t *, sem);
_syscall1(int, sem_post, sem_t *, sem);
_syscall1(int, sem_unlink, const char *, name);

int consumerNum = 5;	/* 消费者个数 */
const int maxNum = 500; /* 写入的数据量 */
const int bufSize = 10; /* 缓冲区大小 */
int data = -404;		/* 存放读取的数据*/
pid_t p_pid[5];			/* 进程号数组*/

sem_t *empty, *full, *mutex; /*三个信号量 */

void producer(){
	int i, fo;
	int endpos_produce = 0; /* 记录消费者进程的消费次数*/
	fo = open("report.txt", O_WRONLY | O_TRUNC, 0666);
	if (!fo){
		perror("打开文件失败!\n");
		return;
	}
	/* 这一步是提前在文件中写入初始的endpos_consumer的值 */
	lseek(fo, bufSize * sizeof(int), SEEK_SET);
	write(fo, &endpos_produce, sizeof(int));
	
	for (i = 0; i <= maxNum; i++){
		sem_wait(empty); /* 如果empty为0,意味着缓冲区已满,阻塞生产者*/
		sem_wait(mutex); /* 互斥控制 */

		lseek(fo, endpos_produce * sizeof(int), SEEK_SET);
		write(fo, &i, sizeof(int));

		endpos_produce = (endpos_produce + 1) % bufSize;

		sem_post(mutex); /* 出了临界区,需要mutex++,以便下一次可以进入 */
		sem_post(full);	 /* 看是不是需要唤醒阻塞 */
	}
	close(fo);
}
void consumer(){
	int cnt, endpos_consumer, fi;
	fi = open("report.txt", O_RDONLY, 0666);
	if (!fi){
		perror("创建文件缓冲区失败!\n");
		return;
	}
	for (cnt = 0; cnt < maxNum / 5; ++cnt){

		sem_wait(full);
		sem_wait(mutex);
		/* 从文件中读取消费者进程的文件指针位置 */
		lseek(fi, bufSize * sizeof(int), SEEK_SET);
		read(fi, &endpos_consumer, sizeof(int));
		/* 将文件中对应位置的数据读取出来 */
		lseek(fi, endpos_consumer * sizeof(int), SEEK_SET);
		read(fi, &data, sizeof(int));
		
		printf("\nC,%d: %d...\n", getpid(), data);
		fflush(stdout);
		/* 移动endpos_consumer的位置,下一次的消费者进程将读取新的数*/
		endpos_consumer = (endpos_consumer + 1) % bufSize;
		lseek(fi, bufSize * sizeof(int), SEEK_SET);
		write(fi, &endpos_consumer, sizeof(int));

		sem_post(mutex);
		sem_post(empty);
	}
	close(fi);
}

int main(){
	/* 建立这三个信号量 */
	empty = sem_open("empty", bufSize);
	if (empty == NULL){
		perror("empty create falied!\n");
		return -1;
	}
	full = sem_open("full", 0);
	if (full == NULL){
		perror("full create failed!\n");
		return -1;
	}
	mutex = sem_open("mutex", 1);
	if (mutex == NULL){
		perror("mutex create failed!\n");
		return -1;
	}
	if (empty && full && mutex){
		printf("create semphore successed!\n");
	}

	/* 调用生产者进程,往文件缓冲区中写入数字 */
	if (!fork()){
		printf("producer is running!\n");
		producer();
		exit(0); /* 生产者任务完成后,杀死该子进程 */
	}

	while (consumerNum--){

		if (!fork()){
			printf("consumer %d is running!\n", getpid());
			consumer();
			exit(0);
		}
	}

	wait(NULL);
	/* 关闭信号量 */
	sem_unlink("empty");
	sem_unlink("full");
	sem_unlink("mutex");
	return 0;
}

最终结果

在这里插入图片描述

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值