📜  进程间通信 (IPC)

📅  最后修改于: 2021-09-28 10:10:44             🧑  作者: Mango

一个进程可以有两种类型:

  • 独立进程。
  • 合作过程。

独立进程不受其他进程执行的影响,而协作进程可能受其他正在执行的进程影响。虽然人们可以认为这些独立运行的进程会非常高效地执行,但实际上,在许多情况下,可以利用合作性质来提高计算速度、便利性和模块化。进程间通信 (IPC) 是一种允许进程相互通信并同步其操作的机制。这些进程之间的通信可以看作是它们之间合作的一种方法。进程可以通过以下两种方式相互通信:

  1. 共享内存
  2. 消息传递

下面的图 1 显示了通过共享内存方法和通过消息传递方法的进程间通信的基本结构。

操作系统可以实现这两种通信方法。首先,我们将讨论通信的共享内存方法,然后是消息传递。使用共享内存的进程之间的通信需要进程共享一些变量,这完全取决于程序员将如何实现它。一种使用共享内存的通信方式可以这样想象:假设 process1 和 process2 同时执行,并且它们共享一些资源或使用来自另一个进程的一些信息。 Process1 生成有关正在使用的某些计算或资源的信息,并将其作为记录保存在共享内存中。当 process2 需要使用共享信息时,它会检查共享内存中存储的记录,并注意 process1 生成的信息并采取相应的行动。进程可以使用共享内存从另一个进程中提取信息作为记录,以及将任何特定信息传递给其他进程。
让我们讨论一个使用共享内存方法在进程之间进行通信的示例。

i) 共享内存方法

例如:生产者-消费者问题
有两个进程:生产者和消费者。生产者生产一些物品,消费者消费该物品。这两个进程共享一个称为缓冲区的公共空间或内存位置,生产者生产的项目存储在该缓冲区中,消费者在需要时从中消费项目。这个问题有两个版本:第一个被称为无界缓冲区问题,其中生产者可以继续生产项目并且缓冲区的大小没有限制,第二个被称为有界缓冲区问题Producer 在开始等待 Consumer 消费它之前可以生产最多一定数量的项目。我们将讨论有界缓冲区问题。首先,生产者和消费者将共享一些共同的内存,然后生产者将开始生产项目。如果生产的项目总数等于缓冲区的大小,生产者将等待消费者消费它。同样,消费者将首先检查物品的可用性。如果没有可用的项目,消费者将等待生产者生产它。如果有可用的物品,消费者将消费它们。下面提供了用于演示的伪代码:
两个进程之间共享数据

C
#define buff_max 25
#define mod %
 
    struct item{
 
        // different member of the produced data
        // or consumed data   
        ---------
    }
     
    // An array is needed for holding the items.
    // This is the shared place which will be 
    // access by both process  
    // item shared_buff [ buff_max ];
      
    // Two variables which will keep track of
    // the indexes of the items produced by producer
    // and consumer The free index points to
    // the next free index. The full index points to
    // the first full index.
    int free_index = 0;
    int full_index = 0;


C
item nextProduced;
     
    while(1){
         
        // check if there is no space
        // for production.
        // if so keep waiting.
        while((free_index+1) mod buff_max == full_index);
         
        shared_buff[free_index] = nextProduced;
        free_index = (free_index + 1) mod buff_max;
    }


C
item nextConsumed;
     
    while(1){
         
        // check if there is an available
        // item  for consumption.
        // if not keep on waiting for
        // get them produced.
        while((free_index == full_index);
         
        nextConsumed = shared_buff[full_index];
        full_index = (full_index + 1) mod buff_max;
    }


C
void Producer(void){
         
        int item;
        Message m;
         
        while(1){
             
            receive(Consumer, &m);
            item = produce();
            build_message(&m , item ) ;
            send(Consumer, &m);
        }
    }


C
void Consumer(void){
         
        int item;
        Message m;
         
        while(1){
             
            receive(Producer, &m);
            item = extracted_item();
            send(Producer, &m);
            consume_item(item);
        }
    }


生产者进程代码

C

item nextProduced;
     
    while(1){
         
        // check if there is no space
        // for production.
        // if so keep waiting.
        while((free_index+1) mod buff_max == full_index);
         
        shared_buff[free_index] = nextProduced;
        free_index = (free_index + 1) mod buff_max;
    }

消费者流程代码

C

item nextConsumed;
     
    while(1){
         
        // check if there is an available
        // item  for consumption.
        // if not keep on waiting for
        // get them produced.
        while((free_index == full_index);
         
        nextConsumed = shared_buff[full_index];
        full_index = (full_index + 1) mod buff_max;
    }

在上面的代码中,当 (free_index+1) mod buff max 空闲时,Producer 将再次开始生产,因为如果它不空闲,这意味着仍然有项目可以被 Consumer 消费,因此没有必要生产更多。同样,如果空闲索引和完整索引指向同一个索引,这意味着没有要消费的项目。

ii) 消息传递方法

现在,我们将开始讨论进程间通过消息传递的通信。在这种方法中,进程相互通信而不使用任何类型的共享内存。如果两个进程 p1 和 p2 想要相互通信,它们的处理如下:

  • 建立通讯链接(如果链接已经存在,无需重新建立。)
  • 开始使用基本原语交换消息。
    我们至少需要两个原语:
    发送(消息,目的地)或发送(消息)
    接收(消息,主机)或接收(消息)

消息大小可以是固定大小或可变大小。如果它是固定大小的,对于操作系统设计者来说很容易,但对于程序员来说很复杂;如果它是可变大小的,那么对于程序员来说很容易,但对于操作系统设计者来说却很复杂。标准消息可以有两部分:标题和正文。
头部分用于存储消息类型、目的 id、源 id、消息长度和控制信息。控制信息包含诸如缓冲区空间不足时如何处理、序列号、优先级等信息。通常,消息使用 FIFO 样式发送。

消息通过通信链路传递。
直接和间接通信链接
现在,我们将开始讨论实现通信链接的方法。在实现链接时,需要记住一些问题,例如:

  1. 链接是如何建立的?
  2. 一个链接可以与两个以上的进程相关联吗?
  3. 每对通信进程之间可以有多少个链接?
  4. 链路的容量是多少?链接可以容纳的消息大小是固定的还是可变的?
  5. 链路是单向的还是双向的?

一个链接有一定的容量,它决定了可以临时驻留在其中的消息数量,每个链接都有一个与之关联的队列,该队列可以是零容量、有界容量或无界容量。在零容量情况下,发送方会等待,直到接收方通知发送方它已收到消息。在非零容量情况下,进程不知道在发送操作之后是否已收到消息。为此,发送方必须明确地与接收方通信。链路的实现视情况而定,它可以是直接通信链路,也可以是间接通信链路。
当进程使用特定的进程标识符进行通信时,会实现直接通信链接,但很难提前识别发送者。
例如打印服务器。
间接通信是通过共享邮箱(端口)完成的,该邮箱由消息队列组成。发件人将邮件保存在邮箱中,收件人将其取走。

消息通过交换消息。

同步和异步消息传递:
被阻塞的进程是一个正在等待某个事件的进程,例如资源变得可用或 I/O 操作的完成。 IPC 可以在同一台计算机上的进程之间以及在不同计算机上运行的进程之间进行,即在网络/分布式系统中。在这两种情况下,进程在发送消息或尝试接收消息时可能会或可能不会被阻塞,因此消息传递可能是阻塞的或非阻塞的。阻塞被认为是同步的阻塞发送意味着发送方将被阻塞,直到接收方收到消息。类似地,阻塞接收使接收器阻塞,直到消息可用。非阻塞被认为是异步的,非阻塞发送让发送者发送消息并继续。类似地,非阻塞接收使接收器接收到有效消息或空消息。经过仔细分析,我们可以得出一个结论,对于发送方来说,消息传递后非阻塞更自然,因为可能需要将消息发送到不同的进程。但是,如果发送失败,发送方希望收到接收方的确认。类似地,接收方在发出接收后阻塞更自然,因为来自接收消息的信息可用于进一步执行。同时,如果消息发送不断失败,接收者将不得不无限期地等待。这就是为什么我们还要考虑消息传递的另一种可能性。基本上有三种优选组合:

  • 阻塞发送和阻塞接收
  • 非阻塞发送和非阻塞接收
  • 非阻塞发送和阻塞接收(最常用)

在直接消息传递中,想要通信的进程必须明确命名通信的接收者或发送者。
例如send(p1, message)表示将消息发送到 p1。
同理, receive(p2, message)表示从p2 接收消息。
在这种通信方式中,通信链路是自动建立的,可以是单向的,也可以是双向的,但一对发送方和接收方之间可以使用一条链路,一对发送方和接收方之间不能拥有超过一对。链接。也可以实现发送和接收之间的对称和不对称,即两个进程将相互命名以发送和接收消息,或者只有发送方命名接收方来发送消息,而无需接收方为发送方命名接收消息。这种通信方法的问题在于,如果一个进程的名称发生变化,这种方法将不起作用。
在间接消息传递中,进程使用邮箱(也称为端口)来发送和接收消息。每个邮箱都有一个唯一的 id,进程只有在共享邮箱时才能进行通信。仅当进程共享一个公共邮箱并且单个链接可以与多个进程相关联时才建立链接。每对进程可以共享多个通信链接,这些链接可以是单向的或双向的。假设两个进程想通过间接消息传递进行通信,需要的操作是:创建一个邮箱,使用这个邮箱发送和接收消息,然后销毁这个邮箱。使用的标准原语是: send(A, message)这意味着将消息发送到邮箱 A。接收消息的原语也以相同的方式工作,例如received (A, message) 。这个邮箱实现有问题。假设有两个以上的进程共享同一个邮箱,并且假设进程 p1 向邮箱发送消息,哪个进程将是接收者?这可以通过强制只有两个进程可以共享一个邮箱或强制在给定时间只允许一个进程执行接收或随机选择任何进程并将接收者通知发送者来解决。邮箱可以对单个发送方/接收方对设为私有,也可以在多个发送方/接收方对之间共享。端口是这种邮箱的实现,可以有多个发送者和一个接收者。它用于客户端/服务器应用程序(在这种情况下,服务器是接收器)。该端口由接收进程拥有,并由操作系统根据接收进程的请求创建,并且可以在接收方终止自身时根据同一接收处理器的请求销毁。可以使用互斥的概念来强制只允许一个进程执行接收。互斥邮箱被创建,由 n 个进程共享。发送方是非阻塞的并发送消息。执行接收的第一个进程将进入临界区,所有其他进程将阻塞并等待。
现在,让我们使用消息传递概念来讨论生产者-消费者问题。生产者将项目(内部消息)放入邮箱,当邮箱中至少存在一条消息时,消费者可以消费项目。代码如下:
生产者代码

C

void Producer(void){
         
        int item;
        Message m;
         
        while(1){
             
            receive(Consumer, &m);
            item = produce();
            build_message(&m , item ) ;
            send(Consumer, &m);
        }
    }

消费者代码

C

void Consumer(void){
         
        int item;
        Message m;
         
        while(1){
             
            receive(Producer, &m);
            item = extracted_item();
            send(Producer, &m);
            consume_item(item);
        }
    }

IPC 系统示例

  1. Posix :使用共享内存方法。
  2. Mach:使用消息传递
  3. Windows XP:使用使用本地过程调用的消息传递

客户端/服务器架构中的通信:
有多种机制:

  • 管道
  • 插座
  • 远程过程调用 (RPC)

以上三种方法将在后面的文章中讨论,因为它们都是非常概念化的,值得单独写一篇文章。
参考:

  1. Galvin 等人的操作系统概念。
  2. 巴伊兰大学 Ariel J. Frank 的讲义/ppt