LInux多进程开发II

1.进程间通信。
  • 进程是独立的资源分配单元,不同进程之间的资源是独立的,没有关联,不能在一个进程中直接访问另一个进程的资源。
  • 进程之间可以进行信息的交互和状态的传递,称为进程间通信(IPC:Inter Processes Communication)。
  • 进程间通信:数据传输、通知时间、资源共享、进程控制。
2.进程通信方式。
  • 同一主机进程通信。匿名管道、有名管道、信号、消息队列、共享内存、信号量。
  • 不同主机(网络)通信。Socket。
3.匿名管道。
  • Unix系统进程间通信。
  • 管道是在内核内存中维护的缓冲区,不同的操作系统大小不同。
  • 管道拥有文件的特质:读写操作。匿名管道没有文件实体;有名管道有文件实体,但不存储数据。管道的两端可以分别用两个文件描述符表示。可以用操作文件的方式对管道进行操作。
  • 一个管道是一个字节流,使用管道不存在消息或消息边界的概念,从管道读取数据的进程可以读取任意大小的数据块,而不管写入进程写入管道的数据块大小是多少。
  • 通过管道传递的数据是顺序的,从管道读取出来的字节的顺序和被写入管道的顺序完全一致。
  • 管道的数据传递方向是单向的,一端用于写入,一端用于读取,管道是半双工的。
  • 从管道读数据是一次性操作,数据一旦被读走就被从管道中抛弃并释放空间可以写入更多的数据。在管道中无法使用lseek()来随机访问数据。
  • 匿名管道只能在具有公共祖先的进程之间使用。子进程通过fork()创建出来与父进程共享文件描述符,因此可以根据相同的文件描述符进行通信。
  • 实现:循环数组。由两个文件描述符标志分别读写位置。
4.实现匿名管道。
#include <unistd.h>
int pipe(int pipedfd[2]);
    - 作用:创建一个匿名管道,用来进程间通信。
    - pipefd[2]: 传出参数。pipefd[0],对应管道的读端;pipefd[1],对应管道的写端。
    - 返回值:成功返回0,失败返回1。
 
注意:
1.匿名管道只用于具有关系的进程之间的通信。
2.管道默认是阻塞的,如果管道中没有数据,read阻塞;如果管道满了,write阻塞。
 
# 查看管道缓冲大小命令
$ ulimit -a
 
# 查看管道缓冲大小函数
#include <unistd.h>
long fpathconf(int fd, int name);
long size = fpathcong(pipefd[0], _PC_PIPE_BUF);
 
注意:实现管道通信,一般只会有一个方向的通信,从进程a传数据到进程b,或者从进程b传数据到进程a。因为如果进程a,b互相传数据的话,可能会造成后果:a写入管道的数据又被a自己读去了,b写入管道的数据也可能被b进程读去了。所以在实现管道时,往管道写入数据的进程会关闭读端close(pipefd[0]);而往管道读数据的进程会关闭管道写端close(pipefd[1])。
 
5.管道的读写特点:使用管道时,有以下几种特殊情况(假设都是阻塞IO操作)
  • 所有指向管道写端的文件描述符都关闭了(管道写端引用计数为0),有进程从管道的读端读取数据,那么管道中剩余的数据被读取之后,再次read会返回0,就像读到文件末尾一样。
  • 如果有指向管道写端的文件描述符没有关闭(管道写端引用计数大于0),而持有管道写端的进程没有往管道中写数据,这个时候若有进程从管道中读取数据,那么管道中剩余的数据被读取之后,再次read会阻塞,直到管道中有数据可以读了才读取数据并返回。
  • 所有指向管道读端的文件描述符都关闭了(管道读端引用计数为0),这个时候有进程往管道中写数据,那么该进程会受到一个信号SIGPIPE,通常会导致进程终止。
  • 如果有指向管道读端的文件描述符没有关闭(管道读端引用技术大于0),而持有管道读端的进程也没有从管道读数据,这时有进程向管道写数据,那么管道在被写满的时候再次write阻塞,直到管道中有空位置能再次写入数据并返回。
总结:
    读管道:
        1. 管道中有数据,read返回实际读到的字节数。
        2. 管道中无数据:a. 写端全关闭,read返回0;b. 写端没有完全关闭,read阻塞等待。
    写管道:
        1. 管道读端全部关闭,进程异常终止(进程受到SIGPIPE信号)。
        2. 管道读端没有全部关闭:a. 管道已满,write阻塞;b. 管道没有满,write写入数据,并返回实际写入的字节数。
 
6.设置管道非阻塞。
int flags = fcntl(fd[0], F_GETFL);       # 获取原来的flag
flags |= O_NONBLOCK;                     # 修改flag的值
fcntl(fd[0], F_SETFL, flags);            # 设置新的flag
 
7.有名管道(FIFO)。
  • 有名管道提供了一个路径名与之关联(即:有一个实体文件),以FIFO的文件形式存在与文件系统中。其打开方式与打开普通文件一样,因此通过访问该路径即可通过FIFO通信。
  • 与匿名管道的区别:1. FIFO有文件实体,但是其内容存放于内存中;2. 当使用FIFO的进程退出后,FIFO文件继续保存在文件系统中之后可以继续使用;3. FIFO有名字,不相关的进程可以通过打开有名管道进行通信。
 
8.实现有名管道。
# 创建管道命令
mkfifo 名字
 
# 通过函数创建,创建FIFO之后文件IO函数(open、read、write、close)都可用于FIFO
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
    - pathname: 管道名称的路径
    - mode: 文件的权限
    - 返回值: 成功返回0,失败返回-1并设置errno
 
注意:
    1.一个为打开管道的只读进程会阻塞,直到另一个只写进程打开管道;
    2.一个为打开管道的只写进程会阻塞,直到另一个只读进程打开管道;
读管道:
    管道中有数据:read返回实际读到的字节数;
    管道中无数据:a.管道写端被全部关闭,read返回0;b.写端没有全部被关闭,read阻塞等待
写管道:
    管道读端被全部关闭,进程异常终止(收到一个SIGPIPE信号);
    管道读端没有全部关闭:a.管道已满,write阻塞;b.管道没满,write写入数据,并返回实际写入的字节数
 
9.内存映射。将磁盘文件的数据映射到内存中,程序可以通过修改内存就能修改磁盘文件。
  • 同一个文件可以映射到不同进程的虚拟内存空间,因此某个进程修改内存后,系统会同步文件的修改,则另一个将内存映射到文件的进程即可收到消息。
  • 可以实现进程间通信:1.有关系的进程(父子进程);2.没有关系的进程间通信。
  • 内存映射区通信是非阻塞的。
#include <sys/mman.h>
void mmap(coid *addr, size_t length, int prot, int flags, int fd, off_t offset);
    - 作用:将一个文件或者设备的数据映射到内存中
    - addr: NULL,由内核指定
    - length: 要映射的数据的长度,这个值不能为0,建议使用文件的长度,使用stat或者lseek获取文件的长度
    - prot: 对申请的内存映射区的操作权限,PROT_EXEC 可执行权限;PROT_READ 读权限;PROT_WRITE 写权限;PROT_NONE 没有权限
    - flags: MAP_SHARED 映射区的数据会自动和磁盘文件进行同步,进程间通信,必须设置这个选项;MAP_PRIVATE 不同步,内存映射区的数据改变了,对原文件不会修改,会创建一个新的文件(copy on write)
    - fd: 需要映射的文件的文件描述符,通过open()得到,文件大小要>0
    - offset: 偏移量,4k的整数倍,0表示不偏移
    - 返回值:成功返回创建的内存的首地址,失败返回MAP_FAILED ((void *) -1)
 
int munmap(void *addr, size_t length);
    - 作用:释放内存映射
    - addr: 要释放的内存地址
    - length: 要释放的内存的大小,和mmap参数的length一样

  

10.使用内存映射实现进程间通信:
  • 有关系的进程(父子进程)
             - 还没有子进程的时候
                    - 通过唯一的父进程,先创建内存映射区
             - 有了内存映射区后,创建子进程
             - 父子进程共享创建的内存映射区
  • 没有关系的进程间通信
             - 准备一个大小不是0 的磁盘文件
             - 进程1 通过磁盘文件创建内存映射区
                    - 得到一个操作这块内存的指针
             - 进程2 通过磁盘文件创建内存映射区
                    - 得到一个操作这块内存的指针
             - 使用内存映射区通信
11.内存映射的注意事项。
  • 如果对mmap的返回值(ptr)做++操作(ptr++),munmap是否能成功?
void *ptr = mmap(...);
ptr++;                    // 可以操作
munmap(ptr, len);         // 错误,应该保存使用之前的地址
  • 如果open时O_RDONLY,mmap时prot参数指定PROT_READ|PROT_WRITE会怎样?
错误,返回MAP_FAILD
open()函数的权限要大于等于prot的权限。
e.g.: 
open()读写,prot可以读写/只读/只写
open()只读,prot只能只读
  • 如果文件偏移量是1000会怎样?
返回错误MAP_FAILED,偏移量只能是4k的整数倍
  • mmap什么情况下会调用失败?
1. 第二个该参数length = 0
2. 第三个参数 prot 只有写权限
3. 第三个参数有 PROT_READ | PROT_WRITE 权限,而第五个参数fd通过open打开时的权限是O_RDONLY / O_WRONLY(内存权限大于文件权限)
  • 可以open的时候O_CREAT一个新文件来创建映射区吗?
可以,但是创建的文件大小为0不行,需要对新文件进行扩展( lseek() / truncate() )
  • mmap后关闭文件描述符,对mmap映射有没有影响?
int fd = open("xxx");
mmap(,,,,fd,0);
close(fd);
映射区还存在,创建营社区的fd被关闭,没有影响。
  • 对ptr越界操作会怎么样?
void *ptr = mmap(NULL,100,,,,);
越界操作操作的是非法内存,造成段错误
 
12.匿名映射:不需要文件实体进行一个内存映射。只能用于父子进程间通信。
mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARD | MAP_ANONYMOUS, -1, 0);
文件描述符 fd = -1
prot 要指定为 PROT_ANONYMOUS
 
 
原文地址:https://www.cnblogs.com/tristatl/p/15120551.html