<kernel v5.0>
머신 설정
시스템을 설정하는 방법은 다음과 같이 두 가지로 나뉜다.
- Legacy
- 머신 디스크립터를 사용하여 아키텍처 또는 머신 specific한 코드를 사용한다.
- 기존 ARM32를 사용한 대부분의 임베디드 SoC 업체들이 사용한 방법이다.
- ARM64에서는 사용하지 않는다.
- Device Tree
- 발빠른 ARM32 SOC 업체들은 디바이스 트리를 지원하기 시작하였다.
- ARM32에서는 DT_MACHINE_START() 매크로를 사용한 머신 디스크립터를 사용한다.
- ARM64는 머신 디스크립터를 사용하지 않는다.
- ARM64는 디바이스 트리만 지원한다. 즉 디바이스 트리 설정에 따라 시스템을 초기화하는 방식을 사용한다.
- 드라이버 개발자들은 디바이스 트리의 코어 운용 기준을 정확히 알아야 하고 이에 따라 드라이버를 구성해야 한다.
인수로 받은 DTB 물리 주소를 사용하여 Machine 테이블에서 name으로 검색한 후 machine_desc 구조체 포인터로 리턴한다. – ARM32
멀티플랫폼(ARCH_MULTIPLATFORM) – ARM32
- 컴파일된 커널이 미리 지정된 여러 개의 플랫폼을 지원할 수 있게하였다. 부팅 중 플랫폼을 선택하여 동작한다.
- 2012년 ARM에서 추가를 시도했으나 개별 플랫폼의 코드를 통합하는 것에 난이도가 높아 잘 사용되지 않을 전망이다.
- 관련 설정
- ARCH_MULTIPLATFORM
- ARCH_MULTI_CPU_AUTO
- MULTI_IRQ_HANDLER
- ARCH_MULTI_V4
- ARCH_MULTI_V4T
- ARCH_MULTI_V4_V5
- ARCH_MULTI_V5
- ARCH_MULTI_V6
- ARCH_MULTI_V6_V7
- ARCH_MULTI_V7
- 참고: ARM: initial multiplatform support | LWN.net
DTB용 Machine 정보
DT_MACHINE_START() 매크로 – ARM32
arch/arm/include/asm/mach/arch.h
#define DT_MACHINE_START(_name, _namestr) \
static const struct machine_desc __mach_desc_##_name \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = ~0, \
.name = _namestr,
#endif
- .arch.info.init 섹션에 const 객체를 만들어 둔다.
- DTB 기반 Machine 테이블을 선언한다.
- 검색시 MACHINE_START() 매크로로 선언된 ATAG 기반 Machine 정보와 다르게 번호가 아닌 name으로 검색한다.
- nr을 -1로 한다.
- __used를 사용하여 이 객체가 참조되지 않아도 컴파일러가 제거하지 않도록 한다.
machine_desc 및 map_desc 구조체 – ARM32
setup_machine_fdt() – ARM32
arch/arm/kernel/devtree.c
/**
* setup_machine_fdt - Machine setup when an dtb was passed to the kernel
* @dt_phys: physical address of dt blob
*
* If a dtb was passed to the kernel in r2, then use it to choose the
* correct machine_desc and to setup the system.
*/
const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)
{
const struct machine_desc *mdesc, *mdesc_best = NULL;
#if defined(CONFIG_ARCH_MULTIPLATFORM) || defined(CONFIG_ARM_SINGLE_ARMV7M)
DT_MACHINE_START(GENERIC_DT, "Generic DT based system")
.l2c_aux_val = 0x0,
.l2c_aux_mask = ~0x0,
MACHINE_END
mdesc_best = &__mach_desc_GENERIC_DT;
#endif
if (!dt_phys || !early_init_dt_verify(phys_to_virt(dt_phys)))
return NULL;
mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);
if (!mdesc) {
const char *prop;
int size;
unsigned long dt_root;
early_print("\nError: unrecognized/unsupported "
"device tree compatible list:\n[ ");
dt_root = of_get_flat_dt_root();
prop = of_get_flat_dt_prop(dt_root, "compatible", &size);
while (size > 0) {
early_print("'%s' ", prop);
size -= strlen(prop) + 1;
prop += strlen(prop) + 1;
}
early_print("]\n\n");
dump_machine_table(); /* does not return */
}
/* We really don't want to do this, but sometimes firmware provides buggy data */
if (mdesc->dt_fixup)
mdesc->dt_fixup();
early_init_dt_scan_nodes();
/* Change machine number to match the mdesc we're using */
__machine_arch_type = mdesc->nr;
return mdesc;
}
인수로 전달받은 디바이스 트리(fdt)가 물리 주소에 있는지 검색하여 해당 machine을 찾고 machine_desc 구조체 포인터로 알아온다. 또한 디바이스 트리로부터 초기 부트업 과정에 미리(early) 설정해야할 정보를 얻어온 다. 초기 정보에는 커멘드 라인 정보나 메모리 정보 등이 있다.
- 코드 라인 5~12에서 멀티(2개 이상의 cpu가 다른 아키텍처) 플랫폼 또는 single ARMv7M을 사용한 시스템인 경우 빈 데이터 상태의 머신 구조체 정보를 사용한다.
- 코드 라인 15~16에서 디바이스 트리(fdt)의 물리 주소를 검사하여 이상이 있는 경우 null을 가지고 그냥 빠져나간다.
- 코드 라인 18~39에서 machine을 찾아 machine_desc 구조체 포인터를 알아온다. 못 찾은 경우 machine 테이블을 덤프하고 시스템을 정지한다.
- 코드 라인 42~43에서 만일 fixup 함수가 준비되어 있으면 동작시킨다.
- dt_fixup 멤버 변수는 보통 null이며 특별히 펌웨어에 문제가 있어 수정되어야 하는 경우 이 멤버 변수에 fixup 관련 함수를 등록하여 사용한다.
- 사용 예:
- arch/arm/mach-exynos/exynos.c – dt_fixup에 exynos_dt_fixup() 함수가 연결되어 있다.
- early_init_dt_scan_nodes()
- 노드를 스캔하여 early하게 초기화 해놓아야 할 항목들을 수행한다.
- boot_commmand_line, dt_root_size_cells, dt_root_addr_cells, early하게 추가해야 할 가용메모리 정보등이 있다.
setup_machine_fdt() – ARM64
arch/arm64/kernel/setup.c
static void __init setup_machine_fdt(phys_addr_t dt_phys)
{
void *dt_virt = fixmap_remap_fdt(dt_phys);
const char *name;
if (!dt_virt || !early_init_dt_scan(dt_virt)) {
pr_crit("\n"
"Error: invalid device tree blob at physical address %pa (virtual address 0xx
%p)\n"
"The dtb must be 8-byte aligned and must not exceed 2 MB in size\n"
"\nPlease check your bootloader.",
&dt_phys, dt_virt);
while (true)
cpu_relax();
}
name = of_flat_dt_get_machine_name();
if (!name)
return;
pr_info("Machine model: %s\n", name);
dump_stack_set_arch_desc("%s (DT)", name);
}
디바이스 트리(fdt)를 읽어들여 fixmap에 매핑하고 초기 부트업 과정에 미리(early) 설정해야할 정보를 얻어온 다. 초기 정보에는 커멘드 라인 정보나 메모리 정보 등이 있다.
- 코드 라인 3에서 디바이스 트리(fdt)를 읽어 fixmap에 매핑한다.
- 코드 라인 6~16에서 노드를 스캔하여 다음과 같이 먼저(early) 초기화할 항목들을 수행한다. 디바이스 트리를 발견할 수 없거나 디바이스트리 물리 주소를 검사하여 이상이 있는 경우 에러 메시지를 출력하고 동작을 멈춘다.
- boot_commmand_line 설정
- initrd_start, initrd_end 설정
- dt_root_size_cells, dt_root_addr_cells 설정
- memory memblock1에 메모리 등록
- 코드 라인 18~22에서 머신명을 알아와서 모델명을 출력한다. 루트 노드의 “model” 속성을 머신명으로 사용하며 속성이 발견되지 않는 경우 “compatible” 속성을 사용한다.
- 코드 라인 23에서 dump_stack_print_info() 함수가 호출될 때 사용할 머신 모델 명을 미리 저장해둔다.
early_init_dt_scan_nodes()
drivers/of/fdt.c
void __init early_init_dt_scan_nodes(void)
{
int rc = 0;
/* Retrieve various information from the /chosen node */
rc = of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
if (!rc)
pr_warn("No chosen node found, continuing without\n");
/* Initialize {size,address}-cells info */
of_scan_flat_dt(early_init_dt_scan_root, NULL);
/* Setup memory, calling early_init_dt_add_memory_arch */
of_scan_flat_dt(early_init_dt_scan_memory, NULL);
}
DTB 노드를 스캔하여 먼저(early) 초기화해놓아야 할 항목들을 수행한다.
- 코드 라인 6~8에서 “/chosen” 노드를 스캔하여 “linux,initrd-start” 속성 값과 “linux,initrd-end” 속성 값을 읽어와서 전역 initrd_start와 initrd_end에 저장한다. 또한 “bootargs” 속성 값을 읽어와서 전역 변수 boot_command_line에 저장한다.
- initrd는 커널 부트 로드 과정에 필요한 초기 램디스크로, 이 램디스크는 보통 일시적으로 루트 파일 시스템으로 마운트되고 처음 유저 모드에서 초기화 코드(sysvinit, systemd, …)가 호출된다. 이 과정에서 다른 정규 파일 시스템이 마운트되고 루트 파일 시스템이 교체되는 과정이 진행된다. 유저 모드에서 실행하는 처음 초기화 코드를 통해 두 과정으로 진행하는데, 하나는 커널이 필요로 하는 최소의 드라이버들을 로드하고 그 후 나머지 추가적인 모듈들이 로드된다.
- CONFIG_CMDLINE 커널 옵션을 사용하는 경우에는 “bootargs” 속성 값이 없다면 커널 빌드 시 주어진 디폴트 CONFIG_CMDLINE을 사용한다. 단, CONFIG_CMDLINE_FORCE 커널 옵션도 사용하는 경우 “bootargs” 속성 값의 유무와 상관없이 무조건 디폴트 CONFIG_CMDLINE을 사용한다.
- 코드 라인 11에서 루트 노드를 스캔하여 “#size-cells” 속성 값과 “#address-cells” 속성 값을 읽어와서 전역 변수 dt_root_size_cells와 dt_root_addr_cells에 저장한다.
- 코드 라인 14에서 “memory” 노드를 스캔하여 “reg” 속성 값을 읽어와서 파싱한 물리 메모리 시작 주소 및 크기 정보로 memory memblock에 추가한다.
#address-cells와 #size-cells
#address-cells와 #size-cells는 이 속성이 있는 노드의 자식(child)부터 그 이하 노드를 대상으로 주소 셀 및 크기 셀이 표현된 값을 읽어들일 때 그 값이 표현하는 데 필요한 워드 수를 의미한다. 적용 범위가 현재 노드가 아니라 항상 하위 노드 이하를 대상으로 함에 주의해야 한다. 디바이스 트리에서 숫자 표현은 항상 워드(4바이트) 단위를 초과할 수 없다. 따라서 이보다 더 큰 숫자를 표기하려고 할 때는 한 칸 띄어서 2개의 워드를 연달아 표현해야 한다.
- 예) regs가 표현하고자 하는 시작 주소는 0x8_0000_0000이며, 크기는 0x2000_0000(512MB)이다. 주소가 4바이트를 초과하므로 2개의 워드를 사용하기 위해서는 속성이 있는 노드의 부모 노드에서 먼저 주소 셀의 크기와 크기 셀의 크기를 적절하게 결정해두어야 한다.
node1 {
#address-cells = <2>;
#size-cells = <1>;
...
node2 {
regs = <0x8 0x0 0x20000000>;
}
}
다음 그림은 earyl_init_dt_scan_nodes() 함수가 처리하는 일을 보여준다.
of_scan_flat_dt()
drivers/of/fdt.c
/**
* of_scan_flat_dt - scan flattened tree blob and call callback on each.
* @it: callback function
* @data: context data pointer
*
* This function is used to scan the flattened device-tree, it is
* used to extract the memory information at boot before we can
* unflatten the tree
*/
int __init of_scan_flat_dt(int (*it)(unsigned long node,
const char *uname, int depth,
void *data),
void *data)
{
const void *blob = initial_boot_params;
const char *pathp;
int offset, rc = 0, depth = -1;
if (!blob)
return 0;
for (offset = fdt_next_node(blob, -1, &depth);
offset >= 0 && depth >= 0 && !rc;
offset = fdt_next_node(blob, offset, &depth)) {
pathp = fdt_get_name(blob, offset, NULL);
if (*pathp == '/')
pathp = kbasename(pathp);
rc = it(offset, pathp, depth, data);
}
return rc;
}
디바이스 트리의 모든 노드를 뒤져 ‘ / ’로 시작하는 모든 노드에 대해 인자로 받은 함수를 호출하여 그 함수가 요청하는 값을 읽어온 경우 처리를 종료한다.
- 코드 라인 13~15에서 디바이스 트리에서 노드만 루프를 돌며 검색하고 원하는 결과(rc = 1) 값을 읽어온 경우 루프를 빠져나간다.
- 코드 라인 17~19에서 노드의 이름을 가져와서 ‘ / ’로 시작하는 노드인 경우에는 처음 ‘ / ’를 제외한 이름을 구한다.
- 코드 라인 20에서 모든 노드에 대해 인자로 받은 함수(it)를 호출한다. 호출할 때 주어지는 인자들을 알아보면 오프셋은 structure 블록 내부에서 현재 순회(iteration)하고 있는 노드의 오프셋 바이트를 말한다. 루트 노드의 오프셋이 0부터 시작함을 기억해둔다. 두 번째 인자 pathp는 노드명을 가리키는 포인터다. 세 번째 인자 depth는 현재 노드의 depth를 의미하며, 루트 노드가 0부터 시작한다. 마지막 인자 data는 it 함수 호출 시 전달할 데이터다.
early_init_dt_scan_chosen()
drivers/of/fdt.c
int __init early_init_dt_scan_chosen(unsigned long node, const char *uname,
int depth, void *data)
{
int l;
const char *p;
pr_debug("search \"chosen\", depth: %d, uname: %s\n", depth, uname);
if (depth != 1 || !data ||
(strcmp(uname, "chosen") != 0 && strcmp(uname, "chosen@0") != 0))
return 0;
early_init_dt_check_for_initrd(node);
/* Retrieve command line */
p = of_get_flat_dt_prop(node, "bootargs", &l);
if (p != NULL && l > 0)
strlcpy(data, p, min((int)l, COMMAND_LINE_SIZE));
/*
* CONFIG_CMDLINE is meant to be a default in case nothing else
* managed to set the command line, unless CONFIG_CMDLINE_FORCE
* is set in which case we override whatever was found earlier.
*/
#ifdef CONFIG_CMDLINE
#if defined(CONFIG_CMDLINE_EXTEND)
strlcat(data, " ", COMMAND_LINE_SIZE);
strlcat(data, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
#elif defined(CONFIG_CMDLINE_FORCE)
strlcpy(data, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
#else
/* No arguments from boot loader, use kernel's cmdl*/
if (!((char *)data)[0])
strlcpy(data, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
#endif
#endif /* CONFIG_CMDLINE */
pr_debug("Command line is: %s\n", (char*)data);
/* break now */
return 1;
}
루트 노드를 스캔하여 “linux,initrd-start” 속성 값과 “linux,initrd-end” 속성 값을 읽어와서 전역 initrd_start와 initrd_end에 저장한다. 또한 “bootargs” 속성 값을 읽어와 전역 변수 boot_command_line에 저장한다.
- 코드 라인 9~11에서 루트 노드의 다음 단계 자식 노드명이 “chosen”이 아니면 이 함수에서 필요한 노드가 아니므로 대상 노드를 처리하지 않는다.
- 코드 라인 13에서 “linux,initrd-start” 속성 값과 “linux,initrd-end” 속성 값을 찾아 전역 변수 initrd_start와 initrd_end에 저장한다.
- 코드 라인 16~18에서 속성 bootargs를 찾아 전역 변수 boot_command_line에 저장한다.
- 코드 라인 25~36에서 CONFIG_CMDLINE 커널 옵션을 사용하는 경우 “bootargs” 속성 값이 없다면 커널 빌드 시 주어진 디폴트 CONFIG_CMDLINE을 사용한다. 단, CONFIG_CMDLINE_FORCE 커널 옵션도 사용하는 경우 “bootargs” 속성 값의 유무와 상관없이 무조건 디폴트 CONFIG_CMDLINE 값을 사용한다.
다음 그림과 같이 커널 옵션에 따라 DTB 또는 커널 설정을 결정한다. default 설정을 사용하는 경우 부트 로더가 DTB를 로드한 후 bootargs 속성에 overwrite한 후 커널에 전달하는 과정을 알 수 있다.
early_init_dt_check_for_initrd()
drivers/of/fdt.c
/**
* early_init_dt_check_for_initrd - Decode initrd location from flat tree
* @node: reference to node containing initrd location ('chosen')
*/
static void __init early_init_dt_check_for_initrd(unsigned long node)
{
u64 start, end;
int len;
const __be32 *prop;
pr_debug("Looking for initrd properties... ");
prop = of_get_flat_dt_prop(node, "linux,initrd-start", &len);
if (!prop)
return;
start = of_read_number(prop, len/4);
prop = of_get_flat_dt_prop(node, "linux,initrd-end", &len);
if (!prop)
return;
end = of_read_number(prop, len/4);
__early_init_dt_declare_initrd(start, end);
phys_initrd_start = start;
phys_initrd_size = end - start;
pr_debug("initrd_start=0x%llx initrd_end=0x%llx\n",
(unsigned long long)start, (unsigned long long)end);
}
“linux,initrd-start” 속성 값과 “linux,initrd-end” 속성 값을 찾아 전역 변수 initrd_start와 initrd_end에 저장한다.
- 코드 라인 9~12에서 “linux,initrd-start” 속성 값을 읽어온다.
- 코드 라인 14~17에서 “linux,initrd-end” 속성 값을 읽어온다.
- 코드 라인 19~21에서 읽은 2개의 값을 읽어 가상 주소로 바꿔 전역 변수 initrd_start와 initrd_end에 저장한다. 그리고 물리 주소도 시작과 사이즈도 전역변수에 저장해둔다.
early_init_dt_scan_root()
drivers/of/fdt.c
/**
* early_init_dt_scan_root - fetch the top level address and size cells
*/
int __init early_init_dt_scan_root(unsigned long node, const char *uname,
int depth, void *data)
{
const __be32 *prop;
if (depth != 0)
return 0;
dt_root_size_cells = OF_ROOT_NODE_SIZE_CELLS_DEFAULT;
dt_root_addr_cells = OF_ROOT_NODE_ADDR_CELLS_DEFAULT;
prop = of_get_flat_dt_prop(node, "#size-cells", NULL);
if (prop)
dt_root_size_cells = be32_to_cpup(prop);
pr_debug("dt_root_size_cells = %x\n", dt_root_size_cells);
prop = of_get_flat_dt_prop(node, "#address-cells", NULL);
if (prop)
dt_root_addr_cells = be32_to_cpup(prop);
pr_debug("dt_root_addr_cells = %x\n", dt_root_addr_cells);
/* break now */
return 1;
}
루트 노드를 스캔하여 “#size-cells” 속성 값과 “#address-cells” 속성 값을 읽어와서 전역 dt_root_size_cells 및 dt_root_addr_cells에 저장한다.
- 코드 라인 6~7에서 노드의 depth가 0이 아닌 경우, 즉 루트 노드가 아닌 경우 빠져나간다.
- 코드 라인 12~14에서 “#size-cells” 속성 값을 찾아 전역 변수 dt_root_size_cells에 저장하되, 찾지 못한 경우 기본 값 1로 한다. “#size-cells”는 size를 표현하는 cell의 수를 나타낸다.
- 코드 라인 17~19에서 “#address-cells” 속성 값을 찾아 전역 변수 dt_root_addr_cells에 저장하되, 찾지 못한 경우 기본 값 1로 한다. “#address-cells”는 address를 표현하는 cell의 수를 나타낸다.
early_init_dt_scan_memory()
drivers/of/fdt.c
/**
* early_init_dt_scan_memory - Look for and parse memory nodes
*/
int __init early_init_dt_scan_memory(unsigned long node, const char *uname,
int depth, void *data)
{
const char *type = of_get_flat_dt_prop(node, "device_type", NULL);
const __be32 *reg, *endp;
int l;
bool hotpluggable;
/* We are scanning "memory" nodes only */
if (type == NULL || strcmp(type, "memory") != 0)
return 0;
reg = of_get_flat_dt_prop(node, "linux,usable-memory", &l);
if (reg == NULL)
reg = of_get_flat_dt_prop(node, "reg", &l);
if (reg == NULL)
return 0;
endp = reg + (l / sizeof(__be32));
hotpluggable = of_get_flat_dt_prop(node, "hotpluggable", NULL);
pr_debug("memory scan node %s, reg size %d,\n", uname, l);
while ((endp - reg) >= (dt_root_addr_cells + dt_root_size_cells)) {
u64 base, size;
base = dt_mem_next_cell(dt_root_addr_cells, ®);
size = dt_mem_next_cell(dt_root_size_cells, ®);
if (size == 0)
continue;
pr_debug(" - %llx , %llx\n", (unsigned long long)base,
(unsigned long long)size);
early_init_dt_add_memory_arch(base, size);
if (!hotpluggable)
continue;
if (early_init_dt_mark_hotplug_memory_arch(base, size))
pr_warn("failed to mark hotplug range 0x%llx - 0x%llx\n",
base, base + size);
}
return 0;
}
“memory” 노드를 스캔하여 “reg” 속성 값을 읽어와서 파싱한 물리 메모리 시작 주소 및 크기 정보로 memory memblock에 추가한다.
- 코드 라인 4~11에서 노드에 대해 device_type 속성을 알아와서 memory 타입이 아닌 경우 그냥 리턴한다. powerpc 아키텍처에서는 device_type이 설정되지 않았어도 루트 다음에 “memory@0” 노드인 경우에는 계속 진행한다.
- 코드 라인 13~17에서 “linux,usable-memory” 속성 또는 “reg” 속성을 찾아 사용 가능 메모리 크기를 정한다.
- powerpc 아키텍처에서만 사용하는 속성이다.
- 코드 라인 20에서 “hotpluggable” 속성이 있으면 그 여부를 저장한다.
- 코드 라인 24~35에서 사용 가능 메모리 크기만큼 reg 값에 있는 메모리 base와 size를 사용하여 계산한 후 early_init_dt_add_memory_arch( )를 호출하여 메모리 블록을 추가한다. 배열인 경우에는 그 수만큼 루프를 돈다.
- 코드 라인 37~43에서 hotplug 메모리의 경우 memblock에 hotplug 표식을 해둔다.
early_init_dt_add_memory_arch()
drivers/of/fdt.c
void __init __weak early_init_dt_add_memory_arch(u64 base, u64 size)
{
const u64 phys_offset = MIN_MEMBLOCK_ADDR;
if (size < PAGE_SIZE - (base & ~PAGE_MASK)) {
pr_warn("Ignoring memory block 0x%llx - 0x%llx\n",
base, base + size);
return;
}
if (!PAGE_ALIGNED(base)) {
size -= PAGE_SIZE - (base & ~PAGE_MASK);
base = PAGE_ALIGN(base);
}
size &= PAGE_MASK;
if (base > MAX_MEMBLOCK_ADDR) {
pr_warning("Ignoring memory block 0x%llx - 0x%llx\n",
base, base + size);
return;
}
if (base + size - 1 > MAX_MEMBLOCK_ADDR) {
pr_warning("Ignoring memory range 0x%llx - 0x%llx\n",
((u64)MAX_MEMBLOCK_ADDR) + 1, base + size);
size = MAX_MEMBLOCK_ADDR - base + 1;
}
if (base + size < phys_offset) {
pr_warning("Ignoring memory block 0x%llx - 0x%llx\n",
base, base + size);
return;
}
if (base < phys_offset) {
pr_warning("Ignoring memory range 0x%llx - 0x%llx\n",
base, phys_offset);
size -= phys_offset - base;
base = phys_offset;
}
memblock_add(base, size);
}
메모리 시작 주소와 크기로 memory memblock에 추가한다. 단, 물리 메모리 범위를 넘어가는 경우 size를 조정한다.
- 코드 라인 3에서 물리 메모리의 하한 주소를 설정한다.
- 코드 라인 5~9에서 사이즈가 너무 작으면 경고 메시지를 출력하고 함수를 빠져나간다.
- 코드 라인 11~15에서 시작 주소가 페이지 단위로 정렬되지 않았다면 시작 주소를 페이지 단위로 정렬하고 그 차이만큼 size를 줄인다. 그런 후 페이지 단위로 내림 정렬한다.
- 코드 라인 17~21에서 시작 주소가 물리 메모리 상한 주소를 초과한다면 더 이상 처리하지 않고 함수를 빠져나간다.
- 코드 라인 23~27에서 끝 주소가 시스템 최대 처리 상한 주소를 초과한다면 초과한 만큼의 크기를 조절한다.
- 코드 라인 29~33에서 끝 주소가 물리 메모리 최소 주소 미만인 경우에는 더 이상 처리하지 않고 함수를 빠져나간다.
- 코드 라인 34~39에서 시작 주소가 물리 메모리 최소 주소 미만인 경우에는 시작 주소를 물리 메모리 하한 주소로 조정하고, 크기도 그 차이만큼 감소시켜 조정한다.
- 코드 라인 40에서 memblock_add( ) 함수를 사용하여 메모리 블록을 추가한다.
참고