海口网站建设联系方式,中国电信黄页网,lol关键词查询,php做网站需要的技术索引 文件描述符分配规则重定向 缓冲区1.什么是缓冲区2.缓冲区在哪里 重定向源码模拟实现缓冲区原理带重定向的简易版Xshell标准输入和标准错误 文件描述符分配规则
文件描述符的分配规则 从头遍历数组fd_array[],找到一个最小的#xff0c;没有被使用的下标#xff0c;分配… 索引 文件描述符分配规则重定向 缓冲区1.什么是缓冲区2.缓冲区在哪里 重定向源码模拟实现缓冲区原理带重定向的简易版Xshell标准输入和标准错误 文件描述符分配规则
文件描述符的分配规则 从头遍历数组fd_array[],找到一个最小的没有被使用的下标分配给新的文件。
int main()7 {8 close(0);9 int fd open(log.txt, O_WRONLY | O_CREAT | O_TRUNC, 0666);10 if(fd 0) {11 perror(open);12 return 1;13 }14 15 16 17 fprintf(stdout, 打开文件成功, fd : %d\n, fd);//将特定的字符串格式化写到特定的流中18 close(fd);19 return 0;20 }
如上所示如果我们一开始将文件描述符0关掉那么此时我们打开log.txt’系统会为其分配的文件描述符为0 再看一个例子 如果此时我们将fd 1 关闭此时1这个文件描述符就会给新的文件log.txt 正常情况下如果我们cat log.txt的时候会打印出内容因为我们在一开始关闭了显示器的标准stdout然后再打开log.txt的时候其文件描述符会被自动填充为stdout. 但是运行之后发现 显示不出来此时如果在源码中加上fflushstdout发现就可以cat log.txt就可以打印出来了 这是为什么呢 先看下面的重定向和缓冲区的理解
重定向 所以也就不难理解追加重定向了追加重定向就是将打开文件的方式修改一下。 输入从定向就是dup2(fd, 0)
缓冲区
1.什么是缓冲区
缓冲区的本质就是一段内存
解放使用缓冲区的进程时间缓冲区的存在集中处理数据刷新减少IO的次数从而达到提高整机效率的目的
2.缓冲区在哪里
先看一段代码
1 #includestdio.h2 #includesys/types.h3 #includesys/stat.h4 #includefcntl.h5 #includeunistd.h6 #includestring.h7 int main()8 {9 printf(hello printf);//printf默认输出时候的文件描述符是1
W 10 const char *msg hello write;11 write(1, msg, strlen(msg));//将msg写到标准输入中12 13 sleep(5); 此时发现先输出hello write 五秒钟之后再输出hello printf printf(hello printf);//printf默认输出时候的文件描述符是110 fprintf(stdout, hello fprintf);11 fputs(hello fputs, stdout); 12 const char *msg hello write;13 14 write(1, msg, strlen(msg));//将msg写到标准输入中15 16 sleep(5);
将代码改成上述发现还是跟原来的现象一样hello write先打印出来然后再打印出printf fprintf fputs 的内容 为什么会这样 可以证明是有缓冲区的printf和fprintf fputs都是封装了write系统调用接口的所以缓冲区必然不在write中 发现三个C语言接口的函数都有一个共同点都有stdout, stdout是FILE类型其是一个结构体而在结构体中除了有文件描述符外还有语言级别的缓冲区。 所以缓冲区是语言级别的缓冲区 什么时候刷新 常规 无缓冲立即刷新 行缓冲逐行刷新显示器的文件 全缓冲缓冲区满刷新这个对应的是磁盘文件 特殊 进程退出C语言的强制刷新 用户强制刷新 fflush 重定向
提问如果在刷新之前关闭了fd会有什么影响 先看不关闭fd时候的重定向
19 int fd open(log.txt, O_WRONLY | O_CREAT | O_TRUNC, 0666);20 if(fd 0) {21 perror(open);22 return 1;23 }24 dup2(fd, 1);25 26 printf(hello printf);//printf默认输出时候的文件描述符是127 fprintf(stdout, hello fprintf);28 fputs(hello fputs, stdout);29 const char *msg hello write;30 write(1, msg, strlen(msg)); 如果我们在刷新之前关闭的话
printf(hello printf);//printf默认输出时候的文件描述符是127 fprintf(stdout, hello fprintf);28 fputs(hello fputs, stdout);29 const char *msg hello write;30 write(1, msg, strlen(msg));31 close(1); 此时发现log.txt中只有write写入的内容再一次证明了上述我们的结论调用C语言的文件接口就是通过fd找到对应的write如果fd关闭了就无法再显示了。 现在就可以理解最开始的问题了如果将标准输出重定向到log.txt关闭了fd之后就无法刷新了
再看一段代码 const char *str1 hello printf\n;11 const char *str2 hello fprintf\n;12 const char *str3 hello fputs\n;13 const char *str4 hello write\n;
W 14 printf(str1);
W 15 fprintf(stdout, str2);16 fputs(str3, stdout);17 //系统接口18 write(1, str4, strlen(str4));19 //调用结束上面的代码执行fork20 fork();
上述代码的话运行后的结果是 发现添加了重定向之后此时代码打印是7行其中C语言接口的函数各打印两条write无论何种情况都只打印一条为什么呢 **代码的最后fork创建了子进程父子进程代码共享数据起初也是共享的因为我们将打印的结果重定向到log.txt了而log.txt是一个磁盘文件其刷新的条件是当缓冲区满的时或者是进程退出的时候会清空缓冲区而无论父子进程谁先发生清空数据都要发生写实拷贝所以父进程刷新一份数据子进程刷新一份数据就是两份数据了所以才会出现上述情况。
源码模拟实现缓冲区原理
#includestdio.h
#includeunistd.h
#includestdlib.h
#includestring.h
#includesys/types.h
#includesys/stat.h
#includefcntl.h
#includeassert.h#define NUM 1024#define NONE_FLUSH 0x0 //没有刷新
#define LINE_FLUSH 0x1 //行刷新
#define FULL_FLUSH 0x2 //全满才刷新typedef struct _MyFILE {int _fileno;char _buffer[NUM];int _end;int _flags; //刷新方式
}MyFILE;MyFILE *my_open(const char *filename, const char *method)
{assert(filename);assert(method);int flag O_RDONLY;if(strcmp(method, r) 0){} else if(strcmp(method, r) 0) {} else if(strcmp(method, w) 0) {flag O_WRONLY | O_CREAT | O_TRUNC;} else if(strcmp(method, w) 0) {} else if(strcmp(method, a) 0) {} else if(strcmp(method, a) 0) {} else {perror(open error!\n);}int fileno open(filename, flag, 0666);if(fileno 0) {return NULL;}MyFILE *fp (MyFILE*)malloc(sizeof(MyFILE));if(fp NULL) {return fp;}memset(fp, 0, sizeof(MyFILE));fp-_fileno fileno;fp-_flags | LINE_FLUSH;fp-_end 0;return fp;}
void my_fflush(MyFILE *fp)
{assert(fp);if(fp-_end 0) {write(fp-_fileno, fp-_buffer, fp-_end);fp-_end 0;syncfs(fp-_fileno);//将数据从内存刷新到磁盘}
}
void my_fwrite(MyFILE *fp, const char *start, int len)
{assert(fp);assert(start);assert(len);//写到缓冲区中strncpy(fp-_bufferfp-_end, start, len);fp-_end len;if(fp-_flags NONE_FLUSH) {} else if(fp-_flags LINE_FLUSH) {if(fp-_end 0 fp-_buffer[fp-_end-1] \n) {//仅仅是写到内核中write(fp-_fileno, fp-_buffer, fp-_end);fp-_end 0;syncfs(fp-_fileno);}} else if (fp-_flags FULL_FLUSH) {}}void my_fclose(MyFILE *fp)
{my_fflush(fp);close(fp-_fileno);free(fp);fp NULL;
}int main()
{MyFILE *fp my_open(log.txt, w);if(fp NULL){perror(my_open error\n);return 1;}// const char *s hello zjt\n;// my_fwrite(fp, s, strlen(s));// printf(消息立即刷新);// sleep(3);// // const char *ss hello zhang;// my_fwrite(fp, ss, strlen(ss));// printf(写入了一个不满足条件的字符串\n);// sleep(3);// const char *sss hello jun;// my_fwrite(fp, ss, strlen(sss));// printf(写入了一个不满足条件的字符串\n);// my_fflush(fp); const char *s bbbbb-;my_fwrite(fp, s, strlen(s));printf(写入了一个不满足刷新条件的字符串\n);//fork();my_fclose(fp);return 0;
}最后几行代码如果不加fork() 将fork()注释解除之后 在fork()函数创建子进程之后此时因为我们的字符串没有\n 所以其是不支持刷新的fork()创建子进程之后父子进程代码数据共享无论父子进程哪个先调用my_fclose(),其都会清空缓冲区另外一个进程都会发生写实拷贝所以父进程刷新一份数据子进程刷新一份数据
带重定向的简易版Xshell
#include stdio.h
#include string.h
#include stdlib.h
#include unistd.h
#include sys/types.h
#include sys/wait.h
#include sys/stat.h
#include fcntl.h
#include assert.h
#include ctype.h// #define BUG 1
#define SEP
#define NUM 1024
#define SIZE 128#define DROP_SPACE(s) \do \{ \while (isspace(*s)) \s; \} while (0)char command_line[NUM];
char *command_args[SIZE];char env_buffer[NUM]; // 为了测试#define NONE_REDIR -1
#define INPUT_REDIR 0
#define OUTPUT_REDIR 1
#define APPEND_REDIR 2int g_redir_flag NONE_REDIR;
char *g_redir_filename NULL;extern char **environ;
void CheckDir(char *commands)
{assert(commands);char *start commands;char *end commands strlen(commands);while (start end){if (*start ){// 有可能是重定向也可能是追加重定向if (*(start 1) ){// 追加重定向// ls -a -l log.txt*start \0;start 2;g_redir_flag APPEND_REDIR;DROP_SPACE(start); // 防止用户加空格g_redir_filename start;break;}else{// ls -a -l log.txt输出重定向*start \0;start;DROP_SPACE(start);g_redir_flag OUTPUT_REDIR;g_redir_filename start;break;}}else if (*start ){// 输出重定向*start \0;start;DROP_SPACE(start);g_redir_filename start;g_redir_flag INPUT_REDIR;break;}else{start;}}
}
int ChangDir(char *newdir)
{chdir(newdir);return 0;
}
int PutEnvMyshell(char *newenv)
{putenv(newenv); // 导出环境变量
}
int main()
{// shell本质上是一个死循环while (1){g_redir_flag NONE_REDIR; // 每一次循环都要重新定义g_redir_filename NULL;// 1.显示提示符printf([zjt1270.0.1 当前目录]# );fflush(stdout);// 获取用户输入memset(command_line, \0, sizeof(command_line) * sizeof(char));fgets(command_line, NUM, stdin); // 键盘标准输入stdin获取到的字符串是c风格的字符串以\0结尾command_line[strlen(command_line) - 1] \0; // 清空\nCheckDir(command_line); // 检查路径// 3.字符串切分command_args[0] strtok(command_line, SEP);int index 1;// 给ls命令添加颜色if (strcmp(command_args[0], ls) 0){command_args[index] (char *)--colorauto;}// strtok截取成功返回字符串起始地址// 截取失败返回NULLwhile (command_args[index] strtok(NULL, SEP));#ifdef BUGfor (int i 0; i index; i){printf(%d : %s\n, i, command_args[i]);}
#endif// 内建命令的编写if (strcmp(command_args[0], cd) 0 command_args[1] ! NULL){ChangDir(command_args[1]); // 让调用方进行路径切换父进程continue;}if (strcmp(command_args[0], export) 0 command_args[1] ! NULL){// 目前环境变量信息在command_line,会被清空// 所以我们要自己保存一下环境变量的内容strcpy(env_buffer, command_args[1]);PutEnvMyshell(env_buffer);continue;}// 创建进程执行pid_t id fork();if (id 0){int fd -1;switch (g_redir_flag){case NONE_REDIR:break;case INPUT_REDIR:fd open(g_redir_filename, O_RDONLY);dup2(fd, 0);break;case OUTPUT_REDIR:fd open(g_redir_filename, O_WRONLY | O_CREAT | O_TRUNC);dup2(fd, 1);break;case APPEND_REDIR:fd open(g_redir_filename, O_WRONLY | O_CREAT | O_APPEND);dup2(fd, 1);break;default:perror(Bug\n?);break;}// child// 程序替换execvp(command_args[0], command_args);exit(1); // 执行到这里子进程一定替换失败}int status 0;pid_t ret waitpid(id, status, 0);if (ret 0){printf(执行命令成功! sig: %d, code : %d\n, status 0x7F, (status 8) 0xFF);}}return 0;
}
标准输入和标准错误
#include iostream
int main()
{// stdoutprintf(hello printf 1\n);fprintf(stdout, hello fprintf 1\n);fputs(hello fputs 1\n, stdout);// stderrfprintf(stderr, hello fprintf 2\n);fputs(hello fputs 2\n, stderr);perror(hello perror 2);// coutstd::cout hello cout 1 std::endl;// cerrstd::cerr hello cerr 2 std::endl;return 0;
}
先看代码代码运行后的结果如下所示 这个没问题但是当我们将显示结果重定向后 发现并不是所有的显示结果都会重定向到文件中 如果这样操作的话此时显示结果被分别重定向到了不同的文件中 为什么呢? 因为默认重定向的话只是将fd 1的stdout重定向到文本文件中如果需要重定向标准错误的话需要显示的写 所以上述重定向的标准写法应该是这样的 ./a.out 1 stdout.txt 2stderr.txt
这么做的意义何在呢 可以区分哪些是程序的日常输出哪些是错误 那么能不能将标准输出和标准错误重定向到一个文件中呢可以如何做呢 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4TGdyG0N-1685098168077)(null)] 可以注意到上述的perror在输出之后还打印出了success 为什么呢 perror也是一个库函数这个函数内部会自己获取errno的值调用这个函数会直接把错误提示符打印出来,此外我们也可以在错误提示字符串前添加一些自己想要打印的信息 什么是errno errno是一个全局变量记录最近一次C库函数调用失败原因
我们可以自己实现一下perror
void my_perror(const char *info)
{fprintf(stderr, %s: %s \n, info, strerror(errno));
}
int main()
{int fd open(log.txt, O_RDONLY);//此时必定是失败的if(fd 0){// perror(open);my_perror(open);return 1;}return 0;**万字长文结束我本身是c 和Linux的欢迎互相交流 **