进程管理之进程间通信

文章目录

  • 一、管道(pipe)
  • 二、命名管道(FIFO)
  • 三、信号量(Semaphore)
    • 1、信号量的基本要素
    • 2、信号量的原理
    • 3、使用信号量实现生产者-消费者问题
    • 4、使用信号量实现读者-写者问题
  • 四、消息队列(message queue)
  • 五、共享内存(shared memory)
  • 六、套接字(socket)

  进程作为人类的发明,自然也免不了脱离人类的习性,也有通信的需求。如果进程之间不进行任何通信,那么进程所能完成的任务就要大打折扣。人类的通信方式无外乎对白(通过声音沟通)、打手势、写信、发电报、拥抱等方法。同理,进程也可以通过同样的方式来进行通信。本篇我们就来看看进程的这些交互方式。

一、管道(pipe)

#include 
int pipe(int fd[2]);

  一个进程向存储空间的一端写入信息,另一个进程存储空间的另外一端读取信息,这个就是管道。ls -l | grep string:将ls -l的结果通过管道传递给grep。

  一般管道的使用方式都是:父进程创建一个管道,然后fork产生一个子进程,由于子进程拥有父进程的副本,所以父子进程可以通过管道进行通信。

在这里插入图片描述
  对于从父进程到子进程的管道,父进程关闭读端(fd[0]),子进程关闭写端(fd[1]);

  对于从子进程到父进程的管道,子进程关闭读端(fd[0]),父进程关闭写端(fd[1])。

二、命名管道(FIFO)

#include 
int mkfifo(const char *path, mode_t mode);
int mkfifoat(int fd, const char *path, mode_t mode);


  POSIX标准中的FIFO又名有名管道或命名管道。我们知道前面讲述的POSIX标准中管道是没有名称的,所以它的最大劣势是只能用于具有亲缘关系的进程间的通信。FIFO最大的特性就是每个FIFO都有一个路径名与之相关联,从而允许无亲缘关系的任意两个进程间通过FIFO进行通信。所以,FIFO的两个特性:

  • 和管道一样,FIFO仅提供半双工的数据通信,即只支持单向的数据流;

  • 和管道不同的是,FIFO可以支持任意两个进程间的通信。

三、信号量(Semaphore)

int semget(key_t key, int num_sems, int sem_flags);
int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
int semctl(int sem_id, int sem_num, int command, ...);

1、信号量的基本要素

  • 原子操作:不可中断的一个或者一系列的操作,即一件事要么做要么不做

  • 临界资源:不同进程能够看到的一份公共资源,一次只能被一个进程使用

  • PV操作:由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:

    • P(sv):如果信号量大于 0 ,执行 -1 操作;如果信号量等于 0,进程睡眠,等待信号量大于 0

    • V(sv):对信号量执行 +1 操作,唤醒睡眠的进程让其完成 P操作。

2、信号量的原理

1. 测试控制该资源的信号量;

2. 若信号量的值为正,则进程可以使用该资源,进程的信号量值减1,表示一个资源被使用;

3. 若此信号量为0,则进程进入休眠,直到该信号量值大于0;

4. 当进程不再使用一个由一个信号控制的共享资源时,该信号量加1,如果有进程正在休眠等待该信号量,则该进程会被唤醒。

在这里插入图片描述

3、使用信号量实现生产者-消费者问题

  问题描述:使用一个缓冲区来保存物品,只有缓冲区没有满,生产者才可以放入物品;只有缓冲区不为空,消费者才可以拿走物品

  因为缓冲区属于临界资源,因此需要使用一个互斥量 mutex 来控制对缓冲区的互斥访问。

  为了同步生产者和消费者的行为,需要记录缓冲区中物品的数量。数量可以使用信号量来进行统计,这里需要使用两个信号量:empty 记录空缓冲区的数量,full 记录满缓冲区的数量。其中,empty 信号量是在生产者进程中使用,当 empty 不为 0 时,生产者才可以放入物品;full 信号量是在消费者进程中使用,当 full 信号量不为 0 时,消费者才可以取走物品
进程管理之进程间通信_第1张图片

#define N 100
semaphore mutex = 1;
semaphore empty = N;
semaphore full = 0;

void producer() {
    while(true) {
        P(&empty);
        P(&mutex);
        insert();
        V(&mutex);
        V(&full);
    }
}

void consumer() {
    while(true) {
        P(&full);
        P(&mutex);
        consume();
        V(&mutex);
        V(&empty);
    }
}

4、使用信号量实现读者-写者问题

  问题描述:允许多个进程同时对数据进行读操作,但是不允许读和写以及写和写操作同时发生。

  一个整型变量 count 记录在对数据进行读操作的进程数量,一个互斥量 count_mutex 用于对 count 加锁,一个互斥量 data_mutex 用于对读写的数据加锁
进程管理之进程间通信_第2张图片

semaphore count_mutex = 1;
semaphore data_mutex = 1;
int count = 0;

void reader() {
    while(true) {
        P(&count_mutex);
        count++;
        if(count == 1) P(&data_mutex); // 第一个读者需要对数据进行加锁,防止写进程访问
        V(&count_mutex);
        read();
        P(&count_mutex);
        count--;
        if(count == 0) V(&data_mutex);
        V(&count_mutex);
    }
}

void writer() {
    while(true) {
        P(&data_mutex);
        write();
        V(&data_mutex);
    }
}

四、消息队列(message queue)

int msgget(key_t key, int msgflg);  
int msgrcv(int msqid, void *msg_ptr, size_t msg_sz, long int msgtype, int msgflg);  
int msgsnd(int msqid, const void *msg_ptr, size_t msg_sz, int msgflg);  
int msgctl(int msqid, int cmd, struct msqid_ds *buf);  

  消息队列是一列具有头和尾的队列,新来的消息放在队列尾部,而读取消息则从队列头部开始,如下图所示:
进程管理之进程间通信_第3张图片
  这样看来,它和管道十分类似,一头读,一头写?的确,看起来很像管道,但又不是管道:

  • 消息队列无固定的读写进程,任何进程都可以读写;而管道需要指定谁读和谁写;

  • 消息队列可以同时支持多个进程,多个进程可以读写消息队列;即所谓的多对多,而管道是点对点;

  • 消息队列只在内存中实现,而管道还可以在磁盘上实现。

五、共享内存(shared memory)

int shmget(key_t key, size_t size, int shmflg);
void *shmat(int shm_id, const void *shm_addr, int shmflg);
int shmdt(const void *shmaddr);
int shmctl(int shm_id, int command, struct shmid_ds *buf);

  共享内存就是两个进程共同拥有同一片物理内存。对于这片内存中的任何内容,二者均可以访问。要使用共享内存进行通信,进程A首先需要创建一片内存空间作为通信用,而其他进程B则将片内存映射到自己的(虚拟)地址空间。这样,进程A读写自己地址空间中对应共享内存的区域时,就是在和进程B进行通信。
进程管理之进程间通信_第4张图片

  • 使用共享内存使得进程间的数据不用传送,而是直接访问内存,也加快了程序的效率。

  • 可用于任意两个进程之间通信。

  • 但是两个进程必须在同一台物理机上,而且完全性脆弱

六、套接字(socket)

进程管理之进程间通信_第5张图片
  套接字(Socket)的功能非常强大,可以支持不同层面、不同应用、跨网络的通信使用套接字进行通信需要双方均创建一个套接字,其中一方作为服务器方,另外一方作为客户方。服务器方必须首先创建一个服务区套接字,然后在该套接字上进行监听,等待远方的连接请求。客户方也要创建一个套接字,然后向服务器方发送连接请求。服务器套接字在受到连接请求之后,将在服务器方机器上新建一个客户套接字,与远方的客户方套接字形成点到点的通信通道。之后,客户方和服务器方便可以直接通过类似于send和recv的命令在这个创建的套接字管道上进行交流了。

你可能感兴趣的:(#,操作系统原理,进程间通信)