【Linux系统与网络编程】06:共享内存

文章详细介绍了Linux下实现进程间通信的两种方式:文件上锁(flock)和共享内存。在文件上锁中,通过flock系统调用确保多个进程在写文件时的顺序。共享内存部分,讲解了ftok、shmget、shmat、shmdt和shmctl等函数的使用,以及如何在亲缘和非亲缘进程间实现通信。同时,提到了利用条件变量进行进程同步的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

共享内存


一、文件上锁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;
}

image-20230224210120883

二、共享内存

允许两个或多个进程共享一个给定的存储区,由于无需复制数据,这是最快的IPC进程间通信。

1.关联共享内存ftok

image-20230224215755103

#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;
}

image-20230224215900200

2.获取共享内存shmget

image-20230224212406973

#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;
}

image-20230224221845280

3.绑定共享内存shmat

image-20230225230606913

#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;
}

image-20230225232517349

4.绑定分离shmdt

image-20230225232744715

#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

image-20230226002223768

#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;
}

image-20230228214143422

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;
}

image-20230228215903113

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;
}

image-20230228223341746

image-20230228223606341

成功输出结果,前100项求和的结果为5050,但是当使用程序求前10000项和时,却会出现问题如图结果为50007661(错误结果)。

image-20230228223444201

出现问题的原因是:执行中的进程没有保障,进程之间发生竞争(同一时刻多个进程对内存进行读写操作,发生在多核处理器)

可以利用条件变量实现线程同步机制(进程同步),从而避免资源抢占竞争。

四、非亲缘进程间通信

共享内存实现多进程计算,

  1. 单核不考虑同步关系,可以正常实现
  2. 多核不考虑同步关系,无法正常实现
  3. 需要设置同步关系
  4. 利用条件变量实现进程同步

非亲缘进程之间的通信,

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;
}

image-20230301103041419

2.通过条件变量同步

思考:如何通过条件变量以及互斥锁,通过共享内存空间实现多进程同步

  • 一个进程1号进程,作为主要发送进程
  • 多个进程,输出1号进程发送的数据
  • 利用条件变量和互斥锁实现同步

image-20230301110138124

#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;
}

image-20230301171032340

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值