马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
一.文件理解
我们要举行文件利用,前提是我们的程序跑起来了,文件的打开和关闭,是CPU在实行代码。
打开一个文件,本质上是进程打开文件。
文件没有被打开时,会在磁盘中存在。
进程能够打开许多文件,许多环境下,在OS内部,一定存在大量被打开的文件。
每打开一个文件,在OS内部,一定要存在对应的描述文件属性的布局体,雷同PCB。
“>”符号,即输出重定向,也是文件利用,可以用来新建文件,清空文件,与用“w”方式打开文件一样。
“>>”符号,为追加重定向,与用“a”方式打开文件一样。
文件->磁盘->外设->硬件->向文件中写入,本质是向硬件中写入->用户没有权利直接写入->OS是硬件的管理者->通过OS写入->OS必须给我们提供系统调用(OS不相信任何人)->fopen/fwrite/fread/fprintf/fscanf/scanf/printf/cin/cout...->我们用的C/C++等语言,都是对系统调用接口的封装。
二.系统调用文件利用
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
//文件打开
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
#include<unistd.h>
//文件关闭
int close(int fd);
#include<unistd.h>
//文件写入
ssize_t write(int fd,const void *buf,size_t count);(文件描述符,缓冲区,缓冲区大小)
#include<unistd.h>
//文件读取
ssize_t read(int fd, void *buf, size_t count);(文件描述符,缓冲区,缓冲区大小)
open是OS系统下调用文件利用接口,其中第一个用于利用已有的文件,第二个用于利用新文件。
1.参数
open:
pathname:文件名
flags:文件利用方式
mode:设置文件权限(二进制方式)
由open创建的新文件都必须给出文件的权限。
但是我们默认创建的权限会与系统默认的权限掩码umask其冲突, 可以使用umask函数更改权限掩码。
flags为文件利用方式,可以有多个同时使用,使用“|”隔开:
- O_WRONLY:只写
- O_CREAT:不存在就创建
- O_TRUNC:清空文件内容
- O_ADDEND:从末端追加内容
- O_RDONLY:只读
2.返回值
open函数的返回值称为文件描述符fd,其本质为内核的进程与文件映射关系的数组的下标。
在利用系统中,每一个被利用的文件,都会链入范例为struct file的文件实行链表中,文件的属性存放在布局体中,文件的内容则存放在内核级缓存中。
在进程task_struct内部,还有一个struct file_struct *files范例的指针,指向范例为struct file_struct的文件描述符表,该布局体中存在一个范例为struct file* fd_array[N]的指针数组,指针指向文件实行链表中的每个文件的地址,数组每个元素的下标即为文件描述符fd。
当open函数打开错误时,返回 -1。
打开正确时,会返回文件映射关系的数组的下标。
该下标默认从3开始,每新建一个文件则加1。为什么不从0开始?
原因是0、1、2三个下标被占用,分别是:
- 0:标准输入 键盘
- 1:标准输出 显示器
- 2:标准错误 显示器
在OS内部,系统访问文件的时候,只认文件描述符!
文件描述符的分配规则:
进程查自己的文件描述符表,从0开始分配最小的没有被使用的fd下标给新文件。
3.open的实际作用
- 创建struct file;
- 开辟文件缓冲区的空间,加载文件数据(延后);
- 查进程的文件描述符表;
- 获取file地址,填入对应的表下标中;
- 返回下标。
三.重定向
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
//获取文件的属性
int stat(const char *pathname, struct stat *statbuf);(路径,文件属性布局体)
int fstat(int fd, struct stat *statbuf);(文件描述符,文件属性布局体)
int lstat(const char *pathname, struct stat *statbuf);(路径,文件属性布局体)
在内核中改变文件描述符表特定下标的内容,从而使一个特定的文件描述符成为目标文件的下标,和上层无关。
例如我们先关闭标准输出stdout文件,随后再打开一个新的文件,那么标准输出stdout的文件描述符1就会给到新的文件。
#include <unistd.h>
int dup2(int oldfd,int newfd);
重定向函数,其参数为文件描述符,作用是让newfd成为oldfd的副本。从而使文件描述符newfd成为oldfd所指向的文件的下标。本质是文件描述符下标所对应的内容的拷贝。
思考一下,标准输出和标准错误都是向显示器打印,那么两者有什么区别呢???
两者有差别的文件描述符,这样就使得我们可以通过重定向将正常的输出信息和错误信息分开,以便我们去探求错误,常用的perror函数就是向标准错误打印信息。
四.缓冲区
在struct FILE内部,存在语言级别的文件缓冲区,printf,fprintf等语言级系统调用获取到数据后,先会放入文件缓冲区中,然后通过文件描述符,将数据冲刷进内核的文件缓冲区,最后在冲刷进磁盘空间。
缓冲区是一段内存空间,给上层提供高效的IO体验,间接提拔整体的效率。
每一个文件都有一个自己的缓冲区。
C语言为什么要在FILE中提供用户级缓冲区:为了减少底层调用系统调用的次数,让使用C语言IO函数(printf,fprintf)效率更高。
函数会将数据先放入缓冲区,最后实行一次性革新。
1.如何工作(用户级)
(1)革新计谋
- 立即革新。fflush(stdout)(语言层)、int fsync(int fd)(系统层)
- 行革新。显示器
- 全缓冲。缓冲区写满才革新,普通文件。
(2)特殊环境
五.文件系统
在系统中存在许多的文件,但是被打开的文件只是很少的一部分。
没有被打开的文件则存放在磁盘中,称为磁盘文件。
想要打开一个文件,就需要通过文件路径+文件名找到该文件,进而从磁盘中将其打开。
1.磁盘的存储布局
磁盘读写的基本单元是扇区:512字节,4KB。
如何找到一个指定位置的扇区?
- 找到指定的磁头。Header
- 找到指定的磁道(柱面)。Cylinder
- 找到指定的扇区。Sector
该方法称为CHS定址法。
文件实在就是在磁盘中占用几个扇区的标题。
2.逻辑抽象磁盘存储
文件由许多个块构成,块从0开始暗号,每个块由8个连续的扇区构成。我们只需文件的起始地址,以及磁盘的总大小,那么有多少块,每个块的块号,进而转换到对应的多个CHS地址就都可以得到了。每个块对应的地址称为LBA :逻辑区块地址。
一整个磁盘会先分为若干个区,即我们电脑中常见的C盘、D盘等,每个区又会分为若干个组,管理好一组,就可以管理好一个分区,进而管理好好整个磁盘,这种思想称为分治。
如图所示即为区的一个分组,下面我们来看一个组中的各种构成。
- 超等块(Super Block):存放文件系统本身的布局信息。记载的信息重要有:bolck 和 inode的总量,未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次查验磁盘的时间等其他文件系统的相干信息。Super Bloc的信息被破坏,可以说整个文件系统布局就被破坏了。
- GDT,Group Descriptor Table:块组描述符,描述块组属性信息。
- inode Bitmap:每个bit表示一个inode是否空闲可用。
- Block Bitmap中记载着Data Block中哪个数据块已经被占用,哪个数据块没有被占用。
- inode table:存放文件属性如文件大小,所有者,最近修改时间。
- Data blocks:存放文件内容。
linux文件系统特定:文件内容和文件属性分开存储。
linux中文件的属性是一个大小固定的聚集体,即struct inode,128个字节。
内核层面上,每一个文件属性布局体中都要有inode number,我们通过inode号标识一个文件。
使用指令:ll -li便可以看到当前目次下的文件的inode number。
inode编号是以整个分区为单元举行划分的,一个分区内不可能存在雷同的inode编号,差别的分区则可能存在。
目次本身也是一个文件,目次 = 文件属性 + 文件内容,目次的内容即:文件名和inode编号的映射关系。
由此我们能够得出一下结论:
- 一个目次下不能创建同名文件。
- 查找文件的顺序:文件名->inode编号。
- 目次的r权限,本质是是否允许我们读取目次的内容即,文件名:inode的映射关系。
- 目次的w权限,新建文件,最后一定要向当前所处目次内容中写入:文件名和inode的映射关系。
想要找到一个文件,就需要找到其所在的目次,但是目次也是一个文件,我们该如何找到这个目次呢?目次通常是由谁提供的呢?
你或者你的进程早就已经提供了。内核文件系统提前写入并构造好,然后我们提供的。
Linux内核再被使用时,一定存在大量的剖析完毕的路径,系统同样需要对这些路径举行管理。
通过struct dentry{}布局体,保存路径剖析的信息,一个文件对应一个该布局体,所有的布局体通过链表管理。
但是得到inode,更前提得是,我的文件在哪一个分区中,如何探求分区呢?
分区->写入文件系统(格式化)->挂载到指定的目次下->进入该目次->在指定的分区举行文件利用。
这样一来,只要我们知道文件在哪个目次下,就同样可以知道它在哪个分区中。
六.软硬链接
软链接指令:ln -s + 目标文件 + 链接文件名(后缀.link)
硬链接指令:ln + 目标文件 + 链接文件名(后缀.link)
1.特征
- 软链接是一个独立的文件,因为有独立的inode number。
- 硬链接不是一个独立的文件,因为没有独立的inode number,用的是目标文件的inode。
- 文件属性中的有一列数字即为硬链接数,即文件的磁盘级引用计数:有多少个文件名字符勾通过inode number指向inode。
软连接的内容:目标文件对应的路径字符串,即windows中的快捷方式。
硬链接就是一个文件名和inode的映射关系,创建硬链接,就是在指定的目次下,添加一个新的文件名和inode number的映射关系。
硬链接的作用:
- 构建linux的路径布局,让我们可以使用"."和".."来举行路径定位。
- 用做文件备份。
七.动静态库
所谓的库文件,本质就是把.o文件打包。
1.静态库
静态库创建:
ar -rc + 库名(前缀lib,后缀.a) + 要打包的文件
使用静态库:
使用静态库有多种方式:
一是库、头文件以及main文件在同一目次下,直接使用:
gcc + main文件 + 库
二是将头文件添加到系统的头文件目次下,同时将库文件也添加到系统的库文件目次下:
gcc + main文件 + -l库名
但是我们不推荐将第三方库添加到系统的库文件下,所以当头文件和库文件在其他路径下时,还有第三种方法:
gcc + main文件 + -I(大i) + 指定头文件所在路径 + -L + 指定库文件所在路径 + -l第三方库名
2.动态库
动态库创建:
gcc -shared + 要打包的.o文件 + -o + 库名(后缀.so)
我们平时使用gcc举行编译时,如果不使用static选项时,默认自动连接的为动态库。
动态库在程序运行时,需要找到动态库并加载运行,所以使用动态库有以下几种方法:
- 将动态库安装到系统的动态库目次中(/lib64)。
- 在系统动态库目次中创建软连接。
- 将动态库所在路径导入环境变量(LD_LIBRARY_PATH)。
- 修改.bashrc配置文件,让环境变量永久生效
- 在/etc/ld.so.conf.d路径下新增动态库搜索的配置文件(.conf后缀),将动态库的绝对路径写入配置文件中,再通过ldconflg指令举行更新。
3.动态库VS静态库
系统默认链接的为动态库。
如果没有使用-static,并且只提供了当前的.a库,其他库正常动态链接。
-static的意义?
必须强制将我们的程序举行静态链接,这就要求我们链接的任何库都必须提供对应的静态版本。
4.动态库加载
动态库以文件形式存放在磁盘中,当程序运行需要加载动态库时,动态库先要被加载到内存中,动态库在内存中也要被OS通过链表管理在一起,随后通过页表映射到程序地址空间的堆栈共享区中。
八.可实行程序理解
我们的可实行程序,编译成功,没有加载运行时,依然具有地址。
可实行程序编译后,会形成许多行汇编语句,每条汇编语句都有它的地址,即假造地址。
进程创建阶段,会初始化自己的地址空间,让CPU知道main函数的入口地址。
pcb创建需要从elf和加载器拿假造地址和mian函数入口地址,pc指针先存着入口地址等背面OS根据页表运行;把可实行程序加载入物理内存,得到物理地址,同时在假造地址空间拥有假造地址,所以这时候就有假造地址和物理地址,根据这两个地址就可以构造页表印射关系,OS运行找pc指针印射物理地址实行代码,pc指针趁便存下一跳地址,然后OS和pc指针继续这种利用,代码就运行起来了。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |