共享内存
OVERVIEW
- 共享内存
- 一、文件上锁flock
- 二、共享内存
- 1.关联共享内存ftok
- 2.获取共享内存shmget
- 3.绑定共享内存shmat
- 4.绑定分离shmdt
- 5.控制共享内存shmctl
- 三、亲缘进程间通信
- 1.共享内存写入与读取
- 2.共享内存解绑与删除
- 3.共享内存综合
- 四、非亲缘进程间通信
- 1.通过sleep同步
- 2.通过条件变量同步
一、文件上锁flock
可以利用flock系统调用实现,当有一个进程在文件中进程写入操作时,其他进程无法在该文件中进行写操作(只能进行读操作)。
- 多进程实现前n项数求和(利用flock实现)
#include "head.h"
struct data {
int now;//中间结果
int sum;//求和结果
};
void getnum(struct data *d) {
int fd;
if ((fd = open(".data", O_RDONLY)) < 0) {
perror("setopen");
exit(1);
}
read(fd, (void *)d, sizeof(struct data));
close(fd);
}
void setnum(struct data *d) {
int fd;
if ((fd = open(".data", O_RDWR | O_CREAT, 0600)) < 0) {//只有文件的所属用户(当前的进程)有访问以及写的权限
perror("getopen");
exit(1);
}
write(fd, (void *)d, sizeof(struct data));
close(fd);
}
void doSum(struct data *d, int max, int i) {
int fd_lock;
//将lock标志给到某个文件上(.lock) 通过该文件判断进程是否有资格打开另一个被保护的文件(.data)
if ((fd_lock = open(".lock", O_RDONLY)) < 0) {
perror("lockopen");
exit(1);
}
while (1) {//计算的过程需要上锁
flock(fd_lock, LOCK_EX);//加锁(加锁后其他进程后面计算的语句将无法执行)
getnum(d);//从.data取出上个结果
if (d->now >= max) break;//判断 计算结果
d->now++;
d->sum += d->now;
setnum(d);//将计算的结果放回.data
printf("<i am the %dth child> now = %d, sum = %d\n", i, d->now, d->sum);
flock(fd_lock, LOCK_UN);//为解锁
}
close(fd_lock);
}
int main(int argc, char *argv[]) {
//多进程实现求前n项和 同一时刻只有一个进程持有该文件
//计算从0到n的和 m个进程
//a.out -i -n n
int opt;
int ins = 1, max = 100;
struct data d;
d.now = 0;
d.sum = 0;
setnum(&d);
while ((opt = getopt(argc, argv, "i:n:")) != -1) {
switch (opt) {
case 'i':
ins = atoi(optarg);
break;
case 'n':
max = atoi(optarg);
break;
default:
fprintf(stderr, "Usage : %s -i num1 -n num2", argv[0]);
exit(1);
}
}
int i;
pid_t pid;
for (i = 0; i < ins; ++i) {//创建ins个进程对文件进行doSum操作
if ((pid = fork()) < 0) {
perror("fork()");
exit(1);
}
if (pid == 0) break;
}
if (pid == 0) {
doSum(&d, max, i);
} else {
for (int k = 0; k < ins; ++k) wait(NULL);
}
return 0;
}
二、共享内存
允许两个或多个进程共享一个给定的存储区,由于无需复制数据,这是最快的IPC进程间通信。
1.关联共享内存ftok
#include "head.h"
int main() {
//ftok将projectId与文件名字转换为键值对
key_t key;
if ((key = ftok("1.ftok.c", 123)) < 0) {
perror("ftok");
exit(1);
}
printf("key = 0x%x\n", key);
printf("123 = 0x%x\n", 123);
return 0;
}
2.获取共享内存shmget
#include "head.h"
int main() {
//1.申请一块共享内存 将projectId与文件名字转换为 共享内存键值
key_t key;
if ((key = ftok("1.ftok.c", 123)) < 0) {
perror("ftok");
exit(1);
}
printf("key = 0x%x\n", key);
//2.根据共享内存对应的key值 和内存大小size 得到共享内存的标识符
int shmid;
if ((shmid = shmget(key, 4096, IPC_CREAT | 0666)) < 0) {
perror("shmget");
exit(1);
}
printf("shmid = %d\n", shmid);
return 0;
}
3.绑定共享内存shmat
#include "head.h"
int main() {
//1.申请一块共享内存 将projectId与文件名字转换为 共享内存键值
key_t key;
if ((key = ftok("1.ftok.c", 123)) < 0) {
perror("ftok");
exit(1);
}
printf("key = 0x%x\n", key);
//2.根据共享内存对应的key值 和内存大小size 得到共享内存的标识符
int shmid;
if ((shmid = shmget(key, 4096, IPC_CREAT | 0666)) < 0) {
perror("shmget");
exit(1);
}
printf("shmid = %d\n", shmid);
//3.将进程的动态内存空间和 共享内存空间关联
void *shmemory = NULL;
if ((shmemory = shmat(shmid, NULL, 0)) == (void*)-1) {
perror("shmat");
exit(1);
}
sleep(10);
return 0;
}
4.绑定分离shmdt
#include "head.h"
int main() {
//1.申请一块共享内存 将projectId与文件名字转换为 共享内存键值
key_t key;
if ((key = ftok("1.ftok.c", 123)) < 0) {
perror("ftok");
exit(1);
}
printf("key = 0x%x\n", key);
//2.根据共享内存对应的key值 和内存大小size 得到共享内存的标识符
int shmid;
if ((shmid = shmget(key, 4096, IPC_CREAT | 0666)) < 0) {
perror("shmget");
exit(1);
}
printf("shmid = %d\n", shmid);
//3.将进程的动态内存空间和 共享内存空间关联
void *shmemory = NULL;
if ((shmemory = shmat(shmid, NULL, 0)) == (void*)-1) {
perror("shmat");
exit(1);
}
//4.将进程的动态内存空间和 共享内存空间关联解除
int flag;
if ((flag = shmdt(shmemory)) < 0) {
perror("shmdt");
exit(1);
}
return 0;
}
5.控制共享内存shmctl
#include "head.h"
int main() {
//1.申请一块共享内存 将projectId与文件名字转换为 共享内存键值
key_t key;
if ((key = ftok("1.ftok.c", 123)) < 0) {
perror("ftok");
exit(1);
}
printf("key = 0x%x\n", key);
//2.根据共享内存对应的key值 和内存大小size 得到共享内存的标识符
int shmid;
if ((shmid = shmget(key, 4096, IPC_CREAT | 0666)) < 0) {
perror("shmget");
exit(1);
}
printf("shmid = %d\n", shmid);
//3.将进程的动态内存空间和 共享内存空间关联
void *shmemory = NULL;
if ((shmemory = shmat(shmid, NULL, 0)) == (void*)-1) {
perror("shmat");
exit(1);
}
//4.将进程的动态内存空间和 共享内存空间关联解除
int flag;
if ((flag = shmdt(shmemory)) < 0) {
perror("shmdt");
exit(1);
}
//5.shmctl删除共享内存空间
if ((flag = shmctl(shmid, IPC_RMID, NULL)) < 0) {
perror("shmctl");
exit(1);
}
return 0;
}
三、亲缘进程间通信
1.共享内存写入与读取
#include "head.h"
//共享内存综合运用
int main() {
key_t key;
int shmid;
pid_t pid;
char *shmemory = NULL;
//1.开辟一块共享内存空间
//(1)申请一块共享内存 将projectId与文件名字转换为 共享内存键值
if ((key = ftok("1.ftok.c", 123)) < 0) {
perror("ftok");
exit(1);
}
//(2)根据共享内存对应的key值 和内存大小size 得到共享内存的标识符
if ((shmid = shmget(key, 4096, IPC_CREAT | 0666)) < 0) {
perror("shmget");
exit(1);
}
//(3)将进程的动态内存空间和 共享内存空间关联
if ((shmemory = shmat(shmid, NULL, 0)) == (void *)-1) {
perror("shmat");
exit(1);
}
//2.创建子进程 进行运算操作
if ((pid = fork()) < 0) {
perror("fork");
exit(1);
}
if (pid) {//父进程写入
while(1) {
printf("i am the father : \n");
scanf("%[^\n]s", shmemory);//向共享内存中写入
getchar();//吞掉回车否则不停循环
sleep(2);
}
} else {//子进程读出
while (1) {
sleep(1);
if (strlen(shmemory)) printf("i am the child : %s\n", shmemory);//如果共享内存空间中有数据才进行输出
memset(shmemory, 0, 4096);//临时清空共享存储空间
}
}
return 0;
}
2.共享内存解绑与删除
#include "head.h"
//共享内存综合运用
int main() {
key_t key;
int shmid;
pid_t pid;
char *shmemory = NULL;
//1.开辟一块共享内存空间
//(1)申请一块共享内存 将projectId与文件名字转换为 共享内存键值
if ((key = ftok("1.ftok.c", 123)) < 0) {
perror("ftok");
exit(1);
}
//(2)根据共享内存对应的key值 和内存大小size 得到共享内存的标识符
if ((shmid = shmget(key, 4096, IPC_CREAT | 0666)) < 0) {
perror("shmget");
exit(1);
}
//(3)将进程的动态内存空间和 共享内存空间关联
if ((shmemory = shmat(shmid, NULL, 0)) == (void *)-1) {
perror("shmat");
exit(1);
}
//2.创建子进程 利用shmdt实现 一次读取一次读入操作
if ((pid = fork()) < 0) {
perror("fork");
exit(1);
}
if (pid) {
printf("i am the father : \n");
scanf("%[^\n]s", shmemory);
getchar();
} else {
sleep(5);
if (strlen(shmemory)) printf("i am the child : %s\n", shmemory);
memset(shmemory, 0, 4096);
}
//3.删除开辟的共享内存空间
//(1)将进程的动态内存空间和 共享内存空间关联解除
int flag;
if ((flag = shmdt(shmemory)) < 0) {
perror("shmdt");
exit(1);
}
//(2)shmctl删除共享内存空间(父进程执行)
if (pid) {
wait(NULL);
if ((flag = shmctl(shmid, IPC_RMID, NULL)) < 0) {
perror("shmctl");
exit(1);
}
}
sleep(5);
return 0;
}
3.共享内存综合
- 利用共享内存实现多进程前n项数求和(利用共享内存实现)
#include "head.h"
struct data {
int now;//中间结果
int sum;//求和结果
};
void doSum(struct data *d, int max, int i) {
while (1) {//计算的过程需要上锁
if (d->now >= max) break;//判断 计算结果
d->now++;
d->sum += d->now;
printf("<i am the %dth child> now = %d, sum = %d\n", i, d->now, d->sum);
}
}
int main(int argc, char *argv[]) {
//1.命令行解析 a.out -i -n n
int opt;
int ins = 1, max = 100;
// struct data d;
// d.now = 0;
// d.sum = 0;
// setnum(&d);
while ((opt = getopt(argc, argv, "i:n:")) != -1) {
switch (opt) {
case 'i':
ins = atoi(optarg);
break;
case 'n':
max = atoi(optarg);
break;
default:
fprintf(stderr, "Usage : %s -i num1 -n num2", argv[0]);
exit(1);
}
}
//2.共享内存的创建于绑定
key_t key;
int shmid;
struct data *shmemory = NULL;
//2.1申请一块共享内存 将projectId与文件名字转换为 共享内存键值
if ((key = ftok("5.shm_sum.c", 123)) == -1) {
perror("ftok");
exit(1);
}
//2.2根据共享内存对应的key值 和内存大小size 得到共享内存的标识符
if ((shmid = shmget(key, sizeof(struct data), IPC_CREAT | 0600)) < 0) {
perror("shmget");
exit(1);
}
//2.3将进程的动态内存空间和 共享内存空间关联
if ((shmemory = (struct data *)shmat(shmid, NULL, 0)) == (struct data *)-1) {
perror("shmat");
exit(1);
}
shmemory->now = 0;
shmemory->sum = 0;
//3.创建ins个子进程对文件进行doSum操作
int i;
pid_t pid;
for (i = 0; i < ins; ++i) {
if ((pid = fork()) < 0) {
perror("fork()");
exit(1);
}
if (pid == 0) break;
}
if (pid == 0) {
doSum(shmemory, max, i);
} else {
for (int k = 0; k < ins; ++k) wait(NULL);
printf("%d\n", shmemory->sum);
}
return 0;
}
成功输出结果,前100项求和的结果为5050,但是当使用程序求前10000项和时,却会出现问题如图结果为50007661(错误结果)。
出现问题的原因是:执行中的进程没有保障,进程之间发生竞争(同一时刻多个进程对内存进行读写操作,发生在多核处理器)
可以利用条件变量实现线程同步机制(进程同步),从而避免资源抢占竞争。
四、非亲缘进程间通信
共享内存实现多进程计算,
- 单核不考虑同步关系,可以正常实现
- 多核不考虑同步关系,无法正常实现
- 需要设置同步关系
- 利用条件变量实现进程同步
非亲缘进程之间的通信,
1.通过sleep同步
- 使用共享内存实现两个非亲缘关系进程(1号进程、2号进程)进行通话
- 1号进程只输出2号进程在共享内存中输入的数据
- 2号进程只输出1号进程在共享内存中输入的数据
- 同步(通过sleep实现同步)
#include "head.h"
struct SHM {
int flag;//SHM能否读写
int type;//第几个进程
char mesg[50];//输入的信息
};
int main(int argc, char *argv[]) {
//1.命令行解析 ./a.out -t 1|2 -m message
int opt;
int type;
char mesg[50];
struct SHM *temp;//用于临时存放准备写入共享内存的数据
if (argc != 5) {
fprintf(stderr, "Usage : %s -t 1|2 -m message\n", argv[0]);
exit(1);
}
while ((opt = getopt(argc, argv, "t:m:")) != -1) {
switch (opt) {
case 't':
type = atoi(optarg);
break;
case 'm':
strcpy(mesg, optarg);
break;
default:
fprintf(stderr, "Usage : %s -t 1|2 -m message\n", argv[0]);
exit(1);
}
}
/**
*存在问题
*为什么直接在switch中使用 temp->type = atoi(optarg); 会出现segmentfault呢?
*必须使用临时变量 int type; 来转接数据才不会报错?
*/
temp->type = type;
strcpy(temp->mesg, mesg);
//2.共享内存的创建与绑定
key_t key;
int shmid;
struct SHM *shmemory = NULL;
//2.1申请一块共享内存 将projectId与文件名字转换为 共享内存键值
if ((key = ftok("1.shm_my.c", 123)) == -1) {
perror("ftok");
exit(1);
}
//2.2根据共享内存对应的key值 和内存大小size 得到共享内存的标识符
if ((shmid = shmget(key, sizeof(struct SHM), IPC_CREAT | IPC_EXCL | 0600)) < 0) {
if (errno == EEXIST) {//处理重复创建共享内存空间
if ((shmid = shmget(key, sizeof(struct SHM), 0600)) < 0) {
perror("shmget1");
exit(1);
}
printf("shmemory exist!");
} else {
perror("shmget2");
exit(1);
}
}
//2.3将进程的动态内存空间和 共享内存空间关联
if ((shmemory = (struct SHM *)shmat(shmid, NULL, 0)) == (struct SHM *)-1) {
perror("shmat");
exit(1);
}
//3.实现非亲缘进程间通信
shmemory->flag = 0;//初始状态为允许写入
while (1) {
if (!shmemory->flag) {
printf("<Process%d> : i get shmemory\n", temp->type);
sprintf(shmemory->mesg, "<Process%d> : <%s>", temp->type, temp->mesg);//向共享内存中写入内容
shmemory->flag = 1;
sleep(1);
} else {
printf("%s\n", shmemory->mesg);//从共享内存中读取 并输出内容
shmemory->flag = 0;
}
}
return 0;
}
2.通过条件变量同步
思考:如何通过条件变量以及互斥锁,通过共享内存空间实现多进程同步
- 一个进程1号进程,作为主要发送进程
- 多个进程,输出1号进程发送的数据
- 利用条件变量和互斥锁实现同步
#include "head.h"
struct SHM {
int type;//第几个进程
char mesg[50];//输入的信息
pthread_mutex_t mutex;//互斥锁
pthread_cond_t cond;//条件变量
};
int main(int argc, char *argv[]) {
//1.命令行解析 ./a.out -t 1|2
int opt;
int type;
if (argc != 3) {
fprintf(stderr, "Usage : %s -t 1|2\n", argv[0]);
exit(1);
}
while ((opt = getopt(argc, argv, "t:")) != -1) {
switch (opt) {
case 't':
type = atoi(optarg);
break;
default:
fprintf(stderr, "Usage : %s -t 1|2\n", argv[0]);
exit(1);
}
}
//2.共享内存的创建与绑定
key_t key;
int shmid;
struct SHM *shmemory = NULL;
//2.1申请一块共享内存 将projectId与文件名字转换为 共享内存键值
if ((key = ftok("2.shm_cond.c", 123)) == -1) {
perror("ftok");
exit(1);
}
//2.2根据共享内存对应的key值 和内存大小size 得到共享内存的标识符
if ((shmid = shmget(key, sizeof(struct SHM), IPC_CREAT | IPC_EXCL | 0600)) < 0) {
if (errno == EEXIST) {//处理重复创建共享内存空间
if ((shmid = shmget(key, sizeof(struct SHM), 0600)) < 0) {
perror("shmget1");
exit(1);
}
printf("shmemory exist!\n");
} else {
perror("shmget2");
exit(1);
}
}
//2.3将进程的动态内存空间和 共享内存空间关联
if ((shmemory = (struct SHM *)shmat(shmid, NULL, 0)) == (struct SHM *)-1) {
perror("shmat");
exit(1);
}
//3.实现非亲缘进程间通信
if (type == 1) {//让1号进程初始化互斥锁和信号量 并设为共享
pthread_mutexattr_t mutex;
pthread_condattr_t cond;
pthread_mutexattr_init(&mutex);
pthread_condattr_init(&cond);
pthread_mutexattr_setpshared(&mutex, 1);//设置为共享
pthread_condattr_setpshared(&cond, 1);//设置为共享
pthread_mutex_init(&shmemory->mutex, &mutex);//初始化锁
pthread_cond_init(&shmemory->cond, &cond);//初始化条件
}
if (type == 1) {
while (1) {
printf("ok\n");
scanf("%[^\n]s", shmemory->mesg); getchar();
if (strlen(shmemory->mesg)) pthread_cond_signal(&shmemory->cond);//1号进程写入后通知其他进程
}
} else {
while (1) {
pthread_mutex_lock(&shmemory->mutex);//一旦有某个进程拿到了共享内存 则将共享内存上锁
pthread_cond_wait(&shmemory->cond, &shmemory->mutex);//共享内存上锁后 等待1号进程写入完成的通知
//if (strlen(shmemory->mesg))
printf("<Process%d> : %s\n", type, shmemory->mesg);//将共享内存中写入的数据输出
memset(shmemory->mesg, 0, strlen(shmemory->mesg));//清空共享内存
pthread_mutex_unlock(&shmemory->mutex);
}
}
return 0;
}