FAT 파일 시스템을 구현한다.
더 자세히 ...
|
| #define | SIZEOF_DENTRY 32 |
| |
| #define | ROOT_DIR_SECTORS(b) |
| |
| #define | FAT_SIZE(b) |
| |
| #define | FIRST_DATA_SECTOR(b) |
| |
| #define | FIRST_SECTOR_OF_CLUSTER(b, N) (((N) - 2) * ((b)->sector_per_cluster) + FIRST_DATA_SECTOR(b)) |
| |
| #define | TOTAL_SECTOR(b) |
| |
| #define | NR_DATA_SECTOR(b) |
| |
| #define | COUNT_OF_CLUSTER(b) (NR_DATA_SECTOR(b) / (b)->sector_per_cluster) |
| |
| #define | IS_FAT12(b) (COUNT_OF_CLUSTER(b) < 4085) |
| |
| #define | IS_FAT16(b) (!IS_FAT12(b) && (COUNT_OF_CLUSTER(b) < 65525)) |
| |
| #define | IS_FAT32(b) (COUNT_OF_CLUSTER(b) >= 65525) |
| |
| #define | COUNT_OF_VALID_MAX_CLUSTER(b) (COUNT_OF_CLUSTER(b) + 1) |
| |
| #define | FAT_OFFSET(b, N) |
| |
| #define | FAT_SECTOR_NUMBER(b, N) (FAT_OFFSET((b), (N)) / (b)->sector_size) |
| |
| #define | FAT_SECTORS(b, N) ((b)->reserved_sector + (N) * FAT_SIZE(b)) |
| |
| #define | THIS_FAT_OFFSET(b, N) (FAT_SECTORS((b), (N)) * (b)->sector_size) |
| |
| #define | FAT_ENTRY_OFFSET(b, N) (FAT_OFFSET(b, (N)) % (b)->sector_size) |
| |
| #define | WORD(buf) ((unsigned short*)(buf)) |
| |
| #define | DWORD(buf) ((unsigned int*)(buf)) |
| |
| #define | FAT_EOF(bpb) |
| |
| #define | SET_CLUSTER_ENTRY_VALUE(b, B, N, val) |
| |
| #define | IS_EOF(b, v) |
| |
| #define | BAD_CLUSTER12 0x0FF7 |
| |
| #define | BAD_CLUSTER16 0xFFF7 |
| |
| #define | BAD_CLUSTER32 0x0FFFFFF7 |
| |
| #define | UPDATE_FAT_SIZE(b, dsksz) |
| |
| #define | FIRST_ROOTDIR_SECTOR(b) ((b)->reserved_sector + (b)->nr_fat * FAT_SIZE(b)) |
| |
| #define | GET_FIRST_BLOCK(d) ((d)->first_cluster_hi << 16 | (d)->first_cluster_lo) |
| |
| #define | IS_FREE_DENTRY(d) |
| |
| #define | IS_KANJI_NAME(d) ((d)->name[0] == 0x05) |
| |
| #define | IS_VALID_CHAR_FOR_NAME(ch) (!(((ch) != 0x05 && (ch) < 0x20) && (ch) != 0x22 && ((ch) >= 0x2A && (ch) <= 0x2F) && ((ch) >= 0x3A && (ch) <= 0x3F) && (ch) != 0x5B && (ch) != 0x5C && (ch) != 0x5D && (ch) != 0x7C)) |
| |
| #define | DAY_OF_MONTH(e) ((e)->write_date & 0x000Fu) |
| |
| #define | MONTH_OF_YEAR(e) (((e)->write_date >> 4) & 0x000Fu) |
| |
| #define | COUNT_OF_YEAR(e) (1980 + (((e)->write_date >> 8) & 0x00FFu)) |
| |
| #define | SECOND_COUNT(e) ((e)->write_time & 0x000Fu) |
| |
| #define | MINUTES(e) (((e)->write_time >> 4) & 0x003F) |
| |
| #define | HOURS(e) (((e)->write_time >> 10) & 0x001F) |
| |
| #define | LAST_LONG_ENTRY 0x40 |
| |
| #define | PATH_MAX 256 |
| |
| #define | IS_LAST_SLOT(e) (((e)->ord & LAST_LONG_ENTRY) == 0) |
| |
|
| enum | dir_Attributes {
ATTR_NONE = 0x00,
ATTR_RDONLY = 0x01,
ATTR_HIDDEN = 0x02,
ATTR_SYS = 0x04,
ATTR_VOLID = 0x08,
ATTR_DIR = 0x10,
ATTR_ARCH = 0x20,
ATTR_EXT = ATTR_RDONLY | ATTR_HIDDEN | ATTR_SYS | ATTR_VOLID,
ATTR_UNUSED = ATTR_ARCH | ATTR_VOLID | ATTR_SYS | ATTR_HIDDEN,
ATTR_LONG_NAME_MASK = ATTR_RDONLY | ATTR_HIDDEN | ATTR_SYS | ATTR_VOLID | ATTR_DIR | ATTR_ARCH,
ATTR_NO_FILE = ATTR_SYS | ATTR_VOLID | ATTR_DIR,
ACTIVE_FLAG = 0x80,
EXTENDED = ATTR_RDONLY | ATTR_SYS,
DELETED_FLAG = 0xE5
} |
| |
FAT 파일 시스템을 구현한다.
2.2 간단한 파일 시스템 탐색 : FAT
운영체제 개발에 있어서, 부트로더가 커널 이미지를 적재하기 위해서는 파일 시스템이 필요할 수 있다. 물론, lilo 와 같이 커널 이미지의 시작 위치를 등록시키는 방법도 있다. 하지만 이번에 구현하게 될 운영체제는 FAT12 파일 시스템의 루트(/) 디렉토리에 저장된 이미지 파일을 로딩할 것이다. 지정된 이름의 커널 이미지를 찾기 위해서는 FAT 파일 시스템에 대한 이해가 필요하다. 이 부분은 “Hardware white paper / FAT: General overview of On-Disk format” 문서를 통해 보다 자세한 정보를 얻을 수 있다.
본론에 들어가기 전에, 사용될 용어에 대해 정의(또는 설명)한다.
용어 정리
| Cluster 파일을 저장하는 논리적인 단위 | FAT 의 Data region 에서 2 번으로 시작한다. FAT 12에서 하나의 Cluster 는 대부분 하나의 Sector 크기와 같다. |
이 장에서는 Real Mode 에서 FAT12 를 탐색할 것이므로, 필수적으로 BIOS Service 들을 사용해야 한다.
Disk I/O
2.2.1 Overview
FAT12 는 가장 단순하면서 오래 전부터 사용되어 온 파일 시스템이다. 이 파일 시스템은 DOS 시절부터 사용되던 것으로 많은 문서가 있으며, 관련 코드도 많다. 그러므로 다른 파일 시스템에 대한 설명보다 다루기 쉬운 FAT12 에 대한 설명을 하기로 한다. 운영체제 개발에 있어서 가장 부딪히기 쉬운 문제가 커널을 저장하고, 찾는 방법이다. 이 문제를 해결하기 위해 파일 시스템을 채용해야 하며, 파일 시스템 없이 구현한다면 향 후 확장성에 큰 문제가 생길지도 모른다.
부트로더에서 파일 시스템을 검색한 후 커널 이미지를 찾고, 해당 이미지를 메모리로 적재, 운영체제를 본격적으로 실행한다.
FAT(File Allocation Table) 파일 시스템은 1970 년대 후반부터, 1980 년대 초반까지 MS 에서 지원해주던 파일 시스템이다. 이것은 초창기 500 KB 보다 작은 플로피 디스크를 위해 개발된 단순 파일 시스템이다. 이 후 발전하면서 보다 큰 용량의 디스크를 위한 파일 시스템이 필요했고, FAT 도 함께 발전했다. 현재는 FAT12, FAT16 그리고 FAT32 인 세 종류의 FAT 파일 시스템이 있다. 이 세 종류의 파일 시스템이 가진 기본적인 차이는 이름을 통해서도 쉽게 알 수 있듯이 Entry 의 크기이다. FAT12 에는 12 bits 의 엔트리가 있고 나머지에는 각각 16 bits, 32 bits 엔트리가 존재한다. FAT 파일 시스템은 디스크상에서 “little-endian” 방식으로 저장된다. 이는 초창기 개발되어 적용된 플랫폼이 IBM PC 이기 때문이다.
FAT 파일 시스템은 다음과 같은 구조를 가진다.
| 영역 순서 | 영역 이름 |
| 0 | Reserved Region |
| 1 | FAT Region |
| 2 | Root directory region (Doesn’t exist on FAT32 volumes) |
| 3 | File and directory data region |
2.2.2 Boot sector and BPB (BIOS Parameter block)
아래의 BPB 는 Reserved Region(Boot Sector) 에 기록되어야 하는 정보로, 가장 처음에 온다.
; BPB (Boot parameter block ; For FAT12)
db 0xeb, 0x3c ; 0x00 - JMP 0x40 (offset +
db 0x90 ; 0x02 - "NOP Instruction"
db 'mkdosfs ' ; 0x03 - B bytes, OEM Name
db 1 ; 0x0D - Sector per cluster
sec dw RESERVED_SECTOR ; 0x0E - Reserved sector count (boot sector)
db 2 ; 0x10 - Number of FATs
root_entry_count dw 0xe0 ; 0x11 - 224
dw 2880 ; 0x13 - Total sector 16 bits
db 0xF0 ; 0x15 - Media
type (F0: Removable)
dw 9 ; 0x16 - FAT size = 9 sectors (16)
header_no dw 2 ; 0x1A - Number of heads
dd 0 ; 0x1C - Hidden sectors
dd 0 ; 0x20 - Total sector (32)
bootdrive db 0 ; 0x24 - Driver number (floppy)
db 0 ; 0x25 - Reserved
db 0x29 ; 0x26 - Boot signature
dd 0x20050718 ; 0x27 - Volume ID
이 섹터가 Memory 로 적재된 이 후 명령을 수행하면, JMP Instruction 을 만나, BPB 다음부터 코드를 실행하게 된다. BPB 에는 File system 및 Disk 에 대한 정보가 기록된다. BPB 에 기록된 값이 잘못된 경우에는 리눅스에서 해당 이미지 파일을 마운트 할 수 없을 수도 있다. 리눅스에서 마운트해서 사용하고자 하는 경우에는 파일 시스템 종류를 FAT12 로 지정해 주어야 한다. 기본적으로 FAT 파일 시스템은 0번 섹터를 부트섹터(예약영역)으로 지정하고, 두번째 섹터를 포함하여 9개의 섹터를 FAT1 으로 보고 뒤이어 오는 9개의 섹터를 FAT2 로 본다. 이러한 정보는 BPB 에 기록되어 있다. 가장 쉽게 OS 를 구현하기 위해서는 “부트섹터 -> FAT1 (섹터9개) -> FAT2(섹터9개) -> Root directory entry” 의 구조로 진행하면 된다. 그러므로, Root directory 에 있는 entry 들을 검색하기 위해서는 19번재 섹터를 읽으면 된다.
2.2.3 Root directory
FAT12 파일 시스템의 Root directory 는 Boot sector FAT1 Sectors(9) FAT2 Sectors(9) 다음에 위치한다. 옛날 초창기 FAT12 파일 시스템에서는 8.3 파일 형식을 지원하고 있었다. 그에 따라 Root entry 등에서 표현되는 하나의 Entry 는 다음과 같은 구조를 갖게 되었다.
| Offset | Size | Field | Description |
| 0 | 8 | Filename | File name |
| 8 | 3 | Extension | File extension |
| 11 | 1 | Attribute | |
| 12 | 1 | lcase | Case for base and extension – NT reserved (FAT32) |
| 13 | 1 | ctime_ms | Creation time, milliseconds |
| 14 | 2 | ctime | Creation time |
| 16 | 2 | cdate | Creation date |
| 18 | 2 | adate | Last access date |
| 20 | 2 | reserved | reserved values (ignored) – FirstClushHi(FAT32) |
| 22 | 2 | time | time stamp |
| 24 | 2 | date | date stamp |
| 26 | 2 | start | starting cluster number – FirstClusterLO(FAT32) |
| 28 | 4 | size | size of the file |
표 FAT12, Root directory entry structure for short name
하지만 이 후에 소개된 Windows 시스템에서는 긴 파일 이름을 지원하게 되었다. 긴 파일 이름을 지원하기 위해 Directory entry 를 약간 변경하게 되었다. 물론, 기존 구조와의 호환성을 위해 긴 파일 이름을 위한 속성을 별도로 지정했고, 해당 속성은 Short name 만 지원하는 시스템에서는 보이지 않는 값이다. 하지만 이러한 구조는 특정 시스템 도구에게는 잘못된 것으로 보였으며, 오래된 버전의 시스템 도구는 긴 파일 이름을 위한 엔트리를 삭제하거나 디스크에 문제가 있다고 경고를 냈다. 하지만 이것은 옛날 시스템 도구로 한정된다. 긴 파일 이름을 지원하기 위해 고안된 새로운 구조체는 다음과 같다.
| Offset | Size | Field | Description |
| 0 | 1 | id | Sequence number for a slot |
| 1 | 10 | name0_4 | first 5 charcters for filename |
| 11 | 1 | attr | attribute byte |
| 12 | 1 | reserved | always 0 |
| 13 | 1 | alias_checksum | checksum for 8.3 alias |
| 14 | 12 | name5_10 | 6 more characters for filename |
| 26 | 2 | start | starting cluster number, always 0 |
| 28 | 4 | name11_12 | last 2 characters for filename |
표 slot structure
긴 파일 이름을 지원하기 위해 고안된 구조체는 slot 이라고도 부른다. 위의 표를 보면 알 수 있겠지만, 몇 개의 필드의 위치와 크기는 기존 구조체의 것과 동일하다. 즉, 이 값들을 이용해 긴 파일이름인지, 짧은 파일 이름인지를 구분하게 되는 것이다. 또한, attribute 필드를 이용해 짧은 파일 이름만 지원하는 시스템에서는 긴 파일 이름이 보이지 않도록 해주는 것이다. 그렇다면, 반대로는 어떻게 되는 것일까? 긴 파일 이름을 지원하는 시스템에서 작성한 파일의 이름은 어떻게 짧은 이름만 지원하는 시스템에서 보일 수 있는 것일까? 그것은 긴 파일 이름을 저장하기 위한 슬롯 바로 다음에 짧은 파일 이름을 위한 엔트리를 하나 더 할당하는 것이다. 이에 따라 하나의 파일 이름을 저장하기 위해서는 적어도 두개 이상의 엔트리가 필요하게 되었다. 또 다른 문제가 있는데, 긴 파일 이름은 하나의 엔트리를 써도 12 글자 밖에 표현할 수 없다. 이는 Windows 시스템에서 지원하는 길이보다 짧다. 이를 해결하기 위해 위에서 설명한 슬롯은 하나 이상이 연결 되기도 한다. 또한 기존 OEM 표현 방식과 달리 Unicode 표현 방식을 지원하고 있다. 결과적으로 12 바이트의 글자를 표현하기 위해 24 바이트가 할당되게 된 것이다.
그렇다면, 어떻게 슬롯들 끼리 연결하여 사용할까?
여러 개의 슬롯을 필요로 하는 파일 이름의 경우, 가장 처음 나타나는 슬롯은 12 글자씩 자른 후 남는 마지막 부분이다. 즉 거꾸로 디스크에 기록되는 것이다. 또한, 이 슬롯의 ID 중 7 번째 비트가 1 로 표시된다. 그 외 나머지 비트들을 이용해 현재 슬로의 index 를 표시한다. 위와 같은 특성을 정리하여 하나의 긴 파일 이름을 찾아 냈다면, 바로 다음에 나오는 짧은 파일 이름을 위한 엔트리를 건너 뛰거나 사용하면 된다. 긴 파일 이름을 표현하는 슬롯이 가지는 속성은 읽기전용, 숨김, 시스템, 볼륨의 각 속성들을 OR 연산한 것이 된다. 물론 파일 이름을 표시할 때, 삭제된 파일 인지(파일 이름의 첫 글자가 0xe5 인 경우), 사용되지 않는 엔트리(속성값이 볼륨, 아카이브, 시스템, 숨김 속성의 OR 연산 값)인지를 판별해 주어야 한다. 이렇게 해서 FAT 파일 시스템의 루트 디렉토리를 Browsing 하는 방법에 대해 알아 보았다. 물론 이 방법으로 다른 디렉토리들을 Browsing 하게 된다.
2.2.4 커널 이미지의 적재
커널 이미지를 적재하기 위해서는 파일 이름으로 찾은 Entry 에서 Start cluster 값을 읽은 후, FAT 에서 Chain 을 따라가며 Data 를 읽어 들여야 한다. Cluster 는 FAT 구성 중 Data region 의 처음부터 2번 Cluster 로 시작한다.
클러스터 번호를 이용하여 다음 클러스터를 찾기 위한 값을 계산하는데, 일단 FAT 에서의 Offset 값을 구하고 해당 Offset 에서 2 Bytes 값을 읽어 다음 Cluster 값을 읽어 온다. 해당 Cluster 에서 값을 읽어 메모리로 적재하고 다음 클러스터를 찾아 가는 식으로 FAT 엔트리에 있는 값이 0x0FF7 보다 같거나 크면 파일을 구성하는 FAT 엔트리의 끝으로 인식한다.
“해당 Cluster 의 값” 이 짝수인 경우에는
-
FAT Entry 상에서의 Offset 구하기 : 현 Cluster 값 / 2 * 3
Entry 에서 읽은 값을 0x0FFF 와 AND 연산
“해당 Cluster 의 값” 이 홀수인 경우에는
-
FAT Entry 상에서의 Offset 구하기 : 현 Cluster 값 / 2 * 3 + 1
Entry 에서 읽은 값을 오른쪽으로 4 bits 쉬프트 (SHR 4)
클러스터를 구하기 위해 SHR 또는 AND 연산을 하는 이유는 FAT12 는 FAT 의 각 Entry 가 12 bits 로 구성되기 때문이다.
- 날짜
- 2011-8-22
- 작성자
- Sung-jae Park nices.nosp@m.j@ni.nosp@m.cesj..nosp@m.com
| #define BAD_CLUSTER12 0x0FF7 |
| #define BAD_CLUSTER16 0xFFF7 |
| #define BAD_CLUSTER32 0x0FFFFFF7 |
- 주의
- If you want add reserved cluster too, then plus one below computation
| #define COUNT_OF_YEAR |
( |
|
e) | |
(1980 + (((e)->write_date >> 8) & 0x00FFu)) |
| #define DAY_OF_MONTH |
( |
|
e) | |
((e)->write_date & 0x000Fu) |
| #define DWORD |
( |
|
buf) | |
((unsigned int*)(buf)) |
값:
IS_FAT16(
bpb) ? 0xFFF8 : \
IS_FAT32(
bpb) ? 0x0FFFFFF8 : 0)
| #define FAT_OFFSET |
( |
|
b, |
|
|
|
N |
|
) |
| |
값:
(b)->size_of_fat16 : \
(b)->
type.fat32.size_of_fat32)
| #define FIRST_DATA_SECTOR |
( |
|
b) | |
|
| #define HOURS |
( |
|
e) | |
(((e)->write_time >> 10) & 0x001F) |
| #define IS_FREE_DENTRY |
( |
|
d) | |
|
값:((
unsigned char)((d)->name[0] == (
unsigned char)
DELETED_FLAG) \
|| ((d)->
name[0] == 0x00))
| #define IS_KANJI_NAME |
( |
|
d) | |
((d)->name[0] == 0x05) |
| #define IS_VALID_CHAR_FOR_NAME |
( |
|
ch) | |
(!(((ch) != 0x05 && (ch) < 0x20) && (ch) != 0x22 && ((ch) >= 0x2A && (ch) <= 0x2F) && ((ch) >= 0x3A && (ch) <= 0x3F) && (ch) != 0x5B && (ch) != 0x5C && (ch) != 0x5D && (ch) != 0x7C)) |
| #define LAST_LONG_ENTRY 0x40 |
| #define MINUTES |
( |
|
e) | |
(((e)->write_time >> 4) & 0x003F) |
| #define MONTH_OF_YEAR |
( |
|
e) | |
(((e)->write_date >> 4) & 0x000Fu) |
| #define NR_DATA_SECTOR |
( |
|
b) | |
|
| #define ROOT_DIR_SECTORS |
( |
|
b) | |
|
값:- 주의
- This computation rounds up
| #define SECOND_COUNT |
( |
|
e) | |
((e)->write_time & 0x000Fu) |
| #define SET_CLUSTER_ENTRY_VALUE |
( |
|
b, |
|
|
|
B, |
|
|
|
N, |
|
|
|
val |
|
) |
| |
값:do { \
(val) = (val) & 0x0FFFFFFF; \
} else { \
if ((N) & 0x0001) { \
(val) = (val) << 4; \
} else { \
(val) = (val) & 0x0FFF; \
} \
} \
} while (0)
| #define TOTAL_SECTOR |
( |
|
b) | |
|
| #define UPDATE_FAT_SIZE |
( |
|
b, |
|
|
|
dsksz |
|
) |
| |
값:do { \
unsigned int tmp[2]; \
unsigned int fatsz; \
tmp[1] >>= 1; \
} \
fatsz = (tmp[0] + (tmp[1] - 1)) / tmp[1]; \
(b)->
type.fat32.size_of_fat32 = fatsz; \
} else { \
} \
} while (0)
| #define WORD |
( |
|
buf) | |
((unsigned short*)(buf)) |
| 열거형 멤버 |
|---|
| ATTR_NONE |
|
| ATTR_RDONLY |
|
| ATTR_HIDDEN |
|
| ATTR_SYS |
|
| ATTR_VOLID |
|
| ATTR_DIR |
|
| ATTR_ARCH |
|
| ATTR_EXT |
|
| ATTR_UNUSED |
|
| ATTR_LONG_NAME_MASK |
|
| ATTR_NO_FILE |
|
| ACTIVE_FLAG |
|
| EXTENDED |
|
| DELETED_FLAG |
|