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