1. Tổng quan
Ở bài viết này mình thực hành trên RAID0 tạo bằng mdadm (software RAID) để dễ quan sát và thao tác. Với hardware RAID, nguyên lý striping (dữ liệu chia thành các khối xen kẽ giữa các ổ) hoàn toàn giống nhau, nên về lý thuyết cách khôi phục cũng tương tự: cần xác định đúng chunk size, offset và thứ tự ổ. Tuy nhiên, mỗi hãng controller phần cứng lại có cách lưu metadata riêng, không theo chuẩn mdadm, do đó thao tác cụ thể sẽ khác (thường phải dùng chính controller cùng loại hoặc phần mềm chuyên dụng). Nói cách khác, bạn có thể áp dụng cùng tư duy, nhưng công cụ và bước thực hiện sẽ tùy môi trường.
Bài viết này tổng hợp quy trình tạo RAID0 và phục hồi dữ liệu trong hai kịch bản phổ biến:
- Còn metadata của RAID (mdadm superblock còn nguyên).
- Mất metadata của RAID (chỉ còn metadata của file system, ví dụ ext4).
Nội dung gồm kiến thức nền cần nhớ, sơ đồ bố cục dữ liệu, quy trình từng bước, cách dò offset/chunk/order khi mất metadata, mẹo dùng dumpe2fs
/mke2fs -n
/mount -o sb=...
2. Kiến thức nền (nên nắm trước khi cứu dữ liệu)
- RAID0 chỉ striping, không có redundancy. Mất layout (offset, chunk, thứ tự đĩa) = rất khó ghép lại đúng → ưu tiên làm việc trên bản backup bằng image.
- Mdadm metadata (superblock) nắm thông tin layout. Nếu mất, vẫn còn cơ hội nhờ metadata của ext4 (primary/backup superblock).
- Các tham số quan trọng khi ghép tay:
- OFFSET: vùng bỏ qua đầu device name hoặc image trước khi bắt đầu data (thường vài MiB tùy cách tạo).
- CHUNK: là đơn vị dữ liệu nhỏ nhất được ghi xuống một đĩa trong mảng RAID (ví dụ 64 KB).
- ORDER: thứ tự sắp xếp 2 nhánh (disk0, disk1). Sai order → dữ liệu được xem như không khớp, ext4 không thể mount được.
- Mount an toàn khi kiểm tra:
-o ro,noload
(chỉ đọc, không replay journal).
Sơ đồ bố cục (minh họa)
[ Image/disk 0 ] [ Image/disk 1 ]
|-- OFFSET --|==== stripe0 ====| |-- OFFSET --|==== stripe1 ====|
|==== stripe2 ====| |==== stripe3 ====| ...
RAID0 (md9) = stripe0 + stripe1 + stripe2 + stripe3 + ...
ext4 nằm rải đều trên md9 (có primary & backup superblocks)
2. Kịch bản còn metadata RAID (dễ nhất)
Bạn có hai lựa chọn an toàn, tùy mục tiêu:
2.1. Assemble bằng metadata sẵn có (khuyến nghị khi chắc chắn thiết bị/phân vùng đúng)
- Mdadm sẽ đọc superblock ở đầu đĩa để biết layout (chunk size, offset, thứ tự đĩa, UUID…).
- Ưu điểm: nhanh, ít rủi ro, chính xác vì dựa trên metadata thật.
- Nhược điểm: nếu metadata hỏng hoặc không đồng bộ thì không assemble được.
Các command dưới đây để khởi tạo 1 mảng raid0, mount raid0 này, copy dữ liệu vào và sau đó umount, stop và backup nó ra image.
shell> mdadm --create --verbose /dev/md1 --level=0 --raid-devices=2 --chunk=64 /dev/sdb /dev/sdc
mdadm: Defaulting to version 1.2 metadata
mdadm: array /dev/md1 started.
shell> mkfs.ext4 /dev/md1
mke2fs 1.46.5 (30-Dec-2021)
Creating filesystem with 1047040 4k blocks and 262144 inodes
Filesystem UUID: 52eb4278-1ff1-482b-ae0e-5c19528fb585
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912, 819200, 884736
Allocating group tables: done
Writing inode tables: done
Creating journal (16384 blocks): done
Writing superblocks and filesystem accounting information: done
shell> mkdir /mnt/raid0
shell> mount /dev/md1 /mnt/raid0
shell> cp -r /usr/bin /mnt/raid0/
shell> cp -r /var /mnt/raid0/
shell> cp -r /etc /mnt/raid0/
shell> sync
shell> umount /mnt/raid0
shell> mdadm --stop /dev/md1
mdadm: stopped /dev/md1
shell>sudo dd if=/dev/sdb of=~/disk0.img bs=4M status=progress
2088763392 bytes (2.1 GB, 1.9 GiB) copied, 7 s, 298 MB/s
512+0 records in
512+0 records out
2147483648 bytes (2.1 GB, 2.0 GiB) copied, 7.46567 s, 288 MB/s
shell> sudo dd if=/dev/sdc of=~/disk1.img bs=4M status=progress
2139095040 bytes (2.1 GB, 2.0 GiB) copied, 13 s, 164 MB/s
512+0 records in
512+0 records out
2147483648 bytes (2.1 GB, 2.0 GiB) copied, 13.4616 s, 160 MB/s
Lưu ý, ở trên mình có sử dụng lệnh sync
, mục đích là:
- Đẩy toàn bộ dữ liệu đã copy trong RAM xuống đĩa thật sự.
- Tránh tình trạng dữ liệu còn nằm trong cache của kernel nhưng chưa kịp ghi xong đã
umount
hoặcmdadm --stop
, dễ dẫn đến mất hoặc thiếu file.
Chạy mdadm --examine
để xem metadata superblock của mdadm (phiên bản 1.2) trên từng disk.
shell> mdadm --examine /dev/sdb /dev/sdc
/dev/sdb:
Magic : a92b4efc
Version : 1.2
Feature Map : 0x0
Array UUID : fcb162ce:35550071:cdb382d3:2a5b776d
Name : node77:1 (local to host node77)
Creation Time : Mon Sep 1 15:15:45 2025
Raid Level : raid0
Raid Devices : 2
Avail Dev Size : 4188160 sectors (2045.00 MiB 2144.34 MB)
Data Offset : 6144 sectors
Super Offset : 8 sectors
Unused Space : before=6064 sectors, after=0 sectors
State : clean
Device UUID : 570444d7:49f0cf2d:fe7aa9ca:5f6d9869
Update Time : Mon Sep 1 15:15:45 2025
Bad Block Log : 512 entries available at offset 8 sectors
Checksum : cb315b22 - correct
Events : 0
Chunk Size : 64K
Device Role : Active device 0
Array State : AA ('A' == active, '.' == missing, 'R' == replacing)
/dev/sdc:
Magic : a92b4efc
Version : 1.2
Feature Map : 0x0
Array UUID : fcb162ce:35550071:cdb382d3:2a5b776d
Name : node77:1 (local to host node77)
Creation Time : Mon Sep 1 15:15:45 2025
Raid Level : raid0
Raid Devices : 2
Avail Dev Size : 4188160 sectors (2045.00 MiB 2144.34 MB)
Data Offset : 6144 sectors
Super Offset : 8 sectors
Unused Space : before=6064 sectors, after=0 sectors
State : clean
Device UUID : e817f362:1a7cb393:70e2c0f4:c3e57842
Update Time : Mon Sep 1 15:15:45 2025
Bad Block Log : 512 entries available at offset 8 sectors
Checksum : bfbbda5b - correct
Events : 0
Chunk Size : 64K
Device Role : Active device 1
Array State : AA ('A' == active, '.' == missing, 'R' == replacing)
Ở output trên chúng ta thấy
- Mảng RAID0 gồm 2 đĩa
/dev/sdb
(role 0) và/dev/sdc
(role 1). - Chunk size = 64 KB, dữ liệu bắt đầu từ offset 3 MiB.
- Metadata v1.2 nằm ở sector 8.
- Raid có cả hai đĩa đều active, UUID mảng trùng khớp.
Thông số layout quan trọng
- Chunk Size : 64K
→ Đây là kích thước stripe unit (chunk). Mỗi block dữ liệu 64 KB được ghi lần lượt xen kẽ sang từng đĩa. - Device Role : Active device 0 / 1
→ Vị trí thứ tự của disk trong raid:/dev/sdb
là ổ số 0,/dev/sdc
là ổ số 1. - Array State : AA
→ Có 2 ký tự ứng với 2 đĩa trong mảng. “A” = active, “.” = missing. Ở đây “AA” nghĩa là cả hai đĩa đều có mặt và đang active.
Ghép lại mảng RAID0 từ hai ổ, ở chế độ chỉ-đọc, không thay đổi metadata.
shell> mdadm --assemble --readonly /dev/md1 /dev/sdb /dev/sdc
mdadm: /dev/md1 has been started with 2 drives.
Gắn /dev/md1
ở chế độ chỉ-đọc, bỏ qua journal, đảm bảo an toàn dữ liệu.
shell> mkdir /mnt/recovered
shell> mount -o ro,noload /dev/md1 /mnt/recovered
shell> df -h
Filesystem Size Used Avail Use% Mounted on
tmpfs 1.6G 1.2M 1.6G 1% /run
/dev/mapper/ubuntu--vg-ubuntu--lv 47G 12G 34G 25% /
tmpfs 7.9G 0 7.9G 0% /dev/shm
tmpfs 5.0M 0 5.0M 0% /run/lock
/dev/sda2 2.0G 126M 1.7G 7% /boot
tmpfs 1.6G 4.0K 1.6G 1% /run/user/1001
/dev/md1 3.9G 997M 2.7G 27% /mnt/recovered
shell> ls /mnt/recovered
bin etc lost+found var
Nếu mount OK → sao chép dữ liệu.
2.2. Bỏ qua metadata, ghép tay (dùng khi muốn khóa cứng OFFSET/CHUNK)
Cách này sẽ bỏ qua hoàn toàn metadata mdadm, bạn phải tự chỉ định offset, chunk size và order.
Ưu điểm: kiểm soát tuyệt đối, dùng được cả khi metadata mất/hỏng.
Nhược điểm: phải đoán hoặc dò layout; sai thông số thì không mount được hoặc dữ liệu sai.
Trong demo trên, mình đang dùng offset 3 MiB cho mỗi image để đi thẳng vào vùng data:
Gắn loop, mỗi loop bỏ qua đúng 3 MiB.
shell> L0=$(sudo losetup -f --show -o 3145728 ~/disk0.img)
shell> L1=$(sudo losetup -f --show -o 3145728 ~/disk1.img)
shell> echo $L0
/dev/loop4
shell>echo $L1
/dev/loop5
Build RAID0 mới (không đụng metadata), chunk 64 KB
shell> sudo mdadm --build /dev/md2 --level=0 --raid-devices=2 --chunk=64 \
--assume-clean "$L0" "$L1"
mdadm: array /dev/md2 built and started.
Umount /mnt/recovered vì ở ví dụ trên chúng ta đang sử dụng nó để mount.
umount -l /mnt/recovered
Mount chỉ-đọc, không replay journal.
shell> sudo mount -o ro,noload /dev/md2 /mnt/recovered
shell> ls /mnt/recovered
bin etc lost+found var
- Ưu điểm: kiểm soát rõ ràng OFFSET/CHUNK
- Nhược điểm: phải biết/đoán đúng
ORDER
.
Nếu ext4
hợp lệ, đọc 2 byte magic tại offset 0x438
(1080) của /dev/md9
sẽ là 53 EF
:
sudo dd if=/dev/md9 bs=1 skip=1080 count=2 status=none | hexdump -C
3. Kịch bản mất metadata RAID (còn metadata của ext4)
Mục tiêu là phải dò đúng bộ ba OFFSET–ORDER–CHUNK để ext4 mount được.
3.1. Chuẩn bị
Nhìn vào kết quả lsblk chúng ta thấy đang còn các device name và thư mục mount đang được sử dụng ở ví dụ trên.
shell> lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
loop0 7:0 0 62M 1 loop /snap/core20/1587
loop1 7:1 0 79.9M 1 loop /snap/lxd/22923
loop2 7:2 0 47M 1 loop /snap/snapd/16292
loop3 7:3 0 89.4M 1 loop /snap/lxd/31333
loop4 7:4 0 2G 0 loop
└─md2 9:2 0 4G 0 raid0 /mnt/recovered
loop5 7:5 0 2G 0 loop
└─md2 9:2 0 4G 0 raid0 /mnt/recovered
sda 8:0 0 50G 0 disk
├─sda1 8:1 0 1M 0 part
├─sda2 8:2 0 2G 0 part /boot
└─sda3 8:3 0 48G 0 part
└─ubuntu--vg-ubuntu--lv 253:0 0 48G 0 lvm /
sdb 8:16 0 2G 0 disk
└─md1 9:1 0 4G 1 raid0
sdc 8:32 0 2G 0 disk
└─md1 9:1 0 4G 1 raid0
sdd 8:48 0 2G 0 disk
sr0 11:0 1 1024M 0 rom
Dọn dẹp sạch sẽ.
shell> sudo umount -f /mnt/recovered 2>/dev/null || true
shell> sudo mdadm --stop /dev/md1 2>/dev/null || true
shell> sudo mdadm --stop /dev/md2 2>/dev/null || true
shell> sudo losetup -D 2>/dev/null || true
shell> lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
loop0 7:0 0 62M 1 loop /snap/core20/1587
loop1 7:1 0 79.9M 1 loop /snap/lxd/22923
loop2 7:2 0 47M 1 loop /snap/snapd/16292
loop3 7:3 0 89.4M 1 loop /snap/lxd/31333
sda 8:0 0 50G 0 disk
├─sda1 8:1 0 1M 0 part
├─sda2 8:2 0 2G 0 part /boot
└─sda3 8:3 0 48G 0 part
└─ubuntu--vg-ubuntu--lv 253:0 0 48G 0 lvm /
sdb 8:16 0 2G 0 disk
sdc 8:32 0 2G 0 disk
sdd 8:48 0 2G 0 disk
sr0 11:0 1 1024M 0 rom
Sao lưu image để giữ bản gốc (tùy chọn).
cp ~/disk0.img ~/d0_demo.img
cp ~/disk1.img ~/d1_demo.img
Xóa metadata mdadm trong image. Với mdadm v1.2 của raid0 trên có Data Offset = 6144 sector = đúng 3 MiB. Vậy zero sạch 3 MiB đầu image là chắc chắn xoá metadata, KHÔNG đụng vùng data.
shell> sudo dd if=/dev/zero of=~/d0_demo.img bs=1M count=3 conv=notrunc status=progress
3+0 records in
3+0 records out
3145728 bytes (3.1 MB, 3.0 MiB) copied, 0.00189227 s, 1.7 GB/s
shell> sudo dd if=/dev/zero of=~/d1_demo.img bs=1M count=3 conv=notrunc status=progress
3+0 records in
3+0 records out
3145728 bytes (3.1 MB, 3.0 MiB) copied, 0.00174413 s, 1.8 GB
Kiểm chứng không còn md superblock, kỳ vọng output là No md superblock detected
.
shell> L0=$(sudo losetup -f --show ~/d0_demo.img)
shell> L1=$(sudo losetup -f --show ~/d1_demo.img)
shell> echo $L0
/dev/loop4
shell> echo $L1
/dev/loop5
shell> sudo mdadm --examine "$L0" || true
mdadm: No md superblock detected on /dev/loop4.
shell> sudo mdadm --examine "$L1" || true
mdadm: No md superblock detected on /dev/loop5.
Tháo loop.
shell> losetup -d "$L0" "$L1"
shell> lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
loop0 7:0 0 62M 1 loop /snap/core20/1587
loop1 7:1 0 79.9M 1 loop /snap/lxd/22923
loop2 7:2 0 47M 1 loop /snap/snapd/16292
loop3 7:3 0 89.4M 1 loop /snap/lxd/31333
sda 8:0 0 50G 0 disk
├─sda1 8:1 0 1M 0 part
├─sda2 8:2 0 2G 0 part /boot
└─sda3 8:3 0 48G 0 part
└─ubuntu--vg-ubuntu--lv 253:0 0 48G 0 lvm /
sdb 8:16 0 2G 0 disk
sdc 8:32 0 2G 0 disk
sdd 8:48 0 2G 0 disk
sr0 11:0 1 1024M 0 rom
3.2. Dò layout tự động (script brute-force)
Sử dụng script raid0_bruteforce.sh
để thử OFFSETS × ORDER × CHUNKS, kiểm tra ext4 magic và thử mount bằng primary hoặc backup superblock.
cat > raid0_bruteforce.sh << 'OEF'
#!/usr/bin/env bash
set -euo pipefail
IMG0=${1:-~/d0_demo.img}
IMG1=${2:-~/d1_demo.img}
OFFSETS=(0 1048576 2097152 3145728 4194304 8388608) # 0,1M,2M,3M,4M,8M
CHUNKS=(32 64 128 256) # KB
BACKUP_SB_4K=(32768 98304 163840 229376 294912 819200 884736) # mkfs.ext4 in ra
MNT=/mnt/recovered
cleanup() {
sudo umount -f "$MNT" 2>/dev/null || true
sudo mdadm --stop /dev/md9 2>/dev/null || true
sudo losetup -D 2>/dev/null || true
}
trap cleanup EXIT
cleanup
sudo mkdir -p "$MNT"
for OFF in "${OFFSETS[@]}"; do
# gắn loop với offset đang thử
L0=$(sudo losetup -f --show -o $OFF "$IMG0")
L1=$(sudo losetup -f --show -o $OFF "$IMG1")
for ORDER in "$L0 $L1" "$L1 $L0"; do
for C in "${CHUNKS[@]}"; do
echo "=== Try: offset=$OFF order=[$ORDER] chunk=${C}KB ==="
sudo mdadm --stop /dev/md9 2>/dev/null || true
sudo mdadm --build /dev/md9 --level=0 --raid-devices=2 --chunk=$C --assume-clean $ORDER
# 1) check magic primary 0xEF53 ở byte 1080
if sudo dd if=/dev/md9 bs=1 skip=1080 count=2 status=none | grep -q $'\x53\xEF'; then
echo ">>> Primary superblock found (53 ef). Mounting ro..."
if sudo mount -o ro,noload /dev/md9 "$MNT" 2>/dev/null; then
echo "OK -> offset=$OFF order=[$ORDER] chunk=${C}KB"
ls -l "$MNT"
exit 0
fi
fi
# 2) nếu primary ko thấy/ko mount, thử backup SB
for B in "${BACKUP_SB_4K[@]}"; do
SB_1K=$((B*4)) # sb= dùng block 1KB
if sudo mount -o ro,noload,sb=$SB_1K /dev/md9 "$MNT" 2>/dev/null; then
echo ">>> MOUNT OK with backup SB=$B (sb=$SB_1K 1KB-blocks)"
echo "-> offset=$OFF order=[$ORDER] chunk=${C}KB"
ls -l "$MNT"
exit 0
fi
done
done
done
# tháo loop trước khi thử offset tiếp theo
sudo mdadm --stop /dev/md9 2>/dev/null || true
sudo losetup -d "$L0" "$L1"
done
echo "❌ Không dò được layout. Mở rộng phạm vi OFFSETS/CHUNKS hoặc kiểm tra image."
exit 1
OEF
chmod +x raid0_bruteforce.sh
Khung dò cơ bản như sau:
- OFFSETS ví dụ:
0, 1M, 2M, 3M, 4M, 8M
- CHUNKS ví dụ:
32, 64, 128, 256
(KB) - ORDER: cả hai hướng
L0 L1
vàL1 L0
- Kiểm tra:
- Đọc 2 byte ở 1080 để kiếm
53 EF
- Nếu chưa mount được, thử lần lượt backup superblock (danh sách lấy từ
mke2fs -n
hoặcdumpe2fs
)
- Đọc 2 byte ở 1080 để kiếm
Kết quả nếu tìm ra offset=3145728 (3 MiB), order=[L0 L1], chunk=32KB
, mount thành công ro,noload
và liệt kê được bin/ etc/ var/
.
shell> ./raid0_bruteforce.sh ~/d0_demo.img ~/d1_demo.img
=== Try: offset=0 order=[/dev/loop2 /dev/loop4] chunk=32KB ===
mdadm: array /dev/md9 built and started.
=== Try: offset=0 order=[/dev/loop2 /dev/loop4] chunk=64KB ===
mdadm: array /dev/md9 built and started.
=== Try: offset=0 order=[/dev/loop2 /dev/loop4] chunk=128KB ===
<đã lược bỏ bớt logs>
mdadm: array /dev/md9 built and started.
=== Try: offset=3145728 order=[/dev/loop2 /dev/loop4] chunk=32KB ===
mdadm: array /dev/md9 built and started.
>>> Primary superblock found (53 ef). Mounting ro...
OK -> offset=3145728 order=[/dev/loop2 /dev/loop4] chunk=32KB
total 52
drwxr-xr-x 2 root root 28672 Aug 26 15:17 bin
drwxr-xr-x 97 root root 4096 Aug 26 15:17 etc
drwx------ 2 root root 16384 Aug 26 15:17 lost+found
drwxr-xr-x 13 root root 4096 Aug 26 15:17 var
Đây là bằng chứng layout đã khớp.
5. Một vài ví dụ tình huống thường gặp
Biết chunk (64 KB), không chắc ORDER
Giữ OFFSET
cố định (ví dụ 3 MiB), thử hai thứ tự:--build ... diskA diskB
rồi --build ... diskB diskA
.
Kiểm tra 53 EF
và thử mount ro,noload
. Thành công → đúng ORDER.
Image từ phân vùng thay vì nguyên đĩa
OFFSET
có thể khác do vị trí bắt đầu phân vùng (alignment). Hãy mở rộng mảng OFFSETS (ví dụ thêm 2048*512 = 1 MiB
, 4096*512 = 2 MiB
, … theo LBA bắt đầu).
Ext4 blocksize 1 KB vs 4 KB
Khi dùng sb=
phải quy đổi đúng đơn vị 1 KB. Nếu bạn thấy danh sách backup theo 4 KB, đừng quên nhân 4.
6. Lời khuyên (best practices)
- Sao lưu sector-level trước: dùng
ddrescue
/dd
để build image read-only; thao tác cứu dữ liệu trên bản sao. - Đảm bảo chỉ-đọc:
--readonly
,-o ro,noload
, tránh bất kỳ ghi đè nào lên image gốc. - Ghi chép layout ngay khi tạo RAID:
chunk
,level
, thứ tự đĩa, offset; lưumdadm --detail --scan
vào note. - Cẩn trọng với
--build
: chỉ dùng khi không còn metadata; luôn kèm--assume-clean
. - Mở rộng phạm vi dò nếu chưa ra: thêm OFFSETS (0/1M/2M/3M/4M/8M/16M…), CHUNKS (16–1024 KB) và cả ORDER.
- Kiểm tra magic ext4 (0xEF53) để lọc nhanh tổ hợp sai; sau đó thử mount bằng backup SB trước khi kết luận hỏng.
- Phân biệt 512e vs 4Kn: sai giả định size sector đôi khi làm lệch OFFSET; kiểm tra thông tin thiết bị/image.
- Không tin tuyệt đối vào số đẹp: OFFSET 1–4 MiB là phổ biến nhưng không bắt buộc; luôn xác nhận bằng thực nghiệm.
8. Kết luận
- Khi còn metadata mdadm, phục hồi RAID0 hầu như là thao tác assemble/mount chỉ-đọc.
- Khi mất metadata mdadm, vẫn còn đường cứu nhờ metadata ext4: brute-force OFFSET–ORDER–CHUNK, xác thực bằng ext4 magic và backup superblock, rồi mount
ro,noload
. - Luôn làm trên bản sao, mở rộng phạm vi dò khi cần và ưu tiên các bước an toàn để bảo toàn dữ liệu.