4 분 소요

elf_title_image.jpg

개요

Android 15에서 16KB ELF 정렬을 사용해서 Android를 빌드할 수 있게 되었다. ELF 정렬이란 무엇일까?

ELF

ELF (Executable and Linkable Format)는 리눅스에서 실행 파일을 저장하는 파일 형식이다. (.exe 비슷한 개념)

즉, 프로그램이 실행되기 전 상태의 ‘완성된 포장 파일’ 이라고 보면 된다.

이 파일 안에는 코드, 데이터, 심볼, 섹션 정보 등이 정리되어 들어 있다.

정렬

정렬은 데이터를 메모리에서 효율적으로 배치하는 규칙

만약 4바이트짜리 데이터를 넣어야 하는데 주소가 1에서 시작하면 (1~4칸 차지) → CPU는 2번 접근해야 한다,

하지만 4의 배수 주소인 4번 칸에 맞춰 넣으면 한 번에 읽을 수 있다.

ELF 파일은 여러 섹션(section) 으로 구성된다.

예:

  • .text → 코드
  • .data → 전역 변수
  • .bss → 초기화되지 않은 변수

각 섹션은 메모리나 파일 내에서 특정 주소 단위(보통 4바이트, 8바이트 등)로 정렬(aligned) 되어 있어야 한다.

1️⃣ 이유 1: CPU 성능 향상

데이터가 정렬돼 있으면 CPU가 더 빠르게 읽고 쓸 수 있다.

(잘못 정렬되면 접근 시 두 번 읽거나, 예외가 발생할 수 있다.)

2️⃣ 이유 2: OS 로더의 요구사항

운영체제(커널)는 ELF를 메모리에 로드할 때 페이지 단위(보통 4KB)로 데이터를 올리기 때문에, 각 섹션이 페이지 경계에 맞게 정렬돼 있어야 한다.

3️⃣ 이유 3: 구조체 패딩과 비슷한 원리

C 구조체(struct)에서 멤버가 메모리 경계에 맞춰져야 하는 것과 같은 개념이다.

정렬 없이 그냥 순서대로 읽으면 안되는가?

CPU가 메모리를 읽는법

사실 CPU가 메모리를 읽는 방식 자체가 그렇게 단순하지 않다.

CPU는 메모리를 한 바이트씩이 아니라, 한 번에 2바이트(16비트), 4바이트(32비트), 8바이트(64비트) 단위로 읽는다. 이걸 버스 폭(bus width) 이라고 한다.

예를 들어 32비트 CPU는 한 번에 4바이트씩 읽을 수 있다.

메모리 구조를 그림으로 보면

주소:  0  1  2  3  |  4  5  6  7  |  8  9  10 11 ...
        <---  번째 4바이트 --->
                       <---  번째 4바이트 --->
  • CPU는 보통 0~3, 4~7, 8~11 이렇게 4바이트 단위 블록으로 나누어 접근한다.
  • 즉, “4칸 묶음” 단위로 읽는다.

정렬이 안되어있을 경우

예를 들어 4바이트짜리 데이터를 주소 1부터 저장했다고 하자.

주소
0 (다른 데이터)
1 A
2 B
3 C
4 D

이때 CPU는 이렇게 해야 한다.

  • 첫 번째 블록(0 ~ 3)에서 1 ~ 3번째 바이트(ABC) 읽고
  • 두 번째 블록(4 ~ 7)에서 4번째 바이트(D) 읽고
  • 그 둘을 합쳐서 하나의 값으로 만들어야 함

즉, 메모리를 두 번 읽고 조립해야한다.

이건 시간이 더 걸리고, 하드웨어 구조도 복잡해진다.

정렬이 되어 있을 경우

이제 4바이트 데이터를 주소 4부터 넣었다고 해보자

주소
4 A
5 B
6 C
7 D

이 경우엔 CPU가 “0x4~0x7 블록”을 한 번에 딱 읽으면 끝이다.

정리

구분 정렬 안 됨 (주소 1부터) 정렬 됨 (주소 4부터)
읽기 횟수 2번 1번
속도 느림 빠름
하드웨어 처리 복잡 단순
일부 CPU 오류 발생 가능 정상

운영체제의 메모리 관리

주소:
0x00000000 ────────────────
0x00001000 ────────────────
0x00002000 ────────────────
0x00003000 ────────────────
        
      여기까지 4KB (4096바이트)

운영체제는 메모리를 4KB(=4096바이트) 단위로 잘라서 관리한다.

이 조각 하나를 페이지(page) 라고 한다.

📦 페이지는 일종의 “메모리 박스” 같다고 생각하면 된다.

한 박스당 4KB씩, 번호를 붙여서 쓴다.

운영체제가 이걸 메모리에 올릴 때는 “페이지 단위(4KB)”로 옮겨야 한다.

데이터 주소 경계가 어긋나면?

만약 코드가 이런 식으로 저장돼 있다고 해보자

코드(.text):     [ 0x08048010 ~ 0x08049000 ]
데이터(.data):   [ 0x08049000 ~ 0x08049800 ]

문제는 .text가 0x08048010처럼 4KB의 배수가 아닌 어중간한 곳에서 시작하면, 운영체제가 이걸 로드할 때 이렇게 된다.

“어? 이 조각이 0x1000 단위(4KB)에 안 맞네?

그럼 두 개 페이지에 걸쳐 있으니까 두 번 읽어야겠다.”

즉,

  • 한 페이지(4KB)에 딱 맞게 안 들어가면
  • 로드가 느려지고, 관리가 복잡해진다.

그래서 정렬이 필요하다.

ELF는 이런 식으로 설정해둔다.

.text   0x08048000 (4KB 배수 주소!)
.data   0x08049000 (그다음 페이지)

이렇게 맞춰놓으면 운영체제가 “페이지 단위로 딱딱 잘라서” 빠르고 깔끔하게 메모리에 올릴 수 있다.

📦 4KB 정렬 = “운영체제가 메모리를 4KB 박스 단위로 관리하니까, ELF의 조각들도 박스 경계에 맞춰 담는다.”

실제로 ELF 안에 이렇게 적혀 있다.

$ readelf -l a.out

LOAD           0x000000 0x08048000 0x08048000 0x001000 0x001000 R E 0x1000

맨 끝의 0x1000 = 정렬 크기(alignment)

→ 즉, “이 세그먼트는 반드시 4KB(=0x1000) 경계에 맞춰서 로드해라.”

“16KB 페이지 크기”는?

“16KB 페이지를 지원한다”는 건

운영체제가 메모리를 16KB(16384바이트) 단위로 자르고 관리할 수 있다는 뜻이다.

즉, 한 페이지가 4배 더 커진 것.

주소:
0x00000000 ~ 0x00003FFF    1페이지 (16KB)
0x00004000 ~ 0x00007FFF    2페이지 (16KB)
0x00008000 ~ 0x0000BFFF    3페이지 (16KB)
...

왜 페이지 크기를 바꿀까?

페이지 크기에는 장단점이 있다.

페이지 크기 장점 단점
4KB 낭비가 적음 (작은 데이터도 효율적) 페이지 수가 많아서 관리 비용이 큼
16KB 관리가 단순해지고 캐시 효율 ↑ 낭비가 커질 수 있음 (작은 데이터도 16KB 차지)

예를 들어,

  • 작은 파일 여러 개를 다루는 경우 → 4KB가 효율적
  • 큰 프로그램이나 대용량 데이터 처리 → 16KB 페이지가 더 빠름

ELF 관점에서 보면?

ELF(실행 파일)는 “페이지 단위”로 메모리에 올라간다.

그래서 ELF에는 이런 정보가 들어 있다.

LOAD           ... R E 0x1000

여기서 0x1000 = 4KB 정렬

즉, “이 프로그램은 4KB 단위 페이지에 맞춰서 로드돼야 한다”는 뜻이다.

그런데 만약 시스템이 16KB 페이지를 지원한다면 이 값은 이렇게 될 수 있다.

LOAD           ... R E 0x4000

0x4000 = 16KB 정렬

즉, “이 프로그램은 16KB 단위 페이지 경계에 맞춰서 올려라.”

이 말은:

ELF의 세그먼트 시작 주소가 16KB의 배수여야 OS가 메모리에 올릴 때 정확히 맞아떨어진다는 뜻이다.

쉽게 비유하자면

비유 4KB 페이지 16KB 페이지
📦 택배 상자 크기 작은 상자 큰 상자
📦 한 상자에 담을 수 있는 물건 4개 16개
📦 상자 관리 수 많음 적음
💰 상자 하나의 빈 공간 낭비 적음 많음

“16KB 페이지를 지원한다” = “운영체제가 더 큰 박스로 메모리를 나눠 관리할 수 있다” 는 뜻이다.

정리

항목 4KB 페이지 16KB 페이지
페이지 크기 4096바이트 16384바이트
정렬 단위 0x1000 0x4000
ELF 정렬 시 4KB 배수 주소 16KB 배수 주소
특징 공간 효율 ↑, 관리 복잡 관리 단순, 대용량 처리 효율 ↑

참조

elf(5) - Linux manual page

카테고리:

업데이트: