Linux基础IO —— 深入理解文件系统 | 软硬链接

Posted 张小姐的猫(考研停更)

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux基础IO —— 深入理解文件系统 | 软硬链接相关的知识,希望对你有一定的参考价值。

🌈欢迎来到Linux专栏~~ 深入理解文件系统


  • (꒪ꇴ꒪(꒪ꇴ꒪ )🐣,我是Scort
  • 目前状态:大三非科班啃C++中
  • 🌍博客主页:张小姐的猫~江湖背景
  • 快上车🚘,握好方向盘跟我有一起打天下嘞!
  • 送给自己的一句鸡汤🤔:
  • 🔥真正的大师永远怀着一颗学徒的心
  • 作者水平很有限,如果发现错误,可在评论区指正,感谢🙏
  • 🎉🎉欢迎持续关注!

深入理解文件系统

一. 答疑解惑

🌈close关闭fd之后文件内部没有数据

代码展示:

#include<stdio.h>                                                           
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
 
int main()

	close(1);
	int fd  = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
	if(fd < 0)
	
	   perror("open");
	   return 0;
	

	printf("hello world: %d\\n", fd);//stdout -> 1
                                                                                            
 	close(fd);
	return 0;
 


发现运行和打印都没有打印出结果,而当我们fflush的时候才在文件中打印出来


看了上两篇博客的都应该游刃有余了

因为close(1),而printf()只向1中打印,此时的fd = 1。此时数据会暂存在stdout的缓冲区中,此时普通文件是全缓冲,遇到\\n不会及时刷新。对应 的fd先关闭, 数据无法及时刷新了,而加了fflush会把数据直接刷新

🌈stdout 和 stderr 区别

#include<iostream>                                                                           
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>

 int main()
 
    //stdout -> 1
    printf("hello printf 1\\n");
    fprintf(stdout, "hello fprintf 1\\n");                                                        

    // stderr -> 2
    perror("hello perror 2"); //stderr

	const char *s1 = "hello write 1\\n";
	write(1, s1, strlen(s1));

    const char *s2 = "hello write 2\\n";
	write(1, s2, strlen(s1));

    //cout -> 1
	std::cout << "hello cout 1" << std::endl;
	//cout -> 2
	std::cerr << "hello cerr 2" << std::endl;

	return 0

结果如下~

  • 我们发现,重定向只对1号文件描述符有用;也就是正确信息被重定向
  • 文件描述符 1 和 2 对应的都是显示器文件,但是题目两个是不同的,如同认为同一个显示器文件,被打开了两次

一般而言,如果程序运行有可能有问题的话,建议使用stderr,或者cerr来打印
如果是常规的文本内容,我们建议进行cout 或 stdout 进行打印

💦举例:常规信息和报错信息分开打印
正确的打印到ok.txt ,错误的打印到err.txt 也称错误重定向

./myfile 1 > log.txt 2 > err.txt

💦但如果我们像都打印在一个文件中呢?

./myfile > log.txt 2>&1

💢先重定向,指向log.txt;把1的内容给2拷贝一份,最后1和2指向同一个文件

结果还真的是

还有一个骚操作也可以实现

cat < log.txt > back.txt//相当于拷贝

我们一个都见过这个errno错误码)的吧

  #include <errno.h>
  
  RETURN VALUE
       open() and creat() return the new file descriptor, or -1 if an error occurred (in which
       case, errno is set appropriately).

如果打开成功,返回文件描述符,打开失败,errno被设置,所以perror会根据全局的错误码输出对应的错误原因

如果我们自己想设计一个perror

 void myperror(const char *msg)
 
 	 fprintf(stderr, "%s: %s\\n", msg, strerror(errno));
  

此前已经删除了log.txt

二 . 理解文件系统

🎨科普背景知识

🥑有没有没有被打开的文件呢? 在哪里呢?

  • 当然存在,在磁盘上

🥑我们学习磁盘级别的文件,我们的侧重点在哪?

  1. 单个文件角度 —— 这个文件在哪里?这个文件多大?这个文件的其他属性是什么?
  2. 站在系统角度 ——一共有多少个文件?各自属性在哪里?如何快速找到?还可以存储多少个文件

接下来我们来正式了解一下磁盘吧

  1. 内存 - 掉电易失存储介质
  2. 磁盘 - 永久性存储介质 还有: SSD(固态 贵!) 、U盘、flash卡、光盘、磁带

🎨了解磁盘结构

🔵物理结构

磁盘是一个外设 且是我们计算机中唯一的一个机械设备(相对而言很慢)

这个磁盘的盘片就像光盘一样,数据就在盘片上放着,只不过光盘是只读的,磁盘是可读可写的

光说有点抽象的,直接上图

盘面上要存储数据!(二进制) -> 但计算机只认识二进制 -> 二进制是两态 —> 我们想到磁铁也是两态的

  • 磁盘写入本质就是改变磁盘的正负性(磁头)

🟡磁盘的存储结构

机械硬盘的寻址的工作方式:盘片不断旋转,磁头不断摆动,定位到特定的扇区(面 — 磁道 — 扇区)
通过柱面Cylinder —— 磁头Head —— 扇区Sector 的寻址方法为CHS寻址

扇区的大小:512字节是硬件上的要求(外磁道和内磁道都是一样大小,密度不一样)

🟢磁盘的虚拟结构

类比磁带,我们可以把磁盘盘片想象成线性结构。

站在OS角度,我们就认为磁盘是线性结构,要访问某一扇区,就要定位数组下标LBA(logic block address);要写到物理磁盘上,就要把LBA地址转化成磁盘的三维地址(磁头,磁道,扇区)。这种关系类似于我们之前的虚拟地址空间和物理内存

所以:找到特定扇区的位置 ——> 找到数组特定的位置;对磁盘的管理——> 对数组的管理

🎨文件系统与inode

文件在磁盘上是如何被保存的?文件是在磁盘中的,而磁盘现在被我们想象成一个线性结构。

💜 磁盘空间很大,管理成本高。我们采用分治思维,类比管理我们的国家,我们分成好几个省份,再分成好几个市,最后轮到区。因此我们就对大磁盘 ——

1️⃣ 分区:大磁盘 → 小空间,化整为零

​ 2️⃣ 格式化:给每个分区写入文件系统

所以理论上,我能把这100G的小空间管理好,其他空间就复刻我就好啦,因为硬件都是标品,当然了不同分区也可以写入不同的文件系统

  • boot block 存在于每个分区的开头,备份文件,是与启动相关的,供启动时查找分区
  • 我们再把剩下的空间继续拆解分组,Block group 0 ,Block group 1 … 那么问题就又变成了如果我能管理好Block Group 0,就能管好1~n这些,因此研究文件系统又缩小范围了,就变成研究这一个Block Group 0

话不多说开始吧

虽然磁盘的基本单位是扇区(512字节),但是操作系统(文件系统)和磁盘进行IO的基本单位是4KB(8*512字节),4KBblock大小,所以磁盘被称为块设备

哪怕我们只想在磁盘上读取1字节,OS也必须直接读取4KB的数据
原因有两个:

  1. 512字节太小了,有可能导致多次IO,进而导致效率的降低
  2. 解耦合:如果操作系统使用和磁盘一样的大小,万一磁盘基本大小改变了,OS的源代码也要不要跟着改呢?硬件(磁盘)和软件(OS)进行解耦

逐个解释:
  1. Super Block:文件系统的属性信息,整个分区属性的属性集(每个块组都有 防止磁盘被刮伤而找不到 文件属性)
  2. Data blocks:多个4KB(扇区*8)大小的集合,保存的都是特定文件的内容
  3. inode Table:inode是一个大小为128字节的空间,保存的是对应文件的属性,该块组内,所有文件的inode空间的集合,需要标识唯一性,每一个inode块都要有一个inode编号!(一般而言,一个文件,一个inode,一个inode编号)
  4. BlockBitmap(位图):统计block的使用情况。假设有10000+个blocks,就有一一对应的比特位。其中比特位为1,代表该block被占用,否则表示可用
  5. inode Bitmap统计inode的使用情况,假设有10000+个inode,就有一一对应的比特位。其中比特位为1,代表该inode被占用,否则表示可用
  6. GDT:块组描述符,已经使用了多少,有多少个inode,已经被占用了多少个,还剩下多少个,使用了多少

💥众所周知,文件 = 文件内容 + 文件属性,其中文件内容放在Data blocks中,属性放在inode Table

inode内部保存了一个数组,保存了对应块的编号,二者关系就联系起来了
Linux中真正标识一个文件,是通过文件的inode编号,一个文件,一个inode(属性集合);一个inode也都有自己的编号。

那么要创建文件就要在inode Table申请一个未被使用的inode,填入属性;文件中还有内容,inode还用数组存储了相关联的blocks块编号,我们可以简单地理解成 ——

struct inode
	//文件的大小
	//文件的inode编号
	//其他属性
	int block[15];

⚡万一这个文件特别大呢? 怎么办?

不是所有的data block 只能存文件数据,也可以存其他块的块号
如果文件特别大,最后的block存的是其他block的块号,所以最后指向的是更多的block来存储

我们知道要找到文件:inode编号 —— 分区特定的block group —— inode —— 属性 —— 文件

🎨可是我们怎么知道inode呢?(Linux中,inode属性里面,没有文件名这样的说法

预备知识:

  • 一个目录下,是不是可以保存很多文件,但这些文件没有重复的文件名!
  • 目录也算是文件,有自己的inode(存的是目录的大小、权限、(链接数)、拥有者、所属组等)、有自己的data block(存的是文件名和inode的映射关系),因为文件名不存在于文件的inode里面

回答上面的问题:我们是依托目录文件来找到inode编号,因为目录的data block中保存了inode编号和文件名的映射关系,所以能找到文件名进而找到inode编号


💢灵魂多问:

  • 目录下创建文件:遍历inode Bitmap,位图中找0,申请一个未被使用的inode,随后便置1;向inode table中填入属性信息(权限、ACM时间)。并把这个映射关系文件名是用户输入,inode是我们从文件系统申请得到的写到当前目录的Data blocks中

  • 查看文件,根据目录inode找到与其映射的文件名 以及属性 全部打印
  • 向文件写入:遍历block Map找到若干未被使用的块儿,将该文件的inode与这些blocks建立映射关系,再向blocks中写入内容。

  • 查看文件内容cat hello.c → 查看当前目录的data Blocks数据块儿 → 找到映射关系:文件名儿对应的inode编号 → 在inode Table中找到inode → 找到对应的blocks[] → 文件内容加在进内存中,在刷新到显示屏。
  • 删除文件 :在目录的data block,用户提供的文件名,以文件名作为key值索引对应的inode,在inode Bitmap中把对应的比特位置中由1置0;再根据属性把使用的数据块儿们也在Bitmap中把它由1置0。最后在目录的data block中,删除inode和文件名的映射关系
  • 所以拷贝一个文件需要一会儿,但是删除很快(因为不需要改文件的属性inode Table和数据data Blocks,标志文件无效即可,并不用覆盖)

( 如果你在Linux系统中,不小心rm -rf误删了文件,还能恢复!前提是inode未被使用,inode和data block没有被占用最好的做法就是什么也不做!找到你在windows下删除文件到回收站,其实只不过是转移了目录,在回收站中删掉才是相当于1置0了)


还有一个问题:位图一开始是不是要被操作系统全清0,那个区域要被分成inode table和super在什么位置 ?GDT是谁写的?inode table的128kb是谁划分的?

  • 以上所有的信息都是:格式化 也就是写入文件系统(写入属性)

【面试题】:
🥗系统里还有空间,为什么创建文件老是失败:

  • 因为inode是固定的,data block也是固定的。可能存在 inode还有,data block不够了;inode没有,data block还有的情况(特别少)

说了一大圈,最终为了引出我们的大boss

三 . 软硬链接

🌏软链接

🍤建立软链接

ln -s testLink.txt soft.link

💛 删除链接,可以rm,但是更建议

unlink soft.link

🔷什么时候会使用到软连接呢?

对于一些执行路径非常深的程序,我们不用每次都输入长长的路径,我们可以通过软链接快速找到它

现在我不断回退,退到所谓工作目录上来,这时如果我还想运行test.sh就比较麻烦,那么我们就可以通过建立软链接的方式 ——

这相当于windows下创建的快捷方式


在系统路径下,也看见了大量了软链接的存在

🌏硬链接

不带s就是硬链接

ln  testLink1.txt hard.link

我们观察发现,软链接是有自己独立inode的,即软链接是一个独立文件!有自己的inode属性集也有自己的数据块儿 (保存的是它所指向文件的路径 + 文件名)

硬链接自己没有独立的inode,用的是别人的inode;根本就不是一个独立的文件,说白了就起别名,其本质:在特定目录下,添加一个文件名和inode编号的映射关系,仅此而已

我们还发现在属性中有一个数字,1-> 2 ->1,这个数字就叫做硬链接数

当我们删除一个文件的时候,并不是把这个文件inode删除,而是把inode引用计数--,当引用计数为0(没有文件名与之关联)的时候,这个文件才真正删除


🍤那么硬链接的使用场景呢?

为什么创建一个文件的时候,硬链接数是1?

  • 因为自己的文件名就和inode就是一组映射关系


那为什么dir目录的硬链接数是2呢?

  • 首先目录和自身的inode就是一组映射,第二个是目录内部的. 和inode 也是一组映射

在dir下又创建了d1目录,此处为什么dir的硬链接数变成3了?

因为d1的有..文件指向上级目录dir,所以硬链接数+1

总结:软硬连接的本质:有无独立的inode

📢写在最后

最近作业有点多,更新可能会慢一点

Linux基础IO篇

文章目录

C文件接口

  1. fopen函数

FILE* fopen(const char* path,const char* mode)
参数含义:
path:

带路径的文件名称(也可以不带路径,此时默认在当前路径下查找)

mode:

r :只读模式打开,文件流指针指向文件的起始位置
r+:可读可写方式打开,文件流指针指向文件的起始位置
w:只写模式打开,被打开文件若存在,则截断文件(清空文件内容),若不存在,创建该文件
w+:可读可写模式打开,,被打开文件若存在,则截断文件(清空文件内容),若不存在,创建该文件
a:追加写。若文件存在,文件流指针指向文件的末尾进行写。若不存在,创建文件
a+:可以读也可以 追加写。文件存在,读的位置被初始化到文件头,追加写的时候依旧是在文件末尾追加 ,文件不存在,创建该文件

返回值

成功:返回文件流指针FILE*
失败:返回NULL

对fopen的测试

  • 场景一:通过r方式打开一个文件,观察其返回值的情况

    插播:perror函数

    详解:
    errno是系统当中的一个错误码,当我们在调用一个函数发生错误的时候,系统会给errno赋值为对应的错误码,该错误码是一个整型数据
    每一个错误码都对应着一个错误信息 error msg
    perror函数的作用就是将errno中的值进行解析,解析完毕后,打印输出。

  • 场景二:将上述的打开方式更改为w,查看能否打开成功

    观察发现,当前可执行程序的路径下会多出来一个1.txt文件

  1. fwrite函数

size_t fwrite(const void* ptr , size_t size , size_t nmemb, FILE* stream)
参数

ptr:要往文件中写的内容
size:定义往文件中写的时候,一个快是多大,单位是字节(通常情况下都是1)
nmemb:期望写多少块
stream:文件流指针,指向被写入的文件

返回值

返回成功读入文件的块的个数

  1. fread函数

size_t fread(void* ptr,size_t size,size_t nmemb,FILE* stream)
参数:

ptr:从文件中读出来的数据存放在ptr中(该内存由程序员自己提供,必须合法)
size:定义从文件中读的时候,一个块是多大,单位是字节(通常定义1个字节)
nmemb:期望读多少块
stream:文件流指针(指向被读取的文件)

返回值

返回成功读入的文件块的个数

对函数fwrite&fread的测试

  1. 情景一:提前向文件1.txt中写入hello world ,然后使用w的方式打开文件并写入 I like Linux!


  2. 情景二:提前向文件1.txt中写入hello world ,然后使用a的方式向文件中写入 I like Linux!

    运行后查看1.txt的内容

  3. 情景三:使用a+的方式打开文件,将文件中的内容读取出来

    结果:

    画图解释一下原因:

    那么这种情况应该如何解决??
    很简单,就是在读之前将我们的文件流指针指向文件的首部。这样就可以避免上述情况的发生
    如何移动呢?那就是接下来的主角fseek函数的功能

  4. fseek函数

int fseek(FILE* stream,long offset,int whence)
参数

stream:文件流指针
offset:偏移量
whence:将文件流指针偏移到什么位置

SEEK_SET:文件头部
SEEK_CUR:当前文件流指针的位置
SEEK_END:文件末尾

返回值

成功 -----> 0
失败 -----> -1

我们在上述的基础上,加入fseek函数再查看结果

结果可以正常输出

注意:为什么读在每次读一块的时候,期望读到的块数是数组长度-1呢?
解释:
从文件中期望读多少字节,会有三种情况:
情况一:文件字节数量和期望字节数量一致
情况二:文件字节数量 大于 期望读的字节数量
情况三:文件字节数量 小于 期望读的字节数量

我们仔细分析前两种情况,如果我们提供的是数组的长度,而没有对其-1,那么在这两种情况下,数组会被存满并且数组的最后一个位置的\\0也不存在了,而是被文件中的某个字符占据了,这样,我们在使用从文件中读取出来的内容的时候,也就会出错。

所以,对数组长度-1的目的就是在字符数组当中预留\\0的位置,防止在后续访问的时候越界访问,导致程序崩溃。
6. fclose函数

int fclose(FILE* stream)
参数

要关闭的文件流指针

返回值

成功: 0
失败:EOF

系统调用文件接口

  1. open

int open(const char* pathname,int flags)

int open(const char* pathname , int flags, mode_t mode)
参数

pathname:要打开或创建的目标文件
flags:打开文件时,可以传入多个参数选项,用下面的一个或者多个进行或运算,构成flags

必须指定一个且只能指定一个的常量

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

其他常量

O_CREAT:若文件不存在,则创建文件,需要使用mode选项来指明新文件的访问权限
O_APPEND:追加写

mode:当创建一个新文件的时候,指定新创建文件的权限,传递一个8进制的数字(e.g:0664)

返回值

成功:新打开的文件描述符,文件描述符又可以称为文件操作句柄,文件句柄
失败:-1

  1. write

ssize_t write(int fd , const void* buf , size_t count)
参数

fd:文件描述符
buf:将buf指向的内容写到文件中去
count:期望写多少个字节

返回值

返回写入的字节数量

  1. read

ssize_t read(int fd , void* buf,size_t count)
参数

fd:文件描述符
buf:将文件中读到的内容写到buf所指向的空间中去
count:期望读多少字节

返回值

返回读到的字节数量

  1. lseek

off_t lseek(int fd , off_t offset , int whence)


5. close

int close(int fd)
关闭文件描述符

综合测试:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

int main()

    int fd = open("1.txt",O_RDWR|O_CREAT,0664);
    if(fd < 0)
    
        perror("open");
        return 0;
    

    const char* str = "I like Linux!";
    write(fd,str,strlen(str));

    lseek(fd,0,SEEK_SET);

    char buf[1024] = 0;
    read(fd,buf,sizeof(buf)-1);

    printf("%s\\n",buf);



文件描述符

是什么

是一个小正数,没有负数。
可以在/proc/进程号/fd文件夹下查看,对应的就是它的文件描述符

验证:

  1. 打印观察文件描述符的值
  2. 查看/proc/[pid]/fd下的文件描述符信息

分配规则

最小未使用
通过代码验证一下:
验证思路:我们知道标准输出对应1号文件描述符,我们先将标准输出关闭,然后再通过open函数打开一个文件。查看该文件的文件描述符

内核角度理解

从task_struct的角度理解文件描述符在内核当中是什么

  1. struct task_struct :PCB进程控制块
  2. 在 struct task_struct结构体中有一个成员变量是一个结构体指针files,该指针指向的是一个文件描述信息的结构体
  3. 我们本次研究的主要对象就是files指向的那个结构体,所以,我们先找到该结构体的定义处
  4. 好的,到这一步是时候综合理解一下了。我们先通过画图的方式进行描述

    总结:文件描述符在内核当中本质上就是task_struct结构体中的一个结构体指针files所指向的结构体struct files_struct中的结构体指针数组fd_array的数组下标

扩展

一个进程最多可以打开多少个文件描述符?

解答:

  1. 系统当中针对一个进程打开的文件描述符的数量是有限的
  2. 通过ulimit -a可以查看“open files”的大小,而“open files”的大小就是限制一个进程可以打开的文件描述符的数量
  3. 我的机器中看到的“open files”的大小是1024,也就是意味着我的机器创建出来的进程最大可以打开的文件数量是1024
  4. open fiels”的大小并不是没有办法改变。可以通过 ulinit -n [数字] 进行改变
    e.g:ulimit -n 10000 就是将一个进程打开文件描述符的数量改为了10000

理解文件描述符和文件流指针的区别

  1. 从源码的角度理解,文件流指针(struct _IO_FILE)是什么
  • 找到对应的源代码 /usr/include/stdio.h
  • 我们发现FILE原来只是一个被重命名后的名称,真正的结构体是struct _IO_FILE,我们转到定义处发现它属于该路径下的libio.h

    struct _IO_FILE的应用

    图示解析:
  1. 将文件流指针和文件描述符联系起来
    2.1读写缓冲区问题
    struct _IO_FILE…这个结构体是C标准库当中的结构体,而该结构体当中维护的读写缓冲区就是进程终止部分提到的exit函数处理的那个缓冲区。
    2.2 文件描述
    文件流指针对应的结构体struct _IO_FILE 这个结构体内部的成员变量 int _fileno保存了对应的文件描述符的数值
    代码验证:
    前提:使用fopen打开一个文件1.txt,输出FILE结构体内的成员变量 fileno ,与/proc/[pid]/fd下的文件描述符对比
#include<stdio.h>
#include<unistd.h>



int main()

    FILE* fp = fopen("1.txt","w+");
    if(NULL == fp)
    
        perror("fopen");
        return 0;
    
    printf("_fileno is %d\\n",fp->_fileno);

    while(1)
    
        sleep(1);
    
    return 0;


图示解析:

重定向

符号

  1. >
    清空重定向
  2. >>
    追加重定向

接口

int dup2(int oldfd , int newfd);
作用:将newfd的值重定向为oldfd,也即newfd拷贝oldfd
参数:两个参数均是文件描述符
返回值

成功(做两件事情)

1、关闭newfd
2、让newfd指向oldfd对应的 struct file*

失败(两种情况)

1、如果oldfd是一个非法或无效的文件描述符,则重定向失败,newfd没有变化
2、如果oldfd和newfd的值相等,则什么也不干

内核角度理解重定向

图示理解

重定向的代码验证

重定向标准输出到某一个文件当中

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main()

    int fd = open("1.txt",O_RDWR|O_CREAT,0664);
    if(fd < 0)
    
        perror("open:");
        return 0;
    

    dup2(fd,1);

    printf("kkkkkkk!\\n");
    return 0;


动态库 && 静态库

库的概念
静态库&&动态库都是程序代码(二进制文件)的集合。一般为了方便将程序提供给第三方使用,就是将程序编写成为库文件提供给第三方(用户)使用。

动态库

  1. 特征
    Windows系统下:没有前缀,后缀为dll
    Linux系统下:前缀为lib,后缀为.so
  2. 生成动态库
    生成动态库的代码当中不需要包含main函数(程序入口函数)
    使用gcc/g++ 编译器(注:需要在普通的编译命令下增加两个命令行参数)

参数1:-fPIC 产生位置无关码
参数2:-shared 生成共享库格式

  1. 场景模拟

生成动态库 && 使用动态库

  1. 编写生成动态库所需要的文件(这里重点在于测试动态库,编写的文件比较简单,具有说明性即可)

  2. 编写Makefile文件,用来生成动态库

  3. 创建用户文件夹,在用户的程序中使用动态库文件

  4. 编写main.c对应的Makefile文件

  5. make后,运行生成的可执行程序,查看运行结果
    出错了!!!
    分析出错原因:

    本质上的原因就是程序在运行的时候无法找到动态库

  6. 下面我们就介绍一下如何找到动态库

  7. 让程序找到动态库的三种方式
    4.1将动态库放到可执行程序的路径下(可以解决问题,但是不推荐)

    4.2配置 LD_LIBRARY_PATH环境变量
    LD_LIBRARY_PATH:一个库文件的环境变量,在~/.bash_profile或者 ~/.bashrc文件中


    下面再次运行test_so

    4.3放到系统库的路径下:/lib64
    极力不推荐,不要将系统的库文件与用户使用的糅合在一块
    不必尝试,极力不推荐!!

静态库

  1. 特征
    Windows系统:没有前缀,后缀为.lib
    Linux系统:前缀为lib,后缀为.a
  2. 生成静态库文件(两个阶段)
    2.1 第一阶段
    使用gcc/g++将源码编译成为目标程序(.o文件)

    2.2 第二阶段
    使用 ar -rc 命令编译目标程序为静态库
    ar -rc [静态库文件名称] [目标程序]


    2.3 注意事项

    3 . 场景模拟
    创建user文件夹使用该静态库,观察结果


    程序正常运行!!

简单的文件系统

  1. ls -l 命令可以列出当前路径下的所有文件&&文件夹的详细信息

    既然文件有这么多的信息,我们知道所有的信息都要存储。
    文件的内容势必会被存放在磁盘当中
    描述文件属性信息的内容也是需要保存在磁盘当中
  2. Linux ext2文件系统
    2.1图示

    详述每一个模块:
  • 超级块(Super Block)
    存放文件系统本身的结构信息。记录的主要信息有:block 和 inode的总量,未使用的block 和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间、最后一次检查磁盘的时间等其他文件系统的信息。
    super Block的信息被破坏,可以说整个文件系统结构就被破坏了
  • GDT,块组描述符
    描述块组属性信息
  • 块位图(Block Bitmap)
    记录Data block中哪个数据块已经被占用,哪个数据块没有被占用
  • 存储文件内容(Data blocks)
    画图理解块位图与Data blocks之间的关系
  • inode位图(inode Bitmap)
    每个bit表示一个inode是否空闲可用
  • i节点表(inode table)
    存放文件属性,如文件大小、所有者、最近修改时间等
    2.2 创建一个新文件主要的4个操作
    我们以下图为例进行分析

    步骤1:存储属性
    inode节点号查看:ll -i 命令列表中第一个就是inode节点号
    先找到一个空闲的 i 节点(265458)。内核将文件信息记录到其中
    步骤2:存储数据
    该文件需要存储到3个磁盘块,内核找到了3个空闲块(300,500,800),将内核缓冲区的第一块数据复制到300,下一块复制到500,以此类推
    步骤3:记录分配情况
    文件内容按顺序300 500 800 存放,内核在inode上的磁盘分布区记录了上述块列表。
    步骤4:添加文件名到目录
    新的文件名ABC,内核将入口(265458,ABC)添加到目录文件。文件名和inode之间的对应关系将文件名和文件的内容及属性连接起来。

软链接 & 硬链接

软链接

  1. 是什么
    目标文件的快捷方式

  2. 如何生成软链接文件
    使用命令 ln -s 源文件 软链接文件

  3. 使用的注意事项
    3.1修改软链接文件,源文件也会被修改,修改源文件,软链接文件也会被改变
    3.2源文件如果被删除,软链接文件还在的,修改软链接文件。会重新建立源文件,重新建立链接关系(这种情况慎重考虑,如果之前的源文件在程序中作用很大,删除后,通过这种方式新生成的源文件与原来的已经不一样了。可能会导致程序崩溃)
    建议:在删除源文件的时候,将软链接文件也一并删除掉,以防后患

  4. 结合文件系统理解
    4.1软链接文件和源文件是拥有不一样的节点号的

硬链接

  1. 是什么
    目标文件的替身
  2. 生成方式
    使用命令 ln 源文件 硬链接文件
  3. 结合文件系统理解
    3.1源文件与硬链接文件的inode节点号相同

    3.2多个文件引用同一个inode节点的时候,inode节点内部的引用计数会++
    当文件删除的时候,引用计数会–,直到引用计数减为0的时候,才会释放inode节点
    模拟:将源文件test.c删掉,查看硬链接文件的情况


以上就是基础IO篇的相关总结,感觉对自己有帮助的老板们还请一键三连~~感谢感谢

以上是关于Linux基础IO —— 深入理解文件系统 | 软硬链接的主要内容,如果未能解决你的问题,请参考以下文章

Linux深入理解重定向inode详解与软硬链接的概念及区别

Linux深入理解重定向inode详解与软硬链接的概念及区别

Linux入门基础IO

Linux入门基础IO

深入理解Linux文件系统与日志分析

理解Linux—inode