Key words
- Memory Mapped I/O
- Port Mapped I/O (I/O Mapped I/O)
- Device Driver
- mmap()
0. 도입
대부분의 Embedded System은(꼭 Embedded system이 아니더라도) 많은 peripheral device을 동반한다. 그렇다면 그 주변 기기들과는 어떤 방식으로 소통을 할까? Computer Architecture시간에 자세하게 다루었지만 자세한 내용은 까먹었다. 핵심은 Hardware 내부에 Device Controller가 있으며, 해당 Device Controller내부의 register 혹은 memory를 제어하여, device가 interrupt를 혹은 polling을 하여 저장된 내용을 읽고 쓰면서 CPU와 소통을 한다. 이 register를 제어하는 방법은 크게 2가지가 있는데 Memory Mapped I/O를 사용하는 방식과 Port Mapped I/O를 사용하는 방식이다. 하나씩 살펴보자.
1. Port Mapped I/O
위의 그림은 Port Mapped I/O를 설명할 때 사용되는 유명한 그림을 내가 다시 그린 것이다. IORQ라는 추가적인 signal을 통해, 현재 내가 접근하는 address space는 ram의 메모리 공간이 아닌 device를 control 하는 영역에 접근하고 싶다고 알려준다. 이러한 방법을 사용하면 User의 입장에서 access할 수 있는 address space가 줄어들지 않지만, I/O를 하기 위해 기존의 R/W를 사용하지 못 하고 새로운 Instruction을 사용해야 된다. (x86의 경우 IN/OUT)
2. Memory Mapped I/O
실제 User의 virtual memory address와 같은 영역을 사용한다. 즉, 평범하게 FFF0주소를 접근하였을 때, 만약 그 구간이 Device의 메모리가 mapping되어 있다면, Device를 control하게 되는 것이다. Memory mapped file과 동일하다고 생각하면 된다. 다만, Memory mapped file의 경우 virtual memory상으로 실제 데이터의 read/write이 이루어지는 반면, Memory Mapped I/O의 경우 해당 device memory를 copy하는 것이 아닌 mapping만하여 직접 쓰게 된다. 대부분의 임베디드 시스템 상에서는 Memory Mapped I/O와 같은 구현을 가지고 있다.
3. Device Driver
- What is Device Driver?
Device Driver는 Hardware을 직접적으로 제어하는 Device Controller을 제어하는 software이다. Unix, Unix like OS에서 '모든 것은 file이다'라는 철학에 따라 device도 file로 표시가 된다. Device File을 만들고 Device Driver를 통해 접근하게 되는 것이다. 보통 Device Driver는 Module 형식으로 구현이 되어 있어 Dynamic하게 kernel에 load하고 unload할 수 있다. (insmod, rmmod)
Device Driver는 module이기 때문에 insmod로 load를 하지만, 실질적으로 device driver을 생성하는 함수는 register_chrdv(blkdv, netdv)이다. 해당 함수를 통해 특정 device driver를 식별할 수 있는 major number를 받게 된다.
register_chrdv(unsigned int major, const char* name, struct file_operations *fops)
'Major'에 원하는 major number를 입력함으로써 static 하게 major number를 할당할 수 있지만, 이렇게 하기 위해서는 <Documentation/devices.txt>에서 빈 major number을 찾아서 할당해주어야 한다. 하지만 권장되는 방법은 major Numberfmf 0으로 하는 것이고, 이렇게 할 경우 register_chrdv의 return 값이 바로 major Number가 된다. 이 Major number는 이후에 device driver를 identify하는데 사용이 된다. device Driver가 제대로 적제 되었는지 확인하기 위해서는 /proc/devices를 확인해보면 된다. procfs안의 Device들을 확인하는 것이다.
file_operations도 중요한 역할을 한다. 각각의 device driver는 system call을 통해서 접근이 되는데, 당양한 종류의 system call들이 있을 수 있다. 특정 system call에 특정 결과를 수행하기 위한 함수들이 존재하는데 이것들이 바로 file_operations에 저장이 되어 넘겨진다. (예: write, read, ioctl, 등등) 이로 인해 flow는 특정 device driver를 제어하기 위해 device file을 열고, 특정 system call을 호출한다. device interface table에 major number를 index로 하여 device driver를 찾고, 해당 device driver에 연결된 file operation에서 호출한 system call에 해당하는 함수를 호출하는 것이다. - Device File
Device Driver가 device와 소통을 하기 위한 interface를 제공해주는 파일이다. Device File을 생성하는 명령어는 다음과 같다.
mknod [filename] [type] [major number] [minor number]
여기서 major number는 device driver를 생성할 때 만들었던 major number이다. minor number는 하나의 device driver에 연결된 다양한 device 들을 identify하기 위해 사용된다. 즉 device file하나로 인해 device를 identify하는 것이다. 여기서 filename과 앞에서 device driver의 name은 다른 값임에 주의하자. (예: tty, tty34) Device File은 /dev안에 모두 있다.
4. mmap()
함수의 기본형은 다음과 같다.
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_d offset)
start는 mapping을 시작할 가상 주소를 뜻하며, 보통은 NULL로 하여 임의로 정해지도록 한다. length는 page size의 배수로 설정을 해야 된다. 따라서 start~start+length까지의 주소를 할당받게 해주는 함수이며, fd가 가리키는 file의 offset~offset+length를 mapping해주는 것이다. prot와 flags는 추가적인 필드로, prot의 경우 PROT_READ, PROT_WRITE, PROT_EXEC, PROT_NONE을 사용하여 mapping된 공간에 대한 rwx를 설정해준다. flags는 MAP_SHARED, MAP_PRIVATE, MAP_FIXED로, 해당 mapping구간을 share할 것인지 아닐지 등등에 대한 설정을 해준다. 보통은 MAP_SHARED를 사용한다. 이렇게 mapping된 이후에는 평범한 memory영역에 접근하는 것과 같이 수식을 쓸 수 있으며, 경우에 따라서는 컴파일러가 최적화하는 것을 막기 위해 volatile keyword를 사용하여 변수를 선언한다.
5. 마치며
mmap의 경우 해당하는 device의 register, memory의 Physical memory상에서 offset이 어떻게 되는지에 대한 정보가 미리 필요하고 어떻게 작동해야 되는지에 대한 기본 지식도 어느정도 있어야 된다. 그렇지만 mapping이 이루어진 이후 쉽게 사용이 가능하며, virtual memory에 mapping되어있기 때문에 다른 process들과 sharing이 가능하다. 더 나아가 device driver의 경우 한번의 추가적인 copy가 필요한데 mmap은 이 과정을 생략해준다. device driver는 단순히 system call에 정해진 형식으로 명령을 내리기만 하면 되기 때문에 편하다.
'리눅스' 카테고리의 다른 글
Timer Management (0) | 2021.06.08 |
---|---|
Kernel Memory Allocation (0) | 2021.06.08 |
Cross Development Environment(교차 개발 환경) (0) | 2021.03.12 |
[linux kernel] CFS Scheduler에서 VR(T) 조정(fork) (0) | 2021.02.21 |
[linux kernel] CFS Scheduler에서 VR(T) 조정(wake_up) (0) | 2021.02.19 |