MMIO 방법인 ioremap()과 mmap() 비교 정리
1. 개요
리눅스에서 메모리와 I/O를 다루는 두 인터페이스를 비교해보자.
mmap()
유저 공간 프로세스가 자신의 가상 주소 공간에 파일이나 메모리 영역을 매핑해 달라고 커널에 요청하는 시스템 콜이다.ioremap()
커널 내부(주로 디바이스 드라이버)에서 디바이스의 물리 I/O 메모리(레지스터 등)를 커널 가상 주소 공간으로 매핑하는 커널 함수이다.
둘 다 “물리적인 어떤 것”을 “가상 주소”에 연결한다는 공통점이 있지만,
- 누가 호출하는지,
- 무엇을 매핑하는지,
- 어떤 속성으로 접근하는지,
가 상당히 다르다.
2. 호출 주체의 차이
2.1 mmap()
- 호출 주체: 유저 공간 프로세스
- 형태: 시스템 콜
void *addr = mmap(NULL, size,
PROT_READ | PROT_WRITE,
MAP_SHARED,
fd, 0);
- 반환값
addr은 유저 공간 가상 주소이다. - 애플리케이션 코드에서는 일반 포인터처럼
*(int *)addr형태로 접근한다.
2.2 ioremap()
- 호출 주체: 커널 / 디바이스 드라이버
- 형태: 커널 내부 함수
void __iomem *base;
base = ioremap(phys_addr, size);
if (!base)
return -ENOMEM;
- 반환값
base는 커널 가상 주소(__iomem표시)이다. - 이 주소에 대해서는
readl(),writel()같은 MMIO용 헬퍼 함수로 접근해야 한다.
3. 주로 매핑하는 대상의 차이
3.1 mmap()의 대상
mmap()은 주로 다음과 같은 대상을 유저 프로세스 주소 공간에 매핑하는 데 사용한다.
- 일반 파일 (예: 큰 파일을 메모리처럼 접근)
- 익명 메모리 (
MAP_ANONYMOUS) – 공유 메모리 등 - tmpfs,
/dev/shm등 메모리 기반 파일시스템 - 드라이버가 허용할 경우, 디바이스 메모리도 매핑 가능
디바이스 메모리에 대한 mmap()은 보통 다음 구조를 가진다.
- 유저 프로세스:
mmap()을/dev/mydev같은 디바이스 파일에 대해 호출 - 커널 드라이버:
file_operations.mmap콜백 안에서
remap_pfn_range(),dma_mmap_*()등을 사용해 물리 I/O 메모리를 유저 공간에 매핑
3.2 ioremap()의 대상
ioremap()은 주로 다음을 매핑한다.
- SoC 주변장치 레지스터
- PCI 디바이스 BAR에 할당된 MMIO 레지스터 영역
- 기타 메모리 매핑 I/O 영역
즉, 하드웨어 데이터시트에 나오는 “레지스터 물리 주소”를 커널이 접근 가능한 가상 주소로 바꾸는 역할을 한다.
4. 메모리 속성과 캐시 정책의 차이
4.1 mmap()의 일반적인 속성
일반적인 파일이나 익명 메모리를 mmap() 할 때는:
- 캐시 가능한(cacheable) 일반 RAM으로 매핑되는 경우가 많다.
- CPU 캐시, 페이지 캐시와 연동되어 디스크 I/O와도 밀접하게 연결된다.
- 애플리케이션은 이 영역을 일반 메모리처럼 사용한다.
단, 디바이스 메모리를 mmap() 하는 경우는 드라이버에서 pgprot_noncached() 같은 것을 사용해 비캐시 또는 특수 속성으로 바꿔줄 수 있다.
4.2 ioremap()의 메모리 속성
디바이스 레지스터는 다음과 같은 이유로 캐시되면 안 된다.
- 레지스터에 쓰기를 했는데, 실제 디바이스까지 쓰기가 가지 않고 캐시에만 머무르면 오류가 발생할 수 있다.
- 레지스터에서 읽을 때, 이전 값이 캐시에 남아 있으면 하드웨어 상태를 잘못 읽게 된다.
그래서 ioremap()은 아키텍처별 구현에서 해당 영역을 비캐시(non-cache) 또는 디바이스 전용 메모리 타입으로 매핑한다.
커널에는 용도에 따라 다음과 같은 변형도 존재한다.
ioremap()/ioremap_nocache()/ioremap_wc()등
5. 매핑 결과 주소를 사용하는 방식의 차이
5.1 mmap() 결과 (user-space)
int fd = open("file.bin", O_RDWR);
void *p = mmap(NULL, 4096,
PROT_READ | PROT_WRITE,
MAP_SHARED,
fd, 0);
int v = ((int *)p)[0];
((int *)p)[1] = 123;
- 결과 주소는 일반 포인터처럼 사용한다.
- 컴파일러는 이를 일반 메모리로 취급한다.
- 별도의 특수 헬퍼 함수 없이
*ptr,ptr[i]형태로 접근한다.
5.2 ioremap() 결과 (kernel-space)
#define REG_STATUS 0x00
#define REG_CONTROL 0x04
void __iomem *base = ioremap(phys, 0x1000);
u32 status = readl(base + REG_STATUS);
writel(0x1, base + REG_CONTROL);
- 결과 주소는
__iomem타입으로 표시된다. - 접근은
readb/readw/readl/readq,write*()계열 함수로 수행한다.- 아키텍처별 MMIO 접근 방식
- 엔디언 처리
- 메모리 배리어(순서 보장)
등을 이 헬퍼들이 담당한다.
단순히 다음과 같이 쓰는 것은 권장되지 않는다.
u32 v = *(volatile u32 *)(base + REG_STATUS); // 비권장
이는 아키텍처 간 이식성과 메모리 ordering 측면에서 문제가 될 수 있기 때문이다.
6. mmap()과 ioremap()이 함께 쓰이는 경우
디바이스 레지스터를 유저 공간에서 직접 접근시키고 싶은 경우, 전형적인 구조는 다음과 같다.
- 드라이버 초기화
ioremap()으로 디바이스 레지스터 영역을 커널 가상 주소에 매핑한다.
file_operations.mmap구현remap_pfn_range()등을 사용해 해당 물리 영역을 유저 공간 프로세스에 매핑한다.
- 유저 프로세스
/dev/mydev에 대해mmap()을 호출해 얻은 포인터를 통해 레지스터를 직접 읽고 쓴다.
이렇게 보면:
ioremap()은 커널이 하드웨어에 접근하기 위한 매핑mmap()은 유저 공간이 파일/메모리/디바이스를 자기 주소 공간에서 보도록 하기 위한 매핑
이라고 정리할 수 있다.
7. 요약 비교 표
| 항목 | mmap() |
ioremap() |
|---|---|---|
| 호출 주체 | 유저 공간 프로세스 | 커널 / 디바이스 드라이버 |
| 위치 | 시스템 콜 | 커널 내부 함수 |
| 반환 주소 공간 | 유저 가상 주소 | 커널 가상 주소(__iomem) |
| 주 대상 | 파일, 익명 메모리, 드라이버가 허용한 디바이스 | 디바이스 레지스터, MMIO 영역 |
| 메모리 속성 | 기본은 캐시 가능 RAM (상황에 따라 변경 가능) | 비캐시 또는 디바이스 타입 메모리로 매핑 |
| 접근 방식 | 일반 포인터 dereference | readl()/writel() 등 MMIO 헬퍼로 접근 |
| 대표 사용 시나리오 | 파일 매핑, shared memory, user-level I/O | 레지스터 맵 매핑, 드라이버에서 하드웨어 제어 |
8. 핵심 정리
mmap()
유저 공간에서"이 파일/메모리를 내 주소 공간에 붙여줘"라고 커널에 요청하는 시스템 콜이다.
결과는 유저 주소이고, 일반 포인터처럼 접근한다.ioremap()
커널에서"이 물리 I/O(레지스터) 영역을 커널 주소 공간에 매핑해줘"라고 하는 함수이다.
결과는 커널 주소이고,readl()/writel()같은 함수로 접근한다.
즉, 두 함수는 모두 “매핑”을 하지만,
- 레이어(유저 vs 커널),
- 대상(일반 메모리/파일 vs I/O 레지스터),
- 접근 방식(일반 포인터 vs MMIO 헬퍼)
댓글남기기