linux常见进程通信方式,Linux进程间通讯
在Linux环境下运行一个程序时,无论是点击桌面上的一个图标,还是在命令行上轻敲一个shell命令,Linux系统都会把我们的程序“打包”成一个进程,然后调度它运行:每个进程轮流占用CPU一段时间来执行,时间到了就交给其他进程。只要转速够快,就会给用户一种错觉,我们在听歌,在电脑上打字。在运行的过程中,不同的进程会根据业务需要互相通信,比如传输数据,发送信号。
Linux环境下进程间通信有许多工具,如匿名管道、命名管道FIFO、消息队列、共享内存、信号量、信号、文件锁、套接字等。这些IPC工具以系统调用或库函数API的形式提供给用户:用户可以使用这些API在不同的进程之间传输数据、同步进程或发送信号。例如,我们可以使用ctrl C组合键来终止一个进程,或者使用shell命令kill 3567来终止一个进程pid为3567的进程。这些实际上是向进程发送信号的进程,进程接收信号并处理它。
不同的IPC工具在不同的场合有各自的优缺点。为了更好地使用它们,我们不仅要掌握API接口的使用,还要对它们的通信机制和内核实现原理有一个大致的了解。只有掌握了底层的实现原理,才能了解每种IPC通信工具的优缺点,以及它们的应用场合。要真正理解Linux进程之间是如何通信的,首先要了解Linux中不同的进程在运行过程中以什么形式存在于内存中,以及它们是如何与Linux内核交互的。要理解这一点,我们还需要对Linux环境下编译和执行程序的过程有一个大致的了解。
程序1的编译和执行
当我们点击桌面上的一个图标或者在命令行轻敲一个shell命令运行时,Linux系统会将这些可执行文件加载到内存中,封装成一个进程,然后才能参与操作系统的调度和运行。操作系统是如何加载的?
首先,我们写的C语言源代码会被编译成一个可执行文件(ELF)。可执行文件被分成不同的部分:代码部分、数据部分、BSS部分等。我们C程序中不同的代码会被编译成不同的段:函数实现会放在代码段中;全局变量和静态局部变量将放在数据段中;未初始化的全局变量将放在BSS段中。
加载器将程序加载到内存中执行,一般分为两步:第一步,用fork创建一个子进程,每个子进程有4G虚拟地址空间。第二步:从磁盘上安装软件的位置读取可执行文件的头文件:ELF header,获取各个段的信息,然后将不同的段加载到进程空间的不同位置,如上图所示。
在一个计算机系统中,通常会有多个进程同时运行,几乎所有的进程都是通过上述fork-exec方法运行的。当运行的进程比较多,每个进程都想独占占用和享用CPU的时候,CPU资源就不够用了,这时操作系统就开始出现了。操作系统扮演调度程序的角色,协调各个进程轮流占用CPU。
如上图所示,内核空间有一个特殊的数据结构来表示用户运行的不同进程:task_struct。这种结构描述了进程的各种信息。不同的task_sruct结构通过链表链接在一起,内核可以通过链表管理这些进程。操作系统将有一个称为调度程序的核心组件。每隔一段时间(通常以毫秒计),就会有一次定时器中断。Linux调度程序将把正在运行的进程从CPU上赶走,然后让另一个进程执行它,如此等等。只要CPU的速度足够快,交替执行的频率足够高,对于用户来说,感觉就是多个程序同时运行。
进程2的地址空间
每个进程都有一个4G大小的独立虚拟地址空间,然后通过页表映射映射到物理内存中的不同位置。当CPU执行不同的进程时,根据每个进程的映射页表,它会从其对应的物理内存中逐个取出、翻译和运行指令。
如上图所示,进程A和进程B在内存中有相同的4G虚拟地址空间,但是每个进程通过自己的页表映射,映射到物理内存中不同的位置。也就是说,虽然每个进程的虚拟地址空间是相同的,但它们在物理内存空间中是相同的隔离和独立的。在每个进程的4G虚拟地址空间中,[0,3G]是每个进程唯一的,而[3G,4G]被内核占用,不同进程的[3G,4G]被内核占用。当内核本身运行时,它也将在物理内存中拥有自己独立的存储空间。
Linux进程间通信的三种方法
通过上面的研究,我们可以看到,用户空间的不同过程在时间和空间上是相互隔离、相互独立的,就像黑夜和白天,太阳和月亮永远不会相遇,老人和死者永远不会联系。但一切都不是绝对的。如果你真的想和对方交流,方法是有的,如下图。
虽然用户空间中的每个进程在物理内存空间中是相互隔离、相互独立的,但是它们仍然可以通过内核空间的共享区域相互通信。只要内核愿意提供一些空间,不同的进程就可以向这个内存空间读写数据,达到进程间通信的目的。磁盘也是一个公共存储空间,不同的进程可以通过向磁盘上的指定文件读写数据来相互通信。另外,不同的进程可以绕过内核,通过内存映射在物理内存上建立共享内存,这样就可以直接通信了。
4匿名管道管道通信机制
以Linux的匿名管道通信机制为例:匿名管道常用于相关进程之间的通信,我们可以通过调用管道系统来创建一个管道:
int pipe(int pipe FD[2]);
这个函数将创建一个管道,它有两个文件描述符,一个用于读取,另一个用于写入。不同的进程可以通过读写描述符来读写这个管道,从而达到进程间通信的目的。
无名管道在内核中的实现其实很简单,就是一个Linux内核空间的缓冲区。通过pipefs机制封装成文件形式,文件读写接口:文件描述符预留给用户空间进程。空间中的不同进程可以通过这对读写描述符来读写管道。
5更多的进程间通信工具
除了无名管道,Linux还提供了许多进程间通信的工具,如命名管道FIFO、信号量、消息队列、共享内存、信号、套接字、Dbus等。不同的IPC工具各有优缺点和应用场合。例如,无名管道只能用于相关进程之间的通信。命名管道解决了这一限制,并支持任意两个进程之间的通信。消息队列可以支持数据格式的通信,以最高的效率共享内存,但需要结合信号量、锁等同步机制。信号主要用于进程间的异步通信,是唯一的异步通信机制。
每种IPC通讯工具都有各自的优缺点、应用场合和局限性。只有充分了解和掌握每种IPC工具的使用,了解其优缺点,才能根据实际需要选择合适的通信机制。除了这些由POSIX/system V标准接口定义的IPC工具,Linux系统还扩展了一些独特的API,如signalfd、timerfd等。解决了信号通信机制的一些缺陷。想多了解一下这些IPC工具接口的使用和实现机制。
版权归作者所有:原创作品来自博主环球CC,转载授权请联系作者,否则将追究法律责任。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。