どちらの場合も、実際に物理的に接続されているメモリ (およびその量) を検出するファームウェア (BIOS または EFI) と、この情報を何らかの形式で知る必要があるオペレーティング システムがあります。
基盤となるマザーボード ファームウェアは EFI ベースですが、このコードは x86 で実行されるため、E820 メモリ マップに変換する必要があります。
ここでの混乱は、EFI と x86 に互換性がないことです。互換性はありません。EFI ファームウェアには、使用可能なメモリを報告するための独自のメカニズムがあります。具体的には、GetMemoryMap
( を呼び出す前にExitBootServices
) ブート サービスを使用して、ファームウェアからメモリ マップを取得できます。EFI_MEMORY_DESCRIPTOR
ただし、重要なことに、このメモリ マップは、 E820 ではなく、EFI ファームウェアが報告したい形式 ( ) になっています。このシナリオでは、int 15h
必要な情報がすでにあるため、 も試しません。
Linux カーネルが行っていることは、x86 アーキテクチャ上のメモリの内部表現として E820 フォーマットを使用することだと思います。ただし、EFI をブートする場合、カーネルは EFI ファームウェア ブート サービスを使用する必要がありますが、取得した応答を E820 形式に戻す変換を選択します。
これは、あなたが書いているカーネルにとって必要なことではありません。メモリがどのようにマップされているかを知る必要があるだけです。
GRUB など、一部のブートローダーがこの情報を提供する場合もあります。マルチブート仕様の一部では、この情報をカーネルに提供する必要があることをブートローダーに指示できます。
詳細については、常に役立つosdev wikiにコード サンプルなどがあります。grub からメモリ マップを取得するための関連セクションは、こちらです。
その他のポイント:
OS は、いくつかの理由から、どのメモリがどこにマップされているかを理解する必要があります。1 つは、ファームウェア サービスが存在する物理メモリを使用しないようにすることですが、もう 1 つは、CPU とメモリを共有するデバイスと通信するためです。ビデオ バッファは、この一般的な例です。
次に、EFI でメモリ マップを一覧表示することはそれほど難しくありません。まだ発見していない場合、一部のファームウェアに付属の UEFI シェルにはmemmap
、メモリ マップを表示するコマンドがあります。これを自分で実装したい場合、それを行うための簡単で汚い方法は次のようになります。
EFI_STATUS EFIAPI PrintMemoryMap(EFI_SYSTEM_TABLE* SystemTable)
{
EFI_STATUS status = EFI_SUCCESS;
UINTN MemMapSize = sizeof(EFI_MEMORY_DESCRIPTOR)*16;
UINTN MemMapSizeOut = MemMapSize;
UINTN MemMapKey = 0; UINTN MemMapDescriptorSize = 0;
UINT32 MemMapDescriptorVersion = 0;
UINTN DescriptorCount = 0;
UINTN i = 0;
uint8_t* buffer = NULL;
EFI_MEMORY_DESCRIPTOR* MemoryDescriptorPtr = NULL;
do
{
buffer = AllocatePool(MemMapSize);
if ( buffer == NULL ) break;
status = gBS->GetMemoryMap(&MemMapSizeOut, (EFI_MEMORY_DESCRIPTOR*)buffer,
&MemMapKey, &MemMapDescriptorSize, &MemMapDescriptorVersion);
Print(L"MemoryMap: Status %x\n", status);
if ( status != EFI_SUCCESS )
{
FreePool(buffer);
MemMapSize += sizeof(EFI_MEMORY_DESCRIPTOR)*16;
}
} while ( status != EFI_SUCCESS );
if ( buffer != NULL )
{
DescriptorCount = MemMapSizeOut / MemMapDescriptorSize;
MemoryDescriptorPtr = (EFI_MEMORY_DESCRIPTOR*)buffer;
Print(L"MemoryMap: DescriptorCount %d\n", DescriptorCount);
for ( i = 0; i < DescriptorCount; i++ )
{
MemoryDescriptorPtr = (EFI_MEMORY_DESCRIPTOR*)(buffer + (i*MemMapDescriptorSize));
Print(L"Type: %d PhsyicalStart: %lx VirtualStart: %lx NumberofPages: %d Attribute %lx\n",
MemoryDescriptorPtr->Type, MemoryDescriptorPtr->PhysicalStart,
MemoryDescriptorPtr->VirtualStart, MemoryDescriptorPtr->NumberOfPages,
MemoryDescriptorPtr->Attribute);
}
FreePool(buffer);
}
return status;
}
これはかなり単純な関数です。GetMemoryMap
十分な大きさのバッファを渡さないと激しく文句を言うので、十分なスペースができるまでバッファ サイズを増やし続けます。次に、ループして印刷します。sizeof(EFI_MEMORY_DESCRIPTOR)
実際には、出力バッファー内の構造体の違いではないことに注意してください。上記の返されたサイズの計算を使用しないと、実際よりもはるかに大きなテーブルになってしまいます (アドレス空間はすべて間違って見えます)。
この表から、E820 と共通のフォーマットを決定することはそれほど難しくありません。