12. EFI 메모리 맵을 리눅스 커널 스타일로 바꾸기
EFI_SHELL_PARAMETERS_PROTOCOL
https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Protocol/ShellParameters.h
typedef struct _EFI_SHELL_PARAMETERS_PROTOCOL {
///
/// Points to an Argc-element array of points to NULL-terminated strings containing
/// the command-line parameters. The first entry in the array is always the full file
/// path of the executable. Any quotation marks that were used to preserve
/// whitespace have been removed.
///
CHAR16 **Argv;
///
/// The number of elements in the Argv array.
///
UINTN Argc;
///
/// The file handle for the standard input for this executable. This may be different
/// from the ConInHandle in EFI_SYSTEM_TABLE.
///
SHELL_FILE_HANDLE StdIn;
///
/// The file handle for the standard output for this executable. This may be different
/// from the ConOutHandle in EFI_SYSTEM_TABLE.
///
SHELL_FILE_HANDLE StdOut;
///
/// The file handle for the standard error output for this executable. This may be
/// different from the StdErrHandle in EFI_SYSTEM_TABLE.
///
SHELL_FILE_HANDLE StdErr;
} EFI_SHELL_PARAMETERS_PROTOCOL;
보다시피 이 프로토콜을 통해 프로그램에 전달된 명령줄 인수에 액세스할 수 있다. 이 프로토콜을 우리가 만든 MemoryInfo
프로그램에서 사용하자.
지난 Lesson에서 우리는 EFI 메모리 맵을 출력해보았다. 그런데 항목이 100개 이상이었다. 우리가 Linux 커널을 부팅할 때 현재 메모리 맵에 대한 정보를 볼 수 있지만 이 때는 길이가 훨씬 짧다.
이것은 두 가지 이유 때문에 그런데,
커널은 EFI 메모리 type을 덜 세분화해서 구분한다.
EfiReservedMemoryType/EfiLoaderCode/EfiLoaderData/...
등등 유형을 구분하지 않고usable/ACPI NVS/ACPI data/reserved
4가지 type만 있다.커널은 인접한 영역을 붙여버린다.
buildroot를 사용해서 EFI x86-64용 커널 이미지를 빌드해보겠다.
cd ~
git clone https://github.com/buildroot/buildroot.git
cd buildroot
make pc_x86_64_efi_defconfig
make
이 커널을 부팅하려면 다음과 같은 명령어를 쓰면 된다.
qemu-system-x86_64 -drive if=pflash,format=raw,file=Build/OvmfX64/RELEASE_GCC5/FV/OVMF.fd \
-drive format=raw,file=fat:rw:~/UEFI_disk \
-nographic \
-kernel ~/buildroot/output/images/bzImage \
-append "console=ttyS0"
커널이 부팅된다면 다음과 같은 부트로그를 볼 수 있다.
BIOS-provided physical RAM map:
BIOS-e820: [mem 0x0000000000000000-0x000000000009ffff] usable
BIOS-e820: [mem 0x0000000000100000-0x00000000007fffff] usable
BIOS-e820: [mem 0x0000000000800000-0x0000000000807fff] ACPI NVS
BIOS-e820: [mem 0x0000000000808000-0x000000000080ffff] usable
BIOS-e820: [mem 0x0000000000810000-0x00000000008fffff] ACPI NVS
BIOS-e820: [mem 0x0000000000900000-0x00000000078eefff] usable
BIOS-e820: [mem 0x00000000078ef000-0x0000000007b6efff] reserved
BIOS-e820: [mem 0x0000000007b6f000-0x0000000007b7efff] ACPI data
BIOS-e820: [mem 0x0000000007b7f000-0x0000000007bfefff] ACPI NVS
BIOS-e820: [mem 0x0000000007bff000-0x0000000007ef3fff] usable
BIOS-e820: [mem 0x0000000007ef4000-0x0000000007f77fff] reserved
BIOS-e820: [mem 0x0000000007f78000-0x0000000007ffffff] ACPI NVS
BIOS-e820: [mem 0x00000000ffc00000-0x00000000ffffffff] reserved
이제 우리의 MemoryInfo
프로그램을 수정해보자.
full
이라는 옵션이 전달되면 현재와 같은 방법으로 출력한다.옵션이 아무것도 전달되지 않는 다면 리눅스 커널 스타일로 출력한다.
먼저 full
이라는 부울 플래그를 생성한다. 인자 full
이 우리 프로그램에 전달되면 플래그가 설정되고 그렇지 않다면 False
와 같게 한다.
EFI_SHELL_PARAMETERS_PROTOCOL* ShellParameters;
Status = gBS->HandleProtocol(
ImageHandle,
&gEfiShellParametersProtocolGuid,
(VOID **) &ShellParameters
);
BOOLEAN full=FALSE;
if (Status == EFI_SUCCESS) {
if (ShellParameters->Argc == 2) {
if (!StrCmp(ShellParameters->Argv[1], L"full")) {
full=TRUE;
}
}
}
EFI_SHELL_PARAMETERS_PROTOCOL
을 사용하려면 헤더 파일을 추가해야한다.
#include <Protocol/ShellParameters.h>
그리고 애플리케이션 *.inf 파일에 GUID를 추가한다.
[Protocols]
gEfiShellParametersProtocolGuid
다음 문제로 넘어가서, OS 메모리 유형 매핑을 위한 함수를 만든다.
const CHAR16 *memory_types_OS_view[] = {
L"reserved", // L"EfiReservedMemoryType",
L"usable", // L"EfiLoaderCode",
L"usable", // L"EfiLoaderData",
L"usable", // L"EfiBootServicesCode",
L"usable", // L"EfiBootServicesData",
L"reserved", // L"EfiRuntimeServicesCode",
L"reserved", // L"EfiRuntimeServicesData",
L"usable", // L"EfiConventionalMemory",
L"reserved", // L"EfiUnusableMemory",
L"ACPI data",// L"EfiACPIReclaimMemory",
L"ACPI NVS", // L"EfiACPIMemoryNVS",
L"reserved", // L"EfiMemoryMappedIO",
L"reserved", // L"EfiMemoryMappedIOPortSpace",
L"reserved", // L"EfiPalCode",
L"usable", // L"EfiPersistentMemory",
L"usable", // L"EfiMaxMemoryType"
};
const CHAR16 *
memory_type_to_str_OS_view(UINT32 type)
{
if (type > sizeof(memory_types_OS_view)/sizeof(CHAR16 *))
return L"Unknown";
return memory_types_OS_view[type];
}
마지막으로 full
플래그가 설정되지 않은 경우 동일한 type의 인접 영역을 붙이도록 프로그램을 수정해야한다.
EFI_MEMORY_DESCRIPTOR* desc = MemoryMap;
EFI_MEMORY_DESCRIPTOR* next_desc;
int i = 0;
while ((UINT8 *)desc < (UINT8 *)MemoryMap + MemoryMapSize) {
UINTN PAGE_SIZE = 4096;
UINTN mapping_size =(UINTN) desc->NumberOfPages * PAGE_SIZE;
UINT64 Start = desc->PhysicalStart;
next_desc = (EFI_MEMORY_DESCRIPTOR *)((UINT8 *)desc + DescriptorSize);
if (!full) {
while ((UINT8 *)next_desc < (UINT8 *)MemoryMap + MemoryMapSize) {
mapping_size =(UINTN) desc->NumberOfPages * PAGE_SIZE;
if ((desc->PhysicalStart + mapping_size) == (next_desc->PhysicalStart)) {
if (desc->Type != next_desc->Type) {
if (StrCmp(memory_type_to_str_OS_view(desc->Type),
memory_type_to_str_OS_view(next_desc->Type)))
break;
}
desc=next_desc;
next_desc = (EFI_MEMORY_DESCRIPTOR *)((UINT8 *)next_desc + DescriptorSize);
} else {
break;
}
}
}
if (full) {
CHAR16 str[ATTRIBUTE_STR_SIZE];
Print(L"[#%02d] Type: %s Attr: %s\n", i++,
memory_type_to_str(desc->Type), memory_attrs_to_str(str, desc->Attribute));
Print(L" Phys: %016llx-%016llx\n", Start, Start + mapping_size - 1);
}
else {
Print(L" [mem: %016llx-%016llx] %s\n", Start, desc->PhysicalStart + mapping_size - 1,
memory_type_to_str_OS_view(desc->Type) );
}
desc = next_desc;
}
프로그램을 빌드 후에 UEFI_disk 폴더에 복사하자. full
옵션을 주고 프로그램을 실행하면 전과 같은 방식으로 출력이 된다.
FS0:\> MemoryInfo.efi full
[#00] Type: EfiBootServicesCode Attr: UC WC WT WB
Phys: 0000000000000000-0000000000000FFF
[#01] Type: EfiConventionalMemory Attr: UC WC WT WB
Phys: 0000000000001000-000000000009FFFF
[#02] Type: EfiConventionalMemory Attr: UC WC WT WB
Phys: 0000000000100000-00000000007FFFFF
[#03] Type: EfiACPIMemoryNVS Attr: UC WC WT WB
Phys: 0000000000800000-0000000000807FFF
[#04] Type: EfiConventionalMemory Attr: UC WC WT WB
Phys: 0000000000808000-000000000080FFFF
[#05] Type: EfiACPIMemoryNVS Attr: UC WC WT WB
Phys: 0000000000810000-00000000008FFFFF
...
하지만 full
옵션과 함께 실행 시키면 커널의 부트로그와 비슷한 메모리 맵이 출력되는 것을 볼 수 있다.

다음의 실제 커널 부트 로그와 비교해보자.
BIOS-provided physical RAM map:
BIOS-e820: [mem 0x0000000000000000-0x000000000009ffff] usable
BIOS-e820: [mem 0x0000000000100000-0x00000000007fffff] usable
BIOS-e820: [mem 0x0000000000800000-0x0000000000807fff] ACPI NVS
BIOS-e820: [mem 0x0000000000808000-0x000000000080ffff] usable
BIOS-e820: [mem 0x0000000000810000-0x00000000008fffff] ACPI NVS
BIOS-e820: [mem 0x0000000000900000-0x00000000078eefff] usable
BIOS-e820: [mem 0x00000000078ef000-0x0000000007b6efff] reserved
BIOS-e820: [mem 0x0000000007b6f000-0x0000000007b7efff] ACPI data
BIOS-e820: [mem 0x0000000007b7f000-0x0000000007bfefff] ACPI NVS
BIOS-e820: [mem 0x0000000007bff000-0x0000000007ef3fff] usable
BIOS-e820: [mem 0x0000000007ef4000-0x0000000007f77fff] reserved
BIOS-e820: [mem 0x0000000007f78000-0x0000000007ffffff] ACPI NVS
BIOS-e820: [mem 0x00000000ffc00000-0x00000000ffffffff] reserved
Last updated