VEDA 복습/리눅스, 리눅스 프로그래밍

VEDA 53일차 - 임베디드 파일시스템, 디바이스 드라이버

잡학다식을꿈꾼다 2025. 6. 2. 17:06
반응형

 

raspberrypi : 리눅스를 지원하는 보드 중에서도 데이터가 아주 많은 편이다.

rpi 1 : S3C2410/BCM2835

rpi 2 : BCM2836

rpi 3 : BCM2837

rpi 4 : BCM2711(architecture changed)

rpi 5 : BCM2712

 

Porting : 보드 위에서 OS(리눅스 등)가 돌게 만드는 것(쉽지만은 않다!), 알아야할 것이 많은 부분

bootloader : uboot(일반적), start.elf(라즈베리파이의 부트로더)

kernel : zImage(압축된 이미지, kernel7.img(rpi3), kernel8.img(rpi4)), module(device drivers, ...ko.xz)

rootfile system(/) : libararies(/lib, /usr/lib), applications(/sbin, /usr/bin)

porting 하는 과정을 자동화한 툴 => Yocto

 

kenrel을 빌드하는 과정을 이해할 필요가 있다!!

 

make config : 설정

모듈 => .ko.xz, 커널 이미지 => .img

kernel info(authority : root) : /proc, /dev 

 

1. synchronous(동기적) : 송수신자 시점 차이가 없음

2. asynchronous(비동기적) : 송수신자 시점 차이가 있음 => 별도의 동기화 과정이 필요하다.

 

read(), write() 구현시 알아야할 사항

사용자 메모리 공간과 커널 메모리 공간 사이 데이터 이동

처리 조건이 되지 않은 블록처리

하드웨어 제어 함수

프로세스간 경합

인터럽트 함수와의 경합

 

memory mapped io

해당 영역에서 데이터를 읽는 함수 : readb, readw, readl (unsigned int addr <= 가상 주소로 접근해야 한다)

해당 영역에 데이터를 쓰는 함수 : writeb, writew, writel (data, unsigned int addr <= 가상 주소로 접근해야 한다) 

 

커널과 유저 공간 사이의 통신도 가능하다.

구분  함수명  방향  용도 및 설명
유저 공간 → 커널 공간 복사 copy_from_user() 유저 → 커널 유저 공간에서 커널 공간으로 데이터를 안전하게 복사 (유저 주소 유효성 검사 포함). 일반적인 read, ioctl 입력 처리에 사용.
커널 공간 → 유저 공간 복사 copy_to_user() 커널 → 유저 커널 공간에서 유저 공간으로 데이터를 안전하게 복사 (유저 주소 유효성 검사 포함). 일반적인 write, ioctl 출력 처리에 사용.
유저 공간 → 커널 공간 복사 (단순) get_user() 유저 → 커널 (단일 값) 유저 공간에서 커널 공간으로 단일 값 (예: int, char 등)을 복사. 주로 간단한 스칼라 값 복사 시 사용.
커널 공간 → 유저 공간 복사 (단순) put_user() 커널 → 유저 (단일 값) 커널 공간에서 유저 공간으로 단일 값 (예: int, char 등)을 복사. 주로 간단한 스칼라 값 반환 시 사용.
안전하지 않은 메모리 복사 memcpy() 메모리 직접 접근 커널 내부 메모리 복사 용도 (유저-커널 간에는 절대 사용 금지). 유저 주소에 대해 쓰면 커널 패닉, 메모리 오류 위험 있음.
보조 함수 (다중 값 복사) copy_in_user() / strncpy_from_user() 유저 내부 복사 또는 문자열 복사 유저 공간 내에서 복사하거나, 유저 공간에서 커널로 문자열 안전 복사. 문자열 관련 데이터 처리 시 유용.
유저 공간 → 커널 공간 긴 문자열 복사 strncpy_from_user() 유저 → 커널 (문자열) 유저 공간에서 커널 공간으로 문자열을 안전하게 복사 (최대 길이 지정). 문자열 기반 ioctl 처리나 사용자 입력 처리에 자주 사용.
커널 공간 → 유저 공간 문자열 복사 strnlen_user() 커널 → 유저 (문자열 길이 확인) 유저 공간 문자열의 길이를 안전하게 측정 (최대 길이 제한). 커널에서 유저 제공 문자열 크기 점검 시 사용.

 

SoC(Processor(core, MMU, cache etc) + Peripheral)

SoC 외부의 장치를 조작한다 하더라도 실질적으로 내부의 컨트롤러를 이용하여 조작하는 것!!

ioremap() : 물리 주소를 커널 가상 주소로 매핑(커널 가상 주소 - 디바이스)

함수명 void __iomem *ioremap(phys_addr_t phys_addr, size_t size)
주요 용도 물리 주소(특히 I/O 메모리 영역, MMIO)를 커널 가상 주소로 매핑
리턴 값 커널 I/O 가상 주소 (__iomem 타입 포인터)
사용 위치 리눅스 커널 모듈, 디바이스 드라이버
필요성 물리 주소는 직접 접근 불가하며, 반드시 페이지 테이블을 통해 매핑 후 접근해야 안전하기 때문

 

mmap() : 커널 자원을 직접 메모리로 매핑(유저 가상 주소 - 커널 공간 - 디바이스), 해당 함수를 이용하여 매핑을 하면 직접 하드웨어 자원에 접근할 수 있다.(매핑의 매핑)

항목  mmap()  ioremap()
적용 계층 유저 공간 ↔ 커널 공간 매핑 커널 가상 주소 ↔ 물리 I/O 주소 매핑
사용 주체 유저 공간 프로그램 (예: 애플리케이션) 커널, 주로 디바이스 드라이버
주요 목적 유저 프로그램에서 커널 자원(파일, 디바이스, 공유 메모리 등)을 직접 메모리로 매핑 커널이 I/O 물리 주소(MMIO, 장치 레지스터)를 커널 가상 주소에 매핑
함수 선언 void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset) void __iomem *ioremap(phys_addr_t phys_addr, size_t size)
대표 호출 위치 유저 공간 (glibc, 시스템 콜) 커널 모드 (커널 코드, 디바이스 드라이버)
매핑 대상 파일, 장치 노드, 공유 메모리, 익명 메모리 등 PCI, SoC, 메모리 맵드 I/O 레지스터 물리 주소 영역
접근 속성 기본적으로 캐싱 사용 가능, 옵션에 따라 다름 비캐시(non-cache) 또는 write-combining; I/O 접근 특화
반환 값 유저 공간 가상 주소 커널 I/O 가상 주소 (__iomem 포인터)
해제 방법 munmap() iounmap()
예제 사용 위치 유저 애플리케이션에서 /dev/mem 같은 특수 파일 매핑 커널 모듈에서 장치 레지스터 매핑 (예: PCI BAR 영역, MMIO 레지스터)

 

가상 주소 : 프로세서에서 사용하는 주소, MMU가 변환(커널 모드 주소 공간도 사용자가 보았을 때는 VA이다.)

물리 주소 : 하드웨어적으로 설정된 고정 주소

구분 함수명  입력 값 출력 값 용도 및 설명
가상 주소 → 물리 주소 virt_to_phys(void *vaddr) 커널 가상 주소 물리 주소 (phys_addr_t) 커널 공간 가상 주소를 해당 물리 주소로 변환. 직접 I/O 또는 DMA 등에서 물리 주소가 필요할 때 사용. 유저 공간 주소에는 사용 금지.
물리 주소 → 가상 주소 phys_to_virt(phys_addr_t paddr) 물리 주소 커널 가상 주소 (void *) 물리 주소를 커널 공간 가상 주소로 변환. 메모리 매핑된 영역 접근 시 주로 사용. 유저 공간 주소에는 사용 금지.
페이지 구조체 → 물리 주소 page_to_phys(struct page *page) 페이지 구조체 포인터 물리 주소 (phys_addr_t) 커널에서 struct page를 물리 주소로 변환. 주로 메모리 관리, 페이지 프레임 접근에서 사용.
가상 주소 → 페이지 구조체 virt_to_page(void *vaddr) 커널 가상 주소 struct page * 커널 가상 주소에 대응하는 페이지 구조체를 가져옴. 커널 내부 페이지 관리용.
페이지 구조체 → 가상 주소 page_address(struct page *page) 페이지 구조체 포인터 커널 가상 주소 (void *) 페이지 구조체로부터 커널 가상 주소를 가져옴. 커널 힙, slab 할당 등에서 사용.
유저 공간 VA → 물리 주소 get_user_pages() + page_to_phys() 유저 공간 가상 주소 → 페이지 물리 주소 (phys_addr_t) 유저 공간 가상 주소를 물리 주소로 변환하려면 get_user_pages()로 페이지를 pinning하고, page_to_phys()로 물리 주소 변환.
유저 공간 주소 변환 (간접) remap_pfn_range() 물리 주소 범위, 유저 mmap 유저 공간 가상 주소 매핑 유저 공간에 물리 주소를 매핑할 때 사용 (ex. mmap). 직접 변환 함수는 없고, 매핑으로 처리.

 

MUX를 연결해 준다.(하드웨어를 동작시키는 방법!!)

 

모듈을 제작하기 위해 알아야하는 사항

 

kthread : 운영체제 커널 내부에서 실행되는 독립적인 실행 흐름, 커널 공간에서만 실행된다는 차별점

항목 설명
실행 공간 유저 공간 없이 커널 공간에서만 실행
태스크 관리 일반 프로세스와 동일한 task_struct를 사용
스케줄링 일반 프로세스와 동일한 스케줄러에 의해 스케줄링됨 (예: CFS)
생성 방법 kthread_create() 또는 kthread_run() 함수 사용
종료 방법 kthread_stop()을 호출하여 종료 요청
커널 API 접근 유저 공간 프로세스와 달리 커널 내부 API, 자료구조, 하드웨어 직접 접근 가능
예시 워크큐(worker), 백그라운드 정리 작업, 디바이스 드라이버에서 인터럽트 처리 후 작업 분리 등

 

함수명  역할  주요 인자  반환값  특징 및 주의사항
kthread_create() 커널 쓰레드 생성 (즉시 실행하지 않음) int (*threadfn)(void *data) → 실행 함수void *data → 함수 인자const char namefmt[] → 쓰레드 이름 포맷 task_struct * (실패 시 ERR_PTR) 생성 후 wake_up_process()로 실행해야 함실패 시 IS_ERR()로 확인 필요
kthread_run() 커널 쓰레드 생성 후 즉시 실행 int (*threadfn)(void *data)void *dataconst char namefmt[] task_struct * (실패 시 ERR_PTR) kthread_create() + wake_up_process()를 결합한 편의 함수
kthread_should_stop() 쓰레드 내부에서 중지 요청이 들어왔는지 확인 없음 bool (중지 요청 시 true) kthread_stop() 호출 시 true 반환쓰레드 루프 내부에서 주기적으로 체크해야 안전 종료 가능
kthread_stop() 지정된 커널 쓰레드에 중지 요청 및 종료 대기 task_struct * → 대상 쓰레드 쓰레드 종료 함수의 반환값 안전한 종료를 위해 쓰레드 측에서 kthread_should_stop()을 체크하도록 설계 필요
wake_up_process() 생성된 커널 쓰레드를 실행 상태로 변경 task_struct * → 대상 쓰레드 int (1: 성공적으로 깨움, 0: 이미 실행 중) kthread_create()로 생성한 쓰레드를 명시적으로 깨워야 실행됨
kthread_bind() 커널 쓰레드를 특정 CPU에 바인딩 task_struct * → 대상 쓰레드unsigned int cpu → CPU 번호 없음 SMP 환경에서 특정 CPU로 스케줄링 제한 가능실제 바인딩은 실행 후에 적용됨
kthread_park() 커널 쓰레드 일시 정지 (parking) task_struct * → 대상 쓰레드 int (0: 성공, 음수: 에러) 쓰레드 측에서 kthread_should_park() 체크 필요일시 정지 상태로 유지됨
kthread_unpark() parking 상태인 커널 쓰레드를 재실행 task_struct * → 대상 쓰레드 없음 kthread_park()로 멈춘 쓰레드를 다시 실행 가능
kthread_should_park() 쓰레드 내부에서 parking 요청이 들어왔는지 확인 없음 bool kthread_park() 호출 후 쓰레드 측에서 주기적으로 확인해야 함
반응형