• 基础IO详解


    目录

    C语言文件操作

    文件的系统调用接口

    文件管理

    Linux下,一切皆文件

    重定向

    缓冲区

    理解文件系统

    动态库和静态库


    C语言文件操作

    C程序默认会打开三个输入输出流,stdin,stdtou,stderr,也被称为标准输入输出流

    stdin:键盘;stdout:显示器;stderr:显示器

    fputs向一般文件(磁盘中的)或者硬件设备都能写入数据 

    stdout和stderr的区别

    stdout在输出重定向后,会将输出内容从显示器转到文件中,而stderr则没有,输出重定向后还是

    将输出内容输出到显示器上

    文件操作,最终都是访问硬件:显示器,键盘,文件(磁盘),而os是硬件的管理者!所以所

    有的语言上的对"文件"的操作,都必须贯穿os!而os不相信任何人,要访问操作系统,是需要通

    系统调用接口的!

    几乎所有的语言文件操作函数fopen,fclose,fread,fwrite,fgets,fputs,fgetc,fputc等底

    层一定需要使用os提供的系统调用!

     

    文件的系统调用接口

    参数mode

    如下图,创建并打开文件,如果没有指定权限,该文件的权限就会是乱的

    指定权限后

    参数flags

    如下图,flags是传递标志位,表示以什么方式打开,它有32个bit位,一个bit位,代表一个标志!

    O_WRONLY、O_CREAT等都是只有一个bit位是1的数据,而且不重复,下图是以8进制的形式 

    返回值

    如下图,连续打开多个文件,它们的返回值是连续的一串数字,但是却是从3开始的,这是因为0代

    表的是标准输入:键盘,1代表的是标准输出:显示器,2代表的是标准错误:显示器,通过打印出

    来的连续的数字,很容易想到数组的下标,而open的返回值又是os给的

    文件管理

    所有的文件操作,表现上都是进程执行对应的函数,即进程对文件的操作,而要操作文件就得先打

    开文件,将文件相关的属性信息加载到内存

    操作系统中存在大量的打开的文件,进程:打开的文件=1:n,既然系统中存在可能更多的打开的

    文件,那os就必须对其进行管理,管理方式还是先描述,再组织

    写文件

    读文件

    如果一个文件,没有被打开,它是被存放在磁盘里面的! 

    空文件是要占磁盘空间的,因为文件有属性,属性也是数据

    磁盘文件 = 文件内容 + 文件属性

    如下图,打开的文件很多,如何确定哪些是我们进程的呢?所以就有了一个struct file* fd_array[]

    数组,来保存struct file的地址

    fd:本质是内核中进程和文件关联的数组的下标!!!

    Linux下,一切皆文件

    在struct file的上层看来,所有的文件,读写操作都是调用读->read,写->write,根本就不关心你

    到底是什么文件! 

    注意:上图中的每个硬件对应的read、write一定是不一样的!!!

    默认的0、1、2也是可以直接读写的!

    如下图,文件描述符的分配原则,给新文件分配的fd,是从fd_array中找一个最小的,没有被使用

    的,作为新的fd!

    如下图,如果关掉了1,把本来输出到显示器的内容,输出到了文件中

    原因:

    如下图,FILE是C语言层面上的结构体!而这个结构体里面一定包含了一个整数,是对应在系统层

    面的,这个文件的打开的对应的fd!

    printf、fprintf、cin——语言层

    系统调用:open,write--fd —— 系统层

    重定向

    追加重定向:O_APPEND

     输入重定向  

     上图的关系一定存在,证明如下图

    dup2:重定向的系统调用接口

     输出重定向

    输入重定向

    执行exec*程序替换的时候,不会影响我们曾经打开的所有的文件

    创建子进程,files_struct会被拷贝一份,但是打开的文件不会

    父进程如果曾经打开了标准输入,标准输出,标准错误,子进程也会继承下去!!!所以我们所有

    的进程,都会默认打开标准输入,标准输出,标准错误

    1与2的区别

    如下图,在重定向后,只有标准输出会写入到文件中,而标准错误还是会打印到显示器上

    如下图,是将标准输出和标准错误都写入文件中的方法

    缓冲区

    如下图,在close(fd)后,将本来打印在显示器中的内容变成写入文件的内容,文件中却也没有!

    原因:

    进程退出的时候,会刷新FILE内部的数据到os缓冲区

    用户->os的刷新策略

    立即刷新(不缓冲)

    行刷新(行缓冲\n),比如,显示器打印

    缓冲区满了,才刷新(全缓冲),比如,往磁盘中写入

    注意:上述的刷新策略,对于os->磁盘,同样适用!!!

    如下图,可知,因为printf、fprintf都是C语言的接口,所以它们的内容会被写入C缓冲区中,而这

    里又发生了重定向,显示器->log.txt,即由行缓冲变成了全缓冲,此时内容还在C缓冲区,又因为

    close(fd),所以C缓冲区也就无法将内容刷到文件的内核缓冲区

    如下图,如果在close(fd)前,加一个fflush,就会立即刷新

    如下图,在结尾加上一个fork后,输出到显示器中的内容都只有一份,而重定向后,输出到文件的

    用C语言的接口输出的内容,则有两份。这是因为发生重定向后,由行缓冲变为了全缓冲,C缓冲

    区的内容没有被刷新到os缓冲区,子进程发生写时拷贝,就有了两份!

     如下图,在加了fflush后,变为立即刷新后,就变为了一份!

    理解文件系统

    磁盘(机械硬盘)

    如下图,磁盘中有一个光盘,用来存储数据的,我们可以把它想象成磁带,卷起来的时候是一个

    圆,其实是一个线性结构!

    磁盘写入的基本单位是:扇区,512字节,但是在与os交互的时候是4KB

    Linux中,文件名在系统层面没有意义!是给用户用的!

    Linux中,真正标识一个文件,是通过文件的inode编号!!!一个文件一个inode编号

      

    如下图,因为磁盘太大,管理成本太高,所以需要将磁盘分区,划分为几块,便于管理,同时给每

    一个区都写入文件系统,即格式化。比如中国这么大,为了便于管理,就划分为了多个省,而每个

    省都有领导班子。同时每个区的空间也太大,就分为了多个组,每个组都有文件属性表和文件内容

    表,其中都有很多数据块,存放着文件的内容和属性,有了inode编号,就能在inode Table中找

    到对应的属性块,又通过blocks[]在Data blocks中找到对应的数据块。因为文件太多,在需要创

    建一个文件时,不可能把所有的件都去遍历一遍,所以就有了inode Bitmap,用位图来判断

    某个文件"是否"被占用!

    目录是文件,有inode,而且有数据,存放的是文件名:inode编号!

    查看文件内容

    cat test.c -> 先查看文件所在的目录lesson -> data block -> 12345:test.c -> 12345 -> inode

    table -> inode->block[] -> 打印文件内容!

    删除文件

    只需要将inode Bitmap中,将inode编号所对应bit位的大小由1变为0即可!

    你所创建的所有文件,全部一定在一个特定的目录下!!!

    软链接

    创建软链接

    删除软链接

    应用场景

    如下图,当我们要执行某个目录下的某个文件,且它的路径很长时,我们就可以给它创建一个软链

    接,这样就不用输入一长串路径来执行,只需要执行创建的软链接即可!这就类似于在

    Windows中创建快捷方式

    软链接是有自己独立的inode的!软链接是一个独立文件!有自己的inode属性,也有自己的数据

    (保存的是指向文件的所在路径+文件名)

     

    硬链接 

    硬链接本质上根本就不是一个独立的文件,而是一个文件名和inode编号的映射关系,因此自己没

    有独立的inode!

    创建硬链接,本质是在特定的目录下,填写一对文件名和inode的映射关系!

    如下图,3是test.c所创建的硬链接数 

    如下图,创建一个目录时,默认的硬链接数是2,是因为该目录下有一个.的文件,是它的其中一个

    硬链

     

    如果在此目录下再创建一个目录,那就会变为3,因为新创建的目录中的..文件也是它上级目录的一

    个硬链接

    文件时间

    如下图,一个文件对应有三个时间,分别是Access、Modify、Change

     Access:文件最近被访问的时间,实际操作后,可能文件实际没有变化

    原因:在较新Linux内核中,Access时间不会被立即更新,而是有一定的时间间隔,os才会自动

    进行更新时间!

    Modify:最近一次修改文件内容的时间

    Change:最近一次修改文件属性的时间

    如下图,在test.c中写了一句printf语句之后,它的三个时间都发生了变化,而Change时间会变,

    因为修改了文件的大小属性,所以当我们修改文件内容的时候,是有可能会修改文件的属性的!

    如下图,在一次make之后,如果继续make,也无法再编译

    原因:

    Makefile与gcc会根据时间问题,来判定源文件和可执行程序谁的时间更新,从而指导系统哪些

    文件需要被重新编译

    如下图,当文件test的时间比文件test.c的时间早一些的时候,就可以make,重新编译

    动态库和静态库

    一套完整的库:库文件本身;头文件(文本的,会说明库中暴露出来的方法的基本使用);说明文档

    库文件命名

    如下图,ldd是显示可执行程序依赖的库,在Linux中,如果是动态库,库文件是以.so作为后缀

    的!如果是静态库,库文件是以.a作为后缀的!动态库和静态库就是文件,和我们所创建的一样!

    库文件的命名:libXXXX.so  or  libYYY.a-..

    库的真实名字:去掉lib前缀,去掉.a-或.so-后缀,剩下的就是库名称!

    在Linux中,C++的源文件有三种格式,.cc、.cpp、.cxx!

    gcc默认动态链接编译

    gcc静态链接编译

    在C/C++中,为什么有时候编写代码时,我们会在.h文件放声明,在.c/.cpp文件放入实现方法?

    因为我们要制作库!方便使用,以及保证源码的私密性

    如下图,多文件的程序编译方法

    另一种方法,则是采用Makefile,将test_lib中的.c文件全部生成.o文件放在当前目录,然后链

    接。<是将.c文件全部展开,一个一个地生成.o文件

    制作静态库 

    将所有的.o文件打包就是静态库文件

    如下图,用ar -rc将.o文件全部打包放入静态库文件libmymath.a中,而如果想要给别人使用,则

    创建一个目录,里面放有静态库文件,以及.h文件,然后将这个目录给他,用ar -tv则能查看静态

    库文件

    使用静态库

    我们给别人交付的其实就是一个库文件+一套头文件

    如下图,将静态库给我的朋友之后,里面包含了库文件和.h文件,他想使用时,只需调用即可!

    -I(i):指明头文件搜索路径

    -L:指明库文件搜索路径

    -l(L):指明要链接哪一个库,后面的库是它的真实名字

    如果不想每次都gcc ...,也可以写一个Makefile文件!!!

    注意:我们之前写的代码,也使用了库,没有指明这些选项,是因为是在系统的默认路径下:比

    如/lib64,/usr/lib,/usr/include等,编译器是能识别这些路径的。也就是说,如果我不想带这些

    选项,也是可以把对应的库和头文件拷贝到默认路径下,但是不推荐这样做!

    制作动态库

    如下图,制作了一个 动态库文件,同时制作了一个目录,将全部.h文件和动态库文件放入其中

    shared:形成一个动态链接的共享库

    fPIC:产生.o目标文件,程序内部的地址方案是:与位置无关,库文件可以在内部的任何位置加

    载,而且不影响和其他程序的关联性

    使用动态库

    如下图,在编译时没有问题,但是在运行时却找不到库文件,这是因为下图中Makefile的那些选项

    只是告知编译器头文件库路径在哪里,当程序编译好的时候,此时已经和编译器无关了!所以需要

    在运行的时候,进一步告知系统,我们的库在哪里

    解决方法一:

    配置环境变量LD_LIBRARY_PATH,将lib目录的路径拷贝给它

    解决方法二:

    如下图,用root身份,进入/etc/ld.so.conf.d/创建一个.conf文件,将路径拷贝到这个文件

     

    总结

    我们其实一直都在直接或间接使用库(C/C++)

    如何使用

    拿到别人的库文件和头文件,加入到自己的项目中!

    如何制作

    注意:所有的源代码都需要先被编译成为.o(可重定向目标文件)

    可以先把自己的所有的源文件编译成为.o

    制作动静态库的本质:就是将所有的.o打包,使用ar或者gcc来进行打包

    交付:头文件+.a or .so文件

    注意

    如果你只提供静态库,我们只能将我们的库,静态链接到我们的程序中

    如果你只提供动态库,我们只能将我们的库,采用动态链接,是无法-static静态链接的

    如果既想动态链接,又想静态链接,一般需要提供两种版本的库文件!!!

    gcc、g++优先动态链接!

    我们之前写的所有的代码都没有报错,因为默认是动态链接的,因为我们一定有动态库(因为系

    统中有很多的命令是用C语言写的,而且是动态链接的!)

  • 相关阅读:
    西门子Mendix低代码资深技术顾问张戟,将出席“ISIG-低代码/零代码技术与应用发展峰会”
    莫名其妙: conda错误ko及总结
    Leetcode.146 LRU 缓存
    python安全脚本开发简单思路
    Revit中如何对项目对象设置透明及“构件元素上色”
    Python爬虫-贝壳新房
    使用DIV+CSS进行网页布局设计【HTML节日介绍网站——二十四节气】
    flutter 混合开发 module 依赖
    【树莓派】常规操作8则
    石油数字孪生可视化管理平台,推动石油行业数字化转型与智能化应用
  • 原文地址:https://blog.csdn.net/weixin_58867976/article/details/126776584