欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 八卦 > Linux进程9-无名管道:1.概述、创建、读写数据、2.进程间通信、3.读写规律、4.fcntl设置阻塞、5.文件描述符概述及复制函数dup,dup2

Linux进程9-无名管道:1.概述、创建、读写数据、2.进程间通信、3.读写规律、4.fcntl设置阻塞、5.文件描述符概述及复制函数dup,dup2

2025/5/13 2:43:12 来源:https://blog.csdn.net/weixin_45925859/article/details/147853436  浏览:    关键词:Linux进程9-无名管道:1.概述、创建、读写数据、2.进程间通信、3.读写规律、4.fcntl设置阻塞、5.文件描述符概述及复制函数dup,dup2

目录

1.无名管道

1.1无名管道的创建及读写数据

1.1.1无名管道创建

1.1.1单次读写

1.1.2多次读写

1.2 无名管道实现进程间通信

1.2.1父进程写入,子进程读取

1.2.2父、子进程单次写入读取

1.2.3父、子进程单次循环写入读取

1.3无名管道读写规律

1.3.1读写端都存在,只读不写

1.3.2读写端都存在,只写不读

1.3.3关闭写文件描述符,只有读端

1.3.4关闭读文件描述符,只有写端

1.4 fcntl 设置文件的阻塞特性

1.4.1读写端都存在,只读不写

1.4.2父子进程

2.文件描述符

2.1文件描述符编号验证

2.2文件描述符编号最大值验证

3.文件描述符的复制

3.1dup函数

3.1.1向终端输出数据

3.1.2向文件写入数据,标准输出重定向

3.1.3标准输出重定向,后续又用标准输出

3.2 dup2 函数

3.2.1输出重定向

3.2.2输出重定向后,再恢复标准输出


1.无名管道

在Linux系统中,无名管道(匿名管道)是一种用于具有亲缘关系的进程间通信(IPC)的机制。

无名管道是一种特殊类型的文件,在应用层体现为两个打开的文件描述符。 任何一个进程在创建的时候,系统都会 给他分配4G的虚拟内存,分为3G的用户空间和1G的内核空间,内核空间是所有进程公有的,无名管道就是创建在内核空间的 ,多个进程知道 同一个无名管道的空间,就可以利用他来进行通信。
无名管道虽然是在内核空间创建的,但是会给当前用户进程两个文件描述符,一个负责执行
读操作fd[0],一个负责执行写操作fd[1]。
管道是最古老的UNIX IPC方式,其特点是:
1、半双工,数据在同一时刻只能在一个方向上流动。
2、数据只能从管道的一端写入,从另一端读出。
3、写入管道中的数据遵循先入先出的规则。
4、管道所传送的数据是无格式的,这要求管道的读出方与写入方必须事先约定好数据的格
式,如多少字节算一个消息等。
5、管道不是普通的文件,不属于某个文件系统,其只存在于内存中。
6、管道在内存中对应一个缓冲区。不同的系统其大小不一定相同。
7、从管道读数据是一次性操作,数据一旦被读走,它就从管道中被抛弃,释放空间以便写
更多的数据。
8、管道没有名字,只能在具有公共祖先的进程之间使用(例如:父子进程)

核心特性

  1. 亲缘限制
    仅用于父子进程、兄弟进程等具有血缘关系的进程间通信。
  2. 半双工模式
    数据单向流动(从写端流入,读端流出),需双向通信时需要建立两个管道。
  3. 内存驻留
    不存于文件系统,进程终止后自动销毁。
  4. 队列结构
    数据先进先出(FIFO),读取后即从缓冲区移除。
  5. 阻塞机制: 读空管道时阻塞,直到数据写入或写端关闭。 写满管道时阻塞,直到读端读取数据。

1.1无名管道的创建及读写数据

1.1.1无名管道创建

函数原型:

#include <unistd.h>
int pipe(int pipefd[2]);  // 创建管道pipefd[0]:固定为读端
pipefd[1]:固定为写端
成功返回0,失败返回-1

1.1.1单次读写

程序:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>int main(int argc, char const *argv[])
{//使用pipe创建一个无名管道int fd_pipe[2];if(pipe(fd_pipe) == -1){perror("fail to pipe");exit(1);}//文件描述符0:标准输入(stdin)、1:标准输出(stdout)、2:标准输出(stdout)printf("fd_pipe[0] = %d\n", fd_pipe[0]);printf("fd_pipe[1] = %d\n", fd_pipe[1]);//对无名管道执行读写操作//由于无名管道给当前用户进程两个文件描述符,所以只要操作这两个文件//描述符就可以操作无名管道,所以通过文件IO中的read和write函数对无名管道进行操作//通过write函数向无名管道中写入数据//fd_pipe[1]负责执行写操作//write函数原型 ssize_t write(int fd, const void *buf, size_t count);if(write(fd_pipe[1], "hello world", strlen("hello world")+1) == -1){perror("fail to write");//失败exit(1);//退出}//通过read函数从无名管道中读取数据//fd_pipe[0]负责执行读操作char buf[32] = {0};ssize_t bytes;if((bytes = read(fd_pipe[0], buf, 20)) == -1){perror("fail to read");exit(1);}printf("buf = [%s]\n", buf);printf("bytes = %ld\n", bytes);return 0;
}

运行结果:

1.1.2多次读写

程序:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>int main(int argc, char const *argv[])
{//使用pipe创建一个无名管道int fd_pipe[2];if(pipe(fd_pipe) == -1){perror("fail to pipe");exit(1);}printf("fd_pipe[0] = %d\n", fd_pipe[0]);printf("fd_pipe[1] = %d\n", fd_pipe[1]);//对无名管道执行读写操作//由于无名管道给当前用户进程两个文件描述符,所以只要操作这两个文件//描述符就可以操作无名管道,所以通过文件IO中的read和write函数对无名管道进行操作//通过write函数向无名管道中写入数据//fd_pipe[1]负责执行写操作//如果管道中有数据,再次写入的数据会放在之前数据的后面,不会把之前的数据替换if(write(fd_pipe[1], "hello world", strlen("hello world")) == -1){perror("fail to write");exit(1);}//第二次写入if(write(fd_pipe[1], "nihao beijing", strlen("nihao beijing")+1) == -1){perror("fail to write");exit(1);}//通过read函数从无名管道中读取数据//fd_pipe[0]负责执行读操作//读取数据时,直接从管道中读取指定个数的数据,如果管道中没有数据了,则read函数会阻塞等待char buf[32] = "";ssize_t bytes;if((bytes = read(fd_pipe[0], buf, 20)) == -1){perror("fail to read");exit(1);}printf("buf = [%s]\n", buf);printf("bytes = %ld\n", bytes);//第二次读取,上次剩余字节数据bytes = read(fd_pipe[0], buf, sizeof(buf));printf("buf = [%s]\n", buf);printf("bytes = %ld\n", bytes);//第3次读取bytes = read(fd_pipe[0], buf, sizeof(buf));printf("buf = [%s]\n", buf);printf("bytes = %ld\n", bytes);return 0;
}

运行结果:分两次写入数据,先读取一部分数据后,第二次在读取剩余数据,第三次读取数据时管道中的内容为空,程序阻塞等待。

1.2 无名管道实现进程间通信

1.2.1父进程写入,子进程读取

程序:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>//使用无名管道实现父子进程间的通信
//由于无名管道创建之后给当前进程两个文件描述符,所以如果是完全不相关的进程
//无法获取同一个无名管道的文件描述符,所以无名管道只能在具有亲缘关系的进程间通信int main(int argc, char const *argv[])
{//创建一个无名管道int pipefd[2];if(pipe(pipefd) == -1){perror("fail to pipe");exit(1);}//使用fork函数创建子进程pid_t pid;if((pid = fork()) < 0){perror("fail to fork");exit(1);}else if(pid > 0) // 父进程{//父进程负责给子进程发送数据 char buf[128] = {};while(1){fgets(buf, sizeof(buf), stdin);//终端输入数据buf[strlen(buf) - 1] = '\0';//字符串最后一位为 \0//向管道写入数据if(write(pipefd[1], buf, sizeof(buf)) == -1){perror("fail to write");exit(1);}} }else //子进程{//子进程接收父进程的数据char buf[128] = "";while(1){//从管道读取数据if(read(pipefd[0], buf, sizeof(buf)) == -1){perror("fail to read");exit(1);}printf("读取:父进程写入内容: %s\n", buf);}}return 0;
}

运行结果:

1.2.2父、子进程单次写入读取

程序:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <stdlib.h>/*
每个管道提供单向通信:
‌pipe1‌:父进程写(fd1[1]),子进程读(fd1[0])
‌pipe2‌:子进程写(fd2[1]),父进程读(fd2[0])
*///双方读写顺序:父先写,子先读;子再写,父再读
int main() 
{int fd1[2], fd2[2];  // 定义两个管道pipe(fd1);            // 创建管道1pipe(fd2);            // 创建管道2pid_t pid = fork();if (pid > 0) {  // 父进程close(fd1[0]);  // 关闭管道1 的读端close(fd2[1]);  // 关闭管道2 的写端// 父进程写入数据到管道1char msg[] = "Hello from parent";write(fd1[1], msg, strlen(msg) + 1);// 父进程从管道2读取子进程的回复char buffer[128];read(fd2[0], buffer, sizeof(buffer));printf("Parent received: %s\n", buffer);close(fd1[1]);  // 关闭管道1 的写端close(fd2[0]);  // 关闭管道2 的读端wait(NULL);     // 等待子进程结束} else if (pid == 0) {  // 子进程close(fd1[1]);  // 关闭管道1 的写端close(fd2[0]);  // 关闭管道2 的读端// 子进程从管道1读取父进程的数据char buffer[128];read(fd1[0], buffer, sizeof(buffer));printf("Child received: %s\n", buffer);// 子进程回复数据到管道2char reply[] = "Hello from child";write(fd2[1], reply, strlen(reply) + 1);close(fd1[0]);  // 关闭管道1 的读端close(fd2[1]);  // 关闭管道2 的写端exit(1);} else{perror("fail to fork");return -1;}return 0;}

运行结果:

1.2.3父、子进程单次循环写入读取

程序:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>//使用无名管道实现父子进程间的通信
//由于无名管道创建之后给当前进程两个文件描述符,所以如果是完全不相关的进程
//无法获取同一个无名管道的文件描述符,所以无名管道只能在具有亲缘关系的进程间通信int main(int argc, char const *argv[])
{//创建2个无名管道int pipefd[2];//父进程写(fd[1]),子进程读(fd[0])int pipefd1[2];//子进程写(fd1[1]),父进程读(fd1[0])if(pipe(pipefd) == -1){perror("fail to pipe");exit(1);}if(pipe(pipefd1) == -1){perror("fail to pipe1");exit(1);}//使用fork函数创建子进程pid_t pid;if((pid = fork()) < 0){perror("fail to fork");exit(1);}else if(pid > 0) // 父进程{//父进程负责给子进程发送数据 char buf[128] = {0};char buf1[128] = {0};while(1){printf("Parent input: ");fgets(buf, sizeof(buf), stdin);//终端输入数据,回车printf("strlen(buf): %d\n", strlen(buf));buf[strlen(buf) - 1] = '\0';//将fgets最后输入的\n替换为\0,否则终端会多余空行//向管道写入数据if(write(pipefd[1], buf, sizeof(buf)) == -1){perror("fail to write");exit(1);}//从管道读取数据if(read(pipefd1[0], buf1, sizeof(buf1)) > 0){//buf1[strlen(buf1) ] = '\0';//字符串最后一位为 \0printf("父进程读取:子进程写入内容: %s\n", buf1);}          } }else //子进程{//子进程接收父进程的数据char buf[128] = {0};char buf1[128] = {0};while(1){//从管道读取数据if(read(pipefd[0], buf, sizeof(buf)) > 0){printf("子进程读取:父进程写入内容: %s\n", buf);}printf("son input: ");fgets(buf1, sizeof(buf1), stdin);//终端输入数据buf1[strlen(buf1) - 1] = '\0';//向管道写入数据if(write(pipefd1[1], buf1, sizeof(buf1)) == -1){perror("fail to write");exit(1);}          }}return 0;
}

运行结果:

1.3无名管道读写规律

读写数据的特点

1、默认用 read 函数从管道中读数据是阻塞的。

2、调用 write 函数向管道里写数据,当缓冲区已满时 write 也会阻塞。

3、通信过程中,读端口全部关闭后,写进程向管道内写数据时,写进程会(收到 SIGPIPE 信号)退出。

1.3.1读写端都存在,只读不写

程序:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>int main(int argc, char const *argv[])
{int pipefd[2];if(pipe(pipefd) == -1){perror("fail to pipe");exit(1);}//读写端都存在,只读不写//如果管道中有数据,会正常读取数据//如果管道中没有数据,则读操作会阻塞等待,直到有数据为止//先写入数据验证:如果管道中有数据,会正常读取数据write(pipefd[1], "hello world", 11);char buf[128] = "";//读取管道数据if(read(pipefd[0], buf, sizeof(buf)) == -1){perror("fail to read");exit(1);}printf("buf = %s\n", buf);//当前管道中已没有数据,读操作会阻塞等待if(read(pipefd[0], buf, sizeof(buf)) == -1){perror("fail to read");exit(1);}printf("buf = %s\n", buf);return 0;
}

运行结果:读写端都存在,只读不写,如果管道中有数据,会正常读取数据;如果管道中没有数据,则读操作会阻塞等待,直到有数据为止。

1.3.2读写端都存在,只写不读

程序:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int main(int argc, char const *argv[])
{int pipefd[2];if(pipe(pipefd) == -1){perror("fail to pipe");exit(1);}//读写端都存在,只写不读//如果一直执行写操作,则无名管道对应的缓冲区会被写满,写满之后,write函数也会阻塞等待//默认无名管道的缓冲区64K字节(65536字节)int num = 0;while(1){if(write(pipefd[1], "6666", 1024) == -1){perror("fail to write");exit(1);}num++;printf(" 第 %d 次写入 \n", num);}return 0;
}

运行结果:读写端都存在,只写不读 ,如果一直执行写操作,则无名管道对应的缓冲区会被写满,写满之后,write函数也会阻塞等待;默认无名管道的缓冲区64K字节(65536字节)

1.3.3关闭写文件描述符,只有读端

程序:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>int main(int argc, char const *argv[])
{int pipefd[2];if(pipe(pipefd) == -1){perror("fail to pipe");exit(1);}//先写入数据write(pipefd[1], "hello world",11);//关闭写文件描述符,只有读端//如果原本管道中有数据,则读操作正常读取数据//如果管道中没有数据,则read函数会返回0close(pipefd[1]);//关闭写端,保留读端char buf[128] = "";ssize_t bytes;//第一次将管道中的数据全部读取if((bytes = read(pipefd[0], buf, sizeof(buf))) == -1){perror("fail to read");exit(1);}printf("bytes = %ld\n", bytes);printf("buf = %s\n", buf);//清除字符串中的内容memset(buf, 0, sizeof(buf));//第2次将读取管道中的数据,管道中已没有数据,则read函数会返回0if((bytes = read(pipefd[0], buf, sizeof(buf))) == -1){perror("fail to read");exit(1);}printf("bytes = %ld\n", bytes);printf("buf = %s\n", buf);return 0;
}

运行结果:关闭写文件描述符,只有读端;如果原本管道中有数据,则读操作正常读取数据;如果管道中没有数据,则read函数会返回0;

1.3.4关闭读文件描述符,只有写端

程序:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>//自定义处理信号函数
void handler(int sig)
{printf("SIGPIPE信号产生了,管道破裂了\n");
}int main(int argc, char const *argv[])
{//以自定义的方式处理信号signal(SIGPIPE, handler);int pipefd[2];if(pipe(pipefd) == -1){perror("fail to pipe");exit(1);}//关闭读操作文件描述符,只有写端//如果关闭读端,一旦执行写操作,就会产生一个信号SIGPIPE(管道破裂),//这个信号的默认处理方式是退出进程close(pipefd[0]);//关闭读端int num = 0;while(1){if(write(pipefd[1], "hello world", 1024) == -1){perror("fail to write");exit(1);}num++;printf("num = %d\n", num);}return 0;
}

运行结果:关闭读操作文件描述符,只有写端 ;如果关闭读端,一旦执行写操作,就会产生一个信号SIGPIPE(管道破裂);这个信号的默认处理方式是退出进程;

1.4 fcntl 设置文件的阻塞特性

函数原型:

#include <unistd.h>
#include <fcntl.h>int fcntl(int fd, int cmd, ... /* arg */);
参数:
fd:需操作的文件描述符
cmd:控制命令类型,决定后续参数形式(可能无需参数、整型参数或结构体指针)主要命令(cmd)及功能
1.复制文件描述符F_DUPFD: 复制描述符,返回大于等于指定值的最小可用描述符。
F_DUPFD_CLOEXEC: 复制描述符并设置 FD_CLOEXEC 标志(执行时关闭)。
2.文件描述符标志F_GETFD: 获取描述符标志(如 FD_CLOEXEC)。
F_SETFD: 设置描述符标志。
3.文件状态标志F_GETFL: 获取文件状态标志(如 O_RDWR, O_NONBLOCK, O_APPEND)。
F_SETFL: 设置文件状态标志
4.信号驱动 I/OF_GETOWN: 获取接收 SIGIO 信号的进程/进程组。
F_SETOWN: 设置接收 SIGIO 信号的进程/进程组。
5.文件锁F_GETLK: 检查锁状态(通过 struct flock 返回锁信息)。
F_SETLK: 非阻塞设置锁(读锁 F_RDLCK/写锁 F_WRLCK/解锁 F_UNLCK)。
F_SETLKW: 阻塞设置锁(等待直到锁可用)/*设置阻塞和非阻塞 例子*/
设置为阻塞:
fcntl(fd, F_SETFL, 0);
设置为非阻塞:
fcntl(fd, F_SETFL, O_NONBLOCK);非阻塞:
如果是阻塞,管道中没有数据,read会一直等待,直到有数据才会继续运行,否则一
直等待;
如果是非阻塞,read函数运行时,会先看一下管道中是否有数据,如果有数据,则正
常运行读取数据,如果管道中没有数据,则read函数会立即返回,继续下面的代码运行;

1.4.1读写端都存在,只读不写

程序:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>int main(int argc, char const *argv[])
{int pipefd[2];if(pipe(pipefd) == -1){perror("fail to pipe");exit(1);}//将pipefd[0]设置为阻塞//fcntl(pipefd[0], F_SETFL, 0);//将pipefd[0]设置为非阻塞fcntl(pipefd[0], F_SETFL, O_NONBLOCK);//读写端都存在,只读不写//如果管道中有数据,会正常读取数据//如果管道中没有数据,则读操作会阻塞等待,直到有数据为止//先写入数据验证:如果管道中有数据,会正常读取数据write(pipefd[1], "hello world", 11);char buf[128] = "";//读取管道数据if(read(pipefd[0], buf, sizeof(buf)) == -1){perror("fail to read");exit(1);}printf("buf = %s\n", buf);memset(buf, 0, sizeof(buf));//buf清0//当前管道中已没有数据,读操作默认·会阻塞等待read(pipefd[0], buf, sizeof(buf));//若是默认阻塞不会执行下面的printf,设为非阻塞会执行下面的printf	printf("第二次读取buf = %s\n", buf);return 0;
}

运行结果:fcntl设置为非阻塞,当管道内容为空时,读取不阻塞。(默认阻塞,见章节1.3.1)

1.4.2父子进程

(1)阻塞

程序:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>int main(int argc, char *argv[])
{int fd_pipe[2];char buf[] = "hello world";pid_t pid;if (pipe(fd_pipe) < 0){perror("fail to pipe");exit(1);}pid = fork();if (pid < 0){perror("fail to fork");exit(0);}if (pid == 0)//子进程{while(1){sleep(3);//3秒后写入数据write(fd_pipe[1], buf, strlen(buf));}}else //父进程{//将fd_pipe[0]设置为阻塞//fcntl(fd_pipe[0], F_SETFL, 0);//将fd_pipe[0]设置为非阻塞//fcntl(fd_pipe[0], F_SETFL, O_NONBLOCK);while(1){memset(buf, 0, sizeof(buf));read(fd_pipe[0], buf, sizeof(buf));printf("buf=[%s]\n", buf);sleep(1);}}return 0;
}

运行结果:阻塞时,子进程每3秒向管道写入一次数据,父进程才读取管道数据。

(2)fcntl设置为非阻塞

程序:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>int main(int argc, char *argv[])
{int fd_pipe[2];char buf[] = "hello world";pid_t pid;if (pipe(fd_pipe) < 0){perror("fail to pipe");exit(1);}pid = fork();if (pid < 0){perror("fail to fork");exit(0);}if (pid == 0)//子进程{while(1){sleep(3);//3秒后写入数据write(fd_pipe[1], buf, strlen(buf));}}else //父进程{//将fd_pipe[0]设置为阻塞//fcntl(fd_pipe[0], F_SETFL, 0);//将fd_pipe[0]设置为非阻塞fcntl(fd_pipe[0], F_SETFL, O_NONBLOCK);while(1){memset(buf, 0, sizeof(buf));read(fd_pipe[0], buf, sizeof(buf));printf("buf=[%s]\n", buf);sleep(1);}}return 0;
}

运行结果:

管道设置为非阻塞,父进程每过1秒,读取一次数据;子进程每3秒写入一次数据,父进程下次读取数据才不为空。

2.文件描述符

文件描述符是非负整数,是文件的标识。

用户使用文件描述符(file descriptor)来访问文件。 利用open打开一个文件时,内核会返回一个文件描述符。

每个进程都有一张文件描述符的表,进程刚被创建时,标准输入、标准输出、标准错误输出

设备文件被打开,对应的文件描述符0、1、2 记录在表中。

在进程中打开其他文件时,系统会返回文件描述符表中最小可用的文件描述符,并将此文件

描述符记录在表中。

注意:
Linux中一个进 程最多只能打开 NR_OPEN_DEFAULT (即 1024 )个文件,故当文件不再使用时应及时调用close函数关闭文件。

2.1文件描述符编号验证

程序:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>int main(int argc, char const *argv[])
{//在进程中打开其他文件时,//系统会返回文件描述符表中最小可用的文件描述符,//并将此文件描述符记录在进程的文件描述符表中。//文件描述符 0:标准输入(stdin)、1:标准输出(stdout)、2:标准输出(stdout)int fd1, fd2, fd3;fd1 = open("file.txt", O_RDONLY | O_CREAT, 0664);fd2 = open("file.txt", O_RDONLY | O_CREAT, 0664);fd3 = open("file.txt", O_RDONLY | O_CREAT, 0664);printf("fd1 = %d\n", fd1);printf("fd2 = %d\n", fd2);printf("fd3 = %d\n", fd3);return 0;
}

运行结果:

2.2文件描述符编号最大值验证

程序:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>int main(int argc, char const *argv[])
{//Linux中一个进程最多只能打开NR_OPEN_DEFAULT(即1024)个文件,//故当文件不再使用时应及时调用close函数关闭文件int fd;while(1){if((fd = open("file.txt", O_RDONLY | O_CREAT, 0664)) < 0){perror("fail to open");exit(1);}printf("fd = %d\n", fd);}return 0;
}
运行结果:

3.文件描述符的复制

3.1dup函数

函数原型:

#include <unistd.h>
int dup(int oldfd);功能:
复制 oldfd 文件描述符,并分配一个新的文件描述符,新的文件描述符是
调用进程文件描述符表中最小可用的文件描述符。参数:
要复制的文件描述符 oldfd。返回值:
成功:新文件描述符。
失败:返回-1,错误代码存于 errno 中。

3.1.1向终端输出数据

程序:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>int main(void)
{//通过dup函数复制一个文件描述符int fd;//dup执行后给返回值文件描述符分配的值是文件描述符表中最小可用的文件描述符fd = dup(1);//文件描述符 0:标准输入(stdin)、1:标准输出(stdout)、2:标准输出(stdout)printf("fd = %d\n", fd);//fd = 3, 0 1 2已经存在//由于通过dup函数将1这个文件描述符复制了一份为fd,所以fd现在就相当于1,所以写数据就是向终端(标准输出)写入数据write(fd, "nihao beijing\n", strlen("nihao beijing\n"));return 0;
}

运行结果:

3.1.2向文件写入数据,标准输出重定向

程序:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>//文件描述符 0:标准输入(stdin)、1:标准输出(stdout)、2:标准输出(stdout)
int main(int argc, char *argv[])
{//如果需要实现输出重定向的功能//首先像printf函数是操作文件描述符1所对应的文件,//默认是操作终端,只要能够把1对应标识的文件改变,就可以实现输出重定向//所以实现创建好文件对应的文件描述符之后,将1文件描述符关闭,接着通过dup//函数复制的新的文件描述符就是1,这样printf函数对1操作,就写到了文件中int fd_file;fd_file = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0664);printf("fd_file = %d\n", fd_file);if(fd_file == -1){perror("fail to open");exit(1);}close(1);//关闭1:标准输出(stdout)后,文件描述符表中最小可用的文件描述符为1//dup执行后给返回值文件描述符分配的值是文件描述符表中最小可用的文件描述符int fd = dup(fd_file);//dup函数将fd_file这个文件描述符复制了一份为fd,
//所以fd现在就相当于fd_file; 1:标准输出(stdout) = fd => fd_file//printf函数默认向向终端(标准输出:1)写入数据,现在1指向test.txt的fd_file文件描述符//数据输出到test.txtprintf("hello world\n");printf("fd = %d\n", fd);// 1return 0;
}

运行结果:关闭1:标准输出(stdout)后,文件描述符表中最小可用的文件描述符为1;dup函数将fd_file这个文件描述符复制了一份为fd,所以fd现在就相当于fd_file;printf函数默认向终端(标准输出:1)写入数据,现在(标准输出:1)指向test.txt的fd_file文件描述符,数据输出到test.txt;

3.1.3标准输出重定向,后续又用标准输出

程序:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main(int argc, char *argv[])
{int fd1;int fd2;//文件描述符 0:标准输入(stdin)、1:标准输出(stdout)、2:标准输出(stdout)fd2 = dup(1);//dup函数将标准输出1这个文件描述符复制了一份为fd2,所以fd2现在就相当于标准输出1printf("new:fd2 = %d\n",fd2);// 3fd1 = open("test.txt", O_RDWR | O_CREAT, 0664);close(1);//关闭1:标准输出(stdout)后,文件描述符表中最小可用的文件描述符为1int fd3 = dup(fd1);//fd3现在就相当于test.txt的文件描述符fd1//printf函数默认向向终端(标准输出:1)写入数据,现在1指向test.txt的fd1文件描述符//数据输出到test.txt; 1:标准输出(stdout) = fd3 => fd1printf("hello world\n");printf("fd3 = %d\n", fd3);// 1close(1);//关闭1:标准输出(stdout)后,文件描述符表中最小可用的文件描述符为1int fd4 = dup(fd2);// 1:标准输出 = fd4 => fd2 为 1:标准输出,值为3printf("nihao beijing\n");printf("fd4 = %d\n", fd4);// 1return 0;
}

运行结果:printf函数是操作文件描述符1所对应的文件,默认是操作终端。

程序先将1:标准输出(stdout)重定向到test.txt文件,再将1:标准输出(stdout)重定向回终端。

3.2 dup2 函数

函数原型:

#include <unistd.h>
int dup2(int oldfd, int newfd)功能:
复制一份打开的文件描述符 oldfd,并分配新的文件描述符 newfd,
newfd 也标识 oldfd 所标识的文件。注意:
newfd 是小于文件描述符最大允许值的非负整数,如果 newfd 是一个
已经打开的文件描述符,则首先关闭该文件,然后再复制。参数:
要复制的文件描述符 oldfd
分配的新的文件描述符 newfd返回值:
成功:返回 newfd
失败:返回-1,错误代码存于 errno 中

3.2.1输出重定向

程序:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>//文件描述符 0:标准输入(stdin)、1:标准输出(stdout)、2:标准输出(stdout)
int main(void)
{int fd1;int fd2;fd1 = open("test.txt", O_CREAT | O_WRONLY | O_TRUNC, 0664);if (fd1 < 0){perror("fail to open");exit(1);}//首先关闭1(标准输出)文件描述符,然后将fd1复制给1,//意味着1和fd1都标识test.txt文件,返回值跟1是一样的fd2 = dup2(fd1, 1);printf("hello world\n");printf("fd2 = %d\n", fd2);return 0;
}

运行结果:

3.2.2输出重定向后,再恢复标准输出

(1)dup2第二个参数对应的变量赋一个初始值

程序:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>//文件描述符 0:标准输入(stdin)、1:标准输出(stdout)、2:标准输出(stdout)
int main(int argc, char *argv[])
{int fd1;//如果使用dup2,则需要实现给第二个参数对应的变量赋一个初始值//不赋初值则,dup2第二个参数的值为当前 未使用的第一个文件描述符int fd2 = 3;//将1复制一份为fd2,所以fd2标识的是标准输出dup2(1, fd2);// fd2 => 1:标准输出(stdout)printf("fd2 = %d\n", fd2);fd1 = open("test.txt", O_CREAT | O_RDWR | O_TRUNC, 0664);//输出重定向:关闭文件描述符1,将fd1复制一份为1,所以1此时标识的是test.txt文件dup2(fd1, 1);// 1:标准输出(stdout) => fd1printf("hello world\n");//再次实现标准输出:关闭文件描述符1,将fd2复制一份为1,所以1此时标识的是标准输出dup2(fd2, 1); // 1:标准输出(stdout) => fd2 (本身fd2标识的是标准输出)printf("你好北京\n");return 0;
}

运行结果:

(2)dup2第二个参数对应的变量,不赋一个初始值

程序:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>//文件描述符 0:标准输入(stdin)、1:标准输出(stdout)、2:标准输出(stdout)
int main(int argc, char *argv[])
{int fd1;//如果使用dup2,则需要实现给第二个参数对应的变量赋一个初始值//不赋初值则,dup2第二个参数的值为当前 未使用的第一个文件描述符int fd2;//将1复制一份为fd2,所以fd2标识的是标准输出dup2(1, fd2);// fd2 => 1:标准输出(stdout)printf("fd2 = %d\n", fd2);fd1 = open("test.txt", O_CREAT | O_RDWR | O_TRUNC, 0664);//输出重定向:关闭文件描述符1,将fd1复制一份为1,所以1此时标识的是test.txt文件dup2(fd1, 1);// 1:标准输出(stdout) => fd1printf("hello world\n");//再次实现标准输出:关闭文件描述符1,将fd2复制一份为1,所以1此时标识的是标准输出dup2(fd2, 1); // 1:标准输出(stdout) => fd2 (本身fd2标识的是标准输出)printf("你好北京\n");return 0;
}

运行结果:如果使用dup2,则需要实现给第二个参数对应的变量赋一个初始值;不赋初值则,dup2第二个参数的值为当前 未使用的第一个文件描述符;比如:0:标准输入(stdin),没有使用,则dup2第二个参数的值为0,如果后续要使用scanf等输入函数时,会受到影响。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词