使用只有正数的信号量实现:
2.sem_wait()- P操作:
int sem_wait(sem_t * sem)
{
//关中断
cli();
//判断:如果传入的信号量不满足要求,P操作失败,返回-1
if(sem < sem_list || sem > sem_list + SEM_LIST_LENGTH)
{
sti();
printk("sem (V) error\n");
return -1;
}
//由于V操作已经唤醒了所有进程,并且已经通过shedule()调度了其中优先级最大的进程,
//然后遍历所有的进程,直到找到value不为0的,也就是获得了信号量的,继续执行
while(sem->value == 0)
{
sleep_on(&(sem->queue));
shedule();
}
sem_value--;
//开中断
sti();
return 0;
}
3.sem_post() - V操作:
int sem_post(sem_t * sem)
{
//关中断
cli();
//判断:如果传入的信号量不满足要求,P操作失败,返回-1
if(sem < sem_list || sem > sem_list + SEM_LIST_LENGTH)
{
sti();
printk("sem (V) error\n");
return -1;
}
//将阻塞队列上所有的进程全部唤醒
sem->value++;
wake_up(&(sem->queue));
//开中断
sti();
return 0;
}
使用有正有负的信号量实现:
2.sem_wait()- P操作:

int sem_wait(sem_t * sem)
{
//关中断
cli();
//value--,判断是不是要阻塞
sem->value--;
if(sem->value < 0)
{
//value-- <0,说明要将这个进程阻塞
current->state = TASK_UNINTERRUPTIBLE;
//将当前阻塞的进程加入到阻塞队列,这里的队列和对队列的操作都要自己实现
insert_task(current,&(sem->wait_queue))
//由于该进程阻塞,所以需要调度下一个进程
schedule();
}
//开中断
sti();
return 0;
}
3.sem_post() - V操作:
int sem_post(sem_t * sem)
{
//关中断
cli();
//value++,判断是不是要唤醒
sem->value++;
if(sem->value <= 0)
{
//value++ <=0,说明要将阻塞队列中的一个进程唤醒(直接取队首的进程,将进程的PCB赋值给p指针)
//将取出来的进程的状态设置为就绪态,加入到就绪队列
struct task_struct * p;
p = get_task(&(sem->wait_queue));
if(p != NULL)
{
p->state = TASK_RUNNING;
}
insert_task(p,&(sem->ready_queue));//这句话好像在(from github的代码没有)
}
}
4.sem_unlink()信号量的删除:
int sem_unlink(const char * name)
{
//首先将信号量的名称赋值到新建的缓冲区中
char nbuf[20];
int i = 0;
for(i = 0; nbuf[i] = get_fs_byte(name+i); i++);
//在信号量数组中找到和要删除的信号量
for(i = 0; i < SEM_LIST_LENGTH; i++)
{
if(!strcmp(sem_list[i].name,nbuf))
{
printk("sem %s is unlinked\n",sem_list[i].name);
sem_list[i].name[0] = '\0';
sem_list[i].value = 0;
sem_list[i].queue = NULL;
return 0;
}
}
//信号量找不到,返回-1
printk("sem %s is not exist\n",nbuf);
return -1;
}
由于添加了系统调用(在内核中多加了一个sem.c文件),所以原来的编译链接方式不适应了现在了,所以需要进行改动,以便进行下一次make all的时候能够正确的编译内核。
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
改为:
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文件:
sem.s sem.o: sem.c ../include/linux/kernel.h ../include/unistd.h
1.建立一个共享缓冲区(文件),缓冲区大小为10
2.建立一个生产者进程、五个消费者进程
3.一个生产者和五个消费者并发执行:生产者不断的写一部分数据,然后切到消费者读一部分数据输出,然后继续写,然后又读…
pc.c:(书上的模板)
#include <unistd.h>
#include <string.h>
#include <sys.h>
#include <sem.c>
#include <sched.h>
char buf[500];//缓冲区(其实要搞成文件的形式的)
int main()
{
//新建三个信号量
empty = sem_open("empty",10);
full = sem_open("full",0);
mutex = sem_open("mutex",1);
//新建一个生产者进程
if(!fork())
{
int i;
for(i = 0; i < 500; i++)
{
sem_wait(empty);
sem_wait(mutex);
buf[i] = i;//向缓冲区中写数据
sem_wait(mutex);
sem_wait(full);
}
}
//新建五个消费者进程
int i;
for(i = 0; i < 5; i++)
{
if(!fork())
{
int i;
for(i = 0; i < 10; i++)
{
sem_wait(full);
sem_wait(mutex);
printk("%d %d\n",p->id,buf[i]);//从缓冲区读数据,输出进程id和数据
sem_wait(mutex);
sem_wait(empty);
}
}
}
}
参考代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#inlude <sys/types.h>
#include <fcntl.h>
//四个系统调用
//_syscall(传参个数)(返回值,系统调用名,参数1类型,参数1名称,参数2类型,参数2名称...)
_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);
const int consumerNum = 5;//消费者个数
const int itemNum = 500;//写入的数据量
const int bufSize = 10;//缓冲区大小
int main()
{
//定义三个指向信号量结构体的指针
sem_t * sem_empty;
sem_t * sem_full;
sem_t * sem_mutex;
//给三个信号量指针分配信号量空间
sem_empty = sem_open("empty",10);
if(sem_empty == NULL)
{
perror("empty信号量分配失败!\n");
return -1;
}
sem_full = sem_open("full",0);
if(sem_full == NULL)
{
perror("full信号量分配失败!\n");
return -1;
}
sem_mutex = sem_open("mutex",1);
if(sem_mutex == NULL)
{
perror("mutex信号量分配失败!\n");
return -1;
}
int data;//从文件中读出的数据临时存在在data中
int buf_in = 0;//写文件buf_in++
int buf_out = 0;//读文件buf_out++
int fd;//文件句柄,也就是标识已打开文件的变量
//打开文件,返回句柄给fd
fd = open("buffer.dat",0_RDWE|O_CREAT|O_TRUNC,777);
//根据后面两个参数重新定位被打开的fd文件的位移量
lseek(fd,bufSize * sizeof(int),SEEK_SET);
//写fd文件,但是此时啥也没写
write(fd,%buf_out,sizeof(int));
//指向进程结构体的指针
pid_t p;
//建立一个生产者进程
if(!(p = fork()))
{
//子进程进入
//循环500次来写文件
int i;
for(i = 0; i < itemNum; i++)
{
sem_wait(sem_empty);
sem_wait(sem_mutex);
lseek(fd,buf_in*sizeof(int),SEEK_SET);
write(fd,(char*)&i,sizeof(int));
buf_in = (buf_in+1) % bufSize;
sem_post(sem_mutex);
sem_post(sem_full);
}
return 0;
}
else if(p < 0)
{
//既不是子进程也不是父进程
perror("生产者进程创建失败!\n");
return -1;
}
//建立五个消费者进程
int i;
for(i = 0; i < consumerNum; i++)
{
if(!(p = fork()))
{
//子进程进入
int k;//itemNum/consumerNum表示每个消费者进程消费的量(读出的数据个数)
for(k = 0; k < itemNum/consumerNum; i++)
{
//消费者开始读文件
sem_wait(sem_full);
sem_wait(sem_mutex);
lseek(fd,bufSize*sizeof(int),SEEK_SET);
read(fd,(char*)&data,sizeof(int));
buf_out = (buf+1)%bufSize;
lseek(fd,bufSize*sizeof(int),SEEK_SET);
write(fd,(char*)&data,sizeof(int));
sem_post(sem_mutex);
sem_post(sem_empty);
}
return 0;
}
else if(p < 0)
{
//既不是子进程也不是父进程
perror("消费者进程创建失败!\n");
return -1;
}
}
//删除信号量
sem_unlink("empty");
sem_unlink("full");
sem_unlink("mutex");
//关闭文件
close(fd);
return 0;
}
1.去掉pc.c中所有与信号量有关的代码,再运行程序,执行效果有什么变化吗?为啥会这样呢?

答案(自己想的,不知道对不对):
信号量的作用是为了使不同的进程在并发执行时能够协调有序的向前推进。
* /mio req /we 0 0 0 内存写 /mwr 0 0 1 内存读 /mrd 0 1 0 i/o写 /wr 0 1 1 i/o读 /rd 1 x x 不用 * th-union 内存片选信号 dc5 138 a15 a14 a13 gnd mmreq vcc c b a g2a g2b g1 y0 y1 y2 y3 y4 y5 y6 y7 0000~1fff 2000~3fff 4000~5fff 6000~7fff 8000~9fff a000~bfff c000~dfff e000~ffff dc5 74ls138: 3-8译码器 另外一片 74ls138 译码器芯片接收地址总线低位字节的最高 4 位地址信息(最高一位恒定为 1 ),当需要接口电路工作时,由这片译码器产生接口芯片的8个片选信号,已选择哪一个接口电路可以读写。设有一组共享数据db和两组并发进程, 一组进程只对此组数据执行 读操作, 另一组进程可对此组数据执行写操作(当然同时也可以执行读 操作),我们将前一组进程称作读者,后一组进程称作写者.为了保 证共享数据的完整性,要求: (1)多个读者的操作可以同时进行。这也是一个非常经典的多线程题目,题目大意如下:有一个写者很多读者,多个读者可以同时读文件,但写者在写文件时不允许有读者在读文件,同样有读者读时写者也不能写。
如果没有信号量的话,如果文件中还没有数据,然后消费者进程就一调度就会阻塞,生产者开始写数据了,就需要唤醒消费者进程了,但是没有信号量,就达不到唤醒的作用。
2)你不会面对着各种复杂的程序分支结构和编程技巧,实验例子都使用很纯粹的汇编代码编写。我们写的程序最终都是要用编译器,进行编译链接形成一段机器可以知道的二进制代码,接着存到一个内存中操作系统 哈工大,这时候每一段程序代码都会有自己的一个地址,计算机按照地址增1,依次执行这段代码,当遇到代码调用别的函数的时候,这时候就要存储目前程序执行的很多状态呀,把这些东西放入堆栈里面,然后去执行被调用的函数,执行完之后再返回原来的程序断点处继续执行。i编写java类代码一 立上f编译成字节代码产生c头文件li编写jni实现代码ili编译连接库文件i图2-7jni运行流程图程序的平台无关性与java程序所调用的c/c++代码相关,java本身无法克服。
Producer()
{
P(Mutex); //互斥信号量
生产一个产品item;
P(Empty); //空闲缓存资源
将item放到空闲缓存中;
V(Full); //产品资源
V(Mutex);
}
Consumer()
{
P(Mutex);
P(Full);
从缓存区取出一个赋值给item;
V(Empty);
消费产品item;
V(Mutex);
}
答案:
不可行。对于某生产者,当mutex=1,empty=0时,申请以后变为:mutex=0,empty=-1,阻塞。
同时对于某消费者,此时mutex=0,申请后变为mutex=-1,也发生阻塞,所以会产生死锁。
(生产者被阻塞等待消费者唤醒,但是消费者因为mutex没有被释放也阻塞)
HIT-OS-LAB参考资料:
1.《操作系统原理、实现与实践》-李治军、刘宏伟 编著
2.《Linux内核完全注释》
3.两个哈工大同学的实验源码
4.Linux-0.11源代码
(上述资料,如果有需要的话,请主动联系我))
该实验的参考资料
官方文档
参考实验报告
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/jisuanjixue/article-106763-2.html
还有不要说密封没空气
若可以住人
还不是老百姓从血汗中得来
有舍有得