왜 이제서야 이 문서를 작성하게 되었는지 페이지 이름을 정하면서 깨달았다. 한편으로는 이제야 진정으로 리눅스에 한발 더 다가섰다는 생각도 든다.
여기서는 리눅스 커널에 대한 모든 것을 다룬다. 아마도 내가 새롭게 알게된 것들을 위주로 작성할 것이다.

리눅스 커널 디렉토리 및 소스

커널 버전이 높아질 수록 커널은 복잡해지고, 더 방대해졌다. 주요 디렉토리를 살펴보자.

디렉토리 이름 설명
최상위 디렉토리(/) 리눅스 커널의 top 디렉토리로 리눅스 커널을 빌드하기 위한 Makefile 을 가지고 있다. 따라서 모든 리눅스 빌드 동작은 이 디렉토리에서 시작된다
include 리눅스 커널에서 사용되는 일반적인 헤더 파일을 정의한 디렉토리다. 타겟 CPU 및 보드에 대한 헤더 파일은 arch 디렉토리의 include 디렉토리에 구현된다
init 리눅스 커널의 일반적인 초기화 절차를 관리하는 main.c 파일을 가지는 디렉토리다. main.c 파일에는 start_kernel() 이라는 함수가 있는데 이 함수에서 리눅스커널의 초기화 절차를 일괄적으로 관리하며, 커널 초기화가 완료된 후에는 init 프로세스를 실행하고, 디바이스 드라이버 초기화를 마무리한 다음에 루트 파일시스템을 마운트하고 application 을 실행한다. start_kernel() 함수는 arch/arm/kernel 디렉토리의 어셈블리 소스 head.S 및 head-common.S 에서 커널 초기화를 위한 시스템 설정 후 호출된다
kernel 리눅스 커널의 주 처리 루틴을 구현하는 디렉토리다. 앞에서 설명된 스케줄러, 프로세스 등 커널의 주요 기능을 구현하고 있으며, 예외처리 및 인터럽트 처리, 주기적인 타이머 인터럽트를 처리하는 소스 등이 구현된다. 이 디렉토리에 구현되 소스는 모두 프로세서와 무관하게 리눅스 커널에서 동작한다. 프로세스 관련 로우 레벨 소스는 arch/arm/kernel 디렉토리에 구현된다
mm 리눅스 커널의 메모리 관리 기능이 구현되어 있다. 가상 주소 관리, 페이지 관리, 프로세스의 메모리 관리 등을 포함한다. 이 디렉토리 역시 프로세스와 무관한 메모리 관리 루틴이 구현된다. 프로세서의 메모리 시스템과 MMU 나 캐시 등 로우 레벨 제어 동작은 arch/arm/mm 디렉토리에 프로세서별로 구현되어 있다
lib 리눅스 커널에서 사용되는 라이브러리가 구현되어 있다. 이 디렉토리에 구현된 라이브러리는 C 언어로 구현된다. 고속처리를 위해 어셈블리어로 구현된 라이브러리는 arch/arm/lib 디렉토리에 있다
arch 아키텍처, 즉 프로세서, 칩셋 및 타겟보드에 관련된 소스가 들어있다. 이 디렉토리는 CPU 별로 디렉토리를 가지고 있으며, ARM 프로세서 관련 소스는 arm 디렉토리에 구현된다. arm 디렉토리는 커널의 로우 레벨 동작을 처리하기 위한 kernel 디렉토리, 로우 레벨에서 메모리를 관리하는 mm 디렉토리, 어셈블리어로 커널 라이브러리를 구현한 lib 디렉토리가 있다. 또한 칩셋 또는 타겟 보드 소스를 구현하는 platform 디렉토리와 머신 디렉토리를 포함한다. 리눅스 커널의 시작은 kernel 디렉토리의 head.S 어셈블리 소스에서 시작된다
fs 가상 파일시스템을 사용하기 위한 각종 시스템 콜을 구현하고 있다. ext2, ext3 등 다양한 파일시스템을 구현한 디렉토리로 구성된다
ipc 시스템 V IPC, 즉 세마포어, 메시지 큐 및 공유 메모리 기능이 구현되어 있다
block 블록 디바이스의 관리를 위한 소스가 구현되어 있다. 블록 디바이스 드라이버는 drivers/block 디렉토리에 구현된다
net 리눅스 네트워크 시스템이 구현되어 있다. 소켓 관리에서부터 시작하여 TCP/IP 등 다양한 네트워크 프로토콜이 구현되어 있다
drivers 리눅스의 디바이스 드라이버가 구현되어 있다. 각 드라이버는 별도의 소스로 구성된다
sound 리눅스의 사운드 드라이버가 구현되어 있다. 사운드 드라이버는 ALSA 드라이버를 기반으로 하고 있으며, 칩의 사운드 인터페이스 및 코덱 드라이버가 구현되어 있다
security 리눅스 커널에서 필요한 보안 기능이 구현되어 있다
usr RAM 디스크를 사용하는 경우에 이미지를 두는 곳이다
crypto 리눅스의 크립토 알고리즘을 구현한 디렉토리다
samples 리눅스 커널에서 사용되는 샘플 소스를 구현한 디렉토리다. 하드웨어 브레이크 포인트 설정 및 커널 코드 트레이스 등 다양한 샘플 코드를 구현한다
scripts 리눅스 커널 빌드에 사용되는 다양한 스크립트를 가지는 디렉토리다. 주로 호스트에서 커널 빌드에 사용되며 커널 소스는 아니다
Documents 리눅스 커널 및 디바이스 드라이버에 대한 많은 자료를 가지고 있다

새로운 타겟 보드에 커널을 포팅하는 경우, 대부분 CPU, SOC 및 타겟 보드와 관련된 arch 디렉토리, 각종 디바이스 드라이버를 구현하는 driver 디렉토리, 그리고 사운드 드라이버를 위한 sound 디렉토리가 주요 편집 대상이 된다.

특히 그중에서도 arch 디렉토리는 새로운 타겟을 추가할 때 매우 중요하다. 아래 표는 이에 대한 설명이다.

디렉토리 또는 파일 이름 설명
Kconfig ARM 프로세서 관련 리눅스 커널 설정에 사용되는 파일로, 리눅스 커널의 다양한 기능을 설정하는 데 사용된다
Makefile ARM 프로세서 관련 소스를 빌드하는데 사용되는 옵션 및 디렉토리를 구현한 파일
include ARM 프로세서 관련 헤더 파일을 정의한 디렉토리
boot 리눅스 커널의 부트 스트랩 코드를 구현한 디렉토리. 압축된 커널 이미지인 zImage 를 사용하는 경우 리눅스 커널이 압축을 해제하고 실행시키는 부트스트랩 코드가 구현되어 있다. 또한 리눅스 빌드가 완료된 후 바이너리 image 와 압축된 커널 이미지 zImage 가 이 디렉토리에 저장된다
kernel 리눅스 커널의 로우 레벨 동작과 예외 처리 및 인터럽트 처리를 담당하는 소스가 구현된다. 이 디렉토리에는 리눅스 커널의 메모리 배치를 관리 담당하는 링커 스크립트 파일 vmlinux.lds 와 커널의 시작 파일인 head.S 도 있다
mm 리눅스 커널의 로우 레벨 메모리 관리 기능을 구현한 디렉토리. MMU 및 TLB 관리, 메모리 매핑, 캐시 제어 등의 동작을 구현하고 있으며, 프로세서별로 별도의 파일로 구성된다
lib 커널에서 사용하는 라이브러리를 ARM 프로세서의 어셈블리 명령을 이용하여 구현한 디렉토리
tools 리눅스 커널 빌드에 사용되는 상수를 생성하는 디렉토리. 이 디렉토리의 mach-types 파일은 리눅스 커널에서 타겟 머신을 관리하기 위해서 사용되는 파일로, 리눅스 커널에서 지원되는 모든 타겟 머신에 대한 이름과 번호를 가진다
plat-samsung 삼성 칩셋 관련 공통적인 소스를 구현한 디렉토리. 칩셋의 로우 레벨 제어 및 플랫폼 디바이스를 정의한다
mach-exynos 리눅스에서 머신(machine)은 SOC 또는 타겟보드를 칭한다. 이 디렉토리에는 삼성의 Exynos 칩셋 기반 소스를 구현하고 있으며, 이 책에서 사용되는 Exynos 4412 관련 소스와 이 칩을 사용한 타깃 보드 소스도 이 디렉토리에 구현된다
configs 커널의 디폴트 설정값을 가지고 있는 디렉토리
기타 플로팅 포인트를 구현하는 nwfpe, vfp 등의 소스를 가지고 있다

커널 옵션 설명

.config 파일을 보자.

CONFIG_64BIT=y
# CONFIG_X86_32 is not set
CONFIG_X86_64=y
CONFIG_X86=y
CONFIG_INSTRUCTION_DECODER=y
CONFIG_OUTPUT_FORMAT="elf64-x86-64"
CONFIG_ARCH_DEFCONFIG="arch/x86/configs/x86_64_defconfig"    // 문자열로 항목이 지정
CONFIG_GENERIC_CMOS_UPDATE=y
CONFIG_BASE_SMALL=0                // 상수 0 으로 지정

위와 같이 커널 옵션은 커널 빌드뿐만 아니라 커널 동작에 필요한 다양한 방법을 문자열이나 상수값으로 설정한다.

.config 파일은 커널 소스 디렉토리 내의 모든 임시 파일 및 오브젝트 파일을 제거하기 위해 사용되는 make mrproer 또는 make distclean 에 의해서 삭제된다.

.config 파일은 커널을 빌드하기 위한 Makefile 또는 커널 설정을 위한 Kconfig 파일에서 사용된다.
그리고 커널을 빌드하기에 앞서 <include/generated/autoconf.h> 파일이 생성된다. 이 파일은 커널을 빌드하는 동안에 C 또는 어셈블리 소스에서 사용되는 커널 옵션을 가지며 .config 에 설정된 옵션을 C 선행자 #define 으로 선언한다.

커널 소스의 대부분 디렉토리에는 커널 설정을 위한 특수 파일인 Kconfig 가 들어있다. 이 파일은 다양한 명령과 옵션정보를 지정하여 커널을 설정하는데 사용된다.
Kconfig 파일에서 사용되는 명령은 다음과 같다.

명령 설명
menu/endmenu menu 에서 endmenu 까지 커널 설정을 한다. menu 에 여러 항목을 묶어서 함께 관리한다
config 메뉴 항목 및 심볼을 선언한다
choice/endchoice choice 에서 endchoice 까지 하나의 항목을 선택한다
comment 메뉴에 대한 설명을 출력한다
if/endif 조건에 따라 커널을 설정한다
source 다른 설정 파일을 포함한다

또한 Kconfig 파일에서 각 메뉴 항목의 하위 옵션을 보면 bool, select 등 다양한 옵션을 사용하는 것을 볼 수 있다.

항목 옵션 설명
bool 항목 옵션을 Y 또는 N 2가지 중 하나 선택
tristate 항목 옵션을 Y, N 또는 M 3가지 중 하나 선택
string 항목 옵션으로 문자열 사용
integer 항목 옵션으로 상수 사용
prompt 문자열을 디스플레이
default 항목의 디폴트 값 지정
depend on 해당하는 조건이 성립될 때 항목 옵션 지정 가능
requires 해당하는 조건이 성립될 때 항목 옵션 지정 가능
select 항목 옵션 설정 시 사용 가능하도록 선택
help 항목의 도움말 메시지 지정

압축되지 않은 순수 커널 바이너리는 아래의 명령으로 빌드할 수 있다.

#make Image

빌드가 완료된 후에는 arch/arm/boot 아래에 Image 라는 파일이 생성된다. 리눅스의 부트스트랩 코드를 포함한 압축된 커널 이미지는 아래와 같이 빌드할 수 있다.

#make zImage
파일 내용
linux/vmlinux ELF 형식의 커널 이미지. 오브젝트들은 섹션으로 분리되어 있고, 프로그램의 링크, 로딩 및 실행에 대한 정보를 가지고 있다. ELF 형식의 파일은 바이너리 파일로의 형식 변환 없이 타겟 보드의 메모리에 탑재될 수 없다
linux/System.map 커널의 심볼 및 주소 정보. 커널에서 사용되는 함수, 변수 등의 심볼과 각 심볼들의 메모리 내 위치 정보를 가지고 있다. 향후 커널을 디버깅할 때 유용한 정보를 제공한다
linux/arch/arm/boot/Image 압축되지 않은 커널 이미지. vmlinux 를 바이너리 형식으로 만든 파일이다
linux/arch/arm/boot/zImage 부트스트랩 코드를 포함하는 압축된 커널이미지. 순수 커널 이미지를 압축하고 커널에서 제공하는 부트 스트랩 코드와 함께 생성된 새로운 커널 바이너리 파일이다
linux/include/generated/autoconf.h 커널 설정을 C 선행 지시어로 가지는 파일

빌드된 커널은 백업이나 기타 필요한 경우에 오브젝트 파일을 비롯한 커널 빌드나 기타 작업을 통해서 생성된 파일을 제거할 수 있다. 커널의 빌드된 타겟을 제거하기 위한 옵션으로 clean, mrproper, distclean 등이 있다.

먼저 clean 옵션은 빌드 동안에 생성된 오브젝트 파일 및 임시 파일을 제거하고 설정 파일이나 모듈 지원에 필요한 파일은 삭제하지 않는다. 하지만 mrproper 옵션은 커널 설정을 비롯한 모든 생성 파일 및 백업 파일을 제거하고, distclean 은 편집기 백업 파일 및 패치 파일까지 제거한다.

커널 소스 트리에 있는 모듈 소스는 make modules 명령으로 빌드되고, 빌드된 모듈은 make modules_install 명령으로 루트파일 시스템의 lib 디렉토리에 설치된다.

이때 설치하는 루트 파일시스템을 별도로 지정하지 않으면 호스트의 루트 디렉토리의 lib 디렉토리에 모듈 오브젝트 파일이 설치된다. 하지만 임베디드 시스템 용으로 모듈을 빌드하는 경우에는 설치될 디렉토리가 호스트 루트가 아닌 생성하려는 루트 파일시스템이 되어야 한다.

커널의 최상위 디렉토리의 Makefile 에서는 설치되는 모듈이 위치를 지정하는 환경변수 MODLIB 를 사용하며, 이 환경 변수는 다음과 같이 선언되어 있다.

#
# INSTALL_MOD_PATH specifies a prefix to MODLIB for module directory
# relocations required by build roots.  This is not defined in the
# makefile but the argument can be passed to make if needed.
#
 
MODLIB  = $(INSTALL_MOD_PATH)/lib/modules/$(KERNELRELEASE)
export MODLIB

환경변수 INSTALL_MOD_PATH 에 아무런 값이 지정되지 않으면, /lib/modules 디렉토리에 모듈 오브젝트가 설치된다. 따라서 호스트가 루트가 아닌 다른 위치에 모듈을 설치하는 경우에는 INSTALL_MOD_PATH 를 지정하여 변경하면 된다.

네트워크 드라이버 검색하기

요즘은 거의 그런 일이 거의 없겠지만, 초창기만 해도 리눅스에서 하드웨어를 인식하지 못해서 며칠 동안 삽질을 했었다. 커널에서 기본적으로 지원해준다면, 얘기는 간단해지겠지만, 그렇지 않다면 고생을 감수해야 할 것이다. 여기서 설명할 것은 컴퓨터에 연결되어 있는 하드웨어의 정보를 알아내서 이에 맞는 드라이버를 커널 상에서 검색하는 방법이다. 여러모로 유용할 것이다. 참고로 여기서는 랜카드를 예로 들어 설명할 것이다. 리눅스 커널은 2.6.28 을 기준으로 한다.

#ls /sys/class/net/
eth0 eth1 lo

sys 디렉토리를 하드웨어의 정보를 보거나 직접적으로 컨트롤할 수 있다. 현재 시스템에서는 네트워크 인터페이스가 2 개 인식되었다.

#basename 'readlink /sys/class/net/eth0/device/driver/module'
e1000

현재 eth0 에 할당된 네트워크 인터페이스가 사용하고 있는 모듈을 찾을 수 있다.

#readlink /sys/class/net/eth0/device/module
../../../../module/e1000

모듈 이름을 알았다면, 이제 커널에서 해당 모듈이 있는지 찾아보자!

#cd /usr/src/linux
#find -type f -name Makefile | xargs grep e1000
./drivers/net/Makefile:obj-$(CONFIG_E1000)+=e1000/
...

좀더 간단한 방법도 있다.

#make menuconfig   // 메뉴 선택 창에서 슬래시(/)를 입력하면, 검색창이 뜬다. 

USB 드라이버 검색하기

현재 USB 포트에 연결된 기기들을 확인하기 위해, 다음과 같이 실행한다.

#/usr/sbin/lsusb
Bus 002 Device 003:ID 045e:0023 Microsoft Corp. Trackball Optical
Bus 002 Device 001:ID 0000:0000    // USB 호스트 컨트롤러 USB Host Controller 를 의미함
Bus 005 Device 003:ID 0409:0058 NEC Corp. HighSpeed Hub
Bus 005 Device 001:ID 0000:0000   // USB 호스트 컨트롤러 USB Host Controller 를 의미함
...

장치 드라이버 검색하기

  1. sysfs 파일 시스템에서 장치 이름을 찾는다. 네트워크 장치들은 /sys/class/net 디렉토리에, 그리고 tty 장치들은 /sys/class/tty 디렉토리에서 찾을 수 있다. 나머지 장치들은 /sys/class 디렉토리에 장치 종류별로 하위 디렉토리가 구성되어 있다.
  2. sysfs 파일 시스템에서 장치를 컨트롤하는 모듈 이름을 찾는다. 이 모듈은 /sys/class/장치별 서브 디렉토리명/장치명/device/driver/module 에서 찾을 수 있으며, basename 과 readlink 명령어를 사용하여 출력할 수 있다.
$basename 'readlink /sys/class/class_name/device_name/device/driver/module'
  1. find 과 grep 명령어를 사용하여 해당 모듈명 앞에 CONFIG_(예: CONFIG_USB) 포함된 커널의 Makefile 을 찾는다.
$find -type f -name Makefile | xargs grep module_name
  1. 커널 설정 명령(make menuconfig/xconfig/gconfig)을 통해 모듈의 위치를 검색하고 사용할 수 있도록 설정한다.

커널 로그 레벨

리눅스 커널에서는 중요도에 따라 나타내는 로그의 정도를 설정할 수 있다.

로그 레벨 명령어 로그 레벨 의미
KERN_EMERG 0 시스템이 동작을 멈춤
KERN_ALERT 1 항상 출력
KERN_CRIT 2 치명적 오류 발생 정보
KERN_ERR 3 오류 발생 정보
KERN_WARNING 4 경고 정보
KERN_NOTICE 5 정상적인 정보
KERN_INFO 6 시스템 정보
KERN_DEBUG 7 디버깅 정보

현재 커널에 설정된 로그 레벨은 시스템 부팅 후에 다음과 같이 확인할 수 있다.

#cat /proc/sys/kernel/printk
7	4	1	7

맨 앞의 (7) 는 현재 로그 레벨, (4) 는 기본 로그 레벨, (1) 은 최소 로그 레벨, 그리고 맨 뒤의 (7)은 부팅 시 로그 레벨을 지정한다.
아래 표를 참고하자.

로그 레벨 기능 의미
7 현재 로그 레벨 이 레벨보다 높은 메시지(숫자가 작은 수)만 출력
4 기본 로그 레벨 별도로 printk 에 로그 레벨을 지정하지 않은 경우의 로그 레벨
1 최소 로그 레벨 부여 가능한 최소 로그 레벨
7 부팅 시 로그 레벨 부팅 시 출력 로그 레벨

printk 의 로그 버퍼 정보는 시스템 부팅 후에 /proc/kmsg 를 보면 알 수 있다. 또한 리눅스 명령어 dmesg 도 로그 버퍼의 내용을 보여준다.

현재의 로그 레벨을 바꾸고 싶다면, 아래와 같이 실행하면 된다.

echo 4 4 1 7 > /proc/sys/kernel/printk

부팅 시에 로그레벨을 변경하고 싶다면, 크게 2가지 방법이 있다.

  1. 첫번째는 커널 컴파일 시에 'make menuconfig→ Kernel Hacking → Default message log level' 항목의 값을 변경하는 것이다.
  2. 부팅 파라미터에 loglevel 을 설정하는 것이다.

grub 를 예로들어 설명하겠다. /etc/default/grub 파일을 아래와 같이 수정한다.

GRUB_DEFAULT=0
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian`
GRUB_CMDLINE_LINUX_DEFAULT="loglevel=7"            // 수정
GRUB_CMDLINE_LINUX=""

부팅 이후, 제대로 추가되었는지 확인한다.

#cat /proc/cmdline 
BOOT_IMAGE=/boot/vmlinuz-3.2.0-4-amd64 root=UUID=5d534fe6-b97b-4643-b2d4-a5dd25142b7c ro loglevel=7
#cat /proc/sys/kernel/printk
7	4	1	7

다음은 로그버퍼에 바로 로그를 남기는 방법이다.

#echo "Hello Kernel-World" > /dev/kmsg

아래는 로그 레벨을 지정하여 남기는 방법이다.

#echo "<2>Writing critical printk messages from userspace" >/dev/kmsg

이후 dmesg 명령어를 사용하여 위의 로그가 출력된 것을 확인할 수 있다.

  • computer/os/리눅스_커널_핵심가이드.txt
  • Last modified: 4 years ago
  • by likewind