一、进程间通信

每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication)。如下图所示。

二、管道是一种最基本的IPC机制,由pipe函数创建:

  1. #include <unistd.h>
  2. int pipe(int filedes[2]);

调用pipe函数时在内核中开辟一块缓冲区(称为管道)用于通信,它有一个读端一个写端,然后通过filedes参数传出给用户程序两个文件描述符,filedes[0]指向管道的读端filedes[1]指向管道的写端(很好记,就像0是标准输入1是标准输出一样)。所以管道在用户程序看起来就像一个打开的文件,通过read(filedes[0]);或者write(filedes[1]);向这个文件读写数据其实是在读写内核缓冲区。pipe函数调用成功返回0,调用失败返回-1。

开辟了管道之后如何实现两个进程间的通信呢?比如可以按下面的步骤通信。

管道 是单向的,先进先出的。它把一个进程的输出和另一个进程的输入连接在一起。一个进程(写进程)在管道的尾部写入数据,另一个进程(读进程)从管道的头部读出数据。数据被一个进程读出后,将被从管道中删除,其他读进程将不能再读到这些数据。

管道提供了简单的流控制机制
a. 进程试图读空管道时,进程将阻塞。
b. 管道满时,进程再试图向管道写入数据时,进程也将阻塞。

在使用管道时,通常在调用了pipe后会调用fork,然后在父子进程之间通过pipe来通讯

当管道的一端被关闭后
1.  当读一个写端已被关闭的管道时,在所有数据都被读取后, read返回0,以指示达到了文件结束处(从技术方面考虑,管道的写端还有进程时,就不会产生文件的结束。可以复制一个管道的描述符,使得有多个进程具有写打开文件描述符。但是,通常一个管道只有一个读进程,一个写进程)。
2.   如果写一个读端已被关闭的管道,则产生信号SIGPIPE。如果忽略该信号或者捕捉该信号并从其处理程序返回,则write出错返回,errn设置为EPIPE。在写管道时,常数PIPE_BUF规定了内核中管道缓存器的大小。如果对管道进行write调用,而且要求写的字节数小于等于PIPE_BUF,则此操作不会与其他进程对同一管道(或FIFO)的write相交错。但是,若有多个进程同时写一个管道(或FIFO),而且某个或某些进程要求写的字节数超过PIPE_BUF字节数,则数据可能会与其他写操作的数据相交错。

示例程序如下:

  1. #include<sys/types.h>
  2. #include<sys/stat.h>
  3. #include<unistd.h>
  4. #include<fcntl.h>
  5. #include<stdio.h>
  6. #include<stdlib.h>
  7. #include<errno.h>
  8. #include<string.h>
  9. #include<signal.h>
  10. #define ERR_EXIT(m) \
  11. do { \
  12. perror(m); \
  13. exit(EXIT_FAILURE); \
  14. } while(0)
  15.  
  16. int main(int argc, char *argv[])
  17. {
  18. int pipefd[2];
  19. if (pipe(pipefd) == -1)
  20. ERR_EXIT("pipe error");
  21.  
  22. pid_t pid;
  23. pid = fork();
  24. if (pid == -1)
  25. ERR_EXIT("fork error");
  26.  
  27. if (pid == 0)
  28. {
  29. close(pipefd[0]);
  30. write(pipefd[1], "hello", 5);
  31. close(pipefd[1]);
  32. exit(EXIT_SUCCESS);
  33. }
  34.  
  35. close(pipefd[1]);
  36. char buf[10] = {0};
  37. read(pipefd[0], buf, 10);
  38. printf("buf=%s\n", buf);
  39.  
  40. return 0;
  41. }

1. 父进程调用pipe开辟管道,得到两个文件描述符指向管道的两端。
2. 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。
3. 父进程关闭管道写端,子进程关闭管道读端。子进程可以往管道里写,父进程可以从管道里读,管道是用环形队列实现的,数据从写端流入从读端流出,这样就实现了进程间通信。

三、命名管道

进程间通信必须通过内核提供的通道,而且必须有一种办法在进程中标识内核提供的某个通道,前面讲过的匿名管道是用打开的文件描述符来标识的。如果要互相通信的几个进程没有从公共祖先那里继承文件描述符,它们怎么通信呢?内核提供一条通道不成问题,问题是如何标识这条通道才能使各进程都可以访问它?文件系统中的路径名是全局的,各进程都可以访问,因此可以用文件系统中的路径名来标识一个IPC通道。

FIFO和UNIX Domain Socket这两种IPC机制都是利用文件系统中的特殊文件来标识的。

FIFO文件在磁盘上没有数据块,仅用来标识内核中的一条通道:

prw-rw-r-- 1 simba simba      0 May 21 10:13 p2

文件类型标识为p表示FIFO,文件大小为0。

各进程可以打开这个文件进行read/write,实际上是在读写内核通道(根本原因在于这个file结构体所指向的read、write函数和常规文件不一样),这样就实现了进程间通信。UNIX Domain Socket和FIFO的原理类似,也需要一个特殊的socket文件来标识内核中的通道,例如/run目录下有很多系统服务的socket文件:

srw-rw-rw- 1 root       root          0 May 21 09:59 acpid.socket

....................

文件类型s表示socket,这些文件在磁盘上也没有数据块

匿名管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
     如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。

命名管道可以从命令行上创建,命令行方法是使用下面这个命令:
     $ mkfifo filename
     命名管道也可以从程序里创建,相关函数有:

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

mkfifo函数已隐含指定O_CREAT | O_EXCL。也就是说,它要么创建一个新的FIFO,要么返回一个EEXIST错误(如果所指定名字的FIFO已经存在)。如果不希望创建一个新的FIFO,那么久改用调用open而不是mkfifo。要打开一个已存在的FIFO或创建一个新的FIFO,应先调用mkfifo,再检查它是否返回EEXIST错误,若返回该错误则改为调用open。

在创建出一个FIFO后,它必须或者打开来读,或者打开来写,所用的可以是open函数,也可以是某个标准I/O打开函数,例如fopen。FIFO不能打开来既读又写,因为它是半双工的。

对管道或FIFO的write总是往末尾添加数据,对他们的read则总是从开头返回数据。如果对管道或FIFO调用lseek,那就返回ESPIPE错误

匿名管道由pipe函数创建并打开。
     命名管道由mkfifo函数创建,打开用open。
     FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,这些工作完成之后,它们具有相同的语义。

四、命名管道的打开规则

O_RDONLY、O_WRONLY和O_NONBLOCK标志共有四种合法的组合方式:

  • flags=O_RDONLY:open将会调用阻塞,除非有另外一个进程以写的方式打开同一个FIFO,否则一直等待。
  • flags=O_WRONLY:open将会调用阻塞,除非有另外一个进程以读的方式打开同一个FIFO,否则一直等待。
  • flags=O_RDONLY|O_NONBLOCK:如果此时没有其他进程以写的方式打开FIFO,此时open也会成功返回,此时FIFO被读打开,而不会返回错误。
  • flags=O_WRONLY|O_NONBLOCK:立即返回,如果此时没有其他进程以读的方式打开,open会失败打开,此时FIFO没有被打开,返回-1。

需要注意的是打开的文件描述符默认是阻塞的,大家可以写两个很简单的小程序测试一下,主要也就一条语句

int fd = open("p2", O_WRONLY);

假设p2是命名管道文件,把打开标志换成 O_RDONLY 就是另一个程序了,可以先运行RD程序,此时会阻塞,再在另一个窗口运行WR程序,此时两个程序都会从open返回成功。非阻塞时也不难测试,open时增加标志位就可以了。

下面示例命名管道完成拷贝文件的功能:

  1. #include<sys/types.h>
  2. #include<sys/stat.h>
  3. #include<unistd.h>
  4. #include<fcntl.h>
  5. #include<stdio.h>
  6. #include<stdlib.h>
  7. #include<errno.h>
  8. #include<string.h>
  9. #include<signal.h>
  10. #define ERR_EXIT(m) \
  11. do { \
  12. perror(m); \
  13. exit(EXIT_FAILURE); \
  14. } while(0)
  15.  
  16. int main(int argc, char *argv[])
  17. {
  18. mkfifo("tp", 0644);
  19. int infd = open("Makefile", O_RDONLY);
  20. if (infd == -1)
  21. ERR_EXIT("open error");
  22.  
  23. int outfd;
  24. outfd = open("tp", O_WRONLY);
  25. if (outfd == -1)
  26. ERR_EXIT("open error");
  27.  
  28. char buf[1024];
  29. int n;
  30. while ((n = read(infd, buf, 1024)) > 0)
  31. write(outfd, buf, n);
  32.  
  33. close(infd);
  34. close(outfd);
  35.  
  36. return 0;
  37. }

程序使用mkfifo函数创建一个命名管道文件tp,将Makefile 的文件都读取到tp文件中。

  1. #include<sys/types.h>
  2. #include<sys/stat.h>
  3. #include<unistd.h>
  4. #include<fcntl.h>
  5. #include<stdio.h>
  6. #include<stdlib.h>
  7. #include<errno.h>
  8. #include<string.h>
  9. #include<signal.h>
  10. #define ERR_EXIT(m) \
  11. do { \
  12. perror(m); \
  13. exit(EXIT_FAILURE); \
  14. } while(0)
  15.  
  16. int main(int argc, char *argv[])
  17. {
  18.  
  19. int outfd = open("Makefile2", O_WRONLY | O_CREAT | O_TRUNC, 0644);
  20. if (outfd == -1)
  21. ERR_EXIT("open error");
  22.  
  23. int infd;
  24. infd = open("tp", O_RDONLY);
  25. if (infd == -1)
  26. ERR_EXIT("open error");
  27.  
  28. char buf[1024];
  29. int n;
  30. while ((n = read(infd, buf, 1024)) > 0)
  31. write(outfd, buf, n);
  32.  
  33. close(infd);
  34. close(outfd);
  35. unlink("tp"); // delete a name and possibly the file it refers to
  36. return 0;
  37. }

可以看到跟上面的程序是相反的,即从tp读取到Makefile2,完成拷贝文件的功能,这里用到了一个unlink函数,属于inode_operations系列的一个函数,即inode引用计数减1,当引用计数为0且进程已经关闭文件描述符时,文件将被删除。

对于FIFO和无名管道的编码区别:

(1)创建并打开一个管道只需调用pipe。创建并打开一个FIFO则需在调用mkfifo后再调用open。

(2)管道在所有进程最终都关闭它之后自动消失。FIFO的名字则只有通过调用unlink才文件系统删除。

       FIFP需要额外调用的好处是:FIFO在文件系统中有一个名字,该名字允许某个进程创建个FIFO,与它无亲缘关系的另一个进程来打开这个FIFO。对于管道来说,这是不可能的。

     系统规定:如果写入的数据长度小于等于PIPE_BUF字节,那么或者写入全部字节,要么一个字节都不写入。
    在非阻塞的write调用情况下,如果FIFO 不能接收所有写入的数据,将按照下面的规则进行:

  • 请求写入的数据的长度大于PIPE_BUF字节,调用失败,数据不能被写入。
  • 请求写入的数据的长度小于PIPE_BUF字节,将写入部分数据,返回实际写入的字节数,返回值也可能是0。

其中。PIPE_BUF是FIFO的长度,它在头文件limits.h中被定义。在linux或其他类UNIX系统中,它的值通常是4096字节。

注意:PIPE_BUF与FIFO容量是有区别的,PIPE_BUF表示可原子的写往一个管道或FIFO的最大数据量。PIPE_BUF为4096,但是FIFO的容量为65536.

五、删除FIFO文件

FIFO文件使用完毕之后需删除,以免造成垃圾文件。

  1. #include <unistd.h>
  2. int unlink(const char *pathname);

UNIX环境高级编程——无名管道和有名管道的更多相关文章

  1. UNIX环境高级编程——管道读写规则和pipe Capacity、PIPE_BUF

    一.当没有数据可读时O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止. O_NONBLOCK enable:read调用返回-1,errno值为EAGAI ...

  2. (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO

    . . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...

  3. (七) 一起学 Unix 环境高级编程(APUE) 之 进程关系 和 守护进程

    . . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...

  4. (九) 一起学 Unix 环境高级编程 (APUE) 之 线程

    . . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...

  5. (十二) 一起学 Unix 环境高级编程 (APUE) 之 进程间通信(IPC)

    . . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...

  6. 【UNIX环境高级编程】文件I/O

    [UNIX环境高级编程]文件I/O大多数文件I/O只需要5个函数: open.read.write.lseek以及close 不带缓冲的I/O: 每个read和write都调用内核中的一个系统调用 1 ...

  7. 《UNIX环境高级编程(第3版)》

    <UNIX环境高级编程(第3版)> 基本信息 原书名:Advanced Programming in the UNIX Environment (3rd Edition) (Addison ...

  8. unix环境高级编程附录 B 通用代码

    0.说明: 在测试 unix 环境高级编程中的代码时,需要一些作者事先写好的代码, 如: apue.h 包含某些标准系统头文件,定义许多常量及函数原型 还有两个作者自编的函数来对错误进行处理 1.ep ...

  9. UNIX环境高级编程——TCP/IP网络编程 常用网络信息检索函数

    UNIX环境高级编程——TCP/IP网络编程   常用网络信息检索函数 gethostname()   getppername()   getsockname()   gethostbyname() ...

随机推荐

  1. 单选、多选框根据value值设置选中

    $("input:checkbox[value='video']").parent().addClass('checked'); $("input:radio[value ...

  2. localStorage 存取与删除

    部分转自 http://blog.csdn.net/oo191416903/article/details/64122379 <!DOCTYPE html> <html> &l ...

  3. JQuery when() done() then()

    jQuery.when(deferreds) 参数deferreds,一个或多个延时对象或JS对象,我们初略的认为它就是一个或多个异步请求. 例如:$.when($.ajax("page1. ...

  4. 离线合成联想到的--canvas合成水印

    前段时间做了功能模块:用户设置自定义勋章: 实现方式:前端把用户设置的昵称传到后台,后台根据不同用户等级,使用离线合成技术合成不同的勋章返回到前端: 方案算是实现了,但是有点坑就是,后台的离线合成没有 ...

  5. 深入理解null的原理

    --null的原理 --oracle一直将null和空字符串''<长度为0>同等对待<如'' is null是true,''=null为false,如果声明a varchar2:=' ...

  6. 如何搭建samba服务?

    为了日后便于查询,本文所涉及到的所有命令集合如下: chkconfig iptables off #关闭防火墙命令 在Centos7中使用的是chkconfig firewalld off seten ...

  7. 利用Bioperl的SeqIO模块解析fastq文件

    测序数据中经常会接触到fastq格式的文件,比如说拿到fastq格式的原始数据后希望查看测序碱基的质量并去除低质量碱基.一般而言大家都是用现有的工具,比如说fastqc这个Java写的小程序,确实很好 ...

  8. Python3 数字(Number)

    Python 解释器可以作为一个简单的计算器:您可以在解释器里输入一个表达式,它将输出表达式的值. 表达式的语法很直白: +, -, * 和/ 和在许多其它语言(如Pascal或C)里一样:括号可以用 ...

  9. Django中过期@cache_page中缓存的views数据

    django的缓存系统中,cache_page 这个装饰器非常好用,只要添加一个装饰器就可以缓存views的响应内容,但是django没有提供过期这个views缓存数据的功能. @cache_page ...

  10. ubuntu初始化python3+postgresql+uwsgi+nginx+django

    一. postgresql 数据库 安装 apt-get update apt-get install postgresql 进入psql客户端 sudo -u postgres psql 创建数据库 ...