Linux 进程的管道通信

文章目录

    • 无名管道pipe
    • 有名管道
    • 讨论

进程之间的通信:Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另外一个进程中都看不到,所以进程之间不能相互访问,要交换数据必须通过内核。如图,在内核中开辟一块缓冲区,进程1把数据从用户空间拷贝到内核缓冲区,进程2在从内核缓冲区中把数据读走,内核提供的这种机制称为进程间通信IPC(InterProcess Communication)

Linux 进程的管道通信_第1张图片

  • 管道是LInux/Unix最经典的一种通信方式,管道实质上是父子进程借助内存文件的一种通信方式。借助进程映像加载等手段,它可以实现两个程序之间的数据交换。
  • 管道的本质是一块内核缓冲区,由两根文件描述符引用,一个表示读端,一个表示写端,规定数据从管道的写端流入管道,从读端流出。当两根进程都终结的时候,管道会自动消失。默认的,管道的读端和写端都是堵塞的。
  • 管道包括两种:无名管道和有名管道。

无名管道pipe

使用无名管道时,还可以搭配使用close()关闭文件描述符和dup()复制管道文件描述符来实现输入输出标准的重定向。

#include
#include
#include
#include

int main(){
	int data_processed;
	int file_pipes[2];
	const char some_data[]="123";
	char buffer[BUFSIZ+1];
	
	memset(buffer,'\0',sizeof(buffer));

	if(pipe(file_pipes)==0){
		data_processed=write(file_pipes[1],some_data,strlen(some_data));
		printf("Wrote %d bytes\n",data_processed);
		data_processed=read(file_pipes[0],buffer,BUFSIZ);
		printf("Read %d bytes:%s\n",data_processed,buffer);
		exit(EXIT_SUCCESS);
	
	}
	exit(EXIT_FAILURE);
}
[cch@aubin os]$ gcc demo.c
[cch@aubin os]$ ./a.out
Wrote 3 bytes
Read 3 bytes:123
[cch@aubin os]$ 
  • menset是c语言的初始化函数,作用是将某一块内存中的内容全部设置为指定的值,这个函数通常为新申请的内存做初始化工作。void *memset(void *s, int ch, size_t n);将s中当前位置后面的n个字节 (typedef unsigned int size_t )用 ch 替换并返回 s 。
  • int pipe(int fd[2])用于创建一个管道,如果函数调用成功,fd[0]存放管道的读端,fd[1]存放管道的写端(都是文件描述符),并且返回0,如果失败则返回-1,并设置errno值。

当父进程使用pipe创建管道之后,一般需要再fork一个子进程,然后通过管道实现父子进程之间的通信。一般来说只要两个进程有血缘关系(有共同的祖先),就可以使用管道进行通信。

  • 父进程创建管道
    Linux 进程的管道通信_第2张图片
  • 父进程fork子进程
    Linux 进程的管道通信_第3张图片
  • 父进程关闭读端,子进程关闭写端,实现进程之间通信
    Linux 进程的管道通信_第4张图片
#include
#include
#include
#include

int main(){
	int data_processed;
	int file_pipes[2];
	const char some_data[]="123";
	char buffer[BUFSIZ+1];
	pid_t fork_result;
	
	memset(buffer,'\0',sizeof(buffer));

	if(pipe(file_pipes)==0){
		fork_result=fork();
		if(fork_result==-1){
			fprintf(stderr,"Fork failure");
			exit(EXIT_FAILURE);
		}

		//子进程
		if(fork_result==0){
			data_processed=read(file_pipes[0],buffer,BUFSIZ);
			printf("son:Read %d bytes:%s\n",data_processed,buffer);
			exit(EXIT_SUCCESS);
		}else{
			data_processed=write(file_pipes[1],some_data,strlen(some_data));
			printf("father:Wrote %d bytes\n",data_processed);
		}
		
		
		exit(EXIT_SUCCESS);
	
	}
	exit(EXIT_FAILURE);
}
[cch@aubin os]$ gcc demo.c
[cch@aubin os]$ ./a.out
father:Wrote 3 bytes
son:Read 3 bytes:123
[cch@aubin os]$ 

管道实现程序之间的通信

//pipe4.c
#include
#include
#include
#include

int main(int argc,char *argv[]){
	int data_processed;
	char buffer[BUFSIZ+1];
	int file_descriptor;
	
	memset(buffer,'\0',sizeof(buffer));
	sscanf(argv[1],"%d",&file_descriptor);//读取格式化的argv[1]给file_descriptor
	data_processed=read(file_descriptor,buffer,BUFSIZ);
	printf("%d-read %d bytes:%s\n",getpid(),data_processed,buffer);
	exit(EXIT_FAILURE);

}
# 0表示键盘,从键盘中读取输入并且输出
[cch@aubin os]$gcc pipe4.c -o pipe
[cch@aubin os]$ ./pipe 0
123466
4947-read 7 bytes:123466

[cch@aubin os]$ 
#include
#include
#include
#include

int main(){
	int data_processed;
	int file_pipes[2];
	const char some_data[]="123";
	char buffer[BUFSIZ+1];
	pid_t fork_result;
	
	memset(buffer,'\0',sizeof(buffer));

	if(pipe(file_pipes)==0){
		fork_result=fork();
		if(fork_result==-1){
			fprintf(stderr,"Fork failure");
			exit(EXIT_FAILURE);
		}

		//子进程
		if(fork_result==0){
			sprintf(buffer,"%d",file_pipes[0]);//将file_pipes[0]转换成字符串,以适应execl调用中参数类型的要求,其中fotmat参数与print中的类型一致
			/*
				execlp("ls", "ls", "-l", "-F", NULL);         使用程序名在PATH中搜索。
				execl("/bin/ls", "ls", "-l", "-F", NULL);    使用参数1给出的绝对路径搜索。
			*/

			if(execl("pipe","pipe",buffer,(char *)0)==-1)
				printf("execl error\n");
			exit(EXIT_FAILURE);

		}else{
			data_processed=write(file_pipes[1],some_data,strlen(some_data));
			printf("father:Wrote %d bytes\n",data_processed);
		}
		
		
		exit(EXIT_SUCCESS);
	
	}
	exit(EXIT_FAILURE);
}
[cch@aubin os]$ gcc demo.c
[cch@aubin os]$ ./a.out
father:Wrote 3 bytes
argv[1]=3
6098-read 3 bytes:123
[cch@aubin os]$ 

有名管道

有名管道可以实现两个没有血缘关系的进程进行通信。

[cch@aubin s]$ mkfifo pp
[cch@aubin s]$ ls -la
总用量 44
drwxrwxr-x. 2 cch cch    80 117 11:01 .
drwxr-xr-x. 5 cch cch    72 1014 20:04 ..
-rwxrwxr-x. 1 cch cch 14176 1023 19:09 a.out
-rw-r--r--. 1 cch cch 12288 1014 17:17 .cc.c.swp
-rw-------. 1 cch cch 12288 1012 17:05 .file1.c.swp
-rw-rw-r--. 1 cch cch   206 117 11:00 file.c
prw-rw-r--. 1 cch cch     0 117 11:01 pp

可以看到,当使用mkfifo创建有名管道后,管道文件的信息的第一个显示为p,表示其为管道文件

[cch@aubin s]$ echo "hhhhsjiqq">pp

在命令行内输入以上,一开始管道会堵塞,因为它会等待一个进程读取数据

[cch@aubin s]$ cat pp
hhhhsjiqq
[cch@aubin s]$ 

有名管道还可以使用c函数来实现,其函数原型为int mkfifo(const char *pathname, mode_t mode);

//fifo.c
#include
#include
#include
#include
#include
#include

int main(){
	int fd;
	char buf[1024];
	int n;

	if(mkfifo("pp",0644)<0){
		perror("mkfifo error:");
		exit(1);
	}

	if((fd=open("pp",O_RDONLY))<0){
		perror("open fifo error:");
		exit(1);
	}

	if((n=read(fd,buf,sizeof(buf)))<0){
		perror("read msg error:");
		exit(1);
	}
	
	buf[n]='\0';
	printf("msg:%s\n",buf);
	unlink("pp");//关闭管道文件,正常管道文件会被删除
	return 0;

}


//fifo2.c
#include
#include
#include
#include
#include
#include
#include

int main(){
	int fd;
	const char *msg="hello from write";
	if((fd=open("pp",O_WRONLY))<0){
		perror("open fifo error:");
		exit(1);
	}

	if(write(fd,msg,strlen(msg))<0){
		perror("write msg error:");
		exit(1);
	}
	
	return 0;

}


[cch@aubin s]$ gcc fifo.c -o fifo
[cch@aubin s]$ gcc fifo2.c -o fifo2
[cch@aubin s]$ ./fifo2
open fifo error:: No such file or directory
[cch@aubin s]$ ./fifo
msg:hello from write
[cch@aubin s]$ 

# 另一个终端
[cch@aubin s]$ ./fifo2
[cch@aubin s]$ ls -l
总用量 56
-rwxrwxr-x. 1 cch cch 14176 10月 23 19:09 a.out
-rwxrwxrwx. 1 cch cch   106 11月 26 17:10 demo
-rwxrwxr-x. 1 cch cch  8824 11月 27 14:00 fifo
-rwxrwxr-x. 1 cch cch  8720 11月 27 14:00 fifo2
-rw-rw-r--. 1 cch cch   369 11月 27 13:57 fifo2.c
-rw-rw-r--. 1 cch cch   482 11月 27 13:52 fifo.c
-rw-rw-r--. 1 cch cch   206 11月  7 11:00 file.c
[cch@aubin s]$ 

讨论

有名管道和无名管道的区别

无名管道
匿名性: 未命名管道是一种临时通信机制,没有明确的文件系统路径或标识符。它通常是由一个进程创建,并通过进程间的文件描述符传递给另一个进程使用。
单向通信: 未命名管道是单向的,无名管道的读端和写端是固定的,只能支持单向的数据流。通常,一个进程作为写入端,而另一个进程作为读取端。
生命周期: 未命名管道的生命周期与创建它的进程相关。当创建它的进程终止时,管道也会被销毁。
通信限制: 未命名管道通常在具有亲缘关系的进程之间使用,因为它们共享同一个父进程。

命名管道:
命名: 命名管道在文件系统中有一个明确的路径名,可以通过文件系统访问。它有一个标识符,允许不相关的进程通过路径名进行通信。
双向通信: 命名管道支持双向通信,进程可以通过相同的路径名进行读取和写入。
生命周期: 命名管道的生命周期不依赖于创建它的进程。它可以持续存在,直到显式地被删除或系统关闭。
通信范围: 命名管道允许不相关的进程进行通信,因为它们可以通过路径名引用。

无名管道和有名管道都不支持seek操作

代码展示如何实现管道的重定向IO

使用无名管道时,还可以搭配使用close()关闭文件描述符和dup()复制管道文件描述符来实现输入输出标准的重定向。

#defiine STD_INPUT 0
#define STD_OUTPUT 1
int main()
{
      int fd[2];
      int pid;
      char str[256];
      if (pipe(fd)<0) {
           perror(“pipe create error!);
           exit(1);
      }						
      if ((pid = fork())==-1) {
           perror(“process fork error!);	
           exit(1)}
     if (pid == 0) {//子进程
           close(fd[0]);					
           close(STD_OUTPUT);		
           dup(fd[1]);					
           close(fd[1]);					
           printf(“abc\n”);//把数据输出到管道,管道写数据
     }
     else {//父进程
           close(fd[1]);					
           close(STD_INPUT);			
           dup(fd[0]);					
           close(fd[0]);					
           fgets(str,sizeof(str),stdin);//从键盘输入数据到管道,管道读数据
           printf(%s\n”,str);
     }
}

写代码表示两个程序的管道通信

//pipe4.c
#include
#include
#include
#include

int main(int argc,char *argv[]){
	int data_processed;
	char buffer[BUFSIZ+1];
	int file_descriptor;
	
	memset(buffer,'\0',sizeof(buffer));
	sscanf(argv[1],"%d",&file_descriptor);//读取格式化的argv[1]给file_descriptor
	data_processed=read(file_descriptor,buffer,BUFSIZ);
	printf("%d-read %d bytes:%s\n",getpid(),data_processed,buffer);
	exit(EXIT_FAILURE);

}
# 0表示键盘,从键盘中读取输入并且输出
[cch@aubin os]$gcc pipe4.c -o pipe
[cch@aubin os]$ ./pipe 0
123466
4947-read 7 bytes:123466

[cch@aubin os]$ 
#include
#include
#include
#include

int main(){
	int data_processed;
	int file_pipes[2];
	const char some_data[]="123";
	char buffer[BUFSIZ+1];
	pid_t fork_result;
	
	memset(buffer,'\0',sizeof(buffer));

	if(pipe(file_pipes)==0){
		fork_result=fork();
		if(fork_result==-1){
			fprintf(stderr,"Fork failure");
			exit(EXIT_FAILURE);
		}

		//子进程
		if(fork_result==0){
			sprintf(buffer,"%d",file_pipes[0]);//将file_pipes[0]转换成字符串,以适应execl调用中参数类型的要求,其中fotmat参数与print中的类型一致
			/*
				execlp("ls", "ls", "-l", "-F", NULL);         使用程序名在PATH中搜索。
				execl("/bin/ls", "ls", "-l", "-F", NULL);    使用参数1给出的绝对路径搜索。
			*/

			if(execl("pipe","pipe",buffer,(char *)0)==-1)
				printf("execl error\n");
			exit(EXIT_FAILURE);

		}else{
			data_processed=write(file_pipes[1],some_data,strlen(some_data));
			printf("father:Wrote %d bytes\n",data_processed);
		}
		
		
		exit(EXIT_SUCCESS);
	
	}
	exit(EXIT_FAILURE);
}
[cch@aubin os]$ gcc demo.c
[cch@aubin os]$ ./a.out
father:Wrote 3 bytes
argv[1]=3
6098-read 3 bytes:123
[cch@aubin os]$ 

你可能感兴趣的:(操作系统,linux,服务器)