Linux-进程间通信---共享内存

文章目录

  • 前言
  • API
  • 举例
  • 说明
  • 参考资料

前言

进程间通信的方式包括管道、消息队列、共享内存等,共享内存是一种几乎没有上限的通信方式,但同时,使用共享内存时需要自己进行加锁等访问控制。

共享内存是开辟一块公共的逻辑内存,通常也是一块公共的物理内存,两个不相关的进程可以共同访问这块内存中的地址,就如同malloc分配的内存一样。如果某个进程向内存中写入数据,这些变化将直接反应到其他进程中。

共享内存没有提供同步机制,在一个进程写入动作结束以前,并不存在一种自动的机制阻止另外一个进程对内存的读写,所以需要使用者自己进行读写控制,包括信号量、各种锁等。

API

Linux提供了一组标准的接口用于对共享内存的访问。

#include 
/*
 * 创建共享内存
 * @ key 访问共享内存所需的
 * @ size 指定共享内存的大小(字节)
 * @ shmflg 权限标志,与open函数的mode参数一致,
 *   如果要想在key标识的共享内存不存在时,创建它的话,可以与IPC_CREAT做或操作
 *   共享内存的权限标志与文件的读写权限一样,
 *   举例来说,0644,它表示允许一个进程创建的共享内存被内存创建者所拥有的进程向共享内存读取和写入数据,
 *   同时其他用户创建的进程只能读取共享内存
 * return: 成功时返回与共享内存相关的标识符
 *         失败时返回-1
 * */
int shmget(key_t key, size_t size, int shmflg);
/*
 * 启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间
 * (在第一次创建完共享内存时,不能被任何进程访问,需要借助该函数启动)
 * @ shm_id shmget返回的共享内存标识
 * @ shm_addr 希望将内存绑定到当前进程的什么位置,一般为NULL,即由系统决定
 * @ shmflg 一组标识,通常为0,(?)
 * return: 成功时返回共享内存第一个字节的位置;
 *         失败时返回-1;
 * */
void *shmat(int shm_id, const void *shm_addr, int shmflg);
/*
 * 解除对共享内存的绑定,将共享内存分离,分离以后,将无法继续访问;分离并非删除,其他进程还可以继续访问
 * @ shm_addr shmat返回的内存地址
 * return: 成功时返回0;
 *         失败时返回-1;
 * */
int shmdt(const void *shmaddr);

/*
 * 共享内存的访问控制结构
 * */
struct shmid_ds
{
    uid_t shm_perm.uid;    // user id
    uid_t shm_perm.gid;    // group id
    mode_t shm_perm.mode;  // 访问模式
};

/*
 * 控制共享内存,
 * @ shm_id shmget返回的共享内存标识
 * @ command 需要采取的动作
 *  IPC_STAT:共享内存的当前关联值覆盖shmid_ds的值。 
 *  IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值
    IPC_RMID:删除共享内存
 * return: 成功时返回0;
 *         失败时返回-1;
 * */
int shmctl(int shm_id, int command, struct shmid_ds *buf);

举例

下面的例子是一个访问共享内存的例子,shmwrite在written为0的情况下向其中写入数据,shmread在written非0的情况下,从共享内存中读取数据。(这里只是演示共享内存的使用,下面的程序是不安全的,多个进程同时读写可能会有问题)

shmwrite.c写入程序

首先通过shmget打开内存空间,此时需要带着IPC_CREAT,以便在没有这块内存的情况下创建;而后通过shmat挂载内存;循环判断written,可写的情况下从标准输入读取数据并写入共享内存;在读到end的情况下,写入共享内存,同时结束循环,分离共享空间。(此处不能删除内存,shmread还需要读出来,这里也可以看出,多个进程读的情况下,删除内存的时机也需要考虑。)

/*
 * !!! 这个程序是不安全的,仅用做访问的例子
 * 对written赋值的动作不是原子的,多进程访问时可能出错
 * */

#include 
#include 
#include 
#include 
#include 
#include "shmdata.h"


int main()
{
	int running = 1;
	void *shm = NULL;
	struct shared_use_st *shared = NULL;
	char buffer[TEXT_SZ + 1];				//用于保存输入的文本
	int shmid;

	//创建共享内存
	shmid = shmget((key_t)SHM_KEY, sizeof(struct shared_use_st), 0666|IPC_CREAT);
	if(shmid == -1)
	{
		LOGEI("shmget failed");
		exit(EXIT_FAILURE);
	}

	//将共享内存连接到当前进程的地址空间
	shm = shmat(shmid, (void*)0, 0);
	if(shm == (void*)-1)
	{
		LOGEI("shmat failed");
		exit(EXIT_FAILURE);
	}

	LOGI("Memory attached at %X", (int)shm);

	//设置共享内存
	shared = (struct shared_use_st*)shm;
	while(running)//向共享内存中写数据
	{
		//数据还没有被读取,则等待数据被读取,不能向共享内存中写入文本
		while(shared->written == 1)
		{
			sleep(1);
			LOGI("Waiting...");
		}

		//向共享内存中写入数据
		LOGI("Please Enter some text: ");
		fgets(buffer, TEXT_SZ, stdin);
		strncpy(shared->text, buffer, TEXT_SZ);

		//写完数据,设置written使共享内存段可读
		shared->written = 1;

		//输入了end,退出循环
		if(strncmp(buffer, "end", 3) == 0)
			running = 0;
	}

	//把共享内存从当前进程中分离
	if(shmdt(shm) == -1)
	{
		LOGEI("shmdt failed");
		exit(EXIT_FAILURE);
	}

	exit(EXIT_SUCCESS);
}


shmread.c读取共享内存中的数据。

首先通过shmget打开内存空间,此时需要带着IPC_CREAT,以便在没有这块内存的情况下创建;而后通过shmat挂载内存;循环判断written,可读的情况下读取数据并输出;在读到end的情况下结束循环,分离共享空间,并删除之。

/*
 * !!! 这个程序是不安全的,仅用做访问的例子
 * 对written赋值的动作不是原子的,多进程访问时可能出错
 * */

#include 
#include 
#include 
#include 
#include "common.h"
#include "shmdata.h"

int main()
{
	int running = 1;
	void *shm = NULL;				//分配的共享内存的原始首地址
	struct shared_use_st *shared;	//指向需要读写的数据
	int shmid;						//共享内存标识符

	//创建共享内存
	shmid = shmget((key_t)SHM_KEY, sizeof(struct shared_use_st), 0666|IPC_CREAT);
	if(shmid == -1)
	{
		LOGEI("shmget failed");
		exit(EXIT_FAILURE);
	}

	//将共享内存连接到当前进程的地址空间
	shm = shmat(shmid, 0, 0);
	if(shm == (void*)-1)
	{
		LOGEI("shmat failed");
		exit(EXIT_FAILURE);
	}
	LOGI("Memory attached at %X", (int)shm);

	//设置共享内存
	shared = (struct shared_use_st*)shm;
	shared->written = 0;

	//读取共享内存中的数据
	while(running)
	{
		//没有进程向共享内存写数据, 有数据可读取
		if(shared->written != 0)
		{
			LOGI("You wrote: %s", shared->text);
			sleep(rand() % 3);

			//读取完数据,设置written使共享内存段可写
			shared->written = 0;

			//输入了end,退出循环(程序)
			if(strncmp(shared->text, "end", 3) == 0)
				running = 0;
		}
		else
			//有其他进程在写数据,不能读取数据
			sleep(1);
	}

	//把共享内存从当前进程中分离
	if(shmdt(shm) == -1)
	{
		LOGEI("shmdt failed");
		exit(EXIT_FAILURE);
	}

	//删除共享内存
	if(shmctl(shmid, IPC_RMID, 0) == -1)
	{
		LOGEI("shmctl(IPC_RMID) failed\n");
		exit(EXIT_FAILURE);
	}

	exit(EXIT_SUCCESS);
}


shmdata.h定义了buffer的大小,共享内存的标识。

#ifndef _SHMDATA_H_HEADER
#define _SHMDATA_H_HEADER

#define TEXT_SZ 2048

#define SHM_KEY 9876		//用来标识共享内存名称的整型变量

struct shared_use_st
{
	int written;			//作为一个标志,非0:表示可读,0表示可写
	char text[TEXT_SZ];		//记录写入和读取的文本
};

#endif


common.h用于更好地显示调试信息

#ifndef __UT_COMMON_H__
#define __UT_COMMON_H__

#define LOGE(fmt, args...)                                              \
    do {                                                                \
        printf("[%s(%d)]error:"fmt"\n", __FILE__, __LINE__, ##args );   \
    } while(0)

#define LOGI(fmt, args...)                                              \
    do {                                                                \
        printf("[%s(%d)]info:"fmt"\n", __FILE__, __LINE__, ##args );    \
    } while(0)

#define LOGD(fmt, args...)                                              \
    do {                                                                \
        printf("[%s(%d)]debug:"fmt"\n", __FILE__, __LINE__, ##args );   \
    } while(0)

#include 
#define LOGEI(fmt, args...)                                             \
    do {                                                                \
        LOGE("(%d/%s)", errno, strerror(strno));                        \
    } while(0)

#endif /*__UT_COMMON_H__*/

编译运行:

$gcc shmread.o -o shmread
$gcc shmwrite.o -o shmwrite

分别在两个终端中运行读写程序,并通过标准输入写入内容:

终端1:

$ ./shmread
Memory attached at A7C91000
You wrote: abc
You wrote: end
$

终端2:

$ ./shmwrite
Memory attached at 10F58000
Enter some text:abc
Waiting...
Waiting...
Enter some text:end
$

说明

该程序的不安全性体现在对written的赋值上,设想一下,如果有多个进程同时读写数据时,其中一个进程发现written=0,并向其中写入数据,并将written赋值为1,而另一个进程只有在written=1的情况下才读,同时赋值written为0。乍看似乎没有问题,但是对written的赋值不是原子的,试想当written为0时,两个进程同时访问,发现written为0,都进行了写入操作,显然不行。

关于共享内存多进程的访问同步,后续的继续研究。

参考资料

https://blog.csdn.net/ljianhui/article/details/10253345

你可能感兴趣的:(linux,C)