DEVELOP

임베디드 시스템 소프트웨어 과목의 강의영상과 강의자료를 바탕으로 작성한 학습용 게시글입니다.


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이 아닌 값을 리턴한다. 

if (printk_ratelimit())
printk(KERN_NOTICE "thr printer is still on fire\n");

 

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();
}
 
  1. 전처리
  2. 컴파일 (C언어 -> 어셈블리어)
  3. 어셈블리어 -> 기계어 -> 바이너리 파일
  4. .o파일 생성 (객체파일 생성)
  5. 링킹 / 합쳐줘야 하는 오브젝트 파일 합쳐 줌 (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의 역할이 종료 
profile

DEVELOP

@JUNGY00N