Linux | 进程间通信:管道、消息队列、共享内存与信号量
文章目录
- 《深入理解进程间通信:管道、消息队列、共享内存与信号量》
- 一、进程间通信介绍
- (一)进程间通信目的
- (二)进程间通信发展
- (三)进程间通信分类
- 二、管道
- (一)什么是管道
- (二)匿名管道
- (三)管道特点
- (四)命名管道
- 三、共享内存
- (一)共享内存简介
- (二)共享内存数据结构
- (三)共享内存函数
- 四、消息队列
- 五、信号量
- (一)进程互斥
《深入理解进程间通信:管道、消息队列、共享内存与信号量》
在操作系统中,进程间通信(Inter-Process Communication,IPC)是一个至关重要的概念,它允许不同的进程之间进行数据传输、资源共享、事件通知以及进程控制等操作。本文将详细介绍进程间通信的几种主要方式:管道、消息队列、共享内存和信号量。
一、进程间通信介绍
(一)进程间通信目的
- 数据传输:一个进程需要将它的数据发送给另一个进程。
- 资源共享:多个进程之间共享同样的资源。
- 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件,例如进程终止时要通知父进程。
- 进程控制:有些进程希望完全控制另一个进程的执行,如调试进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
(二)进程间通信发展
- 管道:是 Unix 中最古老的进程间通信形式。
- System V 进程间通信:包括 System V 消息队列、System V 共享内存、System V 信号量。
- POSIX 进程间通信:包括消息队列、共享内存、信号量、互斥量、条件变量、读写锁等。
(三)进程间通信分类
- 管道:分为匿名管道和命名管道。
- System V IPC:包括消息队列、共享内存、信号量。
- POSIX IPC:包括消息队列、共享内存、信号量、互斥量、条件变量、读写锁等。
二、管道
(一)什么是管道
管道是从一个进程连接到另一个进程的一个数据流。在 Unix 系统中,管道的使用和文件一致,迎合了“Linux 一切皆文件思想”。
(二)匿名管道
- 功能和原型:
int pipe(int fd[2]);
用于创建一无名管道。其中fd
是文件描述符数组,fd[0]
表示读端,fd[1]
表示写端。成功返回 0,失败返回错误代码。 - 实例代码:从键盘读取数据,写入管道,读取管道,写到屏幕。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main( void )
{
int fds[2];
char buf[100];
int len;
if ( pipe(fds) == -1 )
{
perror("make pipe");
exit(1);
}
// read from stdin
while ( fgets(buf, 100, stdin) ) {
len = strlen(buf);
// write into pipe
if ( write(fds[1], buf, len)!= len ) {
perror("write to pipe");
break;
}
memset(buf, 0x00, sizeof(buf));
// read from pipe
if ( (len=read(fds[0], buf, 100)) == -1 ) {
perror("read from pipe");
break;
}
// write to stdout
if ( write(1, buf, len)!= len ) {
perror("write to stdout");
break;
}
}
}
- 用 fork 来共享管道原理:通过 fork 函数创建子进程,父子进程可以共享管道的文件描述符,从而实现进程间通信。
(三)管道特点
- 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信。通常,一个管道由一个进程创建,然后该进程调用 fork,此后父、子进程之间就可应用该管道。
- 管道提供流式服务。
- 一般而言,进程退出,管道释放,所以管道的生命周期随进程。
- 一般而言,内核会对管道操作进行同步与互斥。
- 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道。
(四)命名管道
- 创建命名管道:
- 命令行方法:使用
mkfifo filename
命令创建。 - 程序创建:使用
int mkfifo(const char *filename,mode_t mode);
函数创建。
- 命令行方法:使用
- 匿名管道与命名管道的区别:
- 匿名管道由
pipe
函数创建并打开;命名管道由mkfifo
函数创建,打开用open
。 - 一旦创建和打开完成后,它们具有相同的语义。
- 匿名管道由
- 命名管道的打开规则:
- 如果当前打开操作是为读而打开 FIFO 时:
O_NONBLOCK disable
:阻塞直到有相应进程为写而打开该 FIFO。O_NONBLOCK enable
:立刻返回成功。
- 如果当前打开操作是为写而打开 FIFO 时:
O_NONBLOCK disable
:阻塞直到有相应进程为读而打开该 FIFO。O_NONBLOCK enable
:立刻返回失败,错误码为ENXIO
。
- 如果当前打开操作是为读而打开 FIFO 时:
三、共享内存
(一)共享内存简介
共享内存区是最快的 IPC 形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。
(二)共享内存数据结构
struct shmid_ds {
struct ipc_perm shm_perm; /* operation perms */
int shm_segsz; /* size of segment (bytes) */
__kernel_time_t shm_atime; /* last attach time */
__kernel_time_t shm_dtime; /* last detach time */
__kernel_time_t shm_ctime; /* last change time */
__kernel_ipc_pid_t shm_cpid; /* pid of creator */
__kernel_ipc_pid_t shm_lpid; /* pid of last operator */
unsigned short shm_nattch; /* no. of current attaches */
unsigned short shm_unused; /* compatibility */
void *shm_unused2; /* ditto - used by DIPC */
void *shm_unused3; /* unused */
};
(三)共享内存函数
- shmget 函数:
- 功能:用来创建共享内存。
- 原型:
int shmget(key_t key, size_t size, int shmflg);
- 参数:
key
:这个共享内存段名字。size
:共享内存大小。shmflg
:由九个权限标志构成,它们的用法和创建文件时使用的mode
模式标志是一样的。
- 返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回 -1。
- shmat 函数:
- 功能:将共享内存段连接到进程地址空间。
- 原型:
void *shmat(int shmid, const void *shmaddr, int shmflg);
- 参数:
shmid
:共享内存标识。shmaddr
:指定连接的地址。shmflg
:它的两个可能取值是SHM_RND
和SHM_RDONLY
。
- 返回值:成功返回一个指针,指向共享内存第一个节;失败返回 -1。
shmaddr
为NULL
,核心自动选择一个地址。shmaddr
不为NULL
且shmflg
无SHM_RND
标记,则以shmaddr
为连接地址。shmaddr
不为NULL
且shmflg
设置了SHM_RND
标记,则连接的地址会自动向下调整为SHMLBA
的整数倍。公式:shmaddr - (shmaddr % SHMLBA)
。shmflg = SHM_RDONLY
,表示连接操作用来只读共享内存。
- shmdt 函数:
- 功能:将共享内存段与当前进程脱离。
- 原型:
int shmdt(const void *shmaddr);
- 参数:
shmaddr
:由shmat
所返回的指针。 - 返回值:成功返回 0;失败返回 -1。
- 注意:将共享内存段与当前进程脱离不等于删除共享内存段。
- shmctl 函数:
- 功能:用于控制共享内存。
- 原型:
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- 参数:
shmid
:由shmget
返回的共享内存标识码。cmd
:将要采取的动作(有三个可取值)。buf
:指向一个保存着共享内存的模式状态和访问权限的数据结构。
- 返回值:成功返回 0;失败返回 -1。
四、消息队列
消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法。每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值。
特性方面,IPC 资源必须删除,否则不会自动清除,除非重启,所以 System V IPC 资源的生命周期随内核。
五、信号量
信号量主要用于同步和互斥。
(一)进程互斥
由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥。系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源。在进程中涉及到互斥资源的程序段叫临界区。
特性方面,IPC 资源必须删除,否则不会自动清除,除非重启,所以 System V IPC 资源的生命周期随内核。
进程间通信是操作系统中的重要概念,不同的通信方式各有特点,在实际应用中需要根据具体需求选择合适的方式来实现进程之间的高效通信和协作。