1. Linux文件系统概述

文件系统是Linux操作系统的核心组件之一,负责管理存储设备上的文件数据、目录结构、权限信息及系统资源, 是连接用户、应用程序与硬件存储的桥梁。不同于Windows的单一文件系统架构,Linux采用分层、模块化的文件系统设计, 支持多种物理存储文件系统、统一的虚拟文件接口,同时依托伪文件系统实现系统信息交互,兼具灵活性、稳定性与兼容性。

1.1. Linux文件系统特性

Linux文件系统秉承Unix设计哲学,具备诸多区别于其他系统的核心特性,也是其高效稳定的关键:

  • 一切皆文件:Linux中所有资源均以文件形式呈现,统一操作接口,简化系统调用与程序开发;

  • 单根目录结构:采用唯一的根目录/,所有存储设备、文件、目录均挂载在根目录下,无Windows盘符概念,目录结构层级清晰;

  • 多文件系统兼容:通过虚拟文件系统层,支持ext4、XFS、Btrfs、NTFS、FAT32等数十种文件系统,适配不同存储场景;

  • 权限与归属严格管控:基于文件权限、用户/组归属、SELinux等机制,实现精细化的文件访问控制,保障系统安全;

  • 日志式存储:现代Linux文件系统均支持日志功能,避免异常断电导致的数据丢失与文件系统损坏,提升数据可靠性。

1.2. Linux文件类型

1.2.1. 目录文件

标识字符:d

用于管理文件层级结构,本质是存储子文件/子目录的列表,不直接存储文件数据,仅维护文件索引。 Linux中目录是特殊的文件,支持创建、删除、重命名、遍历等操作,核心目录遵循FHS标准。

1
2
3
4
5
#查看/etc详细信息
ls -ld /etc

#信息输出如下
drwxrwxr-x 111 1001 1001 4096 Mar  4 14:31 /etc

/etc属性首字符为d,表示是目录文件。

1.2.2. 字符设备文件

标识字符:c

对应硬件字符设备,以字节流形式传输数据,无缓冲区,读写操作直接与硬件交互,属于裸设备文件。 常见于键盘、鼠标、串口、终端(tty)等低速设备。

1
2
3
4
5
#查看tty1详细信息
ls -l /dev/tty1

#信息输出如下
crw--w---- 1 root tty 4, 1 Feb 14  2019 /dev/tty1

/dev/tty1属性首字符为c,表示是字符设备文件。

1.2.3. 块设备文件

标识字符:b

对应硬件块设备,以数据块为单位传输数据,自带缓冲区,读写效率更高,用于硬盘、U盘、分区等高速存储设备。 通过块设备文件,系统可访问存储设备的分区与数据。

1
2
3
4
5
#查看板卡eMMC块设备文件
ls -l /dev/mmcblk0

#信息输出如下
brw-rw---- 1 root disk 179, 0 Feb 14  2019 /dev/mmcblk0

/dev/mmcblk0属性首字符为b,表示是块设备文件。

1.2.4. 管道文件

标识字符:p

分为匿名管道与命名管道(FIFO),用于进程间通信(IPC),实现数据单向传输。 匿名管道随进程创建销毁,命名管道以实体文件形式存在,可跨终端、跨进程传输数据。

1
2
3
4
5
6
7
8
#创建命名管道
mkfifo test_pipe

#查看命名管道详细信息
ls -l test_pipe

#信息输出如下
prw-r--r-- 1 root root 0 Mar  5 09:04 test_pipe

test_pipe属性首字符为p,表示是管道文件。

1.2.5. 套接字文件

标识字符:s

用于本地进程间网络通信,支持双向数据传输,相比管道功能更强大,是Linux网络服务与进程通信的核心载体, 如MySQL、Nginx等服务常通过套接字文件实现本地交互。

1
2
3
4
5
#安装MySQL后查看mysqld.sock详细信息
ls -l /var/run/mysqld/mysqld.sock

#信息输出如下
srwxrwxrwx 1 mysql mysql 0 Mar  5 09:10 /var/run/mysqld/mysqld.sock

mysqld.sock属性首字符为s,表示是套接字文件。

1.2.6. 符号链接文件

标识字符:l

即软链接,类似Windows快捷方式,存储的是目标文件的路径,而非实际数据。 可跨文件系统创建,若目标文件删除,软链接失效。

1
2
3
4
5
#查看/bin详细信息
ls -l /bin

#信息输出如下
lrwxrwxrwx 1 root root 7 Sep 17 13:33 /bin -> usr/bin

/bin属性首字符为l,表示是符号链接文件,实际指向/usr/bin。

1.3. inode索引节点

inode(Index Node)是Linux文件系统的核心组件,负责存储文件的元数据信息,是文件的“身份证”, 每个文件对应唯一的inode号,目录仅记录文件名与inode号的映射关系,文件数据存储在磁盘数据块中。

1.3.1. inode存储的信息

inode不存储文件名,仅记录文件的关键元数据,包含以下内容:

  • 文件类型(普通文件/目录/设备等);

  • 文件权限(读/写/执行)、归属信息(所有者UID、所属组GID);

  • 文件大小、占用的数据块数量、数据块指针;

  • 文件时间戳:访问时间(atime)、修改时间(mtime)、状态改变时间(ctime);

  • 链接计数(硬链接数量,计数为0时文件被删除);

  • 数据块地址(指向存储文件实际数据的磁盘块)。

具有以下特性:

  • inode号唯一标识:同一文件系统内,inode号不可重复,跨文件系统可重复;

  • 硬链接本质:多个文件名指向同一inode号,链接计数+1,删除硬链接仅减少计数,不删除数据,计数为0时数据才被释放;

  • 文件移动特性:同一分区内移动文件,仅修改目录映射,inode号与数据块不变;跨分区移动,相当于复制+删除,inode号变更。

1.3.2. inode相关命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 查看文件inode号
ls -i 文件名

# 查看文件详细inode信息
stat 文件名

# 查看分区inode总量与使用情况
df -i

# 查看指定目录下文件的inode信息
find 目录 -type f -exec stat {} \;

1.4. 存储设备文件系统

提到文件系统时,我们首先想到的通常是Windows下的FAT32、NTFS、exFAT以及Linux下常用的ext2、ext3和ext4的类型格式。 这些文件系统都是为了解决如何高效管理存储器空间的问题而诞生的。

从EEPROM、Nor FLASH、NAND FLASH、eMMC到机械硬盘,各种各样的存储器本质就是具有多个能够存储0和1数据单元的设备。 存储内容时,如果程序需要直接访问这些存储单元的物理地址来保存内容,会带来极大的不便,如难以记录有效数据的位置, 难以确定存储介质的剩余空间,以及应以何种格式来解读数据,如同一个巨大的图书馆无人管理,杂乱无章地堆放着各种书籍,难以查找。

为了高效地存储和管理数据,文件系统在存储介质上建立了一种组织结构,这些结构包括操作系统引导区、目录和文件, 就如同图书馆给不同类的书籍进行分类、编号,放在不同的书架上。不同的管理理念引出了不同的文件系统标准, 上述的FAT32、 NTFS、 exFAT、ext2/3/4就是指不同类型的标准,除此之外,还有专门针对NAND类型设备的文件系统jffs2、yaffs2等等。

正是有了文件系统,计算机上的数据才能以文件的形式呈现给用户。

下面简单介绍一下各种不同标准文件系统的特性:

  • FAT32格式:兼容性好, STM32等MCU也可以通过Fatfs支持FAT32文件系统,大部分SD卡或U盘出厂默认使用的就是FAT32文件系统。 它的主要缺点是技术老旧,单个文件不能超过4GB,非日志型文件系统。

  • NTFS格式:单个文件最大支持256TB、支持长文件名、服务器文件管理权限等,而且NTFS是日志型文件系统。 但由于是日志型文件系统,会记录详细的读写操作,相对来说会加快FLASH存储器的损耗。 文件系统的日志功能是指,它会把文件系统的操作记录在磁盘的某个分区,当系统发生故障时,能够尽最大的努力保证数据的完整性。

  • exFAT格式:基于FAT32改进而来,专为FLASH介质的存储器设计(如SD卡、U盘),空间浪费少。单个文件最大支持16EB,非日志文件系统。

  • ext2格式:简单,文件少时性能较好,单个文件不能超过2TB。非日志文件系统。

  • ext3格式:相对于ext2主要增加了支持日志功能。

  • ext4格式:从ext3改进而来,ext3实际是ext4的子集。它支持1EB的分区,单个文件最大支持16TB,支持无限的子目录数量, 使用延迟分配策略优化了文件的数据块分配,允许自主控制是否使用日志的功能。

  • jffs2和yaffs2格式: jffs2和yaffs2是专为FLASH类型存储器设计的文件系统, 它们针对FLASH存储器的特性加入了擦写平衡和掉电保护等特性。由于Nor、NAND FLASH类型存储器的存储块的擦写次数是有限的(通常为10万次), 使用这些类型的文件系统可以减少对存储器的损耗。

总的来说,在Linux下,ext2适用于U盘,但为了兼容,使用得比较多的还是FAT32或exFAT,日常应用推荐使用ext4, 而ext3使用的场景大概就只剩下对ext4格式的稳定性还有疑虑的用户了,但ext4从2008年就已结束实验期,进入稳定版了,可以放心使用。

Linux内核本身也支持FAT32文件系统,而使用NTFS格式则需要安装额外的工具如ntfs-3g。所以使用板卡出厂的默认Linux系统时, 把FAT32格式的U盘直接插入到板卡是可以自动挂载的,而NTFS格式的则不支持。主机上的Ubuntu对于NTFS或FAT32的U盘都能自动识别并挂载, 因为Ubuntu发行版安装了相应的支持。目前微软已公开exFAT文件系统的标准,且已把它开源至Linux,未来Linux可能也默认支持exFAT。

对于非常在意FLASH存储器损耗的场合,则可以考虑使用jffs2或yaffs2等文件系统。

在Linux下,可以通过如下命令查看系统当前存储设备使用的文件系统:

1
2
#在主机或板卡上执行如下命令
df -T

如下图:

未找到图片02|

从上图可以看出,主机的硬盘设备为“/dev/root”,它使用的文件系统 类型为ext4,挂载点是根目录“/”。

1.5. 伪文件系统

除了前面介绍的专门用于存储设备记录文件的文件系统外,Linux内核还提供了procfs、sysfs和devfs等伪文件系统。

伪文件系统存在于内存中,通常不占用硬盘空间,它以文件的形式,向用户提供了访问系统内核数据的接口。 用户和应用程序可以通过访问这些数据接口,得到系统的信息,而且内核允许用户修改内核的某些参数。

procfs是“process filesystem”的缩写,所以它也被称为进程文件系统,procfs通常会自动挂载在根目录下的/proc文件夹。 procfs为用户提供内核状态和进程信息的接口,功能相当于Windows的任务管理器。

使用如下命令可以查看proc文件系统的内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#查看proc目录
ls /proc

#信息输出如下
1     1255   21351  317   50    712        bus            meminfo
10    1261   21617  35    51    72         cgroups        misc
100   1262   21618  36    52    73         cmdline        modules
101   1268   22     37    53    74         config.gz      mounts
1016  1276   22857  378   54    75         consoles       mpp_service
102   1287   236    38    55    76         cpuinfo        mtd
103   13     23764  39    5676  77         crypto         net
104   1324   2387   4     59    78         device-tree    pagetypeinfo
106   13882  24     40    60    79         devices        partitions
108   14     25     41    61    7997       diskstats      rkisp-vir0
1082  14532  25000  414   62    8          driver         sched_debug
109   1493   25004  42    63    82         dynamic_debug  schedstat
11    15     25085  44    634   84         execdomains    scsi
110   1501   26     444   64    863        fb             self
1105  15153  261    449   647   87         filesystems    slabinfo
1111  1517   264    452   65    876        fs             softirqs
1141  153    29     459   66    88         interrupts     stat
1146  157    2910   46    67    89         iomem          swaps
1190  16     29102  464   675   9          ioports        sys
1191  16673  2913   466   677   90         irq            sysrq-trigger
12    177    29158  47    678   907        kallsyms       sysvipc
1205  178    2924   473   680   91         key-users      thread-self
1217  19     2943   474   69    92         keys           timer_list
1230  19283  29471  479   70    95         kmsg           tty
1240  2      3      48    708   98         kpagecgroup    uptime
1243  20     30     482   709   99         kpagecount     version
1249  20075  31     4898  71    991        kpageflags     vmallocinfo
1252  21     31064  49    710   asound     loadavg        vmstat
1254  213    31461  4900  711   buddyinfo  locks          zoneinfo

/proc包含了非常多以数字命名的目录,这些数字就是进程的PID号,其它文件或目录的一些说明见下表。

文件名

作用

pid*

*表示的是进程的 PID 号,系统中当前运行的每一个进程都有对应的一个目录,用于记录进程所有相关信息。对于操作系统来说,一个应用程序就是一个进程

self

该文件是一个软链接,指向了当前进程的目录,通过访问/proc/self/目录来获取当前进程的信息,就不用每次都获取pid

thread-self

该文件也是一个软链接,指向了当前线程,访问该文件,等价于访问“当前进程pid/task/当前线程tid”的内容。。一个进程,可以包含多个线程,但至少需要一个进程,这些线程共同支撑进程的运行。

version

记录了当前运行的内核版本,通常可以使用命令“uname –r”

cpuinfo

记录系统中CPU的提供商和相关配置信息

modules

记录了目前系统加载的模块信息

meminfo

记录系统中内存的使用情况,free命令会访问该文件,来获取系统内存的空闲和已使用的数量

filesystems

记录内核支持的文件系统类型,通常mount一个设备时,如果没有指定文件系统并且它无法确定文件系统类型时,mount会尝试包含在该文件中的文件系统,除了那些标有“nodev”的文件系统。

下面我们以当前bash的进程pid目录,来了解proc文件系统的一些功能。

1
2
3
4
5
6
7
8
#查看当前bash进程的PID号
ps

#信息输出如下
  PID TTY          TIME CMD
  675 ttyFIQ0  00:00:00 login
 2943 ttyFIQ0  00:00:00 bash
21773 ttyFIQ0  00:00:00 ps

每个人的计算机运行运行状况不一样,所以得到的进程号也是不一样,当前得到bash进程的pid是2943。

根据这个pid号,查看proc/2943目录的内容,它记录了进程运行过程的相关信息。

执行如下命令:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#把目录中的数字改成自己bash进程的pid号
ls /proc/2943

#信息输出如下
auxv             exe        mounts         projid_map    statm
cgroup           fd         mountstats     root          status
clear_refs       fdinfo     net            sched         syscall
cmdline          gid_map    ns             schedstat     task
comm             limits     oom_adj        setgroups     timerslack_ns
coredump_filter  map_files  oom_score      smaps         uid_map
cpuset           maps       oom_score_adj  smaps_rollup  wchan
cwd              mem        pagemap        stack
environ          mountinfo  personality    stat

该目录下的一些文件夹和文件的意义如下表。

文件名

文件内容

cmdline

只读文件,记录了该进程的命令行信息,如命令以及命令参数

comm

记录了进程的名字

environ

进程使用的环境变量

exe

软连接文件,记录命令存放的绝对路径

fd

记录进程打开文件的情况,以文件描述符作为目录名

fdinfo

记录进程打开文件的相关信息,包含访问权限以及挂载点,由其文件描述符命名。

io

记录进程读取和写入情况

map_files

记录了内存中文件的映射情况,以对应内存区域起始和结束地址命名

maps

记录当前映射的内存区域,其访问权限以及文件路径。

stack

记录当前进程的内核调用栈信息

status

记录进程的状态信息

syscall

显示当前进程正在执行的系统调用。第一列记录了系统调用号

task

记录了该进程的线程信息

wchan

记录当前进程处于睡眠状态,内核调用的相关函数

如果是文件,可以直接使用cat命令输出对应文件的内容可查看,如查看进程名:

1
2
3
4
5
#把目录中的数字改成自己bash进程的pid号
cat /proc/2943/comm

#信息输出如下
bash

可看到进程名为“bash”,实际上前面的“ps”命令也是通过“proc”文件系统获取到相关进程信息的。

上一节我们提到的procfs是“任务管理器”,sysfs同procfs一样,也是一个伪文件系统。

Linux内核在2.6版本中引入了sysfs文件系统,sysfs通常会自动挂载在根目录下的sys文件夹。 sys目录下的文件/文件夹向用户提供了一些关于设备、内核模块、文件系统以及其他内核组件的信息, 如子目录block中存放了所有的块设备,而bus中存放了系统中所有的总线类型,有i2c,usb,sdio,pci等。

../../_images/filesy0081.jpg

图中的虚线表示软连接,可以看到所有跟设备有关的文件或文件夹都链接到了device目录下,类似于将一个大类, 根据某个特征分为了无数个种类,这样使得/sys文件夹的结构层次清晰明了。

/sys各个文件的作用如下:

文件名

作用

block

记录所有在系统中注册的块设备,这些文件都是符号链接,都指向了/sys/devices目录

bus

该目录包含了系统中所有的总线类型,每个文件夹都是以每个总线的类型来进行命名

class

包含了所有在系统中注册的设备类型,如块设备,声卡,网卡等。文件夹下的文件同样也是一些链接文件,指向了/sys/devices目录

devices

包含了系统中所有的设备,到跟设备有关的文件/文件夹,最终都会指向该文件夹

module

该目录记录了系统加载的所有内核模块,每个文件夹名以模块命名

fs

包含了系统中注册文件系统

概括来说,sysfs文件系统是内核加载驱动时,根据系统上的设备和总线构成导出的分级目录, 它是系统上设备的直观反应,每个设备在sysfs下都有唯一的对应目录,用户可以通过具体设备目录下的文件访问设备。

在Linux 2.6内核之前一直使用的是devfs文件系统管理设备,它通常挂载于/dev目录下。 devfs中的每个文件都对应一个设备,用户也可以通过/dev目录下的文件访问硬件。 在sysfs出现之前,devfs是在制作根文件系统的时候就已经固定的,这不太方便使用, 而当代的devfs通常会在系统运行时使用名为udev的工具根据sysfs目录生成devfs目录。

在后面学习制作根文件系统时,就会接触到静态devfs以及使用udev动态生成devfs的选项。 在接下来的几个章节我们会通过具体的硬件设备讲解sysfs、devfs的使用,为大家建立起设备文件的概念。

1.6. 虚拟文件系统

除了前面提到的存储器文件系统FAT32、ext4,伪文件系统/proc、/sys、/dev外,还有内存文件系统ramfs,网络文件系统nfs等等, 不同的文件系统标准,需要使用不同的程序逻辑实现访问,对外提供的访问接口可能也稍有差异。 但是我们在编写应用程序时,大都可以通过类似fopen、fread、fwrite等C标准库函数访问文件,这都是虚拟文件系统的功劳。

Linux内核包含了文件管理子系统组件,它主要实现了虚拟文件系统(Virtual File System,VFS), 虚拟文件系统屏蔽了各种硬件上的差异以及具体实现的细节,为所有的硬件设备提供统一的接口, 从而达到设备无关性的目的,同时文件管理系统还为应用层提供统一的API接口。

在Linux下,一个与文件操作相关的应用程序结构如下图所示。

未找到图片09|

上图解析如下:

  • 应用层指用户编写的程序,如hello_main.c。

  • GNU C库(glibc)即C语言标准库,例如在编译器章节介绍的libc.so.6文件, 它包含了printf、malloc,以及本章使用的fopen、fread、fwrite等文件操作函数。

  • 用户程序和glibc库都是属于用户空间的,本质都是用户程序。

  • 应用层的程序和glibc可能会调用到“系统调用层(SCI)”的函数,这些函数是Linux内核对外提供的函数接口, 用户通过这些函数向系统申请操作。例如,C库的printf函数使用了系统的vsprintf和write函数, C库的fopen、fread、fwrite分别调用了系统的open、read、write函数,具体可以阅读glibc的源码了解。

  • 由于文件系统种类非常多,跟文件操作相关的open、read、write等函数经过虚拟文件系统层,再访问具体的文件系统。

总的来说,为了使不同的文件系统共存, Linux内核在用户层与具体文件系统之前增加了虚拟文件系统中间层, 它对复杂的系统进行抽象化,对用户提供了统一的文件操作接口。 无论是ext2/3/4、FAT32、NTFS存储的文件,还是/proc、/sys提供的信息还是硬件设备,无论内容是在本地还是网络上, 都使用一样的open、read、write来访问,使得“一切皆文件”的理念被实现,这也正是软件中间层的魅力。