五合一营销型网站,网站建设 中企动力 扬州,阿里巴巴外发加工网珠海,怎么查询个人名下营业执照文章目录 共享内存的通信速度消息队列msggetmsgsndmsgrcvmsgctl 信号量semgetsemctl 内核看待ipc资源单独设计的模块ipc资源的维护 理解信号量总结 本篇主要是基于共享内存#xff0c;延伸出对于消息队列和信号量#xff0c;再从内核的角度去看这三个模块实现进程间通信
共享… 文章目录 共享内存的通信速度消息队列msggetmsgsndmsgrcvmsgctl 信号量semgetsemctl 内核看待ipc资源单独设计的模块ipc资源的维护 理解信号量总结 本篇主要是基于共享内存延伸出对于消息队列和信号量再从内核的角度去看这三个模块实现进程间通信
共享内存的通信速度
共享内存是所有进程间通信里面速度最快的并且是没有质疑的何以见得这个结论呢
要说这个结论还要从管道说起对于进程来说如果想要使用管道进行通信那么首先要有进程的PCB和对应的进程地址空间其次如果选择管道进行通信那么就会建立管道之后将用户自己的数据交给另外一个进程通过write的系统调用写到缓冲区中本质上就是把用户级的缓冲区直接经过系统调用拷贝到内核的管道中对于读端来说就是用read系统调用再读取信息相当于是把内核中的数据经过read系统调用把数据拷贝到用户空间内此时就完成了从进程a到进程b数据通信的目的
而对于共享内存来说创建好共享内存之后经过挂接在自己的地址空间的共享区中就能找到这个区域之后就可以在这个共享内存中进行写入写入后共享内存这里就会立刻有这段信息在这个过程中是没有任何系统调用的写的信息立刻就能看到没有任何延误换而言之这两个进程之间不需要通过内核进行拷贝甚至都不需要用户定义的缓冲区直接向共享内存中写入信息用户立刻就能看到可以理解为是把用户级别的缓冲区合并成一个缓冲区只要进程a向里面写进程b立刻就能看到有效的减少了数据通信过程中经由数据拷贝造成的消耗问题
因此我们说对于共享内存来说它的效率体现在可以减少数据拷贝不需要经过用户到内核内核到用户这样的拷贝而是直接在用户的层面上进程a向共享内存写信息进程b就能看到甚至可能都不需要缓冲区的概念所以是高效的
那能减少多少次拷贝呢
对于这个问题来说这里简单做个解释具体可能会因为实际场景和配置略有差距
假设现在有输入的函数需求数据也从键盘输入了那么从文件的角度来讲就相当于从文件里面读取了信息而从文件中读取信息也算是一层拷贝而现在有输出的需求需要把数据输出到显示器上而现在的数据是存储到内存或者是缓冲区中而从内存刷新到外设这个过程也算是一层拷贝而实际上CPU只认识内存所以想要让CPU处理数据就必须把外设的信息加载到内存中再从内存处理后刷新到外设上而这个过程实际上就是把数据从一个设备拷贝到另外一个设备上凡是数据迁移都可以看成是拷贝所以对于管道文件来说不管是write系统调用还是read系统调用都是和内核进行交互从内核交互的角度来讲这两个函数其实就是一个拷贝的函数那么来回进行数据的拷贝效率自然不能和共享内存比
那么回到这个问题到底可以减少多少次拷贝假设现在有硬件那么从键盘到显示器这个过程输入数据是需要用户自己提供缓冲区把数据从键盘读取到缓冲区中再把数据从缓冲区拷贝到管道当中而最终目标是要打印到显示器上那显示器也是有对应的缓冲区的所以就把数据再拷贝到缓冲区中最终就能写到显示器中这么一套理论保守的来讲都有四次的拷贝过程如果把语言本身提供的缓冲区也加上只会比这个过程更多 那如果对于共享内存来说呢从键盘中读取的数据直接写到共享内存中读取的进程只需要把共享内存中的数据显示到显示器上就可以了此时就相当于第一次拷贝将数据从外设写到共享内存中第二次把共享内存中的数据写到显示器上两次拷贝就刷新过去了直接就省去了内核之间的拷贝过程就算不考虑语言级别的缓冲区也能减少两次拷贝 消息队列
消息队列提供一个进程给另外一个进程发送数据块的能力那如何理解这句话呢先画出下面的示意图 这是对于消息队列画出的最基本的示意图在ipc资源当初被设计的时候能够在内核层面上创建共享内存创建对应的结构来管理这样的共享内存那在操作系统层面上也可以创建一个队列这个队列的名字就叫做msg_queue这个队列刚开始是空的但是用户是有数据的通过一定的接口传递到队列中此时就会在队列的底层形成一个一个的节点这里可以理解成是链队列把这个链队列链入到这个队列之中
消息队列的本质是要进行进程间通信而只要涉及到进程间通信就离不开的话题是让两个不同的进程看到同一份资源这也是在先前已经建立起来的思想观念那么基于这个原因进程a发送的消息队列中的内容必然是需要让进程b见到的所以就有了接口
msgget 它其实和共享内存是很相似的也是从系统V中获取一个消息队列的标识符只要调用了这个接口那么就可以在内核中创建出一个消息队列这个消息队列就可以使用了
那随之而来的下一个问题是现在进程a创建出对应的消息队列也满足了让不同的进程看到同一份资源这样的一个基本的条件但是现在面临的问题是如果进程a向消息队列中写信息进程b也向消息队列中写信息那么如何去进行区分呢消息队列中的节点对于不同的进程来说想要看到的信息当然是不一样的所以必然有对应的标识符由进程a创建的数据节点中就会带有进程a的标识符由进程b创建的节点就会有进程b的标识符这样不同的进程在识别到某个资源中没有自己所对应的标识符就不会识别了而是只会识别到自己对应的标识符
消息队列由于和共享内存一样都是隶属于系统V内部的结构所以它们之间必定会遵循一定的标准所以从接口或是其他的层面上都几乎相似因此消息队列的生命周期也是随内核的而操作系统中各种各样的进程也都会有通信的需求如果创建出各种各样的消息队列那么操作系统也必然会为这一个一个的消息队列进行维护所以从逻辑上讲消息队列和共享内存基本上是一样的所以对于消息队列的管理就转换成了对于描述该消息队列的数据结构对象的增删查改这样就把消息队列管理起来了
msgsnd 发送数据块到消息队列
msgrcv 获取消息队列中的数据块
msgctl 这个接口也和共享内存基本一致这里不再过多描述
信号量
信号量本质上就是一种计数器用来保护共享资源未来可能会有多个线程看到同一个公共资源那在执行和访问共享资源的过程中就可能会产生问题例如一个进程正在写信息另外一个进程就已经来读了那么就会产生数据干扰这当然是不被操作系统认可的行为为了避免这样的问题导致内部数据紊乱所以就引入了信号量的概念来保护操作系统内部的公共资源这是对于信号量最初步的理解
semget 这是信号量的创建接口
semctl 这是信号量的控制接口和消息队列以及共享内存不太一样的是多了一个可变参数所以对于信号量的控制相比起其他来说要略复杂一些
消息队列和共享内存都有具体管理的数据结构对象所以对于信号量也不例外肯定有其对应的管理对象所以也会有对应的struct ipcperm结构体对象
对于信号量之后的其余内容放在之后的内容里这个模块本身主要是要对于共享内存的理解但由于消息队列和信号量都是ipc资源所以拿来一谈之后对于信号量还有更多的内容补充
内核看待ipc资源
下面进行的模块是内核是如何对待ipc资源的
上述有了三种共享资源有共享内存消息队列信号量由于这三个模块都是遵循一套标准做出来的所以也是比较相似例如接口的设计数据结构的管理方式以及返回的id值那对于操作系统来说是不是应该把这些也进行统一的管理呢答案是肯定的
单独设计的模块
第一个想要输出的结论是这个模块是操作系统内部单独设计出的模块对于模块的概念大体上可以细分为进程管理内存管理文件管理驱动管理这是操作系统的四大管理模块而对于ipc资源的管理也是一个模块只不过是下属的细分模块不属于最大的四个管理模块有了这个概念之后那么在操作系统内部是如何进行管理的呢
ipc资源的维护
ipc资源是如何在内核中进行维护的呢现在有三种共享资源这三种共享资源又有它们对应的idkey值这些分散的数据理应被管理起来事实上操作系统也确实把他们管理起来了 那在内核中是如何进行数据维护的呢在操作系统内部存在这样的结构
struct ipc_id_ary
{int size;struct kern_ipc_perm *p[0];
}这个结构体中存储的是数组元素个数以及一个柔性数组在上图中也有对应这里单独将其拿出分析
那生成这样的一个结构存放的数据类型是kern_ipc_perm的一个结构体类型的指针这个指针会指向一个指针数组这个指针数组中存储的不是其他信息存储的是具体的ipc资源的结构体的开头的第一个元素这也就是为什么在内核中不管是消息队列还是共享内存还是信号量它们的第一个元素都是一个perm类型的字样就是为了方便于将这个内容统一管理到这样的一个结构体中这样就能把所有的ipc资源统一用指针数组来管理起来
那这有什么用用处就是未来可以通过这个内容找到对应的内容在实际的使用中可以通过数组中的一个指针找到它对应的属于哪个共享资源然后转换成对应的类型有了起始地址和偏移量整个数组内的对应元素的各种内容也就都有了这样就能做到进行数据的访问过程
因此有了这样的结构之后再管理所有的ipc资源的时候在设计模式中就将所有内核结构的第一个成员设计成一样的都是key值未来在辨别这些ipc资源是否存在的时候只需要遍历这个数组指针在这个数组中找到各个内容中的key值然后判断这个key值是否存在就可以了如果不存在就进行创建因此往后就可以统一用数组的方式访问对应的资源如果想要找到对应资源中的其他信息也可以做出指针对类型做强转来定位到具体的位置
整个流程其实有些类似于C中的多态多态的概念已经不是第一次提出了再对于外设作为文件系统的篇章中已经讲述了虚拟文件系统就有些类似于多态而在这里也是第二次提出对于多态的概念多态就是令子类去继承基类那么对应到ipc的模式中每一个具体的ipc资源填充不同的属性但是开头的元素都一样再定义一个指针数组指针数组都会指向一个具体的ipc资源这就是一个典型的多态的过程
在Linux内核当中管理System V版本的ipc资源虽然内部实现的差异比较大但是利用抽象的方式还是用c语言实现了多态最终把所有的ipc资源都收拢在了一个数组中这样对于ipc资源的管理就转换成了对于这个数组的增删查改这样就做到了管理好共享资源
理解信号量
前面对于信号量的初步认知是信号量是一个计数器这里开始要对于信号量有一个更加具体的认知
首先对于信号量的引入是要让不同的进程看到同一份资源这也是进程通信的本质所以信号量的引入本质上是要让多个执行流看到同一份资源这部分资源就被叫做公共资源无论是命名管道还是匿名管道本质上都是一份公共的缓冲区资源而这些共享资源都是操作系统提供的这也是前面已经拥有的概念如果这个公共资源是由某个特定进程提供的那么就会违背进程的独立性所以在这样的情况下又会诞生的新的问题是当有很多的进程同时挂接到这块公共资源后去进行多进程并发访问这块资源的时候很可能会出现覆盖的情况导致出数据不一致这样的问题出现那么基于这个问题的解决方案是必须要把这部分内容保护起来把这部分内容保护起来就需要引入的两个概念叫做互斥和同步
互斥和同步是解决数据不一致的两种解决方式那具体是如何解决的结论是用户自己来解决或者是操作系统来帮用户解决对于匿名管道命名管道消息队列这样的通信方式其实就是操作系统来帮助用户来解决的因为管道是自带同步机制消息队列也是由操作系统帮助用户进行维护唯独是这块共享内存操作系统从本质上来说没有做任何事它只是帮助用户从内存中拿到了这块区域开辟出来了这块区域但是内核没有对于共享内存做出任何的保护因为共享内存是通信速度中最快的所以操作系统没有对于共享内存做出保护那么对于共享内存来说该如何进行数据的保护工作那么就因此有了下面的话题
在谈下面的话题前先对于几个有一个基本的认知
互斥和同步
互斥任何一个时刻只允许一个执行流访问公共资源加锁完成 同步多个执行流执行的时候按照一定的顺序执行
临界资源和临界区
对于要被保护起来的公共资源这部分资源就叫做临界资源比如对于操作系统来说它会把管道保护起来因为管道就是一种临界资源
而访问临界资源的这些代码和操作就被叫做是临界区由此就引出了临界资源和临界区的概念与之对应的还有非临界资源和非临界区两个概念比如对于访问键盘显示器或是定义变量这样的行为就属于是非临界区的操作但更重要的概念是临界资源的访问是要通过代码访问的也就是说是通过临界区去访问的所以用户要对临界资源进行保护其实只需要保护好临界区就可以换句话说就是把代码保护好写好代码这样就保护好了临界资源于是就有了加锁的概念对于这个概念不是这里的重点在之后再进行学习
原子性
原子性的概念虽然是一个新的词语但是却并不是第一次提及所谓原子性就是说操作一件事没有中间状态要不然把这件事都做完要不然压根不做对于这件事只有两个状态完成或是未完成这样的性质就叫原子性
信号量
有了上述的概念就引出了信息量的概念
在操作系统内部有很多的公共资源这里假设有一个具体的值假设现在有一份公共资源其中可以允许有100个进程同时进行访问那么此时就有一个计数器来了计数器就负责保护这块公共资源它只允许最多有100个进程来访问这块资源每当有一个进程对于这块公共资源进行占用计数器的值就减去1当这个进程离开这块公共资源这个计数器就加上1表示可以访问的进程又多了一个所以说对于这种用来衡量公共资源的数目最终达到对公共资源分配的这样一个目的这种计数器就叫做信号量所以信号量的本质就是一个计数器这是在最初就引出的概念信号量的操作规则就是对于某一个执行流向要访问公共资源中的某一个资源不是直接去访问而是要让执行流去申请信号量资源这样信号量资源的计数器就能做出对应的改变只要申请成功了就能去访问这种就叫做预定机制对于信号量的大体框架就搭建完毕了这也就是信号量最初始的理解
但是这远远不够对于信号量还有很多地方没有解释清楚下面选出一部分来进行解析
信号量的本质是一种计数器执行流就是进程当有进程要访问公共资源中的某一个资源的时候要先申请信号量只要把信号量申请成功就已经完成了对应的预定功能在合适的时候就可以进入来访问了在申请不成功的时候怎么办呢对应的执行流就要进行阻塞正式因为有了信号量的概念从此之后这部分公共资源就能被保护起来了而不是被多个进程随意的读取数据导致数据残缺的问题出现换而言之操作系统中由于有信号量的存在所以整个公共资源的访问上限被决定好了也就意味着访问的上限是可控的信号量的本质是用来描述公共资源中资源的数量申请信号量的本质是对公共资源的一种预定机制当进程申请信号量的时候申请成功就可以访问申请失败就要被阻塞挂起直到申请成功后就能继续访问这块内容如果这个计数器的最大值为1呢也就是说同一时刻只允许有一个进程来对这块区域进行访问多余的内容都不允许访问那么本质上就实现了一个互斥的功能同一时刻只允许一个执行流访问公共资源也就是一种加锁这样就实现了一个互斥锁这样的内容就被叫做是二元信号量用信号量的方式实现了加锁和解锁的过程完成了互斥的效果所以未来在操作系统的内部有一份公共的资源所有的进程都能看到这块资源但是如果想要使用这块资源就假设现在这块资源可以被拆成很多小资源例如现在有一块共享内存有16kb现在把这块内存拆成16个小块一个小块是1kb这样就能做到允许16个进程同时进来访问的是不同的数据块就可以做到并发访问了想要访问这个数据块就必须先经过信号量申请成功就访问申请失败就挂起有了信号量的存在最终形成的效果是在访问公共资源前要先访问信号量有了信号量的运行才能访问公共资源所以两个进程之间创建了一个共享内存并且两个进程都相互挂接到了这个共享内存上因为信号量的存在所以在进行内存访问前要先向信号量申请再对内存进行访问在这个过程中其实也能看出信号量也是一种公共的资源这两个进程都能看到这块资源也算是完成了一种进程间通信因为让不同的资源都看到了信号量的存在信号量是由操作系统提供的操作系统如何让两个进程看到同一个信号量因此操作系统就把信号量也纳入了进程的ipc体系中所以对应不同的进程每申请一份资源就意味着可以被申请的资源少了一份其他的进程也都能知道这就得益于信号量的存在因此对于数据通信来说它的目的并不一定都是为了进程数据传输也可以通过一个计数器来帮助更合理的完成其他的进程间通信因此才有了创建信号量需要一个key值只要有同一个key值才能做到让不同的进程看到同一份资源下一个问题是每一个进程在访问公共资源之前都要先申请信号量所以也就意味着信号量本身也是一种公共资源那假设现在有1000个进程要访问同一块空间这个空间只允许10个进程进行访问在一瞬间有1000个进程同时访问信号量要申请空间信号量只允许10个所以一瞬间就申请结束了因此信号量本身作为管理公共资源的资源它自己也变成了公共资源那这怎么办呢结论是得益于它内部的实现方式遵循原子性对于一个进程来说要不然不申请要申请就必须有一个结果可以申请或者被挂起
信号量的基本结构
因此对于未来操作系统中的信号量来说它里面至少要存储的信息有一个计数器count还需要有一个PCB对应的指针来维护需要被挂起的进程这个就叫做等待队列当一个进程要申请信号量就让count–如果count变成0了就把这个进程从运行队列中剥离出来把状态改成阻塞再放到等待队列中进行等待当有一个进程结束了自己的工作从公共资源中出来了此时就把等待队列中的进程唤醒让它从等待队列中出来再放到运行队列中这样就实现了操作系统的调度功能
总结
信号量本质上也是需要被多个进程看到的所以说信号量本身也是一种公共资源它是一种资源的预定机制属于进程间通信的一种信号量由于也是一种公共资源所以要保证自身的安全性因此在对于资源申请的pv操作(p操作指的是申请资源count–v操作指的是释放资源count)必须要有原则这也就是为什么信号量有pv操作究其原因是它也要保证自身的安全