设计需要看的网站有哪些,郑州企业网站优化,电子书网站 自己做,Vantage wordpress主题前言 
首先#xff0c;关于文件我们最先要理解的是#xff0c;文件不仅仅存储的是数据#xff0c;一个文件包括 内容  数据。内容好理解#xff0c;就是我们先要这文件存储哪一些数据#xff0c;这些数据就是文件的内容。 
但是#xff0c;在计算机当中#xff0c;有两种…前言 
首先关于文件我们最先要理解的是文件不仅仅存储的是数据一个文件包括 内容  数据。内容好理解就是我们先要这文件存储哪一些数据这些数据就是文件的内容。 
但是在计算机当中有两种文件一种是 正在打开的文件 另一种是 没有被打开文件。 
文件没有被打开那么就是存储在外设当中最常见的存储数据的外设比如 磁盘u盘硬盘等等那么我们知道外设当中存储速度是比不上内存的但是外设有一个好处就是存储容量大。 
那么也就意味着在外设当中存储了很多个文件 我们如何 对这些文件进行 增删查改 其实靠的就是文件的属性文件的属性有很多比如文件的最近一次的修改时间文件的创建的时间文件的大小文件的存储位置等等···  这些在windows 当中右键某一个文件我们也可以查看文件的属性 而对于未被打开的文件文件的属性就是为了方便我们来对文件 进行 增删查改 。也就是说如何在“浩瀚”的外设当中找到文件进行 增删查改文件的属性不能少。 
就好比是在快递站当中找快递这些快递不是随机的混乱的堆放在某处然后让我们自己的一个一个找的而是把这个快递 编写一个快递号比如是 12 - 3 - 4444那么我就在 12 号货架的 第 3 层的位置来找 4444 的快递就行了这样不仅方便了 快递站的人来存放也方便了 找快递的人来拿。 不会显得很乱。 文件要被打开也就是要被修改那么肯定是要加载到内存当中 而且一个进程可能不只是打开一个文件一般大多数情况下 一个进程打开的文件数 是 1 : n 的关系。因为存储进程的代码 数据本身就是一个可执行文件当中存储的同时进程很可能还会打开其他的多个文件。 
所以不仅仅是外设当中有很多的文件在内存当中操作系统也要维护很多的 文件。 
所以这就要谈一个六字真言了 -- 先描述在组织。 
其实在操作系统 在 内存当中管理这么多个进程和 文件本质上就是管理这些进程 和 文件 的属性。操作系统 像 Linux 使用 C 语言实现的所以其实本质上就是用 一个个 struct 结构体来表述 一个进程 / 文件 的属性也就是属性的集合这叫做描述然后操作系统只要管理好了这些个 strutc 结构体就可以管理好 所以的进程 / 文件这叫做组织。而管理组织这些 结构体对象本质上就是用 某种 数据结构来维护某种关系。 
比如 进程 等待 硬件资源但是这个硬件当前可能是多个进程在等待资源那么就有先后顺序就要等待。所以在Linux 当中基本上每一个 硬件在操作系统当中都有一个等待队列用于进程之间在这个硬件上的排队。 
所以我们才说在Linux 当中一切都是文件为什么就因为上述我们所说的 -- 先描述在组织。 所以文件也是一样操作系统在内存当中维护这些个资源本质上就是维护 操作系统为 这个加载到内存当中文件 所 创建的 文件结构体对象。 
你在很多的编程语言当中看到的 为什么 像类似 fopen这样的函数返回的往往是一个 文件对象的指针其实就离不开 操作系统当中为了更好管理文件为这个文件所单独创建的 文件结构体对象。两者实际上是大同小异的。 
文件 
在C 语言当中的 各个文件接口函数 
首先当然是 fopen函数打开一个文件   可知道 fopen函数返回的是一个 FILE*文件指针 / 文件句柄 。 
path  打开文件的路径相对路径绝对路径或者不带路径mode打开文件的方式 
后续我们对这个文件的操作都需要 fopen函数的返回值也就是这个 FILE* 文件指针。 如果你以 w 写的方式打开一个文件的话如果文件在你传入的路径之下不存在那么就会自动创建一个文件而且w 方式写入数据到文件的话如果文件当中有数据那么就先把文件的长度清零然后在文件的最开始处写入。这种方式 和 Linux 命令行当中的  输出重定向 向文件当中的写入方式是一样的。如 echo hello Linux!  text.txt  这个文件当中输入 hello Linux字符串就是以 w 的方式来写入的。所以像  text.txt  这样的方式就可以 清空 text.txt 这个文件当中的数据以 a 是在文件末尾处以 追加的方式来写。 同样  如果文件在你传入的路径之下不存在那么就会自动创建一个文件这种方式 和 Linux 命令行当中的  输出重定向 向文件当中的写入方式是一样的。 就是追加的方式来在文件当中输入数据的对于 path 路径如果你只是直接写上文件名没有带上路径那么就会默认是在当前 路径下查找这个文件。此处的 当前路径指的是 调用 fopen函数的 进程的当前路径。 
在 /proc/进程PID 当中就有这个进程对应的 当前工作路径 所以如果把这个进程的  cwd也就是这个进程的当前工作路径给改掉了那么像 fopen(text.txt,w); 这样的操作就会在新修改的 工作路径的下创建这个新文件。 c 程序在运行之时会自动打开 三个 标准输入输出流 像 fprintf这些函数可以像写入数据到文件或者是读取数据到程序当中的一样来像上述的 三种 标准输入输出流当中输入和输出数据 上述只是简单的把 C 语言当中的一下文件操作浅谈一下因为不是本篇博客的主要内容所以不在过多阐述。 被打开的文件被加载到内存当中的文件 我们说文件是存储在 磁盘上的而磁盘是外部设备访问文件的操作其实是在访问硬件所以其实 三个标准输入输出流也是在访问文件。 又因为像在 C 语言当中使用 printf/fprintf/fscanf/fwrite/fread/fgets/gets ···· 这些函数想访问文件的话其实就是想访问对应的硬件。这些函数使用用户所书写的用户不能直接绕过中间的层级直接访问到最底层的 硬件 如上图所示用户和底层硬件之间 还有 很多层级。只能一层一层去访问到 底层硬件。 
所以这些访问到硬件的函数本质上是 用户操作接口然后在这些函数的实现当中就需要使用到 系统调用接口。几乎所有的库只要是想要访问到底层硬件设备都必定是要封住 系统调用接口的。 
Linux 当中常见的 文件系统调用接口介绍 open函数 #include sys/types.h
#include sys/stat.h
#include fcntl.hint open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);pathname: 要打开或创建的目标文件flags: 打开文件时可以传入多个参数选项用下面的一个或者多个常量进行“或”运算构成flags。参数:O_RDONLY: 只读打开O_WRONLY: 只写打开O_RDWR : 读写打开上述这三个常量必须指定一个且只能指定一个O_CREAT : 若文件不存在则创建它。需要使用mode选项来指明新文件的访问权限O_APPEND: 追加写返回值成功新打开的文件描述符失败-1 
上述的 类似 O_RDONLY 的5个参数其实是宏上述的 flags 这个参数其实是作 标志的作用用户表示某一些状态是否存在比如 O_RDONLY 就表示 只读打开。 
以往我们使用一个变量作为一个标志位的时候一般是这一个变量只能作一个标志位也就只能表示一种状态。但是其实一个 int 变量有 32 个bit 位如果每一个位表示一个状态的话那么按道理这个 int 变量是可以表示 32 种状态是否存在的。 
上述的 flags 变量就是一个 比特位方式的标志位传递方式。 我们可以利用像上述一样的 二进制操作符 取出 int 变量 每一个 bit 位的值从而判断某一个 function 状态是否存在从而实现不同的功能。  这样的话我们在外部使用这个函数就可以在 show 函数当中传入这些宏而且顺序无关在 show 函数内部就会一次按位与上 每一个 宏从而解析出 当前传入了多少个宏状态。  O_WRONLY 参数  和 O_CREAT   参数 
而且O_WRONLY 这个参数如果路径当中没有 此文件的话不会创建的新的文件。除非又引入了 O_CREAT  参数。 此时在进程的当前目录下是没有 log.txt 这个文件的。发现文件打开失败了而且在进程的工作目录下没有 创建新的文件。 所以要想上述一样 传入O_CREAT    参数告诉操作系统如果打开文件之时没有该文件就创建一个新的文件。 
但是我们又发现了问题   使用上述  O_CREAT   参数来创建 文件的话发现权限完全是不对的。 
因为 在 Linux 当中新建一个文件的时候必须要告诉 操作系统要 创建的 新文件的 访问权限。 我们要指定 新创建文件的权限那么就要使用 带 mode 参数的 open函数 
向下述一样传参 此时发现新创建的 log.txt 文件就已经创建成功了而且 文件的 访问权限还是 664 权限。 
为什么不是 666 权限呢因为 Linux 创建文件时候有一个 umask 来控制 新创建的文件的权限。  如果必须要使用 666 权限的话有一个 umask系统调用接口   在进程的代码当中调用 umask 函数可以把当前进程的 umask 值修改为传入的参数的值。  调用 umask 函数之后在系统当中是系统的 umask在进程当中是进程自己的 umask。 
有两个 umask那么进程此时应该使用 哪一个 umask 呢使用 就近原则。此时在进程当中最近就有一个新刷新的 umask那么就使用 这个 umask如果没有就使用系统当中的。 open函数的返回值是一个 整形 这个返回值 称之为 -- file descriptor 换句话说 就是 文件描述符。代表的意思就是如果打开文件成功返回的是一个  0 的数此时这个数代表的就是一个 文件。 O_TRUNC参数  
O_WRONLY  参数不仅不会 帮我们创造新的文件而且它不是像 fopen函数当中的 w 一样先把文件当中的数据进行清空然后从文件的开始处进行书写它虽然依然是从文件的开始处进行书写但是 O_WRONLY 不会 把文件当中的数据进行清空 所以如果先要实现 fopen函数当中的 w 参数一样的效果的话需要加上 O_TRUNC 这个参数它所实现的功能就是 把文件当中原本的数据清空。 此时才能实现   fopen函数当中的 w 参数一样的效果。 O_APPEND 参数 这就是追加模式在文件末尾追加数据。 
很显然追加 参数 和 清空 参数两个参数是冲突的。 
此时我们进行追加的方式在文件当中输入数据   close函数 
关闭文件。 fildes 文件描述符也就是上诉 open函数当中打开文件成功返回的  file descriptor。 write函数 
像文件当中写入数据   文件系统调用接口 和 文件库函数的关系 我们之前说过库函数是用户层面使用的接口用户如果想要访问到 底层硬件的话必须要一层一层往下去调用才能调用到 底层硬件或者是下层应用数据或者说是 接口。 
操作系统不会容许 用户或者是上层的各种接口 跨层级 去访问到下层当中的数据硬件接口等等。 所以才会出现了上述所说的 文件系统调用接口 和 文件库函数的关系。库函数要想访问下层只能通过系统调用接口。 他一定是像下述类似的方式来在调用系统调用接口的 fd 文件描述符  操作系统也要维护 打开了的文件这些文件都是被加载到内存当中的来才能进行 增删查改的操作的。 
操作系统如何进行管理这么多的文件用的就是 --- 先描述在组织。 谁管理谁来描述这个被打开的文件操作系统要把这个 被打开的文件的属性放到一起也就是放到一个 struct 一个结构体当中每一个被打开的文件都会在内核当中创建一个 内核结构体。比如 我们把这个内核结构体 称职为 --- struct file。 
在这个 struct file 当中之间或者间接的包含如下属性 
在磁盘的什么位置基本属性权限大小读写位置当前要从那个位置开始读写开始位置的偏移量谁打开的·······文件的内核的缓冲区信息文件被打开时这个文件在内核当中对应的内核空间位置将来要想文件当中写数据会先写到这个缓冲区当中然后操作系统会定期从 缓冲区当中 向磁盘当中拷贝数据····· 
上述只是举了一些例子其实一个文件还有很多信息每一个被打开的文件这些信息都会保存到一个 struct file 当中操作系统可能要维护很多个 struct file 
操作系统会以 双链表的形式把这一个一个的 struct file 链接在一起。   现在我们对被打开的文件进行操作的话就变成了对 这个 文件结构体对象双联表  的 增删查改。 这个过程就是  先描述在组织 的过程和进程当中  先描述在组织  没什么区别。 在前言当中也进行了说明一个进程打开的文件数 是 1 : n 的关系。所以我们如何知道哪一个进程 打开了那些文件或者说是这个进程 和 那些文件有什么关系  进程 如何和 自己打开的文件做关联的    这些都是需要 进程 的 PCB对象当中需要去维护 的。 
在 进程的 PCB 当中会存在一个指针比如是 struct files_struct* f  这个 
源码当中是这样写的 这个指针指向一个 files_struct 结构体对象在这个 files_struct 结构体对象 当中有一个数组 -- struct file * fd_array[] 。 
struct file * fd_array[]  数组是一个指针数组数组当中每一个元素都指向一个文件对象而在这个数组当中的文件对象 所 对应的文件就是这个进程所 打开的文件。 如上如图所示这个 数组维护的就是一个一个 文件对象所以我们使用 open函数之时open函数的返回值就是一个 int 类型的返回值其实对应的就是 这个 struct file * fd_array[]  数组 下标。而这个 struct file * fd_array[]  数组 我们称之为 -- 文件描述符表。 
在这个 文件描述符表当中就存储了 这个进程所打开的 各个文件的 struct file 文件的对象的地址。 所以当我们调用open函数打开一个文件那么 就会帮这个文件创建一个 struct file 文件对象然后在 struct file * fd_array[]  数组当中找到一个没有被使用过的 成员位置把这个文件对象的 地址 存储到这个成员位置。  然后把这个 成员 的下标返回给用户。 所以之所以 被打开的文件被加载到内存当中文件为什么要单独用一个双链表来连接起来呢  因为 进程可能会中途退出但是文件可能还会被访问修改而且打开文件把文件内容加载到内存当中是操作系统做的事情不是进程做的事情进程只能向操作系统申请某一个文件的打开但是不能直接访问到这个文件。  所以关于文件的加载和操作系统所做的事情那么操作系统就要用 一个数据结构来关联 为各个文件创建的 文件对象。方便操作系统进行管理。  而 进程要想和 操作系统维护的 文件对象数据结构 的话就要用到上述所说的  files_struct 结构体对象。在这个 files_struct 结构体对象 当中有一个数组 -- struct file * fd_array[] 。这个数组就存储了这个进程当前所打开的文件的文件对象的地址。所以就用这个数组的下标的方式 和 操作系统维护的 文件对象数据结构 联系起来了。 stdinstdoutstderr 三个标准输入输出流的 文件描述符 在上图的程序当中fd1 - fd4 是分别打开的四个文件的 下标返回值。那么输出结果是什么呢 
输出   发现输出结果是 3 - 6 四个连续的下标但是fd1 不是我们打开的第一个文件吗为什么fd 是从 3 开始而不是 从 0 号开始存储呢 
是不是 前三个 0 - 2 下标的三个位置已经存储了其他的文件了呢 
是的。 
在上述我们说过运行一个C语言程序默认就会打开三个标准输入输出流 stdinstdoutstderr。而这  三个 标准输入输出流 其实本质上就是  文件。 而这 stdinstdoutstderr 三个 标准输入输出流的名字其实是 C语言当中规定的不是操作系统当中规定的。 
操作系统当中对于文件只认 fd文件描述符。 这三个对应的输入输出流的 stdinstdoutstderr  对应 fd 就是 0 1  2。 
所以对于 stdoutstderr   两个文件当中写入数据的话程序执行就会直接 在屏幕上输出我们在文件当中写入的内容   输出 同理我们还可以从 stdin 文件当中读取数据到 程序当中使用 read函数可以从文件当中读取数据出来   输出   在上两个例子当中我们什么文件都没有打开0,1,2三个 标准输出输入的文件是 默认打开的。 操作系统当中的  三个标准输入输出流 stdinstdoutstderr 三个标准输入输出流 是 C语言的特性吗 
肯定不是。 
各种语言肯定会已有自己的  三个标准输入输出流这  三个标准输入输出流 不是C语言特有的。 
所以其实不是 C语言的程序在运行之时 自动打开 这 三个标准输入输出流。 而是这三个 标准输出输入流 文件在操作系统启动之时就已经被打开了。所有的进程只是把操作系统在 文件对象双链表当中找到这三个文件把这个三个文件 链接到 自己的  文件描述符表 当中。 因为使用进程 和调试进程的 用户和程序员就是需要程序的输入输出来查看 进程运行结果。 在 C 当中的 FILE 结构体是 C库当中自己封装的一个结构体。因为操作系统当中只认 fd 文件描述符所以在 FILE 这个结构体当中一定封装得 有 fd 文件描述符。 而 C 当中的 stdinstdoutstderr 三个标准输入输出流  他们的类型就是 FILE 结构体所以在这 stdinstdoutstderr 三个标准输入输出流  当中一定有 对应的 保存 fd 文件描述符  的 成员属性。 所以我们现在就来验证一下这 stdinstdoutstderr 三个标准输入输出流 当中的  fd 文件描述符 程序输出   发现 stdinstdoutstderr 三个标准输入输出流 对应的 fd 就是 0  1  2。 同样的如果使用 close 接口之间关闭了 fd 为   某一个 文件0  1  2那么就相当于是这个文件没有在这个进程当中打开了。 输出 发现程序没有任何输出因为 printf函数就是在 stdout 当中输出所以这个 stdout 文件已经被关闭了就不能在输出了。 printf函数底层必定用了 1 号文件。 其实这里的 printf函数是写入成功了已经在 操作系统当中的 显示器文件 当中输入了但是当前进程已经把这个 显示器文件 从 数组当中剔除了不能再打印了。 
操作系统 文件对象双链表 中的 引用计数 操作系统维护的 文件对象双链表 当中的文件对象肯定是被很多的进程所指向的。一个文件对象可能要多很多个 进程所服务。 
比如 stdinstdoutstderr 三个标准输入输出流 这三个文件就是要本很多文件所使用的。 
那么操作系统如何知道一个文件被多少个 进程所使用呢 
答案就是使用 引用计数。 在 每一个文件对象当中都有一个 count 的字段这个字段就 记录了 当前有多少 个 文件描述符指向这个文件对象。 如果 count 字段不为了0说明当前虽然有进程 不再引用这个 文件对象了但是还有进程 在引用 这个文件对象那么就不能释放这个 文件对象空间。必须要等到 count 为0 才能释放这个文件对象空间。 所以一个进程 关闭文件 和 打开文件本质上其实就是 把 这个文件对象当中的 count 字段           -- 或者 。修改 进程自己的 文件描述符表当中 对应下标当中的指针比如 置空一个 或者 增加一个 下标位置的 指针。 
在 各种语言当中 的 文件流本质其实就是 用 结构体 或者 类 等等的方式把 fd 文件描述符 封装起来了其中一定是有  fd 文件描述符 的。