进程间通信---共享内存(Shared Memory)

共享内存的概念:

共享内存是进程间通信(IPC)中最简单的方式之一,也是最快的IPC形式。共享内存允许两个或多个进程访问同一块内存。当一个进程改变了这块内存中的内容的时候,其他进程就可以察觉到这种更改。一旦这样的内存映射共享它的进程的地址空间,这些进程间的数据传递将不再涉及到内核,即进程不再通过执行进入内核的系统调用来传递数据,而是这些进程通过共享内存来传递数据。

共享内存的原理:

共享内存就是有一块内存被多个进程共享,所谓共享,就是在物理内存中开辟一块内存,让两个或两个以上的进程分别将新开辟的物理内存映射到自己的地址空间。

什么是映射:

在物理内存中开辟一块空间,然后两个进程将各自的页表进行修改,然后映射到各自的进程地址空间上,进而把地址空间上的虚拟地址返回给用户,往后就可以使用这个虚拟地址。当执行一条命令的时候,在CPU上访问的地址其实是虚拟地址,只有正在执行这条命令时,它才会把虚拟地址转换为物理地址。

共享内存的实现步骤:

1.创建共享内存区,通过shmget实现。在物理内存中开辟一块共享内存区。

2.把这块共享内存区挂接映射到两个进程的地址空间上,通过shmat实现。

3.完成通信之后,撤销内存映射关系,通过shmdt进行脱离。

4.删除共享内存区,通过shmctl实现。


共享内存示意图:
共享内存的数据结构:
在/usr/include/linux/shm.h 中:
struct shmid_ds {
	struct ipc_perm		shm_perm;	/* operation perms */
	int			shm_segsz;	/* size of segment (bytes) */
	__kernel_time_t		shm_atime;	/* last attach time */
	__kernel_time_t		shm_dtime;	/* last detach time */
	__kernel_time_t		shm_ctime;	/* last change time */
	__kernel_ipc_pid_t	shm_cpid;	/* pid of creator */
	__kernel_ipc_pid_t	shm_lpid;	/* pid of last operator */
	unsigned short		shm_nattch;	/* no. of current attaches */
	unsigned short 		shm_unused;	/* compatibility */
	void 			*shm_unused2;	/* ditto - used by DIPC */
	void			*shm_unused3;	/* unused */
};
在 /usr/include/linux/ipc.h 中
struct ipc_perm
{
    __kernel_key_t    key;
    __kernel_uid_t    uid;
    __kernel_gid_t    gid;
    __kernel_uid_t    cuid;
    __kernel_gid_t    cgid;
    __kernel_mode_t    mode; 
    unsigned short    seq;
};

 

共享内存的特性:
  • 共享内存是最快的进程间通信的方案。不管是管道还是消息队列都必须把用户数据拷贝至内核,然后接收方再从内核中拷进来。共要进行两次拷贝,而共享内存一旦映射成功,一个进程向共享内存区写入了数据,其他共享这个内存的所有进程就能立刻看到其中的内容。
  • 共享内存没有提供同步与互斥的机制。这部分的功能需要自己完成。若一个进程正在想共享内存区中写数据,则在它做完这一步的操作前,别的进程不应该去读或者写数据。


共享内存函数:

shmget函数:用来创建共享内存

头文件:

 #include <sys/ipc.h>
 #include <sys/shm.h>

 函数原型:
    int shmget(key_t key, size_t size, int shmflg);
参数: 
    key:进程间通信的键值,ftok的返回值
    size:共享内存的大小
    shmflg:标识函数的行为及共享内存的权限。取值如下:
           IPC_CREAT:如果不存在就创建共享内存
           IPC_EXCL:和IPC_CREAT搭配使用,如果已经存在,则返回失败
           权限位:设置共享内存的访问权限
    返回值:成功返回一个非负整数,即共享内存的标识符;失败返回-1


shmat函数:将共享内存段映射到进程地址空间。

即让进程和共享内存之间建立一种联系,让进程的某个指针指向此共享内存。

头文件:

#include <sys/types.h>

#include <sys/shm.h>

函数原型:
    void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:
    shmid:共享内存标识符,shmget的返回值
    shmaddr:指定映射地址(若为NULL,则由系统自动指定)
    shmflg:共享内存的访问权限和映射条件(通常为0),取值如下:
            0:共享内存具有可读可写权限
            SHM_RDONLY:映射的内存只读
            SHM_RND:shmaddr非空时才有效。
                    shmaddr不为NULL且shmflg无IPC_RND标记,则以参数shmaddr为连接地址;
                    shmaddr不为NULL且shmflg指定了IPC_RND标记,则自动将参数shmflg调整为SHMLBA的整数倍。
                    公式:shmaddr = shmaddr-(shmaddr % SHMLBA)
返回值: 成功返回一个指针,指向共享内存第一个节;失败返回-1           
SHMLBA的定义的两种情况:

#define   SHMLBA   PAGE_SIZE

#define   SHMLBA   (4 * PAGE_SIZE)


shmdt函数:将共享内存段与当前进程脱离

头文件:

#include <sys/types.h>
#include <sys/shm.h>

函数原型:
    int shmdt(const void *shmaddr);
参数:
    shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1

注意:将共享内存段与当前进程脱离不等于删除共享内存段。


shmctl函数:用于控制共享内存

头文件:

#include <sys/ipc.h>
#include <sys/shm.h>

函数原型:       
    int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:
    shmid:由shmget返回的共享内存的标识码
    cmd:将要采取的动作,有如下取值:
        IPC_RMID:删除共享内存
        IPC_SET:在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds数据结构中给出的值
        IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值
        SHM_LOCK:锁定共享内存段(超级用户)
        SHM_UNLOCK:解锁共享内存段
    buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1


注意:

SHM_LOCK 用于锁定内存,禁止内存交换。并不代表共享内存被锁定后禁止其它进程访问。其真正的意义是:被锁定的内存不允许被交换到虚拟内存中。这样做的优势在于让共享内存一直处于内存中,从而提高程序性能。

应用小例子:客户端循环26次,每次循环由'A'向后+1,服务器端循环26次,接收客户端的数据并打印出来。

comm.h

#ifndef _COMM_H_
#define _COMM_H_

#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>

#define PATHNAME "."
#define PROJ_ID 0x0001

int CreateShm(int size);
int DestroyShm(int shmid);
int GetShm(int size);

#endif

comm.c

#include "comm.h"

static int CommShm(int size,int flags){
	key_t key = ftok(PATHNAME,PROJ_ID);
	if(key < 0){
		perror("ftok");
		return -1;
	}
	int shmid = 0;
	if((shmid = shmget(key,size,flags)) < 0){
		perror("shmget");
		return -1;
	}
	return shmid;
}

int CreateShm(int size){
	return CommShm(size,IPC_CREAT|IPC_EXCL|0666);
}

int GetShm(int size){
	return CommShm(size,IPC_CREAT);
}

int DestroyShm(int shmid){
	if(shmctl(shmid,IPC_RMID,NULL) < 0){
		perror("shmctl");
		return -1;
	}
	return 0;
}

server.c

#include "comm.h"

int main()
{
	int shmid = CreateShm(4096);
	char *addr = shmat(shmid,NULL,0);
	sleep(2); //2 秒
	int i = 0;
	while( i++ < 26){
		printf("client: %s\n",addr);
		sleep(1);
	}
	shmdt(addr);
	sleep(2);
	DestroyShm(shmid);
	return 0;
}

client.c

#include "comm.h"

int main()
{
	int shmid = GetShm(4096);
	char *addr = shmat(shmid,NULL,0);
	sleep(2);
	int i = 0;
	while(i < 26){
		addr[i] = 'A'+ i;
		i++;
		addr[i] = 0;
		sleep(1);
	}

	shmdt(addr);
	sleep(2);
	return 0;
}
运行结果:






猜你喜欢

转载自blog.csdn.net/qiana_/article/details/80150565