门户网站建设单位资质要求,建设公司网站的原则,开发投资集团,中国核工业二三建设有限公司咋样本篇重点#xff1a;文件描述符#xff0c;重定向#xff0c;缓冲区#xff0c;磁盘结构#xff0c;文件系统#xff0c;inode理解文件的增删查改#xff0c;查找一个文件为什么一定要有路径#xff0c;动静态库#xff0c;有的时候为什么找不到库#xff0c;动态库的…本篇重点文件描述符重定向缓冲区磁盘结构文件系统inode理解文件的增删查改查找一个文件为什么一定要有路径动静态库有的时候为什么找不到库动态库的加载。
目录
需要大概了解的知识
IO接口简单介绍
readwrite和open的初次使用
文件fd
012
重定向
struct file
磁盘的物理结构和逻辑结构
软硬链接
动静态库
静态库
动态库
2.生成动态库
3.使用动态库
总结 需要大概了解的知识
文件内容属性
内容和属性本质上都是数据都存储在磁盘中
由冯诺依曼可得文件想要被打开一定要先被加载到内存中
所以文件可被分为被打开文件内存中和未被打开的文件磁盘中
文件在被加载到内存之前肯定会在内存中生成对应的描述结构体 struct file和进程类似
对于被打开的文件我们也要进行管理怎么管理先描述再组织
一个进程可能打开多个文件所以进程和被打开文件的关系是1n
本篇先要大概了解进程和被打开文件的关系 IO接口简单介绍
之前我们学习过c语言的文件操作但那是语言级别的。由之前所学知道中间肯定需要操作系统参与所以c语言的文件操作必定封装了系统调用
readwrite和open的初次使用
read和write open 这里注意一下mode的使用即可
重点为什么open的返回值是int
文件fd
open的返回值int被成为文件描述符fd
当使用open函数打开文件再将对应的fd打印出来我们可以发现fd是一个较小的大概率连续的整数
这比较符合日常使用的数组的下标的特征 我们之前说过一个进程要打开一个磁盘中的文件的时候要先将磁盘中的文件加载到内存中在这之前我们要先建立对应的struct file 结构体先将里面默认为有属性和struct file*指针和其他字段除此之外还要建立一个数组
对应的结构体也要被管理起来怎么管理先描述再组织
被打开的文件也算一个小的进程了
在进程的对应的描述对象task_struct 结构体中我们可以发现一个数组指针struct file_struct *file指针一个数组
这个数组默认0,1,2下标被填充。
进程每打开一个文件对应的struct file就会被生成然后指向磁盘中的对应部分该数组从0开始找到未被填充的部分将其填充这样就能通过数组找到对应的结构体struct file对应的下标作为返回值返回给fd
这样进程就能通过该数组对struct file实现管理
文件描述符fd的本质就是数组的下标
那c语言的FILE又是什么鬼
由上可以推测出FILE是一个结构体里面必定封装了文件描述符fd
操作系统访问文件只认文件描述符
012
为什么该数组的0,1,2位置会被默认填充呢填充的是什么呢
填充的分别为stdin键盘stdout显示器和stderr显示器目的是为了方便程序员进行读写操作
stderr是什么鬼又如何进一步理解一切皆文件呢
struct file里除了属性还有对应的读写等操作方法集和缓冲区。
因为已经默认填充好0,1,2位置了并且有了对应的struct file所以当我们敲键盘的时候本质就是向对应的键盘设备写入数据当进行打印操作的时候本质就是向显示器写入数据 stdout和stderr都是显示器这是为什么呢为了将代码编辑运行的错误信息和普通的打印信息进行分类更好地为程序员定位错误位置和写日志
文件fd的分配规则从小到大进行寻找没有被使用数据的位置然后分配给指定的打开的文件
重定向
重定向可以分为清空重定向和追加重定向
1.清空重定向 第一次写的hello world被第二次写的你好呀覆盖了这就是清空重定向
2.追加重定向 第一次写的hello没有被第二次写的你覆盖而是追加在hello后面这就是追加重定向。
重定向本质上层fd不变fd指向的内容进行改变 struct file
struct file里主要包含文件的属性文件的操作方法集和文件缓冲区
磁盘中的文件要加载到内存中其实就是加载到对应的文件缓冲区中这一工作由操作系统来做 读数据要将数据读到内存中——将磁盘中数据拷贝到对应的struct file 里的文件缓冲区写数据要将数据读到内存中——将磁盘中数据拷贝到对应的struct file 里的文件缓冲区然后再进行修改
我们在应用层进行数据读写的本质是什么将文件缓冲区中的数据进行来回拷贝
缓冲区是什么呢由上图可得缓冲区是struct file结构体里的一段空间所以缓冲区本质上就是内存的一部分空间
为什么要有缓冲区呢缓冲区就等于现实生活中的菜鸟驿站我们要把一个东西送给朋友有了菜鸟驿站就不用亲自开车去送而是到驿站去寄快递到驿站寄了以后就会说已经把东西送了出去了此时东西也不再属于我们了。所以缓冲区的最主要作用就是提高效率——提高使用者的效率
因为有了缓冲区的存在可以积累一部分数据然后统一发送减少了IO的次数提高了发送效率
缓冲区因为可以暂存数据必定有一定的刷新方式 无缓冲立即刷新行缓冲行刷新\n全缓冲等缓冲区满了再进行刷新 一般策略特殊情况 强制刷新进程退出的时候一般要刷新缓冲区 一般对于显示器文件——行缓冲
对于磁盘上的文件—— 全缓冲重定向后往往由行缓冲变成全缓冲
一段样例
#includestdio.h
#includestring.h
#includeunistd.hint main()
{fprintf(stdout,C:hello fprintf\n);printf(C:hello printf\n);fputs(C: hello fputs\n,stdout);const char* strsystem call:hello write\n;write(1,str,strlen(str));fork();return 0;
}测试结果 1.当我们向显示器文件进行打印的时候显示器文件的缓冲区是行缓冲每一个字符串都有\nfork之前所有数据都已经被刷新有第一次的结果出现
2.当进行重定向后磁盘文件的缓冲区是全缓冲遇到\n不再刷新也意味着缓冲区变大这些字符串不足以将缓冲区写满让其刷新fork执行的时候数据仍然存在缓冲区中
3.我们目前谈的缓冲区和OS无关只和c语言有关
4.当进程退出的时候一般都要刷新缓冲区哪怕没有满足对应的条件
5.fork之后立马退出当一个进程退出的时候刷新缓冲区此时就要发生写时拷贝 缓冲区实际上分为用户缓冲区C语言提供和文件缓冲区
上面所谈以及日常使用最多的其实都是C/C提供的语言级别的缓冲区上面所说的刷新规则也都是C语言缓冲区的刷新规则 上文所说的刷新就是把C缓冲区的数据拷贝到文件缓冲区
假如我们调用printf的函数将要打印的数据拷贝到C语言缓冲区这一函数就返回了
然后进行刷新调用write函数将C缓冲区的数据拷贝到文件缓冲区
如果直接调用系统调用write它没有语言缓冲区所以是直接写到文件缓冲区的
至于从文件缓冲区拷贝到磁盘这一刷新就要视系统而定了
那么C语言的缓冲区具体是在哪当调用C的文件操作的时候返回值都是FILE所以FILE结构体里不仅含有fd还必定含有缓冲区
以上就是对打开文件的大概介绍接下来谈谈在磁盘中存储的文件
虽然磁盘中的文件没有被打开那要不要对其进行管理呢答案是肯定需要的
对于这部分文件最核心的工作就是对其进行快递定位怎么做到路径
在了解如何管理磁盘文件中我们要先大概了解一下磁盘的物理结构抽象成逻辑结构
磁盘的物理结构和逻辑结构
众所周知磁盘是一种硬件结构那么如何存储数据呢
下面是磁盘的物理结构示意图 一个盘面可以有很多的同心磁道一圈磁道可以有很多相同的扇区
扇区是最小的存储单元通常为512字节即4kb
我们想要向一个扇区进行写入那么要怎么寻址呢 我们可以将磁盘结构进行逻辑抽象这样对磁盘文件的管理就变成了数组的管理
所用的寻址方法是CHS
而文件系统通常以文件块为基本单位一个文件块通常为4kb
对磁盘中数据的管理和上面类似
管理工作当然没有那么简单我们肯定要对磁盘进行分类就和C盘D盘一样 假设总共磁盘有500GB对其进行分区分为100100, 100, 200GB的内容
各个分区中又由2GB大小的group组成以及一个启动块Boot Block
这样只要我们管理好着2GB空间就管理好一个分区进一步管理好了整个磁盘文件
怎么管理好2GB大小的空间呢当然是要了解这个块里的组成 超级块Super Block存放文件系统本身的结构信息。记录的信息主要有bolck 和 inode的总量未使用的block和inode的数量一个block和inode的大小最近一次挂载的时间最近一次写入数据的时间最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏可以说整个文件系统结构就被破坏了GDTGroup Descriptor Table块组描述符描述块组属性信息有兴趣的同学可以在了解一下块位图Block BitmapBlock Bitmap中记录着Data Block中哪个数据块已经被占用哪个数据块没有被占用inode位图inode Bitmap每个bit表示一个inode是否空闲可用。i节点表:存放文件属性 如 文件大小所有者最近修改时间等数据区存放文件内容 文件的属性和数据分开存储属性存在iNode中数据存在Data Block中
这里以新建一个文件的过程为例子讲解每个区域的大概作用 新建一个文件首先要在磁盘中的一个分区中找到一个group利用iNode Tables查找struct iNode 的使用情况使用了对应位置为1没使用为0找到一个或者多个未被使用的struct iNode进行填充属性信息
关于存储的数据通过块位图知道哪些文件块未被使用将数据内容填充到对应的文件块并将文件块下标赋值到iNode里的Blocks数组这样通过iNode就能知道哪些文件块被使用哪些文件块没被使用
虽然Blocks数组只有15个内容但是其中下标0-12为直接映射13是间接映射14为三级映射所以能记录的数据块是很多的
iNode编号在一个分区内具有唯一性一个分区大概有32000个iNode文件识别系统不认文件名只认iNode编号
这里有一个很关键的问题一开始你怎么知道文件在哪个分区呢通过路径的前缀判断在哪个分区
上面说的是文件那要怎么理解目录呢
目录本质上也是一个文件存储的是文件名和iNode编号的映射关系
当一个文件被建立好后对应的目录也要进行记录 软硬链接
我们先对一个目录中的文件进行硬软链接操作得到以下结果 可以发现硬链接的iNode编号和原本文件的iNode编号是一样的软链接的iNode编号和原本文件的iNode编号是一样的
可以得出硬链接不是独立的文件软链接是独立的文件
当对一个文件建立硬链接的时候iNode里的引用计数会增加删除的话会减少当引用计数为0时磁盘对应文件内容被释放即相应的iNode BitMap对应的位置改为0
软链接则是一个独立文件因为iNode编号不同内容保存的是指向的目标文件的路径类似Windows的快捷方式
用户无法对目录新建硬链接操作因为建立一个目录并进去后发现默认有.和..两个文件表示当前目录和上级目录这里的.就相当于已经对目录建立硬链接了
动静态库
在理解什么叫做动静态库之前应该先理解什么叫做库
库在日常生活中随处可见我们之前学习的数据结构unordered_map,unoredered_set就需要包含对应的库文件——库其实就是写来给人提供服务的里面包含各种函数从而实现了强大的功能
根据不同用法又将库分为静态库和动态库 静态库
静态库简单来说就是将库里面的代码拷贝到可执行程序的代码区程序运行的时候就不再需要静态库了因为程序里已经有对应的代码了 由于是直接拷贝库的内容所以静态库就会很大 要是一个库被重复使用很多次还是静态库那么很多个可执行程序里都有相同的代码造成空间的浪费
动态库
动态库简单来说就是可执行程序拿到动态库的首地址在可执行程序运行的时候再去拿地址去链接库文件进行使用
动态链接的程序加载的时候不仅程序要被加载被链接的库也要进行加载被映射进虚拟地址空间的共享区 动态库的本质将重复代码放到一个地方以节省空间
动态库因为不是直接在可执行程序里的所以链接的时候就要去找库 库的搜索路径 从左到右搜索-L指定的目录。由环境变量指定的目录 LIBRARY_PATH。由系统指定的目录。/usr/lib/usr/local/lib 本人理解感觉动态库就和指针一样需要用的时候通过它去找代码只存在一份不会占用空间 静态库是直接拷贝的占用空间更大
静态库把方法加载到可执行程序中加载几个可执行程序就在内存中有几个静态库的内容动态库第一次调用加载到内存中以后在继续使用这个库的可执行程序可以直接找到
静态库和动态库的生成
静态库的生成
写出mylib.h代码
#ifndef __MYLIB_H__
#define __MYLIB_H__
#includestdio.h
void print();#endif写出mylib.c代码
#includemylib.h
void print()
{printf(Hello Linux!\n);
}写出main.c代码
#includemylib.h
int main()
{print();return 0;
}生成静态库第一步,先生成目标文件: gcc -c mylib.c -o mylib.o
第二步用目标文件生成静态库:ar -rc libmylib.a mylib.o ar是gnu归档工具rc表示(replace and create)
可以查看静态库中的目录列表ar -tv libmylib.a
t:列出静态库中的文件 v:verbose 详细信息
第三步运行main.c得到结果。 gcc main.c -L. -lmylib
-L 指定库路径 -l 指定库名 测试目标文件生成后静态库删掉程序照样可以运行。 生成动态库
还是上面的代码为例子
生成动态库第一步,先生成目标文件: gcc -fPIC -c mylib.c
2.生成动态库
shared: 表示生成共享库格式fPIC产生位置无关码(position independent code)库名规则libxxx.so
第二步用目标文件生成动态库:gcc -shared -o libmylib.so *.o
3.使用动态库 编译选项I链接动态库只要库名即可(去掉lib以及版本号)。L链接库所在得路径。
第三步运行main.c得到结果。 gcc main.c -o main -L. -lmylib 可以看看这篇文章https://blog.csdn.net/qq_44918090/article/details/118185830?ops_request_misc%257B%2522request%255Fid%2522%253A%2522170859497116800180637536%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257Drequest_id170859497116800180637536biz_id0utm_mediumdistribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-28-118185830-null-null.nonecaseutm_termlinuxspm1018.2226.3001.4450 一些补充 能够在共享区任意加载的关键偏移地址 加载进内存的时候每个指令都有自己的物理地址和虚拟地址偏移地址 虚拟地址偏移地址初始化页表左边物理地址初始化页表右边 cpu用的全是虚拟地址所以调用的时候才会有虚拟地址到物理地址的转变 我们见到的FILE *文件流指针其实就是_IO_FILE的类型重定义其中封装包含了文件描述符因此一个文件流指针一定对应有一个文件描述符。 总结
以上就是今天要讲的内容本文仅仅简单介绍了进场基础IO的使用而系统编程提供了大量能使我们快速便捷地处理数据的函数和方法我们务必掌握。这篇文章写了挺久如有错误欢迎讨论也请大家多多支持