c#+网站开发实例,wordpress入侵工具,wordpress 请提供有效的用户名.,做英文网站怎么赚钱目录
重谈重定向
理解一切皆文件
缓冲区
什么是缓冲区#xff1f;
为什么要引入缓冲区机制#xff1f;
细节
模拟封装glibc-文件接口
源码 重谈重定向
重点标准错误
C语言C都有自己的标准输入输出 ./a.out log.txt实质上就是shell获得命令行…目录
重谈重定向
理解一切皆文件
缓冲区
什么是缓冲区
为什么要引入缓冲区机制
细节
模拟封装glibc-文件接口
源码 重谈重定向
重点标准错误
C语言C都有自己的标准输入输出 ./a.out log.txt实质上就是shell获得命令行字符串先做分割判断是输出重定向确定重定向的文件一方面重定向内容目标文件信息保存起来一方面未来执行fork再进行子进程程序替换之前先进行输出重定向
会把3号文件中的内容覆盖到1号里面而1不再进行标准输出反而指向我们的file而上层的代向标准输出进行打印只认文件描述符所以最后找到1向文件中打印
其实重定向的完整写法是
./a.out 1 log.txt这种写法可以做到见名知意就是把1重定向到新文件log.txt但是我们平常会把1省略默认就是向标准输出进行输出。
我们再加上向标准错误打印的代码我们stderrcerr是向2号打印而标准输出和标准错误对应的都是显示器相当于显示器文件被我们进程打开了两次 但是我们执行重定向命令时却产生了问题 为什么我们的标准输出写到了文件里而标准错误 却还依旧在显示器进行打印呢
原因是因为我们在进行输出的时候虽然我们的标准输出和标准错误都指向同一个文件但当我进行重定向时它的本质是把1重定向到新文件即把新打开的文件描述符的struct_file地址拷贝到1里面所以我们在这里重定向时只是把1进行重定向而2还是正常向显示器里打印
所以当我们在进行输出重定向时为什么它只会把标准输出往log.txt中打印因为我们只做了的标准输出的重定向没有做标准错误的重定向那如何对标准错误进行重定向
./a.out 1 log.normal 2 log.error 所以为什么我们的程序存在标准输出和标准输入还存在一个标准错误呢
因为把标准输出和标准错误做分离其实本质上占用是不同的文件描述符虽然都对应的显示器但是未来我们可以通过重定向能力把常规消息和错误消息进行分离
所以在我们的任何语言里提供标准错误就是为了让我们用重定向的能力方便我们日志的形成
如果我们把stderr和stdout打印到同一个文件呢 我们发现最后文件中只有标准错误
这是因为第一次重定向把标准输出打印到文件里第二次重定向把文件内容清空再打印标准错误
所以我们只要用追加重定向就能看到标准输出和标准错误了 除此之外我们还有一种写法 前面的意思就是把log.txt打开 然后把3的内容覆盖到1里面
后面的取地址符1是shell的语法把1里面的内容写到2里面 读写位置
无论是文本文件还是二进制文件在我看来就是一个char类型的数组只不过char类型数组连起来就是我们的文本文件也可能是8个bit位的二进制流所以读写一个文件本质是读写一个文件的开始位置以及你的偏移量所以要有f_pos来标明当前文件的读写位置又因为文件的读写位置是一个整数只有一个所以当读写同时进行时你刚写入的内容你想再读上来需要更改文件读写位置即f_pos将其回归到最开始。
内存
操作系统管理内存的时候也不是把内存整体使用的而是把操作系统的空间划分为块为单位的以4KB的大小划分为若干代码块所以在我们看来一个4GB的内存是由4GB/4KB个数据页构成的所以在操作系统内一定会存在很多很多的内存块有的是正在占用的有的是正在清理的所以操作系统一定要管理这些内存块怎么管理?
先描述再组织所以在操作系统中一定存在描述内存的结构体这个我们先提一嘴在后续章节再讲 其实file结构体是操作系统内打开的文件但是我们文件相关的硬属性并没有直接在file里存而是在另一个数据结构inode中存这个我们在下一章文件系统学习换句话说我们可以通过file结构体间接找到文件的属性和指定的文件缓冲区 理解一切皆文件
首先在windows中是文件的东西它们在linux中也是文件其次一些在windows中不是文件的东西比如进程、磁盘、显示器、键盘这样硬件设备也被抽象成了文件你可以使用访问文件的方法访问它们获得信息甚至管道也是文件将来我们要学习网络编程中的socket套接字这样的东西使用的接口跟文件接口也是一致的。
这样做最明显的好处是开发者仅需要使用一套 API 和开发工具即可调取 Linux 系统中绝大部分的资源。举个简单的例子Linux 中几乎所有读读文件读系统状态读PIPE的操作都可以用 read 函数来进行几乎所有更改更改文件更改系统参数写 PIPE的操作都可以用 write 函数来进行。 通过函数指针访问上层就不需要关心底层是键盘显示器还是鼠标它只要调用writeread函数指针直接访问就屏蔽了底层的硬件差异struct file往上就是进程骗了进程就是骗了用户因为当前进程都是通过struct file文件描述符来访问所有的文件所以只要把进程骗过去让进程认为一切皆文件就屏蔽了底层差异所以我们把struct file 往上叫做一切皆文件
所以我们对虚拟文件系统一切皆文件的理解就是在系统当中访问任何设备只要它提供文件描述符我就可以不再关心底层文件的差异而直接使用struct file里的函数指针对文件进行访问。
上图中的外设每个设备都可以有自己的read、write但一定是对应着不同的操作方法但通过struct file下file_operation中的各种函数回调让我们开发者只用file便可调取Linux系统中绝大部分的资源这便是“linux下一切皆文件”的核心理解。
访问不同设备的函数指针如下 缓冲区
什么是缓冲区
缓冲区就相当于我们现在的菜鸟驿站别人给你寄快递的时候你当前可能有事来不及处理这个包裹所以快递员就把你的包裹放到了楼下的菜鸟驿站中给你缓冲起来将来你取快递只要去菜鸟驿站取就可以了。
更重要的是如果你是快递员你今天去打电话送快递但是这个人根本就不在那么你就只能等等待的过程过长今天就发不了几个快递但是有菜鸟驿站就不一样了它就不需要等任何人只要把包裹放到菜鸟驿站就可以了提高了快递员的快递效率也方便了用户。
快递员就是操作系统你就是用户而菜鸟驿站就算是缓冲区
缓冲区是内存空间的一部分。也就是说在内存空间中预留了一定的存储空间这些存储空间用来缓冲输入或输出的数据这部分预留的空间就叫做缓冲区。缓冲区根据其对应的是输入设备还是输出设备分为输入缓冲区和输出缓冲区。
缓冲区分为用户级缓冲区和操作系统缓冲区自己定义的缓冲区都是内存的一段空间 为什么要引入缓冲区机制
提高使用者的效率
读写文件时如果不开辟对文件操作的缓冲区直接通过系统调用对磁盘进行操作读、写等那么每次对文件进行一次读写操作时都需要使用读写系统调用来处理此操作即需要执行一次系统调用执行一次系统调用将涉及到CPU状态的切换即从用户空间切换到内核空间实现进程上下文的切换这将损耗一定的CPU时间频繁的磁盘访问对程序的执行效率造成很大的影响。
为了减少使用系统调用的次数提高效率我们可以采用缓冲机制。比如从磁盘里取信息时在对磁盘文件进行操作时可以一次从文件中读出大量的数据到缓冲区中以后对这部分的访问就不需要再使用系统调用了等缓冲区的数据取完后再去磁盘中读取这样就可以减少磁盘的读写次数。再加上计算机对缓冲区的操作大大快于对磁盘的操作故应用缓冲区可大大提高计算机的运行速度。
又比如使用打印机打印文档时由于打印机的打印速度相对较慢我们先把文档输出到打印机相应的缓冲区打印机再自行逐步打印这时CPU可以处理别的事情。可以看出缓冲区就是一块内存区它用在输入输出设备和CPU之间用来缓存数据。它使得低速的输入输出设备和高速的CPU能够协调工作避免低速的输入输出设备占用CPU解放出CPU使其能够高效率工作。 细节
我们来看一个现象 为什么会这样
所以为什么关闭fd就没法将printf的函数写入到文件中呢
因为当你printf所输出的所有字符串其实是没有写到操作系统内的它处于语言层的缓冲区里
当我调用close代码的时候进程也没有退出它既没有强制刷新也没有满足刷新条件进程也没有退出所以数据会一直在语言层的缓冲区中后来我们close了fd进程退出了C语言要刷新缓冲区但是fd已经关掉了所以无法将数据从语言层交付到操作系统内因此数据也不可能被刷新到某种硬件上我们也就看不到对应的文件内容。
怎么保证能刷新呢
很简单 fflush调用刷新函数 这个时候我们文件缓冲区的内容就显示到文件中了 结论就是FILE里会存在文件描述符fd和缓冲区
什么叫做格式化输出
本质上是把%d,%f这些格式化输出成字符串然后将其放到C语言的缓冲区中
细节2
刷新条件是什么意思
1.立即刷新 --- 无缓冲 --- 写透模式 WT
2.缓冲区满了再刷新 --- 全缓冲
3.行刷新 --- 行缓冲
系统调用是有成本的如果频繁调用程序的效率会变低
要提高效率就在语言层提供缓冲区目的在于减少系统调用次数就大大提高了C标准库发fputs/fprintf/fwrite等接口效率因为它不需要过多的系统调用。
是不是写满缓冲区再刷新即全缓冲的效率最高
是的因为这样系统调用次数少
但是全缓冲一般用于普通文件
行刷新一般用于显示器为什么因为这样比较适合用户它如果用全缓冲一次刷新一大片内容对用户的体验不好
上面这三种刷新方式操作系统也在用但是操作系统具体什么时候刷新要不要强制刷新我们不关心由操作系统自主决定。只要把数据交给了OS就相当于交给了硬件
数据交给系统交给硬件本质上全是拷贝
计算机数据流动的本质一切皆拷贝 我们的第一份代码向显示器和文件打印的时候都是四条消息
为什么我们的第二个代码执行的向显示器打印是四条消息而向文件中打印是七条消息
我们发现库函数调用了两次系统函数只调用了一次
原因是因为当我在fork时对应的三条库函数还在缓冲区里最后子进程结束刷新一次父进程结束刷新一次一共刷新两次
而系统函数执行到该语句时直接就写给操作系统了fork时就不存在刷新问题了所以只有一条
./a.out //向显示器刷新 行缓冲
./a.out log.txt 向文件刷新 全缓冲
重定向不只是重定向还改变了刷新方式 模拟封装glibc-文件接口
首先我们需要一个结构体来描述file
需要包含文件描述符打开文件的方式大小有效元素长度刷新方式 然后我们实现自己的fopenfclosefwritefflush封装对应的系统调用函数就能完成 源码
mystdio.h
#pragma once#include stdio.h#define MAX 1024
#define NONE_FLUSH (10)
#define LINE_FLUSH (11)
#define FULL_FLUSH (12)typedef struct IO_FILE
{int fileno;int flag;char outbuffer[MAX];int bufferlen;int flush_method;
}MyFile;MyFile *MyFopen(const char *path, const char *mode);
void MyFclose(MyFile *);
int MyFwrite(MyFile *, void *str, int len);
void MyFFlush(MyFile *);
mystdio.c
#include mystdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include string.h
#include stdlib.h
#include unistd.hstatic MyFile *BuyFile(int fd, int flag)
{MyFile *f (MyFile*)malloc(sizeof(MyFile));if(f NULL) return NULL;f-bufferlen 0;f-fileno fd;f-flag flag;f-flush_method LINE_FLUSH;memset(f-outbuffer, 0, sizeof(f-outbuffer));return f;
}MyFile *MyFopen(const char *path, const char *mode)
{int fd -1;int flag 0;if(strcmp(mode, w) 0){flag O_CREAT | O_WRONLY | O_TRUNC;fd open(path, flag, 0666);}else if(strcmp(mode, a) 0){flag O_CREAT | O_WRONLY | O_APPEND;fd open(path, flag, 0666);}else if(strcmp(mode, r) 0){flag O_RDWR;fd open(path, flag);}else{//TODO}if(fd 0) return NULL;return BuyFile(fd, flag);
}
void MyFclose(MyFile *file)
{if(file-fileno 0) return;MyFFlush(file);close(file-fileno);free(file);
}
int MyFwrite(MyFile *file, void *str, int len)
{// 1. 拷贝memcpy(file-outbufferfile-bufferlen, str, len);file-bufferlen len;// 2. 尝试判断是否满足刷新条件if((file-flush_method LINE_FLUSH) file-outbuffer[file-bufferlen-1] \n){MyFFlush(file);}return 0;
}
void MyFFlush(MyFile *file)
{if(file-bufferlen 0) return;// 把数据从用户拷贝到内核文件缓冲区中int n write(file-fileno, file-outbuffer, file-bufferlen);(void)n;fsync(file-fileno);file-bufferlen 0;
}