linux进程通信之共享内存

share memory共享内存,

原理:每一个进程都有自己独立的地址空间,把同一块内存,分别映射到不同进程的地址空间的某个地址中去,这样每个进程都可以通过自己映射的地址来访问这块共享内存,优点就是,多个进程交换数据时,不用做任何的拷贝,效率高。需要注意的是,linux共享内存机制并没有提供同步机制,如果多各进程同时都往该内存写东西,就会写乱了,所以,一般我们还需要使用其他进程间的通信方法(如信号量、消息队列、管道等)来配合共享内存机制。

相关API:

1、用ftok来生成一个key,参考另一篇博文:消息队列

2、int shmget(key_t key, size_t size, int shmflg);//创建/获取共享内存
举例: int shmid = shmget(key, 1000, IPC_CREAT | 0666);

@key一般来自于ftok函数,

@size,要申请的内存的大小,注意,该值会被圆整为内存页大小的整数倍,内存页大小的宏定义为PAGE_SIZE,

@shmflg,shmflg可选的值可以进行或运算,可选的值有:权限值(例如0666)、参数。权限值不再赘述,只看一下参数:

    IPC_CREAT 如果设置了该标志,则会创建新的共享内存,否则,会查找跟key关联的、已经存在的、且本进程有访问权的共享内存;
    IPC_EXCL 该标志一般与IPC_CREAT 同时使用,作用是:若key关联的共享内存已存在,则shmget返回失败,而不是返回已存在的共享内存shmid;
    SHM_HUGETLB 使用huge page来分配内存
    SHM_NORESERVE 该标志的功能和mmap函数中的MAP_NORESERVE标志相同。不保留本内存段的交换空间,关于交换空间的概念,可自行查阅百科词条:交换空间 (电脑术语)。如果保留了交换空间,我们就一定能修改该内存段的数据;如果没保留交换空间,当物理内存用光时,继续写入 内存,这时本进程会收到SIGSEGV信号。

返回:成功则返回共享内存的描述符shmid,失败则返回-1,并设置errno

注意:何时会创建一个新的共享内存?①key的值为IPC_PRIVATE, 这时不论shmget的shmflg怎么设置,shmget总是创建新共享内存;②key的值不是IPC_PRIVATE,且shmget的形参shmflg包含IPC_CREAT标志,这时也会创建新的。

特性:当共享内存申请成功以后,这段内存区会被初始化为0,且记录本共享内存的信息(该信息用shmid_ds结构来描述)会被初始化,哪些成员会被初始化,初始化后的值为多少,这些信息科自行通过man shmget命令来查看。

3、void *shmat(int shmid, const void *shmaddr, int shmflg);//映射共享内存到本进程的地址空间 attach

       int shmdt(const void *shmaddr);//取消映射  detach

这两个API是一对相反的操作,shmat是把shmid指定的共享内存映射(原文是attach“连接”)到本进程的地址空间,

@shmaddr,该值如果传入的实参为NULL,那么系统会自动选择一个本进程未使用到的、合适的地址,把shmid指定的共享内存映射(attach)到这个地址;

如果传入的实参不是NULL,且shmflg含SHM_RND(自动圆整)标志,那么,系统会把shmid指定的共享内存映射到shmaddr地址(shmaddr值会被自动向下圆整为SHMLBA的整数倍);如果不含SHM_RND标志,那么程序员必须保证shmaddr的实参是个页面对齐的地址。

@shmflg,可选的值有3个,SHM_RND、SHM_RDONLY、SHM_REMAP。

    SHM_RND上面讲过了;
    SHM_RDONLY 设置本进程对这块共享内存是只读的(当然,首先进程对这个共享内存本来就具有读权限,SHM_RDONLY 的设置才能有效,如果本来就没有读权限,即使设置了SHM_RDONLY 标志,本进程也无法读这块内存);如果不设置SHM_RDONLY 标志,那么系统会设置本进程对这块内存是读写的(当然,首先进程对这个共享内存本来就具有读写权限);另外,内核没有提供只写这种标志。
    SHM_REMAP 把shmid指定的共享内存重新映射到shmaddr指定的地址(大小为size),如果shmaddr指定的地址(整个size范围内)早就被占用了,那么本函数会返回错误,这种情况下,只能把shmaddr设定为NULL。

返回值:成功则返回共享内存在本进程空间的地址,失败则返回-1并设置errno。

关联:本函数执行完之后,本共享内存的描述结构shmid_ds的三个成员会被更新:shm_atime更新为当前时间、shm_lpid更新为本进程的PID、shm_nattch加1。
    fork出的子进程可以继承父进程映射好的共享内存;通过execve函数启动新程序之后,前面的映射好的共享内存全部都会被detach掉;_exit(2)函数也会把前面的映射好的共享内存全部detach掉。

shmdt函数的功能就简单了,取消掉那块共享内存到本进程地址空间的映射,形参的值就是shmat函数的返回值。本共享内存的描述结构shmid_ds的三个成员会被更新:shm_dtime更新为当前时间、shm_lpid更新为本进程的PID、shm_nattch减1,If shm_nattch becomes 0 and the segment is marked for deletion, the segment is deleted。

返回值:成功则返回0,失败则返回-1并设置errno。

4、int shmctl(int shmid, int cmd, struct shmid_ds *buf);

形参@buf的结构为:

struct shmid_ds {
               struct ipc_perm shm_perm;    /* Ownership and permissions */
               size_t          shm_segsz;   /* Size of segment (bytes) */
               time_t          shm_atime;   /* Last attach time */
               time_t          shm_dtime;   /* Last detach time */
               time_t          shm_ctime;   /* Last change time */
               pid_t           shm_cpid;    /* PID of creator */
               pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
               shmatt_t        shm_nattch;  /* 本共享内存此时被多少个进程给attach了 */
               ...
           };

       The ipc_perm structure is defined as follows (uid、 gid、 mode are settable using IPC_SET):

           struct ipc_perm {
               key_t          __key;    /* Key supplied to shmget(2) */
               uid_t          uid;      /* Effective UID of owner */
               gid_t          gid;      /* Effective GID of owner */
               uid_t          cuid;     /* Effective UID of creator */
               gid_t          cgid;     /* Effective GID of creator */
               unsigned short mode;     /* Permissions + SHM_DEST and
                                           SHM_LOCKED flags */
               unsigned short __seq;    /* Sequence number */
           };

@cmd,可选的值如下:

· IPC_STAT 读取共享内存的描述结构shmid_ds(前提是进程具备读权限)
· IPC_SET 修改共享内存的描述结构shmid_ds的某些成员,只有这几个成员允许被本函数修改:shm_perm.uid、shm_perm.gid、 shm_perm.mode(低9位),这时shm_ctime会被自动更新。

· IPC_RMID 删除这个共享内存,注意,并不是立即删除,只有所有attach这块共享内存的进程,全部都detach之后(这时shmid_ds结构的shm_nattch成员的值会变成0),才会被真正删除。如果该共享内存已经被标记为删除,那么shm_perm.mode将会被添加SHM_DEST标志。

· IPC_INFO,读取整个系统中,共享内存的各种限制数、参数等。由于第四参数的类型为struct shmid_ds,因此,本命令读取shminfo时,必须得转换一下类型,shminfo的结构如下:

struct  shminfo {
                         unsigned long shmmax; /* Maximum segment size */
                         unsigned long shmmin; /* Minimum segment size;
                                                  always 1 */
                         unsigned long shmmni; /* Maximum number of segments */
                         unsigned long shmseg; /* Maximum number of segments
                                                  that a process can attach;
                                                  unused within kernel */
                         unsigned long shmall; /* Maximum number of pages of
                                                  shared memory, system-wide */
                     };

· SHM_INFO 读取以下内容:

struct shm_info {
                         int           used_ids; /* # of currently existing
                                                    segments */
                         unsigned long shm_tot;  /* Total number of shared
                                                    memory pages */
                         unsigned long shm_rss;  /* # of resident shared
                                                    memory pages */
                         unsigned long shm_swp;  /* # of swapped shared
                                                    memory pages */
                         unsigned long swap_attempts;
                                                 /* Unused since Linux 2.4 */
                         unsigned long swap_successes;
                                                 /* Unused since Linux 2.4 */
                     };
· SHM_STAT 功能与IPC_STAT.命令相同,区别在于传实参时,shmid对应的实参改为:一个内核数组索引,这个数组维护着系统中所有共享内存的信息。

· SHM_LOCK 禁止该共享内存的交换。The caller must fault in any pages that are required to be present after locking is enabled。执行此命令后,shm_perm.mode会被加入SHM_LOCKED标志。
· SHM_UNLOCK 允许共享内存交换。

返回值:失败时返回-1,并设置errno,成功时,根据cmd的不同返回值不同:IPC_INFO 、SHM_INFO的返回值为相关信息数组的最高入口。其他命令成功后返回0。

应用实例:父进程占有信号量,然后fork出子进程,然后阻塞一秒,然后往共享内存写入一个字符串,然后释放信号量。子进程等待信号量,等待成功后,读取共享内存中的数据。

如果不使用信号量进行同步的话,在父进程阻塞1秒的时间段内,子进程就会从共享内存读数据,这时父进程还没把数据写进去。

//编译: gcc -o shm_exe sharedMemory.c
#include <stdio.h>
#include <stdlib.h>
 #include <sys/types.h>
#include <sys/ipc.h>
#include <string.h>
#include <errno.h>
#include <sys/sem.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/shm.h>

void V(int semid) //占用资源
{
	struct sembuf sops[2];
	sops[0].sem_num = 0;        /* 要操作的信号量的编号为0 */
	sops[0].sem_op = 0;         /* Wait for value to equal 0 */
	sops[0].sem_flg =  0;        /* 既不IPC_NOWAIT,也不IPC_UNDO */

	sops[1].sem_num = 0;        /* 要操作的信号量的编号为0 */
	sops[1].sem_op = 1;         /* >0 的作用是,把第sem_num个信号量的当前值+sem_op   */
	sops[1].sem_flg = SEM_UNDO;        /* IPC_NOWAIT,SEM_UNDO. */

	if (semop(semid, sops, 2) == -1)
	{
		printf("V->semop  failed, infor:%s\r\n", strerror(errno) );
	    exit(1);
	}

}

void P(int semid) //释放资源
{
	struct sembuf sops[5];
	sops[0].sem_num = 0;        /* 要操作的信号量的编号为0 */
	sops[0].sem_op = -1;         /* -1 */
	sops[0].sem_flg =  SEM_UNDO;        /* IPC_NOWAIT,SEM_UNDO. */

	if (semop(semid, sops, 1) == -1)
	{
		printf("P->semop  failed, infor:%s\r\n", strerror(errno) );
	    exit(1);
	}
}

/*
 * 功能:	创建一个二值信号量
 * 形参:	filePath文件路径,用于ftok函数
 * 返回:	成功则返回信号量id,失败则直接令调用本函数的进程退出
 * */
int creat_semphore(char* filePath)
{
	key_t key = ftok(filePath, 'f');
		if(-1 ==  key)
		{
			printf("ftok failed, infor:%s\r\n", strerror(errno) );
			exit(EXIT_FAILURE);
		}
		else
		{
			printf("ftok ok, key = %d\r\n", key );
		}

		int semid = semget(key, 1, IPC_CREAT  | 0666 );//灯集中只要一个灯
		if(-1 ==  semid)
		{
			printf("semget failed, infor:%s\r\n", strerror(errno) );
			exit(EXIT_FAILURE);
		}
		else
		{
			printf("semget ok, semid = %d\r\n", semid );
		}

		return semid;
}
/*
 * 功能:	创建一个共享内存
 * 形参:	filePath文件路径,用于ftok函数; size创建的共享内存的字节数
 * 返回:	成功则返回共享内存id,失败则直接令调用本函数的进程退出
 * */
int creat_sharedMemory(char* filePath, size_t size)
{
		key_t key = ftok(filePath, 'f');
		if(-1 ==  key)
		{
			printf("ftok failed, infor:%s\r\n", strerror(errno) );
			exit(EXIT_FAILURE);
		}
		else
		{
			printf("ftok ok, key = %d\r\n", key );
		}

		int shmid = shmget(key, size, IPC_CREAT  | 0666 );//灯集中只要一个灯
		if(-1 ==  shmid)
		{
			printf("shmget failed, infor:%s\r\n", strerror(errno) );
			exit(EXIT_FAILURE);
		}
		else
		{
			printf("shmget ok, shmid = %d\r\n", shmid );
		}

		return shmid;
}
int main(int argc, char *argv[])
{
	printf("exe file path: %s\r\n", argv[0] );

	int semid = creat_semphore(argv[0]);
	int shmid = creat_sharedMemory(argv[0], 100);
	printf("father occupy semaphore!\r\n");
	V(semid);//父进程占用资源,只有父进程写完了,才允许子进程读
	printf("father occupied semaphore!\r\n");
	pid_t pid = fork();

	if(pid == 0 )//子进程
	{
		printf("child process start\r\n");
		char *memAddr = shmat(shmid, NULL, SHM_RND);
		V(semid);
		printf("child occupied semaphore!\r\n");
		printf("child read shared memory: %s\r\n", memAddr);
		P(semid);
		printf("child released semaphore!\r\n");
		_exit(0);
	}
	else if(pid > 0)//父进程
	{
		char *memAddr = shmat(shmid, NULL, SHM_RND);		
		char str[] = "this string is from father process!";
		memcpy(memAddr, str, sizeof(str));
		sleep(1);
		P(semid);
		printf("father released semaphore!\r\n");

		int stat;
		if(waitpid(pid, &stat, 0) != pid)
		{
			printf("child process failed!\r\n");
		}
		else
		{
			printf("child process returned value = %d\r\n", stat);
		}
		printf("father process return\r\n");
	}
	else
	{
		printf("fork failed, infor:%s\r\n", strerror(errno) );
		return -1;
	}

	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值