본문 바로가기

Job Notes/File System

[펌] FAT 구조 파악 및 ATAPI 명령의 개요

MBR(Master Boot Record)은 PC에서 사용되는 포맷된 하드디스크 드라이브의 첫 번째 섹터의 내용을 말한다. 리눅스를 인스톨하거나 멀티부트를 시도해 본 사람이라면 자주 접해보았던 용어일 것이다. MBR은 CHS (cylinder Head Sector) 어드레스로 헤드=0, 실린더=0, 섹터=1 위치에 존재한다. 이 어드레스는 LBA(Linear Block Address)로는 0에 해당된다. MBR은 부팅에 필요한 실행 코드와 디스크의 파티션에 대한 정보를 1섹터(512바이트) 크기 내에 저장하고 있다. 지난 호에 소개했던 READ SECTORS 명령을 사용해 MBR을 읽어보기로 하겠다.

마스터 부트 레코드
우선 포맷된 하드디스크를 보드에 연결한 후 ata.c를 컴파일한 ata.hex 파일을 다운로드하여 실행시킨다. 하드디스크는 마스터로 설정되어 있어야 한다. 이 과정에 대한 상세한 사항은 필자의 홈페이지(참고자료 ?)의 퍼스널 프로젝트에 설명되어 있으니 참고하기 바란다.
우선 LBA=0의 섹터를 1섹터 크기만큼 읽어 덤프해 본다. rd_lba 명령의 형식은 다음과 같다. 현재 하드웨어에서 이 명령으로 읽을 수 있는 섹터의 최대 사이즈는 8섹터(4KB)이다.

rd_lba

<리스트 1> MBR 덤프 내용
$ rd_lba 0 1
Read LBA done

$ dump_buf

E000: 33 C0 8E D0 BC 00 7C FB 50 07 50 1F FC BE 1B 7C 3.....|. P.P....|
E010: BF 1B 06 50 57 B9 E5 01 F3 A4 CB BE BE 07 B1 04 ...PW... ........
E020: 38 2C 7C 09 75 15 83 C6 10 E2 F5 CD 18 8B 14 8B 8,|.u... ........
E030: EE 83 C6 10 49 74 16 38 2C 74 F6 BE 10 07 4E AC ....It.8 ,t....N.
E040: 3C 00 74 FA BB 07 00 B4 0E CD 10 EB F2 89 46 25 <.t..... ......F%
E050: 96 8A 46 04 B4 06 3C 0E 74 11 B4 0B 3C 0C 74 05 ..F...<. t...<.t.
E060: 3A C4 75 2B 40 C6 46 25 06 75 24 BB AA 55 50 B4 :.u+@.F% .u$..UP.
E070: 41 CD 13 58 72 16 81 FB 55 AA 75 10 F6 C1 01 74 A..Xr... U.u....t
E080: 0B 8A E0 88 56 24 C7 06 A1 06 EB 1E 88 66 04 BF ....V$.. .....f..
E090: 0A 00 B8 01 02 8B DC 33 C9 83 FF 05 7F 03 8B 4E .......3 .......N
E0A0: 25 03 4E 02 CD 13 72 29 BE 76 07 81 3E FE 7D 55 %.N...r) .v..>.}U
E0B0: AA 74 5A 83 EF 05 7F DA 85 F6 75 83 BE 48 07 EB .tZ..... ..u..H..
E0C0: 8A 98 91 52 99 03 46 08 13 56 0A E8 12 00 5A EB ...R..F. .V....Z.
E0D0: D5 4F 74 E4 33 C0 CD 13 EB B8 00 00 80 05 13 14 .Ot.3... ........
E0E0: 56 33 F6 56 56 52 50 06 53 51 BE 10 00 56 8B F4 V3.VVRP. SQ...V..
E0F0: 50 52 B8 00 42 8A 56 24 CD 13 5A 58 8D 64 10 72 PR..B.V$ ..ZX.d.r
E100: 0A 40 75 01 42 80 C7 02 E2 F7 F8 5E C3 EB 74 BA .@u.B... ...^..t.
E110: D0 C7 D2 20 C5 D7 C0 CC BA ED C0 CC 20 C0 DF B8 ... .... .... ...
E120: F8 B5 C7 BE FA BD C0 B4 CF B4 D9 2E 20 BC B3 C4 ........ .... ...
E130: A1 B8 A6 20 B0 E8 BC D3 C7 D2 20 BC F6 20 BE F8 ... .... .. .. ..
E140: BD C0 B4 CF B4 D9 2E 00 BF EE BF B5 20 C3 BC C1 ........ .... ...
E150: A6 B8 A6 20 B7 CE B5 E5 C7 CF B4 C2 20 C1 DF 20 ... .... .... ..
E160: BF C0 B7 F9 B0 A1 20 B9 DF BB FD C7 CF BF B4 BD ...... . ........
E170: C0 B4 CF B4 D9 2E 00 00 00 00 00 00 00 00 00 00 ........ ........
E180: 00 00 00 8B FC 1E 57 8B F5 CB 00 00 00 00 00 00 ......W. ........
E190: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
E1A0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
E1B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 80 01 ........ ........
E1C0: 01 00 0B 3F FF FD 3F 00 00 00 41 E0 3E 00 00 00 ...?..?. ..A.>...
E1D0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
E1E0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
E1F0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 AA ........ ......U.

사용한 하드디스크 드라이브는 윈도우 98 세컨드 에디션에서 포맷한 것으로, 읽은 내용의 결과는 운영체제의 종류에 따라 다를 수 있다. 덤프된 내용의 초반부는 기계어 코드로, PC에 전원을 투입했을 때 수행되는 POST(Power on Self Test) 직후 디스크로부터 로드되어 실행된다. 이는 주로 파티션 테이블의 정보를 통해 부트 레코드를 찾아 다시 부트 레코드 내의 코드를 실행하는 과정을 수행하게 된다. MBR에는 총 4개까지 파티션 정보가 존재하는데, 이중 하나의 파티션만 부팅 가능한 것으로 설정된다. MBR 내에서 실제 파티션 테이블의 상대적 위치는 각각 1BEh, 1CEh, 1DEh, 1EEh로서, 덤프된 내용의 E1BEh, E1CEh, E1DEh, E1EEh에서 해당 값을 확인할 수 있다.
각 파티션 테이블의 구성을 구조체 형식으로 아래에 나타내 보았다. 그리고 각 항목에 해당하는 첫 번째 파티션 정보(E1BEh)의 실제 값을 오른쪽에는 표시해 두었다(여기서 제시한 각 구조에 대한 정보는 공식 자료에 의한 것이 아니고 개인적으로 여기저기서 모은 정보를 취합·분석한 것이므로 오류가 있을 수도 있으며 내용이 불충분한 경우도 있음을 밝혀둔다. 이에 대해 양해를 구하며 잘못된 부분이 있으면 수정·보완할 수 있도록 지적해 주기 바란다).

struct partition_table
{
BYTE bootable; // 00h, ,80
BYTE start_drv_head; // 01h, ,01
WORD start_sec_cyl; // 02h, ,01 00
BYTE type; // 04h, ,0B
BYTE end_sec; // 05h, ,3F
WORD end_cyl; // 06h, ,FF FD
DWORD start_lba; // 08h, ,3F 00 00 00
DWORD end_lba; // 0Ch, ,41 E0 3E 00
};

// BYTE : unsigned 8bit
// WORD : unsigned 16bit
// DWORD: unsigned 32bit

맨 처음 바이트인 bootable은 해당 파티션이 부팅 가능한 것인지를 나타내는 것이다. 이 값은 부팅 가능한 경우 80h, 불가능한 경우 00h가 설정된다. 앞의 예에서 첫 번째 파티션이 부팅 가능한 것임을 알 수 있다. 다음 항목인 start_drv_head와 start_sec_cyl은 파티션 정보 확인 후 부트 레코드를 읽어오기 위해 필요한 부트 섹터의 어드레스를 나타낸다. 그 내용은 Int11 인터럽트의 파라미터로 바로 사용될 수 있는 형식으로 되어 있다. type은 도스의 경우 파일 시스템의 종류를 나타내는 것으로 그 내용은 다음과 같다. 여기서는 FAT32가 사용되고 있음을 알 수 있다. 다음은 파티션 타입의 종류를 보여준다.

Type=0 : empty
Type=1 : DOS FAT12
Type=4 : DOS FAT16, 32MB 이하
Type=5 : DOS Extended partition
Type=6 : DOS FAT16, 32MB 이상
Type=Bh : FAT32
Type=Ch : FAT32, LBA 모드

type 이후의 항목들은 해당 파티션의 크기를 CHS와 LBA의 범위로 나타낸 것이다. 특히 start_lba는 파티션의 시작 어드레스를 LBA로 나타낸 것으로, 부트 레코드 위치의 LBA 값을 나타낸다. 덤프된 내용에서 확인할 수 있듯이 MBR의 내용은 x86 CPU의 전통대로 리틀 엔디안으로 되어 있으므로 16비트(WORD)나 32비트(DOUBLE WORD) 값을 참조할 때는 뒤에서 앞으로 읽게 된다. 익숙하지 않은 사람은 주의할 필요가 있다. 참고로 MBR 섹터의 끝에는 항상 매직코드인 AA 55가 쓰인다


 부트 레코드
MBR을 확인했으므로 이번에는 부트 레코드를 찾아 그 내용을 살펴보기로 하겠다. 부트 레코드는 해당 파티션의 첫 번째 섹터에 위치하게 된다. 따라서 부팅 가능한 파티션인 1BEh 위치의 start_lba의 값이 바로 부트 레코드의 어드레스를 나타내게 된다. 앞의 예에서는 0000003Fh가 start_lba가 되므로 이 어드레스를 1섹터 덤프해 본다.

<리스트 2> 부트 레코드 덤프 내용
$ rd_lba 3f 1
Read LBA done

$ dump_buf

E000: EB 58 90 4D 53 57 49 4E 34 2E 31 00 02 08 31 00 .X.MSWIN 4.1...1.
E010: 02 00 00 00 00 F8 00 00 3F 00 40 00 3F 00 00 00 ........ ?.@.?...
E020: 41 E0 3E 00 B8 0F 00 00 00 00 00 00 02 00 00 00 A.>..... ........
E030: 01 00 06 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
E040: 80 00 29 FE 11 2A 05 4C 41 53 45 20 20 20 20 20 ..)..*.L ASE
E050: 20 20 46 41 54 33 32 20 20 20 FA 33 C9 8E D1 BC FAT32 .3....
E060: F8 7B 8E C1 BD 78 00 C5 76 00 1E 56 16 55 BF 22 .{...x.. v..V.U."
E070: 05 89 7E 00 89 4E 02 B1 0B FC F3 A4 8E D9 BD 00 ..~..N.. ........
E080: 7C C6 45 FE 0F 8B 46 18 88 45 F9 38 4E 40 7D 25 |.E...F. .E.8N@}%
E090: 8B C1 99 BB 00 07 E8 97 00 72 1A 83 EB 3A 66 A1 ........ .r...:f.
E0A0: 1C 7C 66 3B 07 8A 57 FC 75 06 80 CA 02 88 56 02 .|f;..W. u.....V.
E0B0: 80 C3 10 73 ED BF 02 00 83 7E 16 00 75 45 8B 46 ...s.... .~..uE.F
E0C0: 1C 8B 56 1E B9 03 00 49 40 75 01 42 BB 00 7E E8 ..V....I @u.B..~.
E0D0: 5F 00 73 26 B0 F8 4F 74 1D 8B 46 32 33 D2 B9 03 _.s&..Ot ..F23...
E0E0: 00 3B C8 77 1E 8B 76 0E 3B CE 73 17 2B F1 03 46 .;.w..v. ;.s.+..F
E0F0: 1C 13 56 1E EB D1 73 0B EB 27 83 7E 2A 00 77 03 ..V...s. .'.~*.w.
E100: E9 FD 02 BE 7E 7D AC 98 03 F0 AC 84 C0 74 17 3C ....~}.. .....t.<
E110: FF 74 09 B4 0E BB 07 00 CD 10 EB EE BE 81 7D EB .t...... ......}.
E120: E5 BE 7F 7D EB E0 98 CD 16 5E 1F 66 8F 04 CD 19 ...}.... .^.f....
E130: 41 56 66 6A 00 52 50 06 53 6A 01 6A 10 8B F4 60 AVfj.RP. Sj.j...`
E140: 80 7E 02 0E 75 04 B4 42 EB 1D 91 92 33 D2 F7 76 .~..u..B ....3..v
E150: 18 91 F7 76 18 42 87 CA F7 76 1A 8A F2 8A E8 C0 ...v.B.. .v......
E160: CC 02 0A CC B8 01 02 8A 56 40 CD 13 61 8D 64 10 ........ V@..a.d.
E170: 5E 72 0A 40 75 01 42 03 5E 0B 49 75 B4 C3 03 18 ^r.@u.B. ^.Iu....
E180: 01 27 0D 0A 49 6E 76 61 6C 69 64 20 73 79 73 74 .'..Inva lid syst
E190: 65 6D 20 64 69 73 6B FF 0D 0A 44 69 73 6B 20 49 em disk. ..Disk I
E1A0: 2F 4F 20 65 72 72 6F 72 FF 0D 0A 52 65 70 6C 61 /O error ...Repla
E1B0: 63 65 20 74 68 65 20 64 69 73 6B 2C 20 61 6E 64 ce the d isk, and
E1C0: 20 74 68 65 6E 20 70 72 65 73 73 20 61 6E 79 20 then pr ess any
E1D0: 6B 65 79 0D 0A 00 00 00 49 4F 20 20 20 20 20 20 key..... IO
E1E0: 53 59 53 4D 53 44 4F 53 20 20 20 53 59 53 7E 01 SYSMSDOS SYS~.
E1F0: 00 57 49 4E 42 4F 4F 54 20 53 59 53 00 00 55 AA .WINBOOT SYS..U.

부트 레코드의 전체 구조는 다음과 같다. 그 내용은 파일 시스템에 대한 각종 정보와 실제 부팅시 사용되는 코드(boot_code)로 구성되어 있다.

struct boot_sector_32
{
BYTE jump[3]; //00h , EB 58 90
BYTE oem_name[8]; //03h , “MSWIN4.1”
BPB32 bpb_32; //0Bh ,
BYTE drive_number; //40h , 80
BYTE not_used; //41h , 00
BYTE ext_boot_signature; //42h , 29
DWORD serial_number; //43h , FE 11 2A 05
char volume_label[]; //47h , “LASE”
char file_system[]; //52h , “FAT32”
BYTE boot_code[422]; //5Ah
};

이중 FAT의 구조를 알기 위해 가장 주의 깊게 살펴봐야 하는 것은 BPB(BIOS Parameter Block)로 앞의 구조에는 bpb_32로 표시되어 있고, 상세 구조는 다음과 같다.

typedef struct bpb_32
{
WORD bytes_per_sector; //0Bh 00 02
BYTE sectors_per_cluster; //0Dh 08
WORD reserved_sectors; //0Eh 31 00
BYTE number_of_fats; //10h 02
WORD root_entries; //11h 00 00
WORD total_sectors_small; //13h 00 00
BYTE media_descriptor; //15h F8
WORD sectors_per_fat; //16h 00 00
WORD sectors_per_track; //18h 3F 00
WORD heads_per_cylinder; //1Ah 40 00
DWORD hidden_sectors; //1Ch 3F 00 00 00
DWORD total_sectors_large; //20h 41 E0 3E 00
DWORD sectors_per_fat32; //24h B8 0F 00 00
WORD flags; //28h 00 00
WORD version; //2Ah 00 00
DWORD root_cluster; //2Ch 02 00 00 00
WORD infor_sector; //30h 01 00
WORD boot_backup_start; //32h 06 00 00 00
BYTE reserved32[12]; //36h
}BPB32;

먼저 섹터당 바이트 수를 나타내는 bytes_per_sector는, 이미 알고있는 대로 200h(512바이트)로 설정되어 있다.

sectors_per_cluster는 클러스터당 섹터의 수를 나타낸다. 클러스터는 윈도우에서 한 개의 FAT 엔트리가 나타내는 영역의 크기이다. 운영체제에서는 FAT를 기준으로 하드디스크 드라이브 내의 데이터를 관리하게 되므로, 클러스터는 데이터를 액세스하는 기준 단위가 된다. FAT32에서는 나타난 바와 같이 8섹터, 즉 4KB이다. FAT32에서는 8섹터를 디스크 액세스의 기본 단위로 사용하게 된다.

reserved_sector는 부트 섹터와 FAT 정보가 저장된 섹터 사이에 예약된 섹터의 크기를 나타낸다. 이 값은 부트 섹터를 포함한 값이다. FAT 파일 시스템은 FAT 엔트리(1클러스터 크기)간의 체인 연결 구조를 통해 파일을 저장하게 된다. FAT의 내용은 전체 데이터를 관리하는데 가장 중요한 데이터이므로 손실을 대비해 같은 내용을 다른 위치에 반복 저장하도록 되어 있다. number_of_fats는 바로 이 반복 횟수를 나타낸다. 여기서는 해당 값이 2이므로 동일 정보가 2회 저장됨을 알 수 있다.

FAT 정보가 저장된 섹터의 시작 위치는 파티션의 시작 어드레스(부트 레코드 어드레스)에서 reserved_sector만큼 건너뛴 위치부터 시작되며, FAT 영역으로 사용되는 총 섹터의 크기는 sectors_per_ fat32에 number_of_fats를 곱한 값이 된다.
sector_size_for_fat32 = number_of_fats * sectors_per_fat32

root_entries는 FAT16에서 사용하는 내용으로 FAT32에서는 사용하지 않는다. total_sectors_small은 해당 파티션 내의 전체 섹터 수를 나타낸다. 이 값은 16비트이므로 값이 이 범위를 초과하면 이 필드 대신 total_sectors_large가 사용된다. media_descriptor는 사용된 디스크의 종류를 나타내는 것으로 하드디스크의 경우에는 그 값이 F8h가 된다.

sectors_per_track과 heads_per_cylinder는 각각 트랙당 섹터 수, 그리고 실린더당 헤드의 수를 나타낸다. 이 값은 CHS 값을 LBA 값으로 변환할 때 중요한 정보가 되는데, 참고로 CHS로부터 LBA를 구하는 공식은 다음과 같다.

LBA = (((C * head_per_cylinder) + H)* sector_per_head) + S - 1;

FAT 엔트리
이번에는 FAT 정보가 저장된 섹터의 내용을 살펴보기로 하겠다. FAT 정보가 위치한 섹터의 시작 위치는 앞에서 얘기한 대로 부트 레코드를 포함한 예약 섹터(reserved sector) 다음 섹터의 어드레스이므로, 부트레코드 어드레스에 예약 섹터의 크기 값을 더하면 된다. 이 값의 위치를 실제로 읽어보기로 하겠다. 예제에서 FAT 어드레스는 3fh+31h=70h가 된다.

fat_start_address = boot_address + reserved_sectors

<리스트 3> FAT 시작 섹터 덤프 내용
$ rd_lba 70 1
Read LBA done

$ dump_buf

E000: F8 FF FF 0F FF FF FF 0F FF FF FF 0F 04 00 00 00 ........ ........
E010: 05 00 00 00 06 00 00 00 07 00 00 00 08 00 00 00 ........ ........
E020: 09 00 00 00 0A 00 00 00 0B 00 00 00 0C 00 00 00 ........ ........
E030: 0D 00 00 00 0E 00 00 00 0F 00 00 00 10 00 00 00 ........ ........
E040: 11 00 00 00 12 00 00 00 13 00 00 00 14 00 00 00 ........ ........
E050: 15 00 00 00 16 00 00 00 17 00 00 00 18 00 00 00 ........ ........
E060: 19 00 00 00 1A 00 00 00 1B 00 00 00 1C 00 00 00 ........ ........
E070: 1D 00 00 00 1E 00 00 00 1F 00 00 00 20 00 00 00 ........ .... ...
E080: 21 00 00 00 22 00 00 00 23 00 00 00 24 00 00 00 !..."... #...$...
E090: 25 00 00 00 26 00 00 00 27 00 00 00 28 00 00 00 %...&... '...(...
E0A0: 29 00 00 00 2A 00 00 00 2B 00 00 00 2C 00 00 00 )...*... +...,...
E0B0: 2D 00 00 00 2E 00 00 00 2F 00 00 00 30 00 00 00 -....... /...0...
E0C0: 31 00 00 00 32 00 00 00 33 00 00 00 34 00 00 00 1...2... 3...4...
E0D0: 35 00 00 00 36 00 00 00 37 00 00 00 38 00 00 00 5...6... 7...8...
E0E0: 39 00 00 00 FF FF FF 0F FF FF FF 0F 3C 00 00 00 9....... ....<...
E0F0: 3D 00 00 00 3E 00 00 00 3F 00 00 00 40 00 00 00 =...>... ?...@...
E100: 41 00 00 00 42 00 00 00 43 00 00 00 44 00 00 00 A...B... C...D...
E110: 45 00 00 00 46 00 00 00 47 00 00 00 48 00 00 00 E...F... G...H...
E120: 49 00 00 00 4A 00 00 00 4B 00 00 00 4C 00 00 00 I...J... K...L...
E130: 4D 00 00 00 4E 00 00 00 4F 00 00 00 50 00 00 00 M...N... O...P...
E140: 51 00 00 00 52 00 00 00 53 00 00 00 54 00 00 00 Q...R... S...T...
E150: 55 00 00 00 56 00 00 00 57 00 00 00 FF FF FF 0F U...V... W.......
E160: FF FF FF 0F 5A 00 00 00 5B 00 00 00 5C 00 00 00 ....Z... [...\...
E170: 5D 00 00 00 5E 00 00 00 5F 00 00 00 60 00 00 00 ]...^... _...`...
E180: 61 00 00 00 62 00 00 00 63 00 00 00 64 00 00 00 a...b... c...d...
E190: 65 00 00 00 66 00 00 00 67 00 00 00 68 00 00 00 e...f... g...h...
E1A0: 69 00 00 00 6A 00 00 00 6B 00 00 00 6C 00 00 00 i...j... k...l...
E1B0: 6D 00 00 00 6E 00 00 00 6F 00 00 00 70 00 00 00 m...n... o...p...
E1C0: 71 00 00 00 72 00 00 00 73 00 00 00 74 00 00 00 q...r... s...t...
E1D0: 75 00 00 00 76 00 00 00 77 00 00 00 78 00 00 00 u...v... w...x...
E1E0: 79 00 00 00 7A 00 00 00 7B 00 00 00 7C 00 00 00 y...z... {...|...
E1F0: 7D 00 00 00 7E 00 00 00 7F 00 00 00 80 00 00 00 }...~... ........

얼핏 보기에도 4바이트 단위로 정리되어 있음을 알 수 있다. FAT32에서는 4바이트가 하나의 엔트리를 이루며, 각 엔트리는 0번부터 그 위치 값에 해당되는 클러스터에 맵핑되어 있다. 즉 하나의 엔트리는 차례로 8개의 섹터를 나타내며, 엔트리의 내용은 해당 클러스터에 저장된 파일의 다음 클러스터의 번호를 나타낸다. 즉 <리스트 3>에서 E010의 내용은 00000005h인데, 이 위치는 4번 클러스터를 지정하는 것이고, 이 클러스터에 저장된 파일의 다음 클러스터 번호는 5번이 되는 것이다. 5번 클러스터의 경우 6번 클러스터로 연결되어 있고, 계속해서 이 연결고리는 0FFFFFFFh를 만날 때까지 계속 이어짐을 알 수 있다(FAT 영역의 엔트리는 대부분 사용 중이고 클러스터 체인의 마지막을 나타내는 부분도 확인할 수 있다). 각 엔트리의 구체적인 의미는 다음과 같다.

00000000h : 사용 가능
00000001h-0FFFFFEFh : 사용 중
0FFFFFF0h-0FFFFFF6h : 예약
0FFFFFF7h : 배드 클러스터
0FFFFFF8h-0FFFFFFFh : 클러스터 체인의 마지막


루트 디렉토리
데이터 영역은 FAT 정보가 저장된 영역의 바로 다음부터 시작된다. 따라서 데이터 영역의 시작 위치는 다음으로부터 구할 수 있다.
data_address = fat_start_address + number_of_fats * sectors_per_fat32

예제로부터 이 값은 3fh + 31h + 2 * 0fb8h = 1FE0h가 된다. 데이터 영역을 탐색하는 데 가장 기본이 되는 루트 디렉토리의 어드레스는 BPB의 root_cluster와 클러스터당 섹터의 수를 통해 다음의 수식으로 구할 수 있다.

root_address = (root_cluster-2)*sectors_per_cluster + data_address

root_cluster-2 = 0이므로 예제의 경우 데이터 시작 위치가 바로 루트 디렉토리의 위치임을 알 수 있다. 이제 루트 디렉토리의 내용을 읽어 보기로 하겠다.

<리스트 4> 루트 디렉토리 덤프 내용
$ rd_lba 1fe0 4
Read LBA done

$ dump_buf

E000: 49 4F 20 20 20 20 20 20 53 59 53 27 00 00 00 00 IO SYS'....
E010: 00 00 00 00 00 00 C0 B2 A5 26 03 00 96 6B 03 00 ........ .&...k..
E020: 4D 53 44 4F 53 20 20 20 53 59 53 27 00 00 00 00 MSDOS SYS'....
E030: 00 00 00 00 00 00 C0 B2 A5 26 3A 00 09 00 00 00 ........ .&:.....
E040: 43 4F 4D 4D 41 4E 44 20 43 4F 4D 20 00 00 00 00 COMMAND COM ....
E050: 00 00 00 00 00 00 C0 B2 A5 26 3B 00 BE C8 01 00 ........ .&;.....
E060: 4C 41 53 45 20 20 20 20 20 20 20 28 00 00 00 00 LASE (....
E070: 00 00 00 00 00 00 50 56 BA 2E 00 00 00 00 00 00 ......PV ........
E080: 41 63 00 75 00 72 00 65 00 00 00 0F 00 15 FF FF Ac.u.r.e ........
E090: FF FF FF FF FF FF FF FF FF FF 00 00 FF FF FF FF ........ ........
E0A0: 43 55 52 45 20 20 20 20 20 20 20 10 00 9B 57 56 CURE ...WV
E0B0: BA 2E BA 2E 00 00 58 56 BA 2E 58 00 00 00 00 00 ......XV ..X.....
E0C0: 42 74 00 00 00 FF FF FF FF FF FF 0F 00 E7 FF FF Bt...... ........
E0D0: FF FF FF FF FF FF FF FF FF FF 00 00 FF FF FF FF ........ ........
E0E0: 01 63 00 61 00 72 00 64 00 69 00 0F 00 E7 67 00 .c.a.r.d .i....g.
E0F0: 61 00 6E 00 73 00 20 00 62 00 00 00 65 00 73 00 a.n.s. . b...e.s.
E100: 43 41 52 44 49 47 7E 31 20 20 20 10 00 8B 5C 56 CARDIG~1 ...\V
E110: BA 2E BA 2E 00 00 5D 56 BA 2E 1A 2D 00 00 00 00 ......]V ...-....
E120: 41 63 00 72 00 61 00 6E 00 62 00 0F 00 48 65 00 Ac.r.a.n .b...He.
E130: 72 00 72 00 69 00 65 00 73 00 00 00 00 00 FF FF r.r.i.e. s.......
E140: 43 52 41 4E 42 45 7E 31 20 20 20 10 00 52 67 56 CRANBE~1 ..RgV
E150: BA 2E BA 2E 00 00 68 56 BA 2E 95 7B 00 00 00 00 ......hV ...{....
E160: 41 41 00 75 00 74 00 75 00 6D 00 0F 00 CE 6E 00 AA.u.t.u .m....n.
E170: 54 00 65 00 61 00 72 00 73 00 00 00 00 00 FF FF T.e.a.r. s.......
E180: 41 55 54 55 4D 4E 7E 31 20 20 20 10 00 3B 6C 56 AUTUMN~1 ..;lV
E190: BA 2E BA 2E 00 00 6D 56 BA 2E 4D A4 00 00 00 00 ......mV ..M.....
E1A0: 42 72 00 6F 00 6D 00 20 00 43 00 0F 00 6D 72 00 Br.o.m. .C...mr.
E1B0: 65 00 61 00 74 00 69 00 6F 00 00 00 6E 00 00 00 e.a.t.i. o...n...
E1C0: 01 44 00 72 00 79 00 26 00 48 00 0F 00 6D 65 00 .D.r.y.& .H...me.
E1D0: 61 00 76 00 79 00 20 00 2D 00 00 00 20 00 46 00 a.v.y. . -... .F.
E1E0: 44 52 59 26 48 45 7E 31 20 20 20 10 00 0B 6E 56 DRY&HE~1 ...nV
E1F0: BA 2E BA 2E 00 00 6F 56 BA 2E 23 B3 00 00 00 00 ......oV ..#.....

긴 파일명 엔트리
루트 디렉토리를 포함하여 각 디렉토리에는 실제적인 파일 정보가 저장된다. 디렉토리 엔트리는 도스의 8바이트 파일명과 3바이트 확장자 구조를 그대로 유지하고 있는데, 현재의 윈도우처럼 긴 파일명을 지원하기 위한 긴 파일명 엔트리(long filename entry)가 추가 적용된다. 엔트리의 크기는 32바이트 크기이고 기본 디렉토리 엔트리와 긴 파일명 엔트리의 구조는 다음과 같다.

struct dir_entry
{
char filename[8];
char extention[3];
BYTE attribute;
BYTE reserved0;
BYTE vfat_cre_time0;
WORD vfat_cre_time1;
WORD vfat_cre_date;
WORD vfat_last_access;
WORD cluster_num_high;
WORD cre_update_time;
WORD cre_update_date;
WORD cluster_number;
DWORD file_size;
};

struct lfn_entry
{
BYTE sequence;
WORD name1[5];
BYTE attribute;
BYTE reserved;
BYTE checksum;
WORD name2[6];
WORD clusterNumber;
WORD name3[2];
};

덤프된 내용을 관찰해 보면 확인할 수 있듯이 각 엔트리는 긴 파일명 엔트리가 먼저 나오고 마지막에 일반 디렉토리 엔트리가 나오는 형식으로 나열되어 있다. 각 엔트리는 크기가 같으므로 먼저 일반 엔트리인지 긴 파일 엔트리인지를 구분할 필요가 있는데, 이것은 두 구조 모두 세 번째 필드에 포함하고 있는 속성(attribute)을 통해서 알아낼 수 있다. 기본적인 속성의 비트 구성은 다음과 같다.

Bit 0: Read-Only
Bit 1: System
Bit 2: Hidden
Bit 3: Volume
Bit 4: Directory
Bit 5: Archive

긴 파일명 엔트리의 경우 Attribute 값이 이 구성 대신 0Fh 값을 갖도록 되어 있으므로 이를 통해서 두 종류의 엔트리를 구분해 낼 수 있다. lfn_entry는 이름의 길이에 따라 여러 개가 올 수 있으므로 각 엔트리는 시퀀스 필드에 일련번호를 갖게 된다. 시퀀스의 비트 구성은 다음과 같다.
bit0-5: sequence number
bit6 : 마지막 entry를 표시

이름은 엔트리 내에서 세 부분(name1, name2, name3)으로 나눠져 있고, 문자는 16비트 유니코드로 저장되어 있다. 일반 디렉토리 엔트리의 첫 번째 필드는 8자로 구성된 전통적인 도스 파일명인데, 전체 엔트리를 통틀어 첫 번째 character가 다음 중 하나일 때에는 실제 파일명이 아닌 엔트리의 구분 코드 값이 된다.

00h : 빈 엔트리
2Eh : 도트 엔트리로 “.” 또는 “..” 중의 하나이다. 단 루트 디렉토리에는 존재하지 않음. “.” 엔트리는 자신의 디렉토리를 나타내며, “..” 엔트리는 부모 디렉토리를 나타낸다.
E5h : 사용된 적이 있으나 현재는 지워진 엔트리. 다른 파일에 사용될 수 있다.

이와 같은 구조를 참고해서 실제 예를 통해 구성을 살펴보기로 하겠다. 덤프된 내용 중 E0C0h의 엔트리를 보게 되면 하나의 디렉토리 구성을 세 개의 엔트리가 표현하고 있다. 먼저 42h로 시작하는 엔트리는 긴 파일명 엔트리로, 시퀀스 번호에 6번 비트가 설정되어 있으므로 마지막 시퀀스임을 알 수 있다. 이와 같이 긴 파일명 엔트리는 시작 위치의 엔트리가 가장 마지막 엔트리이고 디렉토리 엔트리의 바로 위쪽 엔트리가 파일명의 시작 엔트리인데, 간단히 말해서 거꾸로 되어 있다. 이 엔트리에는 유니코드로 0074h, 즉 ‘t’라는 한 글자만 나타나 있다. 두 번째인 0E0h의 엔트리는 시퀀스 번호가 1번으로 긴 파일명의 시작 엔트리이다. 속성은 앞서 말한 바와 같이 모두 0fh로 되어 있음을 알 수 있다. 마지막 E100h 엔트리는 도스의 오리지널 디렉토리 엔트리로, 도스 창에서 익숙한 “~” 문자로 잘린 8자리의 파일명을 확인할 수 있다. 이 엔트리는 속성에서 알 수 있듯이 디렉토리 엔트리이다.

FAT를 이용한 파일 액세스
마지막으로 FAT와 디렉토리 엔트리를 통해서 실제로 파일을 어떤 과정을 통해 액세스하게 되는지 살펴보도록 하자. E100h의 엔트리의 클러스터 넘버 필드 값은 2D1Ah이다. 즉 이 디렉토리의 정보는 data_address로부터 2D1Ah째 클러스터에 있게 된다. 이처럼 주어진 클러스터 번호로부터 LBA 주소를 구할 수 있는데, 그 방법은 다음과 같다.

file_address = data_address + (cluster_num -2)*sectors_per_cluster

따라서 E100h 디렉토리의 내용은 1FE0h+(2D1Ah-2)*8= 188A0h가 된다. 이 내용을 덤프해 보기로 하겠다.

<리스트 5> 일반 디렉토리 덤프 내용
$ rd_lba 188a0 4
Read LBA done

$ dump_buf

E000: 2E 20 20 20 20 20 20 20 20 20 20 10 00 8B 5C 56 . ...\V
E010: BA 2E BA 2E 00 00 5D 56 BA 2E 1A 2D 00 00 00 00 ......]V ...-....
E020: 2E 2E 20 20 20 20 20 20 20 20 20 10 00 8B 5C 56 .. ...\V
E030: BA 2E BA 2E 00 00 5D 56 BA 2E 00 00 00 00 00 00 ......]V ........
E040: 42 6F 00 6F 00 64 00 79 00 20 00 0F 00 2F 2E 00 Bo.o.d.y . .../..
E050: 6D 00 70 00 33 00 00 00 FF FF 00 00 FF FF FF FF m.p.3... ........
E060: 01 32 00 30 00 2E 00 53 00 61 00 0F 00 2F 62 00 .2.0...S .a.../b.
E070: 62 00 61 00 74 00 68 00 20 00 00 00 42 00 6C 00 b.a.t.h. ...B.l.
E080: 32 30 53 41 42 42 7E 31 4D 50 33 20 00 8D 5C 56 20SABB~1 MP3 ..\V
E090: BA 2E BA 2E 00 00 77 7A E4 2A 1B 2D C8 0C 42 00 ......wz .*.-..B.
E0A0: 42 6D 00 70 00 33 00 00 00 FF FF 0F 00 55 FF FF Bm.p.3.. .....U..
E0B0: FF FF FF FF FF FF FF FF FF FF 00 00 FF FF FF FF ........ ........
E0C0: 01 31 00 39 00 2E 00 41 00 66 00 0F 00 55 74 00 .1.9...A .f...Ut.
E0D0: 65 00 72 00 20 00 41 00 6C 00 00 00 6C 00 2E 00 e.r. .A. l...l...
E0E0: 31 39 41 46 54 45 7E 31 4D 50 33 20 00 19 5D 56 19AFTE~1 MP3 ..]V
E0F0: BA 2E BA 2E 00 00 EC 7A E4 2A 3C 31 96 E5 2A 00 .......z .*<1..*.
E100: 42 64 00 65 00 2E 00 6D 00 70 00 0F 00 1B 33 00 Bd.e...m .p....3.
E110: 00 00 FF FF FF FF FF FF FF FF 00 00 FF FF FF FF ........ ........
E120: 01 31 00 38 00 2E 00 43 00 65 00 0F 00 1B 6C 00 .1.8...C .e....l.
E130: 69 00 61 00 20 00 49 00 6E 00 00 00 73 00 69 00 i.a. .I. n...s.i.
E140: 31 38 43 45 4C 49 7E 31 4D 50 33 20 00 4C 5D 56 18CELI~1 MP3 .L]V
E150: BA 2E BA 2E 00 00 79 7B E4 2A EB 33 78 12 34 00 ......y{ .*.3x.4.
E160: 41 31 00 37 00 2E 00 46 00 69 00 0F 00 4B 6E 00 A1.7...F .i...Kn.
E170: 65 00 2E 00 6D 00 70 00 33 00 00 00 00 00 FF FF e...m.p. 3.......
E180: 31 37 46 49 4E 45 7E 31 4D 50 33 20 00 98 5D 56 17FINE~1 MP3 ..]V
E190: BA 2E BA 2E 00 00 F6 7B E4 2A 2D 37 9E F1 2E 00 .......{ .*-7....
E1A0: 42 4F 00 6E 00 65 00 2E 00 6D 00 0F 00 86 70 00 BO.n.e.. .m....p.
E1B0: 33 00 00 00 FF FF FF FF FF FF 00 00 FF FF FF FF 3....... ........
E1C0: 01 31 00 36 00 2E 00 42 00 65 00 0F 00 86 61 00 .1.6...B .e....a.
E1D0: 75 00 74 00 69 00 66 00 75 00 00 00 6C 00 20 00 u.t.i.f. u...l. .
E1E0: 31 36 42 45 41 55 7E 31 4D 50 33 20 00 19 60 56 16BEAU~1 MP3 ..`V
E1F0: BA 2E BA 2E 00 00 7C 7C E4 2A 1D 3A AA 05 33 00 ......|| .*.:..3.

먼저 2Eh로 시작하는 도트 엔트리(“.” 또는 “..”)를 확인할 수 있다. 세 번째부터는 실제로 이 디렉토리에 있는 파일의 엔트리로, 하드디스크 안에 꼭꼭 숨겨 둔 mp3 파일의 이름을 확인할 수 있다. 이 파일의 내용은 해당 엔트리의 클러스터 번호로부터 시작된다.

이제 디렉토리를 뒤져 파일을 찾는 방법까지 알아냈으니 특정 파일을 처음부터 끝까지 전부 찾아 어떻게 읽어 내는가 하는 문제만 남아 있다. 예를 들어 <리스트 5>에서 첫 번째로 보이는 E080h의 파일 클러스터 번호는 2D1Bh이다. 즉 이 디렉토리 클러스터의 바로 다음 클러스터로부터 데이터가 시작됨을 알 수 있다. 앞서 살펴보았듯이 FAT 엔트리는 각자의 위치가 해당 클러스터 번호를 나타내고 그곳에 저장된 32비트 값은 다음으로 연결되는 클러스터 번호를 나타낸다. E080h의 파일을 끝까지 읽기 위해서는 먼저 2D1Bh의 클러스터 내용을 읽어내고, 그 다음 2D1Bh에 해당하는 FAT 엔트리의 내용을 읽어 다음 클러스터 번호를 가져오게 된다. 파일의 맨 마지막에 이를 때까지 FAT를 참조하여 클러스터를 읽어들이는 과정은 반복된다.

fat_entry_per_sector = bytes_per_sector / 4
fat_address = fat_start_address + cluster_num/fat_entry_per_sector

1섹터당 FAT 엔트리의 수는 위의 식에서 80h이고, 2D1Bh 번째 FAT 엔트리가 있는 섹터는 70h+2D1Bh/128=70h+5Ah=CAh이다. 이 섹터 중에서 해당 클러스터의 FAT는 엔트리는 2D1B-5Ah*80h=1Bh번째이므로 그 위치는 덤프된 내용의 6Ch(1Bh*4) 위치의 값이 된다. 이제 해당 클러스터의 FAT 정보가 있는 섹터를 읽어 보겠다.

<리스트 6> 2D1Bh 클러스터가 있는 FAT 덤프 내용
$ rd_lba ca 1
Read LBA done

$ dump_buf

E000: 01 2D 00 00 02 2D 00 00 03 2D 00 00 04 2D 00 00 .-...-.. .-...-..
E010: 05 2D 00 00 06 2D 00 00 07 2D 00 00 08 2D 00 00 .-...-.. .-...-..
E020: 09 2D 00 00 0A 2D 00 00 0B 2D 00 00 0C 2D 00 00 .-...-.. .-...-..
E030: 0D 2D 00 00 0E 2D 00 00 0F 2D 00 00 10 2D 00 00 .-...-.. .-...-..
E040: 11 2D 00 00 12 2D 00 00 13 2D 00 00 14 2D 00 00 .-...-.. .-...-..
E050: FF FF FF 0F 16 2D 00 00 17 2D 00 00 18 2D 00 00 .....-.. .-...-..
E060: 19 2D 00 00 FF FF FF 0F FF FF FF 0F 1C 2D 00 00 .-...... .....-..
E070: 1D 2D 00 00 1E 2D 00 00 1F 2D 00 00 20 2D 00 00 .-...-.. .-.. -..
E080: 21 2D 00 00 22 2D 00 00 23 2D 00 00 24 2D 00 00 !-.."-.. #-..$-..
E090: 25 2D 00 00 26 2D 00 00 27 2D 00 00 28 2D 00 00 %-..&-.. '-..(-..
E0A0: 29 2D 00 00 2A 2D 00 00 2B 2D 00 00 2C 2D 00 00 )-..*-.. +-..,-..
E0B0: 2D 2D 00 00 2E 2D 00 00 2F 2D 00 00 30 2D 00 00 --...-.. /-..0-..
E0C0: 31 2D 00 00 32 2D 00 00 33 2D 00 00 34 2D 00 00 1-..2-.. 3-..4-..
E0D0: 35 2D 00 00 36 2D 00 00 37 2D 00 00 38 2D 00 00 5-..6-.. 7-..8-..
E0E0: 39 2D 00 00 3A 2D 00 00 3B 2D 00 00 3C 2D 00 00 9-..:-.. ;-..<-..
E0F0: 3D 2D 00 00 3E 2D 00 00 3F 2D 00 00 40 2D 00 00 =-..>-.. ?-..@-..
E100: 41 2D 00 00 42 2D 00 00 43 2D 00 00 44 2D 00 00 A-..B-.. C-..D-..
E110: 45 2D 00 00 46 2D 00 00 47 2D 00 00 48 2D 00 00 E-..F-.. G-..H-..
E120: 49 2D 00 00 4A 2D 00 00 4B 2D 00 00 4C 2D 00 00 I-..J-.. K-..L-..
E130: 4D 2D 00 00 4E 2D 00 00 4F 2D 00 00 50 2D 00 00 M-..N-.. O-..P-..
E140: 51 2D 00 00 52 2D 00 00 53 2D 00 00 54 2D 00 00 Q-..R-.. S-..T-..
E150: 55 2D 00 00 56 2D 00 00 57 2D 00 00 58 2D 00 00 U-..V-.. W-..X-..
E160: 59 2D 00 00 5A 2D 00 00 5B 2D 00 00 5C 2D 00 00 Y-..Z-.. [-..\-..
E170: 5D 2D 00 00 5E 2D 00 00 5F 2D 00 00 60 2D 00 00 ]-..^-.. _-..`-..
E180: 61 2D 00 00 62 2D 00 00 63 2D 00 00 64 2D 00 00 a-..b-.. c-..d-..
E190: 65 2D 00 00 66 2D 00 00 67 2D 00 00 68 2D 00 00 e-..f-.. g-..h-..
E1A0: 69 2D 00 00 6A 2D 00 00 6B 2D 00 00 6C 2D 00 00 i-..j-.. k-..l-..
E1B0: 6D 2D 00 00 6E 2D 00 00 6F 2D 00 00 70 2D 00 00 m-..n-.. o-..p-..
E1C0: 71 2D 00 00 72 2D 00 00 73 2D 00 00 74 2D 00 00 q-..r-.. s-..t-..
E1D0: 75 2D 00 00 76 2D 00 00 77 2D 00 00 78 2D 00 00 u-..v-.. w-..x-..
E1E0: 79 2D 00 00 7A 2D 00 00 7B 2D 00 00 7C 2D 00 00 y-..z-.. {-..|-..
E1F0: 7D 2D 00 00 7E 2D 00 00 7F 2D 00 00 80 2D 00 00 }-..~-.. .-...-..

덤프된 내용에서 그 값은 2D1ch임을 알 수 있다. 즉 파일의 내용은 다음 번의 클러스터로 계속 이어지는 것을 알 수 있다. <리스트 6>에서 클러스터의 내용은 대부분 연속적인 값으로 이어지는 형태이지만 실제로 파일의 수정과 삭제가 반복되면 이 값은 얼핏 보기에 오락가락하는 모습을 보이게 된다. 하지만 클러스터 체인이 끊어지지 않는 한(자주 생기는 일이지만) 파일의 내용은 FAT의 체인을 통해 정확히 액세스가 가능하게 된다. 그렇지만 한 파일의 내용을 저장하는 클러스터가 중구난방으로 흩어져 있으면 그만큼 액세스 시간이 더 소모된다(윈도우 시스템 도구의 디스크 정리는 이처럼 얽힌 클러스터를 위의 덤프된 내용처럼 가지런히 배열하는 역할을 하게 된다).

FAT를 통한 클러스터의 체인은 파일의 마지막에 이르면 0x0fffff 값을 만나게 된다. 따라서 파일의 내용 전체를 읽어내는 작업은 이처럼 FAT의 내용을 확인하면서 클러스터 체인을 따라 마지막 엔트리를 만날 때까지 해당 클러스터를 읽어 내는 과정을 통해 이뤄진다. 실제 운영체제에서는 파일 시스템 프로그램이 디바이스 드라이버의 도움을 받아 이러한 작업을 수행하게 된다. 여기서 사용된 방법은 계산을 해가면서 작업을 일일이 수동으로 했지만 프로그램을 통하여 cd, dir 같은 명령을 만들 수 있는 충분한 정보가 제공된 셈이므로 필요하다면 직접 작성하여 확인해 보는 것도 좋을 것이다. 여기에는 효율을 위해서 FAT의 내용을 캐시로 처리하거나 버퍼를 사용하는 전략도 필요하게 된다. 실제 파일 시스템은 이러한 과정을 수행하는 프로그램의 묶음이라고 보면 될 것이다.

이상으로 FAT32에 대한 하드디스크 파일 시스템의 구조를 간략히 알아보았다. 일견 복잡해 보이지만 과정을 정확히 이해하고 나면 파일 시스템의 구성에 대해 어느 정도 감을 잡을 수 있을 것이다.

이제 주제를 하드디스크에서 CD-ROM으로 옮겨, ATAPI(ATA Packet Interface)에 대해 알아보기로 하겠다. ATAPI는 ATA를 확장해 하드디스크 드라이브 이외의 디바이스도 연결할 수 있도록 한 것이다. I/O 레지스터를 이용해 1바이트의 명령을 레지스터에 적어넣는 ATA 프로토콜과는 달리, ATAPI에서는 명령을 12바이트의 패킷형태로 전달하게 된다. 프로토콜 외에 명령 패킷의 내용은 SCSI와 공유하므로 SCSI 명령에 대한 개념도 부수적으로 얻을 수 있을 것이다

 ATAPI 표준의 개요
ATA 표준에서는 I/O 레지스터를 적절한 값으로 설정하고 명령 레지스터에 명령을 쓰는 과정을 통해 필요한 기능을 수행하게 된다. 1바이트의 명령을 사용하는 형식이므로 근본적으로 명령의 개수에 제한이 있고, 새로운 타입의 디바이스를 적용하고자 했을 때도 여러가지 제약이 있게 된다.

그렇지만 CD-ROM을 필두로 해서 다양한 주변장치를 ATA 인터페이스에 연결하고자 하는 요구가 증가하게 되었고 결국 이 문제를 해결하기 위하여 ATAPI라는 새로운 표준이 탄생하게 되었다. 이름에서도 알 수 있듯이 ATAPI는 1바이트 명령어 대신 12바이트의 패킷을 사용한다. 특히 패킷 명령 자체는 기존의 SCSI의 형태를 거의 그대로 사용하고 있다. 초창기 ATAPI 표준은 SFF(Small Form Factor) 위원회라는 곳에서 제정, 관리하여 CD-ROM 드라이브는 SFF-8020,그리고 DVD-ROM 드라이브는 SFF-8090이라는 표준을 기준으로 해 개발되어 왔다. 그후 권한이 ATA 표준을 관장하는 T13 기술위원회로 이관되어 ATA/ATAPI-4라는 이름으로 통합, 정식 표준화되었다. 하지만 ATA/ATAPI 표준에는 ATAPI의 명령 프로토콜에 대한 상세한 내용은 언급되지 않으며 패킷 명령에 대한 내용은 다음의 두 가지 SCSI 표준과 공유하도록 지정되어 있다.

SCSI-3 Primary Commands(SPC)
SCSI-3 Multimedia Commands(MMC)

이 표준은 SCSI를 관장하는 T10 기술위원회에서 제정, 관리되고 있다. 한편 초기 표준인 SFF-8020, 8090은 이미 구식 표준이 되어 사용하지 않도록 권고되고 있지만 이 문서에는 디스크 드라이브에 대한 전체 내용이 통합적으로 정리되어 있으므로 처음 접하는 사람의 입장에서는 이 문서를 통해 전체적인 개념을 잡고 상세한 사항은 SPC나 MMC 표준을 참조하는 것이 더 효율적일 것이라는 생각이 든다.

패킷 디바이스 전용 명령
ATAPI 표준은 ATA 표준을 확장한 것이므로 ATA 형식을 그대로 사용하는 패킷 전용 디바이스 명령이 여러 개 추가되어 있는데, 그 내용은 다음과 같다.

◆ Packet(A0h) - 12바이트의 패킷을 전송하기 전에 디바이스에게 패킷 수신을 준비시키기 위한 명령어

◆ Identify Packet Device(A1h) - ATA의 IDENTIFY DEVICE 명령과 유사한 것으로, 디바이스의 각종 정보를 요청하기 위해 사용된다.

◆ Device Reset(08h) - ATA 디바이스가 디바이스 컨트롤 레지스터의 SRST 비트를 이용해 소프트웨어 리셋을 수행하는 것과는 달리 패킷 디바이스는 Device Reset이라는 별도의 명령을 사용해 디바이스 소프트웨어 리셋을 수행한다.

◆ Service(A2h) - ATA/ATAPI에 정의된 중첩 명령 기능(overlapped feature)을 사용할 때 쓰이는 명령이다. 중첩 명령 기능은 한 포트에 두 개의 디바이스가 연결되어 있는 상태에서 한쪽의 디바이스에 명령을 내린 후 명령 수행이 종료되기 전에 다른 쪽 디바이스에도 명령을 내려 전체적인 성능을 올리기 위한 용도로 사용된다. 이 기능은 여기서는 고려하지 않았다.

패킷 명령의 프로토콜
<그림 1>에 데이터 전송을 수반하는 경우(PIO data)와 수반하지 않는 경우(non-data) 모두에 대한 프로토콜의 상태도를 나타내었다. 각종 상태 비트의 사용 등은 ATA와 유사하지만 패킷 전송 단계가 추가되어 있다.

ATAPI 프로토콜 '>





S0 : 명령 대기 상태에 있는 드라이브에 Packet 명령(a0h)을 내린 후 상태 레지스터를 체크한다. DRQ=1이면 패킷을 받을 준비가 되어있음을 나타낸다.
S1 : 12바이트의 패킷을 차례로 데이터 레지스터에 써넣는다.
S2 : 드라이브가 패킷의 내용을 판독해 주어진 명령을 수행하였거나 전송할 데이터가 있으면 인터럽트를 발생시킨다.
S3 : 데이터 전송을 수반하지 않는 명령의 경우에는 이 상태에서 명령 사이클이 종료된다.
S4: 데이터 전송을 수반하는 명령의 경우에는 DRQ=1이 되고 이에 따라 1DRQ 블럭분의 데이터를 전송한다. 이후 전체 데이터가 전송 완료될 때까지 S3, S4를 반복한다.

하드디스크 드라이브의 경우 1블럭은 512바이트가 되지만, CD-ROM 또는 DVD-ROM의 경우 기본 섹터 사이즈는 2048바이트이다. 단 CD-ROM의 경우 데이터의 형식에 따라 다양한 값을 갖게 된다.

기본적인 패킷의 형태
SCSI에서는 6, 10, 12, 16패킷 사이즈에서 가변 길이에 이르기까지 다양한 길이의 패킷이 사용된다. 그렇지만 ATAPI는 이를 단순화해서 항상 12바이트 크기의 패킷만을 사용하게 된다. 실제 패킷이 어떤 형태로 이루어져 있는지는 명령에 따라 다르지만 어느 정도 전형적인 형태를 취하기 마련이다. ATAPI에서 사용되는 패킷의 기본 구성을 <그림 2>에 나타내었다.


ATAPI의 기본 패킷 구성'>





레지스터 재정의
ATAPI 프로토콜을 위해 기존의 ATA I/O 레지스터의 내용 일부가 새로 정의되거나 추가된다. 예를 들어 실린더 하이, 로우 레지스터는 바이트 카운트 하이, 로우(Byte Count High, Low) 레지스터로 이름이 바뀌며, ATA에서 실린더 번호를 지정했던 것과는 달리 1DRQ 블럭의 크기(지난 호의 READ SECTORS 프로토콜 참조)를 지정하게 된다.

호스트에서 패킷 명령으로 데이터를 요청할 때, 받고자 하는 데이터의 크기를 이 레지스터에 지정하게 된다. 이 때 드라이브에서는 다시 1DRQ 블럭으로 보낼 수 있는 값을 이 레지스터에 지정하게 되고, 호스트에서는 여기에 설정된 크기만큼 데이터를 가져가게 된다. 이 레지스터의 역할은 PIO 모드에서만 사용된다. 이외에도 스테이터스, 섹터 카운터 레지스터 등 몇몇 레지스터의 역할이 바뀌고, 이름도 재정의돼 사용되는데 자세한 내용은 ATA/ATAPI-5 표준의 Packet 명령을 참조하기 바란다.

주요 명령 구성
<표 1>에 SPC와 MMC에 정의된 주요 명령들을 정리하였다. SPC는 디바이스의 타입에 관계없는 기본적인 명령을 정의한 표준이고 MMC는 CD, DVD, RW 등의 디바이스를 위한 전용 명령들이 정의돼 있다. <표 1>에서는 지면 관계상 DVD와 RW에 관련된 명령은 생략하고 CD 명령을 중심으로 정리하였다. 각 명령의 패킷 구성과 설명은 분량이 방대한 관계로 모두 설명하기 어려우므로 직접 표준을 참조하기 바란다. 여기에서는 몇 가지 중요 패킷들을 중심으로 설명해 나가기로 하겠다.

주요 명령의 동작 확인
이제 실제적으로 atapi.c 프로그램을 통해 ATAPI 패킷 명령으로 CD-ROM 드라이브를 제어해 보면서 중요한 몇몇 명령의 내용을 확인해 보기로 하겠다(진행 중 명령에 대한 상세한 내용은 ATAPI 표준을 직접 확인하면서 진행하는 것이 좋다). 우선 전원을 연결하지 않은 상태에서 보드에 드라이브를 연결한다. 이 때 드라이브 안의 CD는 제거시켜 둔다. 전원을 넣은 후 Mterm을 이용해 atapi.hex를 다운로드한 후 실행시킨다. 먼저 rd_regs로 레지스터의 내용을 확인해 본다.

$ run

ATAPI test
$ rd_regs
error reg = 1
sector count reg = 1
sector number reg = 1
cylinder low reg = 14
cylinder high reg = eb
device/head reg = 0
status reg = 0
alt status reg = 0

실린더 로우, 하이(바이트 카운트 로우, 하이) 레지스터에 14ebh 값이 나타나 있는 것을 볼 수 있다. 이 값은 ATAPI Signature라고 하는 것으로 드라이브가 리셋되었을 때 항상 해당 레지스터에 저장된다. 즉 이 값을 통해 드라이브는 자신이 ATAPI 디바이스임을 나타내게 된다. 보드에 처음 전원이 들어오면 power on reset 상태가 되므로 드라이브의 레지스터는 이와 같이 설정된다.

다음으로 가장 기본적인 명령이라고 할 수 있는 test unit ready 명령을 내려본다(현재 드라이브의 준비 상태를 알아보는 명령이다). 차례로 두 번에 걸쳐 명령을 실행해 보자.


 <1> ATAPI의 주요 커맨드 구성
 command  code  standard description 
 Inquiry  12h  SPC 디바이스에 대한 정보를 요청함
 Load/Unload Medium  A6h  MMC 디스크를 로드 또는 언로드함
 Mechanism Status  BDh  MMC 디바이스의 현재 동작상태 정보를 요청함
 Mode Select(10)  55h  SPC 디바이스의 특정 동작모드를 설정함
 Mode Sense(10)  5Ah  SPC 디바이스의 특정 동작모드 정보를 요청함
 Pause/Resume  4Bh  MMC 오디오 디스크 플레이 중에 일시정지 기능을 제어함
 Play Audio(10)  45h  MMC 오디오 디스크를 특정 위치부터 재생함
 Prevent/Allow Medium Removal  1Eh  SPC 디바이스 미디어의 제거를 허가여부를 설정함 
 Read(12)  A8h  MMC CD, DVD의 주어진 LBA 섹터의 내용을 주어진 크기만큼 읽어들임 
 Read Capacity  25h  MMC CD, DVD 디스크의 사이즈를 요청함
 Read CD  BEh  MMC CD-ROM 디스크 데이터의 다양한 섹터 형태별로 읽어들임 
 Read CD MSF  B9h  MMC Read CD와 같으나 LBA대신 MSF를 사용
 Read Sub-channel  42h  MMC CD 디스크의 서브채널 데이터를 읽어들임
 Read TOC/PMA/ATIP  43h  MMC CD 디스크의 TOC, PMA, ATIP 데이터를 읽어들임
 Request Sense  03h  SPC 예외 상황 등이 발생하였을 때 그에 대한 자세한 정보를 요청함
 Scan  BAh  MMC 오디오 디스크의 내용을 트랙별로 스캔함
 Stop Play/Scan  4Eh  MMC 오디오 디스크의 플레이 또는 스캔 동작을 중지함
 Test Unit Ready  00h  SPC 디바이스가 사용 준비가 되어있는지 확인한다.





$ tur
Stat=51
Sense key=6
UNIT ATTENTION

$ tur
Stat=51
Sense key=2
NOT READY

맨 처음 내린 test unit ready 명령에는 스테이터스 레지스터 값이 51h로 CHK(ATA의 ERR비트)가 셋팅되어 CHECK CONDITION 상태를 나타낸다. 이는 에러가 발생했음을 나타내는 것으로, 이러한 경우에는 에러 레지스터의 상위 4비트에 센스 키 값이 나타나게 된다(<그림 3>의 에러 레지스터 구성 참조). 각 센스 키는 에러 상황을 설명하는데 여기서 나타나는 UNIT ATTENTION은 디바이스가 리셋되었거나 디스크가 교환되었을 때 이 상황을 외부에 알리기 위한 수단으로 사용된다. 정상적인 명령 처리 대신 에러를 내고 해당 센스 키 값을 1회 표시한다.

두 번째 test unit ready 명령에 대해서는 이제 UNIT ATTENTION 상황을 해제하고 실제적인 에러 상황을 표시하는 값을 나타낸다. 현재 드라이브 내에 디스크가 없으므로 드라이브가 준비되지 않았음을 보여준다. 센스 데이터에 대한 자세한 사항은 SPC 규격 4장의 Sense Data 부분을 참조하면 된다. 패킷 명령 중의 하나인 request sense는 에러에 대한 더 구체적인 정보를 리턴하는 명령으로, 호스트에서는 대부분의 예기치 못한 상황에 대해 이 명령으로 에러 정보를 읽어 후속 조치를 취하게 된다.

이번에는 패킷 디바이스 전용 명령 중에 IDENTIFY PACKET DEVICE를 사용해보기로 하겠다. 앞서 설명한대로 이 명령은 패킷 명령이 아닌 ATA 프로토콜을 사용한다. ATAPI 디바이스는 ATA의 IDENTIFY DEVICE 명령에는 응답을 하지 않고 IDENTIFY PACKET DEVICE 명령에 응답하게 된다. 통상 부팅 후 포트에 연결된 디바이스가 ATA 디바이스인지 ATAPI 디바이스인지 확인하는 과정에서 주로 사용된다. IDENTIFY DEVICE처럼 512바이트의 정보를 리턴하는데, 여기서는 idp라는 명령으로 텍스트 위주의 정보만을 표시하도록 프로그램을 작성해 보았다.

$ idp
Config :c0
Serial number :
Firmware revision :BS12
Model number :SAMSUNG DVD-ROM SD-608

IDENTIFY PACKET DEVICE와 유사한 패킷 명령으로 inquiry라는 명령이 있다. 두 명령은 비슷하지만 IDENTIFY PACKET DEVICE는 주로 부팅 후 드라이브 인식 과정에서 사용되고, 일단 인식한 후에는 inquiry를 주로 사용하게 된다.

$ inq
Device type :05
Vendor Id :LG (KOR)
Product Id :CD-ROM CRD-8483B
Product Revision :1.02

이제 드라이브에 CD-ROM 디스크를 넣고 디스크 인식 중에 계속해서 test unit ready 명령을 내려본다. 차례로 NOT READY 상태에서 UNIT ATTENTION(새 디스크가 로딩됨)을 거쳐 스테이터스 레지스터 값이 50h로 정상적인 명령 대기 상태로 돌아옴을 알 수 있을 것이다.

$ tur
Stat=51
Sense key=2
NOT READY

$ tur
Stat=51
Sense key=6
UNIT ATTENTION

$ tur
Stat=50
이제 가장 핵심이라고 할 수 있는 read(12) 명령을 내려보기로 한다. read 명령에는 read(10)과 read(12)가 있다. 두 명령의 차이는 명령의 형태가 10바이트 패킷용인지 12바이트 패킷용인지의 차이인데, 항상 12바이트를 사용하는 ATAPI에서는 근본적인 차이는 없으며 다만 패킷 내에 전송할 섹터의 크기를 지정하는 부분이 2바이트와 4바이트로 다를 뿐이다.

이외에도 read CD라는 명령이 있는데, 이 명령은 CD-ROM의 다양한 형태의 데이터를 읽어낼 수 있는 만능형 명령이다. 실제 CD-ROM에 저장할 수 있는 데이터의 형태는 Mode 1, Mode 2, Mode 2 Form1, Mode 2 Form2, Audio 등 여러 가지 형태가 정의돼 있다. 이중 우리가 일반적으로 사용하는 CD-ROM의 데이터는 에러 정정이 가능한 2048바이트의 Mode 1 데이터로서, 이러한 데이터를 읽어낼 때는 주로 read(10), read(12) 명령이 사용된다. 비디오 CD 등에 사용되는 Mode 2 Form2 데이터는 read 명령으로는 읽을 수 없으며 read CD 명령을 사용해야 한다. 마찬가지로 오디오 CD 등의 데이터도 read 대신 read CD 명령을 사용한다. 즉 오디오 CD에서 WAV 파일을 만들 때 쓰는 명령이 바로 read CD인 것이다. read CD와 데이터 포맷에 대한 내용은 MMC의 read CD 명령을 참조하기 바란다.

다음은 임의의 섹터에 대해 read(12) 명령을 내려 한 개의 섹터를 읽은 후 그 결과를 덤프한 내용이다. 여기서 사용한 rd12는 하드웨어의 한계상 최대로 읽을 수 있는 섹터의 수가 2개(4096바이트)로 제한되어 있다.

$ rd12 600 1
Read12 done

$ dump_buf

E000: 78 31 31 2C 30 78 31 31 2C 30 78 31 31 2C 30 78 x11,0x11 ,0x11,0x
E010: 31 31 2C 0D 0A 30 78 31 31 2C 30 78 31 31 2C 30 11,..0x1 1,0x11,0
E020: 78 31 31 2C 30 78 31 31 2C 30 78 31 31 2C 30 78 x11,0x11 ,0x11,0x
E030: 31 31 2C 30 78 31 31 2C 30 78 31 31 2C 30 78 31 11,0x11, 0x11,0x1
E040: 31 2C 30 78 31 31 2C 30 78 31 31 2C 30 78 31 31 1,0x11,0 x11,0x11
E050: 2C 30 78 31 31 2C 30 78 31 31 2C 30 78 31 31 2C ,0x11,0x 11,0x11,
E060: 30 78 31 31 2C 0D 0A 30 78 31 31 2C 30 78 31 31 0x11,..0 x11,0x11
E070: 2C 30 78 31 31 2C 30 78 31 31 2C 30 78 31 31 2C ,0x11,0x 11,0x11,
E080: 30 78 31 31 2C 30 78 31 31 2C 30 78 31 31 2C 30 0x11,0x1 1,0x11,0
E090: 78 31 31 2C 30 78 31 31 2C 30 78 39 39 2C 30 78 x11,0x11 ,0x99,0x
E0A0: 39 39 2C 30 78 31 31 2C 30 78 31 31 2C 30 78 39 99,0x11, 0x11,0x9
E0B0: 39 2C 30 78 39 39 2C 0D 0A 30 78 31 31 2C 30 78 9,0x99,. .0x11,0x
E0C0: 31 31 2C 30 78 31 31 2C 30 78 31 31 2C 30 78 31 11,0x11, 0x11,0x1
E0D0: 31 2C 30 78 31 31 2C 30 78 31 31 2C 30 78 31 31 1,0x11,0 x11,0x11
E0E0: 2C 30 78 31 31 2C 30 78 31 31 2C 30 78 31 31 2C ,0x11,0x 11,0x11,
E0F0: 30 78 31 31 2C 30 78 31 31 2C 30 78 31 31 2C 30 0x11,0x1 1,0x11,0
E100: 78 31 31 2C 30 78 31 31 2C 0D 0A 30 78 31 31 2C x11,0x11 ,..0x11,
E110: 30 78 31 31 2C 30 78 31 31 2C 30 78 31 31 2C 30 0x11,0x1 1,0x11,0
E120: 78 31 31 2C 30 78 31 31 2C 30 78 31 31 2C 30 78 x11,0x11 ,0x11,0x
E130: 31 31 2C 30 78 31 31 2C 30 78 31 31 2C 30 78 31 11,0x11, 0x11,0x1
E140: 31 2C 30 78 31 31 2C 30 78 31 31 2C 30 78 31 31 1,0x11,0 x11,0x11
E150: 2C 30 78 31 31 2C 30 78 31 31 2C 0D 0A 30 78 31 ,0x11,0x 11,..0x1
E160: 31 2C 30 78 31 31 2C 30 78 31 31 2C 30 78 31 31 1,0x11,0 x11,0x11
E170: 2C 30 78 31 31 2C 30 78 31 31 2C 30 78 31 31 2C ,0x11,0x 11,0x11,
E180: 30 78 31 31 2C 30 78 31 31 2C 30 78 31 31 2C 30 0x11,0x1 1,0x11,0
E190: 78 31 31 2C 30 78 31 31 2C 30 78 31 31 2C 30 78 x11,0x11 ,0x11,0x
E1A0: 31 31 2C 30 78 31 31 2C 30 78 31 31 2C 0D 0A 30 11,0x11, 0x11,..0
E1B0: 78 31 31 2C 30 78 31 31 2C 30 78 31 31 2C 30 78 x11,0x11 ,0x11,0x
E1C0: 31 31 2C 30 78 31 31 2C 30 78 31 31 2C 30 78 39 11,0x11, 0x11,0x9
E1D0: 39 2C 30 78 39 39 2C 30 78 31 31 2C 30 78 31 31 9,0x99,0 x11,0x11
E1E0: 2C 30 78 39 39 2C 30 78 39 39 2C 30 78 31 31 2C ,0x99,0x 99,0x11,
E1F0: 30 78 31 31 2C 30 78 31 31 2C 30 78 31 31 2C 0D 0x11,0x1 1,0x11,.

이제 read 명령까지 확인했으니 연재의 마지막인 다음 호에서는 CD-ROM의 규격인 ISO9660에 대해 알아보고 계속해서 MMC의 오디오 CD용 명령을 사용해서 CD-ROM을 이용한 간단한 CD 플레이어를 구현해 보기로 하겠다.

정리 | 전만환 | mhjun@korea.cnet.com


   + + +
① Information Technology - AT Attachment with Packet Interface (ATA/ATAPI-5)
② SPC-3 SCSI Primary Commands
③ MMC-3 SCSI Multimedia Commands
www.t10.org - SCSI 규격을 관장하는 위원회, SPC, MMC 등의 규격의 draft를 구할 수 있다.
⑤ user.chollian.net/~lase - 필자의 홈페이지, personal project에서 80196 HDD 프로젝트 참조