一.实验目的
1)了解进程的创建,进程检查;
2)了解与进程有关函数的实现。如:fork(),exit(),wait(),getpid(),getppid()等
3)了解在多用户环境下进程之间的通信机制, 掌握用共享内存及消息队列实现进程之间的信息传递。
4)掌握进程并发执行的原理,及其所引起的同步、互斥问题的方法
二.实验要求
(1) 掌握在Linux操作系统下,用共享内存方式及消息队列方式实现进程之间通信各个系统调用函数的使用、参数设置等。
(2) 掌握在Linux操作系统下,信号量机制相关系统调用函数的使用、参数设置等。
(3) 用C语言实现。
三.实验内容
3.1 信号量机制
3.1.1信号量机制的基本操作
资源使用者在使用临界资源之前“等待”信号量
资源使用者使用完临界资源后“通知”信号量
3.2C语言实现
注意:以下函数原形在 sys/types.h、sys/ipc.h和sys/sem.h包含文件中。
3.2.1创建信号量集
int semget(key_t key,int nsems,int semflg)
其中:key—创建信号量集关键字。
nsems—信号量集中信号量的数量。
semflg—指定选项及其权限位。
IPC_CREAT—创建新的信号量集
IPC_EXCEL—如果信号量集已经存在,则返回错误。
<XXX XXX XXX>--和文件、目录一样权限。
返回一个信号量集ID--semid
3.2.2获得一个已经存在的信号量集
int semget(key_t key,0,0)
key—含义同上。
3.2.3等待、通知一个信号量集
int semop(int semid,struct sembuf *sops,unsigned nsops)
semid—由函数semget()产生的。
sops—描述信号量的操作:等待、通知等
struct sembuf{
short sem_num;/*semaphore number */
short sem_op; /*semaphore operation:-1 for waiting*/
short sem_flg; /* operation flag :0、IPC_NOWAIT */
}
nsops—指定信号量集中操作的信号量个数。
3.2.4控制信号量集的操作
int semctl(int semid,int semnum,int cmd,union semun arg)
semid—由semget()创建的信号量集标识。
semnum—信号量数量。
cmd—对信号量semid所进行的操作:SETALL—所有参数。
arg—对信号量集操作的初始数据。
arg的类型:
union semun{
int val; /* 仅用于参数SETVAL*/
struct semid_ds *buf; /*指向IPC_STAT和IPC_SET的semid_ds结构*/
ushort *array; /*用于GETALL和SETALL:指向一个初值的数*/
};
union semun arg;
本参数主要用于存放各个信号量的初始值。
四.实验要求
4.1 编写程序,解决生产者及消费者问题。
要求:分别创建6个生产者及消费者进程,使用信号量机制实现生产者及消费者进程间的同步及互斥。每个生产者进程随机睡眠0~9秒模拟生产数据的过程,然后把自己的进程号写入共享存储区,每个消费者进程从中读取数据并输出并且同步输出自己的进程号。要求程序结果可完整演示生产者及消费者同步生产数据及接受数据的全过程。
4.2代码展示:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <time.h>
#define SHM_SIZE 6
#define NUM_PRODUCERS 6
#define NUM_CONSUMERS 6
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
void producer(int sem_id, int *shm_ptr, int *turn) {
while (1) {
sleep(rand() %10);
struct sembuf wait_operation = {0, -1, 0};
semop(sem_id, &wait_operation, 1);
int data;
printf("请输入要生产的数据: ");
scanf("%d", &data);
shm_ptr[(*turn)++] = data;
printf("生产者 %d 生产了数据: %d\n", getpid()%6, data);
if (*turn == SHM_SIZE) {
*turn = 0;
}
struct sembuf signal_operation = {0, 1, 0};
semop(sem_id, &signal_operation, 1);
}
}
void consumer(int sem_id, int *shm_ptr, int consumer_id) {
while (1) {
struct sembuf wait_operation = {0, -1, 0};
semop(sem_id, &wait_operation, 1);
int data = shm_ptr[consumer_id]; // 每个消费者根据自己的ID来读取数据
printf("消费者 %d 消费了数据: %d\n", consumer_id, data);
struct sembuf signal_operation = {0, 1, 0};
semop(sem_id, &signal_operation, 1);
}
}
int main() {
key_t key = ftok("/tmp", 'a');
int sem_id = semget(key, 1, IPC_CREAT | 0666);
union semun arg;
arg.val = 1;
semctl(sem_id, 0, SETVAL, arg);
int shm_id = shmget(key, SHM_SIZE * sizeof(int), IPC_CREAT | 0666);
int *shm_ptr = (int *)shmat(shm_id, NULL, 0);
int turn = 0;
for (int i = 0; i < NUM_PRODUCERS; i++) {
if (fork() == 0) {
srand(getpid());
producer(sem_id, shm_ptr, &turn);
exit(0);
}
}
int current_consumer_id = 0; // 初始化第一个消费者的ID为0
for (int i = 0; i < NUM_CONSUMERS; i++) {
if (fork() == 0) {
consumer(sem_id, shm_ptr, current_consumer_id);
exit(0);
}
current_consumer_id++; // 为下一个消费者分配一个不同的ID
}
for (int i = 0; i < NUM_PRODUCERS + NUM_CONSUMERS; i++) {
wait(NULL);
}
shmdt(shm_ptr);
shmctl(shm_id, IPC_RMID, NULL);
semctl(sem_id, 0, IPC_RMID);
return 0;
}
4.3结果展示:
4.4解析一下这些代码:
这段代码是一个生产者-消费者问题的解决方案,使用了共享内存和信号量来实现生产者和消费者之间的同步和通信。
首先,代码定义了一些常量和结构体,包括共享内存的大小、生产者和消费者的数量,以及一个用于信号量操作的结构体semun。
接下来,实现了两个函数:producer和consumer即生产者和消费者。producer函数用于生产数据并将数据写入共享内存,而consumer函数用于从共享内存中读取数据进行消费。在这两个函数中,使用了信号量来实现对共享内存的互斥访问,以确保生产者和消费者之间的同步。
在main函数中,代码首先创建了一个信号量和共享内存,并初始化了共享内存中的turn变量。然后,使用fork函数创建了多个生产者和消费者进程,每个进程都会调
相应的生产者或消费者函数来进行数据的生产和消费。最后,使用wait函数等待所有进程执行完毕后,释放了共享内存和信号量。
五.实验总结
1)在实验中,我们可以观察到多个进程同时执行的情况,这展示了操作系统中的并发执行特性。
2)通过使用信号量等同步机制,我们可以实现对共享资源的互斥访问,确保多个进程不会同时对共享资源进行写操作,从而避免数据的混乱和错误。
3)实验中的生产者和消费者模型展示了进程之间的同步通信,生产者和消费者通过共享内存进行数据交换,并通过信号量等机制实现了同步操作。
4)我们需要注意避免死锁和饥饿的情况,即多个进程因为争夺资源而陷入无限等待的状态,或者某些进程长时间无法获得所需资源的情况。