OS LOADER是一种特殊类型的EFI Image,它负责将系统由Firmware环境进入OS环境。它要完成以下几个重要步骤:
1. OS loader必须要确定从哪里被调用的,这样做的目的是为了让OS loader从相同的位置获取其他文件。
2. OS loader必须要确定系统中OS存放在哪里,确切讲是OSKERNERL.bin的位置。
3. OS loader必须建立物理内存资源的内存映射图,目的是使OS kernel知道它接下来要管理的内存空间。
4. OS有将启动路径和启动选项以环境变量的形式存储在非易失性存储设备中的选择权,例如将S3或S4的环境变量存储在非易失性存储设备中。OS loader可能需要使用这些环境变量,另外它也有可能需要传递某些环境变量到OS kernel中。
5. 下一步调用ExitBootServices(),在这个时候,OS loader会把控制权交给OS kernel。
6. 最后,在调用ExitBootServices()之后,EFI Boot Services调用就不再可用,这意味着OS kernel已经控制系统,它仅仅会调用EFI Runtime Services。
下面将由代码片段来说明整个流程。
- 设备路径与OS loader image信息
首先调用HandleProtocol(),从ImageHandle得到LOADER_IMAGE_PROTOCOL接口,将它传给OS loader的应用程序。第二步调用HandleProtocol(),得到OS loader Image的device handle的DEVICE_PATH_PROTOCOL接口。这两个调用将OS loader image的设备路径,文件路径和其他image信息交给OS loader本身。
BS->HandleProtocol(ImageHandle, &LoadedImageProtocol, LoadedImage);
//得到LOADER_IMAGE_PROTOCOL接口
BS->HandleProtocol(LoadedImage->DeviceHandle, &DevicePathProtocol, &DevicePath);
//得到OS loader image的device handle的DEVICE_PATH_PROTOCOL接口
//设备路径存在DevicePath中。
//文件路径存在LoadedImage->FilePath中,它是OS loader image的路径,同时它存放OSKERNEL.BIN。
- 在OS loader的设备路径下访问文件(OSKERNEL.BIN)
接下来是怎样通过这些信息来打开OSKERNEL.BIN文件,它和OS loader image放在相同目录下。首先调用HandleProtocol(),获取从上一步骤得到的device handle的FILE_SYSTEM_PROTOCOL接口,然后磁盘卷才可以被打开,最终变量CurDir成为OS loader所在分区的一个文件handle。
BS->HandleProtocol(LoadedImage->DeviceHandle, &FileSystemProtocol, &Vol);
Vol->OpenVolume(Vol, &RootFs);
CurDir = RootFs;
下一步是为与OS loader image相同目录下的OSKERNEL.BIN文件建立一个文件路径,一旦这个路径建立,CurDir这个文件handle就可以用来对OSKERNEL.BIN文件进行Open(), Close(), Read(), Write(),接下来代码建立了文件路径,打开文件,读取文件到已分配的缓冲器中,并关闭文件。
StrCpy(FileName, DevicePathToStr(LoadedImage->FilePath));
for(i=StrLen(FileName), i>=0 && FileName[i] != '\\'; i--);
FileName[i]=0;
StrCat(FileName, L"\\OSKERNEL.BIN');
//建立文件路径
CurDir->Open(CurDir, &FileHandle, FileName, FEI_FILE_MODE_READ, 0);
Size=0x00100000;
BS->AllocatePool(EfiLoaderData, Size, &OsKernelBuffer);
FileHandle->Read(FileHandle, &Size, OsKernelBuffer);
FileHandle->Close(FileHandle);
- 寻找OS分区
对系统的每个分区来说,FEI示例环境实现了一个BLOCK_IO_PROTOCOL实例,OS Loader能够依靠寻找所有的BLOCK_IO设备来搜索系统分区。下面代码用LibLocateHandle()获得BLOCK_IO device handle列表,这些handles检索每个BLOCK_IO设备的第一个block。HandleProtocol()用来获取每个BLOCK_IO设备的DEVICE_PATH_PROTOCOL和BLOCK_IO_PROTOCOL的实例。变量BlkIo是一个使用BLOCK_IO_PROTOCOL接口的BLOCK_IO设备handle,ReadBlocks()可用来读取设备的第一个block。
NoHandles=0;
HandleBuffer=NULL;
LibLocateHandle(ByProtocol, &BlockIoProtocol, NULL, &NoHandles, &HandleBuffer);
//获得BLOCK_IO device handle列表
for(i=0, i<NoHandles; i++){
BS->HandleProtocol(HandleBuffer[i], &DevicePathProtocol, &DevicePath);
BS->HandleProtocol(HandleBuffer[i], &BlockIoProtocol, &BlkIo);
//HandleProtocol用来获取DEVICE_PATH_PROTOCOL和BLOCK_IO_PROTOCOL实例
Block=AllocatePool(BlkIo->BlockSize);
MediaId=BlkIo-MediaId;
BlkIo->ReadBlocks(BlkIo, MediaId, (EFI_LBA)0, BlkIo->BlockSize, Block);
//读取BLOCK_IO设备的第一个block
}
- 获得当前的系统配置信息
通过SystemTable得到,它作为函数参数传递给OS loader image。System Table用来告知OS loader:可用的固件平台服务和访问工业标准表,如ACPI, SMBIOS等。
- 获取当前内存映射表
在Loader在运行的时候,内存由平台固件来管理,平台固件已经给固件划分了内存(boot services memory),也划分了其他需要一直保持到OS runtime的内存资源(runtime memory)。直到OS Loader把最后的控制权交给OS KERNEL并调用ExitBootServices()之前,FEI平台固件一直掌管着内存分配。
MemoryMap=LibMemoryMap(&NoEntries, &MapKey, &DescriptorSize, &DecriptorVersion);
- 获取环境变量
GetNextVariableName(&VariableNameSize, VariableName, &VendorGuid);
- 过渡到OS kernel
当调用ExitBootServices()时,OS loader就要为过渡到OS kernel做准备,OS loader必须将System Table交给OS KERNEL,以便OS Kernel能方便调用Runtime Services。