当前位置: 首页 > news >正文

上海网站开发建设济南网站建设是什么意思

上海网站开发建设,济南网站建设是什么意思,购物网站后台管理系统模板,用仿网站做优化有效果吗目录 前言#xff1a; 一、关于文件的预备知识 二、C语言文件操作 1. fope 2. fclose 3. 文件写入 3.1 fprintf 3.2 snprintf 三、系统文件操作 1. open 2. close 3. write 4. read 四、C文件接口与系统文件IO的关系 五、文件描述符 1. 理解文件描述符 2. 文…目录 前言 一、关于文件的预备知识 二、C语言文件操作 1. fope 2. fclose 3. 文件写入 3.1 fprintf 3.2 snprintf 三、系统文件操作 1. open 2. close 3. write 4. read 四、C文件接口与系统文件IO的关系 五、文件描述符 1. 理解文件描述符 2. 文件描述符的分配规则 六、再次理解一切皆文件 七、重定向 1. FILE 2. 重定向的本质 3. 利用指令重定向 4. 输出重定向 5. 输入重定向 6. 追加重定向  7. 函数重定向 八、缓冲区 前言 文件操作对于不少编程初学者来说都是一件头疼的事情不管你学习的是什么编程语言C/C/Java/Go/Python ,因为我们学习到的文件相关的操作对你来说都是一个黑盒我们只是知道我们应该怎么使用相应的文件函数进行操作而对于内部的实现方式的一无所知这导致很多初学者对于文件操作都很恐惧。         其实这不是你的问题因为文件操作涉及了许多操作系统相关的知识我们只有站在操作系统的角度才能对文件操作有比较深刻的理解所以本篇文章我们来学习一下Linux下的基础IO操作,来加深我们对于语言级别文件操作以及系统的理解。 一、关于文件的预备知识 文件操作的本质是什么 针对文件的操作都是对文件内容和属性的操作语言层面的文件操作就是直接使用库函数而事实上文件操作是系统层面的问题就像进程管理一样系统也会通过 先描述再组织 的方式对文件进行管理、操作。 只有 C/C 这种偏底层的语言才有文件操作吗 并不是其他语言也支持文件操作如 Java。在进行文件操作时不同语言使用方法可能有所不同但 本质上都是在调用系统级接口进行操作 文件由什么构成一般文件放在哪里 文件  内容  属性当我们没有打开文件时文件位于磁盘中当文件被打开时文件就要被操作系统加载到内存中。这也是冯诺依曼体系结构对我们文件操作的限制本文讨论的是已被加载至内存文件的相关操作 文件是由谁打开的 由用户创建进程调用系统级接口再交给 OS 完成文件打开任务文件写入与读取时也是同理  系统是如何管理文件的 实际进程在运行的过程中肯定会打开多个文件对于多个文件我们操作系统要将它们组织起来以便于管理和维护会为被打开的文件创建相应的内核数据结构。像使用 task_struct 管理进程一样通过 struct file 存储文件属性进行管理struct file 结构体包含了文件的各种属性和链接关系 调用库函数进行文件操作时的流程 二、C语言文件操作 1. fopen FILE *fopen(const char *path, const char *mode); fopen 函数返回值类型为 FILE 。参数列表中 path 为文件路径 mode 为打开方式。  注意 若参数1直接使用文件名则此文件需要位于当前程序目录下如果想指定目录存放可以使用绝对路径 打开文件的方式有如下几种 r打开文本文件进行阅读。若文件不存在会打开失败 流位于文件的开头。r开放读和写。 流位于文件的开头w打开文件进行写入。如果文件不存在则创建该文件写入前会先清空内容 流位于文件的开头。w开放读和写。如果文件不存在则创建该文件否则文件被清空。 流位于文件的开头。a打开以追加在文件末尾写入追加前不会清空内容 如果文件不存在则创建该文件。 流位于文件的末尾。a打开以进行读取和追加在文件末尾写入。 如果文件不存在则创建该文件。用于读取的初始文件位置位于文件的开头 但输出始终附加到文件末尾。 //打开文件进行操作 //在当前目录中打开文件 log.txt //注意同一个文件可以同时多次打开 FILE* fp1 fopen(log.txt, w); //只读 FILE* fp2 fopen(log.txt, a); //追加 FILE* fp3 fopen(log.txt, r); //只写文件不存在会打开失败 FILE* fp4 fopen(log.txt, w); //可读可写 FILE* fp5 fopen(log.txt, a); //可读可追加 FILE* fp6 fopen(log.txt, r); //可读可写文件不存在会打开失败若文件打开失败会返回空 NULL可以在打开后判断是否成功 2. fclose 文件打开并使用后需要关闭就像动态内存申请后需要释放一样 int fclose(FILE *stream);关闭已打开文件只需通过 FILE* 指针进行操作即可。只能对已打开的文件进行关闭若文件不存在会报错 //对上面打开的文件进行关闭 //无论以哪种方式打开关闭方法都一样 fclose(fp1); fclose(fp2); fclose(fp3); fclose(fp4); fclose(fp5); fclose(fp6);3. 文件写入 C语言 对于文件写入有这几种方式fputc、fputs、fwrite、fprintf 和 snprintf int fputc(int character, FILE *stream);int fputs(const char *str, FILE * stream ;size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);int fprintf(FILE *stream, const char *format, ...);int snprintf(char *s, size_t n, const char *format, ...);前几种方式比较简单无非就是 逐字符写入、逐行写入 与 格式化写入这里来介绍一下 fprintf、snprintf 。 3.1 fprintf int fprintf(FILE *stream, const char *format, ...); 与 printf 不同 printf 默认向显示器打印消息而 fprintf则可以指定文件流向指定文件打印。 #include stdio.h #define LOG log.txt // 自定义文件int main() {FILE* fp fopen(LOG, w);// 写方式打开文件if(fp NULL){perror(fopen);return 1;}const char* msg good morning; int cnt 3;while(cnt){fprintf(fp, %s: %d\n, msg, cnt);//fputs(msg, fp);cnt--;}fclose(fp);return 0; }也可以使用 fprintf 函数向显示器文件里打印以实现和 printf 函数相同的效果  3.2 snprintf int snprintf(char *str, size_t size, const char *format, ...); snprintf 是 sprintf 的优化版增加了读取字符长度控制更加安全 参数1缓冲区常写做 buffer 数组可以把内容打印到缓冲区里参数2缓冲区的大小通过设置参数 size 控制打印的长度。参数3格式化输入 使用 snprintf 函数写入数据至缓冲区后可以通过 fputs 函数将缓冲区中的数据真正写入文件中 while(cnt) {char buffer[256];snprintf(buffer, sizeof(buffer), %s: %d\n, msg, cnt);//写入数据至缓冲区fputs(buffer, fp);//将缓冲区中的内容写入文件中cnt--; }三、系统文件操作 1. open int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode); 我们打开man手册在2号目录里面可以看到 open的函数原型以及介绍:  参数1pathname 待操作文件名和 fopen 一样文件路径可以是相对路径也可以是绝对路径参数2flags为打开选项open 以位图形式传递选项信号用一个 int 至多可以表示 32 个选项参数3mode 权限设置文件起始权限为 0666返回值如果打开成功会返回一个大于或等于0整数这个整数叫做文件描述符用来标定一个文件如果打开失败会返回-1表示打开失败。下面会详细解释 参数2解释使用了 位图 的方式进行多参数传递 可以利用这个特性写一个关于位图的小demo #include stdio.h #include stdlib.h#define ONE 0x1 #define TWO 0x2 #define THREE 0x4void Test(int flags) {//模拟实现三种选项传递if(flags ONE)printf(This is one\n);if(flags TWO)printf(This is two\n);if(flags THREE)printf(This is three\n); }int main() {Test(ONE | TWO | THREE);printf(-----------------------------------\n);Test(ONE); //位图使得选项传递更加灵活return 0; }函数 open 中的参数2正是位图其参数有很多个这里列举部分 O_RDONLY 只读打开                    英文read only的缩写O_WRONLY 只写打开                    英文write only的缩写O_CREAT 文件不存在就创建该文件             (英文creatO_TRUNC 文件每次打开都会进行清空         (英文truncate)O_APPEND 文件写入时以追加的方式进行写入    (英文append #include stdio.h #include sys/types.h #include sys/stat.h #include fcntl.h #include unistd.h #include errno.h #include string.h #include stdlib.h#define LOG log.txtint main() {// 三种参数组合就构成了 fopen 中的 wint fd open(LOG, O_WRONLY | O_CREAT | O_TRUNC);// 成功与否打印信息if(fd -1) {printf(fd: %d, error: %d, errstring: %s\n, fd, errno, strerror(errno));}else{printf(fd: %d, error: %d, errstring: %s\n, fd, errno, strerror(errno));}close(fd);return 0; }O_CREAT 为如果文件不存在则自动创建该文件。 O_WRONLY 为以只读模式打开文件。需要注意的是他们不会对原始文件内容做清空下一次写入时虽然还是从开头开始写但是原本的内容没被覆盖的部分仍然会残留。 打开文件所返回的文件描述符是 3 。文件描述符下面会做详细解释 参数3解释 先看上述代码结果 第一次打开 这里新创建的文件打开的权限有一点奇怪我们删除再重新运行程序  新创建的文件打开的权限还是有一点奇怪而且和上一次的创建的文件权限竟然不同我们再次删除再重新运行程序  为什么每次创建文件的权限都是随机的呢 因为我们使用的第一个open函数是没有指明创建后的文件权限是什么的所以创建出的文件权限是一个随机值这时我们就要考虑第二个open函数了 第三个参数 mode_t是一种无符号整形我们的第三个参数可以以8进制的方式传入我们创建的文件的权限。 int fd open(LOG, O_WRONLY | O_CREAT | O_TRUNC, 0666); 最终的结果并不是-rw-rw-rw- 这是为什么呢 这是因为我们Linux的普通用户的权限掩码影响了我们文件创建的权限我们可以使用系统调用umask()来进行设置我们进程的权限掩码。 umask(0); int fd open(LOG, O_WRONLY | O_CREAT | O_TRUNC, 0666); 刚刚被创建的 log.txt 文件的权限为 666 。  总结假若文件不存在open 中的参数3最好进行设置否则创建出来的文件权限为随机值。 C语言 中的 fopen 调用 open 函数其中的选项对应关系如下 w - O_WRONLY | O_CREAT | O_TRUNCa - O_WRONLY | O_CREAT | O_APPENDr - O_RDONLY....... 所以只要我们想使用 open 时也能做到 只读方式 打开 不存在的文件也不会报错加个 O_CREAT 参数即可 2. close close 函数根据文件描述符关闭文件 int close(int fildes);Linux 下一切皆文件 包括这三个标准流 stdin、stdout、stderr 它们的文件描述符依次为0、1、2也可以通过 close(1) 的方式关闭标准流 3. write write函数是写入函数通过此函数我们能向文件中写入数据。 ssize_t write(int fd, const void *buf, size_t count); 第一个参数是文件标识符也就是要写入的文件。第二个参数是一个指针指向的是要写入的数据第三个参数是一个变量表示最多写入多少个字节的数据返回值实际写入的字节个数。 #include stdio.h #include sys/types.h #include sys/stat.h #include fcntl.h #include unistd.h #include errno.h #include string.h #include stdlib.h#define LOG log.txtint main() {umask(0);// 三种参数组合就构成了 fopen 中的 wint fd open(LOG, O_WRONLY | O_CREAT | O_TRUNC, 0666);// 成功与否打印信息if(fd -1) {printf(fd: %d, error: %d, errstring: %s\n, fd, errno, strerror(errno));}else{printf(fd: %d, error: %d, errstring: %s\n, fd, errno, strerror(errno));}const char *msg good morning\n;int cnt 3;while(cnt--){// 也可以写入缓冲区中//char line[128]//snprintf(line, sizeof(line), %s, %d\n, msg, cnt);//write(fa, line, strlen(line));write(fd, msg, strlen(msg));// 长度不用1}close(fd);return 0; }注意通过系统级函数 write 写入字符串时不要刻意加上 \0因为对于系统来说调用写入时要的只是数据但不包括\0这也只是一个普通的字符因为\0 作为字符串结尾只是 C语言 的规定但不是系统的规定 4. read read函数是读取函数可以从文件中将数据读进变量中。 ssize_t read(int fd, void *buf, size_t count);现在我们的文件是存在数据的  编写如下代码 #include stdio.h #include sys/types.h #include sys/stat.h #include fcntl.h #include unistd.h #include errno.h #include string.h #include stdlib.h#define LOG log.txtint main() {umask(0);// 只读取文件int fd open(LOG, O_RDONLY);// 成功与否打印信息if(fd -1) {printf(fd: %d, error: %d, errstring: %s\n, fd, errno, strerror(errno));}else{printf(fd: %d, error: %d, errstring: %s\n, fd, errno, strerror(errno));}char buffer[1024];// 读取整个文件ssize_t n read(fd, buffer, sizeof(buffer)-1);if(n0){buffer[n] \0;//注意在读取到的字符串后面加\0才是C语言风格的字符串 printf(%s\n, buffer);}close(fd);return 0; } 注意 使用系统接口来进行IO的时候一定要注意 \0 的问题。 read函数会读取换行符(\n)并将其作为普通字符处理只有在读取到指定的字节数或者遇到文件的结束才会停止读取所以读取到的结果就有一行换行 这些系统级函数成功使用的前提是文件描述符合法 四、C文件接口与系统文件IO的关系 fopen、fclose、fwrite、fputs、fread、fgets 都是C标准库当中的函数我们称之为库函数libc。open、close、write、read都属于系统提供的接口称之为系统调用接口。 可以认为f#系列的函数都是对系统调用的封装方便二次开发。 五、文件描述符 int open(const char *pathname, int flags, mode_t mode); open 函数的返回值是一个整数这个整数就是文件描述符。关于 open 函数返回值的介绍如下 任何一个进程在启动的时候默认会打开当前进程的三个文件 标准输入 、 标准输出 、 标准错误 。分别对应 C语言中的 stdin 、 stdout 、 stderr 与C中的 cin 、 cout 、 cerr  1. 理解文件描述符 我们知道C语言打开一个文件之后会返回一个 FILE* 的指针我们的C的三个标准流也是 FILE*的指针并且它们分别指向了 键盘文件 、显示器文件、显示器文件。 当我们尝试去向这标准输出流或者是标准错误流中去输入数据其实就是向显示器文件进行数据写入当我们尝试向标准输入流中读取数据其实就是对键盘文件进行数据读取。 既然是文件那么在Linux下进行文件操作必须要有文件描述符那么我们C程序以及其他编程语言写的程序默认打开的三个流也要有文件描述符只有有了文件描述符我们系统才能找到对应的文件。实际上C程序以及其他编程语言写的程序形成的进程默认打开的三个流在Linux中对应的文件描述符都是 流文件描述符标准输入流0标准输出流1标准错误流2 #include stdio.h #include sys/types.h #include sys/stat.h #include fcntl.h #include unistd.h #include errno.h #include string.h #include stdlib.h#define LOG log.txtint main() {umask(0);// 只读取文件int fd open(LOG, O_RDONLY);// 成功与否打印信息if(fd -1) {printf(fd: %d, error: %d, errstring: %s\n, fd, errno, strerror(errno));}else{printf(fd: %d, error: %d, errstring: %s\n, fd, errno, strerror(errno));} } 为啥是3捏 实际上文件描述符的本质是数组的下标 根据 先描述、再组织 原则OS 将所有的文件都统一视为 file 对象获取它们的 file* 指针然后将这些指针存入指针数组中可以进行高效的随机访问和管理这个数组为 file* fd_array[]而数组的下标就是神秘的 文件描述符 fd。 一个进程可以打开多个文件OS为了让进程快速的找到文件在内核中创建了一种结构体 files_struct 。进程的PCB结构体 task_struct 中有一个指针 *file 指向这个文件结构体files_struct  。 files_struct 之中包含了一个指针数组 struct file* fd_array[ ] 我们打开文件时操作系统就会在指针数组 struct file* fd_array[ ] 中从前往后遍历找到当前没有被使用的最小的一个下标作为新的文件描述符并把该下标的指针指向新打开文件的文件对象 file 最后向上层返回该下标。 注文件被打开后并不会加载至内存中这样内存早爆了而是静静的躺在磁盘中等待进程与其进行 IO。  所以当一个程序启动时OS 会默认打开 标准输入、标准输出、标准错误 这三个文件流将它们的 file* 指针依次存入 fd_array 数组中显然下标 0、1、2 分别就是它们的文件描述符 fd后续再打开文件流时新的 file* 对象会存入当前未被占用的最小下标处所以用户自己打开的 文件描述符一般都是从 3 开始。 2. 文件描述符的分配规则 fd 的分配规则为先来后到优先使用当前最小的、未被占用的 fd  存在下面两种情况 直接打开文件 file.txt分配 fd 为 3先关闭标准输入 stdin 中原文件执行流键盘再打开文件 file.txt分配 fd 为 0因为当前 0 为最小的且未被占用的 fd #include stdio.h #include sys/types.h #include sys/stat.h #include fcntl.h #include unistd.h #include errno.h #include string.h #include assert.h#define LOG log.txt int main() {//先打文件 int fd open(LOG, O_WRONLY | O_CREAT | O_TRUNC, 0666);assert(fd ! -1); //存在打开失败的情况printf(单纯打开文件 fd: %d\n, fd);close(fd); //记得关闭//先关闭再打开close(0); //关闭键盘文件执行流fd open(LOG, O_WRONLY | O_CREAT | O_TRUNC, 0666);printf(先关闭键盘文件执行流再打开文件 fd: %d\n, fd);close(fd);return 0; }注意 假若将标准输出 stdout 中的原文件执行流显示器关闭了那么后续的打印语句将不再向显示器上打印而是向此时 fd 为 1 的文件流中打印 close(1);// 关闭显示器文件执行流fd open(LOG, O_WRONLY | O_CREAT | O_TRUNC, 0666);printf(先关闭显示器文件执行流再打开文件 fd: %d\n, fd);close(fd); 这其实就是 重定向 的基本操作 六、再次理解一切皆文件 学习过Linux的人大多数都听过一句话“Liunx下一切皆文件”这一点对于文件操作非常重要下面我们来谈一谈对于这句话的理解。 我们知道像键盘显示器网卡… 这些都是硬件根本不是文件为什么我们能把它们当成文件去看待呢 首先我们在操作硬件时都是通过驱动程序进行操作硬件的而对于不同的硬件它们的驱动程序肯定是不同的例如键盘只能被读取而不能被写入显示器只能被写入而不能被读取网卡既可以被读取又可以被写入 … 尽管它们的驱动是不同点但是我们可以设计一个类对于它们的进行封装封装完以后上面的调用者就看不到底层的内容了上面的调用者只需要对于这个类进行操作就可以达到同样的效果这样对于调用者来说它的操作都是在操作这个类它接触不到底层的驱动以及硬件当然对于它来说它看到的是什么那就是什么了。 于是Linux用struct file结构体封装这些驱动的函数指针对于不同的硬件若不需读或写操作方法则只需将对应的函数指针置空就行这样进程在使用键盘显示器时就像是在使用文件一样这样一来不管是普通文件还是硬件对于进程来说都是文件。 而我们用户使用操作系统又是通过进程的方式来使用操作系统的所以我们用户的视角和进程的视角是一样的在我们用户来看想要让磁盘帮我们保存一些数据我们只需要保存一下文件就行了我们没有必要把磁盘拿出来然后通过一些物理手段向磁盘中刻入数据进程也是和我们用户一样它也认为我只要保存一下文件就能完成任务了所以对于我们用户和进程来说一切都是文件我们也只能接触到文件所以Linux下一切皆文件。 七、重定向 1. FILE 在谈论重定向之前我们先来谈论一下C语言中的FILE 我们使用C语言进行打开文件时系统都会给我们一个FILE指针那这个FILE指针是什么呢是谁给我们提供的呢 答案是是C语言给我们提供的这个FILE其实就是一个C库给我们封装的一个结构体而且这个结构体内部一定要有文件描述符 fd因为IO相关函数与系统调用接口对应并且库函数封装系统调用所以本质上访问文件都是通过 fd访问的。所以C库当中的FILE结构体内部必定封装了 fd。 在C库的内部源代码中有这样一些源代码 通过这段源代码我们知道FILE内部有一个叫 _fileno的文件描述符那么我们就可以将stdin stdout stderr的文件描述符打印出来看看与我们上面的结论是不是一样的。 打印三个标准流的文件描述符 #include stdio.h #include sys/types.h #include sys/stat.h #include fcntl.h #include unistd.h #include errno.h #include string.h #define LOG mylog.txtint main() {//打印出文件标识符printf(%d\n, stdin-_fileno); printf(%d\n, stdout-_fileno); printf(%d\n, stderr-_fileno); FILE *fp fopen(LOG, w);printf(%d\n, fp-_fileno);fclose(fp); return 0; } 2. 重定向的本质 前面说过OS 在进行 IO 时只会根据标准输入、输出、错误对应的文件描述符 0、1、2 来进行操作也就是说 OS 作为上层不必关心底层中具体的文件执行流信息fd_array[] 中存储的对象 因此我们可以做到 “偷梁换柱”将这三个标准流中的原文件执行流进行替换这样就能达到重定义的目的了 3. 利用指令重定向 下面直接在命令行中实现输出重定向将数据输出至指定文件中而非屏幕中 echo you can see me file.txt可以看到数据直接输出至文件 file.txt 中当然也可以 从  file.txt 中读取数据而非键盘 cat file.txt现在可以理解了 可以起到将标准输出重定向为指定文件流的效果而  则是从指定文件流中标准输入式的读取出数据。 除此之外我们还可以利用程序进行操作在运行后进行重定向即可 #include iostreamusing namespace std;int main() {cout 标准输出 stdout endl;cerr 标准错误 stderr endl;return 0; }直接运行的结果此时的标准输出和标准错误都是向显示器上打印  利用命令行只对 标准输出 进行重定向file.txt 中只收到了来自 标准输出 的数据这是因为 标准输出 与 标准错误 是两个不同的 fd现在只重定向了 标准输出 1  对 标准错误 也进行 重定向打印内容至 file.txt 4. 输出重定向 看下面一段代码我们就可以尝试如果我们关闭1号文件描述符然后我们再打开一个文件之后我们向stdout里面输入一些数据看一看会发生什么还是打印到显示器上面吗 int main() { close(1); // fclose(stdout);int fd open(LOG, O_WRONLY | O_CREAT | O_TRUNC, 0666); printf(you can see me?\n);return 0; }答案是并没有打印到显示器中而是打印到了文件中相信有了前面的基础你已经明白了我们将stdout关闭后新打开的文件占据了1号文件描述符而我们的printf函数只认识1号文件描述符所以向1号文件描述符指向的文件输入内容,就导致数据输入到了文件里面 5. 输入重定向 同理我们把0号文件标识符给关闭然后打开我们的新文件进行scanf那么我们应该会从新打开的文件中读取数据我们看一看结果 int main() {close(0); // fclose(stdin);int fd open(LOG, O_RDONLY);char str[100];scanf(%s, str);printf(%s\n, str);close(fd);return 0; } 当执行 scanf 函数时并没有要从键盘输入数据而是从默认 0 号的键盘文件读取数据也就变成了从现在的  0 号文件 mylog.txt 中读取数据  6. 追加重定向  追加重定向的原理很简单我们只需要将文件的打开方式加上O_APPEND去掉O_TRUNC 。 例如对于刚才的文件进行重定向 close(1);int fd open(LOG, O_WRONLY | O_CREAT | O_APPEND, 0666);if (fd -1){perror(Failed to open log file);return 1;}printf(this is append!!!!!\n);追加成功 根据这些原理我们来实现一个需求将标准输出流与标准错误流的信息进行分流。 标准输入stdin- 设备文件 - 键盘文件标准输出stdout- 设备文件 - 显示器文件标准错误stderr- 设备文件 - 显示器文件 标准输出 与 标准错误 都是向显示器中输出数据为什么不合并为一个 因为在进行排错时可能需要单独查看错误信息若是合并在一起查看日志时会非常麻烦导致我们难以找到错误所在但如果分开后只需要将 标准错误 重定向后即可在一个单独的文件中查看错误信息 我们可以使用重定向进行分流我们先关闭1号文件描述符然后新打开一个文件normal.txt然后关闭2号文件描述符再打开一个新的文件error.txt这样我们再使用标准输入或标准错误流时信息会被写入两个不同的文件中我们关心错误信息就可以打开error.txt进行查看关心正确信息就可以打开normal.txt进行查看。 原本不分流时是乱的 fprintf(stdout, stdout-normal\n); fprintf(stderr, stderr-error\n); fprintf(stdout, stdout-normal\n); fprintf(stderr, stderr-error\n); fprintf(stderr, stderr-error\n); fprintf(stderr, stderr-error\n); fprintf(stdout, stdout-normal\n);进行分流 close(1);open(normal.txt, O_WRONLY | O_CREAT | O_TRUNC, 0666);close(2);open(error.txt, O_WRONLY | O_CREAT | O_TRUNC, 0666);fprintf(stdout, stdout-normal\n); fprintf(stderr, stderr-error\n); fprintf(stdout, stdout-normal\n); fprintf(stderr, stderr-error\n); fprintf(stderr, stderr-error\n); fprintf(stderr, stderr-error\n); fprintf(stdout, stdout-normal\n);这样就把常规消息与异常消息区分开在不同的文件里了我们C语言中的 printf 与 perror C中的 cout 与 cerr 所做的就是把不同的消息进行分类方便查找。 或者不同消息打印的位置可以通过指令的方式来实现注释掉close(1)和close(2) 以上只是简单演示一下如何通过命令行进行 重定向在实际开发中进行重定向操作时使用的是函数 dup2 7. 函数重定向 每次重定向都要先关闭一个文件的话操作太过于麻烦所以可以使用系统调用 dup2 来简化操作 #include unistd.h int dup2(int oldfd, int newfd); 函数解读将老的 fd 重定向为新的 fd参数1 oldfd 表示新的 fd而 newfd 则表示老的 fd重定向完成后只剩下 oldfd因为 newfd 已被覆写为 oldfd 了如果重定向成功后返回 newfd失败返回 -1 参数设计比较奇怪估计作者认为 newfd 表示重定向后新的 fd 下面来直接使用模拟实现报错场景将正常信息输出至 normal.txt错误信息输出至 error.txt 中 int fdnor open(normal.txt, O_WRONLY | O_CREAT | O_TRUNC, 0666);int fderr open(error.txt, O_WRONLY | O_CREAT | O_TRUNC, 0666);// 重定向dup2(fdnor, 1);dup2(fderr, 2);fprintf(stdout, stdout-normal\n); fprintf(stderr, stderr-error\n); fprintf(stdout, stdout-normal\n); fprintf(stderr, stderr-error\n); fprintf(stderr, stderr-error\n); fprintf(stderr, stderr-error\n); fprintf(stdout, stdout-normal\n);close(fdnor);close(fderr); 八、缓冲区 我们以前学习C语言的文件操作时我们都知道FILE 里面应该是有缓冲区的现在我们学习操作系统时我们又知道操作系统内核里面也是有缓冲区的那这两个缓冲区是一样的吗 对于这个问题我们现在不好回答我们只能先给出结论是不一样的FILE 是C库提供给我们的一个结构体里面的缓冲区对应的是用户态的缓冲区Linux内核中的缓冲区对应的是内核态的缓冲区。 我们先看下面的代码根据现象我们来分析问题最后再来理解一下缓冲区。 #includestdio.h #includeunistd.h #includestring.h int main() { fprintf(stdout, hello fprintf!\n); const char* str hello write!\n; write(1, str, strlen(str)); //创建子进程 fork(); return 0; } 我们发现当我们直接运行和重定向后的结果是不同的而且fprintf会比write多一次打印这时为什么呢 其实呢这与C库的缓冲区有关系缓冲区在哪里?在你用C库函数打开文件的时候你会得到FILE结构体缓冲区就在这个FILE结构体中 函数 fprintf 的返回值类型为 FILE* 在调用 fprintf 函数时会在内存中 malloc 出一个结构体 FILE 并返回其地址。结构体 FILE 中包含文件描述符与缓冲区。当用户使用 fprintf 向 stdout 打印字符串时其实只是把字符串放进了C库的缓冲区里暂时还没有把用户层缓冲区里的数据拷贝到操作系统内核的缓冲区里。C库会结合一定的刷新策略调用 write 函数将用户层缓冲区中的数据写入给OS。 C库的刷新策略有如下三种 无缓冲数据直接刷新到OS内核行缓冲数据先放到用户层缓冲区中如果碰到了 \n 就把 \n 之前的所有数据刷新到OS 内核。全缓冲数据先放到用户层缓冲区中如果用户层缓冲区被写满了就把所有数据刷新到OS 内核。 当直接运行程序时默认是向显示器刷新采取的是行缓冲。遇到 \n 直接把用户缓冲区里 \n 之前的内容刷新到了OS中这就导致在程序最后执行 fork()时用户缓冲区里已经没有数据了我们观测到的结果就是正常显示。 当运行程序并重定向到普通文件时是向普通文件刷新采取的是全缓冲。而我们打印的内容还不足以将缓冲区填满所以到程序最后执行 fork()时用户缓冲区里的数据仍然没有被刷新父子进程各自持有一份数据程序结束时一起刷新到OS中就打印了两次。 因为打印 hello write 使用的是系统调用 write 是直接向OS写入数据的无需用户层刷新所以不管有没有重定向都只打印一次。 为什么C库的FILE里面要有缓冲区呢 答案是节省调用者的时间! 如果我们想直接把数据写到操作系统内核中就需要调用系统调用而系统调用的使用代价是要比普通函数大的多的因此为了尽量少的使用系统调用尽量一次IO能够读取和写入更多的数据所以 FILE内部才有了缓冲区。 操作系统有缓冲区吗 操作系统实际上也是有缓冲区的当我们刷新用户缓冲区的数据时并不是直接将用户缓冲区的数据刷新到磁盘或是显示器上而是先将数据刷新到操作系统缓冲区然后再由操作系统将数据刷新到磁盘或是显示器上。操作系统有自己的刷新机制我们不必关系操作系统缓冲区的刷新规则 因为操作系统是进行软硬件资源管理的软件根据下面的层状结构图用户区的数据要刷新到具体外设必须经过操作系统。
http://www.huolong8.cn/news/439222/

相关文章:

  • 太原网站建设联系方式h5商城网站是什么意思
  • 电子商务网站的规划与建设论文北京市住房和城乡建设部网站首页
  • 网站策划书结尾宁波建网站公司哪家好
  • 新竹自助建站系统线切割加工东莞网站建设技术支持
  • 上海餐饮网站建设2345网址导航设置
  • 网站开发的各个阶段及其完成的任务北京公司名称
  • 电影网站做视频联盟网站开发部门的规章制度
  • 自做跨境电商网站收款清华紫光做网站
  • 郑州网站建设方案ie的常用网站
  • 网站竞价推广哪个好海报设计兼职app
  • 零配件加工东莞网站建设技术支持定制app开发的流程
  • 网站建设与管理学的是什么博客网站设计方案
  • jquery网站后台传奇页游什么好玩
  • 旅游网站开发内容企业网站备案座机号
  • 爱美眉网站源码西安到北京
  • 石家庄规划建设局网站网站建设更改
  • 外贸企业网站策划东莞vi设计公司
  • 百度站长工具使用方法怎样建立网站视频教程
  • 怎么查网站的所有权阿里巴巴网站被关闭了要怎么做
  • 深色网站软文写作实训总结
  • 万云网络网站微信接单任务群
  • 公司网站设计与开发建设工程合同与承揽合同的区别
  • 信息发布网站设计网络规划设计师通常几月考
  • 客户提出网站建设申请常用的网站类型有哪些
  • 苏州市城乡建设局网站社交网站开发
  • 网站怎么做权重新能源电动车
  • 工厂型企业做网站微信开放平台是公众号吗
  • 国际空间站vs中国空间站江苏住房建设厅网站
  • 做网站深紫色搭配什么颜色免费建站赚钱
  • 网站建设模板是什么app软件开发网站