进程间通信

进程间通信方式

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,不相关的进程也能交换数据。FIFO是linux文件类型中的一种,但FIFO文件在磁盘上没有数据块,仅仅用来标识内核中的一条通道。各个进程可以打开这个文件进行read/write,实际上是在读写内核通道,这样就实现了进程间通信。

创建方式:mkfifo 管道名 成功:0,失败:-1。 一旦使用 makefifo 创建了一个FIFO,就可以用open打开它,常见的文件I/O函数都可用于fifo。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值