网站分站系统,物联网应用技术就业方向及前景,哪个公司建网站最好,帮别人做网站用织梦模板行吗一、共识原理#xff1a; 1. 文件内容属性#xff0c;内容与属性都是数据#xff0c;都要在磁盘中保存。 2. 文件分为打开的文件和没打开的文件。 3. 研究打开的文件#xff1a;本质是研究进程和文件的关系#xff0c;因为是进程负责打开文件。 4. 没打开的文件在存储介质…一、共识原理 1. 文件内容属性内容与属性都是数据都要在磁盘中保存。 2. 文件分为打开的文件和没打开的文件。 3. 研究打开的文件本质是研究进程和文件的关系因为是进程负责打开文件。 4. 没打开的文件在存储介质如磁盘上存储。没有被打开的文件非常多所以文件如何被分门别类地归置好成为了我们需要重视的问题我们要快速地进行找到并增删查改文件。
二、研究被打开的文件 1、文件被打开必须先被加载到内存。而文件的属性必须先加载到内存而内容是否加载取决于用户后续是否要将文件写入读取。 2、系统启动时默认以文件形式文件指针/文件句柄file*打开三个输入输出流。由此观之一个进程可以打开多个文件。显然操作系统内部一定存在大量的被打开的文件 3、那么OS操作系统如何管理这些被打开的文件呢 先描述再组织在内核中一个被打开的文件都必须有自己的文件打开对象struct结构体其中包含文件的很多属性。所有定义的数据结构对象在内核里就可以以链表的形式管理起来从而对打开文件的管理就转变为对链表的增删查改。
4、常用接口 ①先打开一个文件试试
#include stdio.hint main()
{//打开文件的路径和文件名默认在当前路径下新建一个文件。FILE *fp fopen(log.txt, w);if(fp NULL){perror(fopen);return 1;}fclose(fp);return 0;
}②打开文件的路径和文件名默认在当前路径下新建一个文件。如果打开文件不存在则会新建一个文件。
当前路径即为进程当前路径cwd.
那让我们实操一下寻找一下进程的当前路径吧~
#include stdio.h
#include unistd.hint main()
{printf(Pid: %d\n, getpid());//打开文件的路径和文件名默认在当前路径下新建一个文件。FILE *fp fopen(log.txt, w);if(fp NULL){perror(fopen);return 1;}fclose(fp);sleep(1000);return 0;
}如果用户更改了当前进程的cwd就可以把文件新建到其他目录。 可以使用chdir修改路径。 #include stdio.h
#include unistd.hint main()
{chdir(/home/aaa);//更改当前路径printf(Pid: %d\n, getpid());//打开文件的路径和文件名默认在当前路径下新建一个文件。FILE *fp fopen(log.txt, w);if(fp NULL){perror(fopen);return 1;}fclose(fp);sleep(1000);return 0;
}③ 写入注该接口返回值为实际写入基本单位个数而非总大小 #include stdio.h
#include unistd.h
#include string.hint main()
{printf(Pid: %d\n, getpid());//打开文件的路径和文件名默认在当前路径下新建一个文件。FILE *fp fopen(log.txt, w);if(fp NULL){perror(fopen);return 1;}const char *message hello Linux message; //strlen(message)无需1 因为/0是一种不可显字符会被vim这样的文本编辑器解释成乱码。fwrite(message, strlen(message), 1, fp); fclose(fp);return 0;
}w清空并写入。
重定向写入等同于fopen(log.txt, w)
echo hello Linux log.txt
a追加写入。
注在c标准库里fopen在语言层为用户mallocFILE故返回值为FILE*。
④ C语言默认在启动的时候会打开三个标准输入输出流文件 stdin键盘文件 stdout显示器文件 stderr显示器文件
C默认在启动的时候会打开三个标准输入输出流文件 cin键盘文件 cout显示器文件 cerr显示器文件
#include stdio.h
#include unistd.h
#include string.hint main()
{printf(Pid: %d\n, getpid());FILE *fp fopen(log.txt, w);if(fp NULL){perror(fopen);return 1;}const char *message hello Linux message;//strlen(message)无需1 fwrite(message, strlen(message), 1, stdout);fclose(fp);return 0;
}三、过渡到系统认识文件系统调用 文件其实是在磁盘上的磁盘是外部设备访问磁盘文件实际上是访问硬件几乎所有的库只要是访问硬件设备必定要封装系统调用接口 1、open返回文件描述符表下标 上图的大写选项均为宏一个整数有32个比特位一个比特位就能表示一种状态所以在Linux中仅用一个整数就能同时传递多个比特位向系统传递多个选项。接下来就让我们用代码阐述比特位级别的标志位传递方式
#include stdio.h
//#include unistd.h
//#include string.h#define ONE (10)// 1
#define TWO (11)// 2
#define THREE (12)// 4
#define FOUR (13)// 8void show(int flags)
{if(flagsONE) printf(hello function1\n);if(flagsTWO) printf(hello function2\n);if(flagsTHREE) printf(hello function3\n);if(flagsFOUR) printf(hello function4\n);
}int main()
{show(ONE);printf(-----------------\n);show(TWO);printf(-----------------\n);show(ONE|TWO);printf(-----------------\n);show(ONE|TWO|THREE);printf(-----------------\n);show(ONE|TWO|THREE|FOUR);printf(-----------------\n);
}接下来让我们实战演练一下吧
#include stdio.h
#include unistd.h
#include string.h
#include sys/types.h
#include sys/stat.h
#include fcntl.hint main()
{int fd open(log.txt, O_WRONLY|O_CREAT, 0666);if(fd 0){printf(open file error\n);return 1;}return 0;
}我们会发现一个奇怪的现象我们打开文件时设置权限为0666可为何log.txt的权限为0664呢 这当然是因为当前文件的权限掩码为0022 那要是我们一定要创建文件权限为0666呢main函数中umask清零即可。
#include stdio.h
#include unistd.h
#include string.h
#include sys/types.h
#include sys/stat.h
#include fcntl.hint main()
{umask(0);//权限掩码清零int fd open(log.txt, O_WRONLY|O_CREAT, 0666);if(fd 0){printf(open file error\n);return 1;}return 0;
}
2、close
3、write(覆盖式向文件写入但不会清空)
#include stdio.h
#include unistd.h
#include string.h
#include sys/types.h
#include sys/stat.h
#include fcntl.hint main()
{umask(0);//file descriptor: 文件描述符fdintint fd open(log.txt,O_WRONLY|O_CREAT,0666);if(fd 0){printf(open file error\n);return 1;}const char *message hello file system call;write(fd, message, strlen(message));//无需1close(fd);return 0;
}其余库函数的底层原理都是对系统调用接口的封装。
四、访问文件的本质 1、操作系统内描述一个被打开文件信息的结构体struct file。 直接或者间接包含如下属性在磁盘的位置文件基本属性权限大小读写位置打开者文件的内核缓冲区信息struct file *next指针引用计数count文件描述符表对应打开文件的缓冲区字段和维护信息。该结构体对象属于用户不属于操作系统 2、而现在知道文件描述符就是从 0 开始的小整数。当我们打开文件时操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了FILE 结构体。表示一个已经打开的文件对象。而进程执行 open 系统调用所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表 files_struct, 该表最重要的部分就是包涵一个指针数组每个元素都是一个指向打开文件的指针所以本质上文件描述符就是该数组的下标。所以只要拿着文件描述符就可以找到对应的文件。 3、 #include stdio.h
#include unistd.h
#include string.h
#include sys/types.h
#include sys/stat.h
#include fcntl.hint main()
{umask(0);//file descriptor: 文件描述符fdintint fd open(log.txt,O_WRONLY|O_CREAT,0666);if(fd 0){printf(open file error\n);return 1;}printf(fd: %d\n, fd);const char *message hello file system call;write(fd, message, strlen(message));//无需1close(fd);return 0;
}可是文件描述符表的下标为什么是从三开始呢 因为C语言默认在启动的时候会打开三个标准输入流。 以下对应的文件描述符为 stdio键盘文件 0 stdout显示器文件 1 stderr显示器文件 2 4、所以这只是C语言的特性吗不是这是操作系统的特性进程默认会打开键盘显示器显示器。
5、FILE是C库自己封装的结构体这里面必须封装文件描述符。
6、文件描述符的分配规则从0下标开始寻找最小的没有使用的数组位置它的下标就是新文件的文件描述符。
总结两层封装①库函数封装系统调用接口 ②文件FILE*类型必定包含文件描述符
7、关闭文件的核心动作①引用计数--②文件描述符表数组下标置空。
五、输入输出重定向
1、重定向
①
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include string.h
#include unistd.h
#define filename log.txtint main()
{//close();int fd open(filename, O_CREAT|O_WRONLY|O_TRUNC, 0666);if(fd 0){perror(open);return 1;}printf(fd: %d\n, fd);const char *msg hello Linux\n;int cnt 5;while(cnt){write(fd, msg, strlen(msg));cnt--;}close(fd);return 0;
}以下是关闭不同fd的结果 close(0); close(1); close(2); 由此观之文件描述符表的分配规则为从0下标开始寻找最小的没使用的数组位置它的下标就是新文件的文件描述符。
②再来做个小实验
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include string.h
#include unistd.h
#define filename log.txtint main()
{//close(1);int fd open(filename, O_CREAT|O_WRONLY|O_TRUNC, 0666);if(fd 0){perror(open);return 1;}//printf(fd: %d\n, fd); const char *msg hello Linux\n;int cnt 5;while(cnt){write(1, msg, strlen(msg));cnt--;}close(fd);return 0;
}如上代码的结果是这样的
[roothecs-210801 lesson21]# ./mytest
hello Linux
hello Linux
hello Linux
hello Linux
hello Linux关闭下标为1的fd
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include string.h
#include unistd.h
#define filename log.txtint main()
{close(1);int fd open(filename, O_CREAT|O_WRONLY|O_TRUNC, 0666);//该文件的fd变为1if(fd 0){perror(open);return 1;}//printf(fd: %d\n, fd); const char *msg hello Linux\n;int cnt 5;while(cnt){write(1, msg, strlen(msg));cnt--;}close(fd);return 0;
}[roothecs-210801 lesson21]# ./mytest
[roothecs-210801 lesson21]# cat log.txt
hello Linux
hello Linux
hello Linux
hello Linux
hello Linux本该打印在显示器上的内容转而打印在了普通文件中这就叫做输出重定向
重定向的本质是对进程的指定描述符表进行内核级别的文件对象地址作拷贝的工作。
2、重定向的接口dup2 ①输入重定向
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include string.h
#include unistd.h#define filename log.txtint main()
{//close(1);int fd open(filename, O_CREAT|O_WRONLY|O_TRUNC, 0666);if(fd 0){perror(open);return 1;}//printf(fd: %d\n, fd); //重定向dup2(fd, 1);close(fd);const char *msg hello Linux\n;int cnt 5;while(cnt){write(1, msg, strlen(msg));cnt--;}close(fd);return 0;
}②输出重定向
int main()
{int fd open(filename, O_RDONLY);if(fd 0){perror(open);return 1;}dup2(fd, 0);char inbuffer[1024];ssize_t s read(0, inbuffer, sizeof(inbuffer)-1);if(s0){inbuffer[s] \0;printf(echo %s\n, inbuffer);}close(fd);return 0;
}3、 内存管理和文件操作解耦进程历史打开的文件与进行的各种重定向关系都和未来进行程序替换无关程序替换并不影响文件访问
4、将stdout中内容写到stderr
[roothecs-210801 lesson21]# 21六、如何理解“一切皆文件” 1、所有操作计算机的动作都是以进程的形式进行操作的所有访问文件的操作最终都是由进程来访问文件的。
2、文件都可以以文件的形式被操作系统用open打开每一个文件都要在内核中创建一个结构体对象struct file还创建了一个方法级对象struct operation_func分为基类与派生类通过struct file中的指针指向struct operation_func其中的函数指针指向底层方法。
3、上层统一使用一个read接口直接通过一个数据结构调用读方法写方法它可以根据指针指向的不同动态的根据不同的设备使用不同的方法。多态
4、以上的设计称为VFS虚拟文件系统
七、重定向缓冲区
1、观察下列两组代码
①C接口
#include stdio.h
#include string.h
#include unistd.hint main()
{//C接口const char *fstr hello fwrite;printf(hello printf); // stout -1fprintf(stdout, hello fprintf);// stout - 1fwrite (fstr, strlen(fstr), 1, stdout);// fread, stout-1close(1);return 0;
}输出结果不打印任何内容
[roothecs-210801 lesson24]# make
gcc -o myfile myfile.c -stdc99
[roothecs-210801 lesson24]# ./myfile②系统调用接口
#include stdio.h
#include string.h
#include unistd.hint main()
{//操作系统提供的systemcallconst char *str hello write;write(1, str, strlen(str));close(1);return 0;
}输出结果打印
[roothecs-210801 lesson24]# make
gcc -o myfile myfile.c -stdc99
[roothecs-210801 lesson24]# ./myfile
hello write[roothecs-210801 lesson24]# 2、为什么会出现这种现象呢 系统调用接口能直接通过进程找到数据对象将数据写到了系统内部缓冲区中close自动将数据刷新打印。而C语言缓冲区是用户级缓冲区不是系统级别的缓冲区一定不在操作系统内部只有在合适的时候c库才会调用对应的write接口将数据写入到操作系统里面。语言都属于用户层不属于操作系统。
而调用close把1号文件描述符关闭进程退出时无法刷新C语言缓冲区所以C程序没有对应的显示结果。而系统接口通过操作系统直接写到系统级缓冲区内部直接交给操纵系统的数据自然而然就能被刷新出来啦目前我们认为只要将数据刷新到了内核数据就可以到硬件了。一般情况下在进程退出的时候也会刷新完成区。
当然使用\n能够立刻清空缓冲区显示器的文件的刷新方案是行刷新所以在printf执行遇到\n的时候将数据进行刷新。刷新的本质就是将数据通过1号文件描述符和write写入到内核中。 这不由得让我们想起了之前所讲的exit和_exit感兴趣的同学请自行阅读上一篇文章——进程控制哦。
3、应用层缓冲区刷新策略 ①无缓冲--直接刷新。 ②行缓冲--不刷新直到碰到\n。 ③全缓冲--缓冲区满了才刷新普通文件写入重定向若将显示器打印变成向文件打印缓冲方案变成了全缓冲遇到\n不再刷新。
4、为什么要有这个缓冲区 ①解决效率问题-用户的效率问题。 ②配合格式化。
5、fork创建子进程时 操作系统在对这个缓冲区做写入的时候发生写时拷贝父子进程各自私有一份最后被系统向文件刷新了两份同样的数据。 ①重定向刷新方案发生更改 ②数据在写入时在缓冲区由刷新改为暂存因为缓冲区没有被写满。 ③fork后父子退出时均要刷新缓冲区且共享该缓冲区各自刷新一次。
八、模拟实现
Mystdio.h
//Mystdio.h//#pragma once
#ifndef __MYSTDIO_H__
#define __MYSTDIO_H__#include string.h#define SIZE 1024#define FLUSH_NOW 1 //立即刷新
#define FLUSH_LINE 2 //行刷新
#define FLUSH_ALL 4 //全刷新typedef struct IO_FILE{int fileno;int flag;char inbuffer[SIZE];//输入缓冲区char outbuffer[SIZE];//输出缓冲区int in_pos;int out_pos;
}_FILE;_FILE *_fopen(const char*filename, const char* flag);
int _fwrite(_FILE *fp, const char *s, int len);
void _fclose(_FILE *fp);#endifMystdio.c
//Mystdio.c#include Mystdio.h#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include stdlib.h
#include unistd.h
#include assert.h#define FILE_MODE 0666//w,a,r
_FILE *_fopen(const char*filename, const char* flag)
{assert(filename);assert(flag);int f 0;int fd -1;if(strcmp(flag,w) 0){f (O_CREAT|O_WRONLY|O_TRUNC);fd open(filename, f, FILE_MODE);}else if(strcmp(flag,a) 0){f (O_CREAT|O_WRONLY|O_APPEND);fd open(filename, f, FILE_MODE);}else if(strcmp(flag,w) 0){f O_RDONLY;fd open(filename, f);}else{return NULL;}if(fd -1) return NULL;_FILE* fp (_FILE*)malloc(sizeof(_FILE));if(fp NULL) return NULL;fp-fileno fd;fp-flag FLUSH_LINE;fp-out_pos 0;return fp;}int _fwrite(_FILE *fp, const char *s, int len)
{memcpy(fp-outbuffer[fp-out_pos], s, len);//没有做异常处理,也不考虑局部问题。fp-out_pos len;if(fp-flag FLUSH_NOW){write(fp-fileno, fp-outbuffer, fp-out_pos);fp-out_pos 0;}else if(fp-flag FLUSH_LINE){if(fp-outbuffer[fp-out_pos-1] \n){write(fp-fileno, fp-outbuffer, fp-out_pos);fp-out_pos 0;}}else if(fp-flag FLUSH_ALL){if(fp-out_pos SIZE){write(fp-fileno, fp-outbuffer, fp-out_pos);fp-out_pos 0;}}return len;
}void _fflush(_FILE *fp)
{if(fp-out_pos 0){write(fp-fileno, fp-outbuffer, fp-out_pos);fp-out_pos 0;}
}void _fclose(_FILE *fp)
{if(fp NULL) return;_fflush(fp);close(fp-fileno);free(fp);
}
main.c
//main.c
#include Mystdio.h
#include unistd.h
#define myfile test.txtint main()
{_FILE * fp _fopen(myfile, w);if(fp NULL) return 1;const char *msg hello world\n;int cnt 10;while(cnt){_fwrite(fp, msg, strlen(msg));//fflush(fp);sleep(5);cnt--;}_fclose(fp);return 0;
}