리눅스/디바이스 드라이버

리눅스/메모리 매핑(memory mapping)

우진샘 2010. 7. 6. 16:29

물리주소
간단한 8비트 프로세서는 하드웨어로 연결된 메모리버스주소와 프로그램에서 접근하는 메모리주소가 같다.
그래서 주소값이 하드웨어에 의해 고정되고 프로세서는 그 안에서 동작한다.


가상주소와 MMU
하지만 다중프로세스를 지원하고, 각 프로세스의 메모리공간을 보호해야 하는 OS는 안정적인 동작을 보장하지 못한다.

이 점을 해결하기 위해 MMU와 가상 주소 개념을 도입했다. 프로세서가 메모리에 접근할때 mmu에 가상주소를 전달하고 mmu는 변환테이블을 참고해 실제 물리주소로 변환해 전달하는 것이다. MMU는 주소를 페이지 단위로 처리한다. 크기는 보통 4KByte이다.  그래서 256MBytes메모리를 관리할때 MMU테이블은 시스템 메모리의 256KByte정도를 사용한다.

프로세서가 처음 부팅되면 리눅스는 메모리의 일부분을 MMU테이블에 할당하고, 관리할 정보를 MMU테이블에 기록한다.  기록이 끝난 후 MMU테이블의 메모리 위치를 MMU에 알려주고 작동시킨다.

MMU테이블은 MMU라는 하드웨어에 의존적이므로 호환성을 위해 VM이라는 가상 메모리 관리 시스템을 사용한다. VM은 프로세스가 생성되면 프로세스동작에 필요한 메모리 관리 정보를 생성하고, MMU테이블을 갱신한다.

프로세스간의 메모리 참조는 프로세스가 시스템 호출을 통해 커널 모드로 진입했을 때만 예외적으로 가능하다.  이때 VM의 특정 함수를 통해 접근 할 수 있다.

VM이 관리하는 MMU테이블에는 메모리 접근 속성도 있다. 그래서 허가되지 않는 접근에는 예외처리가 발생한다. MMU덕분에 프로세스마다 독립된 메모리 주소를 가질 수 있다.


리눅스의 메모리 관리
MMU를 효율적으로 처리하기 위해 커널내부에서 관리하는 데이터 구조는 다음과 같다
1단계 PGD
2단계 PMD 형식적으로만 존재
3단계 PTE


주소변환 함수
DD는 커널 공간에서 동작하기 때문에 물리주소를 가상주소로 바꾸어 사용해야 한다.


물리주소 공간을 커널 주소 공간으로 매핑
MMU가 활성화되어 동작하는 커널 모드에서는 물리주소로 접근하면 페이지폴트가 발생한다. (예외가 많음)
그래서 DD는 HW를 제어하기 위해 물리주소를 가상주소로 매핑해야 한다.  다음과 같은 함수를 사용한다.

void *ioremap(unsigned long offset, unsigned long size);
물리->가상으로 매핑

void *ioremap_nocahe(unsigned long offset, unsigned long size);
물리->가상으로 매핑

void iounmap(void *addr);
가상 주소로 할당된 공간을 해제

ioremap과 ioremap_nocache함수는 물리주소의 시작주소와 크기를 매개변수로 지정한다.  크기는 페이지의 배수여야 한다.  성공하면 가상주소의 선두주소가 반환된다.

char *videoptr;
videoptr = ioremap(0x000b0000, 0x10000);
if(videoptr != NULL)
{
    *videoptr = 'A';
}

PCI 디바이스는 ioremap()함수만 사용하면 문제가 생긴다. PCI 디바이스는 prefetchable영역과 non-prefetchable 영역으로 나뉘는데 후자는 반드시 ioremap_nocache함수를 이용해야 된다.

가상주소로 매핑해 사용후 DD가 커널에서 제거될때는 가상주소를 해제해야 한다. 이때 iounmap이 사용된다. ioremap이나 ioremap_nocache함수에서 반환한 주소를 인자로 넘기면 된다.


I/O 물리 주소와 가상 주소간의 변환 함수
리눅스 커널은 부팅단계에서 시스템의 모든 I/O제어, 램 영역의 물리주소를 MMU테이블로 미리 작성한다. 이렇게 고정된 영역을 예약영역이라고 한다. 이 영역은 PAGE_OFFSET 매크로 상수값을 이용해 물리와 가상으로 변환을 한다. ioremap과 kmalloc에서 반환한 주소도 변환된다. 이 매크로를 이용해 주소변호나을 하는 함수는 다음과 같다.

virt_to_phys
phys_to_virt
ioremap함수는 요구된 물리주소로 시작하는 영역을 커널모드에서 사용할 수 있도록 가상 주소 공간으로 등록하지만 두 함수는 주소를 단순히 변환만 하는 것이다.

virt_to_bus
bust_to_virt
위와 동일한 기능을 수행하지만 DMA관련루틴에서는 이 함수들을 사용하고 램,비디오에서는 위쪽 함수를 사용해야 호환성이 좋아진다.


프로세스 메모리 매핑
대량의 데이터를 처리하는 하드웨어를 다루는 DD작성시 mmap은 필수적으로 구현해야..
app에서 dd로 hw를 제어할때 read,write,ioctl은 프로세스 메모리공간과 커널 메모리 공간사이의 메모리 전달과정이 수반되기 때문에 매우 비효율적이다. 고로 mmap을 이용해 직접 hw의 io주소 공간을 메모리 복사없이 직접적으로 사용할 수 있다.

mmap함수는 메모리 주소를 이용해 파일에 접근하도록 하는 함수다. 그러나 디바이스 파일에 적용할 경우 디바이스에서 제공하는 물리주소를 app에서 사용할 수 있게 한다.  사용법은 간단한다.
app가 동작하는 메모리 영역에 dd가 제공하는 물리주소를 매핑하면 된다.

app가 디바이스파일을 대상으로 mmap함수를 호출하면 커널은 메모리매핑에 대한 작업을 수행하고 물리주소를 얻을 수 있게  디바이스 파일의 파일 오퍼레이션 구조체에 선언된 mmap함수를 호출한다. 이 함수는 매핑을 수행하고 이렇게 처리된 주소는 app로 반환된다.

물리주소를 가상주소로 매핑하는 방법에는 두가지가 있다.
mmap함수를 사용해 app의 프로세스 가상 주소에 매핑하는 방법이고 또 하나는 ioremap함수를 사용해 커널의 가상주소에 매핑하는 것이다.


응용프로그램에서 호출하는 mmap()
이 함수는 매개변수로 전달된 디바이스 파일에 연결된 dd에게 start매개변수로 지정한 값을 시작주소로 주고, offset 매개변수가 가리키는 물리적 수조를 프로세스 메모리 공간 내에 매핑시킬 것을 커널에 요구한다.

char *addr = (char *)mmap(0, 4*4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0x0b0000);

매핑한 영역을 해제하려면 munmap함수를 사용한다

munmap(addr, 4*4096);


DD의 mmap구현
app에서 mmap함수를 디바이스파일에 대해 호출하면 커널에서 적절하게 처리된 이후에 파일에 연결된 dd의 파일 오퍼레이션에 등록된 mmap함수가 호출된다. 적절한 처리란 커널 내부의 VM에서 app에서 호출한 mmap함수의 매개변수를 이용해 메모리 매핑이 가능하도록 mv_area_struct 구조체를 생성, 초기화한 후 전달하는 것을 말한다.

DD의 mmap함수 호출되면 remap_page_range함수를 호출해야 한다. 이 함수는 물리 주소를 가상메모리 영역으로 매핑한다.

이 함수 호출전에 매핑정보의 유효성확인, 실제물리주소구하기, 물리주소의 특성 기술을 먼저 해야 한다.
매핑정보검사는 크게 두 가지인데 첫번째 PAGE_SIZE로 정렬되었는지, 두번째는 매핑 가능한 영역을 초과하는지여부다. 물리주소는  대부분 dd에서 제공한다. 이때는 app의 mmap함수에서 전달하는 offset값을 물리주소의 오프셋값으로 사용할 수 있다.  대표적인 디바이스파일은 /dev/mem이다. 물리주소의 특성은 두가지로 나뉜다.  I/O영역인지 표시하는 것이고 다른 하나는 예약 영역으로 표시하는 것이다.


I/O영역
일반메모리가 아닐 경우 VM_IO 플래그를 OR시켜야 하드웨어 이상으로 I/O영역의 주소 공간 접근이 실패하더라도 프로세서가 죽지 않는다. 


예약영역
VM_RESERVERD를 OR시켜 mmap대상이 되는 메모리를 스왑 아웃 대상에서 제외하도록 한다. 매핑된 메모리는 스왑아웃될 경우 문제가 발생할 수 있다. kmalloc과 같은 함수를 이용하여 할당했을 때 반드시 사용되지 않을 경우에는 해제시켜야 한다. 그렇지 않으면 app가 munmap함수를 호출할 때 문제가 발생한다.


nopage 매핑방식
DD에서 mmap을 처리할 때 remap_page_range()함수를 이용하여 필요한 매핑을 직접 처리하는 방법과 nopage방식을 사용해 페이지 단위로 매핑하는 방법이 있다.

nopage방식은 app에서 mmap함수를 호출하여 프로세스에서 사용할 수 있는 주소를 먼저 요구한다. 이 방식은 remap...함수처럼 app에서 mmap함수를 호출하면 DD의 파일오퍼레이션 구조체에 정의된 mmap함수가 호출된다.

이 방식은 remap_page..함수를 수행하지 않기 때문에 app가 mmap을 통해 주소에 접근하면 페이지폴트가 발생한다. 커널은 이 공간이 예약된 주소 공간인지 확인하고 예약된 영역이면 vma->vm_ops->nopage에 선언된 함수를 호출한다. 이 함수는 페이지폴트가 발생한 가상주소페이지의 물리주소페이지를 DD에 요청한다.

DD에 선언된 nopage함수는 요청된 영역에 대한 검사를 수행하고 성공적으로 수행되어 해당 영역에 해당하는 물리주소를 반환하면 app는 해당영역의 페이지폴트에서 벗어난다. 이 다음부터는 페이지폴트가 발생하지 않는다.

nopage방식은 물리적인 I/O메모리공간을 app의 프로세스에서 사용하기보다는 DD에 의해 할당된 메모리 공간을 공유하기 위해 사용한다.  DMA버퍼공간이 그 예다.  nopage방식으로 다룰 때 가장 편리한 것이 바로 vmalloc함수로 할당한 공간이다.

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

addr에 전달된 가상주소에 대응하는 물리주소 페이지를 반환하도록 작성되어야 한다.
nopage함수에서는 보통 다음 세가지를 구현해야 한다.
요구한 addr에 대한 영역검사
요구한 addr에 대한 물리 주소 페이지 획득
구한 물리주소 페이지의 참조 수 증가