linux的文件系统

目录

1.了解磁盘

1.1物理结构

1.2物理存储

1.3逻辑结构

1.3.1理解

1.3.2实际逻辑

2.前情提要

2.1块概念

2.2分区概念

2.3inode概念

[3.ext2 文件系统](#3.ext2 文件系统)

3.1宏观认识

[3.2Block Group](#3.2Block 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.4inode位图(inode Bitmap)](#3.3.4inode位图(inode Bitmap))

[3.3.5i节点表(inode Table)](#3.3.5i节点表(inode Table))

[3.3.6 DataBlock](#3.3.6 DataBlock)

3.4inode和datablock映射

3.5目录与文件名

3.6路径

3.7路径缓存

3.8挂载分区

3.9小总结

4.软硬连接

4.1硬连接

4.2软连接

4.3软硬对比和用途


磁盘上的文件需要管理,文件系统的核心就是让os快速地定位一个文件。

1.了解磁盘

目前磁盘基本只用于台式机或者服务器。因其性价比高,存储量大,使用量依旧不小。

1.1物理结构

磁盘的盘片就是真正存储数据的地方,是可读可写的,跟光盘那种只读的不一样。

盘片正反2面都可以存数据。

家用的磁盘基本都是一片2面。企业用的服务器都是一摞的,叠在一起的那种。

磁头是一面一个磁头,所以企业用的磁盘,可以看到很多磁头分层放着。

磁头不工作的时候就是放在停于盘片外。开始工作后就会在主轴与边缘的一个半径内来回摆动(这就是在寻址工作),而主轴本身是会高速转动的。两者不会碰到,但距离很近。

磁盘背面都是电路,里面存着伺服系统,以及数据线,电源线插口等。
之所以现在笔记本不用磁盘,是因为磁盘的磁头在笔记本携带过程中,容易抖动,导致刮到盘片,而盘片一旦被刮花,就可能导致部分数据丢失,严重会整个系统无法启动。

像现在的台式机,其实也很少用磁盘,因为虽然磁盘便宜,但是磁盘也怕断电和强制关机。一旦断电或强制关机,磁头会停下来,这时候盘片还在依着惯性转动,这时候就极可能刮伤盘片。而企业的服务器因为保护措施比较好,不怎么怕出现断电或强制关机等等。

我们平时一直说的计算机只识别01,其实并不是说磁盘里真存了个01,根据不同设备,根据高低电平,信号强弱等等,只要能表现出两态,我们就认为其存的是01

而磁盘,可以想象是无数个磁铁构成,所谓的01,就是磁铁南极北极,数据写入,其实就是通过磁头的放电,改变南极北极朝向。而磁铁的磁性是可以保持很久的,这也是为什么说磁盘可以永久存储。

磁盘内部需要保持无尘,因为光速旋转下,再细小的灰尘,也会容易导致盘片报废。

企业内部的服务器用的磁盘,因为使用率高,基本全年无休的工作,差不对几年一个盘就要报废,但因为里面还有数据,所以不能随意丢弃,要经过特殊处理。

1.2物理存储

以下皆为网图,如有侵权请联系


概念

扇区 :是从磁盘读出和写入信息的最小单位,通常大小为512字节,块设备,每个扇区都有编号。
磁头(head)数 :每个盘片一般有上下两面,分别对应1个磁头,共2个磁头
磁道(track)数 :磁道是从盘片外圈往内圈编号0磁道,1磁道..,靠近主轴的同心圆用于停靠磁头,不存储数据
柱面(cylinder)数 :磁道构成柱面,数量上等同于磁道个数
扇区(sector)数 :每个磁道都被切分成很多扇形区域,每道的扇区数量相同
圆盘(platter)数 :就是盘片的数量,同样盘片和磁头也有编号。
磁盘容量 =磁头数×磁道(柱面)数×每道扇区数×每扇区字节数
细节:传动臂上的磁头是共进退的,所有磁头在纵向上是一致的。这些磁头所指向扇区,从横向看,可以视作一个柱面。

定位扇区(适用早期磁盘的CHS寻址):

可以先定位磁头 (header)

确定磁头要访问哪一个柱面(磁道) (cylinder)

定位一个扇区(sector)

为什么盘片需要高速转动?

因为磁头是在一个半径内摆动,定位磁道。如果盘片不转动,磁头就无法遍历整个磁道,无法遍历磁道,就无法找到指定的扇区(万一指定的扇区在当前磁头摆动范围外呢)。

其实从这也能发现,为什么磁盘的速度慢,因为是机械运动,再加上磁盘还是外设,相比计算机的内部的芯片电路等等慢了不止一点。
文件的核心,就是记录存在了哪些扇区,再通过扇区定位,访问这些扇区,从扇区中读取数据。

1.3逻辑结构

1.3.1理解

我们可以将整个磁盘的所有同心圆拉直,一块块拼起来,视作一个很长很长的长方形。

假设一共是800GB,那么我们将一小块一小块视作扇区,那么整个长方形就可以视作容量有

1677721600的数组,即array[1677721600]

从这个思路,我们就可以发现,对磁盘的管理,就变成了对数组的管理

然后问题来了,比如我们要访问array[10231231]的扇区,需要从逻辑转换成物理,即转换成chs需要磁道,盘片,扇区三个数字。我们先通过10231231整除单盘扇区的个数,比如上面假设一个盘面有200g,那么换算下来就是一个盘面419430400个扇区,即10231231整除419430400=0,换算出是目标在第0号盘面。第二步10231231%单盘扇区的个数==目标扇区是第0号盘面的第几个扇区,结果为temp。第三步,temp整除一个磁道上扇区的个数==目标扇区在第几号磁道,结果为x号磁道,第四步,temp%一个磁道上扇区的个数==目标扇区是在x号磁道的第几个扇区。

其中像是一个磁道有多少扇区,一个盘面有多少扇区,计算机开机后都会读取到。而上面的由线性地址转换为chs地址,是由磁盘自己转化,os文件系统只需要知道扇区的线性地址即编号这个线性地址即LBA,Logic Block Addrress

1.3.2实际逻辑

磁道便是,一维数组

柱面便是整个磁盘所有盘面的同一个磁道,即柱面展开:二维数组

整个磁盘就是多个上图的柱面1、2、3....构成的。三维数组

三维数组也可以展开拉成一维数组

cpp 复制代码
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
"//":表示除取整

2.前情提要

2.1块概念

os文件系统认为一个扇区的大小太小了,每次都只能写入512字节,所以os文件系统自己认定了8个连续扇区4KB的空间为一个IO基本单位 块 ,即不管是写入还是读取都是以4KB为单位写入读取,哪怕只是写入1B的数据,也是要从磁盘拿4KB的扇区。不同的系统不一定都是4KB这个块的下标 即块号==LBA/8,也就是块的地址

知道块号,就可以依据 块号*8 + [0,1,2,....7]即可得出该簇的每个扇区的线性地址即LBA,再交给磁盘,磁盘转换成CHS访问相应扇区即可。

块号是格式化的时候确定的,确定后不可更改。

2.2分区概念

对os文件系统来说,管理100GB和管理800GB,管理方式是一样的。假设此时有800GB的磁盘,os文件系统可以将其分区(类似我们windows上的分区,C盘D盘),分出100GB的区(方便记忆就叫其C区),但是100GB还是太大了,所以os文件系统会在C区内部继续分组,假设分出一个个10GB的组,只要管理好其中一个10GB的组,那么其他的组也能管理好,所有组管理好意味着C区管理好,C区管理好意味着其他分区也可以管理好。

整个思路其实就是分治思想,将大问题拆成一个个小问题。

每个分区都会安装一个文件系统,可以理解为上面的管理方法,按默认的分治思想是所有分区都装同样类型的文件系统,但实际成熟的文件系统也不少,不一定要一模一样。

分区实质上是对硬盘的一种格式化

在windows中,C、D、E盘就是分区。

而linux中,柱面是分区的最小单位,我们可以利用参考柱面号码的方式来进行分区,其本质就是设置每个区的起始柱面和结束柱面号码。柱面大小一致,扇区个位一致,那么其实只要知道每个分区的起始和结束柱面号,知道每一个柱面多少个扇区,那么该分区多大,解释LBA是多少也就清楚了。

2.3inode概念

我们知道一个文件包括内容和属性(元数据),比如我们ls -l就可以看到文件的元数据。当然stat命令也可以看到更多信息。

我们思考一个问题,文件内容都储存在块中,那么元数据存在哪个区域呢?答案是inode,索引节点。

每个文件都有对应的inode,里面存储着元数据。

如上,linux下文件的存储是属性和内容分离存储,存元数据的是inode,一个文件对应一个inode,inode内有个分区内唯一标识符,inode号。

任何文件的内容大小可以不同,但是属性大小一定是相同的。属性的类别是一样的,只是属性的值不同文件不一样,所以当inode对象创建后,申请的空间是一样大的。

注意,文件名属性不在inode数据结构内部,inode大小一般是128字节或者256,后面讲述统一128字节。128字节的话,一块4KB,可以存32个文件属性

结构如下

cpp 复制代码
/* 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)

3.ext2 文件系统

3.1宏观认识

我们想要在硬盘上储文件,必须先把硬盘格式化为某种格式的文件系统,才能存储文件。文件系统的目的就是组织和管理硬盘中的文件。在Linux系统中,最常见的是ext2系列的文件系统。其早期版本为ext2,后来又发展出ext3和ext4。

ext3和ext4虽然对ext2进行了增强,但是其核心设计并没有发生变化,我们仍是以较老的ext2作为讲解对象。

ext2文件系统将整个分区划分成若干个同样大小的块组(BlockGroup),如下图所示。只要能管理一个分区就能管理所有分区,也就能管理所有磁盘文件。

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

3.2Block Group

ext2文件系统会根据分区的大小划分为数个BlockGroup。而每个BlockGroup都有着相同的结构组成。

3.3块组内部构成

3.3.1超级块 (Super Block)

存放文件系统本身的结构信息,描述整个分区的文件系统信息。记录的信息主要有:bolck和inode的总量,未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。SuperBlock的信息被破坏,可以说整个文件系统结构就被破坏了

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

**多个分区要不要管理?要。怎么管理?先描述在组织。**所以os启动后,会把每个superblock块放到一个数据结构里,比如链表之类的。所以对分区的管理就变成了对链表的增删查改。

cpp 复制代码
/*
* Structure of the super block
*/
struct ext2_super_block {
    __le32 s_inodes_count;    /* Inodes count */
    __le32 s_blocks_count;    /* Blocks count */
    __le32 s_r_blocks_count;    /* Reserved blocks count */
    __le32 s_free_blocks_count;    /* Free blocks count */
    __le32 s_free_inodes_count;    /* Free inodes count */
    __le32 s_first_data_block;    /* First Data Block */
    __le32 s_log_block_size;    /* Block size */
    __le32 s_log_frag_size;    /* Fragment size */
    __le32 s_blocks_per_group;    /* Blocks per group */
    __le32 s_frags_per_group;    /* # Fragments per group */
    __le32 s_inodes_per_group;    /* # Inodes per group */
    __le32 s_mtime;    /* Mount time */
    __le32 s_wtime;    /* Write time */
    __le16 s_mnt_count;    /* Mount count */
    __le16 s_max_mnt_count;    /* Maximal mount count */
    __le16 s_magic;    /* Magic signature */
    __le16 s_state;    /* File system state */
    __le16 s_errors;    /* Behaviour when detecting errors */
    __le16 s_minor_rev_level;    /* minor revision level */
    __le32 s_lastcheck;    /* Line of last check */
    __le32 s_checkinterval;    /* max. time between checks */
    __le32 s_creator_os;    /* OS */
    __le32 s_rev_level;    /* Revision level */
    __le16 s_def_resuid;    /* Default uid for reserved blocks */
    __le16 s_def_resgid;    /* Default gid for reserved blocks */

/*
* These fields are for EXT2_DYNAMIC_REV superblocks only.
*
* Note: the difference between the compatible feature set and
* the incompatible feature set is that if there is a bit set
* in the incompatible feature set that the kernel doesn't
* know about, it should refuse to mount the filesystem.
*
* e2fsck's requirements are more strict; if it doesn't know
* about a feature in either the compatible or incompatible
* feature set, it must abort and not try to meddle with
* things it doesn't understand...
*/

    __le32 s_first_ino;    /* First non-reserved inode */
    __le16 s_inode_size;    /* size of inode structure */
    __le16 s_block_group_nr;    /* block group # of this superblock */
    __le32 s_feature_compat;    /* compatible feature set */
    __le32 s_feature_incompat;    /* incompatible feature set */
    __le32 s_feature_ro_compat;    /* readonly-compatible feature set */
    __u8 s_uuid[16];    /* 128-bit uuid for volume */
    char s_volume_name[16];    /* volume name */
    char s_last_mounted[64];    /* directory where last mounted */
    __le32 s_algorithm_usage_bitmap; /* for compression */
    /*
    * Performance hints. Directory preallocation should only
    * happen if the EXT2_COMPAT_PREALLOC flag is on.
    */
    __u8 s_prealloc_blocks;    /* Nr of blocks to try to preallocate */
    __u8 s_prealloc_dir_blocks;    /* Nr to preallocate for dirs */
    __u16 s_padding1;
    /*
    * Journaling support valid if EXT3_FEATURE_COMPAT_HAS_JOURNAL set.
    */
    __u8 s_journal_uuid[16];    /* uuid of journal superblock */
    __u32 s_journal_inum;    /* inode number of journal file */
    __u32 s_journal_dev;    /* device number of journal file */
    __u32 s_last_orphan;    /* start of list of inodes to delete */
    __u32 s_hash_seed[4];    /* HTREE hash seed */
    __u8 s_def_hash_version;    /* Default hash version to use */
    __u8 s_reserved_char_pad;
    __u16 s_reserved_word_pad;
    __le32 s_default_mount_opts;
    __le32 s_first_meta_bg;    /* First metablock block group */
    __u32 s_reserved[190];    /* Padding to the end of the block */
};

3.3.2 GDT(Group Descriptor Table)

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

cpp 复制代码
// 磁盘级blockgroup的数据结构
/*
 * Structure of a blocks group descriptor
 */
struct ext2_group_desc
{
    __le32 bg_block_bitmap;		/* Blocks bitmap block */
    __le32 bg_inode_bitmap;		/* Inodes bitmap block */
    __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)

BlockBitmap中记录着DataBlock中哪个数据块已经被占用,哪个数据块没有被占用

bit的位置表示块号。

bit的内容表示该块是否被使用

3.3.4inode位图(inode Bitmap)

每个bit表示一个inode是否空闲可用。

bit的位置表示该分组内第几个inode。

bit的内容表示该inode是否被使用

3.3.5i节点表(inode Table)

存放文件属性如文件大小,所有者,最近修改时间等

当前块组内所有文件的inode的集合,形成的表
inode编号以分区为单位,整体划分,不可跨分区

每个组的inode数量和数据块数量,都是有自己一套固定比例的,有可能出现其中一个已经用完了,另一个还没用完的现象。

在分组的时候,每个组都会有对应的起始inode编号和一共有多少个inode,起始编号+inode位图里的偏移量即该文件的inode编号。后续访问文件的时候,有了inode编号,根据编号的大小,比如一组是1 ~100000,2组是100001~200000,该文件的inode编号是43,那么这个文件就在1组里,然后再根据编号和该分组的起始编号算出该文件的inode在inode table的下标偏移量,然后就可以找到对应的inode对象。

3.3.6 DataBlock

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

对于普通文件,文件的数据存储在数据块中。

对于目录,该目录下的所有文件名和目录名存储在所在目录的数据块中,除了文件名外,ls-命令看到的其它信息保存在该文件的inode中。
Block号按照分区划分,不可跨分区

3.4inode和datablock映射

inode内部存在 __le32 i_block[EXT2_N_BLOCKS];/* Pointers to blocks */,EXT2_N_BLOCKS=15就是用来进行inode和block映射的

这样文件=内容+属性,就都能找到了。

网图

cpp 复制代码
/* 
 * 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 */
};

#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)

//inode 的大小通常是 128 字节 或 256 字节

block数组里前12个下标存的是直接的数据块编号,后3个本质是做多级索引,让存储的数据块不存储文件数据,而是存储更多的该文件的数据块编号,[12]是存下一级索引的数据块编号(一级索引),[13]是二级索引,[14]是三级索引。

删除文件,却不是删除数据和属性,而是将位图的内容变成0即可

分区之后的格式化操作,就是对分区进行分组,在每个分组中写入SB、GDT、BlockBitmap、InodeBitmap等管理信息,这些管理信息统称:文件系统。形象的说就是向分区写入全新的文件系统。

只要知道文件的inode号,就能在指定分区中确定是哪一个分组,进而在哪一个分组确定是哪一个inode

拿到inode文件属性和内容就全部都有了

3.5目录与文件名

目录也是文件,但是磁盘上没有目录的概念,只有文件属性+文件内容的概念。

目录的属性不用多说,内容保存的是:文件名和lnode号的映射关系

下面的代码可以验证。

cpp 复制代码
// readdir.c
#include <stdio.h>
#include <string.h>

#include <stdlib.h>
#include <dirent.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <directory>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    DIR *dir = opendir(argv[1]); // 系统调用,自行查阅
    if (!dir) {
        perror("opendir");
        exit(EXIT_FAILURE);
    }

    struct dirent *entry;
    while ((entry = readdir(dir)) != NULL) {
        // Skip the "." and ".." directory entries
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..")
            == 0) {
            continue;
        }

        printf("Filename: %s, Inode: %lu\n", entry->d_name, (unsigned long)entry->d_ino);
        long entry->d_ino;

        closedir(dir);
        return 0;
    }
}

访问文件,必须打开当前目录,根据文件名,获得对应的inode号,然后进行文件访问

访问文件必须要知道当前工作目录,本质是必须能打开当前工作目录文件,查看目录文件的内容!

比如:要访问a.c,就必须打开da(当前工作目录),然后才能获取a.c对应的inode进而对文件进行访问。

下面是新建文件时,主要的操作

1.存储属性

内核先找到一个空闲的i节点(这里是263466)。内核把文件信息记录到其中。

2.存储数据

该文件需要存储在三个磁盘块,内核找到了三个空闲块:300,500,800。将内核缓冲区的第一块数据复制到300,下一块复制到500,以此类推。

3.记录分配情况

文件内容按顺序300,500,800存放。内核在inode上的磁盘分布区记录了上述块列表。

4.添加文件名到目录

新的文件名abc。linux如何在当前的目录中记录这个文件?内核将入口(263466,abc)添加到目录文件。文件名和inode之间的对应关系将文件名和文件的内容及属性连接起来。

至于删除文件,比如 rm 文件名。那就是通过文件名从目录的内容找到映射关系,然后找到对应的inode编号,根据编号,把对应的inode位图和数据块位图对应位置置0,最后再把文件名和inode的映射关系从目录的内容中去除即可。

其他操作也是类似。


为什么目录没有写权限的时候不能新建或删除文件?,因为没有写权限,就不能对目录的内容做更改,自然就没法新建或删除文件了。

同理,为什么没有读权限,就不能读取目录下的文件信息了?因为没有读权限就不能读取目录的内容,没有目录的内容,自然就不知道有什么文件,文件的属性更无法找到了。

3.6路径

前面的目录和文件名其实逻辑上还差了个闭环,那就是目录的inode怎么确定呢?

任何文件,都有路径,访问目标文件,比如:

/home/zsl/study/test.c

都要从根目录开始,依次打开每一个目录,根据目录名,依次访问每个目录下指定的目录,直到访问到test.c。这个过程叫做Linux路径解析。

问题又来了,那根目录的inode怎么确定呢?根目录是固定的文件名和inode号,无需查找,系统开机后就必须知道。

路径是谁提供?访问文件,比如用指令或工具访问,本质是用进程访问,进程有cwd,存储了当前工作目录。我们open文件的时候也提供了路径。

那么上面提供的路径,最开始的路径从哪来呢?这也是为什么根目录下有很多缺省目录。像是家目录等等。

3.7路径缓存

从上面,我们知道,访问文件需要从根目录开始路径解析。但这样太慢了,所以linux会对已经解析过的路径进行缓存,即缓存历史路径结构,怎么缓存路径?缓存路径本质上也是对路径的管理,那自然就是先描述再组织。

cpp 复制代码
struct dentry {
    atomic_t d_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;
        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 */
    
#ifdef CONFIG_PROFILING
    struct dcookie_struct *d_cookie;
#endif
    
    int d_mounted;
    unsigned char d_iname[DNAME_INLINE_LEN]; /* small names */
};

每个文件其实都要有对应的dentry结构,包括普通文件。这样所有被打开的文件,就可以在内存中形成整个树形结构

整个树形节点也同时会隶属于LRU(LeastRecentlyUsed,最近最少使用)结构中,进行节点淘汰

整个树形节点也同时会隶属于Hash,方便快速查找

更重要的是,这个树形结构,整体构成了Linux的路径缓存结构,打开访问任何文件,都在先在这棵树下根据路径进行查找,找到就返回属性inode和内容,没找到就从磁盘加载路径,添加dentry结构,缓存新路径

3.8挂载分区

前面已经讲了很多,但是对于文件系统,还差了点。我们虽然已经能够根据inode在指定分区找到文件,但有个问题,inode号是不能跨分区的,linux有多个分区,我怎么知道我在哪个分区呢?

在讲清楚前,我们先讲个概念。

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

分区写入文件系统,无法直接使用,需要和指定的目录关联,进行挂载才能使用。

所以,可以根据访问目标文件的"路径前缀"准确判断我在哪一个分区。

比如,我这个云服务器,严格意义上只有/dev/vda1这个分区,这个分区是挂载到/目录下的,只要是访问/目录下的文件,都可以根据/目录找到自己是哪个分区。

因此,对分区的访问,都是通过所挂载的路径访问的。

具体的验证,分区的挂载操作这里不细谈,有兴趣的可以去搜搜相关的小实验。

windows下的文件系统也有类似的设置,像c盘d盘,本身也是一个目录,只是将分区挂载到这个目录上了。

3.9小总结

网图

4.软硬连接

之前介绍linux基础的时候说过软硬连接,但没详细说明。

4.1硬连接

前面我们说过,linux中只看inode,文件名不是最重要的。

linux中可以让一个inode对应对个文件名。

test1和test2的链接状态完全相同,他们被称为指向文件的硬链接。内核记录了这个连接数即硬连接数,inode917517的硬连接数为2。硬链接数其实就是inode结构中的一个引用计数。

我们在删除文件时干了两件事情:1.在目录中将对应的记录删除,2.将硬连接数-1,如果为0,则将对应的磁盘释放(前面说过的,将位图清0)。新建硬链接就是相反

4.2软连接

硬链接是通过inode引用另外一个文件,软链接是通过名字引用另外一个文件,但实际上,新的文件和被引用的文件的inode不同,应用常见上可以想象成一个快捷方式。

小知识,unlike 文件名也可以删除文件,跟rm区别不大

4.3软硬对比和用途

软连接是独立文件,文件内容是目标文件的路径

硬链接只是新增了新的文件名和目标文件inode的映射关系到指定目录的数据块中

硬链接不允许用户给目录创建硬链接,只允许系统自己创建,比如.和..
硬链接常见用途就是文件备份,另一个就是我们每个目录查看隐藏文件都能看到的.和..

我们cd .. cd . 的操作就是基于硬链接,inode一致。

这也是为什么,我们目录创建之后默认就是2个硬连接数。因为其文件内部的.也是一个硬链接。子目录和孙子目录创建的越多,父目录的硬链接数也就越多。一个目录底下有多少个一级子目录,就是该目录的硬链接数-2
软连接常用于快捷方式

相关推荐
珊瑚礁的猪猪侠3 小时前
正则表达式入门到精通教程(Linux实操版)
linux·人工智能·正则表达式
姚远Oracle ACE4 小时前
如何判断Oracle AWR报告中的SQL在多大程度能代表整个系统的负载?
数据库·oracle·1024程序员节
翻斗花园牛图图-4 小时前
Linux——守护进程
1024程序员节
czhc11400756634 小时前
JAVA1026 方法;类:抽象类、抽象类继承;接口、接口继承 Linux:Mysql
java·linux·mysql
海梨花4 小时前
【力扣Hot100】刷题日记
算法·leetcode·1024程序员节
生莫甲鲁浪戴4 小时前
Android Studio新手开发第三十一天
android studio·1024程序员节
Dev7z4 小时前
基于Swin Transformer的宠物皮肤病诊断系统
1024程序员节
m0_748240254 小时前
基于Reactor模式的高性能C++仿Muduo库:Server服务器模块实现
服务器·c++·php
王老师青少年编程4 小时前
AtCoder真题及详细题解 ABC427C: Bipartize
c++·题解·1024程序员节·atcoder·csp·abc·信奥赛