首先简单谈一下什么是进程?
答:进程是装入内存运行的程序段,是许多的系统对象拥有权的集合,换句大家经常引用的话说进程是资源分配的基本单位。
举例来说,我们的浏览器程序存放在C盘的某个位置,这时它只是硬盘上的程序。每次我们打开一个浏览器的时候,这个程序就会被装入内存中去,进行一系列初始化(进程控制块PCB的初始化,包括进程计数器,进程状态,CPU命令,寄存器等等)。此时我们看到了浏览器打开并显示网页,此时这就是一个运行中的进程,我们可以打开任务管理器看到许许多多运行的进程。
为什么说进程拥有许多系统对象?
答:刚才说了,进程是装入内存的程序段,那进程的运行势必涉及到内存的分配(包括堆栈,文本区,数据区等,这里是参考了C语言程序的内存分配),而进程所占用的内存中就可能会有文件句柄,DLL模块(在可执行程序运行时才载入内存的代码),上下文等。
这里,我们顺便说一下线程,大家记住线程是CPU运行调度的基本单位,线程必须被包含在进程中,一个进程可以有很多线程(至少有一个),这些线程共享进程的许多资源(如栈,寄存器)。(关于进程与线程的优缺点,多线程的东西会在之后的博客里写)。
什么是进程通信?为什么要通信?
答:进程通信(IPC),即Inter-ProcessCommunication,字面上理解就是进程之间的通信。
这里举一个不同主机的进程通信,比较容易理解。你电脑上打开一个网页,就能看到各种文字图片,这些图片是哪里来的?如何获取的?我们上面说打开的浏览器就是一个进程,那浏览器获取别处的图片资源就是一种通信的方式来拿到想要的东西的。浏览器向服务器发送请求获取资源就是一种靠套接字来进行的进程通信,这种通信通过一台主机的应用层端口以及Socket下发到运输层(TCP),再经过下面的网络层,数据链路层,物理层(这是计算机网络5层模型)传输到另一台机器的socket,最后服务器获取到该信息,实现进程间通信。一句话说,A主机的浏览器通过socket与另一台B主机的进程进行通信交流。
进程属于内核的基本功能,所以不同的操作系统在实现进程通信有一定的区别,不过想通之处也还是很多的。
其实,可能有很多同学和朋友和我一开始理解的一样,认为IPC只有消息传递和共享内存两种(没错,操作系统书上就是这么写的),可后来我发现消息传递具体是什么,有什么特点,我都说不太清~所以稍微研究了下
我们可以这样理解:
IPC只是按形式上可以分为消息传递,共享内存,同步。
(下面是用linux的IPC举例)
消息传递就包括,管道,FIFO命名管道,消息队列
共享内存,包括匿名与具名的,
同步,包括互斥量,读写锁,信号量等。
我们可以把每一种IPC都拿出来单独作比较。
首先先大致说一下共享内存,管道的概念:
其原理就是将一份物理内存映射到不同进程各自的虚拟地址空间,使每一个进程都可以读取这份数据,来实现通信,因为是直接通过内存操作,所以是一种非常高效的通信方式。
管道(Pipe)是一种具有两个端点的通信通道:有一端句柄的进程可以和有另一端句柄的进程通信。管道可以是单向-一端是只读的,另一端点是只写的;也可以是双向的一—管道的两端点既可读也可写。
下面简单介绍一下Windows与Linux的进程通信以及比较:(这里主要参考了 coolmeme 的博客)
Windows:
1. 文件映射:
文件映射(Memory-Mapped Files)能使进程把文件内容当作进程地址区间一块内存那样来对待。因此,进程不必使用文件I/O操作(正常来说需要用输入输出流来读写文件ioStream),只需简单的指针操作就可读取和修改文件的内容。
Win32API允许多个进程访问同一文件映射对象,各个进程在它自己的地址空间里接收内存的指针。通过使用这些指针,不同进程就可以读或修改文件的内容,实现了对文件中数据的共享。
注意:文件映射是在多个进程间共享数据的非常有效方法,有较好的安全性。但文件映射只能用于本地机器的进程之间,不能用于网络中,而开发者还必须控制进程间的同步。
2. 共享内存:
Win32API中共享内存(SharedMemory)实际就是文件映射的一种特殊情况。进程在创建文件映射对象时用0xFFFFFFFF来代替文件句柄(HANDLE),就表示了对应的文件映射对象是从操作系统页面文件访问内存,其它进程打开该文件映射对象就可以访问该内存块。
3. 匿名管道:
匿名管道(Anonymous Pipe)是在父进程和子进程之间,或同一父进程的两个子进程之间传输数据的无名字的单向管道。通常由父进程创建管道,然后由要通信的子进程继承通道的读端点句柄或写端点句柄,然后实现通信。父进程还可以建立两个或更多个继承匿名管道读和写句柄的子进程。这些子进程可以使用管道直接通信,不需要通过父进程。
4. 命名管道:
FIFO,先进先出的通信方式。
命名管道(Named Pipe)是服务器进程和一个或多个客户进程之间通信的单向或双向管道。不同于匿名管道的是命名管道可以在不相关的进程之间和不同计算机之间使用,服务器建立命名管道时给它指定一个名字,任何进程都可以通过该名字打开管道的另一端,根据给定的权限和服务器进程通信。
5. 动态链接库
DLL解释:与动态链接库连接的可执行文件只包含它需要的函数的引用表,而不是所有的函数代码,只有在程序执行时, 那些需要的函数代码才被拷贝到内存中,这样就使可执行文件比较小, 节省磁盘空间,操作系统使用虚拟内存,使得一份DLL驻留在内存中被多个程序使用,节省内存;
Win32动态连接库(DLL)中的全局数据可以被调用DLL的所有进程共享,这就又给进程间通信开辟了一条新的途径,当然访问时要注意同步问题。
6. 远程调用RPC
Win32 API提供的远程过程调用(RPC)使应用程序可以使用远程调用函数,这使在网络上用RPC进行进程通信就像函数调用那样简单。RPC既可以在单机不同进程间使用也可以在网络中使用。
7. Socket
刚才我们举例子简单说明了socket(套接字)的作用,我们要知道目前socket是一种非常流行的网络通信手段。
WindowsSockets规范是以U.C.Berkeley大学BSD UNIX中流行的Socket接口为范例定义的一套Windows下的网络编程接口。除了Berkeley Socket原有的库函数以外,还扩展了一组针对Windows的函数,使程序员可以充分利用Windows的消息机制进行编程。
8. 剪贴板
Windows剪贴板应该算是一种比较简单开销小的IPC,内置在windows并使用系统的内部资源RAM,或者虚拟内存类临时保存剪切和复制的信息,系统预留的一块全局共享内存,用来暂存在各进程间进行交换的数据:提供数据的进程创建一个全局内存块,并将要传送的数据移到或复制到该内存块;接受数据的进程(也可以是提供数据的进程本身)获取此内存块的句柄,并完成对该内存块数据的读取。这里有一点疑问,我认为剪贴板并不是把所有的信息复制到那个内存,而是把路径等信息存入再进行拷贝(这里希望有大神给解释一下。。。)。
9. WM_COPYDATA消息
当一个应用向另一个应用传送数据时,发送方只需使用调用SendMessage函数,参数是目的窗口的句柄、传递数据的起始地址、WM_COPYDATA消息。接收方只需像处理其它消息那样处理WM_COPY DATA消息,这样收发双方就实现了数据共享。WM_COPYDATA消息无疑是一种经济实惠的一中方法
Linux:
1. 管道:
与上述windows的管道类似,也分为匿名与有名字的管道。管道可用于具有亲缘关系进程间(父子,兄弟进程)的通信,有名管道克服了管道没有名字的限制,允许不同无亲缘关系的进程通信。
2. 信号(signal):
信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction
3. 报文(Message)队列(消息队列):
消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息,注意消息队列是创建文件方式建立,所以即使添加消息的进程结束,保存在消息队列的消息也不会消失。
消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
4. 共享内存:
上面描述了基本原理,往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
5. 信号量(semaphore):
主要作为进程间以及同一进程不同线程之间的同步手段。(线程间通信业经常使用信号量)
信号量是一个特殊的变量,只能对其进行初始化,PV以及删除操作(这里涉及到同步与锁的一些内容,可以查一下)。
6. 套接字(Socket):
上面描述过,可用于不同机器之间的进程间通信。
对几种IPC的简单比较:
1. 管道,速度较慢,缓冲区大小受限,只能承载无格式的字节流。只能用于有亲缘关系的进程。
2. FIFO管道,任何进程间都可以通信,包括不同机器上的管道,速度较慢。
3. 消息队列:因为位于内核中,所以容量受限,第一次读取时要注意上次添加的消息是否读取。
4. 信号量:不能传递复杂的消息,只用于同步信息。
5. 共享内存:能很容易的控制变量,速度快,要注意读写安全性,避免死锁等。