Part18. Tutorial6-간단한 파일시스템을 추가해 보자

원문 : http://kkamagui.springnote.com/pages/447164

 

들어가기 전에...

0.시작하면서...

 이번 Tutorial은 이클립스 버전으로 변경된 OS 프레임워크 1.0.3 버전 소스를 이용해서 진행하도록 하자. 이클립스 버전 프레임워크는 21 OS 프레임워크 소스 릴리즈에 있으니 다운받아서 덮어쓰면된다.

덮어 쓴 뒤에 Tutorial5의 Custom.zip 파일을 다운받아 덮어쓰면 Tutorial 6을 진행할 수 있다.

 이클립스 환경이 없는 사람은 굳이 이클립스를 깔지 않아도 되니 너무 신경쓰지 말자. 이클립스는 단순히 개발 환경을 편리하게 하는 도구일 뿐이다. 이클립스가 없더라도 커맨드 라인에서 make를 입력하여 빌드하고 테스트 할 수 있다.

 

 프레임워크 1.0.3 버전은 makefile을 대폭 수정하여 make 명령을 통해 컴파일 -> 링크 -> 이미지 파일 생성을 한번에 끝낼 수 있다. 새로운 프레임워크 소스를 설치한 사람은 DJGPPBIN 폴더에 있는 실행파일 이름을 변경해야 하는데 20 작업환경 설치를 참고하자.

 

 

1.파일 시스템 설계

 우리 주위에는 다양한 파일 시스템이 존재하며 OS에 따라 메인 파일 시스템이 존재한다. 윈도우즈의 경우는 NTFS, Linux 같은 경우는 ext3fs와 같은 파일 시스템을 사용한다. 제대로된 파일 시스템을 설계한다거나, 혹은 NTFS or FAT or ext3 와 같은 파일 시스템을 지원하는 것은 상당량의 지식과 분석이 필요하므로 간단한 파일 시스템 구현을 목표로 하자.

 위에서 언급한 파일 시스템중에 가장 간단한 파일 시스템은 FAT 파일 시스템으로 파일을 링크드 리스트(Linked List)의 형태로 관리한다. 링크를 따라가면 파일을 찾을 수가 있으며, 파일의 생성 역시 링크를 연결하는 것이 전부이다. 자료구조 자체가 아주 간단하므로 윈도우 머신부터 임베디드 시스템까지 널리 사용된다.

 FAT 파일 시스템에 대한 내용은 01 FAT 파일 시스템(File System)를 참고하도록하고 이 FAT 파일 시스템을 기반으로 간단한 파일 시스템을 만들어 보도록 하자.

 

 

1.1 파일 시스템 공간(File System Area)

 OS 프레임워크는 하드디스크 or 플로피 디스크와 같은 주변기기 I/O 함수를 거의 제공하지 않는다(적어도 지금까지는 그렇다). 그렇다면 어디에 파일 시스템을 구성할까?

 정답은 "메모리"다. 메모리에 구성된 파일 시스템을 우리는 흔히 "램 디스크(RAM Disk)"라고 부른다. 메모리에 파일 시스템을 생성하기위해서는 공간이 필요한데 동적 할당을 위한 힙으로 4Mbyte ~ 8Mbyte의 주소 공간을 이미 할당했다. 메모리 공간을 연속적으로 할당하면 램의 낭비를 줄일 수 있으므로 램디스크의 공간을 8Mbyte ~ 12Mbyte(4Mbyte의 크기)로 잡자.

 램디스크 영역을 결정했으니 Virtual Box의 램 설정을 12Mbyte 이상으로 설정하면 준비 완료다.

 

 

1.2 파일 시스템 메타 데이터(File System Meta Data) 설계

 파일 시스템 공간이 확보되었으니 파일 시스템 관리를 위한 블럭을 설정하고 할당해야 한다. 우리가 만들 간단한 시스템은 FAT 파일 시스템에서 File Allocation Table(FAT) 및 Data Area 영역만 가진 아주 간단한 구조를 가진다. File Allocation Table(FAT)의 각 엔트리(Entry)는 2Byte의 크기를 가지고 각 엔트리는 512byte 크기를 가지는 블럭을 지시한다.

 총 파일 시스템 공간의 크기가 4Mbyte이므로 이것을 512byte(한 섹터의 크기)로 나누면 8192개의 엔트리가 존재한다. 하나의 엔트리당 2Byte 공간을 차지하므로 FAT 공간은 총 16384Byte(16Kbyte)의 크기이다.

 위의 내용을 그림으로 표현하면 아래와 같다.

filesystem1.PNG

<File System Architecture>

 

8Mbyte ~ 8Mbyte + 16Kbyte는 FAT 엔트리를 위한 영역이고 8Mbyte + 16Kbyte ~ 12Mbyte까지는 데이터를 위한 영역이다. 위 그림의 화살표가 의마하는 것은 FAT의 엔트리와 Data Area의 한 블럭이 1:1 대응임을 의미한다. FAT 파일 시스템과 동일한 방식이므로 FAT 파일 시스템의 용어를 사용하여 Data Area의 한 섹터(Sector)를 클러스터(Cluster)라고 부르자.

 

 

1.3 디렉토리 및 파일 설계

 파일 시스템에는 루트 디렉토리(Root Directory)라는 것이 존재한다. 이것은 트리(Tree) 형태를 가지는 디렉토리 구조에서 트리의 루트(Root)에 해당하는 것으로 모든 파일 및 폴더의 시작점이다. 우리가 파일에 접근할때 OS로 넘겨주는 절대 경로를 자세히 들여다 보면 루트 디렉토리부터 시작하는 경로를 넘겨준다("C:\ABC\a.txt" or "/home/kkamagui/a.txt"  같은 경로가 절대 경로이다). 저 경로를 받아서 OS에서는 루트 디렉토리부터 차례로 접근하여 파일 또는 폴더를 찾는 것이다.

 

 그렇다면 디렉토리 구조에서 디렉토리와 파일은 어떻게 다르며 어떻게 구분할까?

 일반적으로 섹터내에 존재하는 데이터라는 관점에서 디렉토리도 파일과 동일하다. 다른점은 파일은 저장한 데이터를 가지지만, 디렉토리는 다른 디렉토리 또는 파일에 대한 정보를 가진다는 점이다.

 

 위에서 디렉토리와 파일에 대해서 차이점을 간략하게 알아봤으니 디렉토리 구조를 디자인 하자. 디렉토리 또한 파일의 형태이므로 클러스터 단위로 구성되게 되는데, 클러스터 단위(섹터 크기와 동일 = 512Byte)를 엔트리 크기로 나누어서 나머지가 0이되게하면 클러스터 단위로 디렉토리를 처리할 수 있으니 구현이 간단해 진다.

 디렉토리 구조의 경우, 최소한의 데이터를 포함해야 하는데, 그중 하나는 엔트리의 이름이고 나머지 하나는 엔트리의 데이터가 시작되는 클러스터 인덱스다. 클러스터 인덱스의 정보는 FAT 내의 엔트리 인덱스와 일치하므로 클러스터 번호를 이용해서 FAT 내의 엔트리에 접근할 수 있다. 그렇다면 FAT 내의 엔트리가 2Byte로 구성되어있으므로 디렉토리 엔트리 역시 클러스터 인덱스가 2Byte로 구성되어야 하고, 이것을 바탕으로 디렉토리 엔트리는 아래와 같이 구성할 수 있다.

  • 파일 이름 : 11Byte 
  • 디렉토리/파일 속성 플래그 : 1Byte 
  • FAT 엔트리 인덱스 : 2Byte
  • FILE 크기 : 2Byte

 위와 같이 구성하면 총 16Byte가 필요하다. 512Byte를 16Byte으로 나누면 총 32개의 엔트리로 딱 떨어지므로 적절한 선택인것 같다.

 만약 디렉토리의 엔트리가 32개가 꽉차서 더이상 넣지 못하는 경우라면 어떻게 할까? 새로운 클러스터를 할당받아서 디렉토리 클러스터에 연결하고 새로 할당받은 클러스터에 엔트리를 생성하면 된다. 클러스터를 연결하는 부분은 각자 구현해 보도록하고 지금은 구현을 간단히 하기위해 32개로 제한하자.

 

 

1.4 파일 시스템 API

 우리가 설계한 파일 시스템은 FAT와 Data Area간의 연결을 통해 파일 및 디렉토리를 관리하므로 이를 관리하고 사용할 API가 필요하다. Standard C API 수준으로까지 파일 시스템 API를 제공하려면 많은 부분을 고민해야하니 Low Level 수준의 API만 제공하고 추후 업그레이드를 하는 걸로 하자.

 Low Level 수준의 API란 어떤 것일까? 아래와 같은 함수들이 Low Level 함수라고 보면 된다(간략하게 표현했다).

  • 섹터 할당 및 FAT 관련 함수 
    • Alloc Cluster() : 빈 클러스터를 하나 할당하여 Alloc으로 마크하고 그 인덱스를 반환
    • Free Cluster( a ) : 넘겨 받은 인덱스 a 클러스터를 Free로 마크
    • Link Cluster( a, b ) : a 클러스터의 뒤에 b 클러스터를 링크 
    • Unlink Cluster( a ) : a 클러스터 뒤에 연결된 링크를 끊음
    • Get Next Cluster( a ) : a 클러스터 뒤에 연결된 다음 클러스터의 인덱스를 반환

 

  • 디렉토리 엔트리 관련 함수 
    • Make Entry( directory cluster, name, attribute, file cluster ) : directory sector 인덱스가 가리키는 클러스터의 내용에 name의 이름을 가지고 attribute의 속성을 가지면서 file sector 클러스터를 시작으로하는 엔트리를 생성
    • Delete Entry( directory cluster, name ) : directory cluster 인덱스가 가리키는 클러스터의 내용을 조사하여 name을 가지는 엔트리를 삭제

 

  • I/O 관련 
    • Read Sector( sector, buffer ) : sector 인덱스의 데이터를 읽어서 buffer에 복사 
    • Write Sector( sector, buffer ) : buffer의 값을 sector 인덱스의 데이터로 복사
    • Read Cluster( cluster, buffer ) : cluster 인덱스의 데이터를 읽어서 buffer에 복사 
    • Write Cluster( cluster, buffer ) : buffer의 값을 cluster 인덱스의 데이터로 복사

  섹터 인덱스는 0x800000(8Mbyte)를 시작으로 하여 섹터 단위(512Byte) 단위로 전체를 나눈 인덱스를 의미한다. 즉 0 섹터의 시작 주소는 0x800000(8Mbyte) + 512 * 0 이 되고, 1 섹터는 0x800000(8Mbyte) + 512 * 1이 되는 것이다.

 반면에 클러스터의 주소는 Data Area의 다음에 위치하는 공간을 512Byte로 나눈 인덱스를 의미한다. 즉 FAT 영역이 16Kbyte를 차지하므로 0번 클러스터의 시작 주소는 0x800000(8Mbyte) + 16Kbyte + 512 * 0 이 되고 1번 클러스터의 시작 주소는 0x800000(8Mbyte) + 16Kbyte + 512 * 1이 된다.

 

1.5 파일 시스템 기능 요약

  • 루트 디렉토리(Root Directory)로 부터 트리(Tree) 형태로 이루어지는 디렉토리 구조 지원
  • 한 디렉토리 당 생성 가능한 디렉토리 및 파일의 개수는 총 32개
  • 파일은 클러스터 링크를 연결함에 따라 가변적 크기 가능

 

 

2.구현

 파일 시스템이나 메모리 관리 같이 세세한 디버깅이 필요한 부분은 커널을 실행하여 테스트하기가 까다롭다. 특히나 Step By Step 형식으로 접근하기 힘든 커널 디버깅은 복잡한 부분에 대한 분석이 더욱 힘들다. 따라서 알고리즘이 결정되면 Visual C++과 같은 개발툴로 알고리즘을 검증하여 정상 동작함을 확인하고 이것을 포팅하는 것이 효율적이다. 실제로 프레임워크의 동적 메모리 할당 코드와 파일 시스템 코드를 Visual C++로 코딩하여 각 케이스를 모두 테스트한 뒤 포팅하였다.

 

2.1 Visual C++ 코드

 Visual C++과 프레임워크의 차이라면 같은 역할을 하는 함수의 이름(memset -> kMemSet, memcpy -> kMemCpy 등등)이 다른 점과 주소공간(프레임워크는 8M~12M의 공간 사용, Visual C++에서는 임의의 할당된 메모리 공간 사용)이 다른 점 정도다. 이 정도의 차이는 매크로를 정의하면 쉽게 포팅할 수 있다. 자세한 부분은 첨부에 포함된 Visual C++용 프로젝트 파일을 참고하자.

FileSystem1(1).PNG

<Visual C++에서 파일 시스템 코드를 실행한 화면>

 

2.2 프레임워크 코드

2.2.1 FileSystem.h 수정

 아래는 위에서 언급한 기능들을 정의해 놓은 FileSystem.h 파일이다.

  1. /**
        File System Management
            파일 시스템에 대한 처리
  2.     Written KKAMAGUI, http://kkamagui.egloos.com
    */
    #ifndef __FILESYSTEM_H__
    #define __FILESYSTEM_H__
  3. //#include <windows.h>
    #include "../FW/DefineMacro.h"
  4. // 클러스터와 섹터의 크기 정의
    #define SECTORSIZE  512
    #define CLUSTERSIZE SECTORSIZE
  5. // 램디스크을 위한 메모리 주소의 시작과 끝
    #define RAMDISK_START_ADDRESS   ( 8 * 1024 * 1024 )
    #define RAMDISK_END_ADDRESS     ( 12 * 1024 * 1024 )
  6. // 램디스크 크기 및 기타 데이터
    #define RAMDISK_SIZE            ( RAMDISK_END_ADDRESS - RAMDISK_START_ADDRESS )
    #define RAMDISK_DATA_START_ADDRESS  ( RAMDISK_SIZE / SECTORSIZE * 2 )
    #define RAMDISK_FATENTRYCOUNT       ( RAMDISK_SIZE / SECTORSIZE )
  7. // 디렉토리 엔트리의 속성 정의
    #define DIRECTORY_ENTRY_FILE        0x01
    #define DIRECTORY_ENTRY_DIRECTORY   0x02
  8. // 디렉토리 엔트리 구조체
    typedef struct directoryEntry
    {
        char vcName[ 11 ];
        BYTE bAttribute;
        WORD wClusterIndex;
        WORD wSize;
    } DIRECTORYENTRY, * PDIRECTORYENTRY;

  9. // 함수 목록
    void InitFileSystem( void );
    BOOL MakeFile( char* pcAbsPath, BYTE bAttribute );
    BOOL DeleteFile( char* pcAbsPath );
    BOOL FindDirectoryClusterFromAbsPath( char* pcAbsPath, int* piClusterIndex );
    BOOL ReadCluster( int iClusterIndex, BYTE* pbBuffer );
    BOOL WriteCluster( int iClusterIndex, BYTE* pbBuffer );
  10. #endif /*FILESYSTEM_H_*/

  파일 및 디렉토리를 생성하고 삭제하는 기능과 클러스터를 읽고 쓰는 기능을 지원한다. 클러스터에 링크를 연결하는 기능이 구현되어있으나 파일 생성 및 클러스터 링크 기능은 지금 사용하지 않으므로 헤더 파일에 포함시키지 않았다. 현재는 파일 및 디렉토리 엔트리를 디렉토리 구조에 생성하고 삭제하는 기능만 포함되어있으니 추후 필요하면 확장하도록 하자.

 FileSyste.c 파일은 내용이 많으므로 아래 첨부에서 소스를 다운받아 보도록 하자. 위에서 언급한 설계대로 구현 했기 때문에 문서와 같이 보면 크게 어렵지 않을 것이다.

 

2.2.2 KShell.c/h 수정

 디렉토리를 생성하는 명령 mkdir과 디렉토리를 삭제하는 명령 rmdir, 그리고 디렉토리를 표시하는 명령 ls를 ProcessCommand() 함수에 추가하였다.

  1.  /**
        엔터가 쳐졌으면 버퍼의 내용으로 명령어를 처리한다.
    */
    void ProcessCommand(char* vcCommandBuffer, int* piBufferIndex)
    {
        char vcDwordBuffer[ 8 ];
        static DWORD vdwValue[ 10 ];
        static int iCount = 0;
  2. ...... 생략 ......
  3.     // Directory를 출력한다.
        else if( ( *piBufferIndex > 3 ) &&
                 ( kMemCmp( vcCommandBuffer, "ls", 2 ) == 0 ) )
        {
            vcCommandBuffer[ *piBufferIndex ] = '\0';
            PrintDirectory( vcCommandBuffer + 3 );
        }
        // Directory를 만든다.
        else if( ( *piBufferIndex > 6 ) &&
                 ( kMemCmp( vcCommandBuffer, "mkdir", 5 ) == 0 ) )
        {
            vcCommandBuffer[ *piBufferIndex ] = '\0';
            MakeFile( vcCommandBuffer + 6, DIRECTORY_ENTRY_DIRECTORY );
        }
        // Directory를  지운다.
        else if( ( *piBufferIndex > 6 ) &&
                 ( kMemCmp( vcCommandBuffer, "rmdir", 5 ) == 0 ) )
        {
            vcCommandBuffer[ *piBufferIndex ] = '\0';
            DeleteFile( vcCommandBuffer + 6 );
        }   
    }
  4. ...... 생략 ......
  5. /**
        디렉토리를 출력한다.
    */
    void PrintDirectory( char* pcPath )
    {
        int iClusterIndex;
        BYTE vbCluster[ CLUSTERSIZE ];
        DIRECTORYENTRY* pstEntry;
        int i;
        char vcBuffer[ 8 ];
  6.     kPrintf( "==== Directory = [%s] ====\n", pcPath );
       
        // Path로 Cluster를 찾는다.
        if( FindDirectoryClusterFromAbsPath( pcPath, &iClusterIndex ) == FALSE )
        {
            kPrintf( "Error!!! Can't Find Cluster\n" );
            return ;
        }
  7.     // Cluster를 읽는다.
        if( ReadCluster( iClusterIndex, vbCluster ) == FALSE )
        {
            return ;
        }
  8.     // 클러스터를 모두 돌면서 Entry를 뽑는다.
        pstEntry = ( DIRECTORYENTRY* ) vbCluster;
        for( i = 0 ; i < ( CLUSTERSIZE / sizeof( DIRECTORYENTRY ) ) ; i++ )
        {
            if( pstEntry->vcName[ 0 ] == '\0' )
            {
                pstEntry++;
                continue;
            }
           
            kPrintf( "%s     ", pstEntry->vcName );
  9.         if( pstEntry->bAttribute == DIRECTORY_ENTRY_FILE )
            {
                kPrintf( "%s     ", "FILE" );
            }
            else
            {
                kPrintf( "%s     ", "DIRECTORY" );
            }
  10.         kPrintf( "%X    %X\n", pstEntry->wSize, pstEntry->wClusterIndex ); 
            pstEntry++;
        }
        kPrintf( "\n" );
    }

 

 

3.Simple File System의 효용성

 앞서 설명했듯이 이 파일 시스템은 작은 용량의 램을 할당받아 저장매체로 사용하는 램디스크이다. 램디스크의 특성상 재부팅하면 내용이 사라진다. 저장매체라면 당연히 그 정보가 남아있어야 하는데, 그러한 관점에서 보면 이 램디스크는 쓸모 없는 것이 아닌가?

 물론 데이터가 유지되지 않는다는 점에서 크게 마이너스지만... 램 디스크의 특성이 그러하듯 매우 빠른 검색과 파일 생성/디렉토리 생성이 가능하다. 그렇기에 새로운 파일 시스템의 테스트용으로 아주 적절하다(같은 수의 파일을 생성했다가 지운다고 가정할때 하드디스크가 빠르겠는가? 램디스크가 빠르겠는가?)

 재부팅 후에 내용이 사라지는 문제도 쉽게 해결할 수 있다. 주기적으로 램디스크의 내용을 하드디스크에 덤프하여 보관하고 OS가 부팅되었을때 하드디스크의 내용을 메모리로 로드하면 된다. 추후 램디스크의 내용이 보관되어야 하거나 지속적으로 관리되어야 하는 경우 위의 방법으로 해결하면 된다. 혹시나 파일 시스템이 너무 간단하여 쓸일이 있을까 하는 사람들을 위해 노파심에 끄적여 보았다. 기능이 부족하다고 생각되면 필요에 맞게 수정하면되니 너무 걱정하지 말자.

 

4.마치면서...

 이상으로 간단한 파일 시스템을 구현해 보았다.

 

5.첨부

5.1 프레임워크 1.0.3 이전 버전

 

5.2 프레임워크 1.0.3 이후 버전

 

 

 

이 글은 스프링노트에서 작성되었습니다.

+ Recent posts