ncloader  0.1
 모두 데이타 구조 파일들 함수 변수 타입정의 열거형 타입 열거형 멤버 매크로 그룹들 페이지들

FAT 파일 시스템을 구현한다. 더 자세히 ...

+ FAT12에 대한 협력 다이어그램:

데이타 구조

struct  bpb
 
struct  DSKSZTOSECPERCLUS
 
struct  fat32_fsinfo
 
struct  fat_dentry
 
struct  fat_long_dirent
 

매크로

#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
}
 

함수

struct fat32_fsinfo __PACKED ((packed))
 

변수

struct bpb __PACKED
 

상세한 설명

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 파일 시스템은 다음과 같은 구조를 가진다.

영역 순서영역 이름
0Reserved Region
1FAT Region
2Root directory region (Doesn’t exist on FAT32 volumes)
3File 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
sector_size dw 512 ; 0x0B - Size of sector
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)
sector_per_track dw 18 ; 0x18 -
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
volume_label db 'NCOS ' ; 0x2B - Volume label
fs_type db 'FAT12 ' ; 0x36 - File system type

이 섹터가 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 는 다음과 같은 구조를 갖게 되었다.

OffsetSizeFieldDescription
08FilenameFile name
83ExtensionFile extension
111Attribute
121lcaseCase for base and extension – NT reserved (FAT32)
131ctime_msCreation time, milliseconds
142ctimeCreation time
162cdateCreation date
182adateLast access date
202reservedreserved values (ignored) – FirstClushHi(FAT32)
222timetime stamp
242datedate stamp
262startstarting cluster number – FirstClusterLO(FAT32)
284sizesize of the file

표 FAT12, Root directory entry structure for short name

하지만 이 후에 소개된 Windows 시스템에서는 긴 파일 이름을 지원하게 되었다. 긴 파일 이름을 지원하기 위해 Directory entry 를 약간 변경하게 되었다. 물론, 기존 구조와의 호환성을 위해 긴 파일 이름을 위한 속성을 별도로 지정했고, 해당 속성은 Short name 만 지원하는 시스템에서는 보이지 않는 값이다. 하지만 이러한 구조는 특정 시스템 도구에게는 잘못된 것으로 보였으며, 오래된 버전의 시스템 도구는 긴 파일 이름을 위한 엔트리를 삭제하거나 디스크에 문제가 있다고 경고를 냈다. 하지만 이것은 옛날 시스템 도구로 한정된다. 긴 파일 이름을 지원하기 위해 고안된 새로운 구조체는 다음과 같다.

OffsetSizeFieldDescription
01idSequence number for a slot
110name0_4first 5 charcters for filename
111attrattribute byte
121reservedalways 0
131alias_checksumchecksum for 8.3 alias
1412name5_106 more characters for filename
262startstarting cluster number, always 0
284name11_12last 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 의 값” 이 짝수인 경우에는

Entry 에서 읽은 값을 0x0FFF 와 AND 연산

“해당 Cluster 의 값” 이 홀수인 경우에는

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
#define COUNT_OF_CLUSTER (   b)    (NR_DATA_SECTOR(b) / (b)->sector_per_cluster)
#define COUNT_OF_VALID_MAX_CLUSTER (   b)    (COUNT_OF_CLUSTER(b) + 1)
주의
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)
주의
01/01/1980 Epoch
#define DWORD (   buf)    ((unsigned int*)(buf))
#define FAT_ENTRY_OFFSET (   b,
 
)    (FAT_OFFSET(b, (N)) % (b)->sector_size)
#define FAT_EOF (   bpb)
값:
(IS_FAT12(bpb) ? 0x0FF8 : \
IS_FAT16(bpb) ? 0xFFF8 : \
IS_FAT32(bpb) ? 0x0FFFFFF8 : 0)
#define FAT_OFFSET (   b,
 
)
값:
(IS_FAT16(b) ? (N) << 1 \
: (IS_FAT32(b) ? (N) << 2 \
: (IS_FAT12(b) ? (N) + ((N)>>1) \
: -1)))
#define FAT_SECTOR_NUMBER (   b,
 
)    (FAT_OFFSET((b), (N)) / (b)->sector_size)
#define FAT_SECTORS (   b,
 
)    ((b)->reserved_sector + (N) * FAT_SIZE(b))
#define FAT_SIZE (   b)
값:
((b)->size_of_fat16 ? \
(b)->size_of_fat16 : \
(b)->type.fat32.size_of_fat32)
#define FIRST_DATA_SECTOR (   b)
값:
((b)->reserved_sector + ((b)->nr_fat * FAT_SIZE(b)) \
#define FIRST_ROOTDIR_SECTOR (   b)    ((b)->reserved_sector + (b)->nr_fat * FAT_SIZE(b))
#define FIRST_SECTOR_OF_CLUSTER (   b,
 
)    (((N) - 2) * ((b)->sector_per_cluster) + FIRST_DATA_SECTOR(b))
#define GET_FIRST_BLOCK (   d)    ((d)->first_cluster_hi << 16 | (d)->first_cluster_lo)
#define HOURS (   e)    (((e)->write_time >> 10) & 0x001F)
#define IS_EOF (   b,
 
)
값:
(IS_FAT12(b) ? (v) >= 0x0FF8 \
: IS_FAT16(b) ? (v) >= 0xFFF8 \
: IS_FAT32(b) ? (v) >= 0x0FFFFFF8 \
: 0)
#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 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_LAST_SLOT (   e)    (((e)->ord & LAST_LONG_ENTRY) == 0)
#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)
값:
(TOTAL_SECTOR(b) - \
((b)->reserved_sector + (b)->nr_fat * FAT_SIZE(b)) + \
#define PATH_MAX   256
#define ROOT_DIR_SECTORS (   b)
값:
((((b)->nr_root_entry * SIZEOF_DENTRY) + ((b)->sector_size - 1)) \
/ (b)->sector_size)
주의
This computation rounds up
#define SECOND_COUNT (   e)    ((e)->write_time & 0x000Fu)
주의
23:59:58
#define SET_CLUSTER_ENTRY_VALUE (   b,
  B,
  N,
  val 
)
값:
do { \
if (IS_FAT16(b)) { \
*DWORD((B) + FAT_ENTRY_OFFSET(b, N)) = (val); \
} else if (IS_FAT32(b)) { \
(val) = (val) & 0x0FFFFFFF; \
*DWORD((B) + FAT_ENTRY_OFFSET(b, N)) &= 0xF0000000;\
*DWORD((B) + FAT_ENTRY_OFFSET(b, N)) |= (val); \
} else { \
if ((N) & 0x0001) { \
(val) = (val) << 4; \
*WORD((B) + FAT_ENTRY_OFFSET(b, N)) &= 0x000F;\
} else { \
(val) = (val) & 0x0FFF; \
*WORD((B) + FAT_ENTRY_OFFSET(b, N)) &= 0xF000;\
} \
*WORD((B) + FAT_ENTRY_OFFSET(b, N)) |= (val); \
} \
} while (0)
#define SIZEOF_DENTRY   32
#define THIS_FAT_OFFSET (   b,
 
)    (FAT_SECTORS((b), (N)) * (b)->sector_size)
#define TOTAL_SECTOR (   b)
값:
((b)->total_sector16 ? \
(b)->total_sector16 : \
#define UPDATE_FAT_SIZE (   b,
  dsksz 
)
값:
do { \
unsigned int tmp[2]; \
unsigned int fatsz; \
tmp[0] = (dsksz) - ((b)->reserved_sector + ROOT_DIR_SECTORS(b)); \
tmp[1] = (256 * (b)->sector_per_cluster) + (b)->nr_fat; \
if (IS_FAT32(b)) { \
tmp[1] >>= 1; \
} \
fatsz = (tmp[0] + (tmp[1] - 1)) / tmp[1]; \
if (IS_FAT32(b)) { \
(b)->size_of_fat16 = 0; \
(b)->type.fat32.size_of_fat32 = fatsz; \
} else { \
(b)->size_of_fat16 = fatsz & 0x0000FFFF; \
} \
} 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 

함수 문서화

struct fat32_fsinfo __PACKED ( (packed)  )

변수 문서화

struct fat_long_dirent __PACKED