Linux系统编程之进程间通信(IPC各种通信方式定义)

一、进程间通信(IPC)概述
二、管道(无名管道)和(命名管道)
三、消息队列(Message Queues)
四、共享内存(Shared Memory)
五、信号(Signals)
六、信号量(Semaphores)

一、进程间通信(IPC)概述

进程间通信(Inter-Process Communication,IPC)是计算机科学中的一个重要概念,指的是不同进程之间进行数据交换和通信的机制。这是在多任务操作系统中非常常见的需求,因为不同的进程需要协同工作,共享数据,或者以其他方式进行交互。以下是一些常见的 IPC 方法:

  1. 管道(Pipes):管道是一种线性数据流通信方式,通常用于父进程和子进程之间或者亲缘关系的进程之间进行通信。管道是单向的,通常用于数据流的传输。

  2. 命名管道(Named Pipes):命名管道也称为FIFO,它允许无关进程之间的通信,通常用于不相关的进程之间的数据交换。

  3. 消息队列(Message Queues):消息队列允许不同进程之间通过在消息队列中发送和接收消息来进行通信。消息队列可以用于多对多的通信。

  4. 共享内存(Shared Memory):共享内存允许多个进程共享同一块内存区域,这使得数据的传输非常高效。然而,需要小心处理同步问题,以避免竞态条件。

  5. 信号(Signals):信号是一种轻量级的通信方式,用于通知进程发生了某个事件。进程可以注册信号处理程序来响应信号。

  6. 套接字(Sockets):套接字是一种用于网络编程的通用 IPC 方法,也可以用于本地进程间通信。它可以用于不同计算机上的进程通信。

  7. 信号量(Semaphores):信号量用于进程同步,允许多个进程协同工作。它可以用于控制对共享资源的访问。

  8. 文件锁(File Locks):文件锁可以用于多个进程协同访问共享文件,以确保数据的一致性和完整性。

  9. RPC(Remote Procedure Call):RPC是一种远程过程调用机制,允许一个进程调用另一个进程中的函数,就像调用本地函数一样。

不同的 IPC 方法适用于不同的场景和需求,选择合适的 IPC 方法取决于进程之间的关系、通信数据的性质和性能要求。IPC 是多进程编程中的关键概念,用于构建复杂的应用程序,协同工作并共享信息。

二、管道(无名管道)和(命名管道)

管道(Pipes)是一种进程间通信(IPC)的机制,允许数据在两个进程之间单向流动。管道通常用于父进程和子进程之间或者亲缘关系的进程之间进行通信,其中一个进程充当写入者(Producer),另一个进程充当读取者(Consumer)。在Unix和Linux系统中,管道有两种类型:无名管道和命名管道。

以下是管道的通信原理:

  1. 无名管道(Anonymous Pipes)

    • 无名管道是最简单的管道形式,通常在创建父进程之后,父进程创建一个管道,然后再创建子进程。
    • 管道在内存中创建一个缓冲区,数据可以从一个进程写入缓冲区,然后从另一个进程中读取。
    • 无名管道是单向的,数据只能在一个方向流动,通常从父进程到子进程。
    • 数据传输是基于文件描述符(File Descriptors)进行的。父进程使用管道的写入文件描述符,子进程使用读取文件描述符。
    • 无名管道在进程间传递数据时,通常用于实现简单的协作和数据交换。
  2. 命名管道(Named Pipes,FIFO)

    • 命名管道是一种具有名称的管道,不限于亲缘关系的进程之间通信。多个进程可以使用相同的命名管道进行通信。
    • 命名管道是基于文件系统的特殊文件,因此它们以文件路径的形式存在。
    • 命名管道也是单向的,但不仅限于父子进程之间。
    • 进程通过打开命名管道的文件路径,可以进行读取或写入操作,实现进程之间的通信。

通信原理概括:

  • 一个进程通过管道的写入端将数据写入管道,这些数据被存储在管道的缓冲区中。
  • 另一个进程通过管道的读取端从缓冲区中读取数据。
  • 管道内部使用缓冲区来协调数据的传输,确保数据能够以适当的速率传输。

需要注意的是,管道是一种有限的缓冲区,如果写入进程产生数据的速度超过读取进程读取数据的速度,那么缓冲区可能会被填满,导致写入进程被阻塞,直到缓冲区中的数据被读取。因此,在使用管道时需要小心处理缓冲区满的情况,以避免阻塞。

总之,管道是一种简单而有效的进程间通信方式,适用于特定场景,特别是在亲缘关系的进程之间或需要单向通信的情况下。

三、消息队列(Message Queues)

消息队列(Message Queues)是一种进程间通信(IPC)机制,用于不同进程之间的数据交换。消息队列允许一个进程向队列发送消息,而另一个进程可以从队列中接收消息。这是一种异步通信方式,允许进程在不需要直接通信的情况下发送和接收数据。

以下是消息队列的一些关键特点和工作原理:

  1. 消息队列的特点

    • 异步通信:发送和接收进程不需要同时在线或等待对方的响应。消息被放入队列后,接收进程可以在合适的时间接收它们。
    • 松耦合:发送者和接收者之间不需要直接知道对方的存在。它们只需要知道消息队列的名称或标识。
    • 队列:消息被存储在队列中,按照先进先出(FIFO)的顺序发送给接收者。多个消息可以被依次处理。
    • 可靠性:通常,消息队列提供一定的可靠性,确保消息被传递和处理,即使接收进程暂时不可用。
  2. 消息队列的工作原理

    • 创建队列:在消息队列系统中,通常会有一个系统级或应用级的组件来创建消息队列,然后为队列分配一个唯一的名称或标识。
    • 发送消息:发送进程将消息封装在数据结构中,包括消息内容和目标队列的标识,然后将消息发送到队列。
    • 接收消息:接收进程通过指定队列标识来接收消息。如果队列中有消息,它可以获取消息并处理它们。
    • 处理消息:接收进程处理接收到的消息,可以根据消息内容执行相应的操作。
  3. 使用场景

    • 消息队列广泛应用于分布式系统、异步通信、解耦系统组件、以及在大规模系统中传递数据的情况。它们在处理任务调度、事件处理、日志传输、通知和数据传递等方面非常有用。
  4. 常见消息队列系统

    • RabbitMQ:一种开源消息代理,实现了高级消息队列协议(AMQP)。
    • Apache Kafka:用于流式数据和事件处理的分布式消息系统。
    • ActiveMQ:一个Apache项目,提供了Java消息服务(JMS)规范的实现。
    • Amazon SQS:Amazon Web Services(AWS)提供的托管消息队列服务。
    • Redis:虽然通常用作缓存,但也可用作消息队列,支持发布/订阅模式。

消息队列是一种强大的工具,有助于构建高度可伸缩、松耦合的分布式系统,并处理异步通信需求。不同的消息队列系统具有不同的特性和适用性,因此根据具体需求选择适当的消息队列系统是重要的。

ftok函数

ftok 是一个函数,通常用于在Unix和Linux系统中创建一个与文件关联的唯一键,以便在进程间共享资源的时候使用。它是一个用于生成 System V IPC(Inter-Process Communication)对象(如共享内存、信号量和消息队列)的键的函数。这个键可以用于标识和访问特定的IPC对象,以便进程能够协同工作。

ftok 函数的声明如下:

key_t ftok(const char *pathname, int proj_id);
  • pathname:这是一个指向文件路径的字符串,通常是已存在的文件。ftok 函数将使用这个文件的信息来生成一个唯一的键。这个文件必须存在,否则 ftok 将返回 -1。
  • proj_id:这是一个整数值,用于进一步区分不同的IPC对象。通常,不同的IPC对象在同一个文件上使用不同的 proj_id

ftok 函数的工作原理如下:

  1. ftok 根据指定的文件路径和 proj_id 生成一个唯一的键值。
  2. 它使用文件的设备号(st_dev)和 inode 号(st_ino)以及 proj_id 来生成这个键值。
  3. 如果多个进程使用相同的文件路径和 proj_id 调用 ftok,它们将获得相同的键值,以便它们能够访问相同的IPC对象。
  4. 进程使用生成的键值来访问或创建System V IPC对象。

需要注意的是,ftok 生成的键值是唯一的,但也可能受到限制。因为它使用文件的设备号和 inode 号,所以在不同文件系统上或不同的设备上,相同的文件路径可能生成不同的键值。此外,文件系统的限制和文件存在性都会影响 ftok 的可用性。

虽然 ftok 仍然在某些传统的Unix系统上使用,但在现代系统中,更多的人转向使用其他IPC机制,如 POSIX IPC(使用 shm_opensem_openmq_open 等函数)或使用更高级的IPC库,如ZeroMQ、RabbitMQ或Apache Kafka。这些IPC机制通常更具可移植性和弹性,并提供更多的功能和性能。

四、共享内存(Shared Memory)

共享内存是一种用于进程间通信(IPC)的机制,它允许不同的进程在同一块物理内存区域中共享数据。这种共享内存区域可以在多个进程之间共享,并且通常用于提高进程间通信的效率,因为数据不需要在进程之间复制,而是直接在内存中共享。

以下是一些关键概念和特点关于共享内存:

  1. 创建共享内存:要使用共享内存,首先需要创建一个共享内存区域。这通常通过系统调用或库函数来实现,如 shmget(在Unix/Linux系统中)或 CreateFileMapping(在Windows系统中)。

  2. 连接共享内存:进程需要连接到已创建的共享内存区域,以便访问其中的数据。这通常通过 shmat(在Unix/Linux系统中)或 MapViewOfFile(在Windows系统中)等函数来实现。

  3. 数据访问:一旦连接到共享内存,进程可以像访问常规内存一样直接读取和写入共享内存区域中的数据。

  4. 同步和互斥:由于多个进程可以同时访问共享内存,因此需要实施适当的同步和互斥机制,以防止竞态条件和数据不一致性。常见的同步机制包括信号量(semaphores)和互斥锁(mutexes)。

  5. 分离和删除:当不再需要访问共享内存时,进程应该分离(detach)它,以释放资源。当最后一个连接到共享内存的进程分离时,共享内存区域可以被删除,以释放内存资源。

  6. 适用性:共享内存通常用于需要高性能数据共享的应用,如数据库管理系统、图形处理、多进程之间的大规模数据共享等。它可以提高数据访问的效率,因为数据不需要在进程之间复制,而且通常比其他IPC机制更快。

  7. 安全性和稳定性:由于共享内存涉及多个进程共享相同的内存区域,因此需要特别小心处理数据的同步和互斥问题,以确保数据的完整性和安全性。

需要注意的是,共享内存通常用于本地进程间通信,不适用于远程通信。对于跨网络的通信,其他IPC机制,如套接字(sockets)和消息队列(message queues),通常更为适用。

在进程间通信(IPC)中,共享内存是一种用于实现多个进程之间数据共享的机制。通过使用共享内存,多个进程可以访问和修改相同的内存区域,从而实现高效的数据传输和协作。以下是关于共享内存的一些关键信息:

  1. 共享内存的特点

    • 高效:共享内存通常是IPC中最快的方式,因为数据直接存储在物理内存中,进程可以直接读取和写入,而无需复制数据。
    • 难以管理:由于多个进程可以访问相同的内存区域,因此需要特别小心处理同步和互斥问题,以避免竞态条件和数据不一致性。
    • 无结构:共享内存本身没有任何结构,只是一块内存区域。进程必须约定如何在共享内存中组织和访问数据。
    • 不适用于网络通信:共享内存通常仅适用于运行在同一台计算机上的进程间通信,不适用于不同计算机上的通信。
  2. 使用共享内存的步骤

    • 创建共享内存区域:一个进程创建一个共享内存区域,通常使用系统调用(如shmgetmmap)来分配一块内存。
    • 连接到共享内存:其他进程可以使用相同的标识符连接到已创建的共享内存区域。
    • 读写数据:多个进程可以在共享内存中读取和写入数据。
    • 分离和删除:进程在不再需要访问共享内存时,需要分离(detach)它,以防止内存泄漏。当不再有进程连接到共享内存时,可以删除它。
  3. 同步和互斥:由于多个进程可以同时访问共享内存,因此需要实现适当的同步和互斥机制,以防止竞态条件和数据不一致性。常见的同步机制包括信号量(semaphores)和互斥锁(mutexes)。

  4. 安全性和稳定性:共享内存在实现上要求高度小心,因为它容易引发安全漏洞和稳定性问题。因此,在使用共享内存时,需要非常谨慎,以确保进程之间的数据共享是安全的。

  5. 适用场景:共享内存通常用于需要高性能数据共享的应用,例如图形处理、数据库管理系统、并行计算和多进程之间的大规模数据共享。共享内存可以帮助避免数据复制和数据传输的开销,提高应用程序的性能。

总之,共享内存是进程间通信中的一种强大机制,适用于需要高性能数据共享的情况。然而,它需要处理同步和互斥问题,以确保数据的完整性和安全性。在选择IPC方法时,需要根据具体的需求和应用场景来考虑是否使用共享内存。

共享内存的通信原理

共享内存是System V版本的最后一个进程间通信方式。共享内存,顾名思义就是允许两个不相关的进程访问同一个逻辑内存,共享内存是两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常为同一段物理内存。进程可以将同一段物理内存连接到他们自己的地址空间中,所有的进程都可以访问共享内存中的地址。如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。

特别提醒:共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取,所以我们通常需要用其他的机制来同步对共享内存的访问,例如信号量。

下面就 Shared Memory 的IPC作以阐述与分析。

共享内存的通信原理
在Linux中,每个进程都有属于自己的进程控制块(PCB)和地址空间(Addr Space),并且都有一个与之对应的页表,负责将进程的虚拟地址与物理地址进行映射,通过内存管理单元(MMU)进行管理。两个不同的虚拟地址通过页表映射到物理空间的同一区域,它们所指向的这块区域即共享内存。

共享内存的通信原理示意图:
在这里插入图片描述

对于上图我的理解是:当两个进程通过页表将虚拟地址映射到物理地址时,在物理地址中有一块共同的内存区,即共享内存,这块内存可以被两个进程同时看到。这样当一个进程进行写操作,另一个进程读操作就可以实现进程间通信。但是,我们要确保一个进程在写的时候不能被读,因此我们使用信号量来实现同步与互斥。

对于一个共享内存,实现采用的是引用计数的原理,当进程脱离共享存储区后,计数器减一,挂架成功时,计数器加一,只有当计数器变为零时,才能被删除。当进程终止时,它所附加的共享存储区都会自动脱离。

为什么共享内存速度最快?

借助上图说明:Proc A 进程给内存中写数据, Proc B 进程从内存中读取数据,在此期间一共发生了两次复制

(1)Proc A 到共享内存 (2)共享内存到 Proc B

因为直接在内存上操作,所以共享内存的速度也就提高了。

信号(Signals)和信号量(Semaphores)

IPC(Inter-Process Communication,进程间通信)中的信号和信号量是两种不同的通信机制,用于在不同的进程之间进行通信和同步操作。它们具有不同的特点和应用场景:

  1. 信号(Signals)

    • 信号是一种轻量级的通信机制,用于通知接收进程发生了某个事件。通常,信号用于异步通信,即发送信号的进程不需要等待接收进程的响应。
    • 信号是一种中断机制,允许一个进程向另一个进程发送信号,如SIGTERM(终止进程)或SIGINT(中断进程)。
    • 信号可以用于处理外部事件,例如用户按下CTRL+C键,通知进程停止运行。进程可以注册信号处理函数来捕获信号并采取相应的操作。
    • 信号在实现简单的通信和进程控制时非常有用,但通常不用于在进程之间传输大量数据。
  2. 信号量(Semaphores)

    • 信号量是一种更复杂的同步机制,用于控制多个进程对共享资源的访问。信号量通常用于实现进程同步和互斥,以防止竞态条件。
    • 信号量可以具有整数值,用于表示可用资源的数量。进程可以通过执行原子操作来增加或减少信号量的值。
    • 进程可以等待信号量,如果信号量的值为零,则进程会被阻塞,直到信号量的值变为正数。这可以用于解决生产者-消费者问题等场景。
    • 信号量还可以用于限制对共享资源的访问,以确保多个进程之间的互斥。

总结:
信号是一种轻量级的通信机制,通常用于通知进程发生事件,而信号量是一种同步机制,用于控制多个进程之间的资源访问。信号适用于处理外部事件和基本的通信需求,而信号量适用于解决复杂的同步和互斥问题,特别是在多进程共享资源的情况下。不同的IPC机制应根据具体需求和应用场景来选择。

五、信号(Signals)

信号是一种轻量级的进程间通信机制,用于通知进程发生了特定事件。它通常用于异步通信,使一个进程可以向另一个进程发送信号,而不需要等待接收进程的响应。在Unix和Linux系统中,信号是常见的,用于处理各种情况,例如进程终止、外部事件(如按下CTRL+C键中断进程)和其他通知。

以下是对信号的详细介绍:

  1. 信号类型

    • 每个信号都由一个唯一的整数标识符表示,通常以SIG开头,如SIGINT表示中断信号。不同的信号有不同的含义和用途,例如SIGTERM(终止信号)、SIGKILL(强制终止信号)、SIGCHLD(子进程状态改变信号)等。
  2. 信号产生

    • 信号通常由操作系统或其他进程生成。例如,用户按下CTRL+C键时,终端会生成SIGINT信号发送给当前运行的进程。
    • 还可以使用系统调用如kill来向特定进程发送信号。
  3. 信号处理

    • 进程可以注册信号处理函数(信号处理器),以定义信号到来时要执行的操作。信号处理函数通常是用户自定义的函数,用于捕获和处理信号。
    • 信号处理函数的注册通常使用signal函数或更现代的sigaction函数来完成。不同的信号可以有不同的处理函数。
  4. 信号处理方式

    • 信号处理可以采用三种方式:忽略、捕获并处理、或采用默认处理方式。进程可以根据自己的需要来定义每种信号的处理方式。
    • 通常,一些信号(如SIGKILL)不能被忽略或捕获,只能使用默认的处理方式。
  5. 信号的用途

    • 信号常用于以下情况:
      • 中断进程:用户可以按下CTRL+C键来发送SIGINT信号,终止正在运行的进程。
      • 进程状态通知:子进程终止时,父进程会收到SIGCHLD信号,以通知子进程的状态改变。
      • 外部事件处理:例如,SIGHUP信号通常用于通知进程重新加载配置文件。
      • 定时任务:SIGALRM信号可用于定时触发操作。
      • 自定义事件通知:开发人员可以自定义信号来处理应用程序内部的事件。
  6. 信号的限制

    • 有些信号是不可捕获或忽略的,如SIGKILL,因为它们通常用于强制终止进程。
    • 进程可以通过ulimit命令或setrlimit函数来限制某些信号的资源使用。

总的来说,信号是一种在Unix和Linux系统中用于处理进程间通信和事件通知的强大机制。它允许进程以异步方式通信,并根据需要执行自定义操作。信号在操作系统和应用程序开发中都具有广泛的应用。

六、信号量(Semaphores)

信号量是一种进程间通信(IPC)机制,用于控制多个进程对共享资源的访问,以确保数据的同步和互斥。信号量的主要目的是解决多个进程之间的竞争条件,以避免并发访问共享资源时发生冲突。以下是对信号量的详细介绍:

  1. 信号量的类型

    • 二进制信号量:二进制信号量的值只能为0或1。通常用于实现互斥,表示资源是否已被占用。0表示资源已被占用,1表示资源可用。
    • 计数信号量:计数信号量的值可以是任意非负整数,表示资源的可用数量。它通常用于控制多个进程之间的资源共享。
  2. 信号量的操作

    • 创建信号量:信号量通过系统调用(如 semget)或库函数来创建。创建信号量时需要指定初始值。
    • 初始化信号量:信号量可以在创建后通过 semctl 进行初始化,设置初始值。
    • 增加信号量的值:通常使用 semop 系统调用来增加信号量的值,这被称为 V 操作(原语操作)。
    • 减少信号量的值:使用 semop 来减少信号量的值,这被称为 P 操作(等待操作)。如果信号量的值为0,进程将被阻塞,直到信号量的值大于0。
    • 删除信号量:信号量可以通过 semctl 删除,释放相关资源。
  3. 应用场景

    • 信号量通常用于解决生产者-消费者问题、读者-写者问题和多进程对共享资源的访问问题。例如,多个进程需要访问一个共享内存区域或文件,信号量可以用来控制访问该资源的进程数量。
    • 在生产者-消费者问题中,生产者进程会增加信号量的值,表示生产了一个新的资源;消费者进程会减少信号量的值,表示消费了一个资源。
  4. 信号量的同步和互斥

    • 信号量用于实现进程之间的同步和互斥。通过 P 操作来锁定资源,防止多个进程同时访问它,通过 V 操作来释放资源。
    • 信号量还可以用于等待和通知机制,一个进程等待另一个进程释放资源时,可以使用信号量来协调。
  5. 信号量的限制

    • 信号量需要小心处理,以避免死锁等问题。正确的信号量使用需要良好的设计和编程实践。
    • 进程需要遵守信号量的规则,确保不会破坏信号量的状态或引发竞争条件。

总结,信号量是一种强大的IPC机制,用于处理多进程之间的同步和互斥问题。它在多线程编程、多进程编程和并发编程中都非常有用,可确保共享资源的安全访问和协调。然而,使用信号量需要小心处理,以避免潜在的问题。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咖喱年糕

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值