叫别人建个网站多少钱,制造业中小微企业,展示设计网站有哪些,网站建设主要包括那些部分目录 一. 共享内存实现进程间通信的原理
二. 共享内存相关函数
2.1 共享内存的获取 shmget / ftok
2.2 共享内存与进程地址空间相关联 shmat
2.3 取消共享内存与进程地址空间的关联 shmdt
2.4 删除共享内存 shmctl
2.5 通信双方创建共享内存代码
三. 共享内存实现进程间…目录 一. 共享内存实现进程间通信的原理
二. 共享内存相关函数
2.1 共享内存的获取 shmget / ftok
2.2 共享内存与进程地址空间相关联 shmat
2.3 取消共享内存与进程地址空间的关联 shmdt
2.4 删除共享内存 shmctl
2.5 通信双方创建共享内存代码
三. 共享内存实现进程间通信
3.1 实现方法及特性
3.2 为共享内存添加访问控制
四. 总结 一. 共享内存实现进程间通信的原理
要实现进程间通信就必须让相互之间进行通信的进程看到同一份资源同一块内存空间如通过管道实现进程间通信本质就是让两个进程分别以读和写的方式打开同一份管道文件一个进程向管道中写数据另一个进程再从管道中将数据读出这样两个进程就可以看到同一份内存空间从而实现了进程间通信。
System V共享内存实现进程间通信的方式与管道相同区别在于管道是基于文件的而共享内存则是直接申请内存空间不需用进行文件相关操作。通过System V共享内存实现通信的进程都会使用物理内存中的同一块空间这一块公共的物理内存空间经过通信双方进程的页表映射到进程地址空间的共享区通信双方进程在运行期间拿到共享区虚拟地址通过页表映射就可以看到同一块物理内存就可以实现进程间通信。
如果操作系统内有多组通过System V共享内存方式相互通信的进程处于运行状态那么就会存在多组共享内存操作系统需要对这些共享内存空间进行管理管理方式为先通过struct结构体进行描述再利用特定的数据结构组织。
可以这样理解共享内存 共享的物理内存 对应的内核级数据结构。 图1.1 共享内存的实现原理 二. 共享内存相关函数
共享内存实现进程间通信的步骤可以总结为创建共享内存 - 共享内存与地址空间相关联 - 通信 - 共享内存与地址空间解绑 - 销毁共享内存。
2.1 共享内存的获取 shmget / ftok shmget函数获取共享内存 头文件#includesys/ipc.h、#includesys/shm.h 函数原型int shmget(key_t key, size_t size, int shmflg) 函数参数 key -- 特定共享内存的标识符 size -- 共享内存的大小 shmflg -- 共享内存获取的权限参数 返回值创建成功返回共享内存的编号称为shmid失败返回-1 共享内存标识符keyOS中可能存在多个共享内存需要保证通信双方看到同一块共享内存因此每个共享内存都需要一个特定的key值进行区分这个key值是多少并不重要只要保证它在OS中是唯一的即可。通信双方进程Serve Client需要约定相同的算法保证他们可以使用shmget获取到同一块共享内存。
ftok函数可以用于获取key值只要调用ftok的实参相同就会返回相同的key值。 ftok函数获取共享内存标识符key 头文件 #includesys/ipc.h、#includesys/types.h 函数原型key_t ftok(const char* pathname, int proj_id); 函数参数 pathname项目文件路径 proj_id项目文件的id编号 返回值成功返回特定的key值否则返回-1。 共享内存大小size以字节为单位建议取页PAGE4096bytes大小的整数倍因为如果获取共享内存空间的大小不是页大小的整数倍OS就会向上取整申请到页大小整数倍的内存空间但是多申请的空间却不能被用户所使用。如申请4097bytes的共享内存OS会实际申请2*4096bytes的空间而能被使用的只有4097bytes剩下的都浪费掉了。
权限参数shmflg有IPC_CREAT、IPC_EXCL、共享内存起始权限码、0这几种选项他们之间通过竖划线 | 隔开每个选项都有其意义。
IPC_CREAT如果key标识的共享内存存在就直接将其获取如果不存在就创建。IPC_EXCL单独使用没有任何意义一般配合IPC_CREAT使用IPC_CREAT | IPC_EXCL表示如果共享内存不存在就将其创建如果存在直接报错这样可以保证获取到的共享内存是一块全新的共享内存。起始权限码用户对于这块共享内存的使用权限如0666就表示拥有者、所属组、其他人就具有读写权限。0只能获取已经存在的共享内存不能创建新的不存在就报错。
一般而言通信双方分别以 IPC_CREAT | IPC_EXCL 和 0 的方式获取共享内存确保一方创建全新的共享内存另一方只能获取到该共享内存传0阻断不存在创建新共享内存的可能。
代码2.1以 IPC_CREAT | IPC_EXCL | 0666 的方式获取共享内存运行代码就可以成功获取共享内存但是当第二次运行代码却发现运行出错了(见图2.1)这是因为该共享内存再第一次程序运行后被创建存在于操作系统中IPC_CREAT | IPC_EXCL获取的共享内存一定是全新的因此第二次运行程序会失败删除该共享内存之后才可以再次成功运行。
结论共享内存的生命周期是随OS内核的而不是随进程的。
代码2.1获取共享内存
// common.hpp -- 头文件
#pragma once#include iostream
#include sys/types.h
#include sys/ipc.h
#include sys/shm.h#define PATH_NAME .
#define PROJ_ID 0x66
#define SIZE 4096// shmServe.cc -- 客户端代码源文件用于接收信息
#include common.hppint main()
{// 获取共享内存key值key_t k ftok(PATH_NAME, PROJ_ID); if(k -1){perror(ftok);exit(1);}// 创建共享内存int shmid shmget(k, SIZE, IPC_CREAT | IPC_EXCL | 0666);if(shmid -1){perror(shmget);exit(2);}printf(Serve# 共享内存获取成功shmid:%d\n, shmid);return 0;
} 图2.1 代码2.1的两次运行结果 这里介绍两条指令分别用于查看共享内存信息和删除共享内存
ipcs -m 指令查看系统中所有共享内存的详细信息。ipcrm -m [shmid]通过指定共享内存的shmid来删除指定的共享内存。
当然也可以通过代码删除共享内存本文后面会讲解。 图2.2 通过指令查看共享内存的属性信息和删除共享内存 2.2 共享内存与进程地址空间相关联 shmat shmat函数将共享内存关联到进程地址空间 头文件#includesys/types.h、#includesys/shm.h 函数原型void* shmat(int shmid, const void* shmaddr, int shmflg) 函数参数 shmid进行挂接的共享内存的shmid shmaddr指定挂接的虚拟地址传NULL表示让OS自动选择挂接地址 shmflg挂接权限相关参数 返回值若成功返回挂接到的虚拟地址失败返回nullptr 挂接地址shmaddr参数由于我们并不可知虚拟地址的具体使用情况所以这个参数基本都是传NULL/nullptr来让OS自动选择虚拟地址进行关联。
挂接权限shmflg如果传SHM_RDONLY这表示对应共享内存空间只有读权限传其他都是读写权限一般shmflg都传实参0。
当共享内存与虚拟地址关联期间使用ipcs -m指令查看共享内存属性信息nattch就会变为1如果通信双方都与共享内存进行了关联那么nattch就是2。
2.3 取消共享内存与进程地址空间的关联 shmdt shmdt函数让共享内存与当前进程脱离 头文件#includesys/types.h、#includesys/shm.h 函数原型int shmdt(const char* shmaddr) 返回值成功返回0失败返回-1 2.4 删除共享内存 shmctl
通过共享内存控制shmctl函数共享内存控制函数可以删除共享内存。
删除共享内存的操作只要通信双方有一方指向即可否则会造成重复删除。一般而言读取信息的进程创建新的共享内存也负责删除共享内存遵循谁创建、谁删除的原则。 shmctl函数控制共享内存 头文件#includesys/ipc.h #includesys/shm.h 函数原型int shmctl(int shmid, int cmd, struct shmid_ds* buf) 函数参数 shmid -- 共享内存的shmid cmd -- 控制指令选择操作 buf -- 指向描述共享内存属性信息的结构体指针 返回值成功返回非负数失败返回-1 形参cmd可以选择具体的控制策略
IPC_STAT -- 以buf为输出型参数获取共享内存的属性信息。IPC_SET -- 设置共享内存的属性为buf指向的内容。IPC_RMID -- 删除共享内存此时buf传空指针NULL。
2.5 通信双方创建共享内存代码
代码2.2头文件common.hpp -- 由通信双方共同包含
#pragma once#include iostream
#include unistd.h
#include sys/types.h
#include sys/ipc.h
#include sys/shm.h#define PATH_NAME .
#define PROJ_ID 0x66
#define SIZE 4096
代码2.3服务端代码shmServe.cc -- 用于数据读取
#include common.hppint main()
{// 获取共享内存key值key_t k ftok(PATH_NAME, PROJ_ID); if(k -1){perror(Serve ftok);exit(1);}printf(Serve# 成功获取key值key:%d\n, k);// 创建共享内存int shmid shmget(k, SIZE, IPC_CREAT | IPC_EXCL | 0666);if(shmid -1){perror(Sreve shmget);exit(2);}printf(Serve# 共享内存获取成功shmid:%d\n, shmid);// 将共享内存与进程相关联char* shmaddr (char*)shmat(shmid, NULL, 0);if(shmaddr nullptr){perror(Serve shmat);exit(3);}printf(Serve# 共享内存与进程成功关联shmid:%d\n, shmid);// 通信代码// ... ...// 让共享内存脱离当前进程int n shmdt(shmaddr);if(n -1){perror(Serve shmdt);exit(4);}printf(Serve# 共享内存成功脱离进程shmid:%d\n, shmid);// 删除共享内存n shmctl(shmid, IPC_RMID, NULL);if(n -1){perror(Serve shmctl);exit(5);}printf(Serve# 共享内存删除成功shmid:%d\n, shmid);return 0;
}
代码2.4客户端代码shmClient.cc -- 用于数据发送
#include common.hppint main()
{// 获取共享内存key值key_t k ftok(PATH_NAME, PROJ_ID); if(k -1){perror(Client ftok);exit(1);}printf(Client# 成功获取key值key:%d\n, k);// 创建共享内存int shmid shmget(k, SIZE, 0);if(shmid -1){perror(Client shmget);exit(2);}printf(Client# 共享内存获取成功shmid:%d\n, shmid);// 将共享内存与进程相关联char* shmaddr (char*)shmat(shmid, NULL, 0);if(shmaddr nullptr){perror(Client shmat);exit(3);}printf(Client# 共享内存与进程成功关联shmid:%d\n, shmid);// 通信代码// ... ...// 让共享内存脱离当前进程int n shmdt(shmaddr);if(n -1){perror(Client shmdt);exit(4);}printf(Client# 共享内存成功脱离进程shmid:%d\n, shmid);return 0;
}
三. 共享内存实现进程间通信
3.1 实现方法及特性
在数据输入端shmClient我们可以将共享内存视为一块通过malloc得来的char*指向的一段动态内存空可以使用printf系列函数向这块空间写数据或者将共享内存空间视为数组使用下标的形式给每个位置赋值这样就实现了将数据写入共享内存。
在数据读取端shmServe可以将共享内存视为一个大字符串通过特定的方式从这个大字符串中获取数据即可。
代码3.1和代码3.2实现了共享内存进程间通信的简单逻辑在shmClient端通过下标访问的方式每隔3s写一次数据在shmServe端每隔1s读取一次数据。先运行shmServe端代码间隔几秒后运行shmClient端代码根据图3.1展示的运行结果shmServe端在shmClient端开始运行之前就开始读取共享内存中的内容在shmClient运行起来后由于读快写慢shmClient写入的内容在shmServe端被多次读取可见共享内存没有访问控制。
结论1共享内存没有访问控制。
代码3.1shmClient端发送数据 // 通信代码char ch a;int count 0;for(; ch c; ch){shmaddr[count] ch;printf(write succsee# %s\n, shmaddr);sleep(3);}snprintf(shmaddr, SIZE, quit);
代码3.2shmServe端读取数据 // 通信代码while(true){printf([Client say]# %s\n, shmaddr);if(strcmp(shmaddr, quit) 0) break;sleep(1);} 图3.1 共享内存通信读写双方代码执行结果 通过观察上面的代码我们发现用户可以直接向共享内存中写数据和从共享内存中读取数据不需要经过用户级缓冲区共享内存的读或写操作最少只需要一次拷贝即可完成。而通过管道进行读写则需要将数据预先写入或读入缓冲区才可以写入管道文件或读出。图3.2为使用管道和共享内存的方法进行进程间通信时读和写操作涉及的数据拷贝情况管道通信至少要进行两次数据拷贝而共享内存可以只进行一次数据拷贝因此共享内存是一种高效的进程间通信手段。
结论2共享内存进行进程间通信通信的一方向共享内存中写入数据通信的另一方马上就能读取到数据不需要向操作系统中拷贝数据共享内存是所有进程间通信方法中效率最高的。 图3.2 管道和共享内存实现进程间通信的资源拷贝情况 管道通信的特性总结
不具有访问控制存在并发问题。不需要向OS内核中拷贝数据通信效率高。
3.2 为共享内存添加访问控制
通过使用命名管道加以辅助就可以为共享内存添加访问控制具体的实现方法和原理为
在读端shmServe程序开始运行时创建命名管道文件程序运行结束后管道文件销毁。在写端shmClient向共享内存中写入数据后向管道文件中写入任意的、少量的数据在读端shmServe获取共享内存内容之前先读取管道中的资源如果写端没有将期望的数据全部写入共享内存那么就不会向管道中写数据读端就必须阻塞等待管道中被写入数据也就无法获取共享内存中的数据。只有当写端完成向共享内存中写入一次数据然后向管道文件中写入数据让读端读到了管道资源后读端代码才可以继续运行获取到共享内存中的资源。
代码3.3 ~ 3.5为通过管道为共享内存添加访问控制的实现代码。
代码3.3common.hpp头文件 -- 被通信双方源文件包含
#pragma once#include iostream
#include cstring
#include cassert
#include unistd.h
#include fcntl.h
#include sys/types.h
#include sys/stat.h
#include sys/ipc.h
#include sys/shm.h#define PATH_NAME .
#define PROJ_ID 0x66
#define SIZE 4096#define FIFO_NAME fifo.ipc
#define MODE 0666// 定义类其构造和析构函数可以创建和销毁管道文件
class Init
{
public:Init(){int n mkfifo(FIFO_NAME, MODE);if(n -1) perror(mkfifo);assert(n ! -1);(void)n;}~Init(){int n unlink(FIFO_NAME);assert(n ! -1);(void)n;}
};#define READ O_RDONLY
#define WRITE O_WRONLY// 管道文件打开函数
int OpenFifo(const char* pathname, int flags)
{int fd open(pathname, flags);assert(fd ! -1);return fd;
}// 等待函数 -- 用于读端访问控制
// 管道内没有资源时就阻塞
void Wait(int fd)
{uint32_t temp 0;ssize_t sz read(fd, temp, sizeof(uint32_t));assert(sz sizeof(uint32_t));(void)sz;
}// 唤醒函数 -- 用于写端进程控制
// 向管道内写数据终止读端进程的阻塞等待
void WakeUp(int fd)
{uint32_t temp 1;ssize_t sz write(fd, temp, sizeof(uint32_t));assert(sz sizeof(uint32_t));(void)sz;
}// 管道关闭函数
void CloseFifo(int fd)
{close(fd);
}
代码3.4读端源文件shmServe.cc代码
#include common.hpp// 全局类对象
// 构造和析构函数分别负责管道文件的创建和销毁
Init init;int main()
{// 获取共享内存key值key_t k ftok(PATH_NAME, PROJ_ID); if(k -1){perror(Serve ftok);exit(1);}printf(Serve# 成功获取key值key:%d\n, k);// 创建共享内存int shmid shmget(k, SIZE, IPC_CREAT | IPC_EXCL | 0666);if(shmid -1){perror(Sreve shmget);exit(2);}printf(Serve# 共享内存获取成功shmid:%d\n, shmid);// 将共享内存与进程相关联char* shmaddr (char*)shmat(shmid, NULL, 0);if(shmaddr nullptr){perror(Serve shmat);exit(3);}printf(Serve# 共享内存与进程成功关联shmid:%d\n, shmid);// 通信代码int fd OpenFifo(FIFO_NAME, READ); // 只读方式打开管道文件while(true){Wait(fd); // 等待读取printf([Client say]# %s\n, shmaddr);if(strcmp(shmaddr, quit) 0) break;}// while(true)// {// printf([Client say]# %s\n, shmaddr);// if(strcmp(shmaddr, quit) 0) break;// sleep(1);// }// 让共享内存脱离当前进程int n shmdt(shmaddr);if(n -1){perror(Serve shmdt);exit(4);}printf(Serve# 共享内存成功脱离进程shmid:%d\n, shmid);// 删除共享内存n shmctl(shmid, IPC_RMID, NULL);if(n -1){perror(Serve shmctl);exit(5);}printf(Serve# 共享内存删除成功shmid:%d\n, shmid);CloseFifo(fd);return 0;
}
代码3.5写端源文件shmClient.cc代码
#include common.hppint main()
{// 获取共享内存key值key_t k ftok(PATH_NAME, PROJ_ID); if(k -1){perror(Client ftok);exit(1);}printf(Client# 成功获取key值key:%d\n, k);// 创建共享内存int shmid shmget(k, SIZE, 0);if(shmid -1){perror(Client shmget);exit(2);}printf(Client# 共享内存获取成功shmid:%d\n, shmid);// 将共享内存与进程相关联char* shmaddr (char*)shmat(shmid, NULL, 0);if(shmaddr nullptr){perror(Client shmat);exit(3);}printf(Client# 共享内存与进程成功关联shmid:%d\n, shmid);// 通信代码int fd OpenFifo(FIFO_NAME, WRITE);while(true){ssize_t sz read(0, shmaddr, SIZE); // 共享内存从键盘中读入数据(换行符也被写入)assert(sz 0);shmaddr[sz - 1] \0; //末尾添加\0表示终止WakeUp(fd); // 唤醒读端进程if(strcmp(shmaddr, quit) 0) break;}// char ch a;// int count 0;// for(; ch c; ch)// {// shmaddr[count] ch;// printf(write succsee# %s\n, shmaddr);// sleep(3);// }// snprintf(shmaddr, SIZE, quit);// 让共享内存脱离当前进程int n shmdt(shmaddr);if(n -1){perror(Client shmdt);exit(4);}printf(Client# 共享内存成功脱离进程shmid:%d\n, shmid);CloseFifo(fd);return 0;
}
四. 总结
System V共享内存实现进程间通信的底层原理是通信双方进程看到同一块内存位于物理内存上的共享内存块通过页表映射到通信双方的进程地址空间的共享区通信双方拿到共享区的虚拟地址通过页表映射访问到同一块物理内存。使用System V共享内存实现进程间通信的操作流程为通过ftok函数获取唯一的共享内存标识符key - 通过shmget函数获取共享内存 - 通过shmat函数让共享内存和进程绑定 - 【进行进程通信】- 通过shmdt函数让共享内存和进程脱离 - 通过shmctl删除共享内存。System V共享内存 进程间通信的特点为1不需要向操作系统内核中拷贝数据是所有进程间通信的方法中效率最高的。2没有访问控制。通过管道的辅助可以为 System V共享内存 进程间通信添加访问控制。