Linux之Ext系列文件系统(含动静态库)

目录​​​​​​​

一、理解硬件

1.1、磁盘、服务器、机柜、机房

1.2、磁盘的物理结构

1.3、磁盘的存储结构

1.4、磁盘的逻辑结构

1.4.4、理解过程

1.4.2、真实过程

[1.5、CHS && LBA地址](#1.5、CHS && LBA地址)

二、文件系统

2.1、"块"概念

2.2、"分区"概念

2.3、"inode"概念

[三、ext2 ⽂件系统](#三、ext2 ⽂件系统)

3.1、宏观认识

[3.2、Block Group](#3.2、Block Group)

3.3、块组内部构成

[3.3.1、超级块(Super Block)](#3.3.1、超级块(Super Block))

[3.3.2、GDT(Group Descriptor Table)](#3.3.2、GDT(Group Descriptor Table))

[3.3.3、块位图(Block Bitmap)](#3.3.3、块位图(Block Bitmap))

[3.3.4、inode位图(Inode Bitmap)](#3.3.4、inode位图(Inode Bitmap))

[3.3.5、i节点表(Inode Table)](#3.3.5、i节点表(Inode Table))

[3.3.6、Data Block](#3.3.6、Data Block)

3.4、inode和datablock映射

3.5、目录与文件名

3.6、路径解析

3.7、路径缓存

3.8、挂载分区

3.8.1、一个实验

3.8.2、结论

3.9、文件系统总结

四、软硬链接

4.1、硬链接

4.2、软链接

4.3、软硬链接对比

4.4、软硬链接用途

五、动态库和静态库

5.1、手动制作静态库

5.2、制作动态库

5.3、原理上理解动态库(共享库)


一、理解硬件

1.1、磁盘、服务器、机柜、机房

  • 机械磁盘是计算机中唯⼀的⼀个机械设备
  • 磁盘---外设
  • 容量⼤,价格便宜

1.2、磁盘的物理结构

1.3、磁盘的存储结构

扇区:是磁盘存储数据的基本单位,512字节,块设备。

如何定位⼀个扇区呢?

  1. 可以先定位磁头(header)
  2. 确定磁头要访问哪⼀个柱⾯(磁道)(cylinder)
  3. 定位⼀个扇区(sector)
  4. CHS地址定位

⽂件 = 内容+属性 都是数据,⽆⾮就是占据哪⼏个扇区的问题!能定位⼀个扇区了,能不能定位多个扇区呢?

  • 扇区是从磁盘读出和写⼊信息的最⼩单位,通常⼤⼩为 512 字节。
  • 磁头(head)数:每个盘⽚⼀般有上下两⾯,分别对应1个磁头,共2个磁头。
  • 磁道(track)数:磁道是从盘⽚外圈往内圈编号0磁道,1磁道...,靠近主轴的同⼼圆⽤于停靠磁头,不存储数据。
  • 柱⾯(cylinder)数:磁道构成柱⾯,数量上等同于磁道个数。
  • 扇区(sector)数:每个磁道都被切分成很多扇形区域,每道的扇区数量相同。
  • 圆盘(platter)数:就是盘⽚的数量。
  • 磁盘容量 = 磁头数 × 磁道(柱⾯)数 × 每道扇区数 × 每扇区字节数。

细节:传动臂上的磁头是共进退的(这点⽐较重要)

柱⾯(cylinder),磁头(head),扇区(sector),显然可以定位数据了,这就是数据定位(寻址)⽅式之⼀,CHS寻址⽅式。

CHS寻址:

对早期的磁盘⾮常有效,知道⽤哪个磁头,读取哪个柱⾯上的第⼏扇区就可以读到数据了。 但是CHS模式⽀持的硬盘容量有限,因为系统⽤8bit来存储磁头地址,⽤10bit来存储柱⾯地 址,⽤6bit来存储扇区地址,⽽⼀个扇区共有512Byte,这样使⽤CHS寻址⼀块硬盘最⼤容量为256 * 1024 * 63 * 512B = 8064 MB(1MB = 1048576B)(若按1MB=1000000B来算就是 8.4GB)

1.4、磁盘的逻辑结构

1.4.4、理解过程

磁带上⾯可以存储数据,我们可以把磁带"拉直",形成线性结构。

那么磁盘本质上虽然是硬质的,但是逻辑上我们可以把磁盘想象成为卷在⼀起的磁带,那么磁盘的逻辑存储结构我们也可以类似于:

这样每⼀个扇区,就有了⼀个线性地址(其实就是数组下标),这种地址叫做LBA。

1.4.2、真实过程

⼀个细节:传动臂上的磁头是共进退的。

柱⾯是⼀个逻辑上的概念,其实就是每⼀⾯上,相同半径的磁道逻辑上构成柱⾯。所以,磁盘物理上分了很多⾯,但是在我们看来,逻辑上,磁盘整体是由"柱⾯"卷起来的。

所以,磁盘的真实情况是:

**磁道:**某⼀盘⾯的某⼀个磁道展开。即:⼀维数组

**柱面:**整个磁盘所有盘⾯的同⼀个磁道,即柱⾯展开。

柱⾯上的每个磁道,扇区个数是⼀样的。所以这就是个二维数组。

整盘:

整个磁盘不就是多张⼆维的扇区数组表(三维数组)。所有,寻址⼀个扇区:先找到哪⼀个柱⾯(Cylinder),在确定柱⾯内哪⼀个磁道(其实就是磁头位置, Head),在确定扇区(Sector),所以就有了CHS。

我们之前学过C/C++的数组,在我们看来,其实全部都是⼀维数组:

所以,每⼀个扇区都有⼀个下标,我们叫做LBA(Logical Block Address)地址,其实就是线性地址。

LBA,1000,CHS 必须要! LBA地址转成CHS地址,CHS如何转换成为LBA地址。

OS只需要使⽤LBA就可以了!!LBA地址转成CHS地址,CHS如何转换成为LBA地址。谁做呢??磁盘⾃⼰来做!固件(硬件电路,伺服系统)

1.5、CHS && LBA地址

CHS 转成 LBA:

  • 磁头数*每磁道扇区数 = 单个柱⾯的扇区总数。
  • LBA = 柱⾯号C*单个柱⾯的扇区总数 + 磁头号H*每磁道扇区数 + 扇区号S - 1。
  • 即:LBA = 柱⾯号C*(磁头数*每磁道扇区数) + 磁头号H*每磁道扇区数 + 扇区号S - 1。
  • 扇区号通常是从1开始的,⽽在LBA中,地址是从0开始的。
  • 柱⾯和磁道都是从0开始编号的。
  • 总柱⾯,磁道个数,扇区总数等信息,在磁盘内部会⾃动维护,上层开机的时候,会获取到这些参 数。

LBA转成CHS:

  • 柱⾯号C = LBA // (磁头数*每磁道扇区数) 【就是单个柱⾯的扇区总数】。
  • 磁头号H = (LBA % (磁头数*每磁道扇区数)) // 每磁道扇区数。
  • 扇区号S = (LBA % 每磁道扇区数) + 1。
  • "//": 表⽰除取整。

所以:从此往后,在磁盘使⽤者看来,根本就不关⼼CHS地址,⽽是直接使⽤LBA地址,磁盘内部⾃⼰转换。所以:从现在开始,磁盘就是⼀个元素为扇区的⼀维数组,数组的下标就是每⼀个扇区的LBA地址。OS使⽤磁盘,就可以⽤⼀个数字访问磁盘扇区了。

二、文件系统

2.1、"块"概念

其实硬盘是典型的"块"设备,操作系统读取硬盘数据的时候,其实是不会⼀个个扇区地读取,这样 效率太低,⽽是⼀次性连续读取多个扇区,即⼀次性读取⼀个"块"(block)。

硬盘的每个分区是被划分为⼀个个的"块"。⼀个"块"的⼤⼩是由格式化的时候确定的,并且不可以更改,最常⻅的是4KB,即连续⼋个扇区组成⼀个 "块"。"块"是⽂件存取的最⼩单位。

注意:

  • 磁盘就是⼀个三维数组,我们把它看待成为⼀个"⼀维数组",数组下标就是LBA,每个元素都是扇 区。
  • 每个扇区都有LBA,那么8个扇区⼀个块,每⼀个块的地址我们也能算出来。
  • 知道LBA:块号 = LBA / 8。
  • 知道块号:LAB=块号*8 + n (n是块内第⼏个扇区)。

2.2、"分区"概念

其实磁盘是可以被分成多个分区(partition)的,以Windows观点来看,你可能会有⼀块磁盘并且将它分区成C,D,E盘。那个C,D,E就是分区。分区从实质上说就是对硬盘的⼀种格式化。但是Linux的设备都是以⽂件形式存在,那是怎么分区的呢?

柱⾯是分区的最⼩单位,我们可以利⽤参考柱⾯号码的⽅式来进⾏分区,其本质就是设置每个区的起始柱⾯和结束柱⾯号码。此时我们可以将硬盘上的柱⾯(分区)进⾏平铺,将其想象成⼀个⼤的平⾯,如下图所⽰:

注意: 柱⾯⼤⼩⼀致,扇区个位⼀致,那么其实只要知道每个分区的起始和结束柱⾯号,知道每 ⼀个柱⾯多少个扇区,那么该分区多⼤,其实LBA是多少也就清楚了。

**注意:**每个区又被划分为若干个组来管理。

2.3、"inode"概念

之前我们说过 ⽂件=数据+属性 ,我们使⽤ ls -l 的时候看到的除了看到⽂件名,还能看到⽂件元数据(属性)。如图:

每⾏包含7列:

  • 模式
  • 硬链接数
  • ⽂件所有者
  • ⼤⼩
  • 最后修改时间
  • ⽂件名

ls -l读取存储在磁盘上的⽂件信息,然后显⽰出来。

到这我们要思考⼀个问题,⽂件数据都储存在"块"中,那么很显然,我们还必须找到⼀个地⽅储存 ⽂件的元信息(属性信息),⽐如⽂件的创建者、⽂件的创建⽇期、⽂件的⼤⼩等等。这种储存⽂件元信息的区域就叫做inode,中⽂译名为"索引节点"。

每⼀个⽂件都有对应的inode,⾥⾯包含了与该⽂件有关的⼀些信息。为了能解释清楚inode,我们需要是深⼊了解⼀下⽂件系统。

注意:

  • Linux下⽂件的存储是属性和内容分离存储的。
  • Linux下,保存⽂件属性的集合叫做inode,⼀个⽂件,⼀个inode,inode内有⼀个唯⼀的标识符,叫做inode号。

所以⼀个⽂件的属性inode⻓什么样⼦呢?

/* * Structure of an inode on the disk */

struct ext2_inode { __le16 i_mode; /* File mode */

__le16 i_uid; /* Low 16 bits of Owner Uid */

__le32 i_size; /* Size in bytes */

__le32 i_atime; /* Access time */

__le32 i_ctime; /* Creation time */

__le32 i_mtime; /* Modification time */

__le32 i_dtime; /* Deletion Time */

__le16 i_gid; /* Low 16 bits of Group Id */

__le16 i_links_count; /* Links count */

__le32 i_blocks; /* Blocks count */

__le32 i_flags; /* File flags */

union {

struct {

__le32 l_i_reserved1;

} linux1;

struct{

__le32 h_i_translator;

} hurd1;

struct {

__le32 m_i_reserved1;

} masix1;

} osd1; /* OS dependent 1 */

__le32 i_block[EXT2_N_BLOCKS]; /* Pointers to blocks */

__le32 i_generation; /* File version (for NFS) */

__le32 i_file_acl; /* File ACL */

__le32 i_dir_acl; /* Directory ACL */

__le32 i_faddr; /* Fragment address */

union {

struct {

__u8 l_i_frag; /* Fragment number */

__u8 l_i_fsize; /* Fragment size */

__u16 i_pad1;

__le16 l_i_uid_high; /* these 2 fields */

__le16 l_i_gid_high; /* were reserved2[0] */

__u32 l_i_reserved2;

} linux2;

struct {

__u8 h_i_frag; /* Fragment number */

__u8 h_i_fsize; /* Fragment size */

__le16 h_i_mode_high;

__le16 h_i_uid_high;

__le16 h_i_gid_high;

__le32 h_i_author;

} hurd2;

struct{

__u8 m_i_frag; /* Fragment number */

__u8 m_i_fsize; /* Fragment size */

__u16 m_pad1;

__u32 m_i_reserved2[2];

} masix2;

} osd2; /* OS dependent 2 */

};

/* * Constants relative to the data blocks */

**#**define EXT2_NDIR_BLOCKS 12

**#**define EXT2_IND_BLOCK EXT2_NDIR_BLOCKS

#define EXT2_DIND_BLOCK (EXT2_IND_BLOCK + 1)

#define EXT2_TIND_BLOCK (EXT2_DIND_BLOCK + 1)

#define EXT2_N_BLOCKS (EXT2_TIND_BLOCK + 1)

备注:EXT2_N_BLOCKS = 15

  • 注意:
  • ⽂件名属性并未纳⼊到inode数据结构内部。
  • inode的⼤⼩⼀般是128字节或者256,我们后⾯统⼀128字节。
  • 任何⽂件的内容⼤⼩可以不同,但是属性⼤⼩⼀定是相同的。

三、ext2 ⽂件系统

3.1、宏观认识

我们想要在硬盘上储⽂件,必须先把硬盘格式化为某种格式的⽂件系统,才能存储⽂件。⽂件系统的⽬的就是组织和管理硬盘中的⽂件。在 Linux 系统中,最常⻅的是 ext2 系列的⽂件系统。其早期版本为 ext2,后来⼜发展出 ext3和ext4。 ext3 和 ext4 虽然对 ext2 进⾏了增强,但是其核⼼设计并没有发⽣变化,我们仍是以较⽼的 ext2 作为演⽰对象。

ext2⽂件系统将整个分区划分成若⼲个同样⼤⼩的块组 (Block Group),如下图所⽰。只要能管理⼀个分区就能管理所有分区,也就能管理所有磁盘⽂件。

上图中启动块(Boot Block/Sector)的⼤⼩是确定的,为1KB,由PC标准规定,⽤来存储磁盘分区信息和启动信息,任何⽂件系统都不能修改启动块。启动块之后才是ext2⽂件系统的开始。

3.2、Block Group

ext2⽂件系统会根据分区的⼤⼩划分为数个Block Group。⽽每个Block Group都有着相同的结构组成。

3.3、块组内部构成

3.3.1、超级块(Super Block)

存放⽂件系统本⾝的结构信息,描述整个分区的⽂件系统信息。记录的信息主要有:bolck和inode的总量,未使⽤的block和inode的数量,⼀个block和inode的⼤⼩,最近⼀次挂载的时间,最近⼀次写⼊数据的时间,最近⼀次检验磁盘的时间等其他⽂件系统的相关信息。Super Block的信息被破坏,可以说整个⽂件系统结构就被破坏了。

超级块在每个块组的开头都有⼀份拷⻉(第⼀个块组必须有,后⾯的块组可以没有)。为了保证⽂ 件系统在磁盘部分扇区出现物理问题的情况下还能正常⼯作,就必须保证⽂件系统的super block信 息在这种情况下也能正常访问。所以⼀个⽂件系统的super block会在多个block group中进⾏备份, 这些super block区域的数据保持⼀致。

3.3.2、GDT(Group Descriptor Table)

块组描述符表,描述块组属性信息,整个分区分成多个块组就对应有多少个块组描述符。每个块组描述符存储⼀个块组的描述信息,如在这个块组中从哪⾥开始是inode Table,从哪⾥开始是Data Blocks,空闲的inode和数据块还有多少个等等。块组描述符在每个块组的开头都有⼀份拷⻉。

// 磁盘级blockgroup的数据结构

/* *

Structure of a blocks group descriptor

*/

struct ext2_group_desc

{

__le32 bg_block_bitmap; /* Blocks bitmap block */

__le32 bg_inode_bitmap; /* Inodes bitmap */

__le32 bg_inode_table; /* Inodes table block*/

__le16 bg_free_blocks_count; /* Free blocks count */

__le16 bg_free_inodes_count; /* Free inodes count */

__le16 bg_used_dirs_count; /* Directories count */

__le16 bg_pad;

__le32 bg_reserved[3];

};

3.3.3、块位图(Block Bitmap)

Block Bitmap中记录着Data Block中哪个数据块已经被占⽤,哪个数据块没有被占⽤。

3.3.4、inode位图(Inode Bitmap)

该位图的每个bit表⽰⼀个inode是否空闲可⽤。

**注意:**删除一个文件的本质是设置对应的inode和block无效。即,将inode Bitmap和Block Bitmap位图对应的bit位由使用标志修改为未使用即可。

3.3.5、i节点表(Inode Table)

  • inode:存放⽂件属性如->⽂件⼤⼩,所有者,最近修改时间等。
  • 当前分组所有Inode属性的集合。
  • 每个组中inode的个数是固定的。
  • inode编号以分区为单位,整体划分,不可跨分区。
  • 因为inode是以区为单位划分的,所以inode分配的时候,只需要确定起始inode即可。
  • 当前组的inode除了可以映射当前组的block,还可以映射其他组的block。这样就可以实现大文件的创建了。
  • 在一个组中,我们通过inode Bitmap位图来确定某个被使用的inode在该组的inode Table中的数组下标,再用该下标加上该组的起始inode号,就可以得到该inode在全局的inode号。同理,通过全局的inode号我们也可以确定这个inode的具体位置,这样我们就可以通过inode号进行文件查找了,进而实现文件的增删改操作。

3.3.6、Data Block

数据区:存放⽂件内容,也就是⼀个⼀个的Block。根据不同的⽂件类型有以下⼏种情况:

  • 对于普通⽂件,⽂件的数据存储在数据块中。
  • 对于⽬录,该⽬录下的所有⽂件名和⽬录名存储在所在⽬录的数据块中,除了⽂件名外,ls -l命令看到的其它信息保存在该⽂件的inode中。
  • 每个组中Block个数是固定的。
  • Block 号按照分区划分,不可跨分区。
  • Block的分配和inode同理,都是以区为单位划分的,分配时只需要确定起始Block号即可。
  • 对Block的查找只需要找到对应的inode即可,因为inode中有当前inode和Block的映射关系。

3.4、inode和datablock映射

  • inode内部存在 **__le32 i_block[EXT2_N_BLOCKS]; /* Pointers to blocks */ ,**该数组就是⽤来进⾏inode和block映射的。其中,EXT2_N_BLOCKS =15。
  • 该数组中前12个位置分别存储了指向前12个块的指针;第13个位置是一级索引指针,也指向一个块,但是该块中不存储数据,而是存储其他块的块号(一个块号是整型,即四字节,一个块是4KB,这样一个块就可以存储1024个块的块号),第14个位置是二级索引指针,指向一个存储块号的块,这个块中的块号所表示的块中也存储的是其他块的块号;第15个位置同理,存储的是三级索引。如下图:
  • 分区之后的格式化操作,就是对分区进⾏分组,在每个分组中写⼊Super Block、GDT、Block Bitmap、Inode Bitmap等管理信息,这些管理信息统称:⽂件系统。
  • 只要知道⽂件的inode号,就能在指定分区中确定是哪⼀个分组,进⽽在哪⼀个分组确定是哪⼀个inode。
  • 拿到inode⽂件属性和内容就全部都有了。

创建⼀个新⽂件主要有以下4个操作:

  1. **存储属性:**内核先找到⼀个空闲的i节点(这⾥是263466)。内核把⽂件信息记录到其中。
  2. **存储数据:**该⽂件需要存储在三个磁盘块,内核找到了三个空闲块:300,500,800。将内核缓冲区的第⼀块数据复制到300,下⼀块复制到500,以此类推。
  3. 记录分配情况:⽂件内容按顺序300,500,800存放。内核在inode上的磁盘分布区记录了上述块列表。
  4. **添加⽂件名到⽬录:**新的⽂件名abc。linux如何在当前的⽬录中记录这个⽂件?内核将⼊⼝(263466,abc)添加到⽬录⽂件。⽂件名和inode之间的对应关系将⽂件名和⽂件的内容及属性连接起来。

3.5、目录与文件名

问题:

  • 我们访问⽂件,都是⽤的⽂件名,没⽤过inode号啊?
  • ⽬录是⽂件吗?如何理解?

答案:

  • ⽬录也是⽂件,但是磁盘上没有⽬录的概念,只有⽂件属性+⽂件内容的概念。
  • ⽬录的属性不⽤多说,内容保存的是:⽂件名和Inode号的映射关系。

所以,访问⽂件,必须打开当前⽬录,根据⽂件名,获得对应的inode号,然后进⾏⽂件访问。所以,访问⽂件必须要知道当前⼯作⽬录,本质是必须能打开当前⼯作⽬录⽂件,查看⽬录⽂件的 内容!

3.6、路径解析

**问题:**打开当前⼯作⽬录⽂件,查看当前⼯作⽬录⽂件的内容?当前⼯作⽬录不也是⽂件吗?我们访问当前⼯作⽬录不也是只知道当前⼯作⽬录的⽂件名吗?要访问它,不也得知道当前⼯作⽬录的inode 吗?

**答案:**要打开一个目录,就要有该目录的inode,而该目录的inode存储在上级目录的data Block中,所以就需要先打开上级目录,而打开上级目录同理,所以就需要类似于 "递归"式的将路径中的所有目录全部解析,出口是根目录。这个解析过程也叫路径的逆向解析。

**答案2:**实际上,任何⽂件,都有路径,访问⽬标⽂件,⽐如:/home/swb/code/test/test.c 都要从根⽬录开始,依次打开每⼀个⽬录,根据⽬录名,依次访问每个⽬录下指定的⽬录,直到访问到test.c。这个过程叫做Linux路径解析。

**注意:**根⽬录固定⽂件名,inode号,⽆需查找,系统开机之后就必须知道。

路径谁提供?

  • 我们访问⽂件,都是指令/⼯具访问,本质是进程访问,进程有CWD!进程提供路径。
  • 我们open⽂件,提供了路径。

最开始的路径从哪⾥来?

  • Linux 系统启动时,内核会通过引导参数(如 root=)或初始化内存文件系统(initramfs挂载一个初始的根文件系统 。这个根目录 / 是系统中所有路径的绝对起点。

3.7、路径缓存

**问题1:**Linux磁盘中,存在真正的⽬录吗?

**答案:**不存在,只有⽂件。只保存⽂件属性+⽂件内容。

问题2:访问任何⽂件,都要从/⽬录开始进⾏路径解析?

**答案:**原则上是,但是这样太慢,所以Linux会缓存历史路径结构。

**问题2:**Linux⽬录的概念,怎么产⽣的?

**答案:**打开的⽂件是⽬录的话,由OS⾃⼰在内存中进⾏路径维护。

Linux中,在内核中维护树状路径结构的内核结构体叫做: struct dentry

struct dentry {

atomic_td_count;

unsigned int d_flags; /* protected by d_lock */

spinlock_t d_lock; /* per dentry lock */

struct inode *d_inode; /* Where the name belongs to - NULL is * negative */

/*

* The next three fields are touched by __d_lookup. Place them here

* so they all fit in a cache line.

*/

struct****hlist_node d_hash; /* lookup hash list */

struct****dentry *d_parent; /* parent directory */

struct qstr d_name;

struct****list_head d_lru; /* LRU list */

/*

* d_child and d_rcu can share memory

*/

union {

struct****list_head d_child; /* child of parent list */

struct rcu_head d_rcu;

} d_u;

struct list_head d_subdirs; /* our children */

struct list_head d_alias; /* inode alias list */

unsigned long d_time; /* used by d_revalidate */

struct dentry_operations *d_op;

struct super_block *d_sb; /* The root of the dentry tree */

void *d_fsdata; /* fs-specific data */

#ifdefCONFIG_PROFILING

struct****dcookie_struct *d_cookie; /* cookie, if any */

**#**endif

intd_mounted;

unsigned char d_iname[DNAME_INLINE_LEN_MIN]; /* small names */

};

注意:

  • 每个⽂件其实都要有对应的dentry结构,包括普通⽂件。这样所有被打开的⽂件,就可以在内存中形成整个树形结构。
  • 整个树形节点也同时会⾪属于LRU(Least Recently Used,最近最少使⽤)结构中,进⾏节点淘汰。
  • 整个树形节点也同时会⾪属于Hash,⽅便快速查找
  • 更重要的是,这个树形结构,整体构成了Linux的路径缓存结构,打开访问任何⽂件,都在先在这棵树下根据路径进⾏查找,找到就返回属性inode和内容,没找到就从磁盘加载路径,添加dentry 结构,缓存新路径。

3.8、挂载分区

我们已经能够根据inode号在指定分区找⽂件了,也已经能根据⽬录⽂件内容,找指定的inode了,在指定的分区内,我们可以为所欲为了。可是:问题:inode不是不能跨分区吗?Linux不是可以有多个分区吗?我怎么知道我在哪⼀个分区???

3.8.1、一个实验

$ dd if=/dev/zero of=./disk.img bs=1M count=5 #制作⼀个⼤的磁盘块,就当做⼀个分区

$ mkfs.ext4 disk.img # 格式化写⼊⽂件系统

$ mkdir /mnt/mydisk # 建⽴空⽬录

$ df -h # 查看可以使⽤的分区

Filesystem Size Used Avail Use% Mounted on

udev 956M 0 956M 0% /dev

tmpfs 198M 724K 197M 1% /run

/dev/vda1 50G 20G 28G 42% /

tmpfs 986M 0 986M 0% /dev/shm

tmpfs 5.0M 0 5.0M 0% /run/lock

tmpfs 986M 0 986M 0% /sys/fs/cgroup

tmpfs 198M 0 198M 0% /run/user/0

tmpfs 198M 0 198M 0% /run/user/1002

$ sudo mount -t ext4 ./disk.img /mnt/mydisk/ # 将分区挂载到指定的⽬录

$ df -h

Filesystem Size Used Avail Use% Mounted on

udev 956M 0 956M 0% /dev

tmpfs 198M 724K 197M 1% /run

/dev/vda1 50G 20G 28G 42% /

tmpfs 986M 0 986M 0% /dev/shm

tmpfs 5.0M 0 5.0M 0% /run/lock

tmpfs 986M 0 986M 0% /sys/fs/cgroup

tmpfs 198M 0 198M 0% /run/user/0

tmpfs 198M 0 198M 0% /run/user/1002

/dev/loop0 4.9M 24K 4.5M 1% /mnt/mydisk

$ sudo umount /mnt/mydisk # 卸载分区

whb@bite:/mnt$ df -h

Filesystem Size Used Avail Use% Mounted on

udev 956M 0 956M 0% /dev

tmpfs 1 98M 724K 197M 1% /run

/dev/vda1 50G 20G 28G 42% /

tmpfs 986M 0 986M 0% /dev/shm

tmpfs 5.0M 0 5.0M 0% /run/lock

tmpfs 986M 0 986M 0% /sys/fs/cgroup

tmpfs 198M 0 198M 0% /run/user/0

tmpfs 198M 0 198M 0% /run/user/1002

注意:

/dev/loop0 在Linux系统中代表第⼀个循环设备(loop device)。循环设备,也被称为回环设备或者loopback设备,是⼀种伪设备(pseudo-device),它允许将⽂件作为块设备 (block device)来使⽤。这种机制使得可以将⽂件(⽐如ISO镜像⽂件)挂载(mount)为⽂件系统,就像它们是物理硬盘分区或者外部存储设备⼀样。

3.8.2、结论

  • 分区写⼊⽂件系统,⽆法直接使⽤,需要和指定的⽬录关联,进⾏挂载才能使⽤。挂载其实就是为其创建对应的数据结构。
  • 所以,可以根据访问⽬标⽂件的"路径前缀"准确判断我在哪⼀个分区。

3.9、文件系统总结

四、软硬链接

4.1、硬链接

从上面的知识我们可以知道,真正找到磁盘上⽂件的并不是⽂件名,⽽是inode。其实在linux中可以让多个⽂件名对应于同⼀个inode。

示例:

file-hard.link 被称为指向⽂件的硬链接。内核记录了这个连接数,inode 为 666852的硬连接数为2。我们在删除⽂件时⼲了两件事情:1.在⽬录中将对应的记录删除,2.将硬连接数-1,如果为0,则将对应的磁盘释放。硬链接数为零之前,文件内容始终存在,所以这个硬链接相当于一份文件备份。删除硬链接直接用 rm 命令就可以。

4.2、软链接

硬链接是通过inode引⽤另外⼀个⽂件,软链接是通过名字引⽤另外⼀个⽂件,但实际上,新的⽂件和被引⽤的⽂件的inode不同,应⽤常⻅上可以想象成⼀个快捷⽅式**。在shell中的做法:**

删除软链接可以使用 rm 命令,也可以使用unlink 命令。如图:

⽂件的三个时间:(acm)

  • Access 最后访问时间。
  • Modify ⽂件内容最后修改时间。
  • Change 属性最后修改时间。

4.3、软硬链接对比

  • 软连接是独⽴⽂件,它有独立的inode。软链接内容上,保存的是目标文件的路径,相当于Windows上的快捷方式。
  • 硬链接只是⽂件名和⽬标⽂件inode的映射关系,没有独立的inode。
  • Linux中不允许对目录新建硬链接,防止造成路径有环。( . 和 .. 是特殊情况)

4.4、软硬链接用途

硬链接:

  • . 和 .. 就是硬链接。
  • ⽂件备份。

软链接:

  • 类似快捷⽅式。

五、动态库和静态库

5.1、手动制作静态库

静态库命名必须以lib开头,.a结尾,中间名称随便起。

情况一:安装到系统里面

首先我们需要先准备好对应的头文件和源文件。(这里源文件指的是 .c 文件)

然后将源文件都编译为.o文件。

一个库中最终可能会对应多个.o文件,所以要将所有的.o文件打包,这里打包.o文件使用 ar 命令,r 选项是将指定文件插入到归档文件中,如果包中已经有同名文件了就进行替换,不存在则新增;c 选项是如果不存在这个归档文件(即包文件),就创建它。

Linux中如果想将我们写的库装到系统中,一般情况下,头文件拷贝到/usr/include/目录下,.a文件拷贝到/lib64/目录下。(其实不同操作系统中,将别入的库安装到系统中供我们使用,都是在进行拷贝操作)

添加到系统中后,我们就可以像使用C语言自己的库那样使用我们写的库了,但是如果直接编译生成可执行程序会有链接错误。这是因为:gcc本身就是用来编译C语言的,所以它可以找到C语言的库,但是对于我们自己写的库或者别人提供的库都属于第三方库,gcc命令是不认识的,我们需要自己指定库文件,指定库文件使用 -l (L 的小写) 选项,后面加的库名需要去掉前缀和后缀,并且每指定一个文件就需要使用一个 -l 选项。如图:

情况二:头文件和库文件与自己的程序在同一目录中

如下图,头文件和库文件还有我们自己的程序都在同一目录中,且系统中没有我们写的头文件和库文件。

gcc在查静态库的时候,不会在当前路径下查,会在系统默认放C语言静态库的地方去查,所以这时直接编译,可以找到头文件,但是无法找到库文件。所以我们需要使用 -L 选项,-L 选项的作用是告诉编译器,编译的时候,查找库,除了系统路径,也要在选项后面指明的路径下查找。

情况三:头文件和库文件既不在系统中,也不在程序目录中

自己的程序和头文件和库文件的路径关系如下:

<>和 " " 方式引入的头文件分别会在系统路径下和当前路径下查找头文件(" " 先在当前路径下找,如果没有就去系统路径下找) ,但是上图中,头文件既没有在当前路径下,也没有在系统路径下,库文件同样,这时除了需要指定库文件路径还需要指定头文件路径,指定头文件路径使用 -I (i 的大写)选项。

5.2、制作动态库

动态库命名必须以lib开头,.so结尾,中间名称随便起。

动态库制作:

形成动态库使用的命令是 gcc。如下图(Makefile文件):

bash 复制代码
  1 libmystdio.so:my_stdio.o my_string.o
  2   gcc -o $@ $^ -shared
  3 %.o:%.c
  4   gcc -fPIC -c $<
  5 
  6 .PHONY:clean
  7 clean:
  8   @rm -rf *.so *.o stdc*
  9   @echo "clean ... done"
 10 
 11 .PHONY:output
 12 output:
 13   @mkdir -p stdc/include
 14   @mkdir -p stdc/lib
 15   @cp -f *.h stdc/include
 16   @cp -f *.so stdc/lib
 17   @tar -czf stdc.tgz stdc
 18   @echo "output stdc ... done"

形成动态库需要先将所有 .c 编译为 .o 文件,而且需要使用 -fPIC 选项,然后再将 .o 文件形成动态库,这需要使用 -shared 选项。

情况一:安装到系统里面

和静态库做法相同,将动态库拷贝到系统的指定路径下(/usr/include/ 和 /lib64/),然后在生成最终可执行时通过 -l (L的小写) 选项指定动态库名称即可,指定的动态库名称需要去掉前缀 lib 和后缀 .so。

情况二:头文件和库文件与自己的程序在同一目录中

**和静态库做法相同,因为头文件在程序所在的路径中,可以自动找到,我们只需要通过 -L 选项指定库文件路径,-l (L的小写)**选项指定库文件名称即可。

情况三:头文件和库文件既不在系统中,也不在程序目录中

这里也和静态库做法相同,通过 -I (i 的大写) 指定头文件路径,-L 指定库文件路径,-l (L的小写) 指定库文件名称。

通过上面的方法确实可以形成可执行程序,但是形成的可执行程序无法运行,因为动态库不像静态库那样会被编译到代码里面,运行时需要能够去找到对应的动态库才行。编译时我们告诉编译器头文件和库文件在哪里,进而完成编译,但是执行程序时操作系统的事情,它不知道动态库在哪里。

解决办法:

  • 拷贝到系统默认路径下,比如/lib64。OS会去默认路径查找。
  • 在系统路径下,为我们的动态库建立软链接(和动态库同名)。

sudo ln -s /home/swb/linux_blog/lesson11/other/stdc/lib/libmystdio.so /lib64/libmystdio.so

  • Linux系统中,OS查找动态库还会去环境变量LD_LIBRARY_PATH中指定的路径去查找,所以我们可以将自己的动态库的路径添加到该环境变量中,可以使用 export,但这样是内存级别的,也可以直接导入 .bashrc 文件中,这样就会一直生效了。

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:自己的动态库的路径

  • Linux系统中,/etc/ld.so.conf.d/ 路径中存放着后缀为 .conf 的文件,这些文件中存储着动态库的路径,当需要链接动态库时,系统就会上这些文件指定的路径中查找,所以我们只需要在这个路径中新建一个后缀为 .conf 的文件,在该文件中写入我们自己的动态库的路径即可。最后在执行 ldconfig 命令,让系统重新加载一下这个路径下的所有配置文件即可。

补充知识:

  • 同时提供动静态库,默认链接的是动态库,如果想要链接静态库,可以在 gcc 命令中添加 -static 选项。
  • 如果我们通过 -static 选项强制进行静态链接,就必须提供对应的静态库。
  • 如果我们只提供静态库,但是链接方式是动态的,gcc/g++命令就会针对我们提供的静态库局部性采用静态链接。

5.3、原理上理解动态库(共享库)

无论是动态库还是静态库,本质都是一个文件,当我们运行一个程序时,它可能会依赖于某些动态库,那么这些动态库就会从磁盘上加载到内存中,并通过进程地址空间中的共享区和页表的映射和进程产生关联,这样在用这个库中的方法时就可以找的到它。如图:

如果有多个进程使用了同一个动态库,那么这个动态库不会加载多份,只需要加载一份即可。不同进程通过共享区和页表的映射,最终找到同一份动态库,并使用它。这样就比静态库要节省空间,这也是为什么动态库也叫做共享库。如图:

相关推荐
muzi_liii2 分钟前
C++11-(3)
开发语言·c++
一叶知秋h11 分钟前
Ubuntu系统的安装参考链接汇总
linux·ubuntu
fictionist12 分钟前
正则表达式篇
linux·运维·服务器·数据库·mysql·正则表达式·c#
南瓜胖胖14 分钟前
R语言开始绘图--柱状图
开发语言·r语言
孙克旭_17 分钟前
day019-特殊符号、正则表达式与三剑客
linux·运维·正则表达式
Unique27617 分钟前
Linux条件变量
linux·开发语言
老歌老听老掉牙22 分钟前
Open CASCADE学习|非线性方程组求解技术详解
c++·学习·opencascade·非线性方程组
潇-xiao29 分钟前
Qt window frame + windowTitle + windowIcon属性(3)
c++·笔记·qt
江池俊31 分钟前
Joplin+群晖NAS远程同步方案:私有云笔记的稳定存储与跨设备管理实践
笔记
A charmer32 分钟前
C++ 日志系统实战第六步:性能测试
c++·日志系统