目录
前言
问:fd 为什么默认从 3 开始,而不是从 0,1,2?
💬 前面还存在默认标准文件,stdin, stdout, stderr ,和 0,1,2一一对应
验证:
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> int main(void) { int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666); if (fd < 0) { perror("open"); return 1; } printf("fd: %d\n", fd); close(fd); }
1.文件描述符分配规则
❗ 从 0 下标开始,寻找最小的没有使用的数组位置,它的下标就是新文件的文件描述符
(1) 关闭0,对返回的 fd 进行测试
int main(void) { close(0); int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666); if (fd < 0) { perror("open"); return 1; } printf("fd: %d\n", fd); close(fd); }
运行:
(2)关闭 1
#include<stdio.h> #include<string.h> #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> #include<unistd.h> #include<stdlib.h> int main() { // close(0); // close(2); close(1); umask(0); int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666); if(fd == -1) { perror("open"); exit(1); } printf("fd:%d\n",fd); //这里必须刷新一下,不然log.txt里面没有内容,这里和缓冲区有关,下面讲 fflush(stdout); close(fd); return 0; }
运行;
❓ 本来应该直接打到显示器上的消息,打印到了 log.txt 上,是为什么呢?
A: 进程不 care 1 文件发生的变化,先关闭再 open,根据验证规则,会实现对 1 文件的补充重定向
❗ 重定向的本质:上层用的 fd 不变,在内核中更改 fd 对应的 struct file* 地址
2.dup2 重定向接口
man dup2
NAME 中提到:dup 复制文件描述符
函数功能:
dup2
函数将文件描述符oldfd
复制到newfd
,并返回一个新的文件描述符。如果newfd
已经打开,则将先关闭它。新的文件描述符将继承oldfd
的文件状态标志(例如,文件偏移量、文件访问模式等)。函数返回值:
- 成功时,返回新的文件描述符;失败时,返回-1,并设置errno变量来指示错误类型。
具体应用: new be old ,新的被老的覆盖,所以保留的是 old,dup2(old, new),所以前一个文件会拷贝形成 double 份,如果想实现对之前操作的实现,即 dup2(fd,1)
tip: 拷贝的是下标指针 struct 的 拷贝替换
#include<sys/stat.h> #include<fcntl.h> #include<unistd.h> #include<stdlib.h> int main() { // close(0); // close(2); // close(1); umask(0); int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666); if(fd == -1) { perror("open"); exit(1); } //重定向 dup2(fd,1); printf("fd:%d\n",fd); //这里必须刷新一下,不然log.txt里面没有内容 fflush(stdout); close(fd); return 0; }
运行:
fd 覆盖了 log,应该输出到 1 中的,就到 fd 文件里面了
3.重定向
3.1>输出重定向
上面内容就是输出重定向,把新打开文件的fd重定向到fd为1(默认为显示器)的位置。
3.2>>追加重定向
实现在文件原内容后面的添加
#include<stdio.h> #include<string.h> #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> #include<unistd.h> #include<stdlib.h> int main() { // close(0); // close(2); // close(1); umask(0); // int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666); int fd=open("log.txt",O_WRONLY|O_CREAT|O_APPEND,0666); if(fd == -1) { perror("open"); exit(1); } //重定向 dup2(fd,1); printf("add hello\n"); //这里必须刷新一下,不然log.txt里面没有内容 fflush(stdout); close(fd); return 0; }
3.3<输入重定向
测试:
#include<stdio.h> #include<string.h> #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> #include<unistd.h> #include<stdlib.h> int main() { // close(0); // close(2); // close(1); umask(0); // int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666); // int fd=open("log.txt",O_WRONLY|O_CREAT|O_APPEND,0666); int fd=open("log.txt",O_RDONLY); if(fd == -1) { perror("open"); exit(1); } //输入重定向 dup2(fd,0); char outbuffer[64]; while(1) { printf("<"); if(fgets(outbuffer,sizeof(outbuffer),stdin) == NULL) break; printf("%s",outbuffer); } return 0; }
输入重定向:
- 使用
dup2(fd, 0)
将文件描述符fd
重定向到标准输入(文件描述符0),即从此程序的标准输入将从log.txt
读取。
读取并输出内容:
- 声明一个字符数组
outbuffer[64]
用于存储从标准输入读取的内容。- 使用
while
循环不断读取标准输入(现在是log.txt
的内容)直到文件结束。printf("<")
打印一个提示符。fgets(outbuffer, sizeof(outbuffer), stdin)
从标准输入读取一行,如果读取失败(例如文件结束),则退出循环。printf("%s", outbuffer)
将读取的内容打印到标准输出。
❗ fgets(stdin)的显示,由键盘变为了从文件中读取,后并打印
C++联动:
>输入(将内容添加到文件之中),<读取(由从键盘读取变为了从某文件读取 fgets 0->fgets file)
3.4 shell 模拟实现< >
思路:
头文件
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <sys/wait.h> #include <assert.h> #include <ctype.h> // 定义重定向类型 #define NONE 0 #define IN_RDIR 1 #define OUT_RDIR 2 #define APPEND_RDIR 3 #define LEFT "<" #define RIGHT ">" #define LABLE "$" // 全局变量用于重定向 char *rdirfilename; int rdir = NONE; int lastcode; int quit = 0; char commandline[1024]; // 函数声明 void getpwd(); char* getusername(); char* gethostname1(); void check_redir(char *cmd); void interact(char *cline, int size); void NormalExcute(char *_argv[]);
- check_redir:
- 该函数处理命令字符串以识别和处理输入(
<
)、输出(>
)和追加(>>
)重定向。它更新命令字符串以排除重定向部分,并相应地设置全局变量rdirfilename
和rdir
。
// 检查并处理命令中的重定向 void check_redir(char *cmd) { char *pos = cmd; while (*pos) {//遍历pos if (*pos == '>') {//查找重定向符号,对之后实现替换 if (*(pos + 1) == '>') {//追加重定向 *pos++ = '\0'; *pos++ = '\0';//切换字符并跳过 while (isspace(*pos)) pos++;//后面的判断怎么理解呢 rdirfilename = pos;//将文件名赋给 rdir = APPEND_RDIR;//命令行操作 break; } else { *pos = '\0'; pos++; while (isspace(*pos)) pos++; rdirfilename = pos; rdir = OUT_RDIR; break; } } else if (*pos == '<') { *pos = '\0'; // ls -a -l -n < filename.txt pos++; while (isspace(*pos)) pos++; rdirfilename = pos; rdir = IN_RDIR; break; } pos++; } }
- interact:
- 该函数提示用户输入,获取命令行,并调用
check_redir
处理任何重定向。
- 该函数提示用户输入,获取命令行,并调用
void interact(char *cline, int size) { getpwd();//获取当前工作目录并打印提示符 printf(LEFT"%s@%s %s"RIGHT""LABLE" ", getusername(), gethostname1(), pwd); char *s = fgets(cline, size, stdin);//获取命令行输入 assert(s); (void)s; cline[strlen(cline) - 1] = '\0';//移除换行符 check_redir(cline);//处理重定向符号 }
- NormalExcute:
- 该函数创建一个新进程来执行给定的命令。如果设置了任何重定向,它会在执行命令之前相应地调整文件描述符。
// Function to execute commands void NormalExcute(char *_argv[]) { pid_t id = fork(); if (id < 0) { perror("fork"); return; } else if (id == 0) { int fd = 0; if (rdir == IN_RDIR) {//对标记的rdir进行操作判断 fd = open(rdirfilename, O_RDONLY); dup2(fd, 0); } else if (rdir == OUT_RDIR) { fd = open(rdirfilename, O_CREAT | O_WRONLY | O_TRUNC, 0666); dup2(fd, 1); } else if (rdir == APPEND_RDIR) { fd = open(rdirfilename, O_CREAT | O_WRONLY | O_APPEND, 0666); //打开操作的mode设置 dup2(fd, 1);//对文件进行重定向 } execvp(_argv[0], _argv); exit(EXIT_FAILURE); } else { int status = 0; pid_t rid = waitpid(id, &status, 0); if (rid == id) { lastcode = WEXITSTATUS(status); } } }
- main:
- 程序的主循环初始化重定向设置,与用户交互以获取命令,将命令行输入分割成参数,并调用
NormalExcute
执行命令。
- 程序的主循环初始化重定向设置,与用户交互以获取命令,将命令行输入分割成参数,并调用
int main() { while (!quit) { rdirfilename = NULL; rdir = NONE; interact(commandline, sizeof(commandline)); // Tokenize the command line input into arguments char *argv[64]; int argc = 0; char *token = strtok(commandline, " "); while (token != NULL) { argv[argc++] = token; token = strtok(NULL, " "); } argv[argc] = NULL; if (argc > 0) { NormalExcute(argv); } } return 0; }
- 占位符函数:
getpwd
、getusername
和gethostname1
是用于获取当前工作目录、用户名和主机名的占位符函数,应根据需要实现实际功能。
void getpwd() { // Implement function to get current working directory } char* getusername() { // Implement function to get the username return "user"; } char* gethostname1() { // Implement function to get the hostname return "hostname"; }
运行:
思考:
问:后面我们做了重定向的工作,后面我们在进行程序替换的时候,难道不影响吗???
不影响
❗ 程序替换,和文件访问是并列的关系,mm_struct && file_struct ,程序替换,只会替换代码和数据,不影响曾经打开的重定向文件
联系:可以通过对文件调动,为进程替换提供良好的环境
问:子进程重定向会影响父进程吗?
不会
❗ 子进程会对父进程进行写实拷贝
3.5 理解>
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> #define filename "log.txt" int main() { fprintf(stdout, "hello normal message\n"); fprintf(stdout, "hello normal message\n"); fprintf(stdout, "hello normal message\n"); fprintf(stdout, "hello normal message\n"); fprintf(stdout, "hello normal message\n"); fprintf(stderr, "hello error message\n"); fprintf(stderr, "hello error message\n"); fprintf(stderr, "hello error message\n"); fprintf(stderr, "hello error message\n"); fprintf(stderr, "hello error message\n");
测试:
./mytest 1>nomal.log 2>err.log //合并 ./mytest 1>all.log 2>&1 //实现了覆盖
>默认指向的是 1,那 2 的设置有什么意义呢?
意义: 将错误信息和正确信息分开放了,便于程序员的查看
❗ 对于重定向的记忆
cat(cout)<<输出
>>(cin)输入到
和原理毫不相关,就是用着发现这样挺好记忆的
4. 理解“Linux 下一切皆文件”
🔔 进程通过对文件的管理实现了对设备的管理,设备的信息其实也是以文件的形式存储了
这里可能有这样一个问题,如果同一个文件被多个指针指向,但是某个进程把这个文件关了,会不会影响其他进程对这个文件的操作呢?
A: 其实并不会,一个被打开的文件有引用计数。
表明有几个指针指向这个文件,这样做是因为一个文件可能被多个指针指向。如果某个进程关闭文件,影响到其他进程,就不能保证进程的独立性。close关闭文件,其实并不是真正关闭文件,只是把引用计数减1,当只有一个指针指向这个文件,close关闭文件才是真正关闭这个文件。
文件有*write file 指针,屏蔽掉了设备的差异化,通过指针指向//C++联想:多态,奇类,派生类
思考:
1. 面向对象,是历史的必然,是先进的表现
2. 万变不离其宗:
学习了底层知识,就可以是我们面对各种语言进行文件读取时,读读文档,就能上手操作了
学习思考:通过不断的实验和提问,来进行学习