当前位置: 首页 > news >正文

seo 网站分析商业网站的相关内容

seo 网站分析,商业网站的相关内容,外网访问内网wordpress,做网站app需要懂些什么软件#x1f525; 本文专栏#xff1a;Linux Linux实践项目 #x1f338;作者主页#xff1a;努力努力再努力wz #x1f4aa; 今日博客励志语录#xff1a; 人生就像一场马拉松#xff0c;重要的不是起点#xff0c;而是坚持到终点的勇气 ★★★ 本文前置知识#xff1a; … 本文专栏Linux Linux实践项目 作者主页努力努力再努力wz 今日博客励志语录 人生就像一场马拉松重要的不是起点而是坚持到终点的勇气 ★★★ 本文前置知识 匿名管道 命名管道 前置知识大致回顾对此十分熟悉的读者可以跳过 那么我们知道进程之间具有通信的需求因为某项任务需要几个进程共同来完成那么这时候就需要进程之间协同分工合作那么进程之间就需要知道彼此之间的完成的进度以及完成的情况那么此时进程之间就需要通信来告知彼此而由于进程之间具有独立性那么进程无法直接访问对方的task_struct结构体以及页表来获取其数据那么操作系统为了满足进程之间通信的需求又保证进程的独立性那么采取的核心思想就是创建一份公共的内存区域然后让通信的进程双方能够看到这份公共的内存区域从而能够实现通信 那么对于父子进程来说由于子进程是通过拷贝父进程的task_struct结构体得到自己的一份task_struct结构体那么意味着子进程会拷贝父进程的文件描述表从而子进程会继承父进程打开的文件而操作系统让进程通信的核心思想就是创建一块公共的内存区域那么这里对于父子进程来说那么文件就可以作为这个公共的内存区域来保存进程之间通信的信息所以这里就要求父进程在创建子进程之前先自己创建一份文件这样再调用fork创建出子进程这样子进程就能继承到该文件那么双方就都持有该文件的文件描述符然后通过文件描述符向该文件进行写入以及读取而我们知道该文件只是用来保存进程之间通信的临时数据而不需要刷新到磁盘中长时间保存那么必定该文件的性质是一个内存级别的文件那么创建一个内存级别的文件就不能在调用open接口因为open接口是用来创建一个磁盘级文件其次就是双方通过该文件来进行进程之间通信的时候那么双方不能同时对该文件进行读写因为会造成偏移量错位以及文件内容混乱的问题所以该文件只能用来实现单向通信也就是智能一个进程向该文件写入然后另一个进程从该文件中进行读取那么由于该文件单向通信的特点并且进程双方是通过文件描述符来访问所以该文件其没有路径名以及文件名因此该文件被称作匿名管道文件那么我们要创建匿名管道文件就需要调用pipe接口那么pipe接口的返回值就是该匿名管道文件读写端对应的file结构体的文件描述符 而对于非父子进程来说此时他们无法再看到彼此的文件描述表那么意味着对于非父子进程来说那么这里只能采取建立一个普通的文件该普通的文件作为公共区域那么一个进程向该文件中写入另一个进程从该文件读取根据父子进程通信的原理我们知道该普通文件肯定不是一般的普通文件它一定也得是内存级别文件其次也只能实现单向通信而对于匿名管道来说通信进程双方看到该匿名管道是通过文件描述符来访问到这块资源而对于命名管道则是通过通过路径加文件名的方式来访问命名管道那么访问的方式就是通信进程的双方各自通过open接口以只读和只写的权限分别打开该命名管道文件获取其文件描述符然后通信进程双方通过文件描述符然后调用write以及read接口来写入以及读取数据而创建一个命名管道就需要我们调用mkfifo接口 那么这就是对前置知识的一个大致回顾如果读者对于上面讲的内容感到陌生或者想要知道其中的更多细节那么可以看我之前的博客 共享内存 那么此前我们已经学习了两种通信方式分别是匿名管道以及命名管道来实现进程的通信那么这期博客我便会介绍第三种通信方式便是共享内存那么我会从三个维度来解析共享内存分别是什么是共享内存以及共享内存的底层相关的原理和结合前面两个维度的理论知识如何利用共享内存来实现进程的通信也就是文章的末尾我们会写一个用共享内存实现通信的小项目 什么是共享内存以及共享内存的底层原理 那么我们知道进程间通信的核心思想就是通过开辟一块公共的区域然后让进程双方能够看到这份资源从而实现通信所以这里的共享内存其实本质就是操作系统为其通信进程双方分配的一个物理内存那么这份物理内存就是共享内存所以共享内存的概念其实很简单与直接 根据进程间通信的核心思想那么这里的公共的区域已经有了那么下一步操作系统要解决的问题便是创建好了共享内存如何让进程双方能够看到这份共享内存资源 那么对于进程来说按照进程的视角那么它手头上只持有虚拟地址那么进程访问各种数据都只能通过虚拟地址去访问然后系统再借助页表将虚拟地址转换为物理地址从而访问到相关数据所以要让通信进程双方看到共享内存那么此时操作系统的任务就是提供给通信进程双方各自一个指向共享内存的虚拟地址然后通信进程双方就可以通过该虚拟地址来向共享内存中写入以及读取数据了那么这个时候操作系统要进行的工作就是创建通信的进程的同时设置好该进程对应的mm_struct结构体中的共享内存段并且在其对应的页表添加其共享内存的虚拟地址到物理地址的映射的条目 那么知道了共享内存来实现进程双方通信的一个大致的原理那么现在的问题就是如何请求让操作系统来为该通信进程双方创建共享内存 那么这里就要让操作系统为该其创建一份共享内存就需要我们在代码层面上调用shmget接口那么该接口的作用就是让内核为我们创建一份共享内存但是在介绍这个接口如何使用之前我们还得补充一些相关的理论基础有了这些理论基础我们才能够认识到shmget这些参数的意义是什么 shmget头文件sys/shm.h 和sys/ipc.h函数声明int shmget(ket_t key,size_t size,int shmflg);返回值调用成功返回shmid调用失败则返回-1,并设置errno key/shmid 那么这里的shmget的一个参数就是一个key那么读者对于key的疑问无非就是这两个方面这个key是什么?key的作用是什么 那么接下来的讲解会以这两个问题为核心来为你解析这个key究竟是何方神圣 首先我们一定要清楚的是系统中存在不只有一个共享内存因为系统中需要通信的进程不只有一对所以此时系统中的共享内存就不只有一个那么系统中存在这么多的共享内存那么每一个共享内存都会涉及到创建以及读取和写入以及最后的销毁那么操作系统肯定就要管理存在的所有的共享内存那么管理的方式就是我们熟悉的先描述再组织的方式来管理这些共享内存也就是为每一个共享内存创建一个struct shm_kernel结构体那么该结构体就记录了该共享内存的相关的属性比如共享内存的大小以及共享内存的权限以及挂载时间等等那么每一个共享内存都有对应的结构体那么内核会持有这些结构体并且会采取特定的数据结构将这些结构体组织起来比如链表或者哈希表那么系统中每一个共享内存肯定是不相同的那么为了区分这些不同的共享内存那么系统就得给这些共享内存分配一个标识符通过标识符来区分这些共享内存 而进程要用共享内存实现通信那么进程首先得请求操作系统为我们该进程创建一份共享内存然后获取到指向该共享内存的虚拟地址而进程间的通信涉及的进程的数量至少为两个那么以两个进程为例子假设进程A和进程B要进行通信那么此时需要为这对进程提供一个共享内存那么就需要A进程或者B进程告诉操作系统来为其创建一份共享内存 那么这里你可以看到我将或者这两个字加粗那么就是为了告诉读者那么这里我们只需要一个进程来告诉内核创建一份共享内存不需要两个进程都向操作系统发出创建共享内存的请求所以只需要一个进程请求内核创建一份共享内存然后另一个进程直接访问创建好的共享内存即可 那么知道了这点之后那么假设这里创建共享内存的任务交给了A进程那么此时A进程请求内核创建好了一份共享内存那么对于B进程来说它如何知道获取到A进程创建好的共享内存呢由于系统内存在那么多的共享内存那么B进程怎么知道哪一个共享内存是A进程创建的那么这个时候就需要key值那么这个key值就是共享内存的标识符 key就好比酒店房间的一个门牌号那么A和B进程只需要各自持有该房间的门牌号那么就能够找到该房间但是这里要注意的就是这里的key值不是由内核自己生成的而是由用户自己来生成一个key值 那么有些读者可能就会感到疑问那么标识符这个概念对于大部分的读者来说都不会感到陌生早在学习进程的时候我们就已经接触到标识符这个概念那么对内核为了管理进程那么会为每一个进程分配一个标识符那么就是进程的PID而在文件系统中任何类型的文件都有自己对应的inode结构体那么内核为了管理inode结构体那么也为每一个文件对应的inode结构体分配了标识符也就是inode编号所以读者可能会感到疑惑那么在这里共享内存也会存在标识符但是这里的标识符为什么是用户来提供而不是内核来提供呢是内核无法做到为每一个共享内存分配标识符还是说因为其他什么原因 那么这个疑问是理解这个key的关键首先我要明确的就是内核肯定能够做到为每一个共享内存提供标识符这个工作对于内核来说并不难完成并且事实上内核也的确为每一个共享内存提供了标识符那么这个标识符就是shmid 在引入了shmid之后可能有的读者又会产生新的疑问按照你这么说的话那么实际上内核为每一个创建好的共享内存分配好了标识符但是这里还需要用户自己在创建一个标识符那么理论上来说岂不是一个共享内存会存在两个所谓的标识符一个是key另一个是shmid而我们访问共享内存只需要一个标识符就够了那么这里共享内存拥有两个标识符岂不是会存在冗余的问题并且为什么不直接使用内核的标识符来访问呢 那么接下来我就来依次解答读者的这些疑问那么首先关于为什么我们进程双方为什么不直接通过shmid来访问内存 那么我们知道内核在创建共享内存的同时会为该共享内存创建对应的struct shm_kernel结构体那么其中就会涉及到为其分配一个唯一的shmid而假设请求内核创建共享内存的任务是交给A进程来完成而B进程只需要访问A进程请求操作系统创建好的共享内存而对于B进程来说它首先得知道哪个共享内存是提供给我们两个A个B两个进程使用的意味着B进程就得通过共享内存的标识符得知因为每一个共享内存对应着一个唯一且不重复的标识符对于A进程来说由于它来完成共享内存的创建而shmget接口是用来创建共享内存并且返回值就是共享内存的shmid那么此时A进程能够知道并且获取进程的shmid标识符但是它能否将这个shmget的返回值也就是shmid告诉该B进程吗毫无疑问肯定是不可能的因为进程之间就有独立性那么如果直接使用shmid来访问共享内存那么必然只能对于创建共享内存的那一方进程可以看到而另一个进程无法看到那么无法看到就会让该进程不知道哪一个共享内存是用来给我们A和B进程通信的所以这就是为什么要有key存在 那么A和B进程双方事先会持有一个相同的key那么A进程是创建共享内存的一方那么它会将将key传递给shmget接口那么shmget接口获取到key会将key作为共享内存中其中一个字段填入最终给A进程返回一个shmid而对于B进程来说那么它拿着同一个key值然后也调用shmget接口而此时对于B进程来说它的shmget的行为则不是创建共享内存而是内核会拿着它传递进来的key到组织共享内存所有结构体的数据结构中依次遍历找到匹配该key的共享内存然后返回其shmid 而至于为什么A和B进程都调用shmget函数但是shmget函数有着不同的行为对于A来说是创建对于B来说则可以理解为是“查询”那么这就和shmget的第三个参数有关那么第三个参数会接受一个宏该宏决定了shmget行为所以A和B进程调用shmget接口传递的宏肯定是不一样的那么我会在下文会讲解shmget接口的第三个参数这里就先埋一个伏笔 所以综上所述这里的key虽然也是和shmid一样是作为标识符但是是给用户态提供使用的是用户态的两个进程在被创建之前的事先约定而内核操作则是通过shmid那么key的值没有任何的意义所以理论上我们用户可以自己来生成任意一个无符号的整形作为key但是要注意的就是由于这里key是用户自己生成自己决定的那么有可能会出现这样的场景那么就是用户自己生成的key和已经创建好的共享内存的key的值一样或者说冲突所以这里系统为我们提供了ftok函数那么该函数的返回值就是key值那么我们可以不调用该函数自己随便生成一个key值但是造成冲突的后果就得自己承担所以这里更推荐调用ftok函数生成一个key值 这里推荐使用ftok函数来生成的key不是因为ftok函数生成的key完全不会与存在的共享内存的key造成冲突而是因为其冲突的概率相比于我们自己随手生成一个的key是很低的 ftok头文件sys/types.h 和sys/ipc.h函数声明key_t ftok(const char* pathname,int proj_id);返回值调用成功返回key值调用失败则返回-1 那么这里ftok会接收两个参数首先是一个文件的路径名以及文件名那么这里注意的就是这里的文件的路径名以及文件名一定是系统中存在的文件因为它会解析这个路径以及文件名从而获取该文件的inode编号然后得到对应的inode结构体从中再获取其设备编号那么这里的proj_id的作用就是用来降低冲突的概率因为到时候ftok函数获取到文件的inode编号以及设备号和proj_id然后会进行位运算得到一个32位的无符号整形那么其位运算就是 ftok 通过文件系统元数据生成 key 的算法如下 key (st_dev 0xFF) 24 | (st_ino 0xFFFF) 8 | (proj_id 0xFF)• st_dev文件所在设备的设备号取低8位 • st_ino文件的inode编号取低16位 • proj_id用户指定的项目ID取低8位 共享内存的大小 那么shmget函数的第二个参数便是指定的就是共享内存的大小那么这里至于内核在申请分配物理内存的单位是以页为单位也就是以4KB为单位来分配物理内存而这里shmget的第二个参数是以字节为单位那么这里我建议我们开辟的共享内存是以4096的整数倍来开辟因为假设你申请一个4097个字节那么此时内核实际上为你分配的物理内存是2*4096也就是8kb的空间虽然人家内核给你分配了8kb的空间但是它只允许你使用其中的4097个字节也就是剩下的空间就全部浪费了所以这就是为什么建议申请的空间大小是4096的整数倍那么这就是shmget的第二个参数 shmget的宏 那么shmget的第三个参数便是宏来指定其行为那么上文我们就埋了一个伏笔就是两个进程都调用了shmget但是却有着不同的行为那么就和这里的宏有关 IPC_CREAT (01000)如果共享内存不存在则创建IPC_EXCL (02000)不能单独使用与IPC_CREAT一起使用若共享内存已存在则失败SHM_HUGETLB (04000)使用大页内存Linux特有 那么这里我们着重要掌握的便是IPC_CREAT以及IPC_EXEC这两个宏 IPC_CREAT: 那么传递IPC_CREAT这个宏给shmget接口其底层涉及到工作就是内核首先会持有我们传递的key值然后去遍历组织所有共享内存的结构体的数据结构如果遍历完了所有共享内存对应的结构体并且发现没有匹配的key值那么这里就会创建一个新的共享内存并且同时创建对应的结构体然后对其结构体的属性进行初始化其中就包括填入key值并将其放入组织共享内存结构体的数据结构中那么最后创建完成后会返回该共享内存的shmid而如果说发现有匹配的key值的共享内存那么就直接返回该共享内存的shmid IPC_EXCL: 而IPC_EXCL则是和IPC_CREAT一起使用那么传递IPC_CREAT| IPC_EXCL这个宏给shmget接口其底层涉及到工作,j就是如果内核发现了有匹配的key值的共享内存那么这里就不会返回该共享内存的shmid而是返回-1并设置errno没有的话就创建新的共享内存并返回器shmid所以这个选项就是保证了创建的共享内存是最新的共享内存 而这里的宏本质上就是一个特定值的32位的二进制序列那么他们的每一个比特位代表着特定含义的标记位而该标记位则是分布在二进制序列的高24位而低8位则是表示该共享内存的权限所以在上文所举的例子中对于A进程来说它是创建共享内存的一方那么它传递的宏就应该是IPC_CREAT|IPC_EXCL|0666而对于B进程来说他是访问的一方那么它传递的就是IPC_CREAT|0666 那么shmget会根据其宏进行相应的行为并且还会核对其权限是否一致不一致则返回-1调用失败 shmat 那么此时上面所讲的所有内容都是关于创建共享内存的一些理论知识那么我们现在已经知道如何创建共享内存那么下一步就是如何让通信的进程双方看到该共享内存那么从上文的共享内存实现通信的大致原理我们知道创建完共享内存的下一个环节就是让进程双方持有指向该共享内存的虚拟地址那么这个时候就需要请求操作系统来设置通信进程的双方的虚拟地址空间的共享内存段以及在页表中添加共享内存的虚拟地址到物理地址的映射条目所以此时就需要我们在代码层面上调用shmat系统调用接口那么该系统调用接口的背后所做的工作就是刚才所说的内容而shmat所做的工作也叫做挂载 shmat头文件sys/shm.h 和sys/types.h函数声明void* shmat(int shmid,void *shmadder,int shmflg);返回值调用成功返回指向共享内存起始位置的虚拟地址调用失败则返回(void*)-1,并设置errno 那么这里shmat接口会接收之前我们调用shmget接口获取的共享内存的shmid然后内核会根据该shmid遍历共享内存对应的结构体然后找到匹配的共享内存接着在将共享内存挂载到通信的进程双方那么这里第二个参数就是我们可以指定内核挂载到共享内存段的哪个具体区域但是这第二个参数最好设置为NULL,那么设置为NULL意味着会让内核来在共享内存段中选择并且分配一个合适的虚拟地址那么该虚拟地址就会作为返回值 而shmat的第三个参数则是会接收一个宏那么这个宏就是用来指定该进程对于该共享内存的一个读写权限 SHM_RDONLY (只读模式)以只读方式附加共享内存SHM_RND (地址对齐)当指定了非NULL的shmaddr时自动将地址向下对齐到SHMLBA边界SHM_REMAP (Linux特有重新映射) 替换指定地址的现有映射需要与具体的shmaddr配合使用 那么这里对于我们来说那么我们不用传递任何宏就进去就传递一个NULL或者0那么我们该进程就能够正常的写入以及读取该共享内存的内容那么这三个宏的使用场景在目前现阶段的学习来说我们暂时还使用不到。 那么这就是shmat接口那么认识了shmat接口之后那么我们就可以来利用共享内存来实现正常的进程之间的通信了那么首先第一个环节就是先让各自通信的进程双方持有key然后一个进程通过key来调用shmget接口来创建共享内存并且获得其shmid而另一个进程也是同样通过key值来调用shmge接口来获取已经创建好的共享内存的shmid,那么下一个环节就是挂载那么此时就需要请求系统设置通信的进程双方的虚拟地址空间的共享内存段并且添加相应的关于该共享内存的虚拟地址到物理地址的映射的条目并且返回给进程双方该共享内存的起始位置的虚拟地址那么此时进程双方就可以持有该虚拟地址去访问共享内存了 shmdet 那么进程通信完之后那此时就要清理相关的资源其中就包括打开的共享内存那么我们要注意的就是共享内存对应的shm_kernel结构体中会有一个属性那么该属性便是引用计数记录了有多少个进程指向它或者说有多少个进程的页表中有关于该共享内存的虚拟地址到物理地址的映射条目那么此时shmdet接口的作用就是删除该进程对应的页表中共享内存的映射条目或者将该页表的条目设置为无效从而解除该进程与共享内存之间的绑定让该进程无法再访问到共享内存的资源并且还会减少该共享内存的引用计数 shmdet头文件sys/shm.h 和sys/ipc.h函数声明int shmdet(const void *shmadder);返回值成功返回0,失败返回-1并设置errno 那么可能会有的读者会感到疑惑的就是这里shmdet接口只接收一个虚拟地址而该虚拟地址是共享内存的起始位置的虚拟地址那么内核可以通过该虚拟地址借助页表来访问到共享内存而引用计数这个属性是存储在共享内存对应的结构体中那么意味着这里shmdet能够通过虚拟地址来访问到共享内存对应的物理结构体而共享内存中存储的内容去啊不是通信的消息那么这里内核是如何通过该虚拟地址访问到共享内存对应的结构体的呢 那么我们知道进程对应的task_struct结构体中会有一个字段mm_struct结构体其中会维护一个vma虚拟内存区域的数据结构那么该数据结构一般是采取链表来实现其中该链表的每一个节点是一个结构体用来描述以及记录该虚拟内存区域的相关属性其中就包括该虚拟内存区域的虚拟地址的起始位置以及虚拟地址的结束位置以及相关的读写权限以及其文件的大小和文件的类型 struct mm_struct {struct vm_area_struct *mmap; // VMA 链表的头节点单链表struct rb_root mm_rb; // VMA 红黑树的根节点用于快速查找// ...其他字段如页表、内存计数器等 };struct vm_area_struct {// 内存范围unsigned long vm_start;unsigned long vm_end;// 权限与标志unsigned long vm_flags;// 文件与偏移struct file *vm_file;unsigned long vm_pgoff;// 操作函数const struct vm_operations_struct *vm_ops;// 链表与树结构struct vm_area_struct *vm_next;struct rb_node vm_rb;// 其他元数据struct mm_struct *vm_mm;// ... }; 其中在vma的视角下那么它将每一个虚拟内存区域比如栈或者堆以文件的形式来看待那么其中这里的vm_file字段会指向该虚拟内存区域创建的一个file结构体其中就会包含该共享内存对应的struct shm_kernal结构体所以这里shmdet接口会获取到虚拟地址然后会查询mm_struct结构体中记录的vma链表根据该虚拟地址确定落在哪一个vma结构体中那么该vma结构体就是共享内存段区域所对应的vma结构体然后通过vm_file来间接获取到共享内存的shmid最后再拿着shmid从保存共享内存对应的数据结构中找到对应匹配的共享内存对应的结构体然后让其引用计数减一 shmctl 而shmdet只是解除了进程与共享内存之间的挂载那么shmdet的作用就好比指向一个动态数组的首元素的指针那么我们只是将该指针置空让我们无法在之后的代码中通过该指针来访问该动态数组但是该动态数组所对应的空间并没有释放而对于共享内存来说那么内核要真正释放共享内存的资源得满足两个前提条件那么就是该共享内存对应的引用计数为0并且该共享内存还得被标记为已删除因为内核没有规定该共享内存只能给创建它的进程双方通信用那么一旦该进程双方结束通信了那么可以让该进程双方解除与该共享内存的挂载然后让其他进程与该共享内存挂载从而通过再次利用该共享内存来进行通信所以这里就是为什么要设计一个删除标志 所以说这里的shmctl接口的作用就是用来控制共享内存那么我们可以通过调用该接口将共享内存标记为可删除那么一旦该共享内存对应的引用计数为0那么此时内核就会释放该共享内存的资源 shmctl头文件sys/shm.h 和sys/ipc.h函数声明int shmctl(int shmid,int cmd,struct shmid_ds* buffer);返回值调用成功返回0调用失败则返回-1,并设置errno 那么这里对于shmctl来说其第一个参数是shmid,那么到时内核会持有该参数去寻找对应的共享内存的结构体而shmctl的第二个参数则是控制shmctl接口的行为那么这里还是通过宏以及位运算的方式来指定shmctl接口的行为 IPC_STAT获取共享内存段的状态IPC_RMID删除共享内存段IPC_SET设置共享内存段的状态 那么这里IPC_SET这个宏我们目前还应用不到那么这里我们如果要将共享内存标记为删除那么就传入IPC_RMID即可 而如果我们要获取共享内存的状态那么我们可以传入IPC_STAT这个宏此时shmctl的第三个参数就有意义那么它会接收一个指向struct shm_ds的结构体那么该结构体的定义是存放在sys/shm.h头文件中那么这里内核会通过shmid然后访问到该共享内存对应的结构体根据其结构体来初始化struct shm_ds struct shmid_ds {struct ipc_perm shm_perm; // 共享内存段的权限信息size_t shm_segsz; // 共享内存段的大小字节time_t shm_atime; // 最后一次附加的时间time_t shm_dtime; // 最后一次断开的时间time_t shm_ctime; // 最后一次修改的时间pid_t shm_cpid; // 创建共享内存段的进程 IDpid_t shm_lpid; // 最后一次操作的进程 IDshmatt_t shm_nattch; // 当前附加到共享内存段的进程数引用计数// ... 其他字段可能因系统而异 }; /* 定义在 sys/ipc.h 中 */ struct ipc_perm {key_t __key; /* 用于标识 IPC 对象的键值 */uid_t uid; /* 共享内存段的所有者用户 ID */gid_t gid; /* 共享内存段的所有者组 ID */uid_t cuid; /* 创建该 IPC 对象的用户 ID */gid_t cgid; /* 创建该 IPC 对象的组 ID */unsigned short mode; /* 权限位类似于文件权限 */unsigned short __seq; /* 序列号用于防止键值冲突 */ };那么我们就可以通过访问该结构体中的相关成员变量来获取共享内存的相关属性信息 利用共享内存来实现进程间的通信 那么在上文我介绍了共享内存相关的理论基础以及关于共享内存相关的系统调用接口那么这里我们就会结合前面所学的知识以及系统调用接口来实现两个进程间通信的一个小项目那么这里介绍这个项目之前我们还是来梳理一下大致的框架然后再具体落实具体各个模块的代码怎么去写 大体框架 那么这里既然要实现进程间的通信那么我首先就得准备两个陌生进程分别是processA以及processB那么processA进程的任务就是就是负责创建共享内存然后将创建好的共享内存挂载然后processA作为共享内存的写入方向共享内存写入数据最后解除挂载然后清理共享内存资源而processB的任务则是访问processA创建好的共享内存然将该共享内存挂载到其虚拟地址空间然后processB作为共享内存的读取法读取数据最后解除挂载 comm.hpp 那么这里我们知道到时候A和B进程会接收到一个共同的key然后一个进程用这个key来创建共享内存而另一个进程则是用该key来获取该共享内存的shmid所以到时候这两个进程对应的源文件会各自引用该comm.hpp头文件那么comm.hpp中就会定义一个全局变量的key然后其中会定义一个Creatkey函数那么该函数内部就会调用ftok接口来生成一个key值并且返回而comm.hpp中还会定义CreaShm函数和Getshm函数那么从这两个函数名我们就知道它们各自的作用那么CreatShm函数就是提供给A进程使用的它的作用是创建共享内存并且返回其shmid而GetShm则是提供给process B使用那么它的作用就是获取process A打开的共享内存并且返回其shmid,而这里只是梳理大致框架那么具体的实现会在后文给出 processA.cpp 1.创建共享内存 那么这里对于process A来说那么它的第一个环节就是创建共享内存也就是调用CreatShm函数来获取shmid 2.将共享内存挂载到虚拟地址空间 那么接下来获取到共享内存的shmid之后那么下一步便是调用shmat接口来将该共享内存给挂载到processA进程的虚拟地址空间然后获取其共享内存的起始位置的虚拟地址 3.向共享内存中写入数据 那么这个环节就是根据之前获取到的共享内存的虚拟地址然后通过该虚拟地址向共享内存中写入数据其中写入数据会回封装到一个死循环的逻辑当中 4.解除挂载 那么这个环节就是解除共享内存与process A的关联其中涉及调用shmdet 5.清理共享内存资源 那么这里清理共享内存资源会调用shmctl接口因为shmdet只是减少引用计数以及删除该进程关于该共享内存的映射条目 processB.cpp 1.获取process A进程创建的共享内存 那么这里就会通过调用GetShm来获取process A进程创建的共享内存的shmid 2.将共享内存挂载 那么这个环节和上文的process A所做的内容是一样的就是调用shmat接口然后获取该共享内存的起始位置的虚拟地址 3.读取process A向共享内存写入的数据 那么这里我们会同样会根据上一个环节获取到的虚拟地址而通过该虚拟地址读取共享内存的内容 4.解除挂载 那么这里对于process A进程来说那么由于process A进程来完成的共享内存的删除所以这里对于B进程来说那么这里它只需解除与共享内存的挂载即可 各个模块的具体实现 comm.hpp 那么这里的comm.hpp的内容就包括process A进程以及process B进程会持有的key以及Creatkey函数该函数内部会调用ftok函数来获取到要创建的共享内存的key值而ftok函数会接收一个已存在的路径以及文件名和project_id那么这里就得保证传递给ftok函数的路径名以及文件名一致那么这里我们将文件的路径以及文件名定义为全局的string类型的变量同时将project_id也定义为了全局变量而CreatShm函数则是创建共享内存那么这里内部实现就会涉及到调用shmget接口那么GetShm函数则是获取到process A创建的共享内存的shmid那么这里内部也要调用shmget只不过传递给shmget接口的宏不一样 而这里我进行一个巧妙的处理那么这里我直接函数的复用那么直接在GetShm函数内部直接复用定义好的CreatShm函数那么这里就得利用缺省参数那么这里的默认缺省参数就是IPC_CREAT|IPC_EXCL|066,那么这里调用GetShm函数中就会显示传递一个IPC_CREAT的宏那么此时GetShm函数就会返回一个与相同key值的共享内存的shmid也就是process A创建的共享内存的shmid const std::string pathname/home/WangZhe; int key; log a; void CreatKey() {keyftok(pathname.c_str(),ProjectId);if(key0){a.logmessage(Fatal,ftoke调用失败);exit(EXIT_FAILURE);} }size_t CreatShm(int flagIPC_CREAT|IPC_EXCL|0666) {CreatKey();int shmidshmget(key,SHM_SIZE,flag);if(shmid0){a.logmessage(Fatal,shemget fail:%s,strerror(errno));exit(EXIT_FAILURE);}return shmid; } size_t GetShm() {int shmidCreatShm(IPC_CREAT|0666);return shmid; }那么这里在函数内部还进行了相应的日志打印逻辑那么如果对日志不熟悉的读者那么建议看我之前的一期博客 processA.cpp 1.创建共享内存 那么这里对于processA.cpp来说第一个环节就是调用CreatShm函数来创建大小为4096字节的共享内存并且获取返回值那么这里我们还要对返回值进行检查如果shmget接口调用失败那么返回值是-1那么这个错误是致命的那么程序就无法再继续往下正常运行然后进行相应的日志打印并且退出 int shmidCreatShm();a.logmessage(debug,processA creat Shm successfully:%d,shmid);int nmkfifo(FIFO_FILE,0666);if(n0){a.logmessage(Fatal,creat fifo fail:%s,strerror(errno));exit(EXIT_FAILURE);}2.将共享内存挂载到虚拟地址空间 那么该环节会利用上一个步骤创建的共享内存的shmid那么这里会调用shmat接口将共享内存挂载到process A的地址空间并且此时会返回一个void* 的指针那么该指针就是指向共享内存起始位置的虚拟地址那么这里接下来process A进程向共享内存中写入数据就会利用该虚拟地址那么这里我们使用该虚拟地址可以联系我们通过调用malloc函数在堆上申请了一个连续的空间然后得到该空间的首元素的地址然后我通过该首元素地址来访问该空间并且写入的过程 那么这里由于之后我们要写入的消息是字符串那么这里我们就可以将共享内存视作一个字符数组那么这里我们就要将void*的指针强制转化为char *类型 而如果shmat调用失败那么此时会返回void*-1的指针那么这里注意的就是这里的-1是指针类型也就是说这里的-1不能按照所谓的数值为-1来理解而是得按照一个值为-1的二进制序列那么这里我们比较返回值与(void *)-1判断shmat是否调用成功 char* Shmadder(char*)shmat(shmid,NULL,NULL);if(Shmadder(void*)-1){a.logmessage(Fatal,processA attach Fail:%s,strerror(errno));exit(EXIT_FAILURE);}a.logmessage(debug,processA attch successfully:0x%x,Shmadder);3.向共享内存中写入数据 那么这里就是根据之前获取到的共享内存的虚拟地址然后通过该虚拟地址向共享内存中写入数据而我们将写入的操作封装到一个死循环中那么这里我们就得注意一个同步机制的问题 同步 那么这里由于A进程和B进程都是一个死循环的读取以及打印的逻辑那么这里就会导致一个问题那么我们知道A进程是写入方进程那么在A进程在写入的过程中那么在同一个时刻下的B进程会一直从共享内存中读取数据那么就会出现这样的场景那么假设此时A进程向共享内存写入了一条消息那么同一个时刻下的B进程读取到了这条消息那么接着A进程便会等待获取用户的键盘输入的下一条消息而我们知道此时对于共享内存来说它里面存储的数据还是之前上一个时刻的A进程写入的那么数据没有被覆盖而与此同时对于B进程来说它根本不管A进程此时是否正在写入下一条消息那么它只是无脑的从共享内存中不停的读取那么此时它在当前时刻会获取到的消息则是A进程在上一个时刻写的消息而此时A进程还在等待用户的键盘输入没有向共享内存中写入那么此时共享内存中的数据还未被覆盖那么此时B进程的视角下那么B进程在当前时刻读取的消息就会被视作是A进程在当前时刻写入的消息但是事实上A还没有往共享内存中写入所以这个场景就是典型的一个读写不同步带来的问题 其次如果说此时B进程正在读取拷贝共享内存中的数据但是此时在同一时刻的A进程正在向共享内存中写入数据那么会导致数据被覆盖那么B进程最终读取的消息就是混乱的这也是读写不同步带来的问题 所以我希望的就是当A还没写或者说正在往共享内存中写入一条消息的时候那么此时B进程就站住不要动也就是不要向共享内存中读取数据那么一旦A进程消息写完了然后你B进程在动开始从共享内存或者读取数据那么这样就是读写同步那么这里实现读写同步可以通过锁来实现但是对于初学真来说可能当前没有学过或者接触过锁那么这里我们就采取的就是命名管道来实现同步机制 那么这里可能有的读者会有疑惑这里我知道此时A和B进程采取共享内存通信会有读写不同步的问题但是这里你采取的措施是通过命名管道来实现读写同步而我们知道命名管道的作用就是可以实现非父子进程的通信那么你干脆就直接用命名管道通信就结束了那么还搞一个共享内存岂不是多次一举 那么对于这个疑问那么首先我想说的就是命名管道确实可以传递消息但是对于共享内存来说我们是直接向物理内存中写入以及读取数据虽然A和B进程双方持有的是虚拟地址但是我们只需要经历一次虚拟地址到物理地址的映射转换便能直接访问到物理内存而这里通过命名管道写入消息那么就会涉及到调用系统调用接口比如write以及read接口而系统接口的调用是有成本有代价的那么这里你比如调用write接口向共享内存中写入数据那么其中涉及到的工作就是会首先找到该文件描述符对应的file结构体然后还要定位其物理内存最后再拷贝写入那么这个时间代价明显比共享内存更大所以说这里采取共享内存是更优秀的 所以这里首先A进程需要先调用mkfifo接口来创建一个命名管道然后再调用open接口打开该命名管道获取到该命名管道文件的文件描述符那么命名管道的内容就是一个字符那么这个字符的内容代表的就是当前是否继续读取以及是否退出那么这里字符x表示退出如果进程B从管道读取到了字符x那么代表着此时进程A结束通信那么就直接退出而如果读取到的是字符a那么代表这此时进程A向共享内存中写入了一条有效消息那么需要B进程去读取 那么接下来就有一个问题那么这里我们是A进程是先向管道文件中写入信息还是先向共享内存中写入信息那么可能有的读者会有这样的感觉那么就是这里A进程先向管道文件中写入信息先告诉b进程我现在是要给你发送一条消息还是说我要结束通信了那么发送完信息之后此时我A进程在向共享内存中写入消息然后让B进程去读 那么这里就得注意一个问题那么如果采取的是这种方式对于B进程来说那么它毫无疑问肯定是得先读取管道文件的信息确定A进程的意图是要我读取还是说结束通信然后下一步再来读取共享内存那么如果此时A没有向管道文件中写入信息那么此时B进程作为读取端由于调用了read接口读取管道文件此时B进程会陷入阻塞如果此时A进程先向管道文件中直接写入了信息那么在同一时刻下B进程读取到管道文件的信息那么它从立即阻塞状态切换为运行那么它就会立即执行后面的读取共享内存的代码而在同一个时刻下A进程此时还在等待用户的键盘输入的消息还没有往共享内存中写入而此时你B进程就已经开始读了那么读的消息就是之前A进程写入的消息那么还是会导致读写不同步的问题 所以这里就得梳理清楚这个关键的细节那么就是这里A进程得先向共享内存中写入消息然后再写向管道写入信息这里对于B进程来说它会一直陷入阻塞直到A进程向管道写入了消息然后开始读取这样就可以做到读写同步 while(true){std::coutPlease Enter the messasge:std::endl;fgets(Shmadder,1024,stdin);size_t lenstrlen(Shmadder);if(len0Shmadder[len-1]\n){Shmadder[len-1]\0;}char c;std::coutPlease Enter the code(Press a:continue to send message/Press x:stop sending):std::endl;std::cinc;getchar();int nwrite(fd,c,1);if(cx){break;}if(n0){a.logmessage(Fatal,write fail:%s,strerror(errno));exit(EXIT_FAILURE);}sleep(1);}那么这里我采取的就是fets函数往共享内存中写入数据因为它会首先会读取空格直到读取换行符结束那么这里注意的就是fets会读取换行符并且还会再数据的最后添加一个’\0’标记这样就能够方便B进程来确定消息的结尾但是由于fets会读取换行符而到时我们B进程通过日志打印终端消息的时候也会输入一个换行符所以这里就要处理末尾的换行符用’\0’来覆盖 而这里要注意的就是我们这里向管道文件写入字符c的时候那么这里我们是从标准输入中读取将其赋值给字符c而这里我们最后会敲一个回车键也就是换行符而这里cin读取标准输入和fets不同的是它这里不会读取换行符读到换行符就结束那么就会导致缓冲区会遗留一个换行符那么这里我们就通过getchar来将这个换行符给读取出来 4.解除挂载 那么最后剩下的两个环节就很简单了那么这里就是调用shmdet接口解除挂载然后判断一下返回值然后进行相应的日志打印 nshmdt(Shmadder);if(n0){a.logmessage(Fatal,processA detach FAILER:%s,strerror(errno));exit(EXIT_FAILURE);}a.logmessage(debug,processA detach successfully);5.清理资源 那么最后一步就是清理资源包括之前创建的管道文件以及共享内存 close(fd); unlink(FIFO_FILE); nshmctl(shmid,IPC_RMID,NULL);if(n0){a.logmessage(Fatal,processA shmctl fail:%s,strerror(errno));exit(EXIT_FAILURE);}a.logmessage(info,processA quit successfully);processB.cpp 1.获取process A进程创建的共享内存 那么这里这个环节调用GetShm来获取进程A创建的共享内存获取其shmid int shmidGetShm();a.logmessage(debug,processB get Shm successfully:%d,shmid);2.将共享内存挂载 那么这个环节和上文的process A所做的内容是一样的就是调用shmat接口然后获取该共享内存的起始位置的虚拟地址 a.logmessage(debug,processB open fifo successfully);char* Shmadder(char*)shmat(shmid,NULL,NULL);if(Shmadder(void*)-1){a.logmessage(Fatal,attch fail:%s,strerror(errno));exit(EXIT_FAILURE);}a.logmessage(debug,processB attch successfully:0x%x,Shmadder);读取process A向共享内存中写入的数据 那么这里由于在上文我介绍了进程双方的读写同步的机制那么这里对于B进程来说那么它首先就要读取管道中的信息确定A进程的意图如果读取到的字符是a说明A进程此时向共享内存写入了一条消息然后我定义了一个临时的字符数组从共享内存中读取1024个字节数据拷贝到该字符数组中而如果此时读到的字符是x说明A进程此时结束通信那么就退出循环 while(true){char c;int nread(fd,c,1);if(cx){break;}else if(n0){break;}else if(n0){a.logmessage(Fatal, processB read fail:%s,strerror(errno));exit(EXIT_FAILURE);}else {char buffer[1024]{0};memcpy(buffer,Shmadder,1024);a.logmessage(info,processB get a message:%s,buffer);}}5.清理资源 那么这里对于B进程来说那么它只需要关闭管道文件的读端以及解除挂载即可因为管道文件以及共享内存的删除都交给了A进程 close(fd);int nshmdt(Shmadder);if(n0){a.logmessage(Fatal,processB detach fail:%s,strerror(errno));exit(EXIT_FAILURE);}a.logmessage(debug,processB detach successfully);a.logmessage(info,processB quit successfully);源码 comm.hpp #pragma once #includesys/ipc.h #includeiostream #includesys/shm.h #includesys/types.h #includesys/stat.h #includelog.hpp #includecerrno #includecstring #includecstdio #define SHM_SIZE 4096 #define FIFO_FILE ./myfifo #define EXIT_FAILURE 1 #define ProjectId 110 const std::string pathname/home/WangZhe; int key; log a; void CreatKey() {keyftok(pathname.c_str(),ProjectId);if(key0){a.logmessage(Fatal,ftoke调用失败);exit(EXIT_FAILURE);} }size_t CreatShm(int flagIPC_CREAT|IPC_EXCL|0666) {CreatKey();int shmidshmget(key,SHM_SIZE,flag);if(shmid0){a.logmessage(Fatal,shemget fail:%s,strerror(errno));exit(EXIT_FAILURE);}return shmid; } size_t GetShm() {int shmidCreatShm(IPC_CREAT|0666);return shmid; } log.hpp #pragma once #includeiostream #includestring #includetime.h #includestdarg.h #includefcntl.h #includeunistd.h #define SIZE 1024 #define screen 0 #define File 1 #define ClassFile 2 enum {info,debug,warning,Fatal, }; class log {private:std::string memssage;int method;public:log(int _methodscreen):method(_method){}void logmessage(int leval,char* format,...){char* _leval;switch(leval){case info:_levalinfo;break;case debug:_leval debug;break;case warning:_levalwarning;break;case Fatal:_levalFatal;break;}char timebuffer[SIZE];time_t ttime(NULL);struct tm* localTimelocaltime(t);snprintf(timebuffer,SIZE,[%d-%d-%d-%d:%d],localTime-tm_year1900,localTime-tm_mon1,localTime-tm_mday,localTime-tm_hour,localTime-tm_min);char rightbuffer[SIZE];va_list arg;va_start(arg,format);vsnprintf(rightbuffer,SIZE,format,arg);char finalbuffer[2*SIZE];snprintf(finalbuffer,sizeof(finalbuffer),[%s]%s:%s,_leval,timebuffer,rightbuffer);int fd;switch(method){case screen:std::coutfinalbufferstd::endl;break;case File:fdopen(log.txt,O_WRONLY|O_CREAT|O_TRUNC,0666);if(fd0){write(fd,finalbuffer,sizeof(finalbuffer));close(fd); }break;case ClassFile:switch(leval){case info:fdopen(log/info.txt,O_WRONLY|O_CREAT|O_TRUNC,0666);write(fd,finalbuffer,sizeof(finalbuffer));break;case debug:fdopen(log/debug.txt,O_WRONLY|O_CREAT|O_TRUNC,0666);write(fd,finalbuffer,sizeof(finalbuffer));break;case warning:fdopen(log/Warning.txt,O_WRONLY|O_CREAT|O_TRUNC,0666);write(fd,finalbuffer,sizeof(finalbuffer));break;case Fatal:fdopen(log/Fatal.txt,O_WRONLY|O_CREAT|O_TRUNC,0666);break;}if(fd0){write(fd,finalbuffer,sizeof(finalbuffer));close(fd);}}} };processA.cpp #includecomm.hpp int main() {int shmidCreatShm();a.logmessage(debug,processA creat Shm successfully:%d,shmid);int nmkfifo(FIFO_FILE,0666);if(n0){a.logmessage(Fatal,creat fifo fail:%s,strerror(errno));exit(EXIT_FAILURE);}a.logmessage(debug,processA creat fifo successfully);a.logmessage(info,processA is waiting for processB open);int fdopen(FIFO_FILE,O_WRONLY);if(fd0){a.logmessage(Fatal,processA open fail:%s,strerror(errno));exit(EXIT_FAILURE);}a.logmessage(debug,processA open fifo successfully);char* Shmadder(char*)shmat(shmid,NULL,NULL);if(Shmadder(void*)-1){a.logmessage(Fatal,processA attach Fail:%s,strerror(errno));exit(EXIT_FAILURE);}a.logmessage(debug,processA attch successfully:0x%x,Shmadder);while(true){std::coutPlease Enter the messasge:std::endl;fgets(Shmadder,1024,stdin);size_t lenstrlen(Shmadder);if(len0Shmadder[len-1]\n){Shmadder[len-1]\0;}char c;std::coutPlease Enter the code(Press a:continue to send message/Press x:stop sending):std::endl;std::cinc;getchar();int nwrite(fd,c,1);if(cx){break;}if(n0){a.logmessage(Fatal,write fail:%s,strerror(errno));exit(EXIT_FAILURE);}sleep(1);}close(fd);unlink(FIFO_FILE);nshmdt(Shmadder);if(n0){a.logmessage(Fatal,processA detach FAILER:%s,strerror(errno));exit(EXIT_FAILURE);}a.logmessage(debug,processA detach successfully);nshmctl(shmid,IPC_RMID,NULL);if(n0){a.logmessage(Fatal,processA shmctl fail:%s,strerror(errno));exit(EXIT_FAILURE);}a.logmessage(info,processA quit successfully);exit(0); }processB.cpp #includecomm.hpp int main() {int shmidGetShm();a.logmessage(debug,processB get Shm successfully:%d,shmid);int fdopen(FIFO_FILE,O_RDONLY);if(fd0){a.logmessage(Fatal,processB open fail:%s,strerror(errno));exit(EXIT_FAILURE);}a.logmessage(debug,processB open fifo successfully);char* Shmadder(char*)shmat(shmid,NULL,NULL);if(Shmadder(void*)-1){a.logmessage(Fatal,attch fail:%s,strerror(errno));exit(EXIT_FAILURE);}a.logmessage(debug,processB attch successfully:0x%x,Shmadder);while(true){char c;int nread(fd,c,1);if(cx){break;}else if(n0){break;}else if(n0){a.logmessage(Fatal, processB read fail:%s,strerror(errno));exit(EXIT_FAILURE);}else {char buffer[1024]{0};memcpy(buffer,Shmadder,1024);a.logmessage(info,processB get a message:%s,buffer);}}close(fd);int nshmdt(Shmadder);if(n0){a.logmessage(Fatal,processB detach fail:%s,strerror(errno));exit(EXIT_FAILURE);}a.logmessage(debug,processB detach successfully);a.logmessage(info,processB quit successfully);exit(0); }运行截图 结语 那么这就是本篇关于共享内存的全面介绍了带你从多个维度来全面剖析共享内存那么下一期博客将会进入Linux的倒数第二座大山那么便是信号那么我会持续更新希望你能够多多关注如果本文有帮组到你还请三连加关注哦你的支持就是我创作的最大的动力
http://www.zqtcl.cn/news/541654/

相关文章:

  • 网站建设捌金手指专业7网站如何设置广告
  • 做网站用什么浏览器好工程公司工作总结
  • 温州做网站哪家好为wordpress移动端
  • 温州平阳县企业网站搭建推荐建立网站的技术路径
  • php c2c网站开发的 书营销型网站sempk
  • 网站建设专业网站设计公司物格网陕西建省级执法人才库
  • 网站后台管理密码忘了建设网站简单吗
  • 做网站在哪里网站开发平台有哪些
  • 网站域名的建立推荐一个两学一做的网站
  • 网站开发开源框架企业影视广告制作公司
  • 网站建设人员的组织音乐网站建设目标
  • 动画制作软件下载安装网站seo置顶
  • 怎么做网站推广的步骤关闭评论 WordPress
  • 合肥建站费用学生做兼职去哪个网站
  • 万户网络做网站如何做网站的企业排名
  • 天猫网站左侧菜单向右滑出的导航菜单阜阳h5网站建设公司
  • 凡科做网站的方法wordpress备份如何安装
  • 网站备案依据四川省广安建设局网站
  • 网站后台管理系统模板品牌营销和品牌推广
  • 网站建设的整个流程图wordpress标题去重
  • 网站手机版模板做拼货商城网站
  • wordpress建自己的网站吗c2c网站的特点
  • 建设网站的成本有哪些龙岩做网站哪家最好
  • wordpress 多站点 子目录安徽望江县城乡建设局官方网站
  • 电子政务网站建设的步骤一般为俱乐部logo免费设计在线生成
  • 网站建设尚品男生学计算机哪个专业最吃香
  • app制作网站收费吗重庆网站产品推广
  • 网站开发预算怎么算厦门建站比较好的公司
  • 涡阳网站优化建设工程公司企业文化
  • 曲靖市住房和城乡建设局网站罗湖区网站公司