IT_Study/Embedded System

Operating System (15) : Device Driver, Kernel Module 및 device file의 이해

__Vivacé__ 2023. 4. 20. 10:40

Device Driver

PC외부 하드웨어 장치가 서로 신호를 주고 받을 수 있도록 통신하는 것을 도와주는 소프트웨어

Firmware에서 임베디드 개발
 - 중간 Layer가 없다 --> H/W memory map address에 직접 값 access가 가능
 - 단점 : H/W 장비 교체 시, 모든 Firmware의 H/W 관련 코드를 수정해야 한다.

Kernel
 - 공통적으로 쓰는 API 제공 --> 중간 layer 역할
 - Kernel 소스코드만 새로운 H/W가 동작되도록 수정해서 다시 build하면, 다른 firmware를 수정할 필요 없음
 - 단점 : 빌드 시간이 매우 길다

Kernel Module ( != Device Driver)
 - 커널에 들어가는 코드 덩어리
 - 디바이스 드라이버를 "커널 모듈 형식"으로 제작
     - 커널 모듈만 동작 중인 커널에 적재 / 해제하는 방식으로 테스트하는 개념을 착안
     - 디바이스 드라이버를 빌드하면 ,결과물이 커널 모듈 형식으로 만들어지고, 커널 안에 적재/해제할 수 있다.

Kernel 내 Application과 Device Driver 전체 상호작용 구조

 

Device Driver의 장점

 - App / Driver 역할 분담

Application 개발자 Device Driver 개발자
HW를 제어할 수 있는 API 사용법 익히기
API가 건네 준 Data 처리
API를 이용하여 HW 제어
Log 남기기
Network / UI 작업
...
HW 제어 API 제작
HW 불량 분석
Latency 측정
...

 

타 OS 에서 Device Driver 개발

• Windows : Windows Driver Kit (WDK) 으로 개발
    - C++
    - WDM ( Windows Driver Model ) : MS에서 Driver 개발 표준화 시킨 모델
    - 개발자 센터 : https://learn.microsoft.com/ko kr/windows hardware/drivers/

• MacOS : I/O Kit Framework 으로 개발
    - C++
    - Application 개발 / Kernel 용 Driver 개발 파트로 나눠 개발
    - I/O Kit Document : https://developer.apple.com/library/archive/documentation/DeviceDrivers/Conceptual/IOKitFunda mentals/Introduction/Introduction.html

 

메모리맵에 GPIO 핀을 매핑하고, GPIO 핀에 H/W 장치를 연결하여 해당 메모리 주소에 신호를 줘서 동작시키는 방법

-> Memory Mapped I/O

 


Device Driver 제작

커널 소스코드
 - 운영체제의 소스코드
 - C로 작성, GPL 라이선스 사용

커널 소스코드를 수정한다는 의미
-> 수정된 소스코드를 컴파일해서 나만의 새로운 커널 개발
다양한 시스템에 적용할 수 있고, 사용자의 요구에 따라 맞춤형 운영체제 커널 제작이 가능

운영체제 커널의 동작방식을 이해, 시스템 최적화, 버그 수정 등
운영체제 개발자, 시스템 엔지니어들은 커널 소스코드에 대한 이해가 필수

 

1. 커널 소스코드 만들기 - "~/test/hi.c" 파일 생성

  1 // 리눅스 커널 모듈 헤더 파일을 포함
  2 #include <linux/module.h>
  3
  4 // 모듈 라이선스를 GPL로 설정 > 소스 코드를 공개, 수정 및 배포 권한을 사용자에게 부여
  5 MODULE_LICENSE("GPL");
  6
  7 
  8 static int hi_init(void)
  9 {
 10     // 로드될 때 "OK HELLO KFC" 메시지를 출력
 11     printk( KERN_INFO "OK HELLO KFC\n");
 12     // 성공적으로 로드되었음을 나타내는 0을 반환
 13     return 0;
 14 }
 15
 16
 17 static void hi_exit(void)
 18 {
 19     // 언로드될 때 "BYE BYE" 메시지를 출력
 20     printk( KERN_INFO "BYE BYE\n\n");
 21 }
 22
 23 // hi_init 함수를 모듈 초기화 함수로 지정
 24 module_init(hi_init);
 25 // hi_exit 함수를 모듈 종료 함수로 지정
 26 module_exit(hi_exit);

특징

 - main 함수가 없음

 - 라이센스 설정이 들어감

 - printk 사용

 - 커널 내부 모듈끼리 함수 중복을 피하기 위해 함수명을 "모듈이름_역할" 형태로 사용

 - 모든 함수에 static 적용

 

2. Makefile 생성

  1 # 현재 실행 중인 커널 버전에 대한 헤더 파일 경로를 변수로 설정
  2 KERNEL_HEADERS=/lib/modules/$(shell uname -r)/build
  3
  4 # obj-m은 빌드할 커널 모듈의 이름을 hi.o로 지정
  5 obj-m := hi.o
  6
  7 # 'go' 라벨은 커널 모듈을 빌드하는 명령을 실행
  8 go:
  9     # 현재 디렉토리의 소스 코드를 사용하여 커널 모듈을 빌드
 10     make -C $(KERNEL_HEADERS) M=$(PWD) modules
 11
 12 # 'clean' 라벨은 빌드 프로세스에서 생성된 파일을 삭제하는 명령을 실행
 13 clean:
 14     # 커널 모듈 빌드에 사용된 파일들을 삭제
 15     make -C $(KERNEL_HEADERS) M=$(PWD) clean

- C : 해당 디렉토리로 이동해서 make를 수행 --> 해당 디렉토리에 있는 Makefile로 make를 실행

- M=$(PWD) : 결과물이 현재 디렉토리에 생성 --> 해당 디렉토리의 Makefile의 결과물을 현재 디렉토리에 생성

 

3. Makefile 실행

make 명령어 성공 시, .ko 파일(디바이스 모듈)이 생성됨을 알 수 있다.

 

4. Kernel 내부 동작 상태 파악

# 최근 커널로그 출력
$ sudo dmesg

# 커널로그 모니터링
$ sudo dmesg -w

dmesg 결과물

모니터링을 켜놓으면, 적재 / 제거 결과를 실시간으로 확인할 수 있다.

 

5. 커널 모듈을 파일에 적재  / 제거

Device Driver를 build 후 생성 된 파일(.ko)을 Kernel에 적재 / 제거할 수 있다.

# insmod
# Device Driver를 제작해서 빌드한 파일(.ko)를 Kernel에 적재 | Kernel이 해당 module을 관리하기 시작
$ sudo insmod hi.ko

# rmmod
# 필요없는 Device Driver Module을 제거
$ sudo rmmod hi

좌 : 명령어 진행 / 우 : dmesg로 모니터링한 결과

 

6. 결론

 - Application 개발 시, Device File에다가 System Call API 만 쓰면, 장치 제어가 된다.
 - 만약 H/W가 바뀌면 , Kernel을 다시 Build 할 필요가 없다. Device Driver 만 다시 작성하면 된다.

 


Kernel Module vs Device Driver

Kernel Module

    - Kernel에 집어 넣을 수 있는 덩어리

    - insmod / rmmod를 사용해 실행 가능

Device Driver

    - HW 장치를 제어할 수 있는 프로그램

    - Driver 개발 완료 후, 배포 시 Kernel에 포함시켜 Kernel 전체 Build 후 배포

 

--> Linux에서는 Device Driver를 Kernel Module 형태로 개발할 수 있다.

 


현재 시스템에 설치된 커널에 대한 디렉토리를 살펴보면, /usr/src로 심볼릭 링크가 되어있다.

이유는, 커널 소스 코드와 커널 헤더 파일의 경로를 일일이 수정하지 않기 위함이다.

예를 들어, 시스템에 두 가지 버전의 커널이 설치되어 있다고 가정해보자.
 - 5.4.0-26-generic
 - 5.8.0-50-generic

이 경우, 커널 소스 코드와 헤더 파일은 다음과 같은 경로에 위치할 것이다.
 - /usr/src/linux-headers-5.4.0-26-generic
 - /usr/src/linux-headers-5.8.0-50-generic

커널 모듈을 개발하거나 외부 드라이버를 빌드할 때, 커널 헤더 파일에 대한 경로를 지정해야 한다.
만약 커널 버전이 변경되면, 헤더 파일 경로도 다음과 같이 수동으로 수정해야 한다.
- KERNEL_HEADERS_5_4 := /usr/src/linux-headers-5.4.0-26-generic
- KERNEL_HEADERS_5_8 := /usr/src/linux-headers-5.8.0-50-generic

이렇게 되면 커널 버전이 바뀔 때마다 경로를 수동으로 수정해야 하는 번거로움이 생기기 때문에,
리눅스는 각 커널 버전별로 '/lib/modules/<커널 버전>/build' 심볼릭 링크를 생성한다.

이렇게 되면, 현재 실행 중인 커널 버전에 대한 헤더 파일 경로를 다음과 같이 지정할 수 있다.
KERNEL_HEADERS := /lib/modules/$(shell uname -r)/build

 


모듈 관련 명령어

# Module에 대한 정보 확인
$ modinfo [module]

# 적재된 kernel Module 확인
$ lsmod

 


Linux 장치 제어 방법

Linux에서 H/W 장치를 제어하려면, 해당 장치에 관한 Device File이 있어야 한다

Device File을 Device Node라고 하며, app 개발자는 syscall을 이용해 장치를 제어할 수 있다.

    - open, read, write, close

 

Device Driver 종류

1. chrdev : 캐릭터 디바이스 드라이버

    - Byte 단위로 값 전달

    - 일반적인 임베디드 장치에 사용

2. blkdev : 블록 디바이스 드라이버

    - Kb 이상의 '블록' 단위로 값 전달

    - Disk 장치에 사용되는 디바이스 드라이버

3. netdev : 네트워크 디바이스 드라이버

    - Socket을 열고 ioctl이라는 System call로 장치를 제어

 

/dev 디렉토리

linux에서 Device File을 위한 디렉토리

실제 장치 or 가상 장치일 수 있다

$ ls -al /dev

왼쪽 - c, b : Device File을 의미

오른쪽 - major / minor number를 의미  

Major Number (주번호)   
    - 디바이스 종류를 나타냄   
    - 같은 기능을 하는 디바이스가 여러 개 있다면, 같은 주번호를 가짐

Minor Number (부번호)   
    - 같은 종류 device 에서 구분 용도   
    - 개발자 마음대로 의미 부여 가능   
    - blkdev 에서는 파티션 번호로 사용   
    - node 이름에서 숫자가 붙은 경우 부번호를 의미

netdev는 노드를 사용하지 않음

Device File 생성

# mknod : 노드 파일을 만드는 유틸리티
#         일반적으로 /dev/에 생성해서 관리
# 사용법
$ sudo mknod [filename] [filetype] [majorN] [minorN]

# 예시
$ sudo mknod /dev/nobrand c 100 0
# --> nobrand라는 이름의 캐릭터 디바이스 주번호 100, 부번호 0 생성

# 권한 설정
$ sudo chmod 666 /dev/nobrand

 


ioctl

input output control을 줄임 / 하드웨어를 제어하기 위한 함수

<sys/ioctl.h> 필요

int ioctl(int fd, unsigned long request, ...);

# ex) ioctl(fd, _IO(0, 3), 0);

- fd : open() 함수로부터 얻은 file descriptor

- request : 사용자 정의 제어 코드

- . . . : 추가 인수가 사용되지 않으므로, 0이 전달; 일반적으로 필요한 추가 데이터를 포인터 타입으로 전달하는데 쓰임

 

Request 추가 설명

request : cmd 인자를 의미

_IO(type, nr)
: 간단한 명령 코드 생성
_IOR(type, nr, data_type) : 읽기 동작이 있는 명령 코드 생성
_IOW(type, nr, data_type) : 쓰기 동작이 있는 명령 코드 생성
_IOWR(type, nr, data_type) : 읽기 및 쓰기 동작이 있는 명령 코드 생성

type: 매직 번호로, 고유한 값을 사용하여 드라이버 간에 구별되는 고유한 값
nr: 명령 번호로, 드라이버 내에서 각 명령을 구별하는 숫자
data_type: 데이터 타입으로, 추가 인수로 전달되는 데이터의 타입, 커널이 사용자 공간과 커널 공간 사이에서 데이터를 안전하게 전송하는 데 필요한 크기를 계산하는 데 사용

 

cmd 구성

 - 약속된 cmd 변수의 비트 단위 Format (32 bit)

Direction : R / W
Size : 데이터 크기
Type : 매직넘버
Number : 구분 번호

 

 

 

 

사용 방법

1. app.c에 ioctl 정의

2. 위와 같이 device driver의 fops에 함수 정의

3. Driver insmod 후 app 실행 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

더보기

리눅스 임베디드 개발자의 이해

 

리눅스 임베디드 개발자 : 리눅스를 사용하는 업체에서 필요로 한다

 

리눅스가 필요한 회사?

-> AP SoC 업체 (삼성 S Lsi)

 

AP ? : Application Processor ( 앱이 동작하는 고성능 CPU )

SoC ? : System on Chip ( 내부에 다양한 기능이 있는 칩 ) 

 

대표적 사례

https://www.samsung-dsrecruit.com/recruits/division_intro/detail/slsi.php

 

SoC 업체에 취업을 한다 -> 임베디드 리눅스 개발을 한다

 -> SoC (칩) 안에 많은 전자 부품이 들어간다

 -> 부품들이 원활하게 동작할 수 있도록 디바이스 드라이버를 개발하면 된다.

 

SoC : 자체적으로 하드웨어도 만들고, 하드웨어가 동작하는 소프트웨어도 만드는 업체

 -> 할 일 : 리눅스 코드, 드라이버 코드, 샘플 코드를 제작하게 된다.

 

--

 

SoC 업체에서 개발한 칩을 갖고 개발을 시작하는 업체?

-> BSP 업체 : Board Support Package 개발 회사 (임베디드 계의 SI 업체)

 

SI 업체 : 시스템 통합 업무를 담당하는 업체

SoC 부품회사들의 부품을 가져와 보드에 장착해서 제대로 동작할 수 있는 시스템을 구축하는 업무를 한다.

-> BSP 업체는 SoC가 포함된 보드를 제작하는 업체

-> 야근이 많다, 중소/중견기업이 많다 정도