进程间通信

管道

即匿名管道,半双工,只能在具有公共祖先的两个进程间使用。fd[0]为读而打开,fd[1]为写而打开。fd[1]的输出是fd[0]输入。

尽管有局限性,半双工管道仍然是最常用的IPC形式。每当在管道中键入一个命令序列,让shell执行时,shell都会为每个命令单独创建一个进程,然后用管道将前一条命令进程的标准输出与后一条命令的标准输入相连接。

当有多个进程同时写一个管道(或FIFO)时,常量PIPE_BUF说明了可被原子地写到FIFO的最大数据量。
在这里插入图片描述

在这里插入图片描述

一般用于父子进程之间通信:

  • 对于父进程到子进程的管道,父进程关闭读,子进程关闭写
  • 对于子进程到父进程的管道,父进程关闭写,子进程关闭读

当管道的一端被关闭时:
(1)当读一个写端已经被关闭的管道时,在所有数据都被读取时,read返回0,表示文件结束
(2)当写一个读端已被关闭的管道,则产生信号SIGPIPE

函数popen和pclose

#include <stdio.h>
FIFE* popen(const cahr* cmdstring, const char* type);
返回值:若成功,返回文件指针;若出错,返回NULL
int pclose(FIFE* fp)
返回值:若返回cmdstring终止状态;若出错,返回-1

这两个函数实现的操作是:创建一个管道,fork一个子进程,关闭未使用的管道端,执行一个shell命令,然后等待命令终止。

popen:执行fork,然后调用exec执行cmdstring,并返回一个标准I/O文件指针。

  • type是"r",表示将命令产生的标准输出,并与I/O文件指针相关联。程序中可以读取该I/O文件指针中的数据流。
  • type是"w",表示从标准输入中读取输入,标准输入与I/O文件指针相关联。程序中可以向该I/O文件指针中写入数据流。
//管道读
//先创建一个文件test,然后再test文件内写入“Read pipe successfully !”
    #include <stdio.h>
  #include <stdlib.h>
  int main()
  {
  FILE *fp;
  char buf[200] = {0};
  if((fp = popen(“cat test”, “r”)) == NULL) {
  perror(“Fail to popen\n”);
  exit(1);
  }
  while(fgets(buf, 200, fp) != NULL) {
  printf(“%s”, buf);
  }
  pclose(fp);
  return 0;
  }
//打印输出: Read pipe successfully !

//管道写:
  #include “stdio.h”
  #include “stdlib.h”
  int main()
  {
  FILE *fp;
  char buf[200] = {0};
  if((fp = popen(“cat > test1″, “w”)) == NULL) {
  perror(“Fail to popen\n”);
  exit(1);
  }
  fwrite(“Read pipe successfully !”, 1, sizeof(“Read pipe successfully !”), fp);
  pclose(fp);
  return 0;
  }
 //执行完毕后,当前目录下多了一个test1文件,打开,里面内容为Read pipe successfully !

命名管道

通过FIFO,不相关的进程也能交换数据。创建FIFO类似于创建文件,也有mode参数。

int mkfifo(const char *filename,mode_t mode);

mode参数指定文件的读写权限

open一个FIFO时,非阻塞标志的影响:

  • 默认情况下,为阻塞。一个进程读模式打开FIFO的时候,必须有另一进程以写模式打开;或者是写模式打开FIFO,必须有另一进程以读模式打开,否则open会阻塞直至满足以上关系。
  • 指定了O_NOBLOCK。则只读open立即返回。若没有其它进程为读而打开一个FIFO,那么只写open将返回-1,设置errno。并且FIFO不会被打开。

用途:

  • shell命令使用FIFO将数据通过管道传输,无需创建临时文件。(可用于实现非线性连接)
  • 在客户端与服务端之间传送数据

SystemV IPC 与 Posix IPC

XSI IPC包含三种IPC:消息队列,信号量,共享内存区

System V,是Unix操作系统众多版本中的一支(一方诸侯)。POSIX是由IEEE 和ISO/IEC 开发的一簇标准。该标准用于保证编制的应用程序可以在源代码一级上在多种操作系统上移植运行。

  • 在POSIX IPC中,每个IPC对象是有名称的(const char*),POSIX IPC使用ipc的名称作为ipc的标识。这个名称不一定是在文件系统中存在的名称。要使用IPC对象,需要创建或者打开,并指定操作的mode。
  • 在SystemV IPC中,SystemV IPC中有一个重要的类型是key_t,每个IPC对象都与一个键相关联。并且系统自动为每个IPC关联了一个ipc_perm结构,用于表示其权限和所有者。可以使用函数ftok将一个已存在的路径名和一个整数标识符(0~255)转换成一个key_t值。
  • Posix函数有下划线分隔,SystemV函数没有,是连在一起的

SystemV IPC的问题:

  • IPC结构是在系统范围内起作用的,没有引用计数。至少是随内核持续的,即IPC对象一直存在到内核重新自举或显式删除该对象为止 。POSIX IPC也可以是随文件系统持续的,即IPC对象一直存在到显式删除该对象为止。即使内核重新自举了,该对象还是保持其值。
  • IPC结构在文件系统中没有名字。系统为其增加了新的函数来操作其属性和访问权限
  • 不使用文件描述符,所以不能对它们直接使用IO多路复用函数。

消息队列

消息队列可认为是一个消息链表。有足够写权限的线程可往队列中放置消息,有足够读权限的线程可从队列中取走消息。每个消息是一个记录,类似UDP数据报,它由发送者赋予一个优先级。
与管道和FIFO的不同之处:

  • 在某个进程往一个队列写入消息之前,并不需要另外某个进程在该队列上等待消息的到达。写入一个没有读出者的管道,是没有意义的。
  • 消息队列具有随内核的持续性。而当一个管道或FIFO的最后一次关闭发生时,仍在该管道或FIFO上的数据将被丢弃,即随进程的持续性。
  • 每个消息包含优先级或类型、消息的数据部分长度和数据本身。而管道和FIFO是字节流模型,没有消息边界,也没有与每个消息关联的类型。

POSIX消息队列与SystemV 消息队列的差别:

  • 对POSIX消息队列的读总是返回最高优先级的最早消息,对SystemV消息队列的读则可以返回任意指定优先级的消息。
  • 当往一个空队列放置一个消息时,POSIX消息队列允许产生一个信号或启动一个线程,SystemV消息队列则不提供类似机制。

信号量

信号量是一个计数器,信号量有三种类型:

  • POSIX有名信号量
  • POSIX基于内存的信号量(无名信号量)
  • SystemV 信号量:在内核中维护。

POSIX信号量不必在内核中维护,是由可能与文件系统中的路径名对应的名字来标识的。

信号量、互斥锁和条件变量之间的差异:

  • 互斥锁必须总是由给它上锁的线程解锁,信号量的挂出(post,++sem)却不必由执行过它的等待操作的同一线程执行。
  • 互斥锁具有二值性,要么被锁住,要么被解开
  • 既然信号量有一个与之关联的状态(它的计数值),那么信号量挂出操作总是被记住。也就是说,在调用sem_wait之前调用了sem_post,信号量的值也会加1。然而当向一个条件变量发送信号时,如果没有线程等待在该条件变量上,那么该信号将丢失。

有名信号量与基于内存的信号量(无名信号量)使用的函数:
在这里插入图片描述
POSIX有名信号量。这些信号量由一个name参数(const char*)识别,它通常指代文件系统中的某个文件。而POSIX基于内存的信号量,由应用程序分配信号量的内存空间。该信号量本身必须驻留在由所有希望共享它的进程所共享的内存区中。

int sem_init(sem_t *sem,int pshared,unsigned int value);
若出错,返回-1;
  • 若shared为0,那么信号量是在同一进程的各个线程间共享的。
  • 否则该信号量是在进程间共享的。当shared非零时,该信号量必须存放在某种类型的共享内存中,而即将使用它的所有进程都要能访问该共享内存区。

当不需要使用与有名信号量关联的名字时,可使用基于内存的信号量。彼此无血缘关系的不同进程需使用信号量时,通常使用有名信号量。

POSIX有名信号量至少是随内核持续的,即使当前没有进程打开着某个信号量,它的值仍然保持。

基于内存的信号量至少具有随进程的持续性,然而它们真正的持续性却取决于存放信号量的内存区的类型。只要含有某个内存信号量的内存区保持有效,该信号量就一直存在。

  • 若基于内存的信号是由单个进程内的各个线程共享的,那么该信号量具有随进程的持续性
  • 若是在不同进程间共享的,只要该共享内存区存在,该信号量就存在。

SystemV 信号量通过定义计数信号量集给信号量增加了另外一层复杂度。除了维护一个信号量集内每个信号的实际值之外,内核还给该集合中每个信号量维护另外三个信息:

  • 对其值执行最后一次操作的进程的进程ID
  • 等待其值增长的进程数计数
  • 等待其值变为0的进程数计数

共享内存

共享内存区是可用IPC形式中最快的。
一个传递各种消息的客户-服务器文件复制程序中涉及的通常步骤:

  • 服务从输入文件中读。该文件的数据由内核读入自己的内存空间,然后从内核复制到服务器进程。(文件–>内核–>服务器进程)
  • 服务器往一个管道、FIFO或消息队列中以一条消息的形式写入这些数据。这些IPC形式通常需要把这些数据从进程复制到内核。(服务器进程—>(IPC)内核)
  • 客户从该IPC通道读出这些数据,这通常需要把消息从内核复制到客户进程中。(IPC(内核)–>客户进程)
  • 将这些数据从write函数的第二个参数指定的客户缓冲区复制到输出文件。(客户缓冲区–>文件)

总共有四次数据复制,而且这四次复制是在内核和某个进程间进行的,往往开销很大。
在这里插入图片描述

这些IPC形式(管道、FIFO和消息队列)的问题在于,两个进程要交换信息时,这些信息必须经由内核传递。通过让两个或多个进程共享一个内存区,可以绕过上述问题。当然,这些进程必须协调或同步地对该共享内存区的使用。

这时,客户–服务器文件复制流程为:

  • 服务器使用(譬如说)一个信号量取得访问某个共享内存区对象的权力。
  • 服务器将数据从输入文件读入到该共享内存区对象。read函数的第二个参数所指定的缓冲区地址指向这个共享内存区。
  • 服务器读入完毕,使用一个信号量通知客户。
  • 客户将这些数据从共享内存区写入输出文件中。

在这里插入图片描述

图中数据中复制了两次,注意,该共享内存区需同时出现在客户和服务器的地址空间中。

mmap函数

mmap函数把一个文件或一个POSIX共享内存区对象映射到调用进程的地址空间中。使用该函数的目的:

  • 使用普通文件以提供内存映射IO
  • 使用特殊文件以提供匿名内存映射(4.4BSD匿名内存映射,设置MAP_SHARED | MAP_ANON; /dev/zero设备文件 )
  • 使用shm_open以提供无亲缘关系进程间的Posix共享内存区
    在这里插入图片描述
    终端和套接字的描述符不能进行内存映射。使用mmap在无亲缘关系的进程间提供共享的内存区,需指定MAP_SHARED标志,使得进程对该共享内存区的任何变动都复制回所映射的文件,以提供随文件系统的持续性。同时,若指定MAP_SHARED标志,则父进程和子进程将共享该内存区。mmap成功返回后,fd参数可以关闭,对mmap的映射关系没有影响。

Posix共享内存区

mmap提供父子间的共享内存区方法:

  • 使用内存映射文件
  • 使用4.4BSD匿名内存映射
  • 使用/dev/zero匿名内存映射

Posix提供的无亲缘关系进程间共享内存区的方法:

  • 内存映射文件也可以在无亲缘关系的进程间共享。由open函数打开文件,由mmap函数把得到的描述符映射到当前进程地址空间中的一个文件。
  • 共享内存区对象。由shm_open函数打开一个名字(名字的格式为/somename,只能有一个flash),所返回的描述符由mmap函数映射到当前进程的地址空间。(该共享区对象存储在/dev/shm中)
    在这里插入图片描述

SystemV 共享内存区

代之以调用shm_open后调用mmap的是,SystemV共享内存区是先调用shmget,再调用shmat。

共享内存的同步、互斥

  • 对于多个进程共享的共享内存来说,惟一可靠的互斥机制 就是带SEM_UNDO的system V信号量。当操作信号量(semop)时,sem_flg可以设置SEM_UNDO标识;SEM_UNDO用于将修改的信号量值在进程正常退出(调用exit退出或main执行完)或异常退出(如段异常、除0异常、收到KILL信号等)时归还给信号量。
    原因:某一个进程在持有锁期间意外退出,所持有的锁还没有来得及释放,这会造成所有等待这个锁的进程/线程死锁。所以不建议使用POSIX 信号量或者互斥锁。(若使用二值匿名信号量,在初始化该信号时,设置为进程间共享,这样当多个进程访问该共享内存时, 可使用该信号量解决访问冲突)
  • 对于同步机制,可以使用POSIX匿名信号量 。
  • 对于异步通知机制,可以使用 FIFO 或者eventfd(>since Linux 2.6.27)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值