Linux系统——基础IO

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

要努力但不要着急繁花锦簇硕果累累都需要过程

目录

1.文件基础必备概念

2.文件系统调用接口

1.open && close

2.write

3.read

3.文件描述符fd

3.1什么是文件描述符

3.2文件描述符意义

3.3文件描述符的分配规则

3.4文件描述符的重定向

4.如何理解Linux下一切皆文件

5.理解缓冲区

6.文件系统

7.软硬链接

7.1理解硬链接

7.2理解软链接 

8.动态库和静态库

8.1动静态库的概念

8.2库的本质

8.3制作静态库

8.4制作动态库

 8.5动静态库加载

1.文件基础必备概念

1.空文件也要占据磁盘空间

2.文件是由文件内容和文件属性组成的

3.文件操作等于对文件内容进行操作或者对文件属性操作或者对文件内容和属性同时进行操作

4.标定一个文件必须是文件路径加文件名

2.文件系统调用接口

        首先我们需要明白文件存在在磁盘中而磁盘又属于硬件要想访问硬件只能通过操作系统而操作系统又为了保护自己所以给用户提供了一系列的操作文件的接口用户可以通过这些接口直接访问文件了下面我们一起来学习一下关于文件对应的系统调用接口

1.open && close

#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);
int open(const char *pathname, int flags);
对已经存在的文件进行操作
int open(const char *pathname, int flags, mode_t mode);

对没有存在的文件进行操作

pathname标定一个文件如果没有指明路径默认在当前路径下创建文件

flags:打开文件时可以传入多个选项

参数O_RDONLY :只读打开O_WRONLY:只写打开O_RDWR读写打开

O_CREAT:若文件不存在则创建文件

O_APPEND:追加写

mode:指明创建文件的权限

打开失败返回-1

close:关闭文件

2.write

3.read

 

3.文件描述符fd

3.1什么是文件描述符

open打开一个文件打开成功会返回一个文件标识符保存到fd中那文件标识符fd到底是什么呢

 

通过代码运行可以观察出文件标识符是一段连续的整数但又有一个问题就是为什么是从3开始的呢 下面我们来介绍一下stdin,stdout,stderr

标准输入键盘标准输出显示器标准错误显示器

stdin,stdout,stderr是三个结构体指针该结构体中保存了相关文件的属性包含fd

注以上三个在程序启动的时候默认是被打开的

 

3.2文件描述符意义

上面我们已经看到什么是文件描述符fd,下面我们继续研究一下为什么文件描述是这样的呢

这其实和底层操作系统管理的结构决定的下面用一组图例说明它们之间的关系是如何样的

通过图例可以理解当文件被加载到内存之后操作系统为了方便管理会给每个文件创建一个结构体然后结构体中保存文件的相关属性另外还有一个结构体该结构体中有一个指针数组数组中的每个元素指向每个文件而元素保存的下标就被当作文件标识符在调用完成之后被返回了因为在使用fd的时候也能通过下标找到对应的文件所以fd是一段连续的整数

3.3文件描述符的分配规则

 

通过上面两张图片的对比可以发现文件描述符的规则是从未被占用下标最小的开始分配

3.4文件描述符的重定向

 

当关闭标准输出之后运行程序发现打印的内容不见了这是为什么呢打印的内容去哪里了呢

此时查看log.txt的文件的时候发现打印到显示器上的内容在文件当中这是为什么呢

未关闭标准输出

关闭标准输出

关闭标准输出之后open打开文件1就分配给了log.txt的fd,然后在底层中就像图中画的指向了log.txt,因为1代表的是标准输出而printf函数就是向标准输出中打印内容所以打印的内容就到了log.txt中了而这种方式就被称为文件描述符的重定向

重定向的本质是上层用的fd不变在内核中更改fd对应的struct file*的地址

在Linux操作系统中专门提供了一个函数调用接口来完成重定向dup2

 

重定向包含:

>:输出重定向

>>:追加重定向

将O_TRUNC改成O_APPEND

<:输入重定向

4.如何理解Linux下一切皆文件

        操作系统在管理硬件的时候不是直接进行管理的而是每个硬件都有自己对应的结构体每个结构体当中包含了各自硬件的属性信息所以操作系统对硬件的管理就变成了对每个结构体对象的管理所以在操作系统看来对普通文件的管理方式和对硬件的管理方式相同所以站在操作系统的角度来看Linux下一切皆文件

5.理解缓冲区

5.1什么是缓冲区

        缓冲区本质上是一块内存当进程想要把数据写入到外设文件中时进程就会向内存申请一块空间将数据放到缓冲区当中。

5.2缓冲区的意义

        进程向外设文件中写入数据的时候因为访问外设速度比较慢所以缓冲区的存在节省了进程进行IO的时间

5.3缓冲区刷新策略

        进程将数据拷贝到缓冲区当中那什么时候缓冲区的数据会将内容写入到文件中呢其实从缓冲区向文件中写入数据并不是单一的按时将缓冲区的内容写入到文件中因为访问外设速度比较慢大概90%的时间是缓冲区在等待文件准备好只有10%的时间是将缓冲区的数据写入到文件中所以操作系统为了将数据以更高效率的写入到文件中定制了不同的缓冲区刷新策略。

缓冲区刷新策略包括

1.无缓冲——立即刷新

2.行缓冲——按行进行刷新——例如将数据写入到显示器当中

3.全缓冲——缓冲区满了之后进行刷新——例如向磁盘文件中写入数据

关于以上三种缓冲区刷新策略存在两种特例

1.用户强制刷新——例如调用fflush函数进行强制刷新

2.当进程退出的时候缓冲区会自动刷新

5.4缓冲区存在的位置

下面先来看一段现象

        这段代码首先调用了C语言的三个接口向显示器输出内容然后调用了系统调用接口write向显示器输出内容然后fork创建子进程。

程序的运行结果

       

  向显示器输出了四条内容和我们的预料是相符的然后我们将这四条内容重定向到一个磁盘文件log.txt

        此时查看log.txt中的内容的时候发现C接口向显示器输出的内容重复出现了两次而系统调用接口向显示器输出的内容只出现了一次这是我们发现与我们的预料是不相符的这是为什么呢

关于上面这个现象是与我们的缓冲区是有关的而且此时我们还可以得出一个结论这个缓冲区存在的位置是在用户层中的因为C接口函数的内容重复出现了两次而C接口的函数存在用户层。

        进一步我们可以明确缓冲区存在的位置在stdin,stdout,stderr中而这三个又是属于FILE* 类型的而FILE又是一种结构体的封装而这个结构体当中就包含缓冲区存在的位置所以最终得出一个结论缓冲区存在在用户层并且在FILE中

明确了这些之后我们也可以对上面代码出现的现象做出解释

        首先如果没有进行重定向stdout默认使用的是行缓冲区刷新策略在进程fork之前已经将内容输出到显示器当中了此时进程内部不存在对应的数据了如果进行了重定向写入文件就不再是显示器而是普通文件向普通文件中写入数据时缓冲区的刷新策略是采用全缓冲 调用C接口的函数将内容拷贝到缓冲区不足以将缓冲区填满所以数据并没有进行刷新执行fork函数的时候创建子进程子进程创建完成之后子进程退出前面我们提到缓冲区的刷新存在两个特例其中有一个就是进程退出的时候会将缓冲区的内容进行刷新又因为进程具有独立性子进程退出不能影响父进程所以子进程退出时刷新缓冲区中的内容本质就是要对缓冲区进行修改所以针对上面的原因子进程在刷新缓冲区的内容的时候就会发生写实拷贝所以C接口输出的内容就会出现两份分别是子进程和父进程向文件中刷新缓冲区的内容而write没有FILE,也就没有C提供的缓冲区所以只出现了一次

5.5缓冲区和操作系统的关系

        以上所说的缓冲区是指在用户层中的当我们将缓冲区的数据刷新到文件中时并没有直接将内容写入到对应的外设文件中而是将内容拷贝到操作系统内部对应的内核缓冲区当中然后最后由操作系统将内核缓冲区数据写入到文件中而如何将内核缓冲区的数据如何写入到文件中这是由操作系统决定的

        对于上面将数据写入到文件中最后是由操作系统来完成的所以存在一个潜在的问题如果在将数据写入到文件的时候操作系统崩溃了就会造成数据丢失的问题针对这个问题有一个调用接口fsync这个函数是专门将内核缓冲区的数据同步写入到文件中此时就解决了数据可能丢失的问题!

 

6.文件系统

        上面关于进程和被打开文件的关系我们理解清除了下面我们继续来看一下关于未被打开的文件操作系统又是如何管理的呢

        首先我们需要明白文件是存储在磁盘上的所以我们想真正理解操作系统是如何管理磁盘上的文件我们首先需要理解磁盘理解磁盘的硬件结构磁盘的存储结构以及磁盘的逻辑结构

磁盘的硬件结构

         通过图片可以看到磁盘是由许多的盘片组成一个盘片有两个盘面两个盘面上都可以进行数据的读写而每个盘面上都有一个磁头来回摆动磁头和盘面是不互相接触的中间包含马达带动盘片进行高速旋转

磁盘的物理存储结构

         了解了磁盘的硬件结构后我们知道了磁盘存储数据是在盘片的盘面上那具体是如何存储的呢这就需要我们进一步了解磁盘的物理存储结构了观察上面的图片我们可以看到一个磁盘是由多个盘片组成的一个盘片有两个盘面每个盘面上又划分了一个个的磁道每个磁道又被划分为一个一个的扇区数据就是存储在一个一个的扇区上的每个扇区上存储数据的大小是相同的都是512byte.

        现在我们已经知道了在磁盘上具体是如何存储数据的之后那如果是在单面上我们应该如何找到对应文件存储的扇区呢盘片在高速旋转磁头在来回摆动的过程中先确认是在哪一个磁道上的然后再定位到对应的扇区上

        上面所说的是在单面的一个盘面上定位扇区那如何在磁盘上定位对应的扇区呢因为一个磁盘是由多个盘片组成的。在磁盘上定位一个扇区其实和在单面盘上定位扇区本质上是相同的因为在磁盘定位扇区还是由磁头进行完成的前面我们已经知道每个盘面上都一个磁头盘片在高速旋转的过程中磁头同时摆动每个磁头摆动的频率都是相同的所以定位扇区首先要定位在哪一个磁道上然后再定位在哪一个盘面上最后在定位在哪一个扇区上。而我们把这种定位方式就称为是CHS定位法

磁盘的逻辑结构

        上面我们已经认识了磁盘的硬件结构和磁盘的物理存储结构那磁盘的逻辑结构又是什么呢磁盘的逻辑结构通俗的讲就是将磁盘的一个个盘片由原型结构扯开变成线性结构把这种磁盘的每一个盘片想象成为线性结构就称为是磁盘的逻辑结构。

        那为什么要有磁盘的逻辑结构呢其实抽象成这种逻辑结构有两个原因一个是是方便操作系统进行管理的第二个原因是不想让操作系统的代码和硬件强耦合因为抽象成线性结构之后可以将一个个对应的扇区划分为一个个的区域就像是在一个数组中对应的一个个元素此时操作系统就将对磁盘硬件的管理转变为对数组的管理了。现在我们要找一个扇区就只需要知道扇区对应的下标就可以了在操作系统内部下标就是对应的一个地址把这种地址就称为是LBA地址

         知道LBA地址之后我们在逻辑上定位了扇区但是最终操作系统还是要访问磁盘的既然要找在磁盘对应扇区的位置那我们必然就要用到CHS定位法所以接下来的问题就是如何通过LBA地址转换为对应的CHS定位呢下面来举一个对应的例子来进行一下转换

假设

一个磁盘有4个盘面每个盘面有10个磁道每个磁道上有100个扇区每个扇区是512byte

所以可以计算出总容量=4*10*100*512 

下标范围=4*10*100

假设现在在数组中定位到扇区在对应的125号位置因为每个盘面有1000个扇区所以可以计算出在哪个盘面上125 /1000 = 0 说明就在0号盘面上因为每个盘面上有10个磁道所以可以计算出在哪个磁道上125/100 =1说明在1号磁道上因为在每个磁道上有100个扇区所以可以计算出在哪个扇区上125%100=25说明在25号扇区上。

通过以上这种计算方式就可以通过LBA地址转换为CHS定位进而找到磁盘上对应的扇区

         上面我们已经了解了磁盘的三种结构并且知道了磁盘被划分为一个个的扇区结构每个扇区大小为512byte操作系统在寻址的时候是以一个扇区为基本单位的但是以一个扇区为基本单位进行寻址的时候依旧很小假如说要访问一个4kb的数据就需要进行8次IO的过程而我们也知道每一IO都是一次访问外设的过程而访问外设是比较慢的所以操作系统为提高效率在读取数据的时候一般是以4kb为基本单位进行访问数据的即使你只访问1个bit位的数据也会将4kb的数据加载到内存进行读取或者修改而这种方式我们称为局部性原理本质上是以空间换时间的做法

        有了上面的这些基础知识之后下面我们来正式看一下操作系统对于每个区域具体是如何进行管理的

        假设你的磁盘有512GB,操作系统直接进行管理不太方便所以操作系统将512GB空间大小进行分块和分组的方式将一块大的空间划分为一块块的小空间所以只需要将其中的一块小空间管理好之后其它空间按照同样的方法进行管理这样就可以实现管理一块小的空间实现对整个磁盘进行管理

下面这张图片就是操作系统对磁盘进行分区和分组后的具体划分

 上面的一行就是被划分为一个块区下面一行就是对每个分区进行分组后的结果下面我们就来一一解释一下分组中的每个区域保存的信息

Boot Block:保存的计算机在启动的时候所需要加载到操作系统内部的数据

Block group 0~n:就是被操作系统划分的一个个分区区域

Super Block:保存的是整个文件系统的信息关于这个区域你可能会有疑问既然保存的是整个文件系统的信息那为什么不放在分区的位置而保存在分组的位置呢这样做的目的是为了做备份假如说文件系统因为某些原因而出现了故障此时就可以用其它块区的Super Block进行恢复

之前我们已经知道文件是由文件属性和文件内容组成的

Data blocks保存的是分组内部所有的数据块

Linux文件属性是分批存储的保存在Inode当中Inode一般是128byte或者是256byte,一个文件一个Inode,文件几乎所有的属性都保存在Inode中文件名并不再Inode中存储Inode为了区分彼此每一个Inode都有一个自己的Id

inode table:保存了分组内部所有可用的inode(已经使用的没有使用的)

inode Bitmap:保存的inode的位图结构如果inode被使用了对应的bit位由1表示没有被使用就用0表示

Block Bitmap:保存的是数据块的位图结构与data block数据块的位置是一一对应的如果被使用了对应的bit位由1表示没有被使用就用0表示

Group Descriptor Table:保存的是inode使用了多少没有被使用了多少data block使用了多少还有多少没有被使用的信息

上面我们已经介绍每个区域保存的内容了下面如果想要查找一个文件该如何找呢

答案是查找一个文件使用对应的Inode编号因为可以先在Inode Bitmap确认是否对应的bit为1然后再Inode Table中提取对应的属性信息。现在可以拿到一个文件的属性信息可那对于文件内容又如何获取呢我们不能直接获取data block中的值因为在data block中保存的是一个个的分组数组块。要想回答这个问题我们就必须要了解一下Inode了。

Inode是一个结构体

struct inode
{
    int id;//inode编号
    mode_t mode;//文件权限
    uid;//拥有者
    gid;//所属组
    size_t size;//个数
    ……
    int data_block[15];
}

inode中除了基本的属性信息之外还有一个data_block的数组前12个是每个下标对用一个文件

后面几个是保存的是下一个文件的地址通过建立索引的方式将所有的文件连接起来二级索引三级索引……通过这样的方式就可以找到一个文件对应的内容了

7.软硬链接

7.1理解硬链接

概念和原来的文件具有相同的inode的文件

特点建立一个硬链接根本没有创建一个新的文件因为没有分配独立的inode

硬链接和原来文件的关系 

所以硬链接的本质是在指定的路径下新增文件名和inode的映射关系

如何标识新增的映射关系呢

在inode中有一个引用计数当新增了一组映射关系之后count++,把这种引用计数也被称为硬链接数

所以一个文件正真被删除是硬链接数变为0才算该文件被正真删除了。

硬链接的作用

当创建一个普通文件的时候硬链接数为1这是因为文件名和inode只有一组映射关系那创建一个空目录为什么硬链接数是2呢 

这是因为在一个空目录中包含一个.的隐藏文件该文件的inode与目录的inode相同所以也就是建立了两组映射关系所以硬链接数为2. 

在empty中在创建一个dir目录

此时硬链接数又变为3了这是为什么呢

这是因为在empty中又创建了一个目录这个目录中有一个..的隐藏文件和empty的inode相同所以有三组映射关系硬链接数为3.所以cd..可以返回到上级

所以硬链接存在可以回退到上一级。

7.2理解软链接 

概念具有独立的inode的文件

 软链接的本质是一个新的文件软链接的data blocks中保存原来文件的文件路径和文件名

因此在Linux上建立软链接就类似于windows上的快捷方式

建立软链接的作用

 

 

 

当我们在一段比较深的路径下写好一段程序如果不在当前进程所在的路径时想要运行就必须带上一串路径如果此时建立软链接就可以直接不用带路径了。

8.动态库和静态库

8.1动静态库的概念

静态库以.a为结尾程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库。

动态库以.so结尾程序在运行的时候才去链接动态库的代码多个程序共享使用库的代码。

~一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表而不是外部函数所在目标文件的整个机器码。
~在可执行文件开始运行以前外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中这个过程称为动态链接dynamic linking。
~动态库可以在多个程序间共享所以动态链接使得可执行文件更小节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用节省了内存和磁盘空间。

8.2库的本质

我们将写的代码生成可执行二进制程序需要经历预处理编译汇编链接四个阶段当程序中调用库中包含的内容时如果不想提供源代码就可以将库中的.o文件(可重定位二进制目标文件)提供给使用者然后使用者将.o文件链接起来就可以生成可执行程序了。

上面这种方式就是库实现的方式但是这种方式存在的问题是如果存在许多.c文件就需要提供多个.o文件这样就是使得成本变高所以一般会将所有的".o文件"打一个包给对方提供一个库文件。

所以库文件的本质是.o文件的集合

8.3制作静态库

1.将多个.o文件打包成一个库文件

 

 2.使用库的本质是将库的实现方式和实现方法下载到自己的本地上然后在使用的时候在本地找对应的内容所以上面我们已经将库生成了如果要想让别人使用就需要下载我们制作的静态库所以为了让别人方便下载我们将实现方式和实现方法放在相应的目录中

 

3.此时我们就将实现方式和实现方法都放在同一个目录中然后可以将这个目录文件打包压缩放在yum源中使用者在使用下载对应的库使用的时候就可以正常使用了

 4.使用者下载完成后进行使用

 

此时在使用的时候出现报错说.h文件找不到这是因为编译器在编译的时候.h文件首先会在当前路径下找如果找不到就会在下载安装的库文件找找不到就报错我们已经下载完成了为什么还会报错呢这是因为我们的.h文件在多级路径下不再当前路径下并且我们并没有再库文件下安装所以解决方式是指定路径下查找

 当指明路径之后程序再编译阶段没有报错在链接阶段出现了链接错误这个是因为程序在链接阶段找不到对应的库所以也需要指明库路径

注意在指明库的时候不仅需要指明库路径还需要指明库名称库名称指明需要去除库的前缀名lib和后缀名.a

 当程序运行完成之后查看程序链接属性发现是动态链接的但是我们提供的是静态库为什么会是动态链接的呢这个gcc编译器的实现是有关系的gcc生成可执行程序默认是动态链接的对于一个特定的库是否是动态链接取决与库本身只有静态库时属于静态链接同时包含动态库和静态库时链接属性为动态链接

除了上述按照指定路径和指定名字的方式实现之外还可以将所使用的库安装到系统默认的库路径下此时执行程序就不用在指明指定的路径了gcc编译器默认就会去找

 安装的本质就是拷贝

8.4制作动态库

1.生成.o文件在生成.o文件的时候多加一个-fPIC的选项

 2.将.o文件打包形成动态库

 3.使用动态库

和静态库一样的使用一样将动态库和.h文件放到指定的路径下

 

 4.利用动态库生成可执行程序

当运行的时候发现报错了我们已经将库和对应的头文件都提供了但是为什么还会报错呢

这是因为我们将动态库和对应的头文件告诉了gcc而gcc在编译完成之后在运行的时候程序加载到内存中被操作系统调度运行但是动态库并没有放到系统默认的指定路径下所以操作系统在找的时候找不到因此出现了报错

该如何解决上述问题呢操作系统除了在系统默认的路径下查找之外还会在下面的这个环境变量中查找我们只需要将库路径放到该环境变量中就可以了

 

 注将库路径导入到环境变量中的方法只在当前登录有效不能永久使用。

解决方法

第一种方法

1.修改系统配置文件将库路径放入到/etc/ld.so.conf.d/路径下的一个.conf文件中

 

 此时就可以永久使用了

 第二种方法

在运行的时候库文件会在当前路径下查找因此可以建立软链接的方式

 8.5动静态库加载

1.静态库的加载

采用绝对编址的方式当代码在磁盘上编译的时候静态库中使用的代码和自己本身写的代码会按照不同地址区域划分保存在对应的位置然后当程序运行的时候加载到内存中会将原来磁盘上代码的地址映射到虚拟地址空间然后让PCB调度进程

2.动态库的加载

采用相对编址的方式当代码在磁盘上编译的时候自己本身的代码会安宅不同地址区域划分保存在对应的位置遇到动态库的代码的时候会将对应的代码按照代码本身加偏移量的方式保存在相应的位置偏移量是指调用动态库的接口在库中的位置然后当程序加载到内存之后将地址映射到虚拟地址空间最后将调用的动态库映射到共享区中然后使用动态库接口的函数按照原来保存的偏移量在上下文中寻找然后让PCB调度进程

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: linux

“Linux系统——基础IO” 的相关文章