门户网站视频,旅游网站设计策划书,十大免费壁纸软件,丽水做企业网站的地方每一个不曾起舞的日子#xff0c;都是对生命的辜负。 共享内存一.共享内存的原理二.共享内存你的概念2.1 接口认识2.2演示生成key的唯一性2.3 再谈key三.共享资源的查看3.1 如何查看IPC资源3.2 IPC资源的特征3.3 进程之间通过共享内存进行关联四.共享内存的特点五.共享内存的内… 每一个不曾起舞的日子都是对生命的辜负。 共享内存一.共享内存的原理二.共享内存你的概念2.1 接口认识2.2演示生成key的唯一性2.3 再谈key三.共享资源的查看3.1 如何查看IPC资源3.2 IPC资源的特征3.3 进程之间通过共享内存进行关联四.共享内存的特点五.共享内存的内核结构六.共享内存函数的总结共享内存是为通信而诞生的。除了上一节中讲到的公共文件的方案还有什么其他方案呢----以共享内存的方式 一.共享内存的原理
在之前学过的进程地址空间的基础上我们知道进程之间具有独立性因为每个进程的内核数据结构的数据以及页表的映射都是独立的。而对于共享内存我们同样了解这是为了让进程之间能够进行通信的公共空间接下来就通过进程地址空间的结构去了解共享空间的位置及原理 OS为了让两个毫不相关的进程之间进行通信进行了三个工作
在对应的内存当中让用户帮OS申请一块空间通过指定的调用接口将创建好的内存映射进进程的地址空间用户就可以通过访问起始地址的方式来进行对申请的这块内存空间的访问未来不想通信 取消进程和内存的映射关系释放内存 因此我们把申请的这块空间称之为共享内存将映射关系称之为进程和共享内存进行挂接。将取消进程和内存的映射关系称之为去关联释放内存释放的就是共享内存。 理解
进程间通信是专门设计的用来IPC的和malloc/new不是一个东西。共享内存是一种通信方式所有想通信的进程都可以用。OS中一定会存在着很多共享内存。
二.共享内存你的概念
通过让不同的进程看到同一个内存块的方式叫做共享内存。
2.1 接口认识
#includesys/ipc.h
#includesys/shm.hint shmget(key_t key, size_t size, int shmflg);// size:共享内存的大小对于shmflg常见的有这么两种选择
IPC_CREAT如果不存在共享文件则创建存在则获取IPC_EXCL 1.无法单独使用单独使用没有意义需要结合IPC_CREAT2.IPC_CREAT|IPC_EXCL如果不存在就创建如果已存在就出错返回。即在用户的角度如果创建成功一定是一个新的shm
shmget返回值 记住他是一个标识符就够用了得到的是共享内存的标识符。和文件fd没有任何关系
key 是什么不重要最重要的是其具备的唯一性。
而获取key值则通过一个新的接口ftok ftok通过指定的字符串数据*pathname以及char类型的proj_id数据进行一系列的算法整合返回了具有唯一性的Key:
key_t ftok(char *pathname, char proj_id);由于创建的key值有可能已经被别人使用了因此有失败的可能性。创建Key值如果失败则返回-1。
2.2演示生成key的唯一性
makefile
.PHONY:all
all:shm_client shm_servershm_client:shm_client.ccg -o $ $^ -stdc11
shm_server:shm_server.ccg -o $ $^ -stdc11.PHONY:clean
clean:rm -f shm_client shm_servercomm.hpp
#ifndef _COMM_HPP_
#define _COMM_HPP_#includeiostream
#includecerrno
#includecstdlib
#includecstring
#includecstdio
#includesys/ipc.h
#includesys/shm.h#define PATHNAME .//当前路径
#define PROJ_ID 0x66key_t getKey()
{key_t k ftok(PATHNAME, PROJ_ID);if(k 0){// cin, cout, cerr -stdin, stdout, stderr-0, 1, 2;标准错误stderr向2打印。std::cerr errno : strerror(errno) std::endl;exit(1);//终止进程}return k;
}#endifshm_server.cc
#includecomm.hppint main()
{key_t k getKey();printf(0x%x\n, k);return 0;
}shm_client.cc
#includecomm.hppint main()
{key_t k getKey();printf(0x%x\n, k);return 0;
}通过make后执行发现两个程序的k值是一样的这就证明了ftok指定参数的返回值是唯一的。k实际上就是32位的一个整数
2.3 再谈key
OS中一定存在多个共享内存因为彼此之间可能都需要通信因此也就都需要申请一块空间。而OS申请的共享空间也一定和进程一样需要被管理既然需要管理那么一定也是先描述再组织的方式即共享内存 物理内存块共享内存的相关属性 。
之前谈到过key是什么不重要能进行唯一性的标识最重要因此创建共享内存的时候是如何保证共享内存在系统中是唯一的呢当然是通过key来确定的只要一个进程也看到了同一个key就能够访问这个共享内存。那么key在哪里实际上这就和PCB一样key就在内核中的属性集合里即
struct shm{key_t key;//...
}即key是通过shmget这样的系统调用设置进入共享内存属性中用来表示该共享内存在内核中的唯一性
shmid和key就好比fd和inode。为什么有了key还需要shmid呢通过key和shmid的区分能够面向系统层面和用户层面这样能够更好的进行解耦以免内核中的变化影响到用户级。
三.共享资源的查看 共享(IPC) 3.1 如何查看IPC资源
ipcs -m/q/s查看
3.2 IPC资源的特征 我们发现当第一次执行成功之后再次调用不会成功这是因为共享内存并不像管道一样进程结束之后自动释放内存共享内存的声明周期是随着OS的不是随着进程的因此这就需要我们主动的去释放这块空间即通过指令ipcrm -m 加上这块共享内存shmid的值虽然key也是唯一的但key是系统层面的shmid才是对于我们用户层面的。
对于释放共享内存除了上述的手动命令其也有自己的接口能够进行共享内存物理空间的释放即
#includesys/ipc.h
#includesys/shm.h
int shmctl(int shmid, int cmd, struct shmid_ds *buf);//cmd代表控制的种类即内置的有多种选择。
//返回值失败则返回-1void delShm(int shmid)
{if(shmctl(shmid, IPC_RMID, nullptr) -1){std::cerr errno : strerror(errno) std::endl;}
}以下代码复制SSH渠道执行while :; do ipcs -m; sleep 1; echo--------------------;done并在左侧执行shm_server观察现象
#ifndef _COMM_HPP_
#define _COMM_HPP_#includeiostream
#includecerrno
#includecstdlib
#includecstring
#includecstdio
#includesys/ipc.h
#includesys/shm.h
#includeunistd.h#define PATHNAME .//当前路径
#define PROJ_ID 0x66
#define MAX_SIZE 4096 //单位是字节
key_t getKey()
{key_t k ftok(PATHNAME, PROJ_ID);//可以获取同样的keyif(k 0){// cin, cout, cerr -stdin, stdout, stderr-0, 1, 2;标准错误stderr向2打印。std::cerr errno : strerror(errno) std::endl;exit(1);//终止进程}return k;
}int getShmHelper(key_t k, int flags)
{//k是要shmget设置进入共享内存属性中的用来表示//该共享内存在内核中的唯一性//shmid VS key//fd VS inodeint shmid shmget(k, MAX_SIZE, flags);if(shmid 0){std::cerr errno : strerror(errno) std::endl;//失败就终止没有共享内存了exit(2);}return shmid;
}
int getShm(key_t k)
{return getShmHelper(k, IPC_CREAT);
}int createShm(key_t k)
{return getShmHelper(k, IPC_CREAT | IPC_EXCL);
}void delShm(int shmid)
{if(shmctl(shmid, IPC_RMID, nullptr) -1){std::cerr errno : strerror(errno) std::endl;}}#endif#includecomm.hppint main()
{key_t k getKey();printf(key: 0x%x\n, k); // keyint shmid createShm(k);printf(shmid: %d\n, shmid); // shmidsleep(5);//5s之后就会被删除delShm(shmid);return 0;
}发现进程执行之前没有对应的信息执行之后信息出现5s最终被释放。再次执行也不会出错。
进程之间进行关联
上述都没有提到进程进行关联的问题有几个进程能够进行关联在上述动图右侧nattch可以看到明显看到上面的nattch值为0那么下面就来进行挂接一下
此时就又有一个新的接口
#includesys/types.h
#includesys/shm.h//参数1指定的共享内存参数二地址空间一般设置为nullptr参数三读写权限一般设置为0就可以了默认就可以读写。//返回值共享内存空间的起始地址就等价于malloc的返回值
void *shmat(int shmid, const void* shmaddr, int shmflg);因此就可以在comm.hpp中加上这个功能
void* attchShm(int shmid)
{void* mem shmat(shmid, nullptr, 0);//64系统指针占8字节if((long long)mem -1L){std::cerr errno : strerror(errno) std::endl;exit(3);}return mem;
}但此时挂接就需要进行读写因此还需要在createshm中添加选项
接下来看看运行结果 可以发现的是由于我们新增了0600即拥有者的读写权限perm也就显示了600此外nattch的链接数量也变成了1这说明有一个进程和这个共享内存关联起来了而我们所演示的就是我自己的进程与共享内存进行了关联。运行之后同样可以释放。但上面直接释放过于粗暴因为我们之前将进程和共享内存进行了关联所以我们需要在释放之前将这个关联去掉否则就有可能出问题。去关联并不是删掉共享内存而是回收对应的页表。为了去关联就又引出了一个接口
// 参数就是在shmat时设定的返回值对于返回值成功就是0失败就是-1.
int shmdt(const void* shmaddr);因此添加了这段代码后就比上述现象在结束之前多了一个nattch变为0的过程。 3.3 进程之间通过共享内存进行关联
上述我们已经实现了shm_server与共享内存的关联如果想让两个进程之间进行通信那就需要另一个shm_client也与同一个共享内存进行关联
但对于这段代码不需要释放共享内存因为在shm_server.cc中已经实现。这段接入之后nattch的关联数就会变成2。 在之前的学习中我们通过管道采用char buffer[1024]缓冲区的方式进行通信现在有了共享内存就可以通过共享内存将两个进程连接起来。
shm_server.cc
#includecomm.hppint main()
{key_t k getKey();printf(key: 0x%x\n, k); // keyint shmid createShm(k);printf(shmid: %d\n, shmid); // shmidsleep(5);//5s之后就会被删除char* start (char*)attchShm(shmid);//挂接成功printf(attach success, addresss start: %p\n, start);//使用while(true){//char buffer[]; read(pipefd, buffer)printf(client say: %s\n, start);sleep(1);}//去关联让进程和共享内存丧失关联性detachShm(start);sleep(5);delShm(shmid);return 0;
}shm_client.cc
#includecomm.hppint main()
{key_t k getKey();printf(key: 0x%x\n, k);int shmid getShm(k);printf(shmid: %d\n, shmid);sleep(5);//与共享内存产生关联char* start (char*)attchShm(shmid);printf(attach success, address start: %p\n, start);const char* message hello server,我是另一个进程正在和你通信;pid_t id getpid();int cnt 1;char buffer[1024];while(true){sleep(1);snprintf(start, MAX_SIZE, %s[pid:%d][消息编号:%d], message, id, cnt);// snprintf(buffer, sizeof(buffer), %s[pid:%d][消息编号:%d], message, id, cnt);// memcpy(start, buffer, strlen(buffer)1);// //pid, count, message}//去关联让进程和共享内存丧失关联性detachShm(start);return 0;
}执行记得手动ipcrm -m) 四.共享内存的特点
共享内存的优点
所有进程间通信速度是最快的因为其共享内存在进程地址空间能够大大减少数据的拷贝次数。即本来用buffer现在没有必要 综合考虑管道和共享内存考虑键盘输入和显示器输入共享内存共有几次数据拷贝即同一段代码通过管道和共享内存分别进行了几次拷贝 对于管道来说通过的是如下步骤 将键盘输入的数据放到自己指定的缓冲区buffer中为第一次将buffer中的数据拷贝到管道中是第二次将管道中的数据拷贝到另一个进程的缓冲区中为第三次将缓冲区的数据打印在显示器中为第四次此外还有输入输出流stdin和stdout所以为42次。 对于共享内存来说没有中间的buffer因此也就是22次。 共享内存的缺点
共享内存不会进行同步和互斥的操作没有对数据做任何的保护。也就是说共享内存并不像管道一样管道是当读写都打开时如果不读写满就会不写了如果写端不写读端就不会继续读了并且阻塞在那里而共享内存没有做这样的保护。那么如何保护?今后将会学到信号量和互斥锁的方式对管道进行保护。
五.共享内存的内核结构
共享内存数据结构
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 */
};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 */
}共享内存的大小
共享内存的大小一般建议是4KB的整数倍因为系统分配共享内存是以4KB为单位的 — 内存划分内存块的基本单位。 否则内核会给你向上取整。但我们能够使用的仍是我们指定的大小。 六.共享内存函数的总结
上面在演示的时候已经逐步的介绍了有关共享内存函数的功能我们在这里总结一下
shmget函数
功能用来创建共享内存
原型
int shmget(key_t key, size_t size, int shmflg);
参数key:这个共享内存段名字size:共享内存大小shmflg:由九个权限标志构成它们的用法和创建文件时使用的mode模式标志是一样的
返回值成功返回一个非负整数即该共享内存段的标识码失败返回-1shmat函数
功能将共享内存段连接到进程地址空间
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数shmid: 共享内存标识shmaddr:指定连接的地址shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值成功返回一个指针指向共享内存第一个节失败返回-1说明 shmaddr为NULL核心自动选择一个地址
shmaddr不为NULL且shmflg无SHM_RND标记则以shmaddr为连接地址。
shmaddr不为NULL且shmflg设置了SHM_RND标记则连接的地址会自动向下调整为SHMLBA的整数倍。公式shmaddr -
(shmaddr % SHMLBA)
shmflgSHM_RDONLY表示连接操作用来只读共享内存shmdt函数
功能将共享内存段与当前进程脱离
原型
int shmdt(const void *shmaddr);
参数shmaddr: 由shmat所返回的指针
返回值成功返回0失败返回-1
注意将共享内存段与当前进程脱离不等于删除共享内存段shmctl函数
功能用于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数shmid:由shmget返回的共享内存标识码cmd:将要采取的动作有三个可取值如下buf:指向一个保存着共享内存的模式状态和访问权限的数据结构一般设定为nullptr即可
返回值成功返回0失败返回-1此外关于最终的代码展示在如下链接共享内存代码