学ucos至事件管理,事件之间将会有对话,开始拿本操作系统的书辅助理论上的理解。
参考《操作系统之哲学原理》,作者 邹恒明。
it's the map of this section.
1 进程对白
进程对白就是一个进程发出某种数据信息,另外一方接受数据信息,而这些数据信息通过一片共享的存储空间进行传递。
管道所占空间可以是内存,也可以是磁盘。就像两个人对白的媒介可以是空气,也可以是线缆一样。要创建一个管道,一个进程只需要调用管道创建的系统调用即可。该系统调用所做的事情就是在某种存储介质上划出一片空间,赋给其中一个进程写的权利,另一个进程读的权利即可。
1.1 管道
从根本上说,管道是一个线性字节数组,类似文件。在其后谈论到管道和记名管道的缺点是提到,管道和记名管道并不是所以操作系统都支持,主要支持管道通信方式的是UNIX和类UNIX的操作系统,这使我想起everything is file这话。
在程序里面,创建管道需要使用系统调用popen()或者pipe()。popen()需要提供一个目标进程作为参数,然后调用该函数的进程和给出目标进程之间创建一个管道。
创建时还需要提供一个参数表明管道类型——读管道或者写管道。而pipe()调用将返回两个文件描述符,即一个用于识别文件流的整数,其中一个用于从管道进行读操作,一个用于写入管道。例如
1 int pp[2]; 2 pipe(pp); 3 4 if(fork() == 0){ 5 read(pp[0]); 6 } 7 else{ 8 write(pp[1]) 9 }
管道的一个重要特点是使用管道的两个进程之间必须存在某种关系。
1.2 记名管道
如果在两个不相关的进程(如两个不同进程里的进程)之间进行管理通信,则需要使用记名管道。记名管道是一个有名字的通信管道,与文件系统共享一个名字空间,即不能与其他任何文件重名。
一个进程创建一个记名管道后,另外一个进程可以使用open来打开这个管道(五名管道不能使用open操作),从而与另外一端交流。
记名管道的名称由两部分组成:计算机名和管道名。
对于同一主机来说,允许有多个同一命名管道的实例并且可以由不同的进程打开,但是不同的管道都有属于自己的管道缓冲区而且有自己的通信环境,互不影响。命名管道可以支持多个客户端连接一个服务器端,命名管道客户端不但可以与本机上的服务通信,也可以同其他主机上的服务通信。
1.3 套接字
套接字(socket)是另外一种可以用于进程间通信的机制,其功能非常强大,可以支持不同层面、不同应用、跨网络的通信。
使用套接字进行通信需要双方都创建一个套接字,其中一方作为服务器方,另一方作为客户方。
服务器方必须先创建一个服务器套接字,然后在该套接字上进行监听,等待远方的连接请求。
欲与服务器通信的客户则创建一个客户套接字,然后向服务区的套接字发送连接请求。
服务器套接字在收到连接请求之后,将在服务器方机器上创建一个客户套接字,与远方的客户机上的客户套接字形成点到点的通信通道。
之后,客户方和服务器方就可以通过send和recv命令在这个创建的套接字通道上进行交流了。
例子略。
这里需要指出的是服务区套接字既不发送数据,也不接受数据(指不接受正常的用户数据,而不是连接请求数据),而仅仅生产出客户套接字。当其他的客户套接字发出一个连接请求时,我们就创建一个客户套接字。一旦创建了客户套接字clientsocket,与客户通信的任务就交给了这个刚刚创建的客户套接字。而原本的服务器套接字seversocket则回到其原来的监听操作上。
因为其强大的功能,套接字有许多种类,不同的操作系统均支持或实现了某种套接字的功能。
然后作者blabla了套接字的类型,no interested, so just skip over.
2 进程电报——信号
管道和套接字同时也存在某些缺点。
首先,如果使用管道和套接字方式来通信,必须事先在通信的进程间建立连接(创建管道或者套接字),这需要消耗资源。
其次,通信是自愿的。即一方虽然可以随意向管道或者套接字发送信息,但对方却可以选择接受的时机,这对发送方不是有利,如果应用到实时系统就违背了实时的原则。
再次,由于建立消耗连接时间,一旦建立,我们就想进行尽可能多的通信。而如果通信的信息量微小,则用管道和套接字有就些浪费资源了。
因此我们选择用另外一个机制,即信号,来应对上述的通信需求:
- 想迫使一方对我们的通信立即做出回应;
- 我们不想事先建立任何连接,而是临时觉得有需要与某个进程通信;
- 传输的信息量微小,使用管道或者套接字不划算。
在ucos里,有信号,应该不存在管道和socket吧。
在计算机里,信号就是一个内核对象,或者说是一个内核数据结构。发送方将该数据结构的内容填好,并指明该信号的目标进程后,发出特定的软件中断。操作系统接受到特定的中断请求后,知道是有进程要发送信号,于是到特定的内核数据结构里查找信号接收方,并进行通知。接到通知的进程则对信号进行相应处理。
3 进程旗语——信号量
作者在这里把信号量比作旗语——引用了火车的一个例子。
在一条单轨铁路上,任何时候只能有一列列车行驶在上面。而管理这条铁路的系统就是信号量。任何时候一列火车需要要等到表明铁路可以行驶的信号后才能进入轨道。当一列列车进入单轨运行之后,需要将信号改为禁止进入,从而防止别的火车同时进入轨道。而当列车驶出单轨之后,则需要将信号变为允许进入状态。
在计算机里,信号量实际上就是一个简单整数。一个进程在信号量(原书没有“量”字)变为0或者1的情况下推进,并且将信号量(原书没有“量”字)变为1或者0来防止别的进程推进。
(就像一个使能信号,比如这里的OS_EVENT_EN?
1 #if (OS_EVENT_EN) 2 void OS_EventWaitListInit (OS_EVENT *pevent) 3 { 4 #if OS_LOWEST_PRIO <= 63 5 INT8U *ptbl; 6 #else 7 INT16U *ptbl; 8 #endif 9 INT8U i; 10 11 12 pevent->OSEventGrp = 0; /* No task waiting on event */ 13 ptbl = &pevent->OSEventTbl[0]; 14 15 for (i = 0; i < OS_EVENT_TBL_SIZE; i++) { 16 *ptbl++ = 0; 17 } 18 } 19 #endif
)
需要注意的是,信号量不只是一种通信机制,更是一个同步机制。
4 进程拥抱——共享内存
管道、套接字、信号、信号量,虽然满足了多种通信需要,但未考虑到两个进程通信需要共享大量数据。这时便引出"共享内存"这个概念。
共享内存就是两个进程共同拥有同一片内存。对于这片内存中的任何内容,二者均可以访问。要实用共享内存进行通信,一个进程需要首先创建一片内存空间专门作为通信用,而其他进程则将该片内存映射到自己的(虚拟)地址空间。这样,读写自己地址空间中对应共享内存的区域时,就是在和其他进程进行通信。如图(来源自书本)
共享内存与管道的区别在于:
- 使用共享内存机制通信的两个进程必须在同一台物理机器上;
- 其次,共享内存的访问方式是随机的,而不是只能从一端写,另一端读,因此其灵活性比管道和套接字大很多,能够传递的信息也复杂很多。
共享内存的缺点是管理复杂和安全性弱。
这里需要注意的是,使用全局变量在同一个进程的进程间实现通信不称为共享内存。
5 信件发送——消息队列
消息队列是一列具有头和尾的消息排列。新来的消息放在队列尾部,而读取消息则从队列头部开始。如图(来源于书本)
消息队列和管道的区别在于:
- 首先,他无需固定的读写进程,任何进程都可以读写(当然是有权限的进程);
- 其次,他可以同时支持多个进程,多个进程可以读写消息队列。即所谓的多对多,而不是管道的点对点;
- 另外,消息队列只在内存中实现;
- 最后,它并不只在unix和类unix操作系统中实现。几乎所有主流操作系统都支持消息队列。
6 其他通信机制
blabla,no interested