一、实验目的:
1、了解进程的创建,进程检查;
2、了解与进程有关函数的实现。如:fork(),exit(),wait(),getpid(),getppid()等
3、了解在多用户环境下进程之间的通信机制, 掌握用共享内存及消息队列实现进程之间的信息传递。
4、掌握进程并发执行的原理,及其所引起的同步、互斥问题的方法
二、实验要求:
(1) 掌握在Linux操作系统下,用共享内存方式及消息队列方式实现进程之间通信各个系统调用函数的使用、参数设置等。
(2) 掌握在Linux操作系统下,信号量机制相关系统调用函数的使用、参数设置等。
(3) 用C语言实现。
三、实验内容
(1) 信号量机制
1、信号量机制的基本操作:
资源使用者在使用临界资源之前“等待”信号量
资源使用者使用完临界资源后“通知”信号量
2、C语言实现:
注意:以下函数原形在 sys/types.h、sys/ipc.h和sys/sem.h包含文件中。
A)创建信号量集
int semget(key_t key,int nsems,int semflg)
其中:key—创建信号量集关键字。
nsems—信号量集中信号量的数量。
semflg—指定选项及其权限位。
IPC_CREAT—创建新的信号量集
IPC_EXCEL—如果信号量集已经存在,则返回错误。
<XXX XXX XXX>--和文件、目录一样权限。
返回一个信号量集ID--semid
B) 获得一个已经存在的信号量集
int semget(key_t key,0,0)
key—含义同上。
C)等待、通知一个信号量集
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—指定信号量集中操作的信号量个数。
D) 控制信号量集的操作
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;
本参数主要用于存放各个信号量的初始值。
四、实验要求:
1、 编写程序,解决生产者及消费者问题。分别创建6个生产者及消费者进程,使用信号量机制实现生产者及消费者进程间的同步及互斥。每个生产者进程随机睡眠0~9秒模拟生产数据的过程,然后把自己的进程号写入共享存储区,每个消费者进程从中读取数据并输出并且同步输出自己的进程号。要求程序结果可完整演示生产者及消费者同步生产数据及接受数据的全过程。
程序源码:
Producer_consumer.c:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <fcntl.h>
#define SHM_SIZE 1024 // 共享存储区大小
#define BUFFER_SIZE 10 // 缓冲区大小
int shmid; // 共享存储区ID
char *shmaddr; // 共享存储区指针
sem_t *empty, *full, *mutex; // 信号量
int in = 0, out = 0, count = 0; // 缓冲区指针和计数器
void *producer(void *arg) {
int id = *(int *)arg;
while (1) {
int data = rand() % 100; // 随机生成一个0~99的数
sleep(rand() % 10); // 随机睡眠0~9秒
sem_wait(empty); // 等待空缓冲区
sem_wait(mutex); // 互斥访问缓冲区
printf("Producer %d produced data: %d\n", id, data);
shmaddr[in] = data; // 写入共享存储区
in = (in + 1) % BUFFER_SIZE;
count++;
sem_post(mutex); // 解锁缓冲区
sem_post(full); // 发送满信号
}
}
void *consumer(void *arg) {
int id = *(int *)arg;
while (1) {
sleep(rand() % 10); // 随机睡眠0~9秒
sem_wait(full); // 等待满缓冲区
sem_wait(mutex); // 互斥访问缓冲区
int data = shmaddr[out]; // 读取共享存储区
printf("Consumer %d consumed data: %d\n", id, data);
out = (out + 1) % BUFFER_SIZE;
count--;
sem_post(mutex); // 解锁缓冲区
sem_post(empty); // 发送空信号
}
}
int main() {
// 创建共享存储区
if ((shmid = shmget(IPC_PRIVATE, SHM_SIZE, IPC_CREAT | 0666)) == -1) {
perror("shmget");
exit(1);
}
// 获取共享存储区指针
if ((shmaddr = shmat(shmid, 0, 0)) == (char *)-1) {
perror("shmat");
exit(1);
}
// 初始化信号量
empty = sem_open("empty", O_CREAT, 0666, BUFFER_SIZE);
full = sem_open("full", O_CREAT, 0666, 0);
mutex = sem_open("mutex", O_CREAT, 0666, 1);
// 创建生产者和消费者线程
pthread_t pro[6], con[6];
int id[6];
for (int i = 0; i < 6; i++) {
id[i] = i+1;
pthread_create(&pro[i], NULL, producer, &id[i]);
pthread_create(&con[i], NULL, consumer, &id[i]);
}
// 等待线程结束
for (int i = 0; i < 6; i++) {
pthread_join(pro[i], NULL);
pthread_join(con[i], NULL);
}
// 关闭信号量
sem_close(empty);
sem_close(full);
sem_close(mutex);
// 删除共享存储区
if (shmctl(shmid, IPC_RMID, 0) == -1) {
perror("shmctl");
exit(1);
}
return 0;
}
运行结果展示:
五.实验总结
在这个生产者-消费者问题的实验中,我们使用了共享存储区和信号量来实现线程之间的同步和互斥。通过创建多个生产者和消费者线程,我们模拟了生产者和消费者的并发工作。通过这个实验,我们可以更好地理解并发编程中的同步和互斥问题,以及如何利用共享存储区和信号量来解决这些问题。同时,我们也学会了在Linux终端下编译和运行多线程程序的基本步骤。