`

IPC 通信之管道

阅读更多
    管道是 UNIX 系统 IPC 的最古老但也是最常用的形式,其有以下两种局限性。
    (1)历史上,管道是半双工的(即数据只能在一个方向上流动),不过现在有些系统也提供全双工管道。但为了移植性,不应预先假定系统支持全双工管道。
    (2)管道只能在具有公共祖先的两个进程之间使用。通常,一个管道由一个进程创建,在进程调用 fork 之后,该管道就能在父进程和子进程之间使用了。
    后面会看到,FIFO 没有第二种局限性,UNIX 域套接字没有这两种局限性。
    每当在管道中键入一个命令序列让 shell 执行时,shell 都会为每一条命令单独创建一个进程,然后用管道将前一条命令进程的标准输出与后一条命令的标准输入相连接。
    管道是通过调用 pipe 函数创建的。
#include <unistd.h>
int pipe(int fd[2]);              /* 返回值:若成功,返回 0;否则,返回 -1 */

    经由参数 fd 返回两个文件描述符:fd[0] 为读而打开,fd[1] 为写而打开。fd[1] 的输出是 fd[0] 的输入。对于支持全双工管道的实现,fd[0] 和 fd[1] 都以读/写方式打开。fstat 函数对管道的每一端都返回一个 FIFO 类型的文件描述符,可以用 S_ISFIFO 宏来测试管道。
    下图显示了两种描绘半双工管道的方法:左图显示管道的两端在一个进程中相互连接,右图则强调数据需要通过内核在管道中流动。

    单个进程中的管道几乎没有任何用处。进程通常会先调用 pipe,接着调用 fork,从而创建从父进程到子进程的 IPC 通道,反之亦然。下图显示了这种情况。

    fork 之后,对于从父进程到子进程的管道,父进程关闭管道的读端 fd[0],子进程关闭写端 fd[1];而对于从子进程到父进程的管道,父进程关闭 fd[1],子进程关闭 fd[0]。
    当管道的一端被关闭后,下列两条规则就会起作用。
    (1)当 read 一个写端已被关闭的管道时,在所有数据都被读取后,read 返回 0,表示文件结束。
    (2)如果 write 一个读端已被关闭的管道,则会产生 SIGPIPE 信号。如果忽略或捕捉该信号,则 write 返回 -1,errno 设置为 EPIPE。
    在写管道(或 FIFO)时,常量 PIPE_BUF 规定了内核的管道缓冲区大小。当对管道写的字节数大于 PIPE_BUF,并且有多个进程同时写一个管道(或 FIFO)时,所写的数据可能会与其他进程的数据相互交叉。使用 pathconf 或 fpathconf 函数可以确定 PIPE_BUF 的值。
    下面这个程序通过管道将父进程的输出直接送到子进程中调用的分页程序(忽略了对函数调用返回值的检查)。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>

#define MAXLINE	1024
#define DEF_PAGER	"/bin/more"		// default pager program

int main(int argc, char *argv[]){
	if(argc != 2){
		printf("Usage: %s <filename>\n", argv[0]);
		exit(1);
	}
	int fds[2];
	pipe(fds);
	pid_t pid = fork();
	if(pid > 0){				// parent
		close(fds[0]);
		char buf[MAXLINE];
		FILE *fp = fopen(argv[1], "r");
		while(fgets(buf, MAXLINE, fp) != NULL){
			write(fds[1], buf, strlen(buf));
		}
		if(ferror(fp)){
			printf("fgets error\n");
			exit(1);
		}
		close(fds[1]);		// close write end of pipe for reader
		fclose(fp);
		waitpid(pid, NULL, 0);	// Note: this is necessary
		exit(0);
	}
	// child
	close(fds[1]);
	if(fds[0] != STDIN_FILENO)
		dup2(fds[0], STDIN_FILENO);
	close(fds[0]);

	char *argv0, *pager;
	if((pager = getenv("PAGER")) == NULL)
		pager = DEF_PAGER;
	if((argv0 = strrchr(pager, '/')) != NULL)
		argv0++;			// step past rightmost slash
	else
		argv0 = pager;			// no slash in pager
	execl(pager, argv0, (char *)0);
	exit(0);
}

    由于经常创建一个连接到另一个进程的管道,然后读其输出或向其输入端发送数据,所以标准 I/O 库提供了 popen 和 pclose 函数(类似于 fopen 和 fclose)。
#include <stdio.h>
FIFE *popen(const char *cmd, const char *type);
                         /* 返回值:若成功,返回文件指针;否则,返回 NULL */
int pclose(FILE *fp);    /* 返回值:若成功,返回子进程的终止状态;否则,返回 -1 */

    函数 popen 先执行 fork,然后子进程调用 exec 执行 cmd,并返回一个文件指针。如果 type 是“r”,则文件指针连接到子进程的标准输出,是可读的;如果 type 是“w”,则文件指针连接到子进程的标准输入,是可写的。
    函数 pclose 则关闭标准 I/O 流,等待命令终止,然后返回 shell 的终止状态。如果 shell 不能被执行,则 pclose 返回的终止状态与 shell 执行 exit(127) 一样。
    cmd 由 Bourne shell 以“sh -c cmd”的方式执行。这表示 shell 将扩展 cmd 中的任何特殊字符,比如可以使用:
        fp = popen("ls *.c", "r");
    但同时也要注意,设置用户 ID 或设置组 ID 程序决不应该调用 popen,因为它是使用调用者继承的 shell 环境来执行 cmd,一个恶意用户可以使 shell 以设置 ID 文件模式所授予的提升了的权限以非预期的方式来执行命令。
    使用 popen 函数重写上面的程序将能减少很多代码量。
#include <stdio.h>
#include <stdlib.h>

#define MAXLINE	1024
#define PAGER "${PAGER:-/bin/more}"	// environment variable, or default

int main(int argc, char *argv[]){
	if(argc != 2){
		printf("Usage: %s <filename>\n", argv[0]);
		exit(1);
	}
	FILE *fpin = fopen(argv[1], "r");
	FILE *fpout = popen(PAGER, "w");
	char buf[MAXLINE];
	while(fgets(buf, MAXLINE, fpin) != NULL){
		fputs(buf, fpout);
	}
	pclose(fpout);
	if(ferror(fpin)){
		printf("fgets error\n");
		exit(1);
	}
	exit(0);
}

    下面的代码是 popen 和 pclose 函数实现。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/wait.h>

#define	MAX_FD	1024			// It depends on the system
static pid_t *childpids = NULL;	 // Pointer to array allocated at run-time

FILE *myPopen(const char *cmd, const char *type){
	if(type[0]!='r'&&type[0]!='w' || type[1]!=0){// only allow "r" or "w"
		errno = EINVAL;
		return NULL;
	}
	if(childpids == NULL){		// first time through
		if((childpids = calloc(MAX_FD, sizeof(pid_t))) == NULL)
			return NULL;
	}
	int fds[2];
	if(pipe(fds) < 0)
		return NULL;			// errno set by pipe()
	if(fds[0] >= MAX_FD || fds[1] >= MAX_FD){
		close(fds[0]);
		close(fds[1]);
		errno = EMFILE;			// too many files are open.
		return NULL;
	}
	pid_t	pid;
	if((pid=fork()) < 0){
		return NULL;			// errno set by fork()
	}else if(pid == 0){			// child
		if(*type == 'r'){
			close(fds[0]);
			if(fds[1] != STDOUT_FILENO){
				dup2(fds[1], STDOUT_FILENO);
				close(fds[1]);
			}
		}else{
			close(fds[1]);
			if(fds[0] != STDIN_FILENO){
				dup2(fds[0], STDIN_FILENO);
				close(fds[0]);
			}
		}
		int i;
		for(i=0; i<MAX_FD; i++)		// close all descriptors in childpids
			if(childpids[i] > 0)
				close(i);
		execl("/bin/sh", "sh", "-c", cmd, (char *)0);
		_exit(127);                     // execl() failed
	}
	// parent continues...
	FILE *fp;
	if(*type == 'r'){
		close(fds[1]);
		if((fp = fdopen(fds[0], type)) == NULL)
			return NULL;
	}else{
		close(fds[0]);
		if((fp = fdopen(fds[1], type)) == NULL)
			return;
	}
	childpids[fileno(fp)] = pid;	// remember child pid for this fd
	return fp;
}

int myPclose(FILE *fp){
	if(childpids == NULL){		// popen has never been called
		errno = EINVAL;
		return -1;
	}
	int fd = fileno(fp);
	if(fd >= MAX_FD){			// invalid file descriptor
		errno = EINVAL;
		return -1;
	}
	pid_t pid = childpids[fd];
	if(pid == 0){				// fp wasn't opened by popen()
		errno = EINVAL;
		return -1;
	}
	childpids[fd] = 0;
	if(fclose(fp) == EOF)
		return -1;
	int stat;
	while(waitpid(pid, &stat, 0) < 0)
		if(errno != EINTR)	// error other than EINTR from waitpid
			return -1;
	return stat;			// return child's termination status
}

    其中,因为一个进程可能调用 popen 多次,所以使用了 childpids 数组来保存该进程打开的子进程 ID 和打开的文件描述符,这里选择用文件描述符作为其下标来保存子进程 ID。另外,POSIX.1 要求 popen 关闭那些以前调用 popen 打开的、现在仍然在子进程中打开着的 I/O 流,所以子进程中逐个关闭 childpids 中仍旧打开着的描述符。还有若 pclose 的调用者捕捉了 SIGCHLD 信号或其他可能中断阻塞的信号,则 waitpid 调用可能返回中断错误 EINTR,对于这种情况,我们应该再次调用 waitpid。如果 pclose 调用 waitpid 时,发现子进程已经不再存在,将返回 -1,并且 errno 会被设置为 ECHILD。
  • 大小: 4.2 KB
  • 大小: 5.7 KB
分享到:
评论

相关推荐

    C++ 命名管道 IPC 进程通信 例子

    在IT领域,进程间通信(IPC,Inter-Process Communication)是一种关键的技术,它允许不同的进程之间交换数据。在本主题中,我们将深入探讨C++如何使用命名管道(Named Pipe)进行IPC,这是一种高效且灵活的通信机制...

    TCP+IPC通信类库(源码+示例)

    2. IPC进程通信:支持本地进程间通信,支持任意类型数据(文件传输除外),模式有push和pull两种。 具体使用方法,请看示例! 给单位做考勤系统,找了一些通信框架,都不理想,索性就自己动手,类库最开始是去年年初...

    Android IPC 通信实例

    在Android系统中,IPC(Inter-Process Communication,进程间通信)是不同应用程序之间进行数据交换的重要机制。Android作为一款多任务、多应用的操作系统,各应用程序运行在各自的进程中,为了实现功能交互,就需要...

    Android Service IPC通信之Messenger机制

    当服务需要与其他应用组件或进程进行跨进程通信(IPC,Inter-Process Communication)时,Android提供了多种机制,其中一种就是Messenger。本文将深入探讨Android Service中的IPC通信机制——Messenger。 Messenger...

    LPC实现IPC通信.rar

    标题“LPC实现IPC通信.rar”表明这是一个关于使用本地过程调用(Local Procedure Call, LPC)来实现进程间通信(Inter-Process Communication, IPC)的资源包。LPC是一种在操作系统内核中直接进行进程间通信的技术,...

    IPC.rar_ipc 多核_ti dsp_多核_IPC_多核IPC通信_核间IPC

    标题中的“IPC.rar_ipc 多核_ti dsp_多核_IPC_多核IPC通信_核间IPC”揭示了本文将探讨的主题,主要集中在基于TI DSP(数字信号处理器)的多核系统中,如何实现进程间通信(IPC, Inter-Process Communication)以及...

    Android-Android跨进程IPC通信的常用例子AIDLMessengerBinder

    在Android系统中,进程间通信(Inter-Process Communication, 简称IPC)是一种关键的技术,使得不同应用程序之间能够共享数据和服务。本篇文章将详细探讨Android跨进程通信的三种常见方式:AIDL(Android Interface ...

    TMS320C6678高性能处理器如何进行TI-IPC多核通信案例.pdf

    TI-IPC是一种基于API的处理器无关通信组件,提供了简洁的接口供多核处理器之间、同一处理器内进程间以及设备间的通信使用。它支持多种通信机制,比如消息队列(MessageQ)、通知(Notify)、内存共享(SharedMemory...

    进程间通信 IPC

    "进程间通信 IPC" 进程间通信(Inter-Process Communication,IPC)是指在多个进程之间交换数据和信息的机制。它是操作系统中的一种基本机制,用于实现进程之间的协作和通信。 管道(Pipe) 管道是一种半双工的...

    ipc 进程间通信原理

    介绍关于ipc 进程间通信原理,android中binder运行机制

    IPC进程通信

    **进程间通信(IPC,Inter-Process Communication)**是操作系统中一种重要的机制,它使得不同进程之间能够共享数据、协调工作。在多进程系统中,每个进程都有自己的独立内存空间,不能直接访问其他进程的数据。因此...

    Android 基于Socket 的IPC通信

    在Android系统中,IPC(Inter-Process Communication,进程间通信)是不同应用程序之间共享数据和协同工作的关键机制。本文将深入探讨如何利用Socket实现Android进程间的通信。Socket,也被称为套接字,是网络编程的...

    C# 进程间通信,IPC通信

    NET Remoting、消息队列、WCF(集成了前述方法的功能,但太新,不支持Windows2000及以前的系统),其中Remoting可以支持TCP、...本例由一个服务端,一个客户端组成,它们之间使用IPC进行通信 1、进程间通信 2、IPC通信

    IPC之使用Socket进程间通信

    **标题解析:** "IPC之使用Socket进程间通信" 指的是利用Socket技术进行进程间的通信(IPC,Inter-Process Communication)。这里的Socket通常是指网络套接字,它在操作系统层面上提供了一种进程间通信的机制,允许...

    electronbetteripc简化Electron程序之间的IPC通信

    Electron框架在构建跨平台桌面应用时提供了一个强大的工具集,其中关键的一项是进程间通信(IPC,Inter-Process Communication)。然而,原生的Electron IPC机制可能会显得相对复杂,特别是对于初学者而言。为了简化...

    IPC通信采用socket方式

    “IPC通信采用socket方式”指的是在进程间通信(Inter-Process Communication, IPC)中,使用socket作为通信机制。Socket是网络通信的一种基础接口,它允许两个或多个进程通过网络进行数据交换,尤其适用于跨网络的...

    人工智能-项目实践-C#-一个基于WCF适用于C#项目进行IPC通信的库.zip

    本文将深入探讨一个基于Windows Communication Foundation(WCF)的人工智能项目实践,它是一个专为C#项目设计的IPC(Inter-Process Communication,进程间通信)库。这个库名为dotnetCampus.IPC.WCF,它的目标是...

    各种进程之间通信(IPC)的demo集合

    3. **共享内存(Shared Memory)**:共享内存允许两个或更多进程访问同一块内存空间,是最快的IPC方式之一。Windows提供了CreateFileMapping和MapViewOfFile函数来创建和映射共享内存。 4. **套接字(Sockets)**:虽然...

Global site tag (gtag.js) - Google Analytics