지엠대우 7주년 기념 시승

<EMBED pluginspage="http://www.macromedia.com/go/getflashplayer" src="http://www.style777.com/scrap/matiz.swf" width="500" height="375" type="application/x-shockwave-flash" quality="high" allowscriptaccess="alway"></EMBED>

NFS ROOT HDD / FileSystem

출처: http://windrain.springnote.com/pages/722612.xhtml

NFS ROOT

 

1. NFS 파일시스템

 

2. NFS 파일시스템 제작

 

3. NFS 부팅을 위한 커널 컴파일

 

4. NFS 파일시스템 부팅


커널 연구회 Kernel이란?


IDE 서브시스템의 초기화 HDD / FileSystem

발취: http://badcodek.springnote.com/pages/3470413

IDE 서브시스템의 초기화
 

IDE 디스크는 IBM PC의 역사의 많은 부분을 함께 해왔다. 이 시간을 통해 이들 장치로의 인터페이스도 변해 왔으며, 이는 IDE 서브시스템의 초기화를 처음 생각했던 것보다 더 복잡하게 만든다. 리눅스가 지원할 수 있는 최대 IDE 컨트롤러의 갯수는 4개이다. 각 컨트롤러는 ide_hwifs 벡터에 있는 ide_hwif_t 자료구조로 표현한다.각 ide_hwif_t 자료구조는 두개의 ide_drive_t 자료 구조를 가지고 있으며, 이 중 하나는 주 IDE 드라이브, 다른 하나 는 종속 IDE 드라이브를 위한 것이다. .IDE 인터페이스, 즉 컨트롤러가 발견되면, 컨트롤러와 이에 연결된 디스크를 반영하여 ide_hwif_t가 설정된다. IDE 드라이버가 I/O 메모리 공간에 있는 IDE 명령 레지스터에 명령을 씀으로써 동작이 이루어진다. 1차IDE 컨트롤러의 명령 레지스터와 제어 레지스터의 기본 I/O 주소는 0x1F0 - 0x1F7이다. 이들 주소는 IBM PC 초창기에서부터 관행으로 설정된 것이다.

IDE 드라이버는 각 컨트롤러를 리눅스 블럭 버퍼 캐시와 VFS에 등록하는데, 이는 blk_dev와 blkdevs 벡터에 추가하는 것이다. IDE 드라이버는 또한 해당하는 인터럽트에 대한 제어권을 요청한다. 이들 인터럽트 역시 관행처럼 1차 IDE 컨트롤러에 14, 2차 IDE 컨트롤러는 15로 설정된다. 그렇지만, 이들 설정은 IDE의 다른 상세한 설정과 마찬가지로 커널에 명령행(command line) 옵션을 주어서 덮어 쓸 수 있다. IDE 드라이버는 또한 부팅시 발견된 IDE 컨트롤러마다 gendisk를 만들어 gendisk의 리스트에 추가한다 이 리스트는 나중에 부팅시 발견된 모든 하드 디스트의 파티션 테이블을 찾는데 사용한다. 파티션을 검사하는 코드는 IDE 컨트롤러가 두개의 IDE 디스트를 제어할 수도 있다는 것을 알고 있다.


ide_hwif_t와 ide_drive_t 자료구조에 합리적인 값을 넣는다.(단 한번만의 실행이 유효하다.(MAGIC_COOKIE))

#define MAGIC_COOKIE 0x12345678
static void __init init_ide_data (void)
{
ide_hwif_t *hwif;
unsigned int index;
static unsigned long magic_cookie = MAGIC_COOKIE;

if (magic_cookie != MAGIC_COOKIE) // 이 함수가 한번만 실행하도록 처리해준다.
return; /* already initialized */
magic_cookie = 0;

/* Initialise all interface structures */
for (index = 0; index < MAX_HWIFS; ++index) {
hwif = &ide_hwifs[index]; // ide_hwfs_t 벡터에서 값을 긁어 온다.
init_hwif_data(hwif, index); // ide_hwfs_t와 ide_drive_t에 초기값을 넣어준다

// in/output 함수포인터 default_hwif_iops(hwif);

// ata in/output 함수 포인터 default_hwif_transpor(hwif);

init_hwif_default(hwif, index);

#if !defined(CONFIG_PPC32) || !defined(CONFIG_PCI)

hwif->irq = hwif->hw.irq = ide_init_default_irq(hwif->io_ports[IDE_DATA_OFFSET]);#endif

}

#ifdef CONFIG_IDE_ARM
initializing = 1;
ide_arm_init();
initializing = 0;
#endif
#ifdef CONFIG_BLK_DEV_IDE_STR8100 // starsemi SOC의 IDE 컨트롤러 초기화를 위한 driver code
initializing = 1;
str8100_ide_init();
initializing = 0;
#endif
}

 


Device driver - memory mapping Device Driver

가상 주소와 MMU 

 

간단한 시스템은 물리 주소만으로도 동작할 수 있다. 그러나 다중프로세스를 지원하고 각 프로세스에 대해 메모리 공간을 보호해야 하는 운영체제는 물리 주소만으로 구현하기는 어렵다.

 

MMU는 프로세서에 전달되는 주소를 다른 주소로 변환한다. 그래서 프로세서가 메모리에 접근하는 주소가 메모리에 직접 전달되는 것이 아니라 먼저 MMU에 전달되고, MMU는 변환 테이블을 참고해 이 주소를 실제 물리 주소로 변환해 전달한다. 이때 프로세서가 MMU에 가상 주소를 전달하면, MMU가 이 가상 주소를 해석하여 나온 물리 주소를 실제 메모리에 전달한다.

 

MMU가 프로세서에서 전달된 가상 주소를 물리 주소로 변환하려면 변환 테이블이 있어야 한다. 그런데 이 테이블에 저장되는 정보가 사용하는 메모리와 주소를 32비트 주소 체계를 사용하는 시스템보다 메모리가 9배나 더 필요하다. 그래서 MMU는 주소를 1:1 대응시키지 않고, 페이지단위로 처리한다. 보통 1 페이지의 크기는 4kbyte로 리눅스에서는 PAGE_SIZE라는 매크로로 관리한다. 32비트 주소 공간을 모두 사용하면 4gbyte크기의 메모리를 사용할 수 있지만 ,이런 시스템은 드물며 실제로 필요한 MMU테이블의 크기는 작다. 256mbytes의 메모리를 관리하려면 256kbyte정도의 MMU table이면 된다 .이렇게 MMU테이블을 이용해 CPU에 필요한 주소가 실제 주소로 변환되는데, 이때 CPU가 요구하는 주소를 가상 주소라 하고, MMU를 통해 접근되는 주소를 물리 주소라고 한다.

 

MMU에는 MMU테이블을 유지하기 위한 별도의 관리 메모리가 없다. MMU는 보통 프로세서에 내장되고 시스템 메모리를 같이 사용한다. 그래서 프로세서가 처음 부팅되면 리눅스는 시스템 메모리의 일부분을 MMU테이블에 할당하고, 관리할 정보를 MMU테이블에 기록한다. 이 과정에서는 MMU가 동작하지 않는다. 리눅스 커널은 MMU테이블에 관련된 정보를 메모리에 모두 기록한 후에 MMU테이블에 해당하는 메모리 위치를 MMU에 알려주고 MMU를 동작시킨다.

 

MMU 테이블은 MMU라는 하드웨어에 의존적이므로 리눅스 커널은 여러 아키텍처와 호환되도록 VM이라는 가상 메모리 관리 시스템을 사용한다. VM에서 관리하는 정보는 항상 MMU테이블에 적용된다. 리눅스 커널이 동작하는 초기 VM은 커널이 동작하는데 필요한 정보만 담고 있다가 프로세스가 생성되면 프로세스 동작에 필요한 메모리 관리 정보를 생성하고 MMU테이블을 갱신한다. 프로세스간의 메모리 참조는 원칙적으로는 불가능하다. 하지만 예외적으로 프로세스가 커널 모드로 진입했을 경우에는 가능하다. 프로세스가 시스템 호출을 통해 커널 모드로 진입하면 프로세스 메모리 공간은 커널 메모리 공간으로 바뀐다. 커널 모드 상태의 프로세스는 수퍼바이저 권한을 갖기 때문에 시스템 내의 모든 메모리 공간에 접근할 수 있다. 그래서 이 경우에는 다른 프로세스의 메모리 공간에 접근할 수 있는데, 이때 직접 접근은 안 되고 VM에서 제공하는 특정 함수를 통해 접근할 수 있다

 

이렇게 MMU라는 장치가 있으면 주소 영역이 같은 프로세스가 수행되더라도 MMU에 의해 실제로 접근되는 물리적인 메모리 주소가 달라지기 때문에 프로세스마다 독립된 메모리 주소를 가질 수 있다. 또한 물리적으로 존재하지 않는 경우에는 보조 기억 장치로 구현하는 가상 메모리 시스템을 만들 수 있다.

 

리눅스에서 관리하는 가상 메모리 테이블에는 커널 모드에서 동작하는 메모리 공간을 관리하는 커널 MMU메모리 테이블과 운영체제에서 수행되는 각 프로세스의 사용자 모드 상태의 메모리 공간을 관리하는 MMU메모리테이블이 있다. 또한 가상 메모리 공간은 이에 상응하는 물리적인 메모리 공간을 할당 받는다.

 

리눅스는 실제로 MMU를 효율적으로 처리하기 위해 커널 내부에서 관리하는 데이터 구조는 3단계 페이지 테이블이다.(PGD, PMD, PTE, OFFSET)

 

PGD 1단계 페이지 테이블로 이는 가장 상위에 존재한다. pgd_t구조체인 각 엔트리는 좀더 세분화된 다음 2단계인 PMD의 엔트리를 가리키고 이 PMD pmd_t라는 엔트리를 가진다. 그리고 이 엔트리는 다시 3단계인 PTE를 가리킨다. 이 중 PMD는 대부분의 리눅스 시스템에 형식적으로만 존재한다. 커널 컴파일 단계에서 최적화 처리로 이 PMD를 사용하지 않으면 코드에서 제거된다. 시스템에 따라서는 이 PMD를 사용하는 경우가 있는데, 이때는 컴파일 단계에서 제거되지 않는다.

 

물리 주소 공간을 커널 주소 공간으로 매핑

 

네트워크 필터와 같은 논리적인 디바이스 드라이버를 제외한 대부분의 디바이스 드라이버는 하드웨어를 제어한다. 그런데 디바이스 드라이버에서 하드웨어를 제어하려면 하드웨어에 관련된 I/O주소가 필요하다. i386계열의 프로세스는 in,out어셈블러 명령으로 제어할 수 있는 I/O주소 영역과 일반 메모리 처리 명령으로 접근할 수 있는 I/O주소 영역으로 나뉜다. in, out 명령으로 제어하는 I/O주소 공간은 inb(), outb()와 같은 매크로 함수로 접근하며, 메모리 처리 명령으로 접근하는 주소 공간은 일반 함수 포인터로 접근할 수 있다. 일반 함수 포인터로 접근 가능한 예가 비디오 버퍼 영역이다. 비디오 버퍼가 있는 0xB0000주소 영역에 데이터를 써 넣으려면 다음과 같은 형식으로 처리한다.

 

char* videoptr;

videoptr = (char*) 0x000B0000;

*videoptr = 'A';

 

위 코드는 TEXT모드의 화면에 'A'라는 문자를 출력하는데 이렇게 메모리 접근 명령으로 처리하는 것을 memory-mapped I/O라 한다. i386계열 이외의 프로세스는 대부분 memory-mapped I/O방식으로 처리한다.

 

사용자 가상 주소: 사용자 영역 프로그램이 보는 일반적인 주소

물리 주소 : 실제 메모리 주소

버스 주소 : 버스 주소는 프로세서가 사용하는 물리 주소와 동일하나 그럴 필요는 없다.

커널 논리 주소 : 일반적인 커널 주소 영역임. 대다수 아키텍처는 논리 주소와 연관된 물리 주소는 단지 상수값 차이만 있다. 아래 매크로는 커널 논리 주소와 연관된 물리 주소를 반환한다.

 

#define __pa(x)((unsigned long)(x) - PAGE_OFFSET)

0x80000000 -> CONFIG_PAGE_OFFSET -> __PAGE_OFFSET -> PAGE_OFFSET

 

커널 가상 주소: 커널 논리 주소와는 달리 물리메모리와 11매핑이 아닐수도 있다.

가상 메모리 영역(VMA) 가상 메모리 영역은 프로세스 주소 영역의 독립적인 영역을 관리하기 위해 사용하는 커널 자료 구조이다.

 

"독자적인 속성으로 무장한 메모리 객체"

 

/proc/*/maps에서 각각의 필드는 이미지 이름을 제외하고 struct vm_area_struct에 있는 필드에 대응한다.

 

vm_area_struct :

 

사용자 영역 프로세스가 mmap을 호출하여 디바이스 메모리를 프로세스 주소 영역으로 사상할 때 시스템은 이 사상을 표현하는 새로운 VMA를 생성하는 방법으로 반응한다.

 

mmap 디바이스 연산

 

디바이스를 사상한다는 말은 사용자 영역 주소 범위를 디바이스 메모리와 연결시킴을 의미한다.

mmap을 구현하기 위해서 드라이버는 단지 주소 범위를 위한 적절한 페이지 테이블을 구축하고 필요하다면 vma->vm_ops를 새로운 연산 집합으로 교체하는 작업만 하면 된다. 드라이버가 단순하면서도 디바이스의 메모리를 사용자 주소 영역으로 선형 사상을 하고 싶다면 remap_pfn_range를 호출하는 작업 이외에는 특별히 할 일이 없다.

 

가상 메모리 영역

 

가상 메모리 영역(VMA)는 프로세스 주소 영역의 독립적인 영역을 관리하기 위해 사용하는 커널 자료 구조이다.  VMA는 독자적인 속성으로 무장한 메모리 객체.

vm_area_struct

사용자 영역 프로세스가 mmap을 호출해서 디바이스 메모리를 프로세스 주소 영역으로 사상할 때 시스템은 이 사상을 표현하는 새로운 VMA를 생성하는 방법으로 반응한다.

unsigned long vm_pgoff 페이지에서 파일 영역의 오프셋을 가리킨다. 파일이나 디바이스를 사상하면, 이 값은 이 영역에 사상된 첫 페이지의 파일 크기가 된다.

unsigned long vm_flags 이 영역을 기술하는 플래그의 집합이다. VM_IO VMA를 메모리 사상 I/O영역으로 취급하게 표시한다. VM_RESERVED는 메모리 관리 시스템에게 이 VMA를 스왑아웃하지 않도록 요청한다. VM_RESERVED는 대부분 디바이스 사상에 필요한 플래그이다.

 

프로세스 메모리 사상

메모리 관리 퍼즐의 마지막 조각은 프로세스 메모리 사상 구조체로서, 모든 다른 자료구조체를 함께 포함하고 있다. 시스템에서 각 프로세스는(몇몇 커널 영역 보조 스레드를 제외하면) (<linux/sched.h>에 정의되어 있는) struct mm_struct 를 포함한다. 이 구조체는 프로세스의 가상 메모리 영역, 페이지 테이블, 메모리 관리 기록 정보를 위한 다양한 비트와 세마포어(mmap_sem), 스핀락(page_table_lock)도 포함하고 있다. 이 구조체를 가리키는 포인터는 task구조체에서 발견할 수 있다. 아주 드물게 드라이버가 이 구조체에 접근할 필요가 있으며, 일반적으로 current->mm을 사용한다. 메모리 관리 구조체는 프로세스 사이에 공유될 수 있다는 사실에 주의한다. 예를 들어 리눅스는 이러한 방식으로 스레드 동작을 구현한다.

 

mmap 디바이스 연산

 

드라이버와 관련된 범위에서 메모리 사상은 사용자 프로그램에 디바이스 메모리를 직접 접근하도록 제공하기 위해 구현할 수 있다. 디바이스를 사상한다는 말은 사용자 영역 주소 범위를 디바이스 메모리와 연결시킴을 의미한다. 프로그램이 할당 받은 주소 영역에 읽거나 쓸 때마다 실제로는 디바이스에 접근한다. 사상은 PAGE_SIZE단위로 이루어져야 한다. 따라서 사상된 영역은 반드시 PAGE_SIZE의 배수여야 하며, 물리 메모리는 PAGE_SIZE의 배수인 주소에서 시작해야만 한다.

시스템 호출은 다음과 같이 선언한다.

 

mmap( caddr_t addr, size_t len, int prot, int flags, int fd, off_t offset);

 

다른 한편으로 커널 파일 연산 자료 구조체에서는 다음과 같이 선언한다.

 

int (*mmap) (struct file* filp, struct vm_area_struct* vma);

 

mmap을 구현하기 위해서 드라이버는 단지 주소 범위를 위한 적절한 테이블을 구축하고 필요하다면 vma->vm_ops 를 새로운 연산 집합으로 교체하는 작업만 하면 된다.

페 이지 테이블을 구축하는 두 가지 방법이 존재한다. remap_pfn_range로 불리는 함수를 사용해서 한방에 해결하거나, nopage VMA메소드를 사용해서 필요할 때 페이지를 만들어내는 방법이 있다. 각 메소드는 장단점이 있다.

 

nopage를 사용한 메모리 사상

 

비록 전부는 아니더라도, remap_pfn_range가 대다수 드라이버를 위한 mmap구현 과정에서 제대로 동작하지만, 종종 좀 더 유연할 필요가 있다. 이러한 상황에서는 nopage VMA메소드를 사용한 구현 방식이 필요하다.

nopage방식은 PAGE_SIZE단위로 매핑을 처리한다.

 

nopage 방식은 응용 프로그램에서 mmap()함수를 호출하여 프로세스에서 사용할 수 있는 주소를 먼저 요구한다. nopage방식은 remap_pfn_range()함수를 이용하는 방법처럼 응용프로그램이 mmap()함수를 호출하면 디바이스 드라이버의 file_operation구조체에 정의된 mmap함수가 호출된다. 그러나 mmap()함수가 요청된 영역의 매핑을 remap_pfn_range()함수를 이용해 처리하는 것과는 달리 nopage방식에서는 mmap()함수가 remap_pfn_range()함수를 수행하지 않는다. 그래서 커널이 해당 영역을 매핑하지 않기 때문에 응용 프로그램이 mmap()을 통해 주소에 접근하면 해당 메모리 주소를 유효하지 않은 영역으로 인식하여 page fault 가 발생한다.

커널은 page fault가 발생하면 디바이스 드라이버로 매핑하기 위해 해당 주소 공간이 예약된 주소 영역인지를 확인하고, 예약된 영역이면 vma->vm_ops->nopage에 선언된 함수를 호출한다. 이 함수는 페이지 폴트가 발생한 가상 주소 페이지의 물리 주소 페이지를 디바이스 드라이버에 요청한다. 여기서 vma vm_area_struct 구조체 변수고, vm_ops vm_operation_struct 구조체 변수다. 디바이스 드라이버에 선언된 nopage()함수는 요청된 영역에 대한 검사를 수행하고 성공적으로 수행되어 해당 영역에 해당하는 물리 주소를 반환하면 응용프로그램은 해당 영역의 페이지 폴트에서 벗어난다.

 

nopage 방식은 물리적인 I/O메모리 공간을 응용프로그램의 프로세스 공간에 사용하기보다는 주로 디바이스 드라이버에 의해 할당된 메모리 공간을 공유하기 위해 사용한다. 디바이스 드라이버와 메모리를 공유하는 대표적인 경우가 DMA버퍼 공간이다. 사운드 장치처럼 최근에 많이 사용되는 멀티미디어 장치들은 많은 데이터를 하드웨어에 전달해야 하기 때문에 DMA방식을 지원한다. DMA에 사용되는 메모리 공간은 kmalloc()이나 __get_free_page()를 이용해 할당한다.  이렇게 할당된 메모리는 응용프로그램에서 직접 다루어야 하므로 mmap이 필요한데 이때 RAM공간에 대한 매핑은 주로 nopage방식을 사용한다. 그리고 nopage방식으로 다룰 때 가장 편리한 것이 바로 vmalloc()함수로 할당한 공간이다.

 

함수 형식

struct page* xxx_nopage(struct vm_area_struct* vma, unsigned long addr, int* type);

 

이 함수는 addr매개변수에 전달된 가상 주소에 대응하는 물리주소 페이지를 반환하도록 작성돼야 한다. 그리고 vm_area_struct구조체 변수 vma와 매핑영역의 선두 주소인 addr그리고 page fault처리 타입을 반환하기 위한 type이 매개변수로 전달된다.

 

unsigned long addr

프로세스 메모리 공간에 매핑할 주소공간이 넘어오는데 이 주소는 프로세스 메모리 공간에 유효한 가상 주소다. nopage()함수는 addr에 대응하는 물리 주소 페이지를 반환해야 하며 이 반환값은 struct page*타입이어야 한다.

addr PAGE_SIZE aligned 되서 넘어온다. nopage함수는 이렇게 전달된 addr값이 가장 먼저 매핑 영역에서 벗어나는지를 검사해야 한다.

 

int* type

page fault의 처리 종류를 반환할 수 있는 주소를 전달하는데 보통 디바이스 드라이버는 VM_FAULT_MINOR값을 반환한다 VM_FAULT_MINOR값은 고정된 페이지 테이블의 매핑처리를 의미한다.

 

함수 구현 예 : vmalloc()함수로 mapping_memory변수에 MAPPING_SIZE만큼 할당한 메모리를 nopage방식으로 응용 프로그램의 프로세스 공간에 매핑한다.

 

struct page* xxx_nopage(struct vm_area_struct* vma, unsigned long addr, int* type) {

struct page* page;

unsigned long offset;

void* page_ptr;

 

//매핑 대상이 되는 메모리의 유효성을 검사한다.

if (mapping_memory == NULL) return NOPAGE_SIGBUS;

 

//매핑 대상의 영역에서 벗어나는지를 검사한다.

offset = addr - vma->vm_start;

if (offset >= MAPPING_SIZE ) return NOPAGE_SIGBUS;

 

//페이지의 물리 주소를 구한다.

page_ptr = mapping_memory + offset;

page = vmalloc_to_page (page_ptr);

 

//페이지 사용수를 증가시킨다. 이러지 않으면 해당 페이지가 매핑되더라도 프로세스가 종료되어 매핑 영역을 해제할 때 문제가 발생한다. 특정 페이지의 사용수를 증가시킬때는 get_page()를 사용한다.

get_page(page);

if (type) *type = VM_FAULT_MINOR;

return page;

}

 

nopage()함수에서는 보통 다음 세 가지를 구현해야 한다.

- 요구한 addr에 대한 영역 검사

- 요구한 addr에 대한 물리 주소 페이지 획득

- 구한 물리 주소 페이지의 참조수 증가

 


1 2 3