Linux 磁盘设备和 LVM 管理命令详解

一、设备文件

在 Linux 操作系统中,设备文件是一种特殊类型的文件。这些文件绝大多数位于 /dev 目录下,用来表示 Linux 主机检测到的某个具体的硬件设备。
比如 /dev/sda 文件通常用来指代系统中的第一块硬盘。
Linux 操作系统及其应用与服务则通过这些设备文件与对应的硬件设备进行交互。

对于常见的磁盘(ATA、SATA、SCSI、SAS、SSD 等)和优盘等块存储设备,其设备文件主要以 sd* 的形式命名。如 sda 表示第一块硬盘,sdb2 表示第二块硬盘的第二个分区,以此类推。
因此,可直接使用 ls -l /dev/sd* 命令查看系统中的磁盘设备:

1
2
3
$ ls -l /dev/sd*
brw-rw---- 1 root disk 8, 0 8月 7 00:47 /dev/sda
brw-rw---- 1 root disk 8, 1 8月 7 00:47 /dev/sda1

即当前系统中只连接了一块硬盘(/dev/sda),且该硬盘只有一个分区(/dev/sda1)。

二、分区

分区可以理解为将一整块硬盘划分为一个或多个相互独立的存储区域。

比如可以将系统的第一块硬盘划分为 3 个分区,分别为 sda1、sda2、sda3 。sda1 用于挂载根目录(/),sda2 挂载 /var ,sda3 挂载 /home 目录。则即使 /var 目录下的日志文件等占用了 sda2 全部的存储空间,也不会影响其他两个分区的使用。

可以使用 fdisk -l 命令查看系统中的磁盘和分区信息:

1
2
3
4
5
6
7
8
9
10
11
$ sudo fdisk -l
Disk /dev/sda: 10 GiB, 10737418240 bytes, 20971520 sectors
Disk model: VBOX HARDDISK
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x20985120

Device Boot Start End Sectors Size Id Type
/dev/sda1 2048 20964824 20962777 10G 83 Linux

创建磁盘分区

fdisk 命令还可以用来对硬盘进行分区操作,包括创建新分区、删除已有的分区、创建分区表等。
我这里通过 VirtualBox 软件为虚拟机中的 Linux 系统添加了一块空白的虚拟硬盘。使用 fdisk -l 命令查看系统检测到的硬盘设备:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ sudo fdisk -l
Disk /dev/sda: 10 GiB, 10737418240 bytes, 20971520 sectors
Disk model: VBOX HARDDISK
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x20985120

Device Boot Start End Sectors Size Id Type
/dev/sda1 2048 20964824 20962777 10G 83 Linux


Disk /dev/sdb: 5 GiB, 5368709120 bytes, 10485760 sectors
Disk model: VBOX HARDDISK
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes

此时系统中多了一块不包含任何分区的新硬盘 /dev/sdb

使用 fdisk /dev/sdb 命令对新硬盘进行分区操作:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
$ sudo fdisk /dev/sdb

Welcome to fdisk (util-linux 2.33.1).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.

Device does not contain a recognized partition table.
Created a new DOS disklabel with disk identifier 0xce119026.

Command (m for help): m

Help:

DOS (MBR)
a toggle a bootable flag
b edit nested BSD disklabel
c toggle the dos compatibility flag

Generic
d delete a partition
F list free unpartitioned space
l list known partition types
n add a new partition
p print the partition table
t change a partition type
v verify the partition table
i print information about a partition

Misc
m print this menu
u change display/entry units
x extra functionality (experts only)

Script
I load disk layout from sfdisk script file
O dump disk layout to sfdisk script file

Save & Exit
w write table to disk and exit
q quit without saving changes

Create a new label
g create a new empty GPT partition table
G create a new empty SGI (IRIX) partition table
o create a new empty DOS partition table
s create a new empty Sun partition table

进入 fdisk 程序界面之后,按下 m 键并回车,即可打印帮助信息,获取该界面下支持的交互式命令。
比如输入 p 可以用来输出当前硬盘的分区信息,输入 n 创建新的分区,输入 d 删除已有的分区。
在对分区进行任何操作之后,最后都需要使用 w 将之前的所有更改写入硬盘。

这里先按下 n 开始新分区的创建,根据提示选择分区类型(p 表示主分区,e 表示扩展分区),进一步选择分区编号和第一个扇区的位置(一般默认即可),最后输入新分区中最后一个扇区的位置(也可以直接指定分区大小),格式为 +/-sectors+/-size 。如输入 +3G 则表示创建大小为 3 GB 的新分区。具体步骤如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Command (m for help): n
Partition type
p primary (0 primary, 0 extended, 4 free)
e extended (container for logical partitions)
Select (default p): p
Partition number (1-4, default 1):
First sector (2048-10485759, default 2048):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-10485759, default 10485759): +3G

Created a new partition 1 of type 'Linux' and of size 3 GiB.

Command (m for help): n
Partition type
p primary (1 primary, 0 extended, 3 free)
e extended (container for logical partitions)
Select (default p): p
Partition number (2-4, default 2):
First sector (6293504-10485759, default 6293504):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (6293504-10485759, default 10485759):

Created a new partition 2 of type 'Linux' and of size 2 GiB.

再用同样的步骤将磁盘的剩余空间划分为另一个分区。此时查看分区信息,原本空白的 5GB 新硬盘 sdb 已经被划分为两个分区 sdb1 和 sdb2 :

1
2
3
4
5
6
7
8
9
10
11
12
Command (m for help): p
Disk /dev/sdb: 5 GiB, 5368709120 bytes, 10485760 sectors
Disk model: VBOX HARDDISK
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xce119026

Device Boot Start End Sectors Size Id Type
/dev/sdb1 2048 6293503 6291456 3G 83 Linux
/dev/sdb2 6293504 10485759 4192256 2G 83 Linux

需要注意的是,如果此时按下 q 按键直接退出 fdisk 程序,则之前所做的全部操作都不会被保存。
如确认前面对硬盘的操作没有问题,应使用 w 命令将新的分区信息写入到磁盘中。类似于编辑文件时的保存并退出。

三、文件系统

可以将磁盘等存储设备看作一个小型的图书馆,存放在其中的书籍即硬盘中的数据,而分区的作用类似于对书籍分门类存放的书架,形成相对独立的区域。
但是书架上的书籍并不是随意放置的,每本书都需要根据一定的规则和顺序有规律地摆放,有时还要记录下摆放的具体位置。这些书籍的摆放规则即对应于分区上的文件系统

文件系统是对存储设备的空间进行组织和分配,负责文件存取并对存入的文件进行保护和检索的系统。对操作系统而言,文件的读写不会直接作用于硬盘扇区,而是通过文件系统以特定的规则处理和组织文件数据。
常见的文件系统如 Windows 中的 NTFS 和 Linux 系统中 Ext4 等。

在 Windows 系统中,通常所说的“分区”操作即包含了创建分区并建立文件系统的过程。而在 Linux 系统中,这两步操作则需要两个独立的命令完成。

可以使用 mkfs.ext4 /dev/sdb1 命令,在之前新加硬盘的第一个分区上创建 Ext4 格式的文件系统。

1
2
3
4
5
6
7
8
9
10
11
$ sudo mkfs.ext4 /dev/sdb1
mke2fs 1.44.6 (5-Mar-2019)
Creating filesystem with 786432 4k blocks and 196608 inodes
Filesystem UUID: d5e21599-12e9-44da-ae51-124d89fe5eda
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912

Allocating group tables: done
Writing inode tables: done
Creating journal (16384 blocks): done
Writing superblocks and filesystem accounting information: done

交换分区

Linux 系统中的 swap 分区可以看作位于硬盘上的“内存设备”。Linux 会将内存中一部分不需要立即使用的数据临时交换至硬盘上的 swap 分区,以缓解内存不足等情况。

我的 Linux 虚拟机安装时并没有分配 swap 分区,这里通过 mkswap 命令将 2G 大小的 sdb2 分区划分为 swap 空间:

1
2
3
$ sudo mkswap /dev/sdb2
Setting up swapspace version 1, size = 2 GiB (2146430976 bytes)
no label, UUID=47006330-810c-4321-8d73-d52a5f70bc88

然后使用 swapon 命令立即启用前面创建的 swap 分区:

1
2
3
4
5
$ sudo swapon /dev/sdb2
$ free -h
total used free shared buff/cache available
Mem: 983Mi 223Mi 168Mi 4.0Mi 590Mi 597Mi
Swap: 2.0Gi 0B 2.0Gi

分区挂载

在 Windows 系统中,一般插入一个已经分好区的硬盘或优盘之后,会自动为添加的一个或多个分区分配盘符(如 D:、E:、F: 等),之后就可以直接通过盘符在新分区上读取或写入文件了。

Linux 系统中没有盘符的概念,它的文件层次是一个从根目录(/)开始的树状结构(目录),一直向下延申,每一个分支都是一条具体的路径,指向某个特定的文件。比如 /usr/root/var/var/log 等。

目录可以说是独立于硬件存储设备的抽象的逻辑结构,用于指定文件系统层次中的某个具体位置。而磁盘分区与目录结构的对应关系,则需要通过挂载来指定。

一般在安装系统时,可以将 sda1 分区挂载到根目录下,则该目录下的所有文件之后都将保存在 sda1 上。如果后面又添加了一块新的数据盘 sdb,该硬盘只有一个分区 sdb1。为了将某些文件保存在 sdb1 分区上,可以在目录树中新建一个空白分支(比如 /mnt/data)并将 sdb1 挂载在该分支下。之后 /mnt/data 目录下创建的任何子目录和文件等数据都会保存在 sdb1 上。
具体命令如下:

1
2
$ sudo mkdir -p /mnt/data
$ sudo mount /dev/sdb1 /mnt/data

使用 df -h 命令查看文件系统占用的磁盘空间的具体情况:

1
2
3
4
5
6
7
8
9
10
11
$ df -h
Filesystem Size Used Avail Use% Mounted on
udev 456M 0 456M 0% /dev
tmpfs 99M 1.1M 98M 2% /run
/dev/sda1 9.8G 5.2G 4.2G 56% /
tmpfs 492M 0 492M 0% /dev/shm
tmpfs 5.0M 4.0K 5.0M 1% /run/lock
tmpfs 492M 0 492M 0% /sys/fs/cgroup
tmpfs 99M 0 99M 0% /run/user/117
tmpfs 99M 0 99M 0% /run/user/1000
/dev/sdb1 2.9G 9.0M 2.8G 1% /mnt/data

可以看到新添加的分区 /dev/sdb1 已经挂载到 /mnt/data 目录下了。

或者也可以使用 lsblk 命令查看块存储设备(即磁盘和分区)的容量和挂载点:

1
2
3
4
5
6
7
8
$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 0 10G 0 disk
└─sda1 8:1 0 10G 0 part /
sdb 8:16 0 5G 0 disk
├─sdb1 8:17 0 3G 0 part /mnt/data
└─sdb2 8:18 0 2G 0 part [SWAP]
sr0 11:0 1 1024M 0 rom

需要注意的是,手动挂载的分区在系统重启以后会自动卸载。如果想像根目录那样,每次系统启动时自动挂载分区,可以修改 /etc/fstab 配置文件,示例内容如下:

1
2
3
4
# <file system>                           <mount point>  <type>  <options>  <dump>  <pass>
UUID=f3435713-b2cd-4196-b07b-2ffb116a028d / ext4 defaults 0 1
/dev/sdb1 /mnt/data ext4 defaults 0 1
/dev/sdb2 none swap sw 0 0

PS:相对于 /dev/sda1 这种形式,使用 UUID 挂载分区往往更保险一点,可以通过 blkid 命令查看磁盘分区的 UUID:

1
2
3
4
$ sudo blkid
/dev/sda1: UUID="f3435713-b2cd-4196-b07b-2ffb116a028d" TYPE="ext4" PARTUUID="20985120-01"
/dev/sdb1: UUID="d5e21599-12e9-44da-ae51-124d89fe5eda" TYPE="ext4" PARTUUID="ce119026-01"
/dev/sdb2: UUID="47006330-810c-4321-8d73-d52a5f70bc88" TYPE="swap" PARTUUID="ce119026-02"

四、LVM(逻辑卷管理)

对于不包含逻辑卷管理(LVM)的磁盘分区方案,分区的位置、大小和数量一般都是固定的,从而导致扩展当前分区和添加新分区等操作变得困难。
此时若添加额外的硬盘和分区,则需要在目录树中创建新的分支作为挂载点,文件数据分散到多个复杂的位置上,不便于合并、备份和管理数据。

LVM 允许将单个或多个分区合并为一个逻辑卷组,且其中包含的逻辑卷可以动态地添加、改变大小或删除。
LVM 系统最底层为物理卷(pv),即磁盘、分区和 RAID 阵列等。物理卷可以用来创建逻辑卷组(vg),而逻辑卷组又可以包含任意数量的逻辑卷(lv),逻辑卷从功能上即对应于物理磁盘上的分区。

创建卷组和逻辑卷

可以使用 pvcreate 命令将某个存储设备(磁盘或分区等)标记为物理卷。
这里我通过 VirtualBox 添加了另一块大小为 5G 的空白的虚拟硬盘,系统检测到该设备为 /dev/sdc :

1
2
3
4
5
6
7
8
9
$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 0 10G 0 disk
└─sda1 8:1 0 10G 0 part /
sdb 8:16 0 5G 0 disk
├─sdb1 8:17 0 3G 0 part /mnt/data
└─sdb2 8:18 0 2G 0 part [SWAP]
sdc 8:32 0 5G 0 disk
sr0 11:0 1 1024M 0 rom

创建物理卷:

1
2
$ sudo pvcreate /dev/sdc
Physical volume "/dev/sdc" successfully created.

通过 pvs 命令列出所有的物理卷:

1
2
3
$ sudo pvs
PV VG Fmt Attr PSize PFree
/dev/sdc lvm2 --- 5.00g 5.00g

通过 vgcreate 命令在物理卷的基础上创建逻辑卷组:

1
2
$ sudo vgcreate data-volume /dev/sdc
Volume group "data-volume" successfully created

使用 vgs 命令列出当前所有的逻辑卷组:

1
2
3
$ sudo vgs
VG #PV #LV #SN Attr VSize VFree
data-volume 1 0 0 wz--n- <5.00g <5.00g

使用 lvcreate 命令在卷组中创建逻辑卷:

1
2
$ sudo lvcreate --name data --size 2G data-volume
Logical volume "data" created.

访问逻辑卷可以通过 /dev/mapper/<vgname>-<lvname> 或者 /dev/<vgname>/<lvname> 形式的路径,即刚刚创建的 data 逻辑卷可以通过 /dev/data-volume/data 指定。
在该逻辑卷上创建 Ext4 文件系统:

1
2
3
4
5
6
7
8
9
10
11
$ sudo mkfs.ext4 /dev/data-volume/data
mke2fs 1.44.6 (5-Mar-2019)
Creating filesystem with 524288 4k blocks and 131072 inodes
Filesystem UUID: 0f24cdd8-62e0-42fd-bc38-aa3bce91e099
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912

Allocating group tables: done
Writing inode tables: done
Creating journal (16384 blocks): done
Writing superblocks and filesystem accounting information: done

此时该逻辑卷即可挂载到某个目录分支下像普通物理分区一样正常使用了。

操作卷组和逻辑卷

可以使用 lvextend 命令动态地扩展逻辑卷的存储空间:

1
2
3
4
5
6
7
8
$ sudo lvextend --size +2G --resizefs /dev/data-volume/data
fsck from util-linux 2.33.1
/dev/mapper/data--volume-data: clean, 11/131072 files, 26156/524288 blocks
Size of logical volume data-volume/data changed from 2.00 GiB (512 extents) to 4.00 GiB (1024 extents).
Logical volume data-volume/data successfully resized.
resize2fs 1.44.6 (5-Mar-2019)
Resizing the filesystem on /dev/mapper/data--volume-data to 1048576 (4k) blocks.
The filesystem on /dev/mapper/data--volume-data is now 1048576 (4k) blocks long.

其中 --size +2G 用于指定增加 2G 空间,--resizefs 指定在扩展逻辑卷大小的同时扩充文件系统的大小(文件系统默认不会随逻辑卷的空间变化而自动扩展)。

或者也可以直接指定扩展后的大小,如:
$ sudo lvextend --size 4G --resizefs /dev/data-volume/data

其他常用的命令比如通过 lvresize 命令扩展逻辑卷,使其占用当前卷组中剩余的全部空间:

1
2
3
$ sudo lvresize -l +100%free /dev/data-volume/data
Size of logical volume data-volume/data changed from <3.00 GiB (767 extents) to <5.00 GiB (1279 extents).
Logical volume data-volume/data successfully resized.

因为上面的命令没有加上 --resizefs 或者 -r 选项,因此文件系统不会随着逻辑卷自动扩展大小,可以通过 resize2fs 命令手动扩展文件系统:
$ sudo resize2fs /dev/data-volume/data

假设一段时间以后,逻辑卷 /dev/data-volume/data 的空间即将被数据填满,可以尝试添加另一块硬盘 sdd

1
2
3
4
5
6
7
8
9
10
11
$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 0 10G 0 disk
└─sda1 8:1 0 10G 0 part /
sdb 8:16 0 5G 0 disk
├─sdb1 8:17 0 3G 0 part /mnt/data
└─sdb2 8:18 0 2G 0 part [SWAP]
sdc 8:32 0 5G 0 disk
└─data--volume-data 253:0 0 5G 0 lvm
sdd 8:48 0 5G 0 disk
sr0 11:0 1 1024M 0 rom

使用 pvcreate 命令创建物理卷:

1
2
3
4
$ sudo pvs
PV VG Fmt Attr PSize PFree
/dev/sdc data-volume lvm2 a-- <5.00g 0
/dev/sdd lvm2 --- 5.00g 5.00g

使用 vgextend 命令将该物理卷添加到之前创建的卷组 data-volume 中:

1
2
3
4
5
$ sudo vgextend data-volume /dev/sdd
Volume group "data-volume" successfully extended
$ sudo vgs
VG #PV #LV #SN Attr VSize VFree
data-volume 2 1 0 wz--n- 9.99g <5.00g

此时的 data-volume 卷组包含了两个物理卷(/dev/sdc/dev/sdd)和一个逻辑卷(/dev/data-volume/data),总大小变为 10G,闲置空间为 5G(即刚刚添加的物理卷)。

最后使用 lvresize 命令扩展逻辑卷大小,使其占据两个物理卷的全部存储空间:

1
2
3
4
5
6
7
8
$ sudo lvresize -l +100%free -r /dev/data-volume/data
fsck from util-linux 2.33.1
/dev/mapper/data--volume-data: clean, 11/196608 files, 30268/785408 blocks
Size of logical volume data-volume/data changed from <5.00 GiB (1279 extents) to 9.99 GiB (2558 extents).
Logical volume data-volume/data successfully resized.
resize2fs 1.44.6 (5-Mar-2019)
Resizing the filesystem on /dev/mapper/data--volume-data to 2619392 (4k) blocks.
The filesystem on /dev/mapper/data--volume-data is now 2619392 (4k) blocks long.

此时逻辑卷 /dev/data-volume/data 的大小扩展为 10G,即占用了整个卷组 data-volume(包含两个 5G 的物理卷)的全部空间。

总结:LVM 卷组(vg)的作用类似于物理磁盘,用于承载逻辑卷(lv)。卷组可以由多个物理卷(磁盘或分区等)构成,空间不够时也可以随时添加新的物理卷进行扩展。
而卷组上的逻辑卷(lv)类似于磁盘分区,可以挂载到目录作为存储空间。但是物理分区的位置和大小固定,而逻辑卷则可以在卷组的基础上动态的改变大小,甚至跨越多个物理磁盘和分区,使得管理起来更加方便和灵活。

常用 LVM 命令列表:

Command Used For
pvcreate Labeling devices for use with LVM
pvremove Removing the LVM label from a physical volume
pvdisplay / pvs Displaying information on the specified device or all physical volumes on the system
vgcreate Creating a new volume group
vgremove Removing (deleting) a volume group
vgextend Adding physical volumes to a volume group
vgreduce Removing physical volumes from a volume group
vgdisplay / vgs Displaying information about the specified group or all volume groups on the system
lvcreate Creating a new logical volume
lvremove Removing (deleting) a logical volume
lvextend Increasing the size of a logical volume
lvreduce Decreasing the size of a logical volume
lvdisplay / lvs Displaying all logical volumes on the system or in a specified volume group

参考资料

Pro Linux System Administration