(원소스: 리눅스 디바이스드라이버, 유영창 저)
- MMU에는 MMU 테이블을 유지하기위한 별도의 관리 메모리가 없다. MMU는 보통 프로세서에 내장되고, 시스템 메모리를 같이 사용한다. 그래서 프로세서가 처음 부팅되면 리눅스는 시스템 메모리의 일부를 MMU 테이블에 할당하고, 관리할 정보를 MMU 테이블에 기록한다. 이 과정에서는 MMU가 동작하지 않는다. 리눅스 커널은 MMU 테이블에 관련된 정보를 메모리에 모두 기록한 후에 MMU 테이블에 해당하는 메모리 위치를 MMU에 알려주고 MMU를 동작시킨다.
- 메모리맵I/O 방식:
char *videoptr;
videoptr = (char *) 0x000B0000;
*videoptr = 'A';
- 메모리 접근 명령으로 처리하는 것을 Memory Mapped I/O라 한다. 하지만, MMU가 활성화되어 동작하는 커널 모드에서는 위의 예처럼 비디오의 물리주소 0x000B0000에 접근하면 페이지폴트가 발생하여 처리되지 않는다. 따라서, 디바이스드라이버는 하드웨어를 제어하기위해 물리주소를 가상주소로 매핑해야 한다. 메모리맵 I/O 방식의 물리적 주소공간을 커널에서 사용 가능한 주소로 매핑하거나 매핑된 영역을 해제할 때는 ioremap(), ioremap_nocache(), iounmap() 함수를 사용해야 한다.
- 응용 프로그램에서는 디바이스 파일로 하드웨어를 제어하기 위해 보통 read(), write(), ioctl() 함수를 사용한다. 이 함수들은 응용 프로그램에 하드웨어의 내부구조를 숨겨주는 효과가 있다. 그러나 이런 함수들은 프로세스 메모리공간과 커널메모리 공간사이의 메모리 전달 과정이 수반되기 때문에 매우 비효율적이다. 특히나 많은 용량의 데이터가 빠르게 전달되어야 하는 사운드나 비디오 장치에 메모리 복사가 수반되는 read(), write(), ioctl() 함수를 사용한다면 시스템 성능이 저하된다. (put_user(), copy_to_user(), get_user(), copy_from_user())
- 이렇게 비효율적인 방법을 극복하기위해 리눅스에서는 mmap() 함수를 제공하여 응용 프로그램에서 직접 하드웨어의 I/O 주소공간을 메모리 복사없이 직접적으로 사용할 수 있도록 한다. mmap() 함수는 원래 메모리 주소를 이용해 파일에 접근할 수 있도록 하는 함수다. 그러나 디바이스 파일에 적용할 경우에는 디바이스에서 제공하는 물리주소 (I/O 메모리 주소 또는 할당된 메모리 공간주소)를 응용 프로그램에서 사용할 수 있게한다. 응용 프로그램이 동작하는 프로세스의 메모리 영역에 디바이스 드라이버가 제공하는 물리주소를 매핑하면 된다.
- 동일한 물리주소를 가상주소로 매핑하는 방법에는 두가지가 있다. 하나는 mmap() 함수를 사용하여 응용프로그램의 프로세스 가상주소에 매핑하는 방법이고, 하나는 ioremap() 함수를 하용하여 커널의 가상주소에 매핑하는 방법이다.
- nopage 매핑방식: 디바이스 드라이버에서 mmap을 처리하는 방식은 mmap() 함수에서 remap_pfn_range() 함수를 이용하여 필요한 매핑을 직접처리하는 방법과 nopage 방식을 사용해 페이지 단위로 매핑하는 방법이 있다. 앞에서 설명한 방법이 remap_pfn_range() 함수를 이용해 매핑 대상이 되는 영역을 한꺼번에 매핑하는 방법이었다면, nopage 방법은 PAGE_SIZE 단위로 매핑을 처리한다. nopage 방식은 응용 프로그램에서 mmap() 함수를 호출하여 프로세스에서 사용할 수 있는 주소를 먼저 요구한다. nopage 방식은 remap_pfn_range() 함수를 이용하는 방법처럼 응용 프로그램 mmap() 함수를 호출하면 디바이스 드라이버의 파일 오퍼레이션 구조체에 정의된 mmap() 함수가 호출된다. 그러나 핲서 설명한 mmap() 함수가 요청된 영역의 매핑을 remap_pfn_range() 함수를 이용해 처리하는 것과 달리 nopage 방식에서는 mmap() 함수가 remap_page_range() 함수를 수행하지 않는다. 그래서 커널이 해당 영역을 매핑하지 않기 때문에 응용 프로그램이 mmap()을 통해 주소에 접근하면 해당 메모리 주소를 유효하지 않은 영역으로 인식하여 페이지폴트가 발생한다. 커널은 페이지 폴트가 발생하면 디바이스 드라이버로 매핑하기 위해 해당 주소공간이 예약된 주소 영역인지를 확인하고, 예약된 영역이면 vma->vm_ops->nopage에 선언된 함수를 호출한다. nopage 방식은 물리적인 I/O 메모리공간을 응용 프로그램의 프로세스 공간에 사용하기 보다는 주로 디바이스 드라이버에 의해 할당된 메모리 공간을 공유하기 위해 사용한다. nopage 방식으로 mmap을 구현하려면 디바이스 드라이버는 가장 먼저 페이지 폴트가 발생할 때 호출된 nopage() 함수를 만들어야 한다.
rsyoung@descartes:~$ cat /proc/iomem
00000000-0000ffff : reserved
00010000-0009fbff : System RAM
0009fc00-0009ffff : reserved
000a0000-000bffff : PCI Bus 0000:00
000e0000-000fffff : reserved
00100000-ba108fff : System RAM
01000000-015952c2 : Kernel code
015952c3-01ad4f6f : Kernel data
01bc1000-01d18113 : Kernel bss
ba109000-ba10bfff : ACPI Non-volatile Storage
ba10c000-bba6afff : System RAM
bba6b000-bbabefff : reserved
bbabf000-bbb72fff : System RAM
bbb73000-bbbbefff : ACPI Non-volatile Storage
bbbbf000-bbbe8fff : System RAM
bbbe9000-bbbfefff : ACPI Tables
bbbff000-bbbfffff : System RAM
bbc00000-bfffffff : reserved
c0000000-febfffff : PCI Bus 0000:00
c0000000-cfffffff : PCI Bus 0000:01
c0000000-cfffffff : 0000:01:00.0
d0000000-dfffffff : 0000:00:02.0
e0000000-e1ffffff : PCI Bus 0000:0b
e2000000-e4ffffff : PCI Bus 0000:01
e2000000-e3ffffff : 0000:01:00.0
e4000000-e4ffffff : 0000:01:00.0
e5000000-e5000fff : Intel Flush Page
e6000000-e80fffff : PCI Bus 0000:0b
e8000000-e8000fff : 0000:0b:04.0
e8000000-e8000fff : yenta_socket
e8001000-e80017ff : 0000:0b:04.1
e8001000-e80017ff : firewire_ohci
e8001800-e80018ff : 0000:0b:04.4
e8001900-e80019ff : 0000:0b:04.2
e8001900-e80019ff : mmc0
e8400000-e87fffff : 0000:00:02.0
e8800000-e97fffff : PCI Bus 0000:02
e9800000-ea7fffff : PCI Bus 0000:06
ea800000-eb7fffff : PCI Bus 0000:08
eb800000-ec7fffff : PCI Bus 0000:08
ec800000-ed8fffff : PCI Bus 0000:06
ec800000-ec801fff : 0000:06:00.0
ec800000-ec801fff : iwlagn
ed900000-ee8fffff : PCI Bus 0000:02
ee900000-ee91ffff : 0000:00:19.0
ee900000-ee91ffff : e1000e
ee920000-ee923fff : 0000:00:1b.0
ee920000-ee923fff : ICH HD audio
ee924000-ee924fff : 0000:00:19.0
ee924000-ee924fff : e1000e
ee925000-ee9257ff : 0000:00:1f.2
ee925000-ee9257ff : ahci
ee925800-ee925bff : 0000:00:1d.7
ee925800-ee925bff : ehci_hcd
ee925c00-ee925fff : 0000:00:1a.7
ee925c00-ee925fff : ehci_hcd
ee926000-ee9260ff : 0000:00:1f.3
f0000000-f3ffffff : PCI CardBus 0000:0c
f4000000-f7ffffff : PCI CardBus 0000:0c
f8000000-fbffffff : PCI MMCONFIG 0000 [bus 00-3f]
f8000000-fbffffff : reserved
f8000000-fbffffff : pnp 00:01
fec00000-fec00fff : reserved
fec00000-fec003ff : IOAPIC 0
fed00000-fed003ff : HPET 0
fed10000-fed13fff : reserved
fed10000-fed13fff : pnp 00:01
fed18000-fed19fff : reserved
fed18000-fed18fff : pnp 00:01
fed19000-fed19fff : pnp 00:01
fed1c000-fed1ffff : reserved
fed1c000-fed1ffff : pnp 00:01
fed20000-fed3ffff : pnp 00:01
fed40000-fed44fff : PCI Bus 0000:00
fed45000-fed8ffff : pnp 00:01
fee00000-fee00fff : Local APIC
fee00000-fee00fff : reserved
fee00000-fee00fff : pnp 00:01
ffe80000-ffffffff : reserved
100000000-1bfffffff : System RAM
rsyoung@descartes:~$