Linux I/O 缓冲区、inode、软硬链接与磁盘结构全解析

文章目录

缓冲区

缓冲区问题

在 Linux 编程中,经常会遇到一个经典问题:

c 复制代码
#include<stdio.h>
#include<unistd.h>
#include<string.h>

int main()
{
    printf("hello printf\n");
    fprintf(stdout,"hello fprintf\n");
    const char *fputsString = "hello fputs\n";
    fputs(fputsString,stdout);

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

    fork();

    return 0;//C语言接口打印两次,缓冲区问题
}

可以看到:

printf、fprintf、fputs 输出 两次

write 输出 一次

产生这个现象的核心原因是:I/O 缓冲区机制。

缓冲区概念

缓冲区本质上是 内存中的一块临时存储区域,用于在程序和外设之间暂存数据。

它的主要作用是:

减少慢速 I/O 设备带来的性能损耗,提高程序执行效率。

原因很简单:

设备 速度
CPU / 内存 纳秒级
磁盘 / 终端 毫秒级

因此系统采用 缓冲区机制:

复制代码
应用程序
     ↓
用户态缓冲区(stdio)
     ↓
系统调用 write()
     ↓
内核缓冲区
     ↓
设备(磁盘 / 终端)

I/O区分

标准I/O缓冲区与系统调用I/O区别

Linux I/O 分为两层:

层级 接口示例 缓冲策略 属于谁
用户层缓冲 I/O printf(), fprintf(), fputs() 使用 FILE* 缓冲区(stdio 缓冲) C 库
内核层系统调用 I/O write(), read() 直接与内核交互,无用户态缓冲 内核

标准IO

C标准库I/O(printffprintffputs)

这三个函数都属于标准库层

最终都会间接调用到系统调用(如write),但中间有一层缓冲机制

函数 所属层级 缓冲类型 说明
printf() 标准输出 行缓冲 写到终端时,遇到换行符 \nfflush() 才真正写出
fprintf(stdout, ...) 标准输出 同上 只是显式指定 stdout
fputs() 标准输出 同上 不自动加 \n,但仍在缓冲区中
这些函数在用户态维护一个FILE缓冲区例如4KB 数据先写道缓冲区,再由标准库统一调用系统调用write()一次性写入内核,提高性能
系统调用I/O
c 复制代码
write(1, wstring, strlen(wstring));

这是真正的系统调用接口,直接陷入内核执行:

  • 1->文件描述符,对应stdout
  • wstring->用户缓冲区地址
  • strlen(wstring)->要写的字节数
    执行流程:
  1. CPU通过系统调用号(_NR_write)进入内核态
  2. 内核根据fd(此处为1,即标准输出)找到对应的文件对象
  3. 调用底层驱动的.write()实现(终端设备驱动)
  4. 数据直接写入内核缓冲区或设备,不经用户态缓冲
  5. 没有缓冲机制,每次write()都是一次系统调用
项目 标准库 I/O(printf 等) 系统调用 I/O(write)
层级 用户态 stdio 库 内核态 syscall
缓冲 有(全缓冲 / 行缓冲) 无缓冲
效率 高(合并多次写) 低(每次陷入内核)
控制粒度 由标准库控制 由程序员控制
文件描述符 隐藏(封装在 FILE*) 显式(int fd)
所以总体过程为
复制代码
用户程序
   ↓
C 标准库(libc)
   ↓
系统调用接口(sys_write)
   ↓
内核文件系统 & 驱动

fwrite->将数据从进程拷贝到缓冲区中

系统调用 I/O

例如:

复制代码
write(fd, buf, size);

特点:

没有 stdio 缓冲

直接进入内核

执行流程:

复制代码
用户程序
  ↓
write()
  ↓
进入内核
  ↓
设备驱动
  ↓
输出设备

因此:

write() 每调用一次,就会触发一次系统调用。

为什么会存在printf不立即输出的问题?

printf()并不会立即调用write()

它先把数据写入用户态缓冲区(由libc)管理,只有以下情况才会刷新到内核:

  • 缓冲区满
  • 输出流遇到换行符(行缓冲)
  • 显式调用fflush
  • 程序正常退出(自动刷新)
    举例:
c 复制代码
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main()
{
    // C接口
    printf("hello printf\n");
    fprintf(stdout, "hellp fprintf\n");
    const char *fputsString = "hello fputs\n";
    fputs(fputsString, stdout);


    // 系统接口
    const char *wstring = "hello write\n";
    write(1, wstring, strlen(wstring));

    //代码结束之前,进行创建子进程
    //1. 如果我们没有进行>,看到了4条消息
    //   stdout 默认使用的是行刷新,在进程fork之前,三条C函数已经将数据进行打印输出到显示器上(外设),你的FILE内部,进程内部不存在对应的数据啦
    //2. 如果我们进行了>, 写入文件不再是显示器,而是普通文件,采用的刷新策略是全缓冲,之前的3条c显示函数,虽然带了\n,但是不足以stdout缓冲区写满!数据并没有被刷新!!!
    //执行fork的时候,stdout属于父进程,创建子进程时, 紧接着就是进程退出!谁先退出,一定要进行缓冲区刷新(就是修改)
    //写时拷贝!!数据最终会显示两份
    //3. write为什么没有呢?上面的过程都和wirte无关,wirte没有FILE,而用的是fd,就没有C提供的缓冲区
    fork(); // fork

    return 0;
}

示例分析:

printf
c 复制代码
 printf("hello printf\n");
流程:
printf() 被调用
↓
格式化字符串内容(如 %d, %s 等处理)
↓
将生成的字符串写入 stdout 的缓冲区(FILE* 结构体内的缓冲区)
↓
发现字符串中包含换行符 \n(且输出目标是终端)
→ 触发 行缓冲刷新
↓
调用 write(1, buf, len)(真正的系统调用)
↓
内核根据文件描述符 1(stdout)找到对应终端设备
↓
将数据交给终端驱动程序显示在屏幕上
fprintf
c 复制代码
fprintf(stdout, "hellp fprintf\n");
流程几乎相同,只是显式指定输出流:
fprintf(stdout, ...)
↓
数据写入 stdout 的缓冲区
↓
检查是否该刷新(含 \n 或调用 fflush())
↓
若刷新 → 调用 write(1, buf, len)
↓
内核写入终端设备
区别:
fprintf() 允许你选择输出流,比如 stderr、文件等,不限于标准输出。
fputs
c 复制代码
fputs("hello fputs\n", stdout);
流程:
将字符串直接拷贝进 stdout 缓冲区(不会自动加 \n)
↓
若输出流为终端并且字符串中含 \n → 行缓冲触发刷新
↓
调用 write(1, buf, len)
↓
内核写入终端设备
注意:
fputs() 不会自动在结尾加换行符,不加 \n 时数据可能仍停留在用户缓冲区中。
write
c 复制代码
write(1, "hello write\n", 12);
流程(系统调用路径):
用户调用 write()
↓
进入内核(系统调用号 __NR_write)
↓
内核函数:
c 复制代码
sys_write(fd, buf, count)
{
    struct file *file = current->files->fd[fd];
    return file->f_op->write(file, buf, count, &file->f_pos);
}
复制代码
↓
根据文件描述符 1 找到文件对象(终端)
↓
调用终端驱动的 .write() 函数
↓
设备驱动将数据送到显示缓冲区,终端显示内容
特点:
直接进入内核,无任何用户态缓冲;
写完立即生效。
fwrite
c 复制代码
fwrite(buf, 1, len, fp);

fwrite()的作用是:把数据从用户进程拷贝到内核缓冲区

真正写入磁盘的操作:

  • 缓冲区满
  • 程序调用fflush()
  • 文件关闭fclose()
  • 系统周期性刷新时

缓冲区的意义:

缓冲区的核心作用:节省时间,提高效率

术语 含义
缓冲区(Buffer) 位于内存中的一块暂存区,用于存放等待输入或输出的数据
缓冲I/O(Buffered I/O) 程序与设备之间通过缓冲区进行间接通信的I/O模式
非缓冲I/O(Unbuffered I/O) 程序直接与设备交互,不经过缓冲区,速度慢但及时
fwrite() 将数据从用户空间拷贝到内核缓冲区
fflush() 强制把缓冲区的数据写入磁盘
fread() 从内核缓冲区读取数据到用户内存
write() 系统调用 直接进入内核态执行I/O(不经过C库缓冲)
缓冲区的存在是为了在快的内存和慢的外设之间架起桥梁
它让CPU不必一直等待慢速磁盘的回应,提高程序的执行效率

缓冲区刷新策略:

外设的I/O速度远慢于内存

一次I/O调用的"等待时间"通常比"传输时间"长的多

所以:

  • 一次性写大块数据(批量)比多次写小块数据效率高的多
  • 因此缓冲区会"攒一攒",等积累到一定量再一次性写入磁盘
    两种特殊刷新时机:
  1. 手动刷新
    • 程序员主动调用fflush(fp)
  2. 系统触发刷新
    • 程序正常关闭(fclose(fp))
    • 程序异常结束(可能丢失未刷数据)
    • 缓冲区被新数据填满
      缓冲区刷新策略的三种主要类型:
策略名称 类比(顺丰故事) 行为描述 应用场景
无缓冲(Unbuffered I/O) 顺丰为你一个键盘开飞机 每次 fwrite() 都立刻写入外设 系统级日志、关键安全数据
行缓冲(Line Buffered) 顺丰每装满一箱(或每行)就发 以"行结束符"(\n)为界触发刷新 终端输入输出(如 stdout
全缓冲(Fully Buffered) 顺丰装满整车货才发 缓冲区满时才写入外设 普通文件写入(FILE* fp 默认)
无缓冲:
数据一旦写入缓冲,就会立即输出到外设,不经过任何延迟或积累
特点:
  • 几乎实时刷新
  • 开销较大(频繁I/O调用)
  • 通常用于用户交互类设备
    标准错误流stderr默认是无缓冲的,希望错误信息立即显示
    行缓冲
    当输出数据遇到换行符\n,或者缓冲被填满时,系统才将缓冲区的数据刷新到设备
    特点:
  • 兼顾性能与交互体验
  • 适合"面向人阅读"的输出设备(如显示器)
  • 用户可手动调用 fflush(stdout) 进行强制刷新
    在实现进度条时,如果 printf 语句没有带换行符,则输出不会立即显示,除非显式调用:
c 复制代码
fflush(stdout);

这说明标准输出设备(stdout)通常采用行缓冲策略。

全缓冲策略:

只有当缓冲区被完全填满时,系统才会将数据一次性写入外设

特点:

  • 效率最高:仅在缓冲区满时执行一次 I/O
  • 适合非交互式设备,如磁盘文件、网络套接字
  • 适合批量数据写入场景
c 复制代码
FILE *fp = fopen("data.txt", "w");
fprintf(fp, "Hello, world!");
// 数据暂存在缓冲区,未必立即写入磁盘
fclose(fp); // 或 fflush(fp) 后,数据才真正写入文件

总体逻辑图:

复制代码
应用层数据
     ↓
   缓冲区
     ↓
  刷新策略决定何时写入外设
     ↓
   外设(显示器 / 文件 / 网络)

语言级缓冲区:

  1. 由C语言标准I/O库提供,用于暂存应用程序输出数据,提高I/O效率
    属于用户态内存空间,由库函数自行管理
  2. 系统级缓冲区
    属于操作系统内核,负责在设备驱动层次进一步缓存数据

FILE结构体

FILE结构体详解

C标准库缓冲区的真正位置

在C标准库中,所有通过标准I/O接口(如printf,fprintf())等进行的操作,实际是通过一个名为FILE的结构体对象完成的
FILE结构体的定义

c 复制代码
typedef struct _IO_FILE {
    int _fileno;          // 文件描述符,对应内核级文件
    char *_IO_buf_base;   // 缓冲区起始地址
    char *_IO_buf_end;    // 缓冲区结束地址
    char *_IO_write_ptr;  // 当前写指针
    char *_IO_read_ptr;   // 当前读指针
    // ... 其他标志位与状态信息
} FILE;

说明:

  • FILE结构体封装了底层的文件描述符(fd)以及一块用户态缓冲区
  • 这块缓冲区正是缓冲区刷新策略所作用的区域
  • 对应FILE指针(例如stdout,stdin,stderr)即是指向这些结构体的指针
    缓冲区的刷新和管理策略:
    C语言库会根据设备类型自动选择缓冲策略(即setvbuf的默认策略)
设备类型 默认缓冲模式 说明
标准输入(键盘) 行缓冲 输入每行结束刷新
标准输出(显示器) 行缓冲 输出带换行符或缓冲满时刷新
标准错误(stderr) 无缓冲 错误信息即时输出
文件(磁盘) 全缓冲 缓冲区写满或关闭文件时刷新
刷新行为出发的典型情况包括:
  1. 缓冲区满
  2. 输出遇到换行符(行缓冲)
  3. 调用`fflush(FILE *stream)
  4. 调用fclose(FILE *stream)(会隐式调用fflush)
  5. 程序正常结束(运行时库会清空所有缓冲区)

特殊情况:

  1. 用户强制刷新
c 复制代码
fflush(stdout)

即为用户强制刷新缓冲区,此时不论缓冲策略为何,数据都会被立即送入内核

  1. 程序或进程退出时刷新

当进程正常结束时,C 语言运行时会自动调用清理函数,将所有仍在缓冲区中的数据刷新到目标设备。

  1. fork()导致输出重复问题

若程序在 printf 输出后但尚未 fflush 时执行 fork(),子进程会复制父进程的用户态缓冲区。于是父子进程都持有相同的缓冲数据,且各自执行 fflush 或进程退出时都会输出一次,从而造成输出重复。

结论:

此现象证明缓冲区位于用户态内存中(即进程私有空间),若缓冲区在内核中,fork 后不会被复制,也不会导致重复输出。

FILE与缓冲区的联系

所有标准I/O操作都必须通过FILE指针进行,因为:

  • FILE 结构体中包含了缓冲区的指针;
  • 只有通过 FILE*,库函数才能定位到对应缓冲区;
  • 当执行 fflush(FILE *fp)fclose(FILE *fp) 时,系统才能正确刷新对应的缓冲区内容。
c 复制代码
FILE *fp =    fopen("data.txt", "w");
fprintf(fp, "Buffered Output\n");
fflush(fp);  // 强制刷新
fclose(fp);  // 关闭文件并刷新缓冲区

缓冲区位置与层次图

复制代码
┌────────────────────────────┐
│         用户空间 (User Space
│ ┌──────────────────────────┐
│ │ C 标准库 FILE 结构体缓冲区  
│ │ (语言级缓冲区)           
│ └──────────────┬───────────┘ 
│                │ write()/read() 调用   
└────────────────┼────────────────────
                 ▼
┌────────────────────────────┐
│         内核空间 (Kernel Space)     
│ ┌──────────────────────────┐ 
│ │ 内核 I/O 缓冲区(页缓存、设备队列)   
│ └──────────────────────────┘ 
└────────────────────────────┘

![[Pasted image 20251116212126.png]]

如果一个文件没有被打开->在磁盘上放着->磁盘上有大量的文件,也必须被静态管理,方便我们随时打开->文件系统

磁盘

磁盘基本结构

盘面:4

磁道/面:10

扇区/磁道:100

扇区:521Byte

4*10*100*512==总容量

4*10*100=下标范围

10*100=1000扇区 每一面

LBA:123号位置

123/1000 = 0--0号盘面

123/100=1 --1号磁道

123%100 = 23--23号扇区

为什么操作系统使用逻辑块

虽然磁盘最小单位是:

512B

但操作系统通常使用:

4KB block

原因:

  1. 提高 I/O 效率
  2. 减少磁盘寻址
  3. 利用局部性原理
  4. 便于管理
  5. 不想让os的代码和硬件强耦合
    因此文件系统读写通常是:
    4KB
    虽然对应的磁盘访问的基本单位是512字节,但是依旧很小->OS内的文件系统定制的多个扇区的读写->1KB,2KB,4KB为基本单位,哪怕指向读取或修改bit,也必须将4KBload内存,进行读取或者修改,如果必要再写回磁盘

局部性原理

(4GB)内存是被划分成了4KB大小的空间 - 页框

磁盘中的文件尤其是可执行文件-按照4KB大小划分好的块--页帧

分组


I/O 到磁盘的完整流程

整个过程如下:

复制代码
用户程序  
   ↓  
printf  
   ↓  
stdio buffer  
   ↓  
write()  
   ↓  
内核 page cache  
   ↓  
文件系统  
   ↓  
inode  
   ↓  
data block  
   ↓  
磁盘

Linux 文件系统与 inode 机制

当程序执行 write() 时,数据并不是直接写入磁盘,而是交给 文件系统 管理。

Linux 文件由两部分组成:

文件 = 文件属性 + 文件内容

在 Linux 文件系统中:

部分 存储位置
文件属性 inode
文件数据 data block

Inode

inode简介

inode(index node) 是文件的元数据结构。

每个文件都有一个 inode。

inode 保存:

  • 文件大小
  • 文件权限
  • 所有者
  • 时间戳
  • 数据块指针
    例如:
bash 复制代码
ls -i file.txt

输出:

bash 复制代码
23456 file.txt

其中 123456 就是 inode 号。

inode 的多级索引结构

文件 = inode + data block

inode 记录文件属性,同时保存 数据块的位置

但是一个问题出现了:

如果文件很大,一个 inode 如何记录大量数据块?

Linux 采用 多级索引结构

inode 的数据块指针

在 Linux(EXT 系列文件系统)中,一个 inode 内通常包含 15 个指针

复制代码
inode  
 │  
 ├── 12 个直接块指针  
 ├── 1 个一级间接块  
 ├── 1 个二级间接块  
 └── 1 个三级间接块

示意:

复制代码
node  
 │  
 ├─ direct[0]  
 ├─ direct[1]  
 ├─ direct[2]  
 ...  
 ├─ direct[11]  
 │  
 ├─ single indirect  
 │  
 ├─ double indirect  
 │  
 └─ triple indirect

直接块(Direct Block)

12 个指针直接指向数据块:

复制代码
inode  
 ├── block0  
 ├── block1  
 ├── block2  
 ...  
 └── block11

如果每个 block 是:

4KB

那么:

12 × 4KB = 48KB

小文件可以直接访问,非常快。


一级间接块

如果文件超过 48KB,就会使用 一级间接块

复制代码
inode  
 │  
 └── single indirect  
        │  
        ├── block  
        ├── block  
        ├── block

一级间接块本身是一个 数据块 ,里面存放 大量数据块地址

假设:

block = 4KB

地址大小 = 4B

一个块可以存:

4096 / 4 = 1024 个地址

因此一级间接块可以管理:

1024 × 4KB ≈ 4MB


二级间接块

如果文件继续变大:

复制代码
inode  
  │  
  └── double indirect  
          │  
          ├── 一级索引块  
          │     ├── 数据块  
          │  
          ├── 一级索引块

容量:

1024 × 1024 × 4KB

≈ 4GB


三级间接块

最后是三级索引:

复制代码
inode  
 │  
 └── triple indirect  
        │  
        └── 二级索引块  
              │  
              └── 一级索引块  
                    │  
                    └── 数据块

容量:

1024 × 1024 × 1024 × 4KB

≈ 4TB


inode 索引结构总结
类型 容量
12直接块 48KB
一级间接块 4MB
二级间接块 4GB
三级间接块 4TB

这样 Linux 就能 同时兼顾小文件效率和大文件扩展能力


EXT4 文件系统结构

当磁盘被格式化为 Linux 文件系统(如 EXT4)时,会被组织成如下结构:

复制代码
磁盘  
 │  
 ├── Super Block  
 ├── Group Descriptor  
 ├── Block Bitmap  
 ├── inode Bitmap  
 ├── inode Table  
 └── Data Blocks

SuperBlock(超级块)

超级块是文件系统最重要的结构。

它保存:

  • 文件系统大小
  • block 大小
  • inode 数量
  • 空闲块数量
  • 挂载信息
    可以查看:
bash 复制代码
dumpe2fs /dev/sda1

Block Bitmap

Block Bitmap 用来管理 哪些数据块被使用

例如:

1 = 已使用

0 = 空闲

示例:

复制代码
Block Bitmap  
​  
1 1 1 0 0 0 1 1

表示:

block3

block4

block5

可用


inode Bitmap

inode 也需要管理:

inode Bitmap

例如:

inode1 已使用

inode2 已使用

inode3 空闲


inode Table

inode Table 存放 所有 inode 结构体

复制代码
inode table  
​  
inode1  
inode2  
inode3  

每个 inode 对应一个文件。


Data Block

数据块存放:

文件内容

例如:

data block

Hello Linux


EXT4 文件系统整体结构

完整结构:

复制代码
磁盘  
 │  
 ├── SuperBlock  
 │  
 ├── Block Bitmap  
 │  
 ├── inode Bitmap  
 │  
 ├── inode Table  
 │  
 └── Data Blocks

示意:

复制代码
+-------------------+  
| Super Block       |  
+-------------------+  
| Block Bitmap      |  
+-------------------+  
| inode Bitmap      |  
+-------------------+  
| inode Table       |  
+-------------------+  
| Data Blocks       |  
+-------------------+


文件

文件=文件+属性

Linux的文件属性和文件内容是分批存储的

文件属性:Inode是固定大小

一个文件一个Inode-》文件几乎所有的属性

文件名不在Inode中存储

文件内容:data block

随着应用类型变化-》大小在变化

inode为了区分彼此,每一个inode都有自己的id

目录创建文件:

很多人误以为文件名存在 inode 中。

实际上:

文件名存储在目录中,而不是 inode 中。

目录本质是一个特殊文件。

目录内容:

文件名 → inode号

示例:

bash 复制代码
file.txt → inode 123456

因此:

复制代码
路径解析  
 ↓  
找到 inode  
 ↓  
读取数据块

文件创建过程

当执行:

bash 复制代码
touch file.txt

Linux 实际做了以下事情:

  1. 分配 inode

  2. 更新 inode bitmap

  3. 在目录中添加

  4. 文件名 → inode
    流程:

    目录

    └── file.txt → inode123


文件读取流程

当程序读取文件:

bash 复制代码
open("file.txt")

执行流程:

复制代码
路径解析  
 ↓  
找到 inode  
 ↓  
读取 inode  
 ↓  
找到 data block  
 ↓  
读取数据

完整流程:

复制代码
程序  
 ↓  
VFS  
 ↓  
inode  
 ↓  
data block  
 ↓  
磁盘

Linux 文件系统整体流程

最终我们可以把整个链路串起来:

复制代码
程序  
 ↓  
printf  
 ↓  
stdio buffer  
 ↓  
write  
 ↓  
VFS  
 ↓  
inode  
 ↓  
data block  
 ↓  
磁盘扇区

Linux 文件系统设计遵循三个核心思想:

  1. 抽象
    磁盘被抽象为:文件

  2. 分离
    文件被拆分为:
    inode(属性)
    data block(数据)

  3. 引用计数
    删除文件机制:

    c 复制代码
    link count

    类似:
    C++ shared_ptr


Linux文件系统思想

Linux 文件系统可以用一句话总结:

文件名只是 inode 的映射,真正的数据存储在 data block 中。

完整流程:

复制代码
程序  
 ↓  
stdio  
 ↓  
write  
 ↓  
文件系统  
 ↓  
inode  
 ↓  
data block  
 ↓  
磁盘

粘滞位:

在所有人都可共享的文件夹

在一个目录删除文件->跟这个文件目录是否具有写权限有关

粘滞位:是所有可以自由的写入文件目录,而限制人的删除权限

软硬链接

Linux 支持两种链接:

硬链接

软链接


硬链接(Hard Link)

硬链接的理解

硬链接没有独立的inode-如何理解硬链接

建立一个硬链接具体做了什么?

建立硬链接根本没有创建文件,因为没有给硬链接分配独立的inode

没有创建文件-》无属性和内容集合-》别人的inode和内容

因此硬链接本质就是:在指定的文件路径下新增文件名和inode编号的映射关系

inode引用计数->硬链接数->只有有一个链接到inode->引用计数-1->shared_ptr

创建硬链接:

bash 复制代码
ln file.txt file2.txt

此时:

bash 复制代码
file.txt  → inode123  
file2.txt → inode123

两者 共享同一个 inode

示意:

复制代码
file.txt  
     \  
      → inode  
     /  
file2.txt

特点:

  • 没有新 inode
  • 只是新增目录映射
  • 数据完全共享

硬链接删除原理

什么时候一个文件算被真正的删除呢?

当一个文件的硬链接数变成0的时候->才算被真正删除

![[Pasted image 20251212203855.png]]

删除文件时:

bash 复制代码
rm file.txt

实际上只是:

inode 引用计数 -1

只有当:

复制代码
link count = 0

文件才真正删除。

这类似于:

C++ shared_ptr


为什么目录不能创建硬链接

Linux 禁止普通用户给目录创建硬链接。

原因是:

防止目录结构出现循环。

例如:

复制代码
dirA  
 └── dirB

如果允许:

bash 复制代码
ln dirA dirA/sub

会变成:

复制代码
dirA  
 └── sub → dirA

形成无限循环:

复制代码
dirA/sub/sub/sub/sub...

这会破坏文件系统。

Linux 禁止对目录创建硬链接,就是为了防止用户把目录硬链接到它的父目录或更上层,从而造成循环结构,破坏整个文件系统的稳定性。

为什么创建的普通文件的硬链接数为1呢?

一个普通关系,本身有一个文件和自己的inode具有一个映射关系

创建一个空目录,为什么目录的硬链接数为2?

目录建立目录-》引用计数增加的根本原因


软链接(Symbolic Link)

软链接的理解

软链接本质是它的数据块保存它所指向的目标文件的路径,他是独立文件独立inode

相当于windows的快捷方式

创建软链接:

bash 复制代码
ln -s file.txt link.txt

此时:

复制代码
link.txt → inode456
inode456 内部存储:

file.txt 的路径

示意:

复制代码
link.txt  
   ↓  
"file.txt"  
   ↓  
inode123

软链接类似于:

Windows 快捷方式

特点:

  • 有独立 inode
  • 存储目标路径
  • 可以跨文件系统

软硬链接对比

特性 硬链接 软链接
inode 共享 独立
跨文件系统 不支持 支持
删除原文件 仍可访问 失效
本质 inode映射 路径引用
软硬链接的区别:是否具有独立的inode
软链接具有独立的inode-可以被当作独立文件看待

Linux的三个时间

change time:文件属性修改的最近时间

modify time:文件内容改变的时间-》会引起chang time改变

access time:文件内容最近被访问的时间-》只有时间到一定时间刷新-》才会更改

相关推荐
HLC++2 小时前
C++中的类和对象
开发语言·c++
子有内涵2 小时前
【Linux】程序地址空间(是什么?为什么?)
linux·运维·算法
setmoon2142 小时前
C++与量子计算模拟
开发语言·c++·算法
异步的告白2 小时前
嵌入式Linux学习-默认规则
linux
Amnesia0_02 小时前
C++的异常
开发语言·c++·学习
每天回答3个问题2 小时前
LeetCodeHot100|链表总结
数据结构·c++·链表
2301_793804692 小时前
C++安全编程指南
开发语言·c++·算法
m0_518019482 小时前
分布式系统安全通信
开发语言·c++·算法
轩情吖2 小时前
MySQL内置函数
android·数据库·c++·后端·mysql·开发·函数