一、管道(pipe)的用法:

进程在使用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:部分代码来自互联网。

Logo

更多推荐