网站服务器提供什么服务,网络营销带来的效果,浙江+外贸网站建设,公司官网制作文章目录 一、重定向1.什么是重定向2.dup2 系统调用3.理解输入重定向、输出重定向和追加重定向4.简易shell完整实现 二、理解linux下一切皆文件三、缓冲区1.为什么要有缓冲区2.缓冲区的刷新策略3.缓冲区的位置4.实现一个简易的C语言缓冲区5.内核缓冲区 一、重定向
1.什么是重定… 文章目录 一、重定向1.什么是重定向2.dup2 系统调用3.理解输入重定向、输出重定向和追加重定向4.简易shell完整实现 二、理解linux下一切皆文件三、缓冲区1.为什么要有缓冲区2.缓冲区的刷新策略3.缓冲区的位置4.实现一个简易的C语言缓冲区5.内核缓冲区 一、重定向
1.什么是重定向
在上面的代码中当我们把0号文件描述符关闭之后会出现下面的状况
#include stdio.h
#include string.h
#include unistd.h
#include sys/types.h
#include fcntl.hint main()
{//关闭标准输出umask(0);int fd open(log.txt, O_WRONLY | O_CREAT | O_TRUNC, 0666);if(fd 0){perror(open failed);return 1;}printf(open fd:%d\n,fd);fprintf(stdout,open fd:%d\n,fd);fflush(stdout);close(fd);return 0;
}我们可以看到本来应该打印到显示器上的数据却最终输出到了 log.txt 文件中原因如下
我们在调用 open 打开 log.txt 之前关闭了标准输出那么其对应的1号 fd 就闲置了出来而 fd 的分配规则是从小到大依次寻找未被使用的最小值所以 log.txt 对应的 fd 就为1
同时我们调用的 printf、fprintf 是C语言封装的输出函数其底层调用系统调用效果是向标准输出文件中写入数据而标准输出 fd 默认为 1但是 printf 与 fprintf 并不知道 1 号 fd 指向了另一个文件 log.txt所以原本应该写入到显示器上的数据会写入到 log.txt 中。
注由于向文件中写数据的缓冲区刷新策略不同所以这里我们需要在关闭文件之前进行缓冲区刷新否则 log.txt 中没有数据具体细节在后文。
像这样本来应该往一个文件中写入数据但是却写入到另一个文件中去了这种特性就叫做重定向而重定向的本质是上层使用的 fd 不变在内核中更改 fd 指向的 file 对象即更改文件描述符表数组中 fd 下标中的内容让其变为另一个 file 对象的地址。(同一个 fd 指向不同的 file 对象)
2.dup2 系统调用
我们可以使用上面 close(1) 的方式实现重定向但是我们发现先关闭、再打开这种方式非常麻烦并且如果 0 和 1 号 fd 都被关闭时我们还需要先创建一个无用的临时文件占用掉 0 号 fd 之后才能使新文件的 fd 为 1。为了解决这种尴尬的情况操作系统提供了一个系统调用接口 dup2 来让我们直接进行重定向。
函数功能
dup2() makes newfd be the copy of oldfd, closing newfd first if necessary – dup2 函数会让 newfd 成为 oldfd 的一份拷贝并且在必要时关闭 newfd。 函数参数
int dup2(int oldfd, int newfd);
# 头文件unistd.h
# oldfd旧的文件描述符
# newfd新的文件描述符
# int函数返回值成功返回 newfd失败返回-1函数使用
#include stdio.h
#include string.h
#include unistd.h
#include sys/types.h
#include sys/stat.h
#include fcntl.hint main()
{int fd open(log.txt, O_WRONLY | O_CREAT | O_TRUNC, 0666);if(fd 0){perror(open failed);return 1;}int n dup2(fd,1);if(n 0){perror(dup2);return 2;}printf(open fd:%d\n,fd);fprintf(stdout,open fd:%d\n,fd);fflush(stdout);close(fd);return 0;
}printf和fprintf是C库当中的IO函数一般往 stdout 中输出但是stdout底层访问文件的时候找的还是fd:1, 但此时fd:1下标所表示内容已经变成了log.txt的地址不再是显示器文件的地址所以输出的任何消息都会往文件中写入进而完成输出重定向。
注意dup2 系统调用让 newfd 成为 oldfd 的一份拷贝其本质是将 oldfd 下标里面存放的 file 对象的地址拷贝到 newfd 下标的空间中即拷贝的是 fd 对应空间中的数据而并不是两个 fd 数字之间进行拷贝这样也没有意义;并且拷贝完成后只会留下 oldfd。
3.理解输入重定向、输出重定向和追加重定向
Linux 中主要有三种主要的重定向 – 输入重定向、输出重定向和追加重定向在 Linux 命令行中它们分别使用 、、 表示演示如下 输出重定向
输入重定向我们上面已经实现了就是通过 dup2(fd, 1) 系统调用将目标文件 fd 中的内容拷贝到 1 号 fd 中从而将本该写入到显示器中的数据写入到目标文件中。
#include stdio.h
#include string.h
#include unistd.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h#define FILE_NAME log.txtint main()
{int fd open(FILE_NAME, O_WRONLY | O_CREAT | O_TRUNC, 0666);if (fd 0){perror(open failed);return 1;}int n dup2(fd, 1);if (n 0){perror(dup2);return 2;}char buffer[64];int cnt 5;while (cnt){sprintf(buffer, %s:%d\n, hello world, cnt--);write(1, buffer, strlen(buffer));}// fflush(stdout);close(fd);return 0;
}追加重定向
理解了输出重定向之后追加重定向就变得非常简单了只需要在打开文件时去掉 O_TRUNC 选项加上 O_APPEND 选项即可。
#include stdio.h
#include string.h
#include unistd.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#define FILE_NAME log.txtint main()
{int fd open(FILE_NAME, O_WRONLY | O_CREAT | O_APPEND, 0666);if (fd 0){perror(open failed);return 1;}int n dup2(fd, 1);if (n 0){perror(dup2);return 2;}printf(fd:%d\n, fd);fprintf(stdout, fd:%d\n, fd);char* msg hello redirect\n;write(1, msg, strlen(msg));fflush(stdout);close(fd);return 0;
}输入重定向
输入重定向就是通过 dup2(fd, 0) 系统调用将目标文件 fd 中的内容拷贝到 0 号 fd 中从而将本该从标准输入 (键盘) 读入的数据转换为从目标文件中读入。
#include stdio.h
#include string.h
#include unistd.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h#define FILE_NAME log.txtint main()
{int fd open(FILE_NAME, O_RDONLY, 0666);if (fd 0){perror(open failed);return 1;}int n dup2(fd, 0);if (n 0){perror(dup2);return 2;}char buffer[64];while(fgets(buffer,sizeof (buffer) - 1,stdin)!NULL){buffer[strlen(buffer)] \0;printf(%s,buffer);}close(fd);return 0;
}4.简易shell完整实现
#include stdio.h
#include unistd.h
#include sys/types.h
#include sys/stat.h
#include sys/wait.h
#include fcntl.h
#include string.h
#include assert.h
#include errno.h
#include ctype.h //isspace
#include stdlib.h#define NUM 1024
#define OPT_NUM 64char commandline[NUM]; // 指令数组
char *myargv[OPT_NUM]; // 指针数组用来保存切割后的字符
int lastsig 0; // 退出信号
int lastcode 0; // 退出码// 定义重定向相关的宏
#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3#define trimspace(start) \do \{ \while (isspace(*start)) \start; \} while (0)int redirtype NONE_REDIR; // 重定向的类型默认为无重定向
char *redirfile NULL; // 重定向的文件的起始位置// ls -a -l -i myfile.txt - ls -a -l -i myfile.txt -
void commandcheck(char *command)
{assert(command);char *start command;char *end start strlen(command);while (start end){if (*start ){*start \0;start;if (*start ){// ls -a file.logredirtype APPEND_REDIR;start;}else{// ls -a file.logredirtype OUTPUT_REDIR;}trimspace(start);redirfile start;break;}else if (*start ){//cat file.txt*start \0;start;trimspace(start);redirtype INPUT_REDIR;redirfile start;break;}else{start;}}
}int main()
{while (1){redirtype NONE_REDIR;redirfile NULL;errno 0;// 输出提示符printf(用户名主机名 当前路径#);fflush(stdout);// 获取用户输入, 输入的时候输入\nchar *s fgets(commandline, sizeof(commandline) - 1, stdin);assert(s ! NULL);(void)s;// 清除最后一个\n , abcd\ncommandline[strlen(s) - 1] 0;// ls -a -l -i - ls -a -l -i - 1-n// ls -a -l -i myfile.txt - ls -a -l -i myfile.txt -// ls -a -l -i myfile.txt - ls -a -l -i myfile.txt -// cat myfile.txt - cat myfile.txt -commandcheck(commandline);myargv[0] strtok(commandline, );int i 1;if (myargv[0] ! NULL strcmp(myargv[0], ls) 0){myargv[i] (char *)--colorauto;}// 如果没有子串了strtok-NULL, myargv[end] NULLwhile (myargv[i] strtok(NULL, ));// 如果是cd命令不需要创建子进程,让shell自己执行对应的命令本质就是执行系统接口// 像这种不需要让我们的子进程来执行而是让shell自己执行的命令 --- 内建/内置命令if (myargv[0] ! NULL strcmp(myargv[0], cd) 0){if (myargv[1] ! NULL){chdir(myargv[1]);}continue;}if (myargv[0] ! NULL myargv[1] ! NULL strcmp(myargv[0], echo) 0){if (strcmp(myargv[1], $?) 0){printf(%d,%d\n, lastcode, lastsig);}else{printf(%s\n, myargv[1]);}continue;}// 执行命令pid_t id fork();assert(id ! -1);if (id 0){// 因为命令是子进程执行的真正重定向的工作一定要是子进程来完成// 如何重定向是父进程要给子进程提供信息的// 这里重定向会影响父进程吗不会进程具有独立性switch (redirtype){case NONE_REDIR:// 什么都不做break;case INPUT_REDIR:{int fd open(redirfile, O_RDONLY);if (fd 0){perror(open failed);exit(errno);}// 重定向的文件已经成功打开了dup2(fd, 0);}break;case OUTPUT_REDIR:case APPEND_REDIR:{umask(0);int flags O_WRONLY | O_CREAT;if (redirtype APPEND_REDIR)flags | O_APPEND;elseflags | O_TRUNC;int fd open(redirfile, flags, 0666);if (fd 0){perror(open);exit(errno);}dup2(fd, 1);}break;default:printf(bug?\n);break;}execvp(myargv[0], myargv); // 执行程序替换的时候会不会影响曾经进程打开的重定向的文件不会exit(1);}int status 0;pid_t ret waitpid(id, status, 0);assert(ret 0);(void)ret;lastcode ((status 8) 0xFF);lastsig (status 0x7F);}
}二、理解linux下一切皆文件
操作系统是一款管理软件它通过向下管理好各种软硬件资源 (手段)来向上提供良好 (安全、稳定、高效) 的运行环境 (目的)也就是说键盘、显示器、磁盘、网卡等硬件也是由操作系统来管理的。而操作系统管理软硬件的方法是 先描述、再组织即先将这些设备的各种属性抽象出来组成一个结构体然后为每一个设备都创建一个结构体对象再用某种数据结构将这些对象组织起来这也就是我们上面学习到的 文件内核数据结构 file
同时每种硬件的访问方法都是不一样的比如向磁盘中读写数据与向网卡中读写数据是有明显差异的所以操作系统需要为每一种硬件都单独提供对应的 Read、Write 方法这些方法位于驱动层。
但是内核数据结构是位于操作系统层的它如何与对应的读写方法联系起来呢-- 通过函数指针即在 struct file 结构体中创建函数指针变量用于指向具体的 Write 和 Read 方法函数这样每一个硬件都可以通过自己 file 对象中的 writep 和 readp 函数指针变量来找到位于驱动层的 Write 和 Read 方法如下
struct file
{//文件的各种属性int types; //文件的类型int status; //文件的状态int (*writep)(...); //函数指针指向读函数int (*readp)(...); //函数指针指向写函数struct file* next; //下一个file对象的地址//...
}如图站在操作系统内核数据结构上层来看所有的软硬件设备和文件统一都是 file 对象即 Linux 下一切皆文件。
注对于键盘来说我们只能从其中读入数据而并不能向其写入数据同样的对于显示器来说我们只能向其写入数据而并不能从它读入数据所以键盘的 Write 方法和显示器的显示器的 Read 方法我们都设为 NULL。 其实 Linux 一切皆文件的特性就是面向对象语言多态的特性file 结构体相当于基类驱动层的各种方法和结构就相当于子类。(Linux 在编写时C等面向对象的语言还并没有出现所以这里是用C语言模拟实现C面向对象)
同时struct file 是操作系统当中虚拟出来的一层文件对象在 Linux 中我们一般将这一层称为 虚拟文件系统 vfs通过它我们就可以摒弃掉底层设备的差别统一使用文件接口的方式来进行文件操作。
三、缓冲区
1.为什么要有缓冲区
缓冲区本质上就是一段内存那么缓冲区是由谁申请的缓冲区属于谁以及为什么要有缓冲区呢
我们以一个快递的例子进行说明 你有一个很好的朋友他在北京而你在重庆你俩经常给对方送东西那么这里有2种选择第一你骑着车或者坐车去北京送给他二是通过顺丰发送快递到你朋友那里。我相信大多数人更愿意选择第二种方案。 顺丰快递不是你送到快递点就立马把你的包裹寄出去而是达到一定的数量之后一起送出去 在现实生活中快递行业的的意义是什么呢节省了发送者的时间。
在上面的案例中重庆就相当于内存北京就相当于磁盘发送的包裹相当于数据但由于磁盘属于外设进程直接向磁盘文件中写数据的效率非常低所以有了缓冲区进程可以将自己的数据拷贝到缓冲区中再由缓冲区将数据写入到磁盘文件中去不过和现实生活中一样顺丰快递不会为了你一个人的一件快递就运输一趟而是快递积累到一定数量时统一运输缓冲区也不会一有数据就立马刷新而是会采取一定的刷新策略。
所以在现实生活中快递行业的意义是节省发送者的时间而在计算机中缓冲区的意义是节省进程进行数据 IO 的时间。
与其理解 fwrite 是将数据写入到文件的函数不如理解 fwrite 是进行数据拷贝的函数因为 fwrite 函数只是将数据从进程拷贝到缓冲区中并没有真正将数据写入到磁盘文件中。
2.缓冲区的刷新策略
于缓冲区中的一块数据一次写入到外设的效率是要高于少量多批次写入到外设的因为缓冲区等待磁盘就绪的时间要远多于写入数据的时间。(比如缓冲区写入一次数据一共要花费 1s那么可能其中 990ms 都在等待外设就绪只有 10ms 左右的时间在进行数据写入)
所以为了提高效率缓冲区一定会结合具体的设备定制自己的刷新策略Linux 中缓冲区一共存在三种刷新策略和两种特殊情况 三种刷新策略 1.立即刷新 (无缓冲)缓冲区中一出现数据就立马刷新这种很少出现 2.行刷新 (行缓冲)每拷贝一行数据就刷新一次显示器采用的就是这种刷新策略因为显示器是给人看了而按行刷新符合人的阅读习惯同时刷新效率也不会太低 3.缓冲区满 (全缓冲)待数据把缓冲区填满后再刷新这种刷新方式效率最高一般应用于磁盘文件。 两种特殊情况 1.用户使用 fflush 等函数强制进行缓冲区刷新 2.进程退出时一般都要进行缓冲区刷新 3.缓冲区的位置
要知道缓冲区的位置我们先来观察下面几个现象
#include stdio.h
#include unistd.h
#include string.h
int main()
{printf(hello printf\n);fprintf(stdout, hello fprintf\n);const char* fputsString hello fputs\n;fputs(fputsString, stdout);const char* msg hello write\n;write(1, msg, strlen(msg));return 0;
}
现象1 现象2 #include stdio.h
#include unistd.h
#include string.h
int main()
{printf(hello printf\n);fprintf(stdout, hello fprintf\n);const char* fputsString hello fputs\n;fputs(fputsString, stdout);const char* msg hello write\n;write(1, msg, strlen(msg));fork();return 0;
}现象3 现象4 我们发现 printf 和 fwrite 库函数都输出了2次而 write 只输出了一次系统调用。为什么呢肯定和
fork有关并且这个现象一定和缓冲区有关并且缓冲区一定不在内存中如果缓冲区在内存中那么write也一定会打印2次。 一般C库函数写入文件时是全缓冲的而写入显示器是行缓冲。 printf fwrite 库函数会自带缓冲区进度条例子就可以说明当发生重定向到普通文件时数据的缓冲方式由行缓冲变成了全缓冲。而我们放在缓冲区中的数据就不会被立即刷新甚至fork之后但是进程退出之后会统一刷新写入文件当中。但是fork的时候父子数据会发生写时拷贝所以当你父进程准备刷新的时候子进程也就有了同样的一份数据随即产生两份数据。write 没有变化说明没有所谓的缓冲 我们之前谈论的所有缓冲区都不在操作系统内核中而是位于用户级语言层面实际上对于C语言来说缓冲区位于 FILE 结构体中Linux 下我们可以在 /usr/include/libio.h 中找到缓冲区的相关信息 综上printf、fwrite、fputs 等库函数会自带缓冲区而 write 系统调用没有带缓冲区同时我们这里所说的缓冲区都是用户级缓冲区。那这个缓冲区谁提供呢 printf、fwrite、fputs 是库函数 write 是系统调用库函数在系统调用的 “上层” 是对系统调用的 “封装”但是 write 没有缓冲区而 printf、fwrite、fputs 有足以说明该缓冲区是二次加上的又因为是C库函数所以是由C标准库提供的。
代码结束之前进行创建子进程
1.如果我们没有进行,看到了4条消息stdout 默认使用的是行刷新在进程fork之前三条C函数已经将数据进行打印输出到显示器上(外设)你的FILE内部进程内部不存在对应的数据啦
2.如果我们进行了, 写入文件不再是显示器而是普通文件采用的刷新策略是全缓冲之前的3条c显示函数虽然带了\n,但是不足以stdout缓冲区写满数据并没有被刷新执行fork的时候stdout属于父进程创建子进程时 紧接着就是进程退出谁先退出一定要进行缓冲区刷新(就是修改)写时拷贝数据最终会显示两份
3.write为什么没有呢上面的过程都和wirte无关wirte没有FILE而用的是fd就没有C提供的缓冲区
现在我们就可以解释上面的四种现象了 现象1printf、fprintf、fputs 三种C语言接口函数都是向标准输出即显示器中打印数据而显示器采用的是行缓冲区同时我们每条打印语句后面都带有换行符所以 printf、fprintf、fputs 语句执行后立即刷新缓冲区而 write 是系统调用不存在缓冲区所以也是语句执行后立即刷新所以输出结果是四条语句顺序打印。 现象2我们通过输入重定向指令 将本该写入到显示器文件中的数据写入到了磁盘文件中由于磁盘文件采用全缓冲刷新策略所以 printf、fprintf、fputs 三条语句执行完毕后数据并不会刷新因为缓冲区并没有被写满而是等到进程退出这种特殊情况才会将三条语句刷新到磁盘文件中但此时write 语句也已经执行完毕而 write 系统调用没有缓冲区执行立即写入所以输出结果是 write 在最前面。 现象3显示器采用行缓冲所以在 fork 之前 printf、fprintf、fputs 三条语句的数据均已刷新到显示器上了而对于进程数据来说如果数据位于缓冲区内那么该数据属于进程此时 fork 子进程也会指向该数据但如果该数据已经写入到磁盘文件了那么数据就不属于进程了此时 fork 子进程也不在指向该数据了所以这里 fork 子进程不会做任何事情结果和现象1一样。 现象4我们使用重定向指令将本该写入显示器文件的数据写入到磁盘文件中而磁盘文件采用全缓冲所以 fork 子进程时 printf、fprintf、fputs 的数据还存在于缓冲区中 (缓冲区没满同时父进程还没有退出所以缓冲区没有刷新)也就是说此时数据还属于父进程那么 fork 之后子进程也会指向该数据而 fork 之后紧接着就是进程退出父子进程某一方先退出时会刷新缓冲区由于刷新缓冲区会清空缓冲区中的数据为了保持进程独立性先退出的一方会发生 写时拷贝然后向磁盘文件中写入 printf、fprintf、fputs 三条数据然后后退出的一方也会进行缓冲区的刷新所以最终 printf、fprintf、fputs 的数据会写入两份 (父子进程各写入一份)且 write 由于属于系统调用没有缓冲区所以只写入一份数据且最先写入。 4.实现一个简易的C语言缓冲区
在理解了缓冲区的各种原理之后我们可以手动的实现一个简易的C语言缓冲区即实现一个简易的 FILE 结构体以及 fopen、fwrite、fclose、fflush 等C语言文件操作的相关函数来使得我们对缓冲区的理解能够更加深刻。
mystdio.h
#pragma once #include stdio.h
#include string.h
#include assert.h
#include errno.h
#include stdlib.h
#include sys/types.h
#include sys/stat.h
#include unistd.h
#include fcntl.h#define SIZE 1024
#define SYNC_NOW 1
#define SYNC_LINE 2
#define SYNC_FULL 4typedef struct EILE_
{int _fileno;//文件描述符int _flags;//刷新方式char buffer[SIZE];int _capacity;int _size;
}_FILE;_FILE* fopen_(const char* path_name,const char* mode);
void fwrite_(const void* ptr,size_t size,_FILE* fp);
void fclose_(_FILE* fp);
void fflush_(_FILE* fp);mystdio.c
#include mystdio.h_FILE *fopen_(const char *path_name, const char *mode)
{int flags 0;int defaultmode 0666; // 文件的权限if (strcmp(mode, r) 0){flags | O_RDONLY;}else if (strcmp(mode, w) 0){flags | (O_WRONLY | O_CREAT | O_TRUNC);}else if (strcmp(mode, a) 0){flags | (O_WRONLY | O_CREAT | O_APPEND);}else{// TODO}int fd 0;if (flags O_RDONLY)fd open(path_name, flags);elsefd open(path_name, flags, defaultmode);if (fd 0){const char *err strerror(errno);write(2, err, strlen(err));return NULL; // 为什么C语言文件打开失败返回NULL的原因}_FILE *fp (_FILE *)malloc(sizeof(_FILE));assert(fp);fp-_fileno fd;fp-_size 0;fp-_capacity SIZE;fp-_flags SYNC_LINE; // 默认为行刷新memset(fp-buffer, 0, SIZE);return fp; // 为什么打开一个文件就会返回一个FILE *指针
}void fwrite_(const void *ptr, size_t size, _FILE *fp)
{// 1. 写入到缓冲区中memcpy(fp-buffer fp-_size, ptr, size); // 这里我们不考虑缓冲区溢出的问题fp-_size size;// 2. 判断是否刷新// 1.立即刷新直接写入if (fp-_flags SYNC_NOW){write(fp-_fileno, fp-buffer, fp-_size);fp-_size 0;}// 2.行刷新判断最后的字符为\0else if (fp-_flags SYNC_LINE){if (fp-buffer[fp-_size - 1] \0){write(fp-_fileno, fp-buffer, fp-_size);fp-_size 0;}}// 3.全刷新判断缓冲区是否写满else if (fp-_flags SYNC_FULL){if (fp-_size fp-_capacity){write(fp-_fileno, fp-buffer, fp-_size);fp-_size 0;}}else{}
}
void fclose_(_FILE *fp)
{fflush_(fp);close(fp-_fileno);
}
void fflush_(_FILE *fp)
{if (fp-_size 0)write(fp-_fileno, fp-buffer, fp-_size);fsync(fp-_fileno); // 将数据强制要求OS进行外设刷新fp-_size 0;
}test.c
#include mystdio.h#define FILE_NAME log.txtint main()
{_FILE *fp fopen_(FILE_NAME, w);if (fp NULL){perror(open failed);return 1;}int cnt 5;char buffer[64];while (cnt){sprintf(buffer, %s:%d\n, hello world, cnt--);fwrite_(buffer, strlen(buffer), fp);}fclose_(fp);return 0;
}5.内核缓冲区
我们之前理解的通过C语言文件接口向磁盘文件写入数据的过程是这样的进程先通过 fprintf、fwrite、fputs 等函数将数据拷贝到缓冲区中然后再由缓冲区以某种刷新方式刷新 (写入) 到磁盘文件中
但实际上缓冲区并不是直接将数据写入到磁盘文件中的而是先将数据拷贝到 内核缓冲区 – 位于内核数据结构 file 结构体中的一块内存空间 中最后再由操作系统自主决定以什么样的刷新策略将数据写入到外设中而这个写入的过程和用户毫无关系。
也就是说我们向外设中写入数据其实一共分为三个步骤 – 先通过 fwrite 等语言层面的文件操作接口将进程数据拷贝到语言层面的缓冲区中然后再根据缓冲区的刷新策略 (无、行、全) 通过 write 系统调用将数据拷贝到 file 结构体中的内核缓冲区中最后再由操作系统自主将数据真正的写入到外设中。(所以 fwrite 和 write 其实叫做拷贝函数更合适
注这里操作系统的刷新策略比我们应用层 FILE 中的缓冲区的刷新策略要复杂的多因为操作系统要根据不同的整体内存使用情况来选择不同的刷新策略而不仅仅是死板的分为分行缓冲、全缓冲、无缓冲这么简单。
这里还存在一种特殊情况既然进程数据被拷贝到内核缓冲区中再由操作系统自主刷新那么如果操作系统崩溃了就势必会出现数据丢失这样对于像银行这种对数据丢失0容忍的机构来说就存在一定的风险所以操作系统提供了一个系统调用函数 fsync其作用就是将内核缓冲区中的数据立刻直接同步到外设中而不再采用操作系统的刷新策略。
我们可以使用 fsync 接口将我们实现的简易C语言缓冲区设置为在内核缓冲区中采用无缓冲
fwrite 等C语言库函数具有缓冲区该缓冲区位于 FILE 结构体中我们通过这些接口向外设写入数据时需要先将数据拷贝到缓冲区中然后再由缓冲区根据特定的刷新策略将数据写入到外设中而 write 等系统调用没有缓冲区进程数据将直接写入到外设中 文章转载自: http://www.morning.prznc.cn.gov.cn.prznc.cn http://www.morning.jzlkq.cn.gov.cn.jzlkq.cn http://www.morning.bfkrf.cn.gov.cn.bfkrf.cn http://www.morning.nzfqw.cn.gov.cn.nzfqw.cn http://www.morning.sgqw.cn.gov.cn.sgqw.cn http://www.morning.myxps.cn.gov.cn.myxps.cn http://www.morning.rpwht.cn.gov.cn.rpwht.cn http://www.morning.rlhh.cn.gov.cn.rlhh.cn http://www.morning.dzgmj.cn.gov.cn.dzgmj.cn http://www.morning.rnsjp.cn.gov.cn.rnsjp.cn http://www.morning.wnnlr.cn.gov.cn.wnnlr.cn http://www.morning.gpnwq.cn.gov.cn.gpnwq.cn http://www.morning.llsrg.cn.gov.cn.llsrg.cn http://www.morning.kwqqs.cn.gov.cn.kwqqs.cn http://www.morning.sqyjh.cn.gov.cn.sqyjh.cn http://www.morning.xrtsx.cn.gov.cn.xrtsx.cn http://www.morning.tsgxz.cn.gov.cn.tsgxz.cn http://www.morning.bzcjx.cn.gov.cn.bzcjx.cn http://www.morning.yhtnr.cn.gov.cn.yhtnr.cn http://www.morning.kfhm.cn.gov.cn.kfhm.cn http://www.morning.mcjp.cn.gov.cn.mcjp.cn http://www.morning.ntwfr.cn.gov.cn.ntwfr.cn http://www.morning.cjxqx.cn.gov.cn.cjxqx.cn http://www.morning.mydgr.cn.gov.cn.mydgr.cn http://www.morning.wchsx.cn.gov.cn.wchsx.cn http://www.morning.txmkx.cn.gov.cn.txmkx.cn http://www.morning.ftync.cn.gov.cn.ftync.cn http://www.morning.dfdhx.cn.gov.cn.dfdhx.cn http://www.morning.qptbn.cn.gov.cn.qptbn.cn http://www.morning.sqfrg.cn.gov.cn.sqfrg.cn http://www.morning.sggzr.cn.gov.cn.sggzr.cn http://www.morning.grfhd.cn.gov.cn.grfhd.cn http://www.morning.npxcc.cn.gov.cn.npxcc.cn http://www.morning.dgsr.cn.gov.cn.dgsr.cn http://www.morning.hjjfp.cn.gov.cn.hjjfp.cn http://www.morning.xxsrm.cn.gov.cn.xxsrm.cn http://www.morning.yggdq.cn.gov.cn.yggdq.cn http://www.morning.rfgc.cn.gov.cn.rfgc.cn http://www.morning.xzsqb.cn.gov.cn.xzsqb.cn http://www.morning.knqck.cn.gov.cn.knqck.cn http://www.morning.qlhwy.cn.gov.cn.qlhwy.cn http://www.morning.jjzxn.cn.gov.cn.jjzxn.cn http://www.morning.ksqzd.cn.gov.cn.ksqzd.cn http://www.morning.hdscx.cn.gov.cn.hdscx.cn http://www.morning.jiuyungps.com.gov.cn.jiuyungps.com http://www.morning.jkzq.cn.gov.cn.jkzq.cn http://www.morning.ckctj.cn.gov.cn.ckctj.cn http://www.morning.rydbs.cn.gov.cn.rydbs.cn http://www.morning.prznc.cn.gov.cn.prznc.cn http://www.morning.rlbg.cn.gov.cn.rlbg.cn http://www.morning.kzhxy.cn.gov.cn.kzhxy.cn http://www.morning.tthmg.cn.gov.cn.tthmg.cn http://www.morning.zcqbx.cn.gov.cn.zcqbx.cn http://www.morning.wqjpl.cn.gov.cn.wqjpl.cn http://www.morning.wdxr.cn.gov.cn.wdxr.cn http://www.morning.mwwnz.cn.gov.cn.mwwnz.cn http://www.morning.dndjx.cn.gov.cn.dndjx.cn http://www.morning.pbknh.cn.gov.cn.pbknh.cn http://www.morning.kdxzy.cn.gov.cn.kdxzy.cn http://www.morning.pxjp.cn.gov.cn.pxjp.cn http://www.morning.bqmhm.cn.gov.cn.bqmhm.cn http://www.morning.pbgnx.cn.gov.cn.pbgnx.cn http://www.morning.pbygt.cn.gov.cn.pbygt.cn http://www.morning.mtgkq.cn.gov.cn.mtgkq.cn http://www.morning.pnntx.cn.gov.cn.pnntx.cn http://www.morning.gjqgz.cn.gov.cn.gjqgz.cn http://www.morning.knczz.cn.gov.cn.knczz.cn http://www.morning.ysllp.cn.gov.cn.ysllp.cn http://www.morning.yzygj.cn.gov.cn.yzygj.cn http://www.morning.grxsc.cn.gov.cn.grxsc.cn http://www.morning.pzlcd.cn.gov.cn.pzlcd.cn http://www.morning.tndxg.cn.gov.cn.tndxg.cn http://www.morning.zthln.cn.gov.cn.zthln.cn http://www.morning.lqchz.cn.gov.cn.lqchz.cn http://www.morning.grxyx.cn.gov.cn.grxyx.cn http://www.morning.rrxnz.cn.gov.cn.rrxnz.cn http://www.morning.kflpf.cn.gov.cn.kflpf.cn http://www.morning.fnlnp.cn.gov.cn.fnlnp.cn http://www.morning.srkwf.cn.gov.cn.srkwf.cn http://www.morning.bqdgr.cn.gov.cn.bqdgr.cn