进程间通信之共享内存与信号量

1.概念:
共享内存是System V版本的最后一个进程间通信方式。共享内存,顾名思义就是允许两个不相关的进程访问同一个逻辑内存,共享内存是两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常为同一段物理内存。进程可以将同一段物理内存连接到他们自己的地址空间中,所有的进程都可以访问共享内存中的地址。如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。

特别提醒:共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取,所以我们通常需要用其他的机制来同步对共享内存的访问,例如信号量。

2.共享内存的通信原理

在Linux中,每个进程都有属于自己的进程控制块(PCB)和地址空间(Addr Space),并且都有一个与之对应的页表,负责将进程的虚拟地址与物理地址进行映射,通过内存管理单元(MMU)进行管理。两个不同的虚拟地址通过页表映射到物理空间的同一区域,它们所指向的这块区域即共享内存。

共享内存的通信原理示意图:

å¨è¿éæå¥å¾çæè¿°

对于上图我的理解是:当两个进程通过页表将虚拟地址映射到物理地址时,在物理地址中有一块共同的内存区,即共享内存,这块内存可以被两个进程同时看到。这样当一个进程进行写操作,另一个进程读操作就可以实现进程间通信。但是,我们要确保一个进程在写的时候不能被读,因此我们使用信号量来实现同步与互斥。

对于一个共享内存,实现采用的是引用计数的原理,当进程脱离共享存储区后,计数器减一,挂架成功时,计数器加一,只有当计数器变为零时,才能被删除。当进程终止时,它所附加的共享存储区都会自动脱离。

为什么共享内存速度最快?

借助上图说明:Proc A 进程给内存中写数据, Proc B 进程从内存中读取数据,在此期间一共发生了两次复制

(1)Proc A 到共享内存 (2)共享内存到 Proc B

因为直接在内存上操作,所以共享内存的速度也就提高了。

3.如何使用共享内存
1)创建共享内存(shmget)
2)访问共享内存(shmat,每一个进程必须绑定到自己的地址空间)
3)解除绑定(shmdt)
4)释放共享内存(shmctl,由一个进程释放即可)

4.相关函数
1.shmget()

å¨è¿éæå¥å¾çæè¿°

 2.shmat()

å¨è¿éæå¥å¾çæè¿°

3.shmdt()

å¨è¿éæå¥å¾çæè¿° 4.shmctl()

å¨è¿éæå¥å¾çæè¿°

 共享内存-父子进程间的通讯

#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SHM_KEY 11

typedef struct{
    int type;//>0
	char dat[100];
}SM;


int main(){
	SM *smp;

	printf("size:%d\n",sizeof(SM));
	int shmid = shmget(SHM_KEY,sizeof(SM),0644|IPC_CREAT);
	if(shmid == -1){
		perror("shmget error!\n");
		exit(1);
	}

	smp = shmat(shmid,NULL,0);
	if(smp == (void*)-1){
		perror("shmat error!\n");
		exit(1);
	}
	printf("share memory allocate successful!\n");

    pid_t pid = fork();

    if(pid == -1){
		perror("fork error!\n");
		exit(1);
	}
	if(pid == 0){//read
		while(1){
			if(strlen(smp->dat) == 0){
				sleep(1);
				continue;
			}
			printf("receive %s\n",smp->dat);

			if(strcmp(smp->dat,"exit") == 0){
			    break;
			}
			memset(smp,0,sizeof(SM));
		}
		if(shmdt(smp) == -1){
			perror("shmid error!\n");
			exit(1);
		}

		if(shmctl(shmid,IPC_RMID,NULL) == -1){
			perror("shmctl error!\n");
			exit(1);
		}
		printf("remove shm successful!\n");
	}else if(pid > 0){//write
		while(1){
			scanf("%s",smp->dat);
			if((strcmp(smp->dat,"exit") == 0)){
				break;
			}
		}
			if(shmdt(smp) == -1){
				perror("parent:shmdt error!\n");
				exit(1);
			}
			sleep(3);
	}
	return 0;
}

 共享内存-无关进程间的通信

å¨è¿éæå¥å¾çæè¿°

 å¨è¿éæå¥å¾çæè¿°

信号量

 

在看信号量之前,我们先来看几个概念:

(1)临界资源:多个进程看到的同一个资源(公共资源);

(2)临界区:访问临界资源的代码;

(3)同步性:在一段时间内,按照一定的顺序访问 ;

(4)互斥性:在一段时间内,一份资源只能被一个进程访问 和占用 ;

(5)原子性: 一个事物要么全做,要么不做,不能只做一半 ;

1. 信号量的概念

(1)信号量本质上是计数器,是衡量临界资源的数量的 ;

(2)信号量也是一种临界资源,它的作用是保护临界资源 ;

(3)信号量只有两种操作,PV原语,P表示申请资源(上锁),V表示释放资源(解锁);//上锁就是能利用cpu资源对公共资源进行访问 //解锁就是即使得到cpu资源也访问不了

2.使用步骤
1)进程调用semget函数创建新的信号量集合,或者获取已有得到信号量集合。
2)调用semctl函数给集合中的每个信号量设置初值。
3)调用semop函数对集合中的信号量进行PV操作。(P加锁:对信号量-1,如果信号量的值为0,P操作会阻塞。V解锁:对信号量+1,不存在阻塞问题)。
4)调用semctl删除信号量集合。

3.相关函数

1)semget
函数原型:int semget(key_t key, int nsems, int semflg);
key:所创建或打开信号量集的键值。
nsems:创建的信号量集中的信号量的个数,该参数只在创建信号量集时有效。
semflg:调用函数的操作类型,也可用于设置信号量集的访问权限,两者通过or表示
返回值说明:
如果成功,则返回信号量集的IPC标识符。
如果失败,则返回-1,errno被设定成以下的某个值。

2)semctl
函数原型:int semctl(int _semid ,int _semnum,int _cmd ……);

功能:控制信号量的信息。

返回值:成功返回0,失败返回-1;

参数:

_semid 信号量的标志码(ID),也就是semget()函数的返回值;

_semnum, 操作信号在信号集中的编号。从0开始。

_cmd 命令,表示要进行的操作。

下面列出的这些命令来源于百度!

参数cmd中可以使用的命令如下:

IPC_STAT读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中。

IPC_SET设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf参数。

IPC_RMID将信号量集从内存中删除。

GETALL用于读取信号量集中的所有信号量的值。

GETNCNT返回正在等待资源的进程数目。

GETPID返回最后一个执行semop操作的进程的PID。

GETVAL返回信号量集中的一个单个的信号量的值。

GETZCNT返回这在等待完全空闲的资源的进程数目。

SETALL设置信号量集中的所有的信号量的值。

SETVAL设置信号量集中的一个单独的信号量的值。

3)semop

int semop(int semid ,struct sembuf *_sops ,size_t _nsops);

功能:用户改变信号量的值。也就是使用资源还是释放资源使用权。

返回值:成功返回0,失败返回-1;

参数:

_semid : 信号量的标识码。也就是semget()的返回值。

_sops是一个指向结构体数组的指针。

struct sembuf{
 unsigned short  sem_num;//第几个信号量,第一个信号量为0;

 short  sem_op;//对该信号量的操作。

 short _semflg;
};

sem_num: 操作信号在信号集中的编号。第一个信号的编号为0;

sem_op : 如果其值为正数,该值会加到现有的信号内含值中。通常用于释放所控资源的使用权;如果sem_op的值为负数,而其绝对值又大于信号的现值,操作将会阻塞,直到信号值大于或等于sem_op的绝对值。通常用于获取资源的使用权;如果sem_op的值为0,则操作将暂时阻塞,直到信号的值变为0。

_semflg IPC_NOWAIT //对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息。

IPC_UNDO //程序结束时(不论正常或不正常),保证信号值会被重设为semop()调用前的值。这样做的目的在于避免程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定。

nsops:操作结构的数量,恒大于或等于1。

共享内存-互斥

#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

typedef struct{
    int type;//>0
	char dat[100];
}SM;

#define SHM_KEY 1
#define SEM_KEY 2

int main(){
	SM *smp;
	int i;

	printf("size:%d\n",sizeof(SM));
	int shmid = shmget(SHM_KEY,sizeof(SM),0644|IPC_CREAT);
	if(shmid == -1){
		perror("shmget error!\n");
		exit(1);
	}

	smp = shmat(shmid,NULL,0);
	if(smp == (void*)-1){
		perror("shmat error!\n");
		exit(1);
	}
	printf("share memory allocate successful!\n");

	int semid = semget(SEM_KEY,1,0644|IPC_CREAT);
	if(semid == -1){
		perror("semget error!");
		exit(1);
	}

	semctl(semid,0,SETVAL,1);

    pid_t pid = fork();

    if(pid == -1){
		perror("fork error!\n");
		exit(1);
	}
	if(pid == 0){//read
		usleep(1);
		struct sembuf sb;
		sb.sem_num = 0;
		while(1){
			//lock p ->wait...
			sb.sem_op = -1;
			printf("receive:%s\n",smp->dat);
			if(strcmp(smp->dat,"exit") == 0){
			    break;
			}
			memset(smp,0,sizeof(SM));
			//unlock V
			sb.sem_op = 1;
			semop(semid,&sb,1);
			usleep(100);
		}
		if(shmdt(smp) == -1){
			perror("shmid error!\n");
			exit(1);
		}

		if(shmctl(shmid,IPC_RMID,NULL) == -1){
			perror("shmctl error!\n");
			exit(1);
		}
		printf("remove shm successful!\n");
	}else if(pid > 0){//write
		i = 0;
		struct sembuf sb;
		sb.sem_num = 0;
		while(1){
			//lock -1 P
			sb.sem_op = -1;
			semop(semid,&sb,1);
			sprintf(smp->dat,"%d",i++);
			if((strcmp(smp->dat,"exit") == 0)){
				break;
			}
			//unlock +1
			sb.sem_op = 1;
			semop(semid,&sb,1);
			usleep(100);
		}
			if(shmdt(smp) == -1){
				perror("parent:shmdt error!\n");
				exit(1);
			}
			sleep(3);
	}
	return 0;
}

共享内存-互斥取款1

#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

typedef struct{
	char dat[3]; //save balance
}SM;

#define SHM_KEY 1
#define SEM_KEY 2

int main(){
	SM *smp;
	int balance;

	int shmid = shmget(SHM_KEY,sizeof(SM),0644|IPC_CREAT);
	if(shmid == -1){
		perror("shmget error!\n");
		exit(1);
	}

	smp = shmat(shmid,NULL,0);
	if(smp == (void*)-1){
		perror("shmat error!\n");
		exit(1);
	}

	int semid = semget(SEM_KEY,1,0644|IPC_CREAT);
	if(semid == -1){
		perror("semget error!");
		exit(1);
	}
	semctl(semid,0,SETVAL,1);
	strcpy(smp->dat,"300");


    pid_t pid = fork();

    if(pid == -1){
		perror("fork error!\n");
		exit(1);
	}
	if(pid == 0){
		struct sembuf sb;
		sb.sem_num = 0;
		while(1){
			sb.sem_op = -1;
			semop(semid,&sb,1);
			balance = atoi(smp->dat);
			if(balance > 0){
				printf("child balance before:%d\n",balance);
				balance--;
				printf("child balance now:%d\n",balance);
				memset(smp->dat,0,sizeof(smp->dat));
				sprintf(smp->dat,"%d",balance);
			}
			sb.sem_op = 1;
			semop(semid,&sb,1);
			if(balance == 0){
				break;
			}
		}
		if(shmdt(smp) == -1){
			perror("shmid error!\n");
			exit(1);
		}

		if(shmctl(shmid,IPC_RMID,NULL) == -1){
			perror("shmctl error!\n");
			exit(1);
		}
		printf("remove shm successful!\n");
		if(semctl(semid,0,IPC_RMID) == -1){
			perror("semctl error!");
		}
		printf("remove sem successful!\n");
	}else if(pid > 0){
		struct sembuf sb;
		sb.sem_num = 0;
		while(1){
			//lock -1 P
			sb.sem_op = -1;
			semop(semid,&sb,1);
			balance = atoi(smp->dat);
			if(balance > 0){
				printf("parent balance before:%d\n",balance);
				balance--;
				printf("parent balance now:%d\n",balance);
				memset(smp->dat,0,sizeof(smp->dat));
				sprintf(smp->dat,"%d",balance);
			}
			sb.sem_op = 1;
			semop(semid,&sb,1);
			if(balance == 0){
				break;
			}

		}
			if(shmdt(smp) == -1){
				perror("parent:shmdt error!\n");
				exit(1);
			}
			sleep(3);
	}
	return 0;
}

共享内存-互斥存款2

#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

typedef struct{
	char dat[10]; //save balance
}SM;

#define SHM_KEY 1
#define SEM_KEY 2

int main(){
	SM *smp;
	int balance;

	int shmid = shmget(SHM_KEY,sizeof(SM),0644|IPC_CREAT);
	if(shmid == -1){
		perror("shmget error!\n");
		exit(1);
	}

	smp = shmat(shmid,NULL,0);
	if(smp == (void*)-1){
		perror("shmat error!\n");
		exit(1);
	}

	int semid = semget(SEM_KEY,3,0644|IPC_CREAT);
	if(semid == -1){
		perror("semget error!");
		exit(1);
	}
	semctl(semid,0,SETVAL,1);
	semctl(semid,1,SETVAL,0);
	semctl(semid,2,SETVAL,0);
	strcpy(smp->dat,"0");

pid_t pid = fork();

if(pid == -1){
	perror("fork error!\n");
	exit(1);
}
if(pid == 0){
	pid_t pid1 = fork();
	if(pid1 == 0){
		struct sembuf sb;
		sb.sem_num = 2;
		while(1){
			//lock -1 P
			sb.sem_op = -1;
			semop(semid,&sb,1);
			balance = atoi(smp->dat);
			if(balance < 300){
				printf("grandson balance before:%d\n",balance);
				balance++;
				printf("grandson balance now:%d\n",balance);
				memset(smp->dat,0,sizeof(smp->dat));
				sprintf(smp->dat,"%d",balance);
			}
			semctl(semid,0,SETVAL,1);
			sleep(1);
			if(balance == 300){
				break;
			}
		}
		if(shmdt(smp) == -1){
					perror("shmid error!\n");
					exit(1);
				}

				if(shmctl(shmid,IPC_RMID,NULL) == -1){
					perror("shmctl error!\n");
					exit(1);
				}
				printf("remove shm successful!\n");
				if(semctl(semid,0,IPC_RMID) == -1){
					perror("semctl error!");
				}
				printf("remove sem successful!\n");
	}else if(pid1 > 0){
		struct sembuf sb;
		sb.sem_num = 1;
		while(1){
			//lock -1 P
			sb.sem_op = -1;
			semop(semid,&sb,1);
			balance = atoi(smp->dat);
			if(balance < 300){
				printf("child balance before:%d\n",balance);
				balance++;
				printf("child balance now:%d\n",balance);
				memset(smp->dat,0,sizeof(smp->dat));
				sprintf(smp->dat,"%d",balance);
			}
			semctl(semid,2,SETVAL,1);
	    	sleep(1);
			if(balance == 300){
				break;
			}

		}
			if(shmdt(smp) == -1){
				perror("child:shmdt error!\n");
				exit(1);
								}
	}
}else if(pid > 0){
	struct sembuf sb;
	sb.sem_num = 0;
	while(1){
		//lock -1 P
		sb.sem_op = -1;
		semop(semid,&sb,1);
		balance = atoi(smp->dat);
		if(balance < 300){
			printf("parent balance before:%d\n",balance);
			balance++;
			printf("parent balance now:%d\n",balance);
			memset(smp->dat,0,sizeof(smp->dat));
			sprintf(smp->dat,"%d",balance);
		}
		semctl(semid,1,SETVAL,1);
		sleep(1);
		if(balance == 300){
			break;
		}
	}
		if(shmdt(smp) == -1){
						perror("parent:shmdt error!\n");
						exit(1);
					}
					sleep(3);

}
return 0;
}


————————————————
版权声明:本文为CSDN博主「郑广翰」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_43669302/article/details/105143452

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值