北太平庄网站建设,南京网站制作公司报价,wordpress分类目录绑定二级域名,wordpress alipay充值共享内存消息队列信号量 1.共享内存1.1共享内存的原理1.2共享内存的概念1.3接口的认识1.4实操comm.hppservice.cc #xff08;写#xff09;clint.cc #xff08;读#xff09; 1.5共享内存的总结1.6共享内存的内核结构 2.消息队列2.1原理2.2接口 3.信号量3.1信号量是什么3… 共享内存消息队列信号量 1.共享内存1.1共享内存的原理1.2共享内存的概念1.3接口的认识1.4实操comm.hppservice.cc 写clint.cc 读 1.5共享内存的总结1.6共享内存的内核结构 2.消息队列2.1原理2.2接口 3.信号量3.1信号量是什么3.2为什么要信号量3.3接口 4.IPC资源的组织方式 进程间通信方式目前我们已经学了匿名管道命名管道。让两个独立的进程通信前提是看到同一份资源。匿名管道适用于血缘关系的进程一个打开写端一个打开读端实现的。命名管道适用于完全独立的进程打开同一份文件实现的。
接下来我们看看剩下的实现进程间通信的方式。
1.共享内存
1.1共享内存的原理 可执行程序加载到内存OS会对进程进行管理。进程的内核结构是独立的加载到物理地址的地址也独立因此进程具有独立性互不影响。
那共享内存是如何实现两个独立的进程进行通信的呢 1.申请一块空间用户给OS发信号然后OS去申请 2.将创建好的内存经过页表映射到进程地址空间中的一段区域将这块区域的起始地址返回给用户用户通过访问这里的起始地址方式来进行对这块区域的访问 3.未来不想通信了 a.取消进程和内存的映射关系 b.释放内存 上面就是共享内存的原理。
这块申请的内存—共享内存 进程和内存建立映射关系过程—进程和共享内存挂接 取消进程和内存的映射关系—去关联 释放内存—释放共享内存
如何理解上面这个过程
在内存上申请空间然后把地址返回是不是像C/C空间的申请如malloc函数底层都是一样的先去物理内存申请空间然后经过页表映射到进程地址空间最后把这块空间的起始地址返回用户。虽然过程都是一样但是malloc申请的空间没有办法让另一个进程看见因为这块空间是在堆上申请的OS并没有专门为malloc这样的机制和其他进程建立映射关系策略。 a.进程间通信是专门设计的用来IPC b.共享内存是一种通信方式所有想通信的进程都可以用 c.OS中一定可能会同时存在很多的共享内存 1.2共享内存的概念
通过让不同的进程看到同一个内存的方式共享内存。
1.3接口的认识 1.创建共享内存。 size 要申请多大的内存空间
shmflg 常用参数。 看到这里这个参数是不是和open接口有点类似大写的宏。
shmflg是一个标志位。
IPC_CREAT如果想创建的共享内存不存在就创建如果存在就获取 IPC_EXCL无法单独使用。 IPC_CREAT | IPC_EXCL如果不存在就创建如果存在就出错返回。用户创建共享内存如果成功一定是一个新的共享内存。
创建共享内存非常容易那如果保证进程看到的是同一块共享内存呢 key用来保证。 key是什么不重要能进行唯一性标识最重要。 将路径名和项目标识符转换为key。
随便写个路径和项目id。经过算法转换成key。 两个进程传一样参数能保证是同一个key因此可以在系统中找到同一个内存。 返回值。成功是返回共享内存的标识符失败返回-1。
再来理解key_t key
OS中一定可能会同时存在很多的共享内存。 OS也要对共享内存进行管理—先描述在组织。
申请一块空间—共享内存物理内存块共享内存的相关属性
key是什么不重要能进行唯一标识最重要。 创建共享内存的时候key能保证共享内存在系统中的唯一性。两个进程如何看到同一份资源只要另一个进程也看到同一个key。
key在那 key在共享内存的相关属性这个结构体里。
创建共享内存把key传到shmget本质上是把key设置进创建好的共享内存的某个属性里另一个进程获取时查这么多的共享内存不是查共享内的物理内存块而是去遍历共享内存对于的相关属性去查找key。
key传到shmget设置到共享内存属性中用来表示共享内存在内核中的唯一性。 返回值返回的共享内存的标识符取名shmid。
shmid vs key的关系 shmid是为了用户去访问共享内存的。就像fd vs inode的关系。 2.共享内存和进程关联 shmid和哪一个共享内存关联 shmaddr把共享内存映射到地址空间的那一块区域 shmflg与读写权限有关默认设置为0 成功是返回的是对应进程地址空间的起始地址失败返回-1。 3.删除共享内存之前要先去关联 将共享内存从当前调用这个函数的进程地址空间进行卸装 shmaddr进程地址空间的首地址 4.删除共享内存 删除共享内存接口是shmctl本质上控制共享内存不过常用的是删除选项。
shmid控制哪一个共享内存 cmd做什么控制常用选项IPC_RMID buf如果不想获得共享内存的属性可以设置nullptr不然就传一个对象接收共享内存部分属性信息。 1.4实操 comm.hpp
将写端和读端用的代码封装起来。
#includeiostream
#includecerrno
#includecstring
#includesys/types.h
#includesys/ipc.h
#includesys/shm.h#define PATHNAME .
#define PROJ_JD 0x11#define MAX_SIZE 4096key_t GetKey()
{key_t kftok(PATHNAME,PROJ_JD);//获得唯一标识keyif(k 0){//cin cout cerr-stdin stdout stderr(默认打开的三个文件)-(fd)0,1,2-键盘显示器显示器cerrerrno:strerror(errno)endl;exit(1);}return k;
}//获得共享内存表示符shmid
int getShmHelper(int key,int shmflg)
{int shmidshemget(key,MAX_SIZE,shmflg);if(shmid 0){cerrerrno:strerror(errno)endl;exit(1);}return shmid;
}//写端创建共享内存
int CreateShm(key_t key)
{//这里运行时会报错。下面再看运行结果有解决方法return getShmHelper(key,IPC_CREAT|IPC_EXCL);
}//读端获取共享内存
int GetShm()
{return getShmHelper(key,IPC_CREAT);
}看运行结果写端和读端的key是一样的shmid也是一样的。 当我再次执行一样的操作发现不能再创建共享内存了。显示已经存在了。可是我已经退出进程了啊。OS不会帮我自动关闭吗。 共享内存的生命周期是随操作系统的不是随进程的 查看IPC资源
ipcs查看IPC资源 ipcs -m 查看共享内存 ipcs -q 查看队列 ipcs -s 查信号量 ipcrm -m shmid 删除共享内存 代码删除共享内存
//删除共享内存
void DelShm(int shmid)
{
//删除共享内存
void DelShm(int shmid)
{if(shmctl(shmid,IPC_RMID,nullptr) -1){cerrerrno:strerror(errno)endl;}}
}通信之前需要关联挂接将共享内存经页表映射到进程地址空间
void* attachShm(int shimid)
{void* memshmat(shimid,nullptr,0);//linux 64位机器指针大小位9if((long long)men -1L)//1L代表是长整型{cerrerrno:strerror(errno)endl;exit(1);}return men;
}删除共享内存之前需要去关联
void detachShm(const void* adder)
{if(shmdt(adder) -1){cerrerrno:strerror(errno)endl;exit(1);}
}完整代码如下
#includeiostream
#includecerrno
#includecstring
#includesys/types.h
#includesys/ipc.h
#includesys/shm.h
#includeunistd.husing namespace std;#define PATHNAME .
#define PROJ_JD 0x11#define MAX_SIZE 4096key_t GetKey()
{key_t kftok(PATHNAME,PROJ_JD);if(k 0){//cin cout cerr-stdin stdout stderr(默认打开的三个文件)-(fd)0,1,2-键盘显示器显示器cerrerrno:strerror(errno)endl;exit(1);}return k;
}//获得共享内存表示符shmid
int getShmHelper(int key,int flage)
{int shmidshmget(key,MAX_SIZE,flage);if(shmid 0){ cerrerrno:strerror(errno)endl;exit(1);}return shmid;
}//写端创建共享内存
int CreateShm(key_t key)
{//这里运行时会报错。下面再看运行结果有解决方法return getShmHelper(key,IPC_CREAT|IPC_EXCL);
}//读端获取共享内存
int GetShm(key_t key)
{return getShmHelper(key,IPC_CREAT);
}//关联
void* attachShm(int shimid)
{void* memshmat(shimid,nullptr,0);//linux 64位机器指针大小位9if((long long)mem -1L)//1L代表是长整型{cerrerrno:strerror(errno)endl;exit(1);}return mem;
}//去关联
void detachShm(const void* adder)
{if(shmdt(adder) -1){cerrerrno:strerror(errno)endl;exit(1);}
}//删除共享内存
void DelShm(int shmid)
{if(shmctl(shmid,IPC_RMID,nullptr) -1){cerrerrno:strerror(errno)endl;}}service.cc 写
#includecomm.hppint main()
{key_t keyGetKey();printf(key-%d\n,key);int shmidCreateShm(key);printf(shimid-%d\n,shmid);//关联char*start(char*)attachShm(shmid);//我想将这个地址认为是一字符串;printf(attach success, address start: %p\n, start);//写while(true){}//删除DelShm(shmid);return 0;
}共享内存没有read和write这样的接口。 写—直接把内容写到这块内存 读—直接打印这块内存
以往我们可能是这样的写法对于往内存中写有点麻烦了。 //写char buffer[1024];const char* messgehello clint,我是另一个进程,我正在和你通信;int idgetpid();int cnt0;while(true){//以往我们的做法snprintf(buffer,sizeof buffer,%s([%d]-[%d]),messge,cnt,id);memcpy(start,buffer,strlen(buffer)1);}看这个函数我们可以直接把内容写到内存。 完整代码如下
#includecomm.hppint main()
{key_t keyGetKey();printf(key-%d\n,key);int shmidCreateShm(key);printf(shimid-%d\n,shmid);//关联char* start(char*)attachShm(shmid);//我想将这个地址认为是一字符串;printf(attach success, address start: %p\n, start);//写char buffer[1024];const char* messgehello clint,我是另一个进程,我正在和你通信;int idgetpid();int cnt0;while(true){snprintf(start,MAX_SIZE,%s([%d]-[%d]),messge,cnt,id);//以往我们的做法// snprintf(buffer,sizeof buffer,%s([%d]-[%d]),messge,cnt,id));// memcpy(start,buffer,strlen(buffer)1);}//去关联detachShm(start);//删除DelShm(shmid);return 0;
}clint.cc 读
#includecomm.hppint main()
{key_t keyGetKey();printf(key-%d\n,key);int shmidGetShm(key);printf(shimid-%d\n,shmid);char* start(char*)attachShm(shmid);printf(attach success, address start: %p\n, start);while(true){printf(service say : %s\n, start);}return 0;
}运行时报了这个没有权限的错误。 这是因为再创建共享内存的时候没有给权限。 补上权限即可 看运行结果虽然两个进程进行了通信但是这个通信有点问题。 共享内存不像管道那样阻塞等待而是一直在读。
1.5共享内存的总结
共享内存的特点
1.共享内存的生命周期是随OS的不是随进程的。 2.所有进程间通信速度最快的能大大的减少数据的拷贝次数—优点。
同样的代码如果用管道来实现综合考虑管道和共享内存考虑键盘输入和显示器输出共享内存有几次数据拷贝管道呢 3.不给我进行同步和互斥的操作没有对数据做任何保护----缺点
1.6共享内存的内核结构 给这个接口传一个struct shmid_de 对象就看到共享内存一些信息。
#includecomm.hppint main()
{key_t keyGetKey();printf(key-%d\n,key);int shmidGetShm(key);printf(shimid-%d\n,shmid);char* start(char*)attachShm(shmid);printf(attach success, address start: %p\n, start);while(true){printf(service say : %s\n, start);struct shmid_ds ds;shmctl(shmid,IPC_STAT,ds);printf(获得属性: size :%d,link :%d\n,ds.shm_segsz,ds.shm_nattch);sleep(1);}return 0;
}我们确实获得了共享内核的一些属性。
我们的key在struct shmid_ds结构体的第一个变量里。
#includecomm.hppint main()
{key_t keyGetKey();printf(key-%d\n,key);int shmidGetShm(key);printf(shimid-%d\n,shmid);char* start(char*)attachShm(shmid);printf(attach success, address start: %p\n, start);while(true){printf(service say : %s\n, start);struct shmid_ds ds;shmctl(shmid,IPC_STAT,ds);printf(获得属性: size :%d,link :%d,key :%d\n,\ds.shm_segsz,ds.shm_nattch,ds.shm_perm.__key);sleep(1);}return 0;
}共享内存大小一般建议4KB4096的整数倍 系统分配共享内存是以4KB为单倍的------内存划分内存块的基本单位page 虽然申请大小是4097但是内核给你的实际是40962内核给你向上取整。 虽然内核给的是40962但是注意内核给你的和你能用的是两码事。
2.消息队列
在前面说过System V IPC—聚焦在本地通信目前已经陈旧了共享内存还是值得我们好好学习一番剩下的我们看一看原理和接口就可以了。
2.1原理 进程AB可以相互为读写段一端把数据放到队列里一端去拿。 那如何保证两个进程不会拿到自己的而去拿对方的呢。
这个队列内核数据结构中有一个type用来标识这个信息是谁发的不是自己的不拿。
2.2接口 创建一个消息队列 看到没这些参数和共享内存的参数非常相似。 成功是返回消息队列标识符失败返回-1。 向消息队列中放数据 msgp:发送的数据 msgsz数据大小 msgflg默认为0 接收数据 msgp接收数据放到这里 msgsz大小 msgtyp类型 删除队列 这些接口都和共享内核相似。
3.信号量
关于信号量这里补充一些概念也是为了后面的学习。
3.1信号量是什么 本质上是一个计数器通常用来表示公共资源中资源数量的多少问题。 信号量本质是一个计数器那可以直接设置一个全局变量用来充当计数器吗
不可以的就如在匿名管道设置一个全局变量因为写时拷贝父子进程看到的根本不是同一个全局变量。
进程通信之前要看到同一份资源然后才能通信。 公共资源被多个进程同时可以访问的资源。
以管道的方式通信的双方注意到写端没写读端一直在阻塞等待读端没读写端写完也在阻塞等待等等而共享内存的方式呢不管写端是否写完读端一直在读。假设写一段数据结果数据没写完就读。就造成了问题这些数据就在共享内存中没有被保护。
访问没有被保护的公共资源导致数据不一致的问题。
我们来捋一捋这个过程。
为什么要让不同的进程看到同一份资源呢 因为我想通信让进程之间实现协同但是进程具有独立性因此需要先让进程看到同一份资源。 提出了方法然后也引入了新的问题------数据不一致问题。
我们未来将被保护起来的公共资源临界资源 进程大部分的资源是独立的。
资源(内存文件网络)是要被使用的如何被进程使用呢 一定是该进程有对应的代码来访问这部分临界资源这个代码临界区其他代码叫非临界区。 那如何保护公共资源呢 互斥和同步 互斥当有两个进程想访问一个公共资源的时候我们只能让一个进程进行完整的访问。 同步在多线程哪里再说。
还有一组概念比较不好理解。 假如我想向缓冲区写一段数据要求我必须要把这段数据写完你才能读我没写完你就读不了。对于我来讲我在写的时候我要么不写要写就写完才对你有意义。 这种要么不做要做就做完两态的这种情况原子性。
假设张三要去银行给李四转账200 张三1000 李四1000 1000-200 1000200 但是网络出现问题转账失败那银行不能就不管了必须要把这200还给张三保持原样。 转账对于我们来说就只有两态要么不转要转就转完虽然可能有中间过程但是最终结果就是要么不转要转就转完。不会说正在转账中。 这就是我们所有的原子性。
上面说这么多主要是为了说明让多进程和多线程用来进行原子性的互斥和同步信号量是其中一种解决方案。
3.2为什么要信号量
举个例子
去电影票看电影我是不是只要坐到座位上这个位置才是属于我的 并不是买票票号座位号之后这个位置在那个时间段就是属于我的。
看电影买票的本质对放映厅中座位进行进行预定机制。
当我们想要某种资源的时候我们可以进行预定。 电影院就相当于共享资源。 每个进程想访问某些公共资源时先申请信号量申请成功就相当于预定了共享资源的某一部分资源才能允许进入这个小资源里进行访问申请失败就不允许这个基础进入共享资源进而达到保护共享资源以及其他基础的目的。 所有进程在访问公共资源之前都必须先申请sem信号量----必须申请sem信号量的前提是所有进程必须先看到同一个信号量----信号量本身就是公共资源---- 信号量是不是也要保护自己的安全呢(- - 操作)----信号量必须要保护这种操作的安全性- -操作是原子的 - -就是我们所说的PV操作
信号量本质是一把计数器这个计数器可以在多进程环境中可以被多进程先看到必须给我们匹配上两种操作P操作V操作让进程对我们的共享资源进行访问。
如果信号量初始值是1代表共享资源当作整体来使用一个进程申请成功了其他进程不能申请-------互斥。
提供互斥功能的信号量----二元信号量。
这里可能有这样一个问题进程申请成功了信号量是有资格去访问共享资源但具体是去访问那个资源子部分不知道可能存在多个进程访问同一个资源的问题也就是买票票号有了座位号还没有该怎么办
这部分其实是由我们在写代码时来确认不同进程去访问那个资源的。
3.3接口 获取信号量 nsems:想申请几个信号量 申请成功返回一个信号量集的标识符。失败返回-1。 删除信号量或者获取信号量属性 semnum可能你申请很多信号量这个是信号量对应的下标申请一个信号量下标为0申请10个信号量假设我想对第10个信号量操作下标就是9。
cmd删除或者获取信号量相关属性。 对信号量做PV操作 对指定信号量做对应的操作
struct sembuf结构有三个变量 sem_num对申请的多个信号量哪一个进行操作。 sem_op一般只有两种值一种是1代表V操作。一种是-1代表- -P操作。 sem_flg默认为0
nspos:有几个这样的结构体。
允许对多个信号量同时进行PV操作。
4.IPC资源的组织方式
我们注意到共享内存消息队列和信号量的接口都非常相似。都有struct …id_ds这样的结构体还有对应描述IPC属性资源的第一个字段都是一样的。 这三种共性可以看出来一个细节这三种都叫做System V标准的进程间通信。 所谓的标准就是大家用的方式接口设计数据结构的设置都必须遵守某种标准。
那OS如何对管理这些比如一会申请共享内存一会消息队列信号量的呢
OS并不是对它们本身进行管理而是先描述在组织对匹配的相关资源的内核结构进行管理。
因为当前三种方式的属性的第一个字段都是一样的。 在内核中操作系统可以维护一个指针数组。 结构体第一个成员的地址在数字上和结构体对象本身的地址数字是相等的 OS有对应的方式知道强转成什么结构体指针。
这就是OS对IPC资源的组织方式。