這章節透過fdisk來了解底層的kernel相關的訊息, 為了方便學習, 這裡會使用qemu來模擬掛載eMMC.
首先會透過qemu-img create創建一個1GByte的eMMC, 並透過qemu掛載起來,
[brook@:~/Projects/qemu]$ qemu-img create -f qcow2 emmc_image.qcow2 1G
Formatting 'emmc_image.qcow2', fmt=qcow2 cluster_size=65536 extended_l2=off compression_type=zli6
[brook@:~/Projects/qemu]$ qemu-img info emmc_image.qcow2
image: emmc_image.qcow2
file format: qcow2
virtual size: 1 GiB (1073741824 bytes)
disk size: 196 KiB
cluster_size: 65536
Format specific information:
compat: 1.1
compression type: zlib
lazy refcounts: false
refcount bits: 16
corrupt: false
extended l2: false
[brook@:~/Projects/qemu]$ qemu-system-arm -smp cpus=4 -s \
-M vexpress-a9 -m 128M -kernel ./linux/arch/arm/boot/zImage \
-dtb ./linux/arch/arm/boot/dts/vexpress-v2p-ca9.dtb \
-initrd ./initrd-arm.img -nographic -append "console=ttyAMA0" \
-drive id=mysdcard,if=none,format=qcow2,file=emmc_image.qcow2 \
-device sd-card,drive=mysdcard
進入qemu之後, 會長出/dev/mmcblk0, 可以看到就是當時候的1GB, 不論是透過fdisk或是/proc/partitions都可以看出這是一個未經分割的eMMC, 隨後透過fdisk分割成兩個512MB的分區, 寫入離開fdisk之後, 就可以看到/proc/partition有多出兩個partition, 對應的/dev也會長出mmcblk0p1與mmcblk0p2, 沒看到可以用/sbin/mdev -s再掃一次
/ # fdisk -l /dev/mmcblk0
Disk /dev/mmcblk0: 1024 MB, 1073741824 bytes, 2097152 sectors
32768 cylinders, 4 heads, 16 sectors/track
Units: sectors of 1 * 512 = 512 bytes
/ # cat /proc/partitions
major minor #blocks name
31 0 131072 mtdblock0
31 1 32768 mtdblock1
179 0 1048576 mmcblk0
/ # fdisk /dev/mmcblk0
Device contains neither a valid DOS partition table, nor Sun, SGI, OSF or GPT disklabel
Building a new DOS disklabel. Changes will remain in memory only,
until you decide to write them. After that the previous content
won't be recoverable.
The number of cylinders for this disk is set to 32768.
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): m
Command Action
a toggle a bootable flag
b edit bsd disklabel
c toggle the dos compatibility flag
d delete a partition
l list known partition types
n add a new partition
o create a new empty DOS partition table
p print the partition table
q quit without saving changes
s create a new empty Sun disklabel
t change a partition's system id
u change display/entry units
v verify the partition table
w write table to disk and exit
x extra functionality (experts only)
Command (m for help): n
Partition type
p primary partition (1-4)
e extended
p
Partition number (1-4): 1
First sector (16-2097151, default 16):使用default, 直接按下enter
Using default value 16
Last sector or +size{,K,M,G,T} (16-2097151, default 2097151): +512M
Command (m for help): n
Partition type
p primary partition (1-4)
e extended
p
Partition number (1-4): 2
First sector (1048592-2097151, default 1048592):使用default, 直接按下enter
Using default value 1048592
Last sector or +size{,K,M,G,T} (1048592-2097151, default 2097151):使用default, 直接按下enter
Using default value 2097151
Command (m for help): p
Disk /dev/mmcblk0: 1024 MB, 1073741824 bytes, 2097152 sectors
32768 cylinders, 4 heads, 16 sectors/track
Units: sectors of 1 * 512 = 512 bytes
Device Boot StartCHS EndCHS StartLBA EndLBA Sectors Size Id Type
/dev/mmcblk0p1 0,1,1 1023,3,16 16 1048591 1048576 512M 83 Linux
/dev/mmcblk0p2 1023,3,16 1023,3,16 1048592 2097151 1048560 511M 83 Linux
Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table
mmcblk0: p1 p2
/ # cat /proc/partitions
major minor #blocks name
31 0 131072 mtdblock0
31 1 32768 mtdblock1
179 0 1048576 mmcblk0
179 1 524288 mmcblk0p1
179 2 524280 mmcblk0p2
/ # ls -al /dev/mmcblk0p1
brw-rw---- 1 0 0 179, 1 Sep 2 06:13 /dev/mmcblk0p1
/ # ls -al /dev/mmcblk0p2
brw-rw---- 1 0 0 179, 2 Sep 2 06:13 /dev/mmcblk0p2
Cylinder-head-sector (CHS) 是早期硬碟定址的方式, head就是讀寫頭, 每個磁盤(platter)兩面各有一個讀寫頭, 而一個又一個的同心圓就是track, 一個track被切割成若干個sector, 早期每個sector大小約512 byte, Cylinder是把所有磁盤上相同同心圓的總稱.
可以參考Wiki的圖片與解說
而後來改用LBA(Logical Block Addressing)取代了CHS, 其轉換公式如下,
LBA = (Cylinder × HPC + Head) × SPT + (Sector − 1)
Cylinder = LBA ÷ (HPC × SPT)
Head = (LBA ÷ SPT) mod HPC
S = (LBA mod SPT) + 1
HPC(Header Per Cylinder=4)與SPT(Sector Per Track=16)都是由driver回傳.以上述/dev/mmcblk0p1的StartLBA=16, 是因為offset就是16, EndLBA=1048591= (Cylinder=32768 × HPC=4 + Head) × SPT=16 + (Sector − 1), 這些轉換目前已經沒有太大意義了, 只要有概念就可以了
當執行完前面的fdisk作分割之後, 其實fdisk就會將partition table寫入/dev/mmcblk0前面的LBA0中, 也就是我們所謂的 Master Boot Record(MBR), 以下就是MBR的格式
Mnemonic | Byte Offset | Byte Length | Description |
BootCode | 0 | 440 | x86 code used on a non-UEFI system to select an MBR par tition record and load the first logical block of that partition. This code shall not be executed on UEFI systems. |
Unique MBRDisk Signature | 440 | 4 | Unique Disk Signature This may be used by the OS to identify the disk from other disks in the system. This value is always written by the OS and is never written by EFI firmware |
Unknown | 444 | 2 | Unknown. This field shall not be used by UEFI firmware |
Partition Record | 446 | 16*4 | Array of four legacy MBR partition records |
Signature | 510 | 2 | Set to 0xAA55(i.e., byte 510 contains 0x55 and byte 511 contains 0xAA). |
以下就是Partition Record的格式
Mnemonic | Byte Offset | Byte Length | Description |
BootIndicator | 0 | 1 | 0x80 indicates that this is the bootable legacy partition. Other values indicate that this is not a bootable legacy partition. This field shall not be used by UEFI firmware |
StartingCHS | 1 | 3 | Start of partition in CHS address format. This field shall not be used by UEFI firmware |
OSType | 4 | 1 | Type of partition |
EndingCHS | 5 | 3 | End of partition in CHS address format. This field shall not be used by UEFI firmware. |
StartingLBA | 8 | 4 | Starting LBA of the partition on the disk. This field is used by UEFI firmware to determine the start of the partition. |
SizeInLBA | 12 | 4 | Size of the partition in LBA units of logical blocks. This field is used by UEFI firmware to determine the size of the partition |
以下是常見的OS Type, 其餘可以參考
Partition types
ID | Name |
0x00 | Empty (Unused) |
0x01 | FAT12 (DOS) |
0x05 | DOS 3.3+ Extended Partition |
0x07 | NTFS (Windows) |
0x82 | Linux SWAP |
0x83 | Linux |
0x8E | Linux LVM |
0xEE | GPT Protective Partition |
以下的sample code分成兩部分, dump_geometry()與dump_mbr(), dump_geometry()主要是透過IOCTL取得MMC的物理資訊, 也就是Cylinder-head-sector (CHS), dump_mbr()主要是取得partition table資訊
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <stdint.h>
#include <linux/fs.h>
#include <linux/hdreg.h>
#define MAX_SECTOR_SIZE 512
#pragma pack(push, 1)
static unsigned char MBRbuffer[MAX_SECTOR_SIZE];
struct PartitionEntry {
uint8_t boot_ind;
uint8_t chs_start[3];
uint8_t type;
uint8_t chs_end[3];
uint32_t lba_start;
uint32_t sectors;
};
struct MBR {
uint8_t bootstrap_code[446];
struct PartitionEntry partitions[4];
uint16_t signature;
};
#pragma pack(pop)
static int sec_size = 0;
static struct hd_geometry geo;
static int dump_geometry(int fd)
{
unsigned long long sz;
if (ioctl(fd, BLKSSZGET, &sec_size) < 0) {
printf("get BLKSSZGET failed\n");
return 1;
}
if (ioctl(fd, HDIO_GETGEO, &geo) < 0) {
printf("%s(#%d): Cylinders: %d\n", __FUNCTION__, __LINE__, geo.cylinders);
printf("%s(#%d): Heads: %d\n", __FUNCTION__, __LINE__, geo.heads);
printf("%s(#%d): Start: %lu\n", __FUNCTION__, __LINE__, (unsigned long) geo.start);
return 1;
}
sz = geo.cylinders * geo.heads * geo.sectors * sec_size;
printf("%d MB, %llu bytes, %lu sectors\n", sz / (1024 * 1024), sz, geo.sectors);
printf("%lu cylinders, %d heads, %d sectors/track\n", geo.cylinders, geo.heads, geo.sectors);
printf("Units: sectors %d bytes\n\n", sec_size);
return 0;
}
static void set_hsc(unsigned long lba, unsigned long *h, unsigned long *s, unsigned long *c)
{
if ((lba / (geo.sectors * geo.heads) > 1023))
lba = geo.heads * geo.sectors * 1024 - 1;
*s = (lba % geo.sectors) + 1;
*h = (lba / geo.sectors) % geo.heads;
*c = (lba / geo.sectors) / geo.heads;
}
static int dump_mbr(int fd, char const * const prefix)
{
struct MBR *mbr;
struct PartitionEntry *p;
ssize_t bytes_read;
unsigned long lba, sc, sh, ss, ec, eh, es;
bytes_read = read(fd, MBRbuffer, sizeof(MBRbuffer));
if (bytes_read != 512) {
printf("read failed: %d\n", bytes_read);
return 1;
}
mbr = (struct MBR *) MBRbuffer;
if (mbr->signature != 0xAA55) {
printf("invalid MBR. signature:%04x/ M[510]:%02x, M[511]:%02x\n", mbr->signature, MBRbuffer[510], MBRbuffer[511]);
return 1;
}
for (int i = 0; i < 4; i++) {
p = &(mbr->partitions[i]);
if (p->sectors == 0) {
continue;
}
lba = p->lba_start;
set_hsc(lba, &sh, &ss, &sc);
lba = p->lba_start + p->sectors - 1;
set_hsc(lba, &eh, &es, &ec);
printf("Device\t\t Boot\t StartCHS\t EndCHS\t StartLBA\t EndLBA\t Sectors\n");
printf("%sp%d \t %c \t %d,%d,%d \t\t %d,%d,%d \t %d\t %d \t %lu \t %dM \t %x\n",
prefix, i, (p->boot_ind & 0x80) ? '*' : ' ', sc, sh, ss, ec, eh, es,
p->lba_start, p->lba_start + p->sectors - 1, p->sectors, (p->sectors * sec_size) / (1024 * 1024), p->type);
}
}
static int sys_check(int argc, char *argv[])
{
struct MBR *mbr;
struct PartitionEntry *p;
if (argc < 2) {
printf("./%s <dev_name>\n", argv[0]);
exit(-1);
}
if (sizeof(*mbr) != 512) {
printf("invalid mbr size:%d\n", sizeof(*mbr));
exit(-1);
}
if (sizeof(*p) != 16) {
printf("invalid mbr size:%d\n", sizeof(*p));
exit(-1);
}
}
int main(int argc, char *argv[])
{
const char *dev = argv[1];
struct MBR *mbr;
struct PartitionEntry *p;
int fd, sec_size = 0;
sys_check(argc, argv);
fd = open(dev, O_RDONLY);
if (fd == -1) {
printf("Error opening device %s failed\n", dev);
return 1;
}
printf("Disk %s: ", dev);
dump_geometry(fd);
dump_mbr(fd, dev);
close(fd);
return 0;
}
以下為sample code執行結果
/ # /list-part /dev/mmcblk0
Disk /dev/mmcblk0: 1073741824 MB, 1024 bytes, 1073741824 sectors
32768 cylinders, 4 heads, 16 sectors/track
Units: sectors 512 bytes
Device Boot StartCHS EndCHS StartLBA EndLBA Sectors
/dev/mmcblk0p0 * 0,1,1 1023,3,16 16 1048591 1048576 512M 83
Device Boot StartCHS EndCHS StartLBA EndLBA Sectors
/dev/mmcblk0p1 1023,3,16 1023,3,16 1048592 2097151 1048560 511M 83
參考資料:
- https://en.wikipedia.org/wiki/Cylinder-head-sector, Cylinder-head-sector
- Documentation/ioctl/hdio.txt, Summary of HDIO_ ioctl calls
- busybox/blob/master/util-linux/fdisk.c, busybox fdisk
- https://uefi.org/sites/default/files/resources/UEFI_Spec_2_10_Aug29.pdf, CH5 - GUID PARTITION TABLE (GPT) DISK LAYOUT