🌸🌸🌸前期回顾:🌸🌸🌸
目录🌝🌝🌝
🌻🌻(1)semget函数:建立新的信号量对象或获取已有对象的标识符。
🌻🌻(3) semop函数:改变信号量对象中各个信号量的状态。
🌹🌹一、使用信号量机制来模拟实现PV操作,控制多进程对共享资源的互斥访问。
are you ready? go!
前言:🍀🍀
🌻🌻🌻按照惯例,在实验前,我们需要先了解一些基本知识~
【实验资料】 🍀🍀🍀
一、什么是信号量呢🤓🤓🤓?
信号量是一个计数器,用来控制多个进程对共享资源的使用。进程为了获得共享资源,需要执行下列操作:
(1) 测试控制该资源的信号量。
(2) 若此信号量的值为正,则进程可以使用该资源。进程将信号量值减1,表示它使用了一个资源单位。
(3) 若此信号量的值为0,则进程进入睡眠状态,直至信号量值大于0。若进程被唤醒后,它返回至(第(1)步)。
信号量通常是在内核中实现的。常用的信号量形式被称之为双态信号量。它控制单个资源,其初始值为1。但是,一般而言,信号量的初值可以是任一正值,该值说明有多少个共享资源单位可供共享应用。
二、 相关系统调用👀👀
🌻🌻1、ftok函数:生成关键字。
系统建立IPC通讯 (消息队列、信号量和共享内存) 时必须指定一个ID值。通常情况下,该ID值通过ftok函数得到。使用 ftok创建共享内存,毫无关系的进程,可以通过得到同样的key,来操作同一个共享内存,对共享内存进行读写时,需要利用信号量进行同步或互斥。
头文件:#include <sys/types.h>
#include <sys/ipc.h>
2、以下信号量相关的操作函数有相同的头文件:
#incldue <sys/types.h>
#include <sys/ipc.h>
#include <sem.h>
🌻🌻(1)semget函数:建立新的信号量对象或获取已有对象的标识符。
int semget(key_t key,int nsems,int semflg);
参数nsems是信号量对象所特有的,指定了新生成的信号量对象中信号量的数目。如果函数执行的是打开而不是创建操作,则这个参数被忽略。flag是用户设置的标志,如IPC_CREAT。IPC_CREAT表示若系统中尚无指名的共享存储区,则由核心建立一个信号量;若系统中已有信号量,便忽略IPC_CREAT。
例:semid=semget(key, nsems,(IPC_CREAT|0400))创建一个关键字为key的信号量.
函数调用成功时返回信号量标识符,失败时则返回-1。
🌻🌻(3) semop函数:改变信号量对象中各个信号量的状态。
它的声明格式为:
int semop(int semid,struct sembuf *sops,unsigned nsops);
参数semid是要操作的信号量对象的标识符,sops是sembuf的数组,它定义了semop函数要进行的操作序列,nsops保存了sops数组的长度,即semop函数将进行操作的个数。
该函数调用成功时返回0,失败时则返回-1。
🌻🌻(4) semctl函数:直接对信号量对象进行控制。
int semctl(int semid,int semnum,int cmd,union semun arg);
semid:对象的标识符,semnum:信号量在集合中的序号,二者唯一确定一个信号量。
参数arg提供了操作所需要的一些信息。参数cmd指定了函数进行的具体操作:IPC_RMID,从内存中删除信号量对象;SETVAL,用arg参数中val成员的值来设定对象内某个信号量的值;GETVAL,返回信号量的计数值。
本文案例可供参考(直接ctrl+C/V到作业嘿嘿~🌝🌝)***
正文:🍀🍀
【实验目的】🍀🍀🍀
1、熟悉临界资源、信号量及 PV 操作的定义与物理意义;
2、了解进程间通信机制,熟悉信号量机制,掌握进程互斥与进程同步的相关知识;掌握用信号量机制解决进程之间的同步与互斥问题;
3.实现生产者-消费者问题,进一步理解进程同步问题。
【实验内容】🍀🍀🍀
🌹🌹一、使用信号量机制来模拟实现PV操作,控制多进程对共享资源的互斥访问。
1、参考代码:🌛🌛🌛
/********** sem_pv.c *************************/
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
typedef union semun{ //semun是用来操作信号量的 共用体!!!
int val; //信号量的值
struct semid_ds *buf; //缓冲区
unsigned short *array; //记录生产消费者数量的数组
}semun;
//初始化信号量
int sem_init(int key)
{
int semid; //对象的标识符
semun arg; //信号量的新名称
if((semid=semget(key,1,0660|IPC_CREAT|IPC_EXCL))<0) //创建包含1个信号量的信号量对象
{
perror("sem_init:semget error");
exit(1);
}
arg.val=1;
if(semctl(semid,0,SETVAL,arg)<0) //给信号量赋初值为1
{
perror("sem_init:semctl error");
exit(2);
};
return semid; //返回对象标识符
}
//信号量的P操作
int sem_p(int semid)
{
struct sembuf pbuf;
pbuf.sem_num=0;
pbuf.sem_op=-1;
pbuf.sem_flg=SEM_UNDO;
if(semop(semid,&pbuf,1)==-1)
{
perror("sem_p:semop error");
exit(3);
}
return 0;
}
//信号量的V操作
int sem_v(int semid)
{
struct sembuf vbuf;
vbuf.sem_num=0;
vbuf.sem_op=1;
vbuf.sem_flg=SEM_UNDO;
if(semop(semid,&vbuf,1)==-1)
{
perror("sem_v:semop error");
exit(3);
}
return 0;
}
//删除信号量
void sem_rmv(int semid)
{
if(semctl(semid,0,IPC_RMID)<-1)
{
perror("sem_rmv:semctl error");
exit(2);
}
}
int main(void)
{
key_t key;
int pid,fd,semid,n;
char str[80];
key=ftok(".",5); //将fname设置为当前目录
semid=sem_init(key); //初始化信号量
fd=open("test.txt",O_RDWR|O_CREAT|O_TRUNC,0644);//打开指定文件
while((pid=fork())==-1); //创建进程
if(pid==0) //子进程(模拟消费者)
{
sleep(1);
sem_p(semid);
lseek(fd,SEEK_SET,0); //lseek是打开文件设置偏移量,文件偏移量设置为,距文件开始处
read(fd,str,sizeof(str));
sem_v(semid);
printf("child:read str from test file:%s\n",str); //从文件中读出这个字符串
exit(0);
}
else //父进程(模拟生产者)
{
sem_p(semid);
printf("parent:please enter a str for test file(strlen<80):\n");//写入文件字符串
fgets(str,80,stdin);
n=strlen(str);
lseek(fd, SEEK_SET,0);
write(fd,str,n);
sem_v(semid);
wait(0);
close(fd);
sem_rmv(semid);
exit(0);
}
}
2、运行结果:🌜🌜🌜
3、分析结果:⭐⭐⭐
本实验主要通过信号量模拟机制,来实现PV操作。父进程即代表生产者,子进程代表消费者。父进程向buffer(文件)内写入字符串,子进程从buffer(文件)内读出字符串。程序中的sem_p (),sem_v()都是原子操作。由结果可知,能够达到预期效果。
🌹🌹🌹 二、编程解决苹果桔子问题
桌上有1空盘,允许存放1个水果。爸爸向盘中放苹果,也可以向盘中放桔子。儿子专等吃盘中的桔子,女儿专等吃盘中的苹果。规定当盘空时一次只能放1个水果供吃者取用。请用PV操作实现爸爸、儿子、女儿三个并发进程的同步。(具体实现时,可以设置爸爸放水果次数,例如放五次水果,三次放苹果,两次放桔子。)
1、参考代码:🌛:🌛:🌛:🌛
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <semaphore.h>
#include <pthread.h>
#define P sem_wait
#define V sem_post
#define full_apple &fullA
#define full_orange &fullB
#define empty &empty_aha
sem_t fullA;
sem_t fullB;
sem_t empty_aha;
int num=0,num1=0,num2=0;
void* Dad(void *p)
{
while (num<5)
{ if(num1<3)
{ P(empty);
num++;
num1++;
printf("dad put an apple%d\n",num1);
V(full_apple);}
if (num2<2){
P(empty);
num++;
num2++;
printf("dad put an orange%d\n",num2);
V(full_orange);}
}
}
void* Daughter(void *p)
{
while(num<5)
{
P(full_apple);
printf("daughter eat an apple%d\n",num1);
V(empty);
}
}
void* Son(void *p)
{
while(num<5)
{
P(full_orange);
printf("son eat an orange%d\n",num2);
V(empty);
}
}
int main()
{
sem_init(full_apple,0,0);
sem_init(full_orange,0,0);
sem_init(empty,0,1);
pthread_t tid0;
pthread_t tid1;
pthread_t tid2;
pthread_t tid3;
pthread_create(&tid0,NULL,Dad,NULL);
pthread_create(&tid1,NULL,Dad,NULL);
pthread_create(&tid2,NULL,Son,NULL);
pthread_create(&tid3,NULL,Daughter,NULL);
getchar();
pthread_exit(0);
return 0;
}
2、运行结果:🌜🌜🌜
3、分析结果:⭐⭐⭐
整体思路:可以将盘子看作是缓冲区,它的容量是1,所以是互斥信号量;
同步关系:
父亲将苹果放入盘子后,女儿才可以取苹果,否则必须等待;
父亲将橘子放入盘子后,儿子才可以取橘子,否则必须等待;
只有盘子为空时,父亲才能放入水果,否则必须等待。
编写代码思路:进行具体问题的pv操作时,由于是多线程的运行,所以我们在编译时加上-pthread来进行多线程的函数调用。要进行pv操作,首先需要掌握特定的函数,例如信号量的设置,标识符的设定。
【实验心得】:🍀🍀
本次实验主要是针对操作系统中的进程间同步和互斥等知识的运用。在实验中,掌握了Linux和Windows下共享内存的创建、使用和销毁,掌握了信号量的申请、使用记忆销毁,对Linux和Windows下关于共享内存、信号量创建、使用、销毁以及进程创建、销毁的系统调用有了更深刻的认识和理解。在实验中,掌握了如何通过信号量实现进程间的同步和互斥。要注意释放已申请的共享内存和信号量。
坚持完成的小伙伴最棒! JUJUJ为你打call~