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

北京网站建设公司华网天下wap网站推荐

北京网站建设公司华网天下,wap网站推荐,商业网站开发文档,网上国网app推广Linux 文件 IO 管理#xff08;第一讲#xff09; 回顾 C 语言文件操作#xff0c;提炼理解新创建的文件为什么被放在可执行文件的同级目录下#xff1f;上述 log.txt 何时被创建#xff1f;又是谁在打开它#xff1f;那文件没有被打开的时候在哪里#xff1f;一个进程可… Linux 文件 IO 管理第一讲 回顾 C 语言文件操作提炼理解新创建的文件为什么被放在可执行文件的同级目录下上述 log.txt 何时被创建又是谁在打开它那文件没有被打开的时候在哪里一个进程可以打开多个文件吗如何管理 认识文件理解文件用户如何通过进程操作文件呢使用系统调用对文件操作open权限掩码flags 参数标记位传参 closewrite 几个问题open 的返回值文件描述符文件描述符分配规则 理解读写读写结论 open 的作用 理解 Linux 系统一切皆文件文件描述符 fd 和 C 语言库函数里的 FILE* 回顾 C 语言文件操作提炼理解 下面的 C 语言代码一定很熟悉才对 #include stdio.h int main() {FILE* fp fopen(log.txt, w);if (fp NULL){perror(fopen);return 1;}fclose(fp);return 0; }代码意思很简单就是以 w 的方式 打开 一个名为 log.txt 的文件 失败则返回失败原因并退出perror 可以看成底层封装类似 strerror 的函数根据错误码给你描述错误原因成功 就有情况了 如果存在 log.txt 文件就会 清空 log.txt 里的内容从文件开头写入可以直接理解 输出重定向 的工作机制如果不存在 log.txt 文件就会 在可执行文件的同级目录下创建 log.txt 文件 新创建的文件为什么被放在可执行文件的同级目录下 这个问题也可以先揪住最核心的矛盾系统怎么知道可执行文件的路径呢 前面进程的内容就说了代码跑起来变成进程后进程会维护自己的工作目录那么创建文件总不能胡乱位置创建和自己的可执行文件放在一起当然是更好的选择 所以这个路径有个更普适的名字叫做 当前路径 上述 log.txt 何时被创建又是谁在打开它 是在你 gcc 编译的时候吗并不是吧 而是你 运行被编译后的可执行文件的时候 也就是说我们要进行文件操作前提是要把我们的程序跑起来变成进程就像文件的打开和关闭是 CPU 执行到这部分代码的原因啊 所以 打开文件本质上是进程打开文件 那文件没有被打开的时候在哪里 再理解一下问题 如果现在 已经存在 log.txt 文件再次运行上述代码 log.txt 文件 在没有被打开的时候 在哪里 磁盘硬盘 对吧总不能直接加载到空间紧张的内存里吧 一个进程可以打开多个文件吗 可以一定是可以的 那 OS 里有那么多进程所以在大多数情况下OS 内部一定存在大量的被打开的文件 打开这么多文件 OS 不会混乱吗还真是不会毕竟 OS 是管理者会对文件进行管理 如何管理 先描述再组织 一定是会存在和类似进程 PCB 一样的结构体来描述文件属性的通过此结构体OS 可以有效管理计算机内的文件属性通过管理文件的数据 而既然是 进程打开文件那么最后一定是演化为两个结构体之间的指针关系 认识文件 你真的认识文件吗如果是那文件是什么呢 文件里会存放数据毋庸置疑那如果现在新创建一个文件里面毛都没有请问这个新文件占据磁盘空间吗占据吧虽然里面没有内容但是这个文件的文件名创建时间大小权限类型路径最近修改时间等等都要知道吧 不说别的你把数据存放在文件里是为什么是为了有朝一日能更好的找到它并且顺利的把数据拿出来吧既如此文件的相关属性就必不可少 所以文件 文件属性 文件内容那么 描述文件的结构体 大差不差是 存放管理文件的相关属性 的 理解文件 不要受程序语言影响我们 在意的一直都是进程和文件的关系语言编译运行后不就是进程吗 所以操作文件的本质是进程在操作文件 用户如何通过进程操作文件呢 文件是在磁盘硬盘上的磁盘硬盘是外设啊是硬件资源所以你 向文件中写入本质上就是向外设中写入 你是用户啊你有资格 直接 写入吗不好意思没有 因为 OS 才是软硬件的管理者要想写入文件也就是外设你绕不开 OS 那怎么办 OS 就必须为我们提供系统调用而我们在任何一门语言里使用的文件接口函数一定是对 系统调用 封装的结果 所以除了 库函数我们还可以直接使用 系统调用 来操作文件 使用系统调用对文件操作 open 这是 打开文件 的系统调用 使用 man 2 open 命令查看该 系统调用 原型 int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode);需要包以下头文件 #include sys/types.h #include sys/stat.h #include fcntl.h仔细阅读以下代码 #include stdio.h #include sys/types.h #include sys/stat.h #include fcntl.hint main() {// system callumask(0);int fd open(log.txt, O_WRONLY | O_CREAT, 0666);if (fd 0){perror(open);return 1;}return 0; }此时如果当前路径没有此 log.txt 文件那将会被创建 上面的代码创建出来的 log.txt 文件权限是 -rw-rw-rw- 即所有角色都具有读写权限 现在大可以把 open 的第三个参数去掉用第一个原型 open 会发现编译运行后的 log.txt 文件权限是乱码哦为什么使用 系统调用 就是让 OS 帮你做事情OS 如何得知你要创建的文件应该是什么权限呢所以 第三个参数就是要指明被创建的文件的权限那 umask(0); 是什么意思如果不带这一行代码会发现编译运行后的 log.txt 文件权限是 -rw-rw-r-- 就是 权限掩码 造成的 而 open 第二个参数作用就是指明打开文件的操作O_WRONLY 是只写O_CREAT 是创建如果没有此文件就创建 那为什么会存在两个 open 呢因为第一个两个参数的 open 是操作已存在的文件呐所以压根不需要传什么看不懂的权限 权限掩码 umask(0); 这一步是在程序编译为可执行文件运行之后动态的 调整 当前进程 对应的创建文件时的掩码可是系统中人家自己就有 权限掩码 umask 命令就能查询那上述代码里是要创建权限为 666 的文件进程会使用哪个掩码呢 一般采用 就近原则有自己设置的就用自己设置的没有就用系统的 flags 参数标记位传参 这似乎是个 int 整型对吧那为何是上述代码样式的传参呢 O_WRONLY | O_CREAT我们知道一个 int 整型是 32 位 bit 所以一定可以使用比特位进行标志位的传递这是 OS 设计很多系统调用的常见方法 通俗点讲这个 flags 看起来是个整数但咱是按比特位来使用所以在本质上flags 是一张 位图 而类似 O_WRONLY 这样的参数全部都是大写的按照编程习惯很容易就能想到这是一个 宏 我们直接设计一个 传递 位图标记位 的函数来帮助理解仔细观察下面代码 #include stdio.h#define ONE 1 // 1 0000 0001 #define TWO (1 1) // 2 0000 0010 #define THREE (1 2) // 4 0000 0100 #define FOUR (1 3) // 8 0000 1000void Print(int flags) {if (flags ONE)printf(one\n);if (flags TWO)printf(two\n);if (flags THREE)printf(three\n);if (flags FOUR)printf(four\n); }int main() {Print(ONE);printf(\n);Print(TWO);printf(\n);Print(ONE | TWO);printf(\n);Print(ONE | TWO | THREE);printf(\n);Print(ONE | FOUR);printf(\n);Print(ONE | TWO | THREE | FOUR);printf(\n);return 0; }很显然这和上面 open 里的用法一模一样 使用标记位组合的方式一次向指定函数传递多种标记位 日后可以将 one two three 的打印变成函数调用轻松实现相关功能 这就是 标记位传参 所以 O_WRONLY 和 O_CREAT 等等之类的选项就可以直接理解了是只有一个比特位为 1 的宏彼此之间宏值不重复而此时我们只需要理解更多的像这样的选项表示的含义即可 见名知意 close 这是 关闭文件 的系统调用 还是 man 2 命令查看 man 2 close查看该 系统调用 原型 int close(int fd);需要包以下头文件 #include unistd.h参数就是 open 返回值呢 ^ ^ write 这是 向文件写入 的系统调用将指定缓冲区写入指定文件需指明缓冲区的大小 使用 man 2 write 命令查看该 系统调用 原型 ssize_t write(int fd, const void *buf, size_t count);需要包以下头文件 #include unistd.h此系统调用的原型一看便知其意应该无需解释下面演示使用 #include stdio.h #include sys/types.h #include sys/stat.h #include string.h #include fcntl.h #include unistd.hint main() {// system callumask(0);int fd open(log.txt, O_WRONLY | O_CREAT, 0666);if (fd 0){perror(open);return 1;}const char* message Hello Linux File!!!\n;write(fd, message, strlen(message));close(fd);return 0; }这里需要注意我们使用的是 strlen 而不是 sizeof 因为字符串后面要有 \0 是你 C 语言的规定关我文件什么事而 \0 也不是我们要的内容所以不能是 sizeof 编译通过删除 原 log.txt 文件运行可执行文件会有新 log.txt 生成可使用 cat log.txt 命令查看文件内容 这很简单无需解释 现在我们将 message 内容改为 aaaa 此时重新编译不删除 原 log.txt 文件直接运行可执行文件会是什么结果 运行 cat log.txt 命令如下结果 aaaao Linux File!!!诶 竟然是从头开始写入但老内容没有被清空而是覆盖写入所以 O_WRONLY | O_CREAT 虽然表示 文件不存在即创建存在即打开写入但 默认不清空文件 那我就是要 清空文件 呢带上 O_TRUNC 选项意思为 截断即可 int fd open(log.txt, O_WRONLY | O_CREAT | O_TRUNC, 0666);那么现在的意思是以写的方式打开文件文件不存在就创建存在就打开并清空原内容 好样的就是本文开头使用 C 语言库函数 fopen 的 w 打开方式文件的含义 那如果现在我想要追加写入怎么办就是 C 语言库函数 fopen 的 a 打开文件方式的含义 还是核心要义现在只需找到和意义对应的选项即可就是 O_APPEND 选项如下使用即可 int fd open(log.txt, O_WRONLY | O_CREAT | O_APPEND, 0666);几个问题 open 的返回值 成功了就会创建一个新的文件描述符用来返回失败了就返回 -1 那这个 int 类型的 fd 究竟是什么呢 如果你打印 fd 的值就会发现最小的值是从 3 开始的那 012 去哪了没有并不是不用而是被其他文件占据了 其实是以下含义 0标准输入 -- 常规对应 键盘1标准输出 -- 常规对应 显示器2标准错误 -- 常规对应 显示器 当然啦在 C 语言中也会 默认给我们打开 相应的这三个 文件流 stdin 标准输入流stdout 标准输出流stderr 标准错误流 下图 把这几个文件流是当作文件来看 的 因为从类型可以看出和 C 语言库函数 fopen 函数的返回值是一样的 所以printf 可以做的操作fprintf 也可以做就是需要 指明是什么流 罢了 既然上面 fd 值为 123 的文件是 默认打开 的那也就是说我可以直接向 标准输出文件 写入内容看看会不会在 显示器 上打印就是了编译运行如下代码即可 int main() { const char* message Hello System Call!!!\n;write(1, message, strlen(message));return 0; }验证过的都知道一定是可以的 文件描述符 上面的例子也看到了调用 write 系统调用 向特定文件里写入居然是使用 fd 这样的整型数字这到底是什么意思 现在我们站在 OS 的角度宏观调查 文件管理 的方式 前面提到 能操作文件的是进程但一个进程可以打开很多文件一台机器里运行那么多进程在磁盘中可能有上万文件光是被打开就可能有上百文件OS 要知道哪些文件被打开了哪些文件正在被读哪些文件正在被写哪些文件正准备要被关闭等等所以 OS 得具备准确管理大量文件的能力 OS 怎么对 被打开的文件 做管理呢 先描述再组织所以 OS 一定会对每一个被打开的文件创建 内核数据结构名为 struct file 里面一定包含可以完整描述文件的属性权限以什么方式被打开标记位…… 那么在 OS 内核里有多少文件就有多少 struct file 所以在 OS 内核里一定会有大量 struct file 怎么被组织起来呢高效的数据结构Linux 是双链表那么 OS 内部就会使用 双链表组织 这些 struct file 那么 OS 对文件的管理就转变为了对链表的增删查改! 而 struct file 内部一定会有一个指针指向 文件内核级的缓存其实就是 OS 给我们申请的一块内存而 文件 属性 内容 那么将来 OS 会使用磁盘文件的属性初始化 struct file 的内容而内容会直接 load 进这片 文件内核级的缓存 未来想读直接从这片缓存里读想修改写入会先在这片缓存里修改写入再由 OS 刷新进入磁盘里 注意每一个被打开的文件都有自己的 struct file 而每一个 struct file 都有自己的 文件内核级的缓存 但现在有这么多 struct file 怎么知道它们都是被哪个进程打开的呢所以一定要有方法可以表征进程和它打开的文件的关系因为 OS 把文件是管理在一起的那么在进程 PCB 里存在属性结构体 struct files_struct* files 而在其内部会包含一个 数组 数组的原型为 struct file* fd_array[N] 只要是数组就必定有自己的 下标 而这个数组是指针为 struct file* 的 指针数组想要进程和文件产生关系只需要将文件 struct file 的地址依次填入特定的下标中现在每一个 struct file 结构体也都对应一个下标 所以现在一个 进程要想操控文件只需要把此文件的 struct file 对应在 struct file* fd_array[N] 数组中的 下标 返回给上层上层拿着这个 int 下标也就是 fd 就可以访问文件了 所以 fd 的本质就是内核进程和文件映射关系的数组下标 而 fd 的名字就叫做文件描述符 文件描述符分配规则 在 files_struct 数组当中找到当前没有被使用的最小的一个下标作为新的文件描述符 理解读写 读 如果现在一个进程要想读文件 log.txt 肯定会先打开文件 log.txt 接着利用磁盘内有关属性初始化对应的 struct file 然后将其内容拷贝进 文件内核级的缓存 然后会把 文件内核级的缓存 里的数据拷贝进上层应用层 如果进程要读的数据不在这个该缓冲区里OS 就会将你的进程阻塞住或者挂起然后从磁盘里把想要的数据拷贝进缓冲区完事之后再唤醒该进程再做拷贝拷贝数据时进程是被链接到磁盘的等待队列里的详情见往期本人拙作 写 如果文件缓冲区没有内容倒也好说直接将数据拷贝进缓冲区里然后再由 OS 刷新到磁盘里 那缓冲区要是有数据呢是修改数据呢一样的是先把数据读进缓冲区里修改然后再由 OS 定期刷新即可 所以 对文件的修改是内存级的修改是读进来之后把文件改完再刷出去的哪怕只是修改一个字节一个比特 结论 无论读写都必须在合适的时候让 OS 把文件的内容读到文件缓冲区中 所以无论对于 write 还是 read 本质上都是拷贝作用 open 的作用 创建 文件结构体 file开辟 文件缓冲区 的空间加载文件数据可能会延后查进程的 文件描述符表将 文件结构体 file 地址填入对应表的 下标 中返回 下标 理解 Linux 系统一切皆文件 现在可以理解 OS 内核数据结构里的文件可以是一台机器不止 OS 还有硬件呢 都知道可以从键盘读数据往显示器打印刷新数据那请问如何做到呢这可是硬件而读写数据肯定都是从文件角度出发的不然要重新搞一套机制实现硬件的读写吗那怎么可能所以文件和这些硬件有什么关系呢 所有像磁盘键盘鼠标什么的外设在冯诺依曼体系结构里被称为 IO 设备而每一种设备我们关心的就俩属性和操作方法 同样的OS 要对每一种设备做管理同样的六字真言 先描述再组织 所以每一种设备都存在可以完美描述它的结构体 struct device 里面拥有必不可少的属性名字类型状态厂商等等 虽然属性不一样但他们的类别是一样的都是用结构体 struct device 描述的可是 刚刚这些属性并不重要重要的是方法 每一个设备需不需要方法需要不然你怎么操作它以读和写操作为例键盘需要读写方法吗键盘似乎是用来读的好像没有往键盘写的需求 但依然需要写的函数只是会将它置为空 这个理解太重要了没有往键盘写的需求为什么要设计一个这样的空函数呢不急我们继续往下说 除了读写键盘肯定还会有其他这样的操作函数而其他设备也一样会有和键盘一样的读写函数以及其他操作函数但经验告诉我每一个设备的操作方法函数实现一定是不一样的不然为啥是不同的设备呢而这一层操作往往是设计硬件的工程师完成的叫做驱动 驱动控制我们的设备去操作例如读写之类的这也可以理解下面 重点来了 OS 依然会对每一个被打开的硬件设备做管理而对每一个设备OS 都会构建 struct file 虽然里面会包含相关文件的属性但也一定会包含可以调用底层驱动的 函数指针因为不同硬件相同功能的驱动接口设计一定是大差不差的在 struct file 里存放对应驱动的 函数指针 如此一来每一个硬件对应的 struct file 都会有全部操作的 函数指针因为 OS 毕竟不知道每一个硬件的特性所以类似没有往键盘写的需求情况下依然要给出写的驱动就是这个原因 所以 struct file 在底层还要有自己的 方法指针表里面都是 函数指针 如果现在要从键盘读数据OS 就可以找到键盘的 struct file 调用其内部的关于 read 的 函数指针紧接着通过 函数指针 找到键盘关于 read 的驱动就可 完美实现键盘和文件的关联 其他硬件也是如此现在站在 OS 的角度不需要关心底层硬件的差异因为我们读写硬件外设的方法都是调用对应的 函数指针 想读谁直接调用谁的 struct file 里关于读的 函数指针至于如何实现读那是驱动的事和我 OS 没有半毛钱关系 以上技术叫什么呀如果是使用 C 的类来封装这个 struct file 那这一定会使用 多态 技术 所以在上层看到的视角就是 一切皆文件 而 OS 操作系统这一层又叫做 vfs virtual file system虚拟文件系统 那么现在就有很多事情都能说的通了比如键盘输入为什么有缓冲区这是因为你的键盘对于 OS 而言是个文件是文件自然有文件缓冲区啊所以 OS 从键盘文件的缓冲区读数据键盘也会将数据送进自己对应的文件缓冲区而键盘也就是对应 标准输入流stdin文件描述符 为 0 的 struct file 就这么简单 文件描述符 fd 和 C 语言库函数里的 FILE* 通过上面的理解OS 只认 文件描述符 fd 来操作文件那 C 语言库函数 里的 FILE* 是如何操控文件的呢 那第一个问题就是 FILE 是什么这是 C 语言提供的 结构体类型 但是由于 OS 只认 文件描述符 fd 来操作文件所以直接大胆猜想文件结构体 FILE 底层一定封装了 文件描述符 fd 而在 FILE 里是一个名为 _fileno 的成员代码证明 int main() {FILE* fp fopen(log.txt, w);if (NULL fp){perror(fopen);return 1;}// 文件描述符 fd 打印printf(fp: %d\n, fp-_fileno);fwrite(Hello, 5, 1, fp);fclose(fp);return 0; }咱们之前不是说 stdin stdout stderror 不是对应文件描述符 012 吗验证 printf(stdin - fd: %d\n, stdin-_fileno); printf(stdout - fd: %d\n, stdout-_fileno); printf(stderr - fd: %d\n, stderr-_fileno);结论所有 C 语言上的文件操作函数底层本质都是对 系统调用 的封装
http://www.sczhlp.com/news/262079/

相关文章:

  • 徐州智能建站怎么做网站优化对企业有什么好处
  • 网站统计系统网站窗口代码
  • 安溪网站建设公司黄冈论坛遗爱网
  • 北京做网站的公司哪家好河南省建设厅官方网站郭风春
  • 上海网站推广广告山西省三基建设办公室网站
  • 如何制作网络游戏优化wordpress调用文章函数
  • 移动电子商务网站建设重庆企业100强排名
  • 成都私人网站建设wordpress 导航 class
  • 舞阳专业做网站厦门建网站多少钱
  • 社区网站开发进度表百度扫一扫
  • 免费的个人网站平台网站 展示板
  • 晋城手机网站建设杭州网络推广专员
  • 国家电网网站制作抖音代运营合同范标准版
  • 网站制作培训费用网站建设公司销售技巧
  • 中英文网站是怎么做的网站建设4038gzs
  • 打开一个网站在建设中小红书关键词排名优化
  • AI元人文:大语言模型与价值权衡的共生之道
  • 德阳市建设局官方网站安全月茶叶网站flash模板
  • 网站页面关键字在哪里没有安装 wordpress
  • 国内最好用免费建站系统宁波网站免费建设服务平台
  • 网站开发环境和运行环境seo的基本步骤
  • 邯郸专业做网站地方人力资源公司加盟合作
  • 网站开发长沙凡客诚品购物
  • 上海网站制作多少钱wordpress 内容表
  • 无锡网站推广哪家公司好深圳北站设计方案
  • 高明网站设计多少钱wordpress 自己做主题
  • 仲恺企业网站建设优化大师win10能用吗
  • iis网站下载陕建云采电子商务平台
  • 辽宁网站建设多少钱网页设计论文题目什么样的好写
  • 学做美食交流网站做一个app大概需要多少费用