进程篇——进程间通信:共享内存

说明
  本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知,马上删帖致歉。
  QQ 群 号:513683159 【相互学习】
内容来源
  《Linux系统编程》、《Linux网络编程》、《Unix环境高级编程》

目录:

    • 共享内存
      • 一、函数简介
        • (1)shmget()——共享内存创建函数
        • (2)shmat()——获得共享内存地址函数
        • (3)shmdt()——删除共享内存函数
        • (4)shmctl()——共享内存控制函数
      • 二、示例实践:

共享内存

  共享内存是在多个进程之间共享内存区域的进程间的通信方式,它是在多个进程之间对内存段进行映射的方式实现内存共享的。这是IPC最快捷的方式,因为共享内存方式的通信没有中间过程,而管道、消息队列等方式则是需要将数据通过中间机制进行转换;与此相反,共享内存方式直接将某段内存段进行映射,多个进程间的共享内存是同一块的物理空间,仅仅是地址不同而已,因此不需要进行复制,可以直接使用此段空间。

一、函数简介

(1)shmget()——共享内存创建函数

  1.函数功能:创建一个新的共享内存段 或 访问一个现有的共享内存段(获取共享内存段)

项目 说明
函数原型 int shmget(key_t key, size_t size, int shmflg);
头文件 sys/ipc.h、sys/shm.h
参数说明 key:键值(可用flok函数生成)
拿来与内核其他消息队列关键字相比较,打开或访问操作依赖于shmflg参数内容
size:
shmflg:标记变量
(参数说明如下)
返回值 若成功则返回
若失败则返回-1
注意

  2.shmflg参数
    IPC_CREAT:如果在内核中不存在该内存段,则创建它。
    IPC EXCL:当与IPC _CREAT一起使用时,如果该内存段早已存在,则此次调用将失败。
  如果只使用了IPC_CREATshmget()函数或者返回新创建的内存段的段标识符,或者会返回现有的具有同一个关键字值的内存段的标识符。
  如果同时使用了IPC_EXCLIPC_CREAT,那么将可能会有两个结果:
    或者创建一个新的内存段;
    或者如果该内存段存在,则调用将出错,并返回-1
  IPC_EXCL本身是没有什么用处的,但在与IPC_CREAT组合使用时,它可用于防止一个现有的内存段为了访问而打开着。一旦进程获得了给定内存段的合法IPC标识符,它的下一步操作就是连接该内存段,或把该内存段映射到自己的寻址空间中。

(2)shmat()——获得共享内存地址函数

  1.函数功能:获取共享内存的地址,成功获取后,可像使用通用内存一样对其进行读写操作(附加共享内存段)

项目 说明
函数原型 void *shmat(int shmid, const void *shmaddr, int shmflg);
头文件 sys/ipc.h、sys/shm.h
参数说明 shmid:共享内存的句柄
(shmget()获取的返回值)
shmaddr:指向某地址
等于0,内核将试着查找一个未映射的区域
指定一个地址,该地址只用于访问所拥有的硬件,或解决与其他应用程序的冲突
shmflg:标记变量
(参数说明如下)
返回值 若成功则返回
若失败则返回-1
注意

  SHM_RND标志可以与标志参数进行OR操作,结果再置为标志参数,这样可以让传送的地址页对齐(舍入到最相近的页面大小)。
  此外,如果把 SHM_RDONLY标志与标志参数进行OR操作,结果再置为标志参数,这样映射的共享内存段只能标记为只读方式。
  当申请成功时,对内存的操作与一般内存一样,可以直接进行写入和读出,以及偏移的操作。

(3)shmdt()——删除共享内存函数

  1.函数功能:删除一段共享内存(分离共享内存段)
    当某进程不再需要一个共享内存段时,它必须调用这个函数来断开与该内存段的连接
    这与从内核删除内存段是两回事!在成功完成了断开连接操作以后,相关的shmid_ds结构的shm_nattch成员的值将减去1。如果这个值减到0,则内核将真正删除该内存段。

项目 说明
函数原型 int shmdt(const void *shmaddr);
头文件 sys/ipc.h、sys/shm.h
参数说明 shmaddr:指向某地址
返回值 若成功则返回
若失败则返回-1
注意

(4)shmctl()——共享内存控制函数

  1.函数功能:向共享内存的句柄发送命令,来完成某种功能。(共享内存控制操作)
    类似ioctl()的方式对共享内存进行操作。

项目 说明
函数原型 int shmctl(int shmid, int cmd, struct shmid_ds *buf);
头文件 sys/ipc.h、sys/shm.h
参数说明 shmid:共享内存的句柄
(shmget()获取的返回值)
cmd:共享内存发送的命令
buf:向共享内存发送命令的参数
返回值 若成功则返回
若失败则返回-1
注意

  2.shmid_ds结构

struct shmid_ds
  {
    struct ipc_perm shm_perm;		/* 所有者和权限 */
    size_t shm_segsz;				/* 段大小,以字节为单位 */
    time_t  shm_atime;				/* 最后挂接时间 */
    time_t  shm_dtime;				/* 最后取出时间 */
    time_t  e_t shm_ctime;			/* 最后修改时间 */
    pid_t shm_cpid;					/* 建立者PID */
    pid_t shm_lpid;					/* 最后调用shmat()/shmdt()的PID */
    shmatt_t shm_nattch;			/* 现在挂接的数量 */
    __syscall_ulong_t __glibc_reserved4;
    __syscall_ulong_t __glibc_reserved5;
  };
struct ipc_perm 
{
      key_t   __key; /*  Key提供给semget(2) */
      uid_t   uid;   /*  owner的有效UID */
      gid_t   gid;   /* owner的有效GID */
      uid_t   cuid;  /* 创建器的有效UID */
      gid_t  cgid;   /* E创建者的有效GID */
      unsigned short mode;  /* 权限 */
      unsigned short __seq; /* 序列号 */
};

  3.合法命令值
    1️⃣IPC_STAT:获取内存段的 shmid_ds结构,并把它存储在 buf参数所指足的地址中。
    2️⃣IPC_SET设置内存段shmid_ds结构的ipc_perm成员的值,此命令是从 buf参数中获得该值的。
    3️⃣IPC_RMID:标记某内存段,以备删除。该命令并不真正地把内存段从内存中删除。相反,它只是标记上该内存段,以备将来删除。只有当前连接到该内存段的最后一个进程正确地断开了与它的连接,实际的删除操作才会发生。当然,如果当前没有进程与该内存段相连接,则删除将立刻发生。为了正确地断开与其共享内存段的连接,进程需要调用shmdt()函数。

二、示例实践:

  1.示例介绍:
    父进程和子进程之间利用共享内存进行通信,父进程向共享内存中写入数据,子进程读出数据。
    两个进程之间的控制采用了信号量的方法,父进程写入数据成功后,信号量加1,子进程在访问信号量之前先等待信号。
  2.源程序 :shm.c

#include 
#include 
#include 
#include 
#include 
#include 

static char msg[]="你好,共享内存\n";
typedef int sem_t;
union semun{
	int val;									//整型变量
	struct semid_ds *buf;						//semid_ds结构指针
	unsigned short *array;						//数组类型
}arg;	

/**
 * @function:建立信号量
 * 
 * @param key:	  魔数
 * @param value:  信号量的初始值
 * 
 * @desciption:
 * 		按用户键值生成一个信号量,
 * 		信号量的初始值设为用户输入的value
*/
sem_t CreateSem(key_t key, int value)
{
	union semun sem;							//信号量结构变量
	sem_t semid;								//信号量ID
	sem.val = value;							//设置初始值
	semid = semget(key,1,IPC_CREAT|0666);		//获得信号量的ID
	if(semid == -1)								//获得信号量ID失败
	{
		printf ("create semaphore error\n");
		return -1;								//返回错误
	}
	semctl(semid,0,SETVAL,sem);					//发送命令,建立value个初始值的信号
	return semid;								//返回建立的信号量
}

/*增加信号量*/
int Sem_P(sem_t semid)
{
	struct sembuf sops={0,+1,IPC_NOWAIT};		//建立信号量结构值(对信号量0,进行+1的操作,)
	return (semop(semid,&sops,1));				//发送命令
}

/*减小信号量值*/
int Sem_V(sem_t semid)
{
	struct sembuf sops={0,-1,IPC_NOWAIT};		//建立信号量结构值(对信号量0,进行-1的操作,)
	return (semop(semid, &sops,1));				//发送信号量操作方法
}

/* 设置信号量的值 */
void SetvalueSem(sem_t semid, int value)
{	
	union semun sem; 							//信号量操作的结构
	sem.val = value;							//值初始化	
	semctl(semid,0,SETVAL,sem);					//设置信号量的值
} 

/* 获得信号量的值 */
int GetvalueSem(sem_t semid)
{	
	union semun sem;							//信号量操作的结构
	return semctl(semid,0,GETVAL,sem);			//获得信号量的值
} 

/* 销毁信号量 */
void DestroySem(sem_t semid)
{
	union semun sem;							//信号量操作的结构
	sem.val = 0;								//信号量值的初始化
	semctl(semid,0,IPC_RMID,sem);				//设置信号量
} 

int main (int agrc,char *argv[])
{
	/* Step 1 初始化 */
	key_t key;											 	//键值
	int semid;												//信号量ID
	int shmid;												//共享内存ID
	char i;
	char *shms = NULL;
	char *shmc = NULL;
	int value = 0;
	char buffer[80];
	pid_t p;												//进程
	struct semid_ds buf;
	
	/* 
		Step 2 生成键值
			将路径名和项目的标识符转变为系统V的IPC键值
	*/
	key = ftok("/ipc/sem",'a');

	/* 
		Step 3 创建共享内存
			大小为1024个字节
	*/
	shmid = shmget(key,1024, IPC_CREAT|0604);				
	
	/* 
		Step 4 建立信号量
	*/
	semid = CreateSem(key, 0);	

	/* 
		Step 5 父子进程通讯
	*/
	p = fork();												//分叉程序
	if(p > 0)						/*父进程*/
	{
		shms = (char *)shmat(shmid,0,0);					//挂接共享内存
		memcpy (shms,msg,strlen(msg)+1);					//复制内容
		sleep(10);											//等待10s,另一个进程将数据读出
		Sem_P(semid);										//获得共享内存的信号量
		shmdt(shms);										//摘除共享内存
		DestroySem(semid);									//销毁信号量
	}
	else if(p == 0)					/*子进程*/
	{
		shmc = (char*)shmat(shmid, 0,0);					//挂接共享内存
		Sem_V(semid);										//减小信号量
		printf("共享内存的值为:%s\n",shmc);
		shmdt(shmc);										//摘除共享内存
	}
	return 0;
}

  3.执行步骤
  ①编译:gcc shm.c -o shm
  ②运行:./shm

你可能感兴趣的:(嵌入式,c语言,linux,运维)