본문바로가기

ARM Linux Kernel Makefile의 이해와 실행 순서 정리


v2.4.0-text? 커널에 있는 Makefile을 기초로 zImage가 어떻게 만들어지는지 정리했습니다. 그리고 실행되는 순서를 정리했습니다. 
$(TOPDIR)은 linux kernel source code가 위치한 root directory를 말한다. 보통 /usr/src/linux일 것이다. 
Makefile에서 사용되는 Flag 들은 $(TOPDIR)/arch/arm/Makefile을 참조한다. 


system.map
c0bee6d4 T wifi_set_platform_data
c0bee73c t lwifi_power_init
c0bee7c4 t wifi_power_probe
...
c0bf2548 t sdhci_drv_init
c0bf256c t sdhci_odin_driver_init
c0bf2578 t sdhci_odin_probe

1. Kernel compile 철차 
make mrproper 
make xconfig 
make dep 
make modules 
make modules_install 
make zImage 

2. Make 되는 순서 
$(TOPDIR)/vmlinux 
$(TOPDIR)/arch/arm/boot/compressed/piggy.o 
$(TOPDIR)/arch/arm/boot/compressed/head.o 
$(TOPDIR)/arch/arm/boot/compressed/misc.o 
$(TOPDIR)/arch/arm/boot/compressed/vmlinux 
$(TOPDIR)/arch/arm/boot/zImage 

3. $(TOPDIR)/vmlinux 
vmlinux는 압축되지 않는 Kernel core를 말한다. 이 file은 elf32 format executable 이다. 시작 점은 $(TOPDIR)/arch/arm/kernel/head-armv.S의 stext 또는 _stext가 된다. 

4. $(TOPDIR)/arch/arm/boot/compressed/piggy.o 
$(TOPDIR)/vmlinux를 objcopy를 사용해 binary로 만들고 gzip으로 압축한 후 다시 elf32 relocatable file(executable이 아니다)로 만들어 놓은 것이다. 

5. $(TOPDIR)/arch/arm/boot/compressed/vmlinux 
gzip을 풀기위한 함수들과 32bit startup code를 piggy.o와 묶어 준다. 이 놈들이 위치할 .text의 offset도 함께 준다. 
text offset은 0x00008000이고 부팅 시에 vmlinux는 반드시 해당 위치에 올려져야 한다. 
만들어진 vmlinux는 elf32 executable이다. 

6. $(TOPDIR)/arch/arm/boot/zImage 
$(TOPDIR)/arch/arm/boot/compressed/vmlinux를 objcopy를 사용해 binary로 만든다. 이 것이 최종의 커널 이미지가 된다.
 
7. SA-110 Footbridge System의 Memory Map 
Function Start Address End Address Size 
SDRAM 0000 0000h 0FFF FFFFh 256MB 
Reserved 1000 0000h 3FFF FFFFh - 
SDRAM array 0 mode register 4000 0000h 4000 3FFFh 16KB 
SDRAM array 1 mode register 4000 4000h 4000 7FFFh 16KB 
SDRAM array 2 mode register 4000 8000h 4000 BFFFh 16KB 
SDRAM array 3 mode register 4000 C000h 4000 FFFFh 16KB 
X-Bus XCS0 4001 0000h 4001 0FFFh 4KB 
X-Bus XCS1 4001 1000h 4001 1FFFh 4KB 
X-Bus XCS2 4001 2000h 4001 2FFFh 4KB 
X-Bus no CS 4001 3000h 4001 3FFFh 4KB 
Reserved 4001 4000h 40FF FFFFh - 
ROM 4100 0000h 41FF FFFFh 16MB 
CSR space 4200 0000h 420F FFFFh 1MB 
Reserved 4210 0000h 4FFF FFFFh - 
SA-110 cache flush 5000 0000h 50FF FFFFh 16MB 
Reserved 5100 0000h 77FF FFFFh - 
Outbound write flush 7800 0000h 78FF FFFFh 16MB 
PCI IACK/special space 7900 0000h 79FF FFFFh 16MB 
PCI type 1 configuration 7A00 0000h 7AFF FFFFh 16MB 
PCI type 0 configuration 7B00 0000h 7BFF FFFFh 16MB 
PCI I/O space 7C00 0000h 7C00 FFFFh 64KB 
Reserved 7C01 0000h 7FFF FFFFh - 
PCI memory space 8000 0000h FFFF FFFFh 2GB 

8. Footbridge System의 부팅 순서 
$(TOPDIR)/arch/arm/boot/compressed/head.S에 보면 kernel start address, decompress kernel start의 두 address가 사용된다. 이 두 값이 실제로 커널이 실행될 위치와 압축이 풀릴 위치에 해당하는 address를 나타낸다. 
kernel start address는 처음엔 head.S가 실행될 위치로 사용되고 이어 decompress kernel start에 커널이 압축 풀리고, 압축이 풀린 커널은 다시 kernel start address로 옮겨진 후 마지막으로 kernel start address에서 커널이 실행된다. 
이렇게 해야 메모리의 낭비를 막을 수 있기 때문에 그렇다. 실제로 head.S의 코드 중 커널 압축을 푸는 과정 까지의 코드는 압축이 풀린 후 다시 사용될 일이 전혀 없기 때문에 버려도 된다. 그리고 압축이 풀린 위치와 head.S의 시작 위치 사이는 적어도 1MB정도 떨어져 있으므로 압축 풀린 커널을 kernel start address로 옮기지 않고 그냥 실행하면 head.S에서 부터의 약 1MB를 사용하지 않는 꼴이 된다. 
Footbridge System의 경우 kernel start address는 0x8000, decompress kernel start는 대충 (kernel start address) + sizeof(zImage) + 64KB 정도가 된다. 
Assabet은 kernel start address가 0xc0008000이다. 
만들어진 zImage는 아래와 같이 구성되어있다. 
head.S 
misc.c 
piggy.o 
$(TOPDIR)/arch/arm/boot/compress/vmlinux가 만들어질 때 사용되는 LD scrip file의 내용을 살펴 보면 load_addr과 .text의 시작이 0x8000으로 명시되어있다. 즉 zImage는 0x8000에서 부터 시작되도록 만들어져 있으므로 부팅시에 반드시 이 위치에 올려져야 한다(이 값이 바로 kernel start address다). 
그리고 ROM에 올라갈 start-up code와 zImage를 합친 것은 다음과 같이 만들었다. 중간에 dummy를 넣은 것은 zImage의 시작 위치를 항상 같은 곳으로 고정하기 위한 것이다. dummy에는 그냥 0을 채워 넣었다. 
start-up code 시작=0, 크기=약 110000Bytes 
dummy
zImage 시작=120000, 크기=약 800KB 
일반 적인 Footbridge System에는 ROM이 있고 여기에 start-up code와 zImage가 들어가게 될 것이다. 이 ROM에 있는 start-up code는 시스템 초기화(SDRAM 초기화가 주된 목적일 것이다)를 하고 ROM의 어딘가에 있을 zImage를 SDRAM으 0x8000으로 복사해 놓고 커널 부팅에 필요한 parameter를 0x100에 설정해 놓은 다음 0x8000으로 jump하면 리눅스 부팅이 시작되게 된다. 
그러나 zImage가 어디에 얼마 만한 크기로 있을지 알기 힘들기 때문에 위와 같이 절대 위치에 zImage를 무조건 올려놓고 zImage를 복사할 땐 120000에서 부터 약 1MB를 복사하도록 했다. 이렇게 하면 zImage의 시작 부터 끝까지 충분히 커버 되므로 이상 없이 동작할 수 있을 것이다(물론 linking할 때 제대로 만들어 줄 수도 있지만 start-up code 자체가 elf executable을 추출해 만든 것이기 때문에 linking을 할 수 없었다). 
절차를 나타내면 아래와 같은 식이 된다. 
a. CPU Reset 
ROM의 시작 번지인 0x41000000에서 부터 실행된다(Footbridge인 21285가 알아서 0x00000000을 0x41000000으로 인식하게 해준다). 
Assabet이라면 angel을 이용해 zImage가 그대로 0xc0008000에 올려 지므로 e단계로 바로 가면 될 것이다. 
b. System Initialization 
start-up code는 SDRAM을 초기화해 SDRAM을 사용할 수 있는 상태로 만들어 놓는다. 
c. zImage Copying 
ROM의 시작인 0x41000000에서부터 120000에 위치한(0x4101D4C0가 된다) zImage를 0x8000에서 부터 복사해 놓는다. 복사할 땐 임의로 1MB를 복사한다. zImage의 크기가 얼마인지 모르기 때문이고 1MB면 충분하기 때문에 그렇다. 
d. Kernel Parameter 
0x100에 커널에 넘겨질 parameter를 담은 structure를 만들어 놓는다. parameter는 $(TOPDIR)/include/asm/setup.h에 있는 struct param_struct의 내용을 따른다. 
e. Run head.S 
kernel start address로 jump한다. 
f. gunzip 
head.S가 실행되고 piggy.o의 뒤쪽 어딘가 RAM에 압축을 풀어 커널을 올려 놓는다. 
g. Relocating Kernel 
압축이 풀린 커널의 뒤에 압축 풀린 커널을 head.S의 시작위치인 kenrel start address로 옮길 코드를 복사해 준다(이 코드는 head.S가 포함하고 있고 나중에 커널 뒤에 복사되고 실행된다). 복사후 압축 풀린 커널의 끝에 있는 relocating을 위한 코드를 실행한다. 이 코드는 압축 풀린 커널을 kernel start address에서 시작하도록 복사한다. 
h. Run Kernel 
압축 풀린 커널 이미지는 kernel start address로 옮겨 졌고 이 위치로 jump해 리눅스 커널을 실행한다. 
h. 리눅스 부팅 시작 
http://samse.tistory.com/121
http://kkamagui.tistory.com/817

컴파일 순서 바꾸기
module_init(init_cramfs_fs) 를 예를 들어보겠습니다. __initcall_init_cramfs_fs 라는 이름으로 do_initcalls() 를 통해 init_cramfs_fs() 는 수행되지요. __initcall_xxx 의 위치는 System.map 파일에서 확인할수 있습니다
고로 A, B 드라이버의 로딩순서를 바꿀려면 System.map 에서 그 위치를 확인해가면서 각 Makefile 에서 컴파일 순서를 바꿔주면 됩니다.

디렉토리 별로 Makefile내에는 아래와 같이 내용을 포함하고 있다. 
이중 mmc와 wifi의 순서를 바꾸고 싶다면 android/kernel/Makefile 파일에서 net, mmc 순서를 바꾸면 된다.
android/kernel/Makefile 
obj-y += net/
obj-y += mmc/

obj-y += net/
  android/kernel/net/Makefile
  - obj-$(CONFIG_WLAN) += wireless/
       android/kernel/net/wirelessMakefile
     - obj-$(CONFIG_WIFI_COMMON) += wifi/
...
obj-y += mmc/

obj-y 와 obj-m 이란 ? 
 커널 Makefile을 볼 때마다 참 신기한 것들이 많이 있다.
그래서 이번에는 obj-y와 obj-m에 대해서 알아 보려고 한다.

흔히들 모듈을 만들기 위해서는 obj-m를 사용하고 빌트인으로 사용하기 위해서는 obj-y로 사용한다. 

조금 더 자세히 알아보도록 해볼까?

obj-m := hello.o

을 Makefile 에다가 넣어 놓는다면 hello.ko 라는 모듈이 만들어 진다.

그러면 insmod 혹은 modprobe를 사용해 모듈을 로드 시키면 사용할 수 있다.

obj-y := hello.o

이 것은 built-in 으로 사용하게 하는 것이다. 

그러면.. built-in 이라는게 어떻게 사용된다는 것일까?? 한번 따라가 보도록 해보자.


android/kernel/scripts/Makfile.build

modorder-target := $(obj)/modules.order


# We keep a list of all modules in $(MODVERDIR)


__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \

$(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \

$(subdir-ym) $(always)

@:

obj-m 이면 modules.order가 생성됨


vmlinux Complier가 만드는 Rule
init.h
#define __define_initcall(level,fn) \
static initcall_t __initcall_##fn __attribute_used__ \
__attribute__((__section__(".initcall" level ".init"))) = fn

#define core_initcall(fn) __define_initcall("1",fn)
#define postcore_initcall(fn) __define_initcall("2",fn)
#define arch_initcall(fn) __define_initcall("3",fn)
#define subsys_initcall(fn) __define_initcall("4",fn)
#define fs_initcall(fn) __define_initcall("5",fn)
#define device_initcall(fn) __define_initcall("6",fn)
#define late_initcall(fn) __define_initcall("7",fn)

#define __initcall(fn) device_initcall(fn)
#define __exitcall(fn) \
static exitcall_t __exitcall_##fn __exit_call = fn

#define console_initcall(fn) \
static initcall_t __initcall_##fn \
__attribute_used__ __attribute__((__section__(".con_initcall.init")))=fn

#define security_initcall(fn) \
static initcall_t __initcall_##fn \
__attribute_used__ __attribute__((__section__(".security_initcall.init"))) = fn

#define module_init(x) __initcall(x);
#define module_exit(x) __exitcall(x);


'개발관련' 카테고리의 다른 글

커널 spin lock의 차이점  (0) 2015.03.17
C 언어 자료형과 Print format  (0) 2015.02.11
Linux Error 종류 및 코드값  (0) 2014.11.23
블루투스 Service Discovery Protocol(SDP) UUID  (0) 2014.11.23
SIGNAL ERROR 정의 및 설명  (0) 2014.09.03