进程在使用fork函数创建子进程前先创建一个管道,该管道用于在父子进程间通信,然后创建子进程,之后父进程关闭管道的读端,子进程关闭管道的写端。父进程负责向管道写数据而子进程负责读数据。也可以父进程关闭管道的写端,子进程关闭管道的读端。这样管道就可以用于父子进程间的通信,也可以用于兄弟进程间的通信。
linux下创建管道可以通过函数pipe来完成。该函数如果调用成功返回0,并且数组中将包含两个新的文件描述符;如果有错误发生,返回-1。
#include<unistd.h>
int pipe(int fd[2])
管道两端可分别用描述符fd[0]以及fd[1]来描述。一个端只能用于读,由描述符fd[0]表示,称其为管道读端;另一端只能用于写,由描述符fd[1]来表示,称其为管道写端。如果试图从管道写端读数据或者从管道读端写数据,都将导致出错。
示例代码1:
一个管道实现半双工通信
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<sys/types.h> #include<unistd.h> /*读管道*/ void read_from_pipe(int fd) { char message[100]; read(fd,message,100); printf("read from pipe:%s",message); } /*写管道*/ void write_to_pipe(int fd) { char *message="Hello,pipe!\n"; write(fd,message,strlen(message)+1); } int main() { int fd[2]; pid_t pid; int stat_val; if(pipe(fd)) { printf("create pipe failed!\n"); exit(1); } pid=fork(); switch(pid) { case -1: printf("fork error!\n"); exit(1); case 0: //子进程 close(fd[1]);//关闭子进程写端 read_from_pipe(fd[0]);//子进程读 exit(0); default: //父进程 close(fd[0]);//关闭父进程读端 write_to_pipe(fd[1]);//父进程写 wait(&stat_val);//等待子进程 exit(0); } return 0; }运行结果:
pc@ubuntu:~/linux_lan/pipe$ ./pipe
read from pipe:Hello,pipe!
示例代码2:
两个管道实现全双工:
#include<stdio.h> #include<stdlib.h> #include<sys/types.h> #include<unistd.h> #include<string.h> //子进程读写管道的函数 void child_rw_pipe(int readfd,int writefd) { char *message1="from child process!\n"; write(writefd,message1,strlen(message1)+1); char message2[100]; read(readfd,message2,100); printf("child process read from pipe:%s",message2); } //父进程读写管道的函数 void parent_rw_pipe(int readfd,int writefd) { char *message1="from parent process!\n"; write(writefd,message1,strlen(message1)+1); char message2[100]; read(readfd,message2,100); printf("parent process read pipe:%s",message2); } int main() { int pipe1[2],pipe2[2]; pid_t pid; int stat_val; printf("realize full-duplex communication:\n\n"); if(pipe(pipe1))//创建管道1 { printf("pipe1 failed\n"); exit(1); } if(pipe(pipe2))//创建管道2 { printf("pipe2 failed\n"); exit(1); } pid=fork();//创建进程 switch(pid) { case -1: printf("fork error!\n"); exit(1); //子进程 case 0: close(pipe1[1]);//子进程管道1关闭写端 close(pipe2[0]);//子进程管道2关闭读端 child_rw_pipe(pipe1[0],pipe2[1]);//子进程管道1控制读端,管道2控制写端 exit(0); default: close(pipe1[0]);//父进程管道1关闭读端 close(pipe2[1]);//父进程管道2关闭写端 parent_rw_pipe(pipe2[0],pipe1[1]);//父进程管道1控制写端,管道2控制读端 wait(&stat_val); exit(0); } return 0; }
pc@ubuntu:~/linux_lan/pipe$ ./dual_pipe
realize full-duplex communication:
parent process read pipe:from child process!
child process read from pipe:from parent process!
二、dup&dup2
1.文件描述符:
内核(kernel)利用文件描述符(file descriptor)来访问文件。文件描述符是非负整数。打开现存文件或新建文件时,内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定待读写的文件。
标准输入(standard input)的文件描述符是 0,标准输出(standard output)是 1,标准错误(standard error)是 2。尽管这种习惯并非Unix内核的特性,但是因为一些 shell 和很多应用程序都使用这种习惯,因此,如果内核不遵循这种习惯的话,很多应用程序将不能使用。
POSIX 定义了 STDIN_FILENO、STDOUT_FILENO 和 STDERR_FILENO 来代替 0、1、2。这三个符号常量的定义位于头文件 unistd.h。
进程获取文件描述符最常见的方法是通过本机子例程open或create获取或者通过从父进程继承。后一种方法允许子进程同样能够访问由父进程使用的文件。文件描述符对于每个进程一般是唯一的。当用fork子例程创建某个子进程时,该子进程会获得其父进程所有文件描述符的副本,这些文件描述符在执行fork时打开。在由fcntl、dup和dup2子例程复制或拷贝某个进程时,会发生同样的复制过程。
dup dup2
#include<unistd.h>
int dup(int oldfd);
int dup2(int oldfd,int newfd);
dup和dup2函数调用成功时返回一个oldfd文件描述符的副本,失败则返回-1。所不同的是,由dup函数返回的文件描述符是当前可用文件描述符中最小数值,而dup2函数则可以利用参数newfd指定欲返回的文件描述符。如果参数newfd指定的文件描述符已经打开,系统先将其关闭,然后将oldfd指定的文件描述符赋值到该参数。如果newfd等于oldfd,则dup2返回newfd,而不是关闭它。
/dup程序片段*/
pid=fork();
if(pid==0)
{
/*关闭子进程的标准输出*/
close(1);
//复制管道输入端到标准输出
dup(fd[1])
execve("exam",argv,environ);
}
/*dup2程序片段*/
pid=fork();
if(pid==0)
{
/*关闭标准输出并复制管道输入端到标准输出*/
dup2(1,fd[1]);
execve("exam",argv,environ);
}
可见,dup2系统调用将close操作和文件描述符拷贝操作集成在同一个函数里,而且保证操作具有原子性。
2.示例代码:
#include <unistd.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> int main(void) { int fd, save_fd; char msg[] = "This is a test of dup() & dup2()\n"; int test; fd = open("zhonghe.txt", O_RDWR|O_CREAT, S_IRUSR|S_IWUSR); if(fd<0) { perror("open"); exit(1); } save_fd = dup(STDOUT_FILENO); //运行后save_fd指向STDOUT_FILENO,即save_fd指向标准输出 printf("save_fd=%d\n",save_fd); //测试用 test=dup2(fd, STDOUT_FILENO); //运行后STDOUT_FILENO指向fd所指向的文件,即STDOUT_FILENO指向zhonghe.txt printf("dup2_1=%d\n",test); //测试用 此时的标准输出不再指向显示器,因此该段测试将写入zhonghe.txt文件中 close(fd); write(STDOUT_FILENO, msg, strlen(msg)); //此时STDOUT_FILENO所指向的是zhonghe.txt文件不再是标准输出流,因此该段将 //写入zhonghe.txt文件中 test=dup2(save_fd, STDOUT_FILENO); //运行后STDOUT_FILENO指向save_fd所指向的文件,即标准输出流 printf("dup2_2=%d\n",test); //测试用 此时标准输出流重新指回显示器,因此该段测试将写入显示器 write(STDOUT_FILENO, msg, strlen(msg)); //此时STDOUT_FILENO所指向的便回标准输出流该段将写入显示器 close(save_fd); return 0; }运行结果:
pc@ubuntu:~/linux_lan/pipe$ ./test_zh
save_fd=4
dup2_2=1
This is a test of dup() & dup2()
并且在目录下创建名为zhonghe.txt文件,内容为:
dup2_1=1
This is a test of dup() & dup2()
PS:部分代码来自互联网。