임베디드 시스템 소프트웨어 과목의 강의영상과 강의자료를 바탕으로 작성한 학습용 게시글입니다.
Background (배경)
Operating Systems vs. Kernel ( OS와 커널의 비교 )
- 보통은 OS = 커널이라고 생각하기 쉬움
- OS는 리눅스 커널 기반의 소프트에어 집합체로 만들어진다.
- OS는 Linux Kernel (OS의 핵심) , GCC 등의 GNU 툴과 라이브러리, GUI를 통해 시스템을 관리할 수 있는 윈도우 시스템, 데스크톱 환경 등을 포함한다.
- 따라서, OS 안에 커널이 포함된다고 할 수 있다.
- 일반적으로, 디바이스 드라이버는 커널 안에 포함되지 않고, OS 안에 포함된다.
- 배포 시 대표적인 디바이스 드라이버를 포함한다.
타입의 종류에 따른 커널의 분류
- Monolithic Kernel (모노리틱 커널)
- Micro Kernel (마이크로 커널)
Monolithic Kernel (모노리틱 커널)
: 하나의 큰 바이너리 이미지로 생성된 커널
- CPU 스케줄링 관련, 가상메모리 관리 관련, 파일시스템 관련 기능들이 모두 포함
- 포인터 변수 또는 함수 호출로 내부의 자료구조와 루틴들에 접근을 쉽게 할 수 있다.
- 장점
: 고성능 - 단점
: 유연성 떨어짐 (수정이 어려움)
- 일부만 수정하더라도 커널 전체 컴파일 후 새로운 하나의 이미지 파일을 생성해야 함
- 일부 수정 시 다른 곳에 영향을 줄 수 있다.
- 커널 공간에서 돌아가는 이미지 크기가 매우 커질 수 있다.
Micro Kernel (마이크로 커널)
: 커널의 기능들을 잘개 쪼개어 별도의 유닛으로 관리하는 커널
- 별도의 바이너리 이미지로 구성 (다른 주소값)
- 서로 간 의존성 x, 다른 기능이나 데이터 필요 시 서로 통신을 통해 요청하고 받음
- 장점
: 유연성이 좋다.
- 기능들이 서로 독립적이기 때문에, 통신 프로토콜만 지켜준다면 기능을 수정하더라도 서로 영향을 받지 않는다.
: 네트워크 링크를 통해 시스템을 분산할 수 있다.
- 하지만, 잘 하지 않는다. - 단점
: 성능이 좋지 않다.
- 모든 것을 통신으로 해결해야 하므로, 통신 오버헤드가 발생하여 성능이 낮다.
- 현존하는 대부분의 os는 Monolithic Kernel로 구현되어 있다.
- 향후에는 어떻게 os의 형태가 바뀔 지 모른다.
Kernel Modulex (커널 모듈)
: 런타임에 커널에 추가할 수 있는 코드 조각
- 커널 모듈에 포함되지 않고, 별도의 이미지로 생성될 수 있다.
- 커널의 이미지가 작게 만들어질 수 있다.
- 커널 모듈 단위로만 제거, 수정하므로 매번 재생성하고 재부팅할 필요 없이, 새롭게 시스템 안에 넣어줄 수 있다.
커널 모듈의 사용
- 디바이스 드라이버
- netfilter
: 네트워크 트래픽 관찰하면서 보안 기능 구현하고 싶을 때 커널 모듈로 구현하여 커널 안에 동적으로 넣어줄 수 있다. - FUSE (파일 시스템)
- 시스템 내 통신 시 copy 발생하는데, 이 data copy 횟수를 줄여줄 때
Kernel Module Programming (커널 모듈 프로그래밍 )
커널 모듈의 구성
- main 함수가 없다.
- Entry 함수와 exit 함수가 있다.
- entry 함수는 삽입될 때 저절로 호출되는 함수이다.
- exit 함수는 제거될 때 호출되는 함수이다.
- 프레임워크에 제공되어야 하는 함수가 정의되어 있다.
커널 모듈의 특징
- 함수들만 쭉 정의해 놓은 것이기 때문에,
필요할 때마다 그때 그때 os가 불러와 사용한다. - 커널 자체의 생명력은 없다.
- 커널 공간에서 동작한다. (주의깊게 설계하고 구현해야 함)
Entry 함수와 Exit 함수
- entry 함수와 exit 함수의 이름은 마음대로 정의할 수 있다.
- 해당 함수가 entry 함수 / exit 함수임을 매크로 함수를 통해 알려주면 된다.
- > module_init / module_exit
module_init(my_init); module_exit(my_exit); |
static int __init my_init(void) { ... }
- 성공 시 0 리턴
- static
- 같은 파일 내에서만 scope 가짐
- 다른 파일에서 같은 이름을 가진 함수가 있어도 된다. - __init
- init할 때만 불려지는 함수임을 명시해주는 키워드
static void __exit my_exit(void) { ... }
- __exit
- exit할 때만 불려지는 함수임을 명시해주는 키워드 - 커널 이미지 안에 포함되도록 옵션 설정이 가능하다.
Compling Modules ( 모듈 컴파일 )
▼ Makefile 예시
obj-m := mymod.o
KERNELDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(PWD) clean
|
- 커널 모듈로서 생성될 파일은 mymod.o 이다.
- 컴파일 후에는 mymod.c 로 생성된다.
- 최종적으로 생성된 모듈 파일은 mymod.ko
commands (커맨드 명령어)
- 루트 권한이 있어야 사용이 가능하다.
insmod
: 커널 모듈을 삽입하기 위한 명령어
modprobe
: 의존성 갖는 커널 모듈들을 함께 올려주는 명령어
- os 가 의존성 관계를 파악하고 있어야하고, 해당 정보는 modules.dep 에 저장되어 있다.
depmod
: modules.dep과 map 파일을 생성하는 명령어
ismod
: 현재 로드 가능한 모듈들의 리스트를 보여주는 명령어
rmmod
: 커널로부터 모듈을 해제하는 명령어
modinfo
: 커널 모듈에 관한 정보를 보여주는 명령어
- 제작자, 설명, 라이센스 등
- 해당 정보들을 매크로 함수를 통해 소스 코드에 명시해야만 정보들을 볼 수 있다.
License
- 원하는 라이센스를 명시
- 커널 모듈들 간 의존성 때문에 중요한 문제가 된다.
Printing Messages (printk)
printk()
printk(KERN_NOTICE "Hello, World!\n");
- printf 와 사용방법은 유사하다.
- Log level (로그 레벨) 0~7
KERN_EMERG (0): for emergency messages (precede a crash)
KERN_ALERT: when requiring immediate action
KERN_CRIT: critical conditions (SW or HW failures)
KERN_ERR: error conditions (HW difficulties)
KERN_WARNING
KERN_NOTICE: normal situation but worthy to note
KERN_INFO: information messages
KERN_DEBUG (7): debugging messages - 어떠한 로그레벨을 쓰느냐에 따라 기능적으로 크게 달라지지는 않는다.
Console Loglevel
- 콘솔도 자체 loglevel을 가지고 있다.
- 콘솔의 loglevel보다 우선순위가 낮은 메시지가 있을 경우에 해당 메시지는 출력되지 않는다.
- printk를 넣었는데도 불구하고 화면에 출력이 안되는 경우는 콘솔의 loglevel이 메시지보다 높은 경우이다.
- 환경설정을 통해 콘솔의 우선순위를 낮춰주거나, 코드에서 사용한 printk의 우선순위를 높여줄 수 있다.
- /proc/sys/kernel/printk 에서 콘솔의 loglevel을 확인할 수 있다.
$ cat /proc/sys/kernel/printk 7 4 1 7 |
current consol level; default message level; mininum console level; default console level; |
- 위의 경우에는 콘솔의 loglevel이 낮은 우선순위로 되어 있기 때문에 모든 메시지가 화면에 다 보여질 것이다.
Logging Messages ( 메시지 Log )
- 메시지들은 콘솔 화면에 보이기도 하지만 출력한 내용을 저장하고 있다.
/proc/kmsg
: 첫번째로 저장되는 위치 - printk 할 때마다 loglevel과 관계없이 저장된다.- 컴퓨터가 꺼지면 없어지는 공간이다. - dmesg 커맨드 명령어 통해 볼 수 있다.
klogd 와 syslogd
: klogd와 syslodg가 협력해 로그메시지를 저장한다. - 컴퓨터의 꺼짐과 상관없이 저장된다. - klogd: kmesg에 있는 내용을 계속 읽어서 다른 곳에 전달- syslogd : 전달받은 정보를 /var/log/messages에 저장, 재부팅 시에도 남아있음 - 시스템 돌아가면서 남는 여러 로그들이 남아있다.- 설정에 따라 로그인 / 로그아웃 기록 등도 남아있어 해커들이 조용히 나갈때 삭제하기도 한다.
printing rate
- 프린트하는 rate를 조절한다. - printk는 유저 레벨에서 돌아가는 것이 아니고 커널 레벨에서 실행된다.- 독립적으로 실행되는 프로세스가 아닌 운영체제가 사용할 함수들을 개발해놓으면 운영체제가 그때 그때 필요한 것을 사용하다보니, 얼마나 자주, 언제 쓰일 지 예측이 불가하다.- 운영체제 안에서 실행하면서 print되는 메시지들이 많으면, 성능에 방해요소가 되며 콘솔 화면에 엄청난 메시지가 출력되는 경우도 있다. - printf를 쓸 때 처럼 가벼운 마음으로 printk를 사용하면 안된다. - 더 중요한 내용의 출력이 밀리는 경우도 있다.
printk_ratelimit()
: printk가 자주 찍히는 것을 방지한다. - 설정해 놓은 threshold 보다 초과하면 0을 리턴하고 너무 자주 화면에 출력하고 있음을 의미한다. - 그렇지 않으면 0이 아닌 값을 리턴한다.
Turning Printing On/Off
- 디버깅으로 printk를 사용 후 더 이상 필요 없다고 생각하여 소스코드에서 더 이상 동작을 안하도록 한다. - printf 사용시에도 동일하게 쓸 수 있다. - printk를 일일이 삭제하지 않고 쉽게 on/off 가능하다.
No Complication Error?
(Loadable Kernel Modules)
▼ 사용자 프로그램 에시
- 커널 모듈 x, 단순한 유저 프로그램임
- my_func 함수는 프로토타입만 함수 내 선언되어있고, 어떻게 구현되어있는지 모른다.
int my_func(void);
int main(void)
{
int a;
a = my_func();
}
|
- 전처리
- 컴파일 (C언어 -> 어셈블리어)
- 어셈블리어 -> 기계어 -> 바이너리 파일
- .o파일 생성 (객체파일 생성)
- 링킹 / 합쳐줘야 하는 오브젝트 파일 합쳐 줌 (excurable 파일)
- gcc로 컴파일했을 때 my_func가 define되어있지 않다는 오류 -> 링킹단계에서 발생
- my_func는 object파일 만들기 전까지는 unresolved 파일이다.
- gcc-c 로 컴파일 하면 unresolved 임을 기억한 채로 객체 파일까지만 만들게 된다. (->오류 발생x)
Dynamic Linking
- .ko 파일은 단순한 오브젝트 파일이다.
- unresolved symbol이 있을 수 있다.
- 위의 예시에서 my_func 함수나 prink도 unresolve로 남아있을 수 있다.
=> insmod라는 명령어로 컨널을 동적으로 삽입할 때 비로소 symbol resolve 작업이 이루어지게 된다.
Symbol Table
- 커널은 symbol table을 갖고 있다.
- 커널 안에서 제공해주는 함수들의 리스트를 저장해 놓는다.
- 어떤 함수, 어떤 전역변수가 몇번지 주소에 있다는 것을 저장해 놓는다.
- insmod를 해서 커널모듈을 커널 안에 삽입할 때마다 그 커널 모듈 안에 unresolve 되어있는 심볼들이 심볼 테이블 안에 있는지를 쭉 찾아낸다.
- 매칭이 되는 심볼이 있으면 unresolve되었던 심볼들이 몇번지의 함수/전역변수인지를 확인한다.
- 링킹할 때 unresolve된 심볼들에 대해 처리가 된다.
유의할 점
- .ko 파일이 만들어졌다고해서 완전한 것이 아니다. (insmod를 했을 때와 동일한 레벨이라고 생각하면 안된다.)
- unresolv된 파일이 있을 수 있고, insmod를 해서 커널 모듈을 커널에 삽입할 때 오류 메시지를 볼 수도 있다.
- insmod를 할 때 기본적으로 커널 모듈이 작성된 것이다.
( 하지만 여전히 버그가 있을 수는 있다.) - makefile이 성공적으로 끝난 것이 다가 아님을 유의해야 한다.
Exporting Symbols
( symbol table 안에 어떤 symbol이 있느냐)
- device driver를 만들 때 커널에서 제공해주는 많은 기능들을 가져다 쓸 수 밖에 없다.
- 커널이 제공해주는 함수(symbol table 안에 있는 함수)를 잘 활용해야 한다.
- 아무런 함수나 쓸 수 있는 것이 아니고, symbol table 안에 있는 함수, 전역 변수들만 사용이 가능하다.
- printk도 symbol table 안에 있다.
- symbol table을 잘 알고 활용해야만 device driver를 짤 때 수월하다.
Exported Kernel Symbols
- symbol table 안에 있는 것들은 기본적으로 운영체제가 exported
- 커널에서 이 symbol들은 공개해준 것이다.
- 이 symbol들은 /proc/kallsyms 에서 내용 확인이 가능하다.
- 수많은 함수들 중에서 내가 원하는 기능을 가지고 있는 함수가 무엇인지를 아는 것이 device driver를 작성하는 사람들에게 중요한 것이다.
- 커널도 심볼을 export하지만, 커널 모듈 안에서도 다른 커널 모듈을 위해서 심볼을 export할 수 있다.
- insmod를 할 때 symbol table안에 들어가게 된다.
- ex) usb 드라이버를 위한 컨트롤러가 먼저 insmod되고, 그 안 export한 커널 심볼들이 심볼 테이블에 등록되고 나서, usb 주변장치를 위한 드라이버가 올라와야 함수들이 symbol resolve가 되면서 이상없이 insmod가 된다.
EXPORT_SYMBOL()
: 개발한 변수나 함수를 외부로 공개하기 위한 매크로 함수
- () 안에 변수의 이름 / 커널함수의 이름을 작성한다.
- symbol table에 들어가고, 커널에서 호출이 가능하다.
- EXPORT_SYMBOL_GPL()
: symbol이 GPL 라이센스로 오픈되어 있는 것을 사용하려면 커널 모듈도 GPL 라이센스를 따라야될 수도 있다는 점을 유의할 것
Kernel Symbol Resolve
insmod
: 커널에 모듈을 적재하는 커맨드 명령어
- 커널 모듈을 컴파일하게 되면 .ko파일(오브젝트 파일)이 생성된다.
- insmod를 해서 커널 삽입을 하면 비로소 symbol resolve가 된다.
- sys_init_module
- 커널 모듈을 적재하기 위한 메모리 영역 할당
- .ko파일 안에 있는 instruction들을 메모리 영역 copy
- 커널 심볼 테이블 돌면서 unresolve 심볼을 resolve
- export된 심볼들을 커널 안 커널 테이블에 copy
- 커널 모듈 안에 있는 init 함수를 호출하면서 insmod의 역할이 종료
'LECTURE > [2023-1] 임베디드시스템소프트웨어' 카테고리의 다른 글
[임베디드시스템소프트웨어] 05. General Purpose I/O (0) | 2023.04.19 |
---|---|
[임베디드시스템소프트웨어] 04. Blocking I/O (0) | 2023.04.12 |
[임베디드시스템소프트웨어] 03. Basic Kernel Functions (0) | 2023.04.11 |
[임베디드시스템소프트웨어] 02. Character Device Drivers (캐릭터 디바이스 드라이버) (0) | 2023.04.10 |