【Linux】进程通信(共享内存Shared Memory)

(一)共享内存

(1)共享内存实现进程间通信的原理:

  • 共享内存在物理内存上开辟的空间
  • 多个进程可以将自己的虚拟空间地址映射到该块物理空间上
  • 多个进程都可以访问共享内存中的数据
  • 共享内存上的改动可以被其他进程看到
  • 并未实现同步机制,需要其他机制同步对共享内存的访问

(2)共享内存逻辑结构图

在这里插入图片描述

(二)共享内存相关函数

使用sys/shm.h

(1)创建(获得)共享内存

int shmget(key_t key, size_t size, int shmflg);

  • key_t :键值用于标识使用同一个共享内存段;(IPC_PRIVATE创建只属于进程的共享内存,但不是真正的私有)
  • size : 共享内存的容量(字节)
  • shmflg : IPC_CREAT(创建生效 / 获得忽略)(9个位的权限标志,与文件权限mode类似)
  • 返回值:成功返回共享内存标识符(非负数),失败-1

(2)共享内存和进程的连接(at)

void* shmat(int shm_id, const void* shm_addr, int shmflg);

  • shm_id : 共享内存标识符
  • shm_addr : 指定共享内存连接到当前进程的地址位置(通常是空指针NULL,表示让系统自己分配共享内存的地址
  • shmflg : 一般是0,SHM_RND(与shm_addr联合使用控制共享内存连接地址)、SHM_RDONLY(连接的内存只读)(一组位标志)
  • 返回值:成功返回指向共享内存第一个字节的指针,失败-1
    注意:
  • 共享内存的读写权限:由创建者、它的访问权限、当前进程属主决定(类似于文件权限)
  • 例外:shmflg & SHM_RDONLY为真时,此时的共享内存即使可写入,都不能写入;

(3)共享内存的控制(ctl)

int shmctl(int shm_id, int command, struct shmid_ds* buf);

  • shm_id : shmget的返回值,共享内存标识符
  • command : 采取的操作
命令 说明
IPC_STAT 把shmid_ds结构中的数据设置成共享内存的当前关联值
IPC_SET 若进程有足够的权限,就把共享内存的当前关联值设置成shmid_ds结构中给定的值
IPC_RMID 删除共享内存段
  • buff :是一个指针,指向包含共享内存模式和访问权限的结构
  • 返回值:成功返回0,失败-1

注意:删除一个正在处于连接状态的共享内存段后,通常还能够使用,直到最后一个进程脱离该共享内存段的连接(最好不要这样做!!哒咩)

(4)共享内存的分离(dt)

将共享内存从当前进程中分离;
int shmdt(const void* shm_addr);

  • shm_addr是shmat()返回的指针
  • 返回值:成功0,失败-1

注意:
共享内存分离,但未删除,只是分离的进程变得不可用

(三)练习巩固

(1)(未进程同步)简单的使用共享内存

  • a.c
#include <stdio.h>
#include <unistd.h>
#include <sys/shm.h>
#include <string.h>

//向共享内存中写入数据
void WriteToShm()
{
    
    
	//创建共享内存
	int shmid = shmget((key_t)8888, 128, IPC_CREAT | 0600);
	if(shmid == -1)
	{
    
    
		perror("shmget err");
		return;
	}
	//连接共享内存
	char* shm = shmat(shmid, NULL, 0);
	if(shm == NULL)
	{
    
    
		perror("shmat shm err");
		return;
	}
	//向共享内存中写入数据
	printf("input data : \n");
	while(1)
	{
    
    
		char buff[128] = {
    
    0};
		fgets(buff, 127, stdin);
		if(strncmp(buff, "end", 3) == 0)
		{
    
    
			break;			
		}
		strncpy(shm, buff, strlen(buff) - 1);
	}

	//脱离连接
	shmdt(shm);
}
int main()
{
    
    
	WriteToShm();
	return 0;
}
  • b.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/shm.h>
//从共享内存中读取数据
void ReadFromShm()
{
    
    
	//获得共享内存
	int shmid = shmget((key_t)8888, 128, IPC_CREAT | 0600);
	if(shmid == -1)
	{
    
    
		perror("shmget jion err");
		return;
	}

	//连接共享内存
	char* shm = shmat(shmid, NULL, 0);
	if(shm == NULL)
	{
    
    
		perror("shmat err");
		return;
	}

	while(1)
	{
    
    
		printf("%s\n", shm);
		sleep(2);
	}
}
int main()
{
    
    
	ReadFromShm();
	return 0;
}
  • 结果:
    在这里插入图片描述
  • 使用ipcs -m查看共享内存
    在这里插入图片描述
  • 结论:
  • 从结果可知,a程序写入共享内存中的数据,b程序从共享内存中读取时(不管共享内存中的数据有没有更新,都会一直打印),违背了我们a输入一次,b程序打印一次

(2)使用信号量同步后的进程a , b

思路:

  • 当a进程键盘输入写入共享内存期间,b进程不能读取共享内存,即要被阻塞

  • 当b进程读取共享内存的数据,进行输出,这个期间 ,a进程不能再共享内存中写入数据,否则就会覆盖共享内存中的数据

  • 使用2个信号量:初始值s1 = 1, s2 = 0 ;

  • a进程每次进行输入之前对s1进行p操作,b进程每次读取输出之前对s2 进行p操作,此时就会阻塞,需要等待a进程写入数据完毕后对s2进行v操作后,b进程才能对s2进行p操作;

  • 当b进程在读取输出期间,a进程试图对s1进行p操作,此时阻塞,需等待b进程对s1进行v操作;
    在这里插入图片描述

  • sem.h

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/sem.h>
#include <string.h>
//两个信号量
#define SEMSIZE 2

union semun
{
    
    
	int val;
};

//创建(获得)信号量集 并初始化信号量的值
void sem_init();

//p操作
void sem_p(int index);

//v操作
void sem_v(int index);

//销毁信号量集
void sem_destory();
  • sem.c
#include "sem.h"

//默认的信号量集的ID
static int semid = -1;

//创建(获得)信号量集 并初始化信号量的值
void sem_init()
{
    
    
	semid = semget((key_t)1234, SEMSIZE, IPC_CREAT | IPC_EXCL | 0600);
	//创建失败,获得
	if(semid == -1)
	{
    
    
		semid = semget((key_t)1234, SEMSIZE, IPC_CREAT);
		//获取失败
		if(semid == -1)
		{
    
    
			perror("semget err");
			return;
		}
	}
	//创建成功,并初始化
	else
	{
    
    
		union semun tmp[SEMSIZE];
		tmp[0].val = 1;	//信号量的值
		tmp[1].val = 0;
		for(int index = 0; index < SEMSIZE; index++)
		{
    
    
			if(semctl(semid, index, SETVAL, tmp[index]) == -1)
			{
    
    
				perror("semctl init err");
				return;
			}
		}
	}
}
//p操作
void sem_p(int index)
{
    
    
	if(index < 0 || index > SEMSIZE)
	{
    
    
		perror("index out of line");
		return;
	}
	struct sembuf buf;
	buf.sem_num = index;
	buf.sem_op = -1;
	buf.sem_flg = SEM_UNDO;

	if(semop(semid, &buf, 1) == -1)
	{
    
    
		perror("semop p err");
		return;
	}
}

//v操作
void sem_v(int index)
{
    
    
	if(index < 0 || index > SEMSIZE)
	{
    
    
		perror("index out of line");
		return;
	}
	struct sembuf buf;
	buf.sem_num = index;
	buf.sem_op = 1;
	buf.sem_flg = SEM_UNDO;

	if(semop(semid, &buf, 1) == -1)
	{
    
    
		perror("semop v err");
		return;
	}
}

//销毁信号量集
void sem_destory()
{
    
    
	if(semctl(semid, 0, IPC_RMID) == -1)
	{
    
    
		perror("sem_destory err");
		return;
	}
}

  • a.c
#include <sys/shm.h>
#include "sem.h"

//向共享内存中写入数据
void WriteToShm()
{
    
    
	//初始化信号量集
	sem_init();
	//创建共享内存
	int shmid = shmget((key_t)8888, 128, IPC_CREAT | 0600);
	if(shmid == -1)
	{
    
    
		perror("shmget err");
		return;
	}
	//连接共享内存
	char* shm = shmat(shmid, NULL, 0);
	if(shm == NULL)
	{
    
    
		perror("shmat shm err");
		return;
	}

	//向共享内存中写入数据
	printf("input data : \n");
	while(1)
	{
    
    
		sem_p(0);
		char buff[128] = {
    
    0};
		memset(shm, 0, 128);
		fgets(buff, 127, stdin);
		if(strncmp(buff, "end", 3) == 0)
		{
    
    
			strcpy(shm, buff);
			sem_v(1);
			break;			
		}
		strncpy(shm, buff, strlen(buff) - 1);
		sem_v(1);
	}

	//脱离连接
	shmdt(shm);
}

int main()
{
    
    
	WriteToShm();
	
	return 0;
}
  • b.c
#include <sys/shm.h>
#include "sem.h"
//从共享内存中读取数据
void ReadFromShm()
{
    
    
	//获得信号量集
	sem_init();
	
	//获得共享内存
	int shmid = shmget((key_t)8888, 128, IPC_CREAT | 0600);
	if(shmid == -1)
	{
    
    
		perror("shmget jion err");
		return;
	}

	//连接共享内存
	char* shm = shmat(shmid, NULL, 0);
	if(shm == NULL)
	{
    
    
		perror("shmat err");
		return;
	}

	while(1)
	{
    
    
		sem_p(1);
		if(strncmp(shm, "end", 3) == 0)
		{
    
    
			break;
		}
		printf("%s\n", shm);
		sem_v(0);
	}

	//脱离共享内存
	shmdt(shm);

	//销毁共享内存
	if(shmctl(shmid, IPC_RMID, NULL) == -1)
	{
    
    
		perror("destory shm err");
		return;
	}
	//销毁信号量集
	sleep(2);
	sem_destory();
}

int main()
{
    
    
	ReadFromShm();

	return 0;
}

结果:
在这里插入图片描述

Guess you like

Origin blog.csdn.net/xiaoxiaoguailou/article/details/121538313