进程间通信:
Linux环境下,进程之间,地址空间相互独立,进程和进程之间不能相互访问。想在进程间完成数据传输需要操作系统
提供特殊的方法:IPC(interprocess communication)如:文件、管道、信号、共享内存、消息队列、套接字、命名管道…
现今常用的进程间通信方式有三种:
① 管道 (使用最简单)
② 信号 (开销最小)
*③ 共享映射区 (无血缘关系)
④ 本地套接字 (最稳定)
=====================================================================================
管道pipe的概念:
1. 本质是伪文件(实为内核缓冲区) 2.用于进程间通信,由两个文件描述符引用,一个表读端,一个表写端。
3. 规定数据从管道的写端流入管道,从读端流出。
管道的原理: 管道实为内核使用环形队列机制,借助缓冲区(4k)实现。意味着管道中的数据: ①自己写不能自己读,
② 数据一旦被读走,便不在管道中存在,不可反复读取。
管道的局限性:由于是半双工通信方式,数据只能在一个方向上流动。且只能在有公共祖先的进程间使用管道。
———————————
pipe函数: 创建管道
int pipe(int pipefd[2]); 成功:0;失败:-1
函数调用成功返回r/w两个文件描述符。 无需open,但需手动close。 规定:fd[0] → r; fd[1] → w
【练习】:父子进程使用管道通信,父写入字符串,子进程读出并,打印到屏幕 pipe.c
思考:为甚么,程序中没有使用sleep函数,但依然能保证子进程运行时一定会读到数据呢?
———————————
管道的读写行为:
① 读管道: 1. 管道中有数据,read返回实际读到的字节数。
2. 管道中无数据:(1) 管道写端被全部关闭,read返回0 (好像读到文件结尾)
(2) 写端没有全部被关闭,read阻塞等待(不久的将来可能有数据递达,此时会让出cpu)
② 写管道: 1. 管道读端全部被关闭, 进程异常终止(也可使用捕捉SIGPIPE信号,使进程不终止)
2. 管道读端没有全部关闭: (1) 管道已满,write阻塞。
(2) 管道未满,write将数据写入,并返回实际写入的字节数。
【练习】:使用管道实现父子进程间通信,完成:ls | wc -l pipe1.c
程序执行,发现程序执行结束,shell还在阻塞等待用户输入。这是因为,shell → fork → ./pipe1, 程序pipe1的子进程
将stdin重定向给管道,父进程执行的ls会将执行结果通过管道给子进程。若父进程在子进程打印wc的结果到屏幕之前被
shell调用wait回收,shell就会先输出$提示符。
【练习】:使用管道实现兄弟进程间通信。 兄:ls 弟: wc -l 父:等待回收子进程。 pipe2.c
注意管道读写行为。 画图分析程序执行。
【编程测试】:是否允许,一个pipe有一个写端,多个读端呢?是否允许有一个读端,多个写端呢? pipe4.c
拓展练习: 统计当前系统中进程ID大于10000的进程个数。
——————————
管道的优劣:
优点:简单,相比信号,套接字实现进程间通信,简单很多。
缺点:1. 只能单向通信,双向通信需建立两个管道。
2. 只能用于父子、兄弟进程(有共同祖先)间通信。该问题后来使用fifo有名管道解决。
=====================================================================================
fifo有名管道
创建方式:1. 命令: mkfifo 管道名
2. 库函数: int mkfifo(const char *pathname, mode_t mode); 成功:0; 失败:-1
【fifo_w.c/fifo_r.c】
=====================================================================================
文件实现进程间通信
理论依据是,fork后,父子进程共享文件描述符。也就共享打开的文件。
【练习】:编程测试,父子进程共享打开的文件。借助文件进行进程间通信。 fork_shared_fd.c
=====================================================================================
mmap函数:
void *mmap(void *adrr, size_t length, int prot, int flags, int fd, off_t offset); 成功返回映射区首地址;失败:MAP_FAILED
addr: 建立映射区的首地址,由Linux内核指定。使用时,直接传递NULL
len: 欲创建映射区的大小 (但内核实际在分配时最小分配4K(一个page))
prot:映射区选 PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITE
flags:标志位参数(常用于设定更新物理区域、设置共享、创建匿名映射区)
fd: 用来建立映射区的文件的文件描述符
off: 映射文件的偏移(4k的整数倍)
int munmap(void *addr, size_t length); 成功:0; 失败:-1
MAP_SHARED: 会将映射区所做的操作反映到物理设备(磁盘)上。
MAP_PRIVATE: 映射区所做的修改不会反映到物理设备。
注意事项:【演示】:mmap.c
创建映射区的过程中,隐含着一次对映射文件的读操作。
当MAP_SHARED时,要求:映射区的权限应 <= 文件打开的权限(出于对映射区的保护)
而MAP_PRIVATE,则无所谓,因为mmap中的权限是对内存的限制。
映射区的释放与文件关闭没有任何关系。只要映射建立成功,文件可以立即关闭
【重点】当映射文件大小为0时,不能创建映射区。所以:用于映射的文件必须要有实际大小!!
使用0字节大小的文件来创建映射区,而在mmap函数调用中指定非0大小,会出“总线错误”。
父子进程间:
MAP_PRIVATE: (私有映射) 父子进程各自独占映射区;
MAP_SHARED: (共享映射) 父子进程共享映射区;
【练习】:父进程创建映射区,然后fork子进程,子进程修改映射区内容,而后,父进程读取映射区内容,查验是否共享。 fork_mmap.c
结论:父子进程共享:1. 打开的文件 2. mmap建立的映射区(但必须要使用MAP_SHARED)
———————-
匿名映射:MAP_ANONYMOUS (或MAP_ANON):
通常为了建立映射区要open一个tmp文件,创建好了再unlink()。 可以直接使用匿名映射来代替。
int *p = mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
MAP_ANNOYMOUS(或MAP_ANON) 为Linux系统独有宏。”4″随意举例,该位置表大小,可依需要随意填。
【fork_map_anon_linux.c】
在类Unix系统中可使用两步来完成
① fd = open(“/dev/zero”, O_RDWR);
② p = mmap(NULL, size, PROT_READ|PROT_WRITE, MMAP_SHARED, fd, 0);
【fork_map_anon.c】
【smalloc.c】:练习,参照malloc和free的用法,封装函数smalloc和sfree,用来建立、释放共享内存。
=====================================================================================
mmap在 –无血缘关系– 的进程间通信
【mmp_w.c/mmp_r.c】
====================================================