文件概念

  • 文件 = 文件内容 + 属性(也是数据)
  • 对文件的所有操作,无外乎是:a.对内容 b.对属性
  • 站在系统的角度,能够被input读取,或者能够output写出的设备就叫做文件
    • 狭义文件:普通的磁盘文件
    • 广义文件:显示器,键盘,网卡,声卡,显卡,磁盘,几乎所有的外设,都可以称之为文件

访问文件

  • 访问文件的本质:是进程在访问
  • 文件在磁盘(硬件)上放着,而向硬件写入,只有操作系统才有这个权利
  • 如果普通用户,也想写入那就必须让OS提供接口----文件类的系统调用接口

关于系统接口

  • 一旦使用系统接口,编写所谓的文件代码,就无法再其他平台上直接运行了,
  • 而像C++/Java这样的语言,它们就对文件系统接口进行了封装,包括所有访问文件的操作
  • C++对所有的平台的代码都实现一遍,通过条件编译,动态裁剪实现封装性

为什么要学习OS层的文件接口

  •  不同的语言,有不同的语言级别的文件访问接口(都是不一样的),但是底层都封装了系统接口
  • 系统接口只有一个,OS也只有一个,

关于printf向显示器打印

  • 显示器是硬件,printf向显示器打印,本质也是一种写入,和磁盘写入到文件中是没有本质区别!

C语言文件接口

当前路径

  • 当一个进程运行起来,每个进程都会记录自己当前所处的工作路径,这个路径就叫做工作路径

向文件里写入:fwrite fprintf fputs

  • 在fwrite写入时,传的strlen(s1),不需要加一
  • \0结尾是C语言的规定,文件不用遵守,文件要保存的是有效数据!

从文件中读取: fgets

  • 这里以只读的方式打开

直接使用系统接口

关于系统接口:open

  •  pathname表示文件名,
  • flags是一种标记,标记打开文件的状态
  • mode表示没有文件,创建文件时给的初始权限

flags标记

  • 系统中的 flags就是宏定义

使用系统接口: open write read close

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{umask(0);// 清空过滤权限//fopen("log.txt", "w");int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);// 写入 创建 清空const char* s = "hello write\n";//const char* s = "aa\n";write(fd, s, strlen(s));flose(fd);//fopen("log.txt", "a");int fd1 = open("log1.txt", O_WRONLY|O_CREAT|O_APPEND, 0666);// 写入 创建 追加printf("open success, fd: %d\n", fd1);int fd2 = open("log2.txt", O_WRONLY|O_CREAT|O_APPEND, 0666);// 写入 创建 追加printf("open success, fd: %d\n", fd2);int fd3 = open("log3.txt", O_WRONLY|O_CREAT|O_APPEND, 0666);// 写入 创建 追加printf("open success, fd: %d\n", fd3);int fd4 = open("log4.txt", O_WRONLY|O_CREAT|O_APPEND, 0666);// 写入 创建 追加printf("open success, fd: %d\n", fd4);close(fd1);close(fd2);close(fd3);close(fd4);//fopen("log.txt", "r");int fdd = open("log.txt", O_RDONLY); // 只读char buffer[64];memset(buffer, '\0', sizeof(buffer));read(fdd, buffer, sizeof(buffer));printf("%s", buffer);flose(fdd);return 0;
}

  • 我们在应用层看到一个很简单的动作,在系统接口层面甚至OS层面,可能要做非常多的动作!

系统接口的细节,引入fd(文件描述符)

这里借用一下上面程序的结果

  • 这里怎么是从3开始的呢,0 1 2 去那哪里了?
  • 一个文件打开默认会打开stdin stdout stderr(标准输入 标准输出 标准错误),它们也用0 1 2 表示

文件指针FILE*的认识

  • FILE是一个struct结构体(内部有很多成员),是由C标准库提供的
  • 而在C文件库函数内部一定要调用系统调用,站在系统角度,它只认识fd,所以可以推出FILE结构体里面必定封装了fd!!!

证明FILE内部封装了fd


fd的细节

fd的理解

  • 进程要访问文件,必须先打开文件
  • 文件要被访问,前提是加载到内存中,才能被直接访问
  • 一般而言 进程 : 打开的文件 = 1 : n,但是如果是多个进程都要打开自己的文件呢?
    • 系统中会存在大量的被打开的文件,OS面对如此的文件也要管理起来,通过先描述,再组织

  • 在内核中,OS内部会为了管理一个打开的文件,构建一个file结构体
  • 创建的struct file的对象,充当一个被打开的文件,如果用的很多,就用双链表组织起来,

  • 上面这张是进程和文件之间的关系
  • fd在内核中,本质是一个数组下标

文件的分类

  • 磁盘文件(没有被打开)

  • 内存文件(被进程在内存中打开)

对一个内存文件的操作流程

  •  fopen -> open -> fd -> FILE -> FILE *
  • fwrite() -> FILE* -> fd -> write -> write(fd,...) -> 自己执行操作系统内部的write方法 -> 能找到进程的task_struct -> *fs -> files_struct -> fd_array[] -> fd_array[fd] -> struct file -> 内存文件被找到了! -> 操作

对输出重定向的理解

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{close(1);// 这里的fd的分配规则是: 最小的,没有被占用的文件描述符// 0,1,2 -> close(1) -> fd -> 1int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);if (fd < 0){perror("open");return 1;}printf("fd: %d\n", fd); // stdout->FILE{fileno=1}->log.txtprintf("fd: %d\n", fd);fprintf(stdout, "hello fprintf\n");const char* s = "hello fwrite\n";fwrite(s, strlen(s), 1, stdout);fflush(stdout); //stdout->_fileno == 1;close(fd); //后面解释return 0;
}

  • 不管是输出重定向还是输入重定向,都是在OS内部,更改fd对应的内容指向

dup2:更改fd

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char* argv[])
{if (argc != 2) {return 2;}int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);//int fd = open("log.txt", O_WRONLY | O_CREAT | O_APPEND);if (fd < 0){perror("open");return 1;}dup2(fd, 1);fprintf(stdout, "%s\n", argv[1]); //stdout->1->显示器return 0;
}

  • dup2使old_fd变成new_fd,就相当于更改fd对应的内容指向

理解Linux下一切皆文件

linux是用C语言写的,而C语言要实现面向对象,甚至是运行时多态,必须封装

但是C语言struct只能封装成员属性,要封装成员方法,就需要使用函数指针(用来调用成员方法)

  • linux的设计哲学:体现在操作系统的软件设计层面
  • Linux看待所有硬件都是struct file,通过函数指针调来调去,这是一种统一化

理解缓冲区

  • 缓冲区就是一段内存空间
  • 存在缓冲区的原因是:为了提高整机效率,提高用户的响应速度,

缓冲区的刷新策略

  1. 立即刷新
  2. 行刷新(行缓冲) \n
  3. 满刷新(全缓冲)

所有设备都是倾向于全缓冲的,特殊情况:a.用户强制刷新(fflush),b.进程退出

缓冲区的位置

  • 第一个程序,向显示器打印输出4行文本,
  • 向普通文件(磁盘上),打印的时候,变成了7行,其中:
    • C语言 IO接口是打印了2次的
    • 系统接口,只打印1次和向显示器打印的一样
  • 第二个程序,加上了fflush(stdout)强制刷新,向显示器和向普通文件(磁盘上)输出的文本都是4行

解释上面2个程序产生不同结果的原因

  1. 如果向显示器打印,刷新策略就是行刷新,那么最后执行fork的时候,
    一定是函数执行完了 && 数据已经被刷新了,那么fork就无意义
  2. 如果你对应的程序进行了重定向,要向磁盘文件打印
    隐形的刷新策略就变成的了全缓冲,那么\n就无意义
  3. 第2个程序中的fflush(stdout)强制刷新,不管刷新策略是什么都会被强制刷新
  4. fork()之前的函数已经执行完了,但是并不代表数据已经刷新了!!
    这些数据在当前进程对应的C标准库中的缓冲区中
    这些数据也是父进程的数据,fork()创建子进程,是会发生写时拷贝的,这就是为第一个程序向普通文件(磁盘上),打印的时候,变成了7行的原因

缓冲区如果是OS统一提供的,那么我们上面的代码,表现应该是一样,所以是C标准库提供且维护的  

用户级缓冲区

  • C标准库给我们提供的用户级缓冲区,在struct FILE 结构体中,这个结构体中不仅封装了fd
    还包含了该文件fd对应的语言层的缓冲区结构

简单设计用户级缓冲区

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#include <stdlib.h>
#define NUM 1024struct MyFILE_ {int fd;char buffer[1024];int end; //当前缓冲区的结尾
};typedef struct MyFILE_ MyFILE;MyFILE* fopen_(const char* pathname, const char* mode)
{assert(pathname);assert(mode);MyFILE* fp = NULL;if (*mode == 'w') {// 假设是以'w'的方式打开int fd = open(pathname, O_WRONLY | O_TRUNC | O_CREAT, 0666);// 系统接口if (fd >= 0){fp = (MyFILE*)malloc(sizeof(MyFILE));memset(fp, 0, sizeof(MyFILE));fp->fd = fd;}}return fp;
}// C标准库中的实现!
void fputs_(const char* message, MyFILE* fp)
{assert(message);assert(fp);// 把数据存放在缓冲区中strcpy(fp->buffer + fp->end, message); //abcde\0fp->end += strlen(message);printf("%s\n", fp->buffer);//暂时没有刷新if (fp->fd == 0){//标准输入}else if (fp->fd == 1){//标准输出if (fp->buffer[fp->end - 1] == '\n'){write(fp->fd, fp->buffer, fp->end);fp->end = 0;}}else if (fp->fd == 2){//标准错误}else{//其他文件}
}void fflush_(MyFILE* fp)
{assert(fp);if (fp->end != 0){//暂且认为刷新了--其实是把数据写到了内核write(fp->fd, fp->buffer, fp->end);syncfs(fp->fd); //将数据写入到磁盘fp->end = 0;}
}void fclose_(MyFILE* fp)
{assert(fp);fflush_(fp);close(fp->fd);free(fp);
}int main()
{//close(1);MyFILE* fp = fopen_("./log.txt", "w");if (fp == NULL){printf("open file error");return 1;}fputs_("one: hello world", fp);fork();fclose_(fp);
}

对stdout和stderr的补充

  • 1和2对应的都是显示器文件
  • ./myfile > ok.txt 2>err.txt    将标准输出重定向到ok.txt中,标准错误重定向到err.txt
  • ./myfile > log.txt 2>&1    将标准输出标准错误都重定向到log.txt

理解文件系统

背景知识

  1. 文件系统中的磁盘->磁盘级文件是没有被打开的文件
  2. 学磁盘级别的文件的侧重点
    1. 单个文件角度 -- 这个文件位置?,多大?,其他属性...
    2. 站在系统角度 -- 一共有多少个文件?,各自的属性在哪?,如何快速找到,..
  3. 磁盘文件
    1. 内存 - 掉电易失存储介质
    2. 磁盘 -- 永久性存储介质 -- SSD,U盘,flash卡,光盘,磁带
      磁盘不仅是一个外设,而且还是我们计算机中唯一的一个机械设备

4.磁盘结构

物理结构

  • 磁盘的盘面上会存储数据,里面有大量的像磁铁一样的东西,它们是有正负性
  • 先磁盘里写入,本质就是改变磁盘上的正负性,毕竟计算机只认识二进制

 存储结构

  • 灰色的圆圈叫做磁道,磁道的一部分叫做扇区

在物理上,如何把数据写入到指定的扇区中?

  • 通过一中叫CHS的寻址方案,通过这种方案所有的扇区就都能够找到了

    • 1.在那一个面上(对应的就是那一个磁头)
    • 2.在那一个磁道(柱面)上
    • 3.在那一个扇区上

磁盘的抽象(虚拟,逻辑)结构

  • 磁盘的存储数据的基本单位是512字节
  • 将磁盘的盘面展开,想象成一种线性结构
  • 访问一个扇区,只要通过数组的下标就可以了,

对磁盘的管理

  • 将数据存储到磁盘中 -> 将数据存储到该数组
  • 找到磁盘特定扇区的位置 -> 找到数组特定的位置
  • 对磁盘的管理 - > 对数组的管理

对磁盘的管理 -> 对一个小分区(小数组) 的管理

  • 虽然磁盘的基本单位是扇区(512字节),但是操作系统(文件系统)和磁盘进行IO的基本单位是:4KB(8*512byte),硬件和软件(OS)进行了解耦!
  • 如果基本单位太小了,有可能会导致多次IO,进而导致效率的降低!
  • 如果操作系统使用的是和磁盘一样的大小,万一磁盘基本大小变了的话,OS的源代码也必须发生改变,所以IO没有以512字节为基本单位

在Linux在磁盘上存储文件的时候,内容和属性是分开存储的!

  •  Date blocks:多个4KB(扇区*8)大小的集合,报错的都是特定文件的内容
  • inode Table:inode是一个大小为128字节的空间,保存的是对应文件的属性
    该块组内,所有文件的inode空间的集合,需要标识唯一性,每一个inode块,都要有一个inode编号!
    一般而言,一个文件,一个inode对应node编号
  •  BlockBitmap:假设有10000+个blocks,10000+比特位:比特位和特定的block是一一对应的,其中比特位为1,代表该block被占用,否则表示可用
  • inode Bitmap:假设有10000+个blocks,10000+比特位:比特位和特定的indoe是一一对应的,其中比特位为1,代表该inode被占用,否则表示可用
  •  GDT(简写): 快组描述符,这个快组多大,已经使用了多少了,有多少个inode,已经占用了多少个,还剩多少,一共多少个block,使用了多少...
  • SuperBlock(简写):文件系统的属性信息
  • 这些分区使一个文件的信息可追溯,可管理

上面的快组分割成为上面的内容,并且写入相关的管理数据 -> 每一个快组都这么做 -> 整个分区就被写入了文件系统信息(格式化) 


  • 一个文件"只"对应一个inode属性节点,inode编号,但是一个文件不只有一个block,

文件名 VS inode编号

  • 找到文件: inode编号(依托于目录结构) -> 分区特定的bg -> inode -> 属性 -> 内容
  • 在linux中inode属性里面,是没有文件名这样的说法的,但是目录也是文件,它也应该有自己的inode,有自己的data block,
  • 文件名 : inode 编号存在一种映射关系,互为Key值
  •  inode是固定的,datablock是固定的!

在linux中删除文件,能恢复出来吗?

  • 能恢复出来,这与inode编号有关,如果inode编号,没有被使用,inode和datablock没有被重复占用,就可以恢复删除文件

软硬连接

软连接: ln -s 源文件 目标文件

  • 软连接有独立的inode -> 它是一个独立文件
  • 特性: 软连接的文件内容,是指向的文件对应的路径!
  • 应用: 相当于windows下的快捷方式

硬链接: ln 源文件 目标文件

  • 硬连接没有独立的inode -> 它不是一个独立文件
  • 创建硬链接时,仅仅只是在指定的目录下,建立了文件名 和 指定inode映射关系

inode的引用计数

  • 这个引用计数表示有多少个文件名与它相关,
  • 当我们删除一个文件的时候,并不是把这个文件inode删除,而是将这个文件的inode引用计数--
    当引用计数为0的时候,这个文件,才是真正的删除

  • 默认创建目录:引用计数(硬链接)默认是2

    • 自己目录名:inode
    • 自己目录内部: inode
  • 实际引用: 在目录的引用计数- 2就是目录里的文件数(进入这个目录,不包括目录中的目录)

动态库.so 与 静态库.a

写一个静态库

 

  • 生成一个静态库的命令:  ar -rc lib文件名.a 其他.o文件

  •  hello这个包里面有.h静态库

使用静态库

  • 第一步就是要把hello这个包拷贝到当前路径,也就是间接性的把这个静态库拷贝当前路径
  • gcc 源文件 -I(大写的i) 头文件搜索路径 -L 库文件搜索路径 -l (小写的l)指定静态库

写一个动态库

  • gcc -c -fPIC 其他.c -o 其他.o

  • gcc -shared 其他.o -o lib文件名.so

使用动态库

  • 动态库是一个独立的库文件,它可以和可执行程序,分批加载!!!

  • 当动态库和静态库同时存在时,默认调用动态库
  • 如果这时要使用静态库,需要在最后面加上-static

上面的那段代码只是给gcc说了动态库路径,系统加载器不知道,需要给它说明动态库路径 

方法一:  环境变量

  •  export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:要使用的动态库路径
  • 不过这种方式重启之后就会被清除

方法二:  新增配置文件

  •  sudo touch /etc/ld.so.conf.d/文件名.conf
  • sudo vim /etc/ld.so.conf.d/文件名.conf ,将自己写的动态库的路径放进去
  • sudo ldconfig,更新配置文件
  • 这种方式设置之后,重启依旧生效

  •  sudo rm /etc/ld.so.conf.d/文件名.conf
  • sudo ldconfig,清理缓存

方法三:  软连接

  •  sudo ln -s 动态库的路径 /lib64/lib目标文件名.so

lesson5-基础IO相关推荐

  1. java中sum=a+aa+aaa_Java面向对象基础IO系统

    Java面向对象基础–IO系统 一.IO 输入:input 输出:output Java的IO主要包含三个部分 流式部分:IO的主题部分 非流式部分:主要包含一些辅助流式部分的类 文件读取部分的与安全 ...

  2. Java基础-IO流对象之数据流(DataOutputStream与DataInputStream)

    Java基础-IO流对象之数据流(DataOutputStream与DataInputStream) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.数据流特点 操作基本数据类型 ...

  3. Linux中的基础IO(二)

    Linux中的基础IO(二) 文章目录 Linux中的基础IO(二) 一.基本接口 二.文件描述符 三.文件描述符的分配规则 四.重定向 五.dup2系统调用 六.minishell 一.基本接口 i ...

  4. Linux中的基础IO(一)

    Linux中的基础IO 文章目录 Linux中的基础IO 一.C语言中的文件接口 二.随机读写数据文件 三.文件读写的出错检测 一.C语言中的文件接口 写在前面 计算机文件是以计算机硬盘为载体存储在计 ...

  5. Linux系统编程25:基础IO之亲自实现一个动静态库

    本文接:Linux系统编程24:基础IO之在Linux下深刻理解C语言中的动静态库以及头文件和库的关系 文章目录 A:说明 B:实现静态库 C:实现动态库 A:说明 前面说过,库其实就是头文件和和.a ...

  6. 黑马 程序员——Java基础---IO(下)

    黑马程序员--Java基础---IO(下) ------Java培训.Android培训.iOS培训..Net培训.期待与您交流! ------ 一.概述 Java除了基本的字节流.字符流之外,还提供 ...

  7. 基础IO(文件接口、安装内核源码超详细步骤图解、静态库与动态库)

    基础IO C语言的文件操作接口 fopen fclose fread fwrite fseek 系统调用文件接口 open close read write lseek 安装内核源码 文件描述符&am ...

  8. Java基础IO流(二)字节流小案例

    JAVA基础IO流(一)https://www.cnblogs.com/deepSleeping/p/9693601.html ①读取指定文件内容,按照16进制输出到控制台 其中,Integer.to ...

  9. C++基础——IO库基础

    C++学习--IO库基础 IO处理通过继承封装了不同输入输出流读取的细节,其继承关系为 #mermaid-svg-vfNR5uEIxowMKr5Z .label{font-family:'trebuc ...

  10. Linux系统:基础IO

    基础IO 1. 内存文件 1.1 理解内存文件 文件是内容和属性的集合,文件不仅有内容,还有各种属性.任何文件操作都可以分为对文件内容的操作和对文件属性的操作. 文件没被打开时是放在磁盘上的,想要打开 ...

最新文章

  1. 数据中心内虚拟机迁移带来的网络技术难题
  2. c++ 数据类型转换笔记
  3. Linux中vi的常用命令和快捷键使用
  4. TensorRT学习笔记4 - 运行sampleGoogleNet
  5. C++之静态成员变量和静态成员函数
  6. F-Secure Client Security 注册机
  7. With you With me
  8. vue中用数组语法绑定class
  9. grub4dos命令引导自定义映像_电脑C盘过小,教你在任意磁盘下安装windows系统,应用引导即可...
  10. 第十八届绵竹年画节开幕 大巡游展示清末年画《迎春图》场景
  11. 国内统一Android应用市场,最全最干净的安卓应用市场
  12. 2022年的1024
  13. 从菜鸟到完全学不会二维傅立叶在图像处理算法中的应用【老司机教你学傅立叶】
  14. 必要的系统组件未能正常运行,请修复Adobe Flash Player
  15. 【Python表白小程序】表白神器——赶紧收藏起来~
  16. JHU计算机专业学费,约翰霍普金斯大学学费多少 贵不贵
  17. uni-app获取设备的唯一标识
  18. 中国数字地球行业发展态势与投资前景展望报告(新版)2022-2027年
  19. RSA算法和DH算法的区别
  20. FPGA[视频+文档+例程]170G相关资料放送(持续更新)

热门文章

  1. 渣本全力以赴33天,四面阿里妈妈(淘宝联盟),拿下实习岗offer
  2. 谈下自己的认识只掌握一门语言
  3. 青岛大学计算机专硕分数线,2020青岛大学研究生分数线汇总(含2016-2019历年复试)...
  4. Unity 项目 导出到手机后像素特别低 图片模糊解决
  5. 正则表达式 要求只能有汉字和数字(不能全为数字)(至少5个字符)
  6. 【容易打工网】于千万人之中,你是匠人
  7. 电子阅读+社交分享 QQ阅读中心被看好
  8. leetcode|割冷冻韭菜的最佳时机
  9. Elasticsearch(ES6)------(5)kibana的es查询、mysql查询转换和对应javaAPI使用(一)
  10. 日历怎样设置每月提醒