你不可不知的Linux知识总结(下)


前言

衔接上文,之前我们说到了Linux的一些基础概念以及Linux的进程与线程,这次我们来看看Linux操作系统的内核结构中的内存管理,I/O管理以及文件系统,本笔记只有上下两篇哦,看完你也能轻松掌握Linux操作系统。

一、Linux 内存管理

众所周知,Linux采用虚拟内存管理技术,每个进程都有独立的进程地址空间。这是Linux内存管理的基础,所以我们先讲解一下虚拟内存技术。虚拟内存技术是基于交换技术(swap)的,只不过交换的是页或者段。

1.虚拟内存

虚拟内存技术的好处:

  1. 扩大内存(主要催生虚拟内存原因)
  2. 安全性提高(不直接访问物理内存)
  3. 易于开发(每个进程拥有独立的用户空间)
    虚拟内存技术就是只装入程序的一部分,就开始运行整个程序。那么这样内存压力就会变小,能够运行大内存需求的软件。
    虚拟内存的实现原理是:当进程要求运行的时,不是将他的全部信息装入内存,而是将其一部分先装入内存,另一部分暂时留在外存,进程在运行过程中,要使用信息不在内存时,发生中断,由操作系统将他们调如内存,以保证进程的正常运行。但是呢,从进程角度来说,会认为它拥有连续可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。
    总结一下,虚拟内存实现可以总结为三步:
  4. 先加载进程的一部分数据
  5. 需要不在内存的数据,发生中断(缺页中断/缺段中断)
  6. 将数据调入内存
    在Linux内部的地址的映射过程为逻辑地址–>线性地址–>物理地址,逻辑地址经段机制转化成线性地址;线性地址又经过页机制转化为物理地址。简单的讲就是,在虚拟内存管理的时候,实质上的管理的是一段一段的(逻辑段),这一段一段的内存组和起来就是我们常规理解的线形地址,而在这一段一段的虚拟内存再使用页机制转化到物理内存上
    请添加图片描述
    Note:我们要知道,页是信息的物理单位,分页是为实现离散分配方式,以消减内存的外零头,提高内存的利用率。段则是信息的逻辑单位,它含有一组其意义相对完整的信息。分段的目的是为了能更好地满足用户的需要。这也是段和页的区别。

页机制
Linux中内存管理单元(MMU,把虚拟地址转换为物理地址的硬件设备)是以页为单位处理,我们把虚拟出来的内存分成等大的内存块,叫做页(page)(虚拟内存空间的顺序划分),把物理内存分成同等大小的内存块,叫做页框(pageframe)(对物理内存按顺序等大小的划分)

页面置换算法
请添加图片描述
首先检测是否有空闲的页框,如果有,那么将从交换分区调入的页装配到空闲页框中(swap in)。如果没有空闲页框,那系统按预定的置换策略(页面置换算法)自动选择一个或一些在内存的页面,如果这个或者这些页面为dirty,那么就将其换到(swap out)交换分区,空出页框了,就可以安置所需的页(swap in)
但是,在页面置换算法上,Linux 并没有采取这一种常规做法,Linux有一个守护进程kswapd,比较每个内存区域的高低水位来检测是否有足够的空闲页面来使用,每次运行时,仅有一个确定数量的页面被回收。
Linux采用的页面置换算法是一种改进的LRU算法–最近最少使用(LRU)页面的衰老算法,维护两组标记:活动/非活动和是否被引用。第一轮扫描清除引用位,如果第二轮运行确定被引用,就提升到一个不太可能回收的状态,否则将该页面移动到一个更可能被回收的状态。
处于非活动列表的页面,自从上次检查未被引用过,因而是移除的最佳选择。被引用但不活跃的页面同样会被考虑回收,是因为一些页面是守护进程访问的,可能很长时间不再使用。转换状态如下:
请添加图片描述
另外,内存管理还有一个守护进程pdflush,会定期醒来,写回脏页面;或者可用内存下降到一定水平后被内核唤醒。

MMU
内存管理单元(Memory Management Unit),MMU会将逻辑地址映射为物理地址,其实MMU虚实地址映射就是寻找物理页帧的过程请添加图片描述
既然所有发往内存的地址信号都要经过MMU处理,那么MMU就可以以很小的代价承担更大的责任,比如内存保护。可以在PTE条目中预留出几个比特,用于设置访问权限的属性,如禁止访问、可读、可写和可执行等。

物理内存映射
Linux 采用的是三级页表。随着64位CPU,比如X86_64,出现四级页表页开始诞生,原理相同,这里看一下Linux的三级页表。
请添加图片描述
页全局目录 (Page Global Directory,即PGD) :全局字典,指向中间页目录。
页中间目录( Page Middle Directory,即PMD) :中间字典,也可以理解为二级目录,指向PTE中的表项。
页表 (Page Table,即PTE):指向物理页面 。
偏移量(Page Offset):即页内偏移。

2.进程内存分布

每一个进程都有4G的线性虚拟地址空间,且内核空间是由内核负责映射,不会跟着进程变化;内核空间地址有自己对应的页表,用户进程各自有不同的页表。可以理解为每个普通进程都有自己的用户空间,但是内核空间被所有普通进程所共享(每个进程虚拟空间的3G~4G部分是相同的 )。另外,用户态进程只能访问0-3G,但是内核态进程既可以访问0-3G,也可以访问3G-4G地址空间。
请添加图片描述
下面来看看进程在虚拟内存中的分布情况:

  • 内核态
    请添加图片描述
  • 固定映射区(Fixing Mapping Region):该区域和4G的顶端只有4k的隔离带,其每个地址项都服务于特定的用途,如ACPI_BASE等。
  • 永久内存映射区(PKMap Region):该区域可访问高端内存。访问方法是使用alloc_page(_GFP_HIGHMEM)分配高端内存页或者使用kmap函数将分配到的高端内存映射到该区域。
  • 动态内存映射区(Vmalloc Region):该区域由内核函数vmalloc来分配,特点是:线性空间连续,但是对应的物理空间不一定连续。vmalloc分配的线性地址所对应的物理页可能处于低端内存,也可能处于高端内存。
  • 直接映射区(Direct Memory Region):线性空间中从3G开始最大896M的区间,为直接内存映射区,该区域的线性地址和物理地址存在线性转换关系:线性地址=3G+物理地址。
  • 用户态
    请添加图片描述
  • 栈:栈是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。
  • mmap 内存映射区:用于文件映射(包括动态库)和匿名映射。常见的就是使用 mmap 分配的虚拟内存区域。
  • 堆:堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)。
  • BSS段:BSS段包含了未初始化的全局变量,在内存中bss段全部置零。
  • 数据段:数据段用来存放已初始化的全局变量,换句话说就是存放程序静态分配的变量和全局变量。
  • 代码段:代码段是用来存放可执行文件的操作指令,也就是说是它是可执行程序在内存中的镜像。代码段需要防止在运行时被非法修改,所以是不可写的。

3.进程内存管理

进程内存管理的对象是进程线性地址空间上的内存镜像,这些内存镜像其实就是进程使用的虚拟内存区域(virtual memory areas,即VMA)
Linux内核通过一个被称为进程描述符的task_struct 结构体来管理进程,这个结构体包含了一个进程所需的所有信息。mm_struct中的pgd为页表,就是前面讲的一级页表。task_struct中有一个结构体被称为内存描述符的mm_struct,描述了一个进程的整个虚拟地址空间。每个进程正是因为都有自己的mm_struct,才使得每个进程都有自己独立的虚拟的地址空间。
经过前面介绍,我们知道Linux以页为单位进行分配,随之而来的就会有两个问题:
如何解决页外碎片问题
如何解决页内碎片问题
Linux 采用 **buddy 系统(伙伴系统)**来解决页外碎片,采用 slab 分配器来解决页内碎片。
同时 Linux 采用了 Node,Zone 和 page三级结构来描述物理内存的。buddy 系统是建立在这三级结构之上的。
slab 同时又在 buddy 系统之上管理着物理页之内的内存请求(小内存分配)。

  • node、zone、page
    在介绍管理物理内存之前我们要先了解一下什么是 UMANUMA
    在多核系统中,如果物理内存对所有CPU来说没有区别,每个CPU访问内存的方式也一样,则这种体系结构被称为Uniform Memory Access(UMA)
    请添加图片描述
    如果物理内存是分布式的,由多个cell组成(比如每个核有自己的本地内存),那么CPU在访问靠近它的本地内存的时候就比较快,访问其他CPU的内存或者全局内存的时候就比较慢,这种体系结构被称为Non-Uniform Memory Access(NUMA)
    请添加图片描述
    Linux适用于各种不同的体系结构, 而不同体系结构在内存管理方面的差别很大,因此linux内核需要用一种体系结构无关的方式来表示内存。因此linux内核把物理内存按照CPU节点划分为不同的node, 每个node作为某个cpu结点的本地内存, 而作为其他CPU节点的远程内存, 而UMA结构下, 则任务系统中只存在一个内存node, 这样对于UMA结构来说, 内核把内存当成只有一个内存node节点的伪NUMA。
    请添加图片描述
    内存管理区(zone),zone由struct zone_struct 数据结构来描述。zone的类型由zone_t表示,主要有ZONE_DMA(可以用于DMA操作的页), ZONE_NORMAL(正常的、规则映射的页), ZONE_HIGHMEM(正常的、规则映射的页) 这三种类型。
    node、zone、page之间的关系如下图:
    请添加图片描述
  • 伙伴算法
    内核子系统中有一个分区页框分配器,用于处理对连续页框组的内存分配请求。其中名为管理区分配器部分接受动态内存分配与释放请求。请添加图片描述
    每个内存管理区(Zone)内,页框由伙伴系统来分配。为了达到更好的系统性能,一小部分页框保留在高速缓存中用于快速满足对单个页框的分配请求。Buddy 算法将所有的空闲物理页分成 10 组,每组分别包含大小 1,2,4,8,16,32,64,128,256,512 个连续物理页。每一组用链表组织起来。是否为伙伴块需要满足三个条件:1. 两个块大小相同;2. 两个块地址连续;3. 两个块必须是同一个大块中分离出来的。
  • slab
    Linux为了解决这种问题呢,在内核实现了slab分配器,主要针对内核中经常分配并释放的对象。核心思想就是存储池的运用。slab会把对象池化,相同的对象放到一个存储池里,每个对象池都是一个 kmem_cache 结构的引用(称为一个 cache)。所有的对象池使用链表组织起来,即cache_chain
    请添加图片描述
    而每一个对象池(kmem_cache)存在3种slab:
  • slabs_full:完全分配的slab
  • slabs_partial:部分分配的slab
  • slabs_empty:空slab,或者没有对象被分配
    每一个slab就是一个或者多个连续的物理框(通常只有一个),他们是从伙伴系统中申请过来的物理内存,被划成很多小块,用于快速分配给对应的小对象。其中, slabs_empty 列表中的 slab 是进行回收(reaping)的主要备选对象。正是通过此过程,slab 所使用的内存被返回给操作系统供其他用户使用。
    每一个slab是不断移动的。当一个 slab 中的所有对象都被使用完时,就从 slabs_partial 列表中移动到 slabs_full 列表中。当一个 slab 完全被分配并且有对象被释放后,就从 slabs_full 列表中移动到 slabs_partial 列表中。当所有对象都被释放之后,就从 slabs_partial 列表移动到 slabs_empty 列表中。

二、Linux I/O管理

1.Linux I/O 基本概念

Linux 中也有磁盘、打印机、网络等 I/O 设备,Linux 把这些设备当作一种 特殊文件 整合到文件系统中,可以使用与普通文件相同的方式来对待这些特殊文件。
特殊文件一般分为两种:

  • 块特殊文件是一个能存储固定大小块信息的设备,它支持以固定大小的块,扇区或群集读取和(可选)写入数据。每个块都有自己的物理地址。所有传输的信息都会以连续的块为单位。块设备的基本特征是每个块都较为对立,能够独立的进行读写。常见的块设备有硬盘、蓝光光盘、USB 盘与字符设备相比,块设备通常需要较少的引脚。
  • 另一类 I/O设备是字符特殊文件。字符设备以字符为单位发送或接收一个字符流,而不考虑任何块结构。字符设备是不可寻址的,也没有任何寻道操作。常见的字符设备有打印机、网络设备、鼠标、以及大多数与磁盘不同的设备。
    我们知道,在计算机系统中,CPU 并不直接和设备打交道,它们中间有一个叫作 设备控制器(Device Control Unit)的组件,例如硬盘有磁盘控制器、USB 有 USB 控制器。
    网络
    I/O 的另外一个概念是网络, 也是由 UNIX 引入,网络中一个很关键的概念就是套接字(socket)。套接字允许用户连接到网络,正如邮筒允许用户连接到邮政系统,套接字的示意图如下:
    在这里插入图片描述
    套接字(之前有提到过)的位置如上图所示,套接字可以动态创建和销毁。成功创建一个套接字后,系统会返回一个文件描述符(file descriptor),在后面的创建链接、读数据、写数据、解除连接时都需要使用到这个文件描述符。
    可靠的面向连接的字节流会使用管道pipe 在两台机器之间建立连接。能够保证字节从一台机器按照顺序到达另一台机器,系统能够保证所有字节都能到达。
    以上涉及两种形式的传输协议,即TCP和UDP(之前有提到过),TCP 是传输控制协议,它能够传输可靠的字节流。UDP是用户数据报协议,它只能够传输不可靠的字节流。它们都属于 TCP/IP 协议簇中的协议,下面是网络协议分层。
    请添加图片描述
    可以看到TCP 、UDP 都位于运输层上,它们都把IP 协议即互联网协议作为基础。

2.Linux I/O系统调用

Linux 系统中的每个 I/O 设备都有一个特殊文件(special file)与之关联,什么是特殊文件呢?

在操作系统中,特殊文件是一种在文件系统中与硬件设备相关联的文件。特殊文件也被称为 设备文件(device file)。特殊文件的目的是将设备作为文件系统中的文件进行公开。特殊文件为硬件设备提供了借口,用于文件 I/O的工具可以进行访问。因为设备有两种类型,同样特殊文件也有两种,即字符特殊文件和块特殊文件。

对于大部分 I/O 操作来说,只用合适的文件就可以完成,并不需要特殊的系统调用。然后,有时需要一些设备专用的处理。在 POSIX 之前,大多数 UNIX 系统会有一个叫做 ioctl 的系统调用,它用于执行大量的系统调用。随着时间的发展,POSIX 对其进行了整理,把 ioctl 的功能划分为面向终端设备的独立功能调用,现在已经变成独立的系统调用了。下面是几个管理终端的系统调用:
在这里插入图片描述

3.Linux I/O实现

Linux 中的 IO 是通过一系列设备驱动实现的,每个设备类型对应一个设备驱动。设备驱动为操作系统和硬件分别预留接口,通过设备驱动来屏蔽操作系统和硬件的差异。当用户访问一个特殊的文件时,由文件系统提供此特殊文件的主设备号和次设备号,并判断它是一个块特殊文件还是字符特殊文件。主设备号用于标识字符设备还是块设备,次设备号用于参数传递。
I/O 实现指的就是对字符设备块设备的实现:

  • 块设备实现
    系统中处理块特殊文件 I/O 部分的目标是为了使传输次数尽可能的小。为了实现这个目标,Linux 系统在磁盘驱动程序和文件系统之间设置了一个高速缓存(cache),如下图所示:

(图片来自Linux技术栈)
请添加图片描述

在 Linux 内核 2.2 之前,Linux 系统维护着两个缓存:页面缓存(page cache)缓冲区缓存(buffer cache),因此,存储在一个磁盘块中的文件可能会在两个缓存中。2.2 版本以后 Linux 内核只有一个统一的缓存一个 通用数据块层(generic block layer) 把这些融合在一起,实现了磁盘、数据块、缓冲区和数据页之间必要的转换。那么什么是通用数据块层?

通用数据块层(generic block layer)是一个内核的组成部分,用于处理对系统中所有块设备的请求。通用数据块主要有以下几个功能:1. 将数据缓冲区放在内存高位处,当 CPU 访问数据时,页面才会映射到内核线性地址中,并且此后取消映射;2. 实现零拷贝机制,磁盘数据可以直接放入用户模式的地址空间,而无需先复制到内核内存中管理磁盘卷,会把不同块设备上的多个磁盘分区视为一个分区;3. 利用最新的磁盘控制器的高级功能,例如 DMA 等。

cache 是提升性能的利器,不管以什么样的目的需要一个数据块都会先从 cache 中查找,如果找到直接返回,避免一次磁盘访问,能够极大的提升系统性能。如果页面 cache 中没有这个块,操作系统就会把页面从磁盘中调入内存,然后读入 cache 进行缓存。cache 除了支持读操作外,也支持写操作,一个程序要写回一个块,首先把它写到 cache 中,而不是直接写入到磁盘中,等到磁盘中缓存达到一定数量值时再被写入到 cache 中。

Linux 系统中使用 IO 调度器 来保证减少磁头的反复移动从而减少损失。I/O 调度器的作用是对块设备的读写操作进行排序,对读写请求进行合并。Linux 有许多调度器的变体,从而满足不同的工作需要。最基本的 Linux 调度器是基于传统的 Linux 电梯调度器(Linux elevator scheduler)。Linux 电梯调度器的主要工作流程就是按照磁盘扇区的地址排序并存储在一个双向链表中。新的请求将会以链表的形式插入。这种方法可以有效的防止磁头重复移动。但电梯调度器会容易产生饥饿现象,因此Linux 在原基础上进行了修改,维护了两个链表,在最后日期(deadline) 内维护了排序后的读写操作。默认的读操作耗时 0.5s,默认写操作耗时 5s。如果在最后期限内等待时间最长的链表没有获得服务,那么它将优先获得服务。

  • 字符设备实现
    和字符设备的交互是比较简单的。由于字符设备会产生并使用字符流、字节数据,因此对随机访问的支持意义不大。一个例外是使用行规则(line disciplines)。一个行规可以和终端设备相关联,使用 tty_struct 结构来表示,它表示与终端设备交换数据的解释器,当然这也属于内核的一部分。例如:行规可以对行进行编辑,映射回车为换行等一系列其他操作。

什么是行规则?
行规是某些类 UNIX系统中的一层,终端子系统通常由三层组成:上层提供字符设备接口,下层硬件驱动程序与硬件或伪终端进行交互,中层规则用于实现终端设备共有的行为
请添加图片描述
网络设备的交互是不一样的。虽然网络设备(network devices) 也会产生字符流,因为它们的异步(asynchronous) 特性是他们不易与其他字符设备在同一接口下集成。网络设备驱动程序会产生很多数据包,经由网络协议到达用户应用程序中。

4.Linux 中的模块

UNIX 设备驱动程序是被静态加载到内核中的。因此,只要系统启动后,设备驱动程序都会被加载到内存中。随着个人电脑 Linux 的出现,这种静态链接完成后会使用一段时间的模式被打破。相对于小型机上的 I/O 设备,PC 上可用的 I/O 设备有了数量级的增长。

Linux 为了解决这个问题,引入了可加载(loadable module)机制。可加载是在系统运行时添加到内核中的代码块。当一个模块被加载到内核时,会发生下面几件事情:1. 在加载的过程中,模块会被动态的重新部署;2. 系统会检查程序程序所需的资源是否可用,如果可用,则把这些资源标记为正在使用;3. 设置所需的中断向量;4. 更新驱动转换表使其能够处理新的主设备类型;5. 最后再来运行设备驱动程序

在完成上述工作后,驱动程序就会安装完成,其他现代 UNIX 系统也支持可加载机制。

三、Linux 文件系统

1.磁盘分区

Linux系统启动时,首先挂载根文件系统,之后可以自动或手动挂载其他的文件系统,这些文件系统要挂载到挂载点 *(挂载点是Linux访问磁盘的入口,能够使数据正常写入磁盘)*上,与虚拟文件系统(Virtual File System)和通用块设备层(General Block Device Layer)建立联系。因此一个系统中可以同时存在不同的文件系统。
磁盘的分区主要分为基本分区(primary partion)扩充分区(extension partion)两种,基本分区和扩充分区的数目之和不能大于四个。且基本分区可以马上被使用但不能再分区。扩充分区必须再进行分区后才能使用,也就是说它必须还要进行二次分区。那么由扩充分区再分下去的是什么呢?它就是逻辑分区(logical partion),况且逻辑分区没有数量上限制。
Linux 规定了主分区(或者扩展分区)占用 1 至 16 号码中的前 4 个号码。以第一个 IDE 硬盘为例说明,主分区(或者扩展分区)占用了 hda1、hda2、hda3、hda4,而逻辑分区占用了 hda5 到 hda16 等 12 个号码。
因此,Linux 下面每一个硬盘总共最多有 16 个分区。IDE硬盘最多有64个分区
请添加图片描述
磁盘分区表主要有两种格式,一种是限制较多的 MBR 分区表,一种是较新且限制较少的 GPT 分区表。

  1. MBR(Master boot record)
    MBR 中,第一个扇区最重要,里面有主要开机记录(Master boot record, MBR)及分区表(partition table),其中主要开机记录占 446 bytes,分区表占 64 bytes。分区表只有 64 bytes,最多只能存储 4 个分区,这 4 个分区为主分区(Primary)和扩展分区(Extended)。其中扩展分区只有一个,它使用其它扇区用记录额外的分区表,因此通过扩展分区可以分出更多分区,这些分区称为逻辑分区。Linux 也把分区当成文件,分区文件的命名方式为:磁盘文件名 + 编号。
    注意,逻辑分区的编号从 5 开始。
  2. GPT(GUID Partition Table)
    全局唯一标识分区表(GUID Partition Table,缩写:GPT)是指全局唯一标示磁盘分区表格式。GPT 为了兼容所有磁盘,在定义扇区上使用逻辑区块地址(Logical Block Address, LBA)。GPT 第 1 个区块记录了主要开机记录(MBR),紧接着是 33 个区块记录分区信息,并把最后的 33 个区块用于对分区信息进行备份。这 33 个区块第一个为 GPT 表头纪录,这个部份纪录了分区表本身的位置与大小和备份分区的位置,同时放置了分区表的校验码 (CRC32),操作系统可以根据这个校验码来判断 GPT 是否正确。若有错误,可以使用备份分区进行恢复。
    GPT 没有扩展分区概念,都是主分区,每个 LAB 可以分 4 个分区,因此总共可以分 4 * 32 = 128 个分区。MBR 不支持 2.2 TB 以上的硬盘,GPT 则最多支持到 233 TB = 8 ZB。

2.Linux文件系统结构

请添加图片描述

1. 硬盘驱动 Device Driver

常见的硬盘类型有PATA, SATA和AHCI等,在Linux系统中,对不同硬盘所提供的驱动模块一般都存放在内核目录树drivers/ata中,而对于一般通用的硬盘驱动,也许会直接被编译到内核中,而不会以模块的方式出现,可以通过查看/boot/config-xxx.xxx文件来确认:

CONFIG_SATA_AHCI=y

2. 通用块设备层 General Block Device Layer

这一层的作用,正是解答了上面提出的第一个问题,不同的硬盘驱动,会提供不同的IO接口,内核认为这种杂乱的接口,不利于管理,需要把这些接口抽象一下,形成一个统一的对外接口,这样,不管你是什么硬盘,什么驱动,对外而言,它们所提供的IO接口没什么区别,都一视同仁的被看作块设备来处理。

所以,如果在一层做的任何修改,将会直接影响到所有文件系统,不管是ext3,ext4还是其它文件系统,只要在这一层次做了某种修改,对它们都会产生影响。

3. 文件系统 File System

文件系统这一层相信大家都再熟悉不过了,目前大多Linux发行版本默认使用的文件系统一般是ext4,另外,新一代的btrfs也呼之欲出,不管什么样的文件系统,都是由一系列的mkfs.xxx命令来创建,如:

mkfs.ext4 /dev/sda
mkfs.btrfs /dev/sdb

内核所支持的文件系统类型,可以通过内核目录树 fs 目录中的内容来查看。

4. 虚拟文件系统 Virtual File System

Virtual File System这一层,正是用来解决上面提出的第二个问题,试想,当我们通过mkfs.xxx系列命令创建了很多不同的文件系统,但这些文件系统都有各自的API接口,而用户想要的是,不管你是什么API,他们只关心mount/umount,或open/close等操作。

所以,VFS就把这些不同的文件系统做一个抽象,提供统一的API访问接口,这样,用户空间就不用关心不同文件系统中不一样的API了。VFS所提供的这些统一的API,再经过System Call包装一下,用户空间就可以经过SCI的系统调用来操作不同的文件系统。

VFS所提供的常用API有:

mount()
umount()
open()
close()
mkdir()

(之前提到过)Linux启动时,第一个必须挂载的是根文件系统;若系统不能从指定设备上挂载根文件系统,则系统会出错而退出启动。之后可以自动或手动挂载其他的文件系统。因此,一个系统中可以同时存在不同的文件系统

3.Linux文件存储结构

介绍文件存储结构前先来看看文件系统如何划分磁盘,创建一个文件、目录、链接的过程。
我们知道文件最终是保存在硬盘上的。硬盘最基本的组成部分是由坚硬金属材料制成的涂以磁性介质的盘片,不同容量硬盘的盘片数不等。每个盘片有两面,都可记录信息。盘片被分成许多扇形的区域,每个区域叫一个扇区,每个扇区可存储128×2的N次方(N=0,1,2,3)字节信息。在DOS中每扇区是128×2的2次方=512字节,盘片表面上以盘片中心为圆心,不同半径的同心圆称为磁道。硬盘中,不同盘片相同半径的磁道所组成的圆柱称为柱面。磁道与柱面都是表示不同半径的圆,在许多场合,磁道和柱面可以互换使用,我们知道,每个磁盘有两个面,每个面都有一个磁头,习惯用磁头号来区分。扇区,磁道(或柱面)和磁头数构成了硬盘结构的基本参数,帮这些参数可以得到硬盘的容量,基计算公式为:

存储容量=磁头数×磁道(柱面)数×每道扇区数×每扇区字节数

那么这些空间又是怎么管理起来的呢?unix/linux使用了一个简单的方法。 它将磁盘块分为以下三个部分:
1) 超级块
文件系统中第一个块被称为超级块。这个块存放文件系统本身的结构信息。比如,超级块记录了每个区域的大小,超级块也存放未被使用的磁盘块的信息。
2) I-节点表(Inode table)
超级块的下一个部分就是inode table。每个inode就是一个对应一个文件/目录的结构,这个结构它包含了一个文件的长度、创建及修改时间、权限、所属关系、磁盘中的位置等信息。一个文件系统维护了一个inode的数组,每个文件或目录都与索inode数组中的唯一一个元素对应。系统给每个inode分配了一个号码,也就是该节点在数组中的index number,称为inode number。
3) 数据区
文件系统的第3个部分是数据区。文件的内容保存在这个区域。磁盘上所有块的大小都一样。如果文件包含了超过一个块的内容,则文件内容会存放在多个磁盘块中。一个较大的文件很容易分布上千个独产的磁盘块中。

Linux正统的文件系统(如ext2、ext3)一个文件由目录项、inode和数据块组成。
目录项:包括文件名和inode节点号。
Inode:又称文件索引节点,是文件基本信息的存放地和数据块指针存放地。
数据块:文件的具体内容存放地。

  • 当查看某个文件时,会先从inode table中查出文件属性及数据存放点,再从数据块中读取数据。

文件存储结构大概如下:
请添加图片描述
目录项结构:
请添加图片描述
其中文件的inode结构如下(inode里所包含的文件信息可以通过stat filename查看得到):
请添加图片描述

4.Linux文件操作

创建一个文件的过程
我们从前面可以知道文件的内容和属性是分开存放的,那么又是如何管理它们的呢?现在我们以创建一个文件为例来讲解。
在命令行输入命令:

$ who > userlist

当完成这个命令时,文件系统中增加了一个存放命令who输出内容的新文件userlist。那么这整个过程到底是怎么回事呢?
文件主要有属性、内容以及文件名三项。内核将文件内容存放在数据区,文件属性存放在i-node,文件名存放在目录中。
创建成功一个文件主要有以下四个步骤:

  • 存储属性
    也就是文件属性的存储,内核先找到一块空的i-节点。例如,内核找到i-节点号921130。内核把文件的信息记录其中。如文件的大小、文件所有者、和创建时间等。
  • 存储数据
    即文件内容的存储,由于该文件需要3个数据块。因此内核从自由块的列表中找到3个自由块。如600、200、992,内核缓冲区的第一块数据复制到块600,第二和第三分别复制到922和600.
  • 记录分配情况
    数据保存到了三个数据块中,所以必须要记录起来,以后再找到正确的数据。分配情况记录在文件的i-节点中的磁盘序号列表里,这3个编号分别放在最开始的3个位置。
  • 添加文件名到目录
    新文件的名字是userlist 内核将文件的入口(47,userlist)添加到目录文件里。文件名和i-节点号之间的对应关系将文件名和文件和文件的内容属性连接起来,找到文件名就找到文件的i-节点号,通过i-节点号就能找到文件的属性和内容。

创建一个目录的过程
前面说了创建一个文件的大概过程,也了解文件内容、属性以及入口的保存方式,那么创建一个目录时又是怎么回事呢?
我现在test目录使用命令mkdir 新增一个子目录child:
从用户的角度看,目录child是目录test的一个子目录,那么在系统中这层关系是怎么实现的呢?实际上test目录包含一个指向子目录child的i-node的链接,原理跟普通文件一样,因为目录也是文件。
目录其实也是文件,只是它的内容比较特殊。所以它的创建过程和文件创建过程一样,只是第二步写的内容不同。

  1. 系统找到空闲的i-节点号 (887220),写入目录的属性
  2. 找到空闲的数据块1002来存储目录的内容,只是目录的内容比较特殊,包含文件名字列表,列表一般包含两个部分:i-节点号和文件名,这个列表其实也就是文件的入口,新建的目录至少包含三个目录”.”和”…”其中”.”指向自己,”…”指向上级目录,我们可以通过比较对应的i-节点号来验证,887270 对应着上级目录中的child对应的i-节点号
  3. 记录分配情况。这个和创建文件完全一样
  4. 添加目录的入口到父目录,即在父目录中的child入口
    一般都说文件存放在某个目录中,其实目录中存入的只是文件在i-节点表的入口,而文件的内容则存储在数据区。我们一般会说“文件userlist在目录test中”,其实这意味着目录test中有一个指向i-节点921130的链接,这个链接所附加的文件名为userlist,这也可以这样理解:目录包含的是文件的引用,每个引用被称为链接。文件的内容存储在数据块。文件的属性被记录在一个被称为i-节点的结构中。I-节点的编号和文件名关联起来存在目录中。

注意:其中.表示是当前目录。而..是当前目录的父目录。但也有特殊情况:如我们查看根目录/的情况:
发现...都指向i-节点2。实际上当我们用mkfs创建一个文件系统时,mkfs都会将根目录的父目录指向自己。所以根目录下...指向同一个i-节点也不奇怪了。

*以上就是今天要讲的内容,本文介绍了Linux的基本概念,和有关于内存管理,I/O管理以及Linux文件系统,文章内容参考来自Leetcode和知乎,感谢所有作者的支持!!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值