赞
踩
IPC方法:linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间不能相互访问,要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信。
内核空间中的一块缓冲区大小为4096
在进程间完成数据传递需要借助操作系统提供的特殊的方法,文件、管道、信号、共享内存、消息队列、套接字、命名管道等。
1、管道(使用简单)
2、信号(开销最小)
3、共享映射区(无血缘关系)
4、本地套接字(最稳定)
调用pipe系统函数即可创建一个管道。本质是一个伪文件(内核缓冲区),有两个文件描述符引用,一个表示读端,一个表示写端。规定数据从管道的写端流入管道,从读端流出。
管道的原理:为内核使用环形队列机制,借助内核缓冲区(4k)实现。
局限性:数据不能进程自己写,自己读;管道中数据不可反复读取,一旦读走,管道中不再存在;采用半双工通信方式,数据只能在单方向上流动;只能在有公共祖先的进程间使用管道。
makefifo f1:创建的管道可以作用在不具有血缘关系的两个进程之间。
7种文件类型,除文件、软连接、硬链接外其余为伪文件
int pipe(int fd[2]); 0表示读端,成功返回0,失败返回-1
#include<stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <pthread.h> void sys_err(const char *str){ perror(str); exit(1); } int main(int argc, char *argv[]){ int ret; int fd[2]; pid_t pid; char buf[1024]; ret = pipe(fd); if(ret == -1){ sys_err("pipe error"); } pid = fork(); if(pid > 0){ close(fd[0]);//父进程关闭读端 write(fd[1], "hello pipe", strlen("hello pipe")); close(fd[1]); }else if(pid == 0){ close(fd[1]);//子进程关闭写端 ret = read(fd[0], buf, sizeof(buf)); write(STDOUT_FILENO, buf, ret); close(fd[0]); } return 0; }
父进程写,子进程读
运行结果为:
ymyy@ymyy-virtual-machine:~/systemcode$ ./pipe
hello pipe
管道的读写行为
读管道:
1、管道中有数据,read返回实际读到的字节数。
2、管道中无数据,管道写端被全部关闭,read返回0(读到文件结尾),写端没有被全部关闭,read阻塞等待(不久的将来可能数据抵达)
写管道:
1、管道读端全部被关闭,进程异常终止(也可捕捉SIGPIE信号,使进程不终止)
2、管道读端没有全部关闭,管道已满,write阻塞,管道未满,write 将数据写入,并返回实际写入的字节数。
小练习:使用管道实现父子进程间通信,完成 ls | wc -l。假定父进程实现 ls,子进程实现 wc。
ls 命令正常会将结果写在 stdout,但现在会写入管道的写端:wc-l 正常应该从 stdin 读取数据,但此时会从管道的读端读。
#include<stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <pthread.h> void sys_err(const char *str){ perror(str); exit(1); } int main(int argc, char *argv[]){ int ret; pid_t pid; int fd[2]; char buf[1024]; ret = pipe(fd); if(ret == -1){ sys_err("pipe error"); } pid = fork(); if(pid > 0){ close(fd[0]); dup2(fd[1], STDOUT_FILENO); execlp("ls", "ls", NULL); }else if(pid == 0){ close(fd[1]); dup2(fd[0], STDIN_FILENO); execlp("wc", "wc", "-l", NULL); }else{ sys_err("fork error"); } return 0; }
执行结果为:(为避免bash进程先于子进程执行完抢占终端,可以交换父子进程的内容,即子进程写,父进程读,bash进程在父进程结束后抢占终端)
ymyy@ymyy-virtual-machine:~/systemcode$ ./ls-wc-l
ymyy@ymyy-virtual-machine:~/systemcode$ 37
ls
block_readtty fcntl.c fork_exec lseek_test1 ls-R.c mycp.c pipe.c zoom_test
block_readtty.c fcntl_dup fork_exec.c lseek_test1.c ls-wc-l nonblock_readtty unlink_exe zoom_test.c
dup fcntl_dup.c loop_fork lseek_test.c ls-wc-l.c nonblock_readtty.c unlink_exe.c
dup.c fork loop_fork.c lseek.txt makefile out waitpid_while
fcntl fork.c lseek_test ls-R mycp pipe waitpid_while.c
#include<stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <pthread.h> #include <sys/wait.h> void sys_err(const char *str){ perror(str); exit(1); } int main(int argc, char *argv[]){ int fd[2]; int ret, i; pid_t pid, wpid; ret = pipe(fd); if(ret == -1){ sys_err("pipe error"); } for(i = 0; i < 2; i++){//表达式2出口,仅限父进程使用 pid = fork(); if(pid == -1){ sys_err("fork error"); } if(pid == 0){//子进程出口 break; } } if(i == 2){ close(fd[0]);//一定要关闭父进程管道,否则数据流向父进程,不能保证单向流动 close(fd[1]); wait(NULL); wait(NULL); /*回收子进程的另一种写法 while((wpid = waitpid(-1, NULL, WNOHANG)) != -1){ if(wpid > 0){ printf("wait child %d\n", wpid); }else if(wpid == 0){ continue; } } */ } else if(i == 0){//兄进程ls close(fd[0]); dup2(fd[1], STDOUT_FILENO); execlp("ls", "ls", NULL); }else if(i == 1){//弟进程wc-l close(fd[1]); dup2(fd[0], STDIN_FILENO); execlp("wc", "wc", "-l", NULL); } return 0; }
结果为:
ymyy@ymyy-virtual-machine:~/systemcode$ ./lswcl-brother
42
ymyy@ymyy-virtual-machine:~/systemcode$ ls | wc -l
42
一个父进程读,两个子进程写,一个读端两个写端。调控写入顺序。
#include<stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <pthread.h> #include <sys/wait.h> void sys_err(const char *str){ perror(str); exit(1); } int main(int argc, char *argv[]){ int fd[2]; int ret, i; pid_t pid, wpid; int n; char buf[1024]; ret = pipe(fd); if(ret == -1){ sys_err("pipe error"); } for(i = 0; i < 2; i++){//表达式2出口,仅限父进程使用 pid = fork(); if(pid == -1){ sys_err("fork error"); } if(pid == 0){//子进程出口 break; } } if(i == 0){ close(fd[0]); write(fd[1], "1.hello\n", strlen("1.hello\n")); }else if(i == 1){ close(fd[0]); write(fd[1], "2.hello\n", strlen("2.hello\n")); }else{ close(fd[1]); sleep(1);//父进程需要等待两个子进程执行完毕后再读 n = read(fd[0], buf, 1024); write(STDOUT_FILENO,buf, n); wait(NULL); wait(NULL); } return 0; }
结果为:
ymyy@ymyy-virtual-machine:~/systemcode$ ./pipe3
1.hello
2.hello
管道只能用于有血缘关系的进程间,但通过FIFO,不相关的进程也能交换数据。FIFO是linux文件类型中的一种,但FIFO文件在磁盘上没有数据块,仅仅用来标识内核中的一条通道。各个进程可以打开这个文件进行read/write,实际上是在读写内核通道,这样就实现了进程间通信。
创建方式:mkfifo 管道名 成功:0,失败:-1。 一旦使用 makefifo 创建了一个FIFO,就可以用open打开它,常见的文件I/O函数都可用于fifo。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。