摘要
学习如何为Linux系统设计硬盘分区布局。本文用来备考LPI 101 或者仅仅是为了兴趣。
概述
在本文学习为Linux系统设计硬盘分区布局,具体说来是:
- 为文件系统或者交换空间分配单独的分区
- 对于系统的不同应用设计不同的分区布局
- 理解逻辑卷管理(LVM)的基本特征
本文对应LPI考试的102.1目标,权重为2。
注意:本文关注的是分区布局的设计计划,具体的实现步骤参考Topic 104。
注意:本文包含了LPI101 2012年更新的目标。除此之外,我们还增加了逻辑卷管理的基本知识,以及传统的基于MBR的硬盘的分区表细节和GPT的基本知识。本文实验平台为64位的Fedora 16系统。
文件系统概述
一个Linux的文件系统包含以目录组织的很多文件,这些文件通常存放在硬盘或者其他块设备上。像其他系统一样,目录可以包含其他的子目录。与Windows系统不同的是,Linux文件系统是一个从/开始的单树结构,而Windows为每个分区分配不同的驱动器标识符。
既然文件系统是一个单树结构,你可能想知道为什么硬盘分区还很重要。实际上,每一个块设备,如硬盘分区、CD-ROM、或者软盘,都有一个文件系统。之所以看到的是一个单树文件系统,是因为这些不同设备上的文件系统都被挂载在这个单一树的不同点上,这个点叫做挂载点。
通常,内核会挂载一个硬盘分区上的文件系统到/上,然后你可以挂载其他的硬盘分区到/boot,/tmp,或者/home。你也可以挂载CD-ROM到/media/cdrom1,挂载一个软盘到/mnt/floppy。你也可以挂载其他Os上的网络文件系统,如NFS。还有很多其他类型的挂载。既然挂载过程实际上挂载的是设备上的文件系统,所以也通常简称为“挂载设备”。
现在,假设你刚刚挂载了根文件系统,并且想挂载一个CD-ROM,/dev/sr0,到挂载点/media/cdrom。在挂载之前,这个挂载点必须存在。当挂载完毕后,CD-ROM上的文件和目录会变成/media/cdrom目录下的子目录和文件。/media/cdrom下原来的文件和目录将会不再可见,尽管这些文件和目录仍然存在于包含/media/cdrom这个挂载点的设备上。当CD-ROM取消挂载后,这些原来的文件和目录又重新变得可见。为了避免这个问题,不要把文件放到一个准备作为挂载点的目录下。
下表显示了FHS标准要求的根目录下的子目录结构。
目录名 | 目录下文件 |
---|---|
bin | 必备的命令文件 |
boot | 启动加载器的文件 |
dev | 设备文件 |
etc | 系统配置文件 |
lib | 共享库文件和内核模块文件 |
media | 移动介质挂载点 |
mnt | 临时文件系统挂载点 |
opt | 附加的应用程序和软件包 |
sbin | 系统命令文件 |
srv | 系统服务的数据 |
tmp | 临时文件 |
usr | 用户安装的文件 |
var | 经常变动的文件 |
分区
第一个SCSI驱动器通常是/dev/sda。在老的Linux系统上,第一个IDE硬盘是/dev/sda。在同时有串口硬盘和传统IDE硬盘的系统上,Ide设备通常被命名为/dev/hda,串口硬盘被命名为/dev/sda。在较新的系统上,所有的IDE设备都被叫做/dev/sda,/dev/sdb等等。IDE设备命名之所以发生变化,是因为热插拔系统的缘故,它最开始支持USB设备。热插拔允许你随时插入一个设备并立刻使用。现在,热插拔系统被用来管理所有设备,不管这个设备是内建的还是在系统启动后插入的。
传统上,一个硬盘被格式化成512字节大小的扇区。同一个盘面上不用移动磁头就可以读取的所有扇区组成一个磁道,所有盘面对应的磁道组成一个柱面。现在有的硬盘的扇区是4KB,如果一个文件系统仍然假设扇区大小是512字节的话,当分区不是在以4KB为边界的扇区开始时,系统性能将会受损。
(译注:关于这部分是传统的硬盘知识,没有一对一翻译)。
现在硬盘的物理结构往往不再是传统的扇区、磁道、柱面,而是采用了一种叫做LBA(逻辑块寻址)的方式。今天使用的大硬盘采用了LBA48标准,使用48位来表示扇区号。一种叫做GPT的新格式也被用在大硬盘上,以取代传统的MBR格式。GPT默认支持多达128个分区。
一个硬盘上的空间被划分为分区。分区之间不能有重叠。没有分配给分区的空间叫做空闲空间。分区的名字类似/dev/hda1,/dev/hda2,/dev/sda1等等。在不使用热插拔的系统上,IDE设备最多有63个分区。支持热插拔的SCSI硬盘、USB硬盘、IDE硬盘的分区不能超过15个分区。一个分区通常包含整数个柱面(也许不是传统的柱面)。
如果两个分区工具程序对硬盘的特征理解不同,那么很有可能会发生一个硬盘分区程序创建的分区在另一个分区工具下不能使用或报错。这种问题经常会在不同系统间移动硬盘时发生,特别是两个系统的BIOS不兼容。
在Linux系统中,可以使用parted或者fdisk工具来查看硬盘的标称大小。老的Linux系统中还可以通过/proc文件系统查看这些信息,一般是文件/proc/ide/hda/geometry,这个文件在新的Linux系统中不再存在。下表显示了怎样利用fdisk命令来查看硬盘的分区信息。显示或者操纵分区表,需要root权限。
Listing 1. Hard disk geometry
ian@attic4:~$ fdisk -v fdisk (util-linux-ng 2.16) ian@attic4:~$ sudo fdisk /dev/sdb [sudo] password for ian: The number of cylinders for this disk is set to 30401. There is nothing wrong with that, but this is larger than 1024, and could in certain setups cause problems with: 1) software that runs at boot time (e.g., old versions of LILO) 2) booting and partitioning software from other OSs (e.g., DOS FDISK, OS/2 FDISK) Command (m for help): p Disk /dev/sdb: 250.1 GB, 250059350016 bytes 255 heads, 63 sectors/track, 30401 cylinders Units = cylinders of 16065 * 512 = 8225280 bytes Disk identifier: 0x000404d6 Device Boot Start End Blocks Id System /dev/sdb1 1 25 200781 83 Linux /dev/sdb2 26 12965 103940550 83 Linux /dev/sdb3 12966 30401 140054670 83 Linux Command (m for help): |
注意:fdisk给出了一个警告,说柱面的数量超过了1024。第1024个柱面在一些老的系统中很重要,因为这些系统中的BIOS只能启动位于前1024柱面的分区。这种限制只存在于不支持LBA的老的BIOS和老的启动加载器中,在现代的机器中,这种问题不再存在,但是你还是要注意到这种限制的存在。
你可以通过-u选项或者在交互模式下使用u命令来让fdisk显示的大小单位从柱面变为扇区。parted命令支持很多不同的单位。如下表:
ian@attic4:~$ sudo parted /dev/sdb [sudo] password for ian: GNU Parted 1.8.8.1.159-1e0e Using /dev/sdb Welcome to GNU Parted! Type 'help' to view a list of commands. (parted) help u unit UNIT set the default unit to UNIT UNIT is one of: s, B, kB, MB, GB, TB, compact, cyl, chs, %, kiB, MiB, GiB, TiB (parted) p Model: ATA HDT722525DLA380 (scsi) Disk /dev/sdb: 250GB Sector size (logical/physical): 512B/512B Partition Table: msdos Number Start End Size Type File system Flags 1 32.3kB 206MB 206MB primary ext3 2 206MB 107GB 106GB primary ext4 3 107GB 250GB 143GB primary ext3 (parted) u s (parted) p Model: ATA HDT722525DLA380 (scsi) Disk /dev/sdb: 488397168s Sector size (logical/physical): 512B/512B Partition Table: msdos Number Start End Size Type File system Flags 1 63s 401624s 401562s primary ext3 2 401625s 208282724s 207881100s primary ext4 3 208282725s 488392064s 280109340s primary ext3 (parted) u chs (parted) p Model: ATA HDT722525DLA380 (scsi) Disk /dev/sdb: 30401,80,62 Sector size (logical/physical): 512B/512B BIOS cylinder,head,sector geometry: 30401,255,63. Each cylinder is 8225kB. Partition Table: msdos Number Start End Type File system Flags 1 0,1,0 24,254,62 primary ext3 2 25,0,0 12964,254,62 primary ext4 3 12965,0,0 30400,254,62 primary ext3 (parted)
注意: parted和fdisk在显示分区的起始和终止柱面时数据不同,这是因为fdisk从1开始计数,而parte从0开始计数柱面。下表显示了fdisk和parted其实对分区的理解是相同的。
Listing 3. Checking start and end sector numbers
|
分区类型
IDE硬盘又三种分区类型:主分区、逻辑分区、扩展分区。分区表存在MBR中。MBR是硬盘的第一个扇区,所以分区表就不可能太大,这种结构限制了主分区的个数最多是4.当需要4个以上分区时,其中的一个主分区就要充当扩展分区。一个扩展分区是一个或多个逻辑分区的容器。通过这种方式,就可以在一个MBR格式的硬盘上分出超过4个的分区。
MBR的布局也限制了硬盘的最大大小为2TB左右。新的GPT布局解决了这个问题,同时也解决了分区个数少的问题。一个使用GPT格式化的硬盘,默认就支持128个主分区,并且不需要扩展分区和逻辑分区。 更多关于MBR和GPT内部的信息,请参考:
MBR,EBR,GPT和LVM内部结构。
一个扩展分区仅仅是一个或者多个逻辑分区的容器。这种分区策略最初被MS DOS和PC DOS使用,并且允许PC硬盘被DOS, Windows和Linux系统使用。一个硬盘可能只有一个扩展分区,数据存储在扩展分区内的逻辑分区中。在创建逻辑分区之前不能够存储数据到扩展分区中。
Linux把主分区或者扩展分区编号为1到4.所以,dev/sda可能有4个主分区,/dev/sda1,/dev/sda2, /dev/sda3, /dev/sda4。或者也可能只有一个主分区/dev/sda1和一个扩展分区/dev/sda2。如果有逻辑分区,那么逻辑分区的编号从5开始,所以第一个逻辑分区的编号是/dev/sda5,即使没有主分区,只有一个扩展分区/dev/sda1。所以,如果你需要4个以上分区,那么就需要为扩展分区保留一个主分区号。尽管Linux内核使用热插拔理论上能支持15个IDE分区,但是你也许不能创建这么多。如果你使用超过12个分区,那么要好好检查是否系统能正常工作。
上面例子中的硬盘有3个主分区,并且都被格式化为Linux使用。其中两个分区被格式化为Ext3文件系统,另一个被格式化为Ext4文件系统。下表显示了Ubunto9.10 系统下一个一个内部硬盘以及Fedora12 系统中一个通过USB挂载的硬盘的信息。注意不同的文件系统类型,以及parted可以使用命令行参数代替交互模式的命令。
Listing 4. Displaying the partition table with parted
ian@attic4:~$ sudo parted /dev/sda u s p [sudo] password for ian: Model: ATA WDC WD6401AALS-0 (scsi) Disk /dev/sda: 1250263728s Sector size (logical/physical): 512B/512B Partition Table: msdos Number Start End Size Type File system Flags 1 63s 2040254s 2040192s primary ext3 2 2040255s 22523129s 20482875s primary linux-swap(v1) 4 22523130s 1250258624s 1227735495s extended boot 5 22523193s 167397299s 144874107s logical ext3 6 167397363s 310761359s 143363997s logical ext3 7 310761423s 455442749s 144681327s logical ext3 8 455442813s 600092009s 144649197s logical ext3 [root@echidna ~]# parted /dev/sdc p Model: WD My Book (scsi) Disk /dev/sdc: 750GB Sector size (logical/physical): 512B/512B Partition Table: msdos Number Start End Size Type File system Flags 1 32.3kB 135GB 135GB primary fat32 lba 2 135GB 750GB 616GB extended 5 135GB 292GB 157GB logical ext3 6 292GB 479GB 187GB logical ext3 7 479GB 555GB 76.5GB logical ext3 8 555GB 750GB 195GB logical ext3 |
fdisk命令不支持GPT格式化的硬盘。可以使用parted,gparted或者gdisk工具代替。
警告: 如果使用gdisk作用于一个MBR格式的硬盘,那么它会提供一个转换到GPT的提示,倍加小心!!
逻辑卷管理器
既然你已经理解了不同的分区类型,你可能想知道如果没有计划好分区的大小会怎样。 怎样扩展或者缩小一个分区?如果你需要的空间超过了一个硬盘的大小该怎么办?进入“逻辑卷管理器”。
使用LVM,你可以对磁盘空间管理进行抽象,这样一个单一的文件系统可以跨越多个硬盘或分区,你也可以很容易地为文件系统增加或减少空间。现在的版本是lvm2,它兼容过去的lvm(有时叫做lvm1)。
LVM通过如下管理硬盘空间:
- 物理卷
- 卷组
- 逻辑卷
一个物理卷可以是整个硬盘也可以是硬盘的一个分区。尽管LVM可以使用一个没有经过分区的硬盘,但是这通常不是一个好主意。
一个卷组是一个或多个物理卷的集合。一个卷组的空间被当作一个大的硬盘来管理,即使卷组下的卷可能跨越了多个硬盘或分区。底下的物理卷可以大小不同,也可以位于不同类型的硬盘上。
一个逻辑卷在某种意义上类似于一个物理的GPT或者MBR分区,它是使用文件系统类型(如ext4或XFS)格式化的空间单位,然后就可以挂载到Linux文件系统树上了。一个逻辑卷位于一个卷组中。
可以这样想象,PV是组成一个叫做VG的虚拟硬盘的物理空间单元。然后这个虚拟的硬盘(VG)又被分区为多个LV以便供文件系统使用。
在一个VG里,你使用extents来管理空间。默认的extent大小是4MB,通常这足够了。如果你使用大的extent,注意VG中的所有PV必须使用相同大小的extent单位。当你分配或者调整LV的大小,分配单元是extent的大小。所以默认情况下,LV的大小是4MB的整数倍,所以扩张或缩小LV时,必须也是extent的整数倍。
LVM最后一个谜团就是设备映射。这是Linux内核的一部分,它为像LVM或者软Raid这样的虚拟设备提供了基础平台。
LVM所使用的命令通常在lvm2包中,你可以在命令行运行很多命令,也可以运行lvm命令进入一个特殊的shell,在这里可以运行不同的LVM命令。下表显示了lvm命令和lvmshell下可以执行的各种命令。
Listing 5. The lvm command and its subcommands
[root@echidna ~]# lvm lvm> help Available lvm commands: Use 'lvm help <command>' for more information dumpconfig Dump active configuration formats List available metadata formats help Display help for commands lvchange Change the attributes of logical volume(s) lvconvert Change logical volume layout lvcreate Create a logical volume lvdisplay Display information about a logical volume lvextend Add space to a logical volume lvmchange With the device mapper, this is obsolete and does nothing. lvmdiskscan List devices that may be used as physical volumes lvmsadc Collect activity data lvmsar Create activity report lvreduce Reduce the size of a logical volume lvremove Remove logical volume(s) from the system lvrename Rename a logical volume lvresize Resize a logical volume lvs Display information about logical volumes lvscan List all logical volumes in all volume groups pvchange Change attributes of physical volume(s) pvresize Resize physical volume(s) pvck Check the consistency of physical volume(s) pvcreate Initialize physical volume(s) for use by LVM pvdata Display the on-disk metadata for physical volume(s) pvdisplay Display various attributes of physical volume(s) pvmove Move extents from one physical volume to another pvremove Remove LVM label(s) from physical volume(s) pvs Display information about physical volumes pvscan List all physical volumes segtypes List available segment types vgcfgbackup Backup volume group configuration(s) vgcfgrestore Restore volume group configuration vgchange Change volume group attributes vgck Check the consistency of volume group(s) vgconvert Change volume group metadata format vgcreate Create a volume group vgdisplay Display volume group information vgexport Unregister volume group(s) from the system vgextend Add physical volumes to a volume group vgimport Register exported volume group with system vgmerge Merge volume groups vgmknodes Create the special files for volume group devices in /dev vgreduce Remove physical volume(s) from a volume group vgremove Remove volume group(s) vgrename Rename a volume group vgs Display information about volume groups vgscan Search for all volume groups vgsplit Move physical volumes into a new or existing volume group version Display software and driver version information lvm> quit Exiting. |
为了给你一个LVM操作的简明例子,我已经创建好了一个GPT分区/dev/sdc5和一个MBR分区/dev/sdd1。可以使用pvscan命令查看:
Listing 6. Displaying your physical volumes
[root@echidna ~]# pvscan PV /dev/sdc5 lvm2 [146.43 GiB] PV /dev/sdd1 lvm2 [232.88 GiB] Total: 2 [379.32 GiB] / in use: 0 [0 ] / in no VG: 2 [379.32 GiB] |
现在,你将使用vgcreate命令来窜改建一个卷组来包含这两个PV,然后使用lvcreate命令来创建一个空间大于任意一个PV的LV。最后,你会把新建的LV格式化为ext4,然后把它挂载到/mnt/lvdemo下。如下表:
Listing 7. Creating a volume group and a logical volume
[root@echidna ~]# vgcreate demo-vg /dev/sdc5 /dev/sdd1 Volume group "demo-vg" successfully created [root@echidna ~]# lvcreate -L 300G -n demo-lv demo-vg Logical volume "demo-lv" created [root@echidna ~]# lvscan ACTIVE '/dev/demo-vg/demo-lv' [300.00 GiB] inherit [root@echidna ~]# mkfs -t ext4 /dev/demo-vg/demo-lv mke2fs 1.41.14 (22-Dec-2010) Filesystem label= OS type: Linux Block size=4096 (log=2) Fragment size=4096 (log=2) Stride=0 blocks, Stripe width=0 blocks 19660800 inodes, 78643200 blocks 3932160 blocks (5.00%) reserved for the super user First data block=0 Maximum filesystem blocks=4294967296 2400 block groups 32768 blocks per group, 32768 fragments per group 8192 inodes per group Superblock backups stored on blocks: 32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208, 4096000, 7962624, 11239424, 20480000, 23887872, 71663616 Writing inode tables: done Creating journal (32768 blocks): done Writing superblocks and filesystem accounting information: done This filesystem will be automatically checked every 36 mounts or 180 days, whichever comes first. Use tune2fs -c or -i to override. [root@echidna ~]# mount /dev/demo-vg/demo-lv /mnt/lvdemo [root@echidna ~]# df -h /mnt/lvdemo/ Filesystem Size Used Avail Use% Mounted on /dev/mapper/demo--vg-demo--lv 296G 191M 281G 1% /mnt/lvdemo |
注意:
- 像mkfs和mount这样的文件系统命令使用像/dev/卷组名/逻辑卷名 的格式来访问逻辑卷。
- 默认情况下,当你创建一个LV后,它会立即变成活动的,如表中lvscan(译注:原文此处有错误,在此改正)命令的输出所示。如果LV存在一个可移除的硬盘上,再从系统中移除硬盘之前,需要先使用lvchange命令让LV处于非活动状态。
想更深入的学习LVM的知识,请参考文章:
“逻辑卷管理"。
MBR,EBR,GPT和LVM内幕
在学习分配磁盘空间之前,绕道看看MBR,EBR,GPT和LVM分区表内幕来强化这些概念。注意:LPI考试不要求你掌握这些,所以如果你时间紧或者对内幕不感兴趣可以直接跳到”分配磁盘空间“那一节。
MBR
主引导区是硬盘的第一个扇区。MBR中包含有启动代码,也可能还有其他信息,然后是64字节的分区表信息,最后是2个字节的启动标识符。64字节的分区表有4个16字节大小的项,分区表起始于446(0x1BE)处,下表显示了每一个16字节项。
Table 2. Partition table entry format
注意到第一个记录的状态是80h,表示一个启动分区,分区类型是07h,表示是一个NTFS分区。其余的分区类型分别是83h(Linux),05h(扩展分区),82h(交换分区)。所以该硬盘有三个主分区和一个扩展分区。所有的CHS地址都是feffff,在LBA硬盘上,这是标准值。LBA起始扇区地址和扇区总数要作为小端法的整数来解释,所以9866b908代表08b96698h。所以可以看出,第一个分区(NTFS分区)起始于63(00003f)扇区,共占用146368152(08b96698h)个扇区。因此,fdisk会显示结束扇区的编号为 63+146368152=146368214。
实现文件mbr.cpp
Offset (hex) | Length | Description |
---|---|---|
0h | 1 | 状态。80h表示活动(可启动)分区。. |
1h | 3 | 分区第一扇区所在的CHS地址 |
4h | 1 | 分区类型 |
5h | 3 | 分区最后一个扇区的CHS地址 |
8h | 4 | 分区第一扇区的LBA地址 |
Ch | 4 | 分区占用的扇区数量 |
看一个实际的例子。根用户可以使用dd命令读取硬盘上的扇区。下表显示了打印出来的/dev/sda的MBR的前510个字节。然后利用tail来选择最后64个字节(也就是分区表了)并用16进制显示。
Listing 8. Displaying the partition table on /dev/sda
[root@echidna ~]# dd if=/dev/sda bs=510 count=1 2>/dev/null|tail -c 64 |hexdump -C 00000000 80 01 01 00 07 fe ff ff 3f 00 00 00 98 66 b9 08 |........?....f..| 00000010 00 fe ff ff 83 fe ff ff 61 5c 39 09 21 c7 17 00 |........a\9.!...| 00000020 00 fe ff ff 05 fe ff ff 82 23 51 09 85 ab 68 66 |.........#Q...hf| 00000030 00 fe ff ff 82 fe ff ff d7 66 b9 08 8a f5 7f 00 |.........f......| 00000040 |
注意:
- MBR中的分区表不包含逻辑分区的任何信息。一会我们会介绍逻辑分区。
- 如果你的硬盘(老式硬盘)使用CHS而不是LBA,那么需要根据CHS来计算绝对扇区地址,除了计算方法外,原理完全相同,这里不再累述。
Listing 9. Fdisk output for /dev/sda
[root@echidna ~]# fdisk -l /dev/sda Disk /dev/sda: 1000.2 GB, 1000204886016 bytes 255 heads, 63 sectors/track, 121601 cylinders, total 1953525168 sectors Units = sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disk identifier: 0x000de20f Device Boot Start End Blocks Id System /dev/sda1 * 63 146368214 73184076 7 HPFS/NTFS/exFAT /dev/sda2 154754145 156312449 779152+ 83 Linux /dev/sda3 156312450 1874448134 859067842+ 5 Extended /dev/sda4 146368215 154754144 4192965 82 Linux swap / Solaris /dev/sda5 156312513 336031604 89859546 83 Linux /dev/sda6 336031668 636880859 150424596 83 Linux /dev/sda7 636880923 865983824 114551451 83 Linux /dev/sda8 865983888 949891319 41953716 83 Linux /dev/sda9 949891383 954003959 2056288+ b W95 FAT32 /dev/sda10 954007552 1187801087 116896768 83 Linux /dev/sda11 1187803136 1229760511 20978688 83 Linux /dev/sda12 1229762560 1874446335 322341888 83 Linux Partition table entries are not in disk order |
可以看出,/dev/sda1的最后一个扇区确实是146368214。
注意上表中给出警告说,分区表的项目没有按顺序排列。这是因为/dev/sda4,这个Linux交换分区物理上在/dev/sda3(扩展分区)的前面。这不是一个错误,但是像fdisk以及一些其他工具提供选项来按照顺序重写分区表。
扩展启动记录(EBR)
在你继续研究扩展分区之前,快速看一下图像工具gparted的输出结果,它会给出一个图形化的分区布局展示,并显示出扩展分区的容器本质。
下图显示了gparted命令显示的/dev/sda分区信息。像刚刚说的,这个磁盘有三个主分区(/dev/sda1,/dev/sda4, /dev/sda2)以及一个扩展分区(/dev/sda3)。扩展分区包含逻辑分区/dev/sda5到/dev/sda12。图中,扩展分区表示为蓝色虚线的框架,浅绿色的框表示逻辑分区。
前面已经说过,MBR中不包含逻辑分区的信息。它只是定义了一个容器(扩展分区),利用这个容器去查找其他逻辑分区。那么究竟是如何工作的呢?
一个扩展分区包含的逻辑分区数目没有硬性限制,也没有固定大小的逻辑分区表。实际上,每一个逻辑分区都对应有一个扩展启动记录(EBR)。与MBR类似,EBR也是512个字节大小,其中的分区表也是起始于446(1BEh)处。在EBR的分区表中,只有两个项被利用。第一个项定义了本分区的起始扇区和大小,第二项定义了到下一个逻辑分区的偏移量。对于最后一个逻辑分区来说,第二项总是0.
为了方便转换小端法的十六进制数字到十进制数字的转换,我们创建了一个bash函数,用来显示EBR的两个项。然后利用hexdump的字符串格式化功能来用10进制而不是16进制显示LBA起始扇区和扇区总数。下表是函数showebr的定义:
Listing 10. Function to display an EBR
showebr () { dd if=$1 skip=$2 bs=512 count=1 2> /dev/null | tail -c 66 | hexdump -n 32 -e '"%07.7_ax " 8/1 "%2.2x " 2/4 " %12d" "\n"' } |
第一个EBR就是扩展分区的第一个扇区。所以使用前面表中的结果来调用showebr函数。下面是输出结果:
Listing 11. The first EBR on /dev/sda
[root@echidna ~]# showebr /dev/sda 156312450 0000000 00 fe ff ff 83 fe ff ff 63 179719092 0000010 00 fe ff ff 05 fe ff ff 179719155 300849255 |
EBR的第一项显示分区/dev/sda5起始于此EBR后面的63个扇区,分区大小是179719092个扇区。第二项显示,下一个EBR位于此EBR后面179719155扇区处,count的意思是,下一个EBR到下一个分区结束扇区的距离。下表显示了/dev/sda扩展分区以及逻辑分区信息。/dev/sda6的大小是636880859-336031668+1=300849192。考虑到300849255,你可以计算出/dev/sda6的起始扇区是300849255-300849192=63.这对于很多系统来说是一个常见的偏移量,包括Windows XP。
Listing 12. Extended and logical partitions on /dev/sda
dev/sda3 156312450 1874448134 859067842+ 5 Extended /dev/sda5 156312513 336031604 89859546 83 Linux /dev/sda6 336031668 636880859 150424596 83 Linux /dev/sda7 636880923 865983824 114551451 83 Linux /dev/sda8 865983888 949891319 41953716 83 Linux /dev/sda9 949891383 954003959 2056288+ b W95 FAT32 /dev/sda10 954007552 1187801087 116896768 83 Linux /dev/sda11 1187803136 1229760511 20978688 83 Linux /dev/sda12 1229762560 1874446335 322341888 83 Linux |
利用你刚刚学到的知识,以及showebr函数和一些shell算术逻辑,你可以现在便利整个EBR链了。如下表:
Listing 13. Walking the chain of logical partitions on /dev/sda
[root@echidna ~]# showebr /dev/sda 156312450 0000000 00 fe ff ff 83 fe ff ff 63 179719092 0000010 00 fe ff ff 05 fe ff ff 179719155 300849255 [root@echidna ~]# showebr /dev/sda $(( 156312450 + 179719155 )) 0000000 00 fe ff ff 83 fe ff ff 63 300849192 0000010 00 fe ff ff 05 fe ff ff 480568410 229102965 [root@echidna ~]# showebr /dev/sda $(( 156312450 + 480568410 )) 0000000 00 fe ff ff 83 fe ff ff 63 229102902 0000010 00 fe ff ff 05 fe ff ff 709671375 83907495 [root@echidna ~]# showebr /dev/sda $(( 156312450 + 709671375 )) 0000000 00 fe ff ff 83 fe ff ff 63 83907432 0000010 00 fe ff ff 05 fe ff ff 793578870 4112640 [root@echidna ~]# showebr /dev/sda $(( 156312450 + 793578870 )) 0000000 00 fe ff ff 0b fe ff ff 63 4112577 0000010 00 fe ff ff 05 fe ff ff 797691510 233797128 [root@echidna ~]# showebr /dev/sda $(( 156312450 + 797691510 )) 0000000 00 fe ff ff 83 fe ff ff 3592 233793536 0000010 00 fe ff ff 05 fe ff ff 1031488638 41959424 [root@echidna ~]# showebr /dev/sda $(( 156312450 + 1031488638 )) 0000000 00 fe ff ff 83 fe ff ff 2048 41957376 0000010 00 fe ff ff 05 fe ff ff 1073448062 644685824 [root@echidna ~]# showebr /dev/sda $(( 156312450 + 1073448062 )) 0000000 00 fe ff ff 83 fe ff ff 2048 644683776 0000010 00 00 00 00 00 00 00 00 0 0 |
注意有一个分区的偏移量为3592.这对英语/dev/sda9和/dev/sda10之间存在1.75MB的空闲空间。还要注意/dev/sda12起始于2048而不是63。你可能在扇区大小是2048字节大小的硬盘或者是固态盘上发现这个。对于这些硬盘来说,扇区对齐非常重要。
GUID分区表(GPT)
前面我说过MBR布局的硬盘,如果扇区是512字节的话,其最大容量是2TB。随着大于2TB硬盘的出现,一种新的叫做GPT的布局被设计出来。这种格式可以用于小硬盘,对于大硬盘是必须的。硬盘格式化后,一个叫做保护的MBR出现在通常MBR出现的地方,这对于不能识别GPT的系统来说,会把整个硬盘当成是未知类型分区(EEh),分区大小就是整个硬盘的大小或者MBR支持的最大大小。
最近的Linux发行版包含了格式化GPT以及从GPT硬盘启动的工具,如gdisk(对应fdisk),parted,gparted, grub2,以及最新版本的GRUB。
最近的Linux系统支持从GPT分区启动,使用传统的BIOS或者新的EFI(扩展固件借口)。最近的64位Windows也支持使用EFI从GPT分区启动。
GPT分区包含一个GPT头以及GPT分区矩阵。GPT分区矩阵包含至少128个项,所以一个GPT硬盘可以支持最少128个分区。GPT头在GPT分区的第一个扇区,接下来就是GPT矩阵。GPT头和GPT矩阵都会在分区的尾部重复。为了进一步保护,GPT头还包含了GPT矩阵的32位CRC校验值。
下表显示了gdisk命令作用于一个包含5个主分区的移动硬盘(/dev/sdac)的输出结果。
Listing 14. Output from gdisk for /dev/sdc
[root@echidna ~]# gdisk -l /dev/sdc GPT fdisk (gdisk) version 0.8.4 Partition table scan: MBR: protective BSD: not present APM: not present GPT: present Found valid GPT with protective MBR; using GPT. Disk /dev/sdc: 3906963456 sectors, 1.8 TiB Logical sector size: 512 bytes Disk identifier (GUID): 7E637BAC-33FC-46D3-9860-6A300CDD0E5F Partition table holds up to 128 entries First usable sector is 34, last usable sector is 3906963422 Partitions will be aligned on 2048-sector boundaries Total free space is 1361893309 sectors (649.4 GiB) Number Start (sector) End (sector) Size Code Name 1 2048 773580799 368.9 GiB 0700 Ian's partition 1 2 773580800 1387724799 292.8 GiB 0700 Ian's partition 2 3 1387724800 1831110655 211.4 GiB 0700 4 1831110656 2237978623 194.0 GiB 0700 5 2237978624 2545072127 146.4 GiB 8E00 |
你可以在”Make the most of large drives with GPT and Linux"这篇文章中找到更多的GPT的信息。
为物理和逻辑卷管理一定要进行分区
我们说过,LVM可以使用没有任何分区的硬盘(裸盘)。处于和GPT使用保护MBR同样的原因,你应该在你的裸盘上创建一个分区,然后再在这个分区上创建PV,而不要依赖LVM去把一个裸盘定义为PV。这样,其他不能使用LVM的操作系统会意识到这个硬盘有一个未知分区,而不是认为这个硬盘没有格式化。
LVM为了管理抽象层,显然需要比原始MBR分区表更多地信息。除了建议你为PV创建一个分区,我们不会进一步查看LVM使用的盘上的数据结构。要想学习LVM的更多知识,参考文章“逻辑卷管理”。
分配硬盘空间
前面说过,Linux文件系统是一个以/为根的单树。很显然,软盘或者CD-ROM里的数据必须要挂载。但是为什么要把硬盘上的数据进行分割呢?如下是分割文件系统的几个原因:
- 启动文件。启动时,一些文件必须能被BIOS或者启动加载器读取。
- 多个硬盘。典型的,每一个硬盘都会被分成一个或多个分区,每一个都需要挂载到文件系统树上。
- 共享文件。多个操作系统可以共享静态文件,如可执行文件。动态文件,如用户家目录或者邮箱文件也可能需要共享,这样用户可以登入任何一台机器使用相同的家目录和邮箱。
- 潜在的溢出。如果一个文件系统的使用率达到了100%,把这部分和系统启动需要的文件分割开存放是个好注意。
- 磁盘配额。限制用户或用户组可以使用的磁盘空间大小。
- 只读加载。在日志文件系统出现之前,系统崩溃后恢复一个文件系统总是会花费很长时间。因此,你可以把不怎么需要变动的文件系统(比如可执行程序目录)只读加载。这样就可以减少系统崩溃后的磁盘检查时间。
除了这些应用,你也需要考虑在硬盘上分配出一块交换空间。对于一个Linux系统,这通常是一个或者几个必须的分区。
做出选择
假设你在建立一个至少一个硬盘的系统,并且你需要从硬盘启动系统。(本文不针对从LAN启动或者使用LiveCD/DVD的无盘工作站)。尽管以后可能改变分区的大小,但是通常会非常费力。所以,事先选择好是很重要的。让我们开始吧。
你首先要考虑的是,你的系统可以被启动。一些老的机器有BIOS只能启动位于1024柱面内的扇区的限制。如果你使用这样的机器,你必须创建一个boot分区来存放系统启动必须的文件。一旦系统被加载,Linux系统会接管磁盘的操作,1024柱面的限制也不再存在。如果你需要创建启动分区,大概100MB大小就足够了。
你下一个需要考虑的可能是需要的交换空间。与内存价格相比,交换空间是一个慢速的内存。曾经的一个说法是,交换空间的大小等于内存。今天,你可能想把交换空间配成内存的1到2倍大小,这样就可以运行很多大的程序而不至于超出内存限制。切换大程序可能是很慢的,但是在一个给定的时间里,你可能只运行一个这样的程序。
对于一个内存很小的系统来说,一个大的交换空间也是建议的。对于服务器,你可能需要的交换空间为内存的一半大小,除非你运行一个有特殊要求的程序。任何时候,你都应该检测服务器内存的使用情况来确定增加物理内存或者进行多服务器负载均衡。对于服务器,太多的交换空间不好。也可以使用交换文件,但是一个专门的交换分区性能更好。
现在,我们来到了一个岔路口。个人工作站的用途相对于服务器来说非常不确定。我的建议,特别是对新用户来说,是为大多数的标准目录(如/usr, /opt, /var等等。)建立单独的大分区。这对于那些不知道接下来会安装什么软件的新手来说非常有用。一个运行图形桌面和一定数量开发工具的工作站至少需要5GB空间,这还不算用户的数据。一些大的开发工具可能需要更多的空间。我通常为一个操作系统分配40GB-60GB的空间,剩下的空间用来加载其他的操作系统。
服务器的负载是比较固定的,如果一个文件系统的空间被用完了将会是一场灾难。所以,通常需要创建多个分区,跨越多个硬盘,可能使用硬Raid或软Riad,或者是逻辑卷组。
你也要考虑一个特定文件系统的负载,以及这个文件系统是否与其他系统共享还是仅仅被一个系统使用。你需要使用经验、计划工具、增长评估来为你的系统分配空间。
无论是配置工作站还是服务器,你都需要一些系统特定地文件部署到本地硬盘上。典型地,这包括/etc/系统配置目录,/boot启动文件目录,/sbin启动和系统修复,/root根用户的家目录,/var/lock锁定文件目录,/var/run系统运行信息,/var/log系统日志。其他文件系统,如/home家目录,/user,/opt,/var/mail或者/var/spool/news可能是单独的分区,或者是网络挂载的,这是根据你的安装需要和偏好来设定的。
原创内容(一个分析MBR及跟踪EBR的C程序)
程序源码
头文件:mbr.h
/****************************************************
*
* MBR 物理结构
*
* *************************************************/
#ifndef MBR_H
#define MBR_H
/* 分区表项结构 */
typedef struct _PARTION
{
unsigned char state; //状态
unsigned char chs_frist[3]; //第一扇区的CHS地址
unsigned char type; //分区的类型
unsigned char chs_last[3]; //最后一个扇区的CHS地址
unsigned char lba_first[4]; //第一个扇区的LBA地址
unsigned char sector_count[4]; //分区占用的扇区数量
}__attribute__((packed)) PARTION;
/* 分区表结构 */
typedef struct _PTABLE
{
PARTION p[4];
} __attribute__((packed)) PTABLE;
/* MBR结构 */
typedef struct _MBR
{
unsigned char boot[446]; //MBR开始的加载程序
PTABLE pt; //分区表
unsigned char active[2]; //分区活动标记
} __attribute__((packed)) MBR, *PMBR;
#endif
实现文件mbr.cpp
/*************************************************
*
* MBR读取分析
*
* ToDo: 追踪逻辑分区EBR
* **********************************************/
#include "mbr.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct partion_info
{
unsigned int start_sector;
unsigned int end_sector;
char state[16]; /*状态描述*/
char type[16]; /*分区类型*/
};
/*计算lba的地址*/
static unsigned int cal_lba(void* pstart)
{
/* mbr中LBA地址是小端法存储的 */
unsigned int result = (*(unsigned char*)pstart) +
(*((unsigned char*)pstart+1)) * 16*16 +
(*((unsigned char*)pstart+2)) * 16*16*16*16 +
(*((unsigned char*)pstart+3)) * 16*16*16*16*16*16;
return result;
}
/* 获取MBR信息 */
static void get_mbr_info(MBR* pmbr, struct partion_info pinfo[4])
{
int i=0;
for(i=0; i<4; i++) {
PARTION p = pmbr->pt.p[i];
if(p.state==0x80) {
strcpy(pinfo[i].state, "可启动");
} else {
strcpy(pinfo[i].state, "不可启动");
}
switch(p.type) {
case 0x7: strcpy(pinfo[i].type, "NTFS"); break;
case 0x83: strcpy(pinfo[i].type, "Linux"); break;
case 0x82: strcpy(pinfo[i].type, "Swap"); break;
case 0x05: strcpy(pinfo[i].type, "Extend"); break;
default: strcpy(pinfo[i].type, "Unknown"); break;
}
pinfo[i].start_sector = cal_lba(p.lba_first);
pinfo[i].end_sector = pinfo[i].start_sector + cal_lba(p.sector_count) - 1;
}
}
/* 追踪扩展分区 */
static int get_ebr_info(struct partion_info extend)
{
MBR ebr;
struct partion_info pi[4];
memset(&ebr, 0, sizeof(ebr));
memset(pi, 0, sizeof(pi));
FILE *fp = fopen("/dev/sda", "r");
if(fp==NULL) {
printf("打开设备出错\n");
return -2;
}
int n=0;
if(0!=fseek(fp, (extend.start_sector)*512L, 0)) {
printf("移动文件指针失败\n");
fclose(fp);
return -3;
}
unsigned long addr_ebr1 = ftell(fp); //记录下当前的位置
if(512 != (n=fread(&ebr, 1, 512, fp))) {
printf("实际读取%d, 读取MBR出错\n", n);
fclose(fp);
return -1;
}
int pid = 5;
while(1) {
get_mbr_info(&ebr, pi); //对于EBR,只有两个项, 其中的偏移量是相对于扩展分区的,而不是整个硬盘的,这里进行转换
printf("%-20d %-20s %-20s %-20d %-20d %-20d\n",
pid++, pi[0].state, pi[0].type,
extend.start_sector + pi[0].start_sector,
extend.start_sector + pi[0].end_sector,
(pi[0].end_sector-pi[0].start_sector+1)/2/1024);
//判断是否是最后一个逻辑分区
if(pi[1].start_sector==0) {
break;
}
fseek(fp, addr_ebr1 + pi[1].start_sector*512L, 0);
memset(&ebr, 0, sizeof(ebr));
memset(pi, 0, sizeof(pi));
fread(&ebr, 1, 512, fp);
}
fclose(fp);
return 0;
}
int main(int argc, char** argv)
{
MBR mbr;
struct partion_info pinfos[4];
memset(&mbr, 0, sizeof(mbr));
memset(&pinfos, 0, sizeof(sizeof(pinfos)));
FILE *fp = fopen("/dev/sda", "r");
if(fp==NULL) {
printf("打开设备出错\n");
return -2;
}
int n=0;
if(512 != (n=fread(&mbr, 1, 512, fp))) {
printf("实际读取%d, 读取MBR出错\n", n);
return -1;
}
fclose(fp);
get_mbr_info(&mbr, pinfos);
int i=0;
printf("%-20s %-20s %-20s %-20s %-20s %-20s\n",
"分区", "状态", "文件系统", "起始扇区(从0计数)", "终止扇区(从0计数)", "分区大小(MB)");
for(i=0; i<4; i++) {
printf("%-20d %-20s %-20s %-20d %-20d %-20d\n",
i+1, pinfos[i].state, pinfos[i].type, pinfos[i].start_sector, pinfos[i].end_sector,
(pinfos[i].end_sector-pinfos[i].start_sector+1)/2/1024);
}
for(i=0; i<4; i++) {
if(0==strcmp(pinfos[i].type,"Extend")){
get_ebr_info(pinfos[i]);
}
}
return 0;
}
运行结果:
注意事项:
(1)MBR,EBR里的起始扇区都是从0开始编号的,也可以说是代表偏移量
(2)逻辑分区的EBR的第一项中的起始扇区是以扩展分区的头为基础算起的,而主分区是以整个硬盘的头也就是(MBR)算起的
(3)逻辑分区的EBR的第二项中的下一个EBR的偏移量都是以扩展分区为头算起的,而不是以前面相邻的EBR。