Chromium多进程机制解析

的解决方案!主要运用了以下关键技术:

让我们从头说起!

先修路! 建立通道

写一个单机下多进程应用的核心就是建立进程之间沟通的方式,可以称为channel 或者 pipe。操作系统会提供这样的基础机制,包括:named pipe, shared memory, socket。

Chromium在POSIX下使用Unix Domain Socket来实现。Unix Domain Socket通过复用网络socket的标准接口,提供轻量级的稳定的本机socket通讯。Unix Domain的命名则是来自于使用socket时将domain参数定义为: PF_UNIX (Mac OS)或AF_UNIX, 来标识为单机的通讯使用。socket的建立不以IP地址为目标了,而是由文件系统中的FD(文件描述符)。

socket API原本是为网络通讯设计的,但后来在socket的框架上发展出一种IPC机制,就是UNIX Domain Socket。虽然网络socket也可用于同一台主机的进程间通讯(通过loopback地址127.0.0.1),但是UNIX Domain Socket用于IPC更有效率:不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程。 UNIX域套接字与TCP套接字相比较,在同一台主机的传输速度前者是后者的两倍。这是因为,IPC机制本质上是可靠的通讯,而网络协议是为 不可靠的通讯设计的。UNIX Domain Socket也提供面向流和面向数据包两种API接口,类似于TCP和UDP,但是面向消息的UNIX Domain Socket也是可靠的,消息既不会丢失也不会顺序错乱。

使用UNIX Domain Socket的过程和网络socket十分相似,也要先调用socket()创建一个socket文件描述符,address family指定为AF_UNIX,type可以选择SOCK_DGRAM或SOCK_STREAM,protocol参数仍然指定为0即可。

UNIX Domain Socket与网络socket编程最明显的不同在于地址格式不同,用结构体sockaddr_un表示,网络编程的socket地址是IP地址加端口 号,而UNIX Domain Socket的地址是一个socket类型的文件在文件系统中的路径。

Chromium在POSIX下直接使用socketpair() API创建了已经连通的匿名管道, 逻辑结构如下:

可以看过IPC机制里区分了Server和Client, 其实通过socketpair()创建的匿名管道是全双工,实质上并不区分Server/Client。

这个IPC建立的过程是在主进程完成的,需要使用其它的机制通知子进程。在Android Chrome下,通过传递FD列表完成这个操作。这个稍后再解释 (可以查找kPrimaryIPCChannel学习)。

当子进程知道Server端的socket FD后,就可以进行连接,发送hello message,认证后就可以开始通讯了。

通讯端口的管家

当端口打通后,效率成为关键。通常而言,一般这时候会考虑为了及时处理收到消息,要么轮询,要么实现回调。但事情并不是这么简单。

虽说Unix Domain Socket不走网络栈已经提升不了性能。但还有负载的问题需要解决。使用回调机制是必须的,更重要的是面临C10K问题:

C10K Problem

C10K 问题的最大特点是:设计不够良好的程序,其性能和连接数及机器性能的关 系往往是非线性的。举个例子:如果没有考虑过 C10K 问题,一个经典的基于 select 的程序能在旧服务器上很好处理 1000 并发的吞吐量,它在 2 倍性能新服务器上往往处 理不了并发 2000 的吞吐量。

Chromium使用了大名鼎鼎的第三方并发网络库:libevent来完成这项工作 (另外还有ACE,自适应通信环境)。 下面是它的功能介绍:

Libevent是一个轻量级的开源高性能网络库.有几个显著的亮点:

a.事件驱动(event-driven),

b.高性能 轻量级,专注于网络,不如ACE那么臃肿庞大

c.注册事件优先级

基本的socket编程是阻塞/同步的,每个操作除非已经完成或者出错才会返回,这样对于每一个请求,要使用一个线程或者单独的进程去处理,系统资源没法支撑大量的请求(所谓c10k problem),例如内存:默认情况下每个线程需要占用2~8M的栈空间。posix定义了可以使用异步的select系统调用,但是因为其采用了轮询的方式来判断某个fd是否变成active,效率不高[O(n)],连接数一多,也还是撑不住。于是各系统分别提出了基于异步/callback的系统调用,例如Linux的epoll,BSD的kqueue,Windows的IOCP。由于在内核层面做了支持,所以可以用O(1)的效率查找到active的fd。基本上,libevent就是对这些高效IO的封装,提供统一的API,简化开发。

简而言之,就是:"不要等!只要好了,我就会通知你!",就是典型的好莱坞法则。这相当于在通讯端口设了一位大管家,提升IPC交互的能力。

Libevent的核心是应用了解决并发问题中常用的基于事件驱动的Reactor模式。简单而言就是通过一个内部的循环,在事件触发时启动并进行响应,无事件时则挂起.它同样需要注意事件回调的处理不能做太多事情,避免拥塞.在Chromium里的代码体现IPC Channel实现如下接口:

// Used with WatchFileDescriptor to asynchronously monitor the I/O readiness// of a file descriptor.class Watcher { public: // Called from MessageLoop::Run when an FD can be read from/written to // without blocking virtual void OnFileCanReadWithoutBlocking(int fd) = 0; virtual void OnFileCanWriteWithoutBlocking(int fd) = 0; protected: virtual ~Watcher() {}};

关于Reactor模式和另一类相似的模式的比对, 可以读一下这篇文章。

专人接待! 排队,排队…

接受失败等于回归真实的自我,

Chromium多进程机制解析

相关文章:

你感兴趣的文章:

标签云: