前面介绍过在 UEFI 下创建内存虚拟盘的操作,这次介绍如何创建包含需要内容的内存虚拟盘。生成的虚拟盘可以在没有硬盘的情况下充当临时文件的存放位置。当然如果关机或者断电后,盘中内容会消失。
第一步,根据【参考1】制作一个磁盘镜像VHD,这里出于体积考虑,制作了一个 64MB 的FAT32 空盘;
第二步,上面制作磁盘镜像大部分内容都是 0x00(可以看作是以512Bytes为单位的稀疏矩阵),因此我们还需要一个工具提取文件中的非零内容保存起来,这样能够有效降低镜像尺寸。使用 C# 编写代码如下。简单的说就是以512Bytes为单位读取文件,然后检查512 bytes 中是否为全0.如果不是就记录下来保存到文件中。每一个项目是由 4 Bytes 记录加一个 512Byte的数组构成。特别注意 VHD 镜像文件末尾包含了一个文件头,我们这里会对文件大小取整,对于末尾的文件头不会进行处理。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
namespace ConsoleApp7
{
class Program
{
static void Main(string[] args)
{
if (args.Count() == 0) {
Console.WriteLine("Please input file name!");
Console.ReadKey();
Environment.Exit(0);
}
if (File.Exists((args[0] + ".raw"))) {
File.Delete(args[0] + ".raw");
}
FileStream fs = new FileStream(args[0], FileMode.Open);
FileStream RawFs = new FileStream(args[0]+".raw", FileMode.CreateNew);
byte[] array = new byte[512];
UInt32 Data;
Boolean EmptyMark;
// 写入 Disk Image 的Size (以 1MB 为单位)
Data = (UInt32) fs.Length / 1024 / 1024;
RawFs.Write(BitConverter.GetBytes(Data), 0, 4);
// 处理整数以内的磁盘,例如:扫描 64MB 以内的内容生成及镜像
while (fs.Position < fs.Length / 1024 / 1024 * 1024 * 1024) {
EmptyMark = true;
fs.Read(array, 0, array.Length);
for (int i = 0; i < array.Length; i++)
{
if (array[i] != 0x00)
{
EmptyMark = false;
break;
}
}
if (EmptyMark==false)
{
Data = (UInt32)(fs.Position / 512 - 1);
RawFs.Write(BitConverter.GetBytes(Data),0,4);
RawFs.Write(array, 0, array.Length);
Console.WriteLine("{0}", Data);
}
}
RawFs.Close();
Console.WriteLine("Done!");
}
}
}
例如:64MB的FAT32 空盘,经过上述操作后会变成6K 大小。
第三步,编写Shell 下的 UEFI Application,创建内存虚拟盘,然后读取前述生成的文件,将内容放在虚拟盘中。这样就得到了一个带有文件系统的内存虚拟盘。代码如下:
#include <Library/BaseLib.h>
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/PrintLib.h>
#include <Library/ShellCEntryLib.h>
#include <Protocol/RamDisk.h>
#include <Protocol/DevicePathToText.h>
#include <Protocol/HiiDatabase.h>
#include <Protocol/HiiPackageList.h>
#include <Protocol/HiiImageEx.h>
#include <Protocol/PlatformLogo.h>
#include <Protocol/GraphicsOutput.h>
#include <Library/UefiApplicationEntryPoint.h>
#include <Library/ShellLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/MemoryAllocationLib.h>
//DO NOT REMOVE IMAGE_TOKEN (IMG_LOGO)
extern EFI_BOOT_SERVICES *gBS;
EFI_STATUS
EFIAPI
UefiMain (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
EFI_RAM_DISK_PROTOCOL *MyRamDisk;
EFI_FILE_HANDLE FileHandle;
UINTN tmp, DiskSize,SectorIndex;
UINT64 *StartingAddr,Position;
UINT8 Sector[512];
UINTN ImageFileSize;
EFI_DEVICE_PATH_PROTOCOL *DevicePath;
// 磁盘镜像文件
CHAR16 *DiskImage=L"DiskImage.BIN";
// 如果磁盘镜像不存在,报错退出
if (ShellFileExists(DiskImage)!=EFI_SUCCESS)
{
Print(L"Couldn't find 'DiskImage.bin'\n");
return EFI_INVALID_PARAMETER;
}
// 打开磁盘镜像文件
Status = ShellOpenFileByName(
DiskImage,
(SHELL_FILE_HANDLE *)&FileHandle,
EFI_FILE_MODE_READ,
0);
if(Status != RETURN_SUCCESS)
{
Print(L"OpenFile failed!\n");
return EFI_INVALID_PARAMETER;
}
// Look for Ram Disk Protocol
Status = gBS->LocateProtocol (
&gEfiRamDiskProtocolGuid,
NULL,
&MyRamDisk
);
if (EFI_ERROR (Status))
{
Print(L"Couldn't find RamDiskProtocol\n");
return EFI_ALREADY_STARTED;
}
tmp=4;
Status = ShellReadFile(FileHandle,&tmp,&DiskSize);
Print(L"Disk size %dMB\n",DiskSize);
DiskSize=DiskSize*1024*1024;
//Allocate a memory for Image
StartingAddr = AllocateReservedZeroPool ((UINTN)DiskSize);
if(StartingAddr==0)
{
Print(L"Allocate Memory failed!\n");
return EFI_SUCCESS;
}
ShellGetFileSize(FileHandle,&ImageFileSize);
Position=0;
Print(L"File size %d\n",ImageFileSize);
while (Position<ImageFileSize)
{
tmp=4;
Status = ShellReadFile(FileHandle,&tmp,&SectorIndex);
if (Status!=EFI_SUCCESS)
{
break;
}
Print(L"Sector index %d\n",SectorIndex);
tmp=512;
Status = ShellReadFile(FileHandle,&tmp,&Sector);
if (Status==EFI_SUCCESS)
{
//Print(L"Read success %d\n",(UINT8*)StartingAddr+SectorIndex*512);
CopyMem((UINT8*)StartingAddr+SectorIndex*512,&Sector,tmp);
}
ShellGetFilePosition(FileHandle,&Position);
//Print(L"postion %d\n",Position);
}
//
// Register the newly created RAM disk.
//
Status = MyRamDisk->Register (
((UINT64)(UINTN) StartingAddr),
DiskSize,
&gEfiVirtualDiskGuid,
NULL,
&DevicePath
);
if (EFI_ERROR (Status))
{
Print(L"Can't create RAM Disk!\n");
return EFI_SUCCESS;
}
ShellCloseFile(&FileHandle);
return EFI_SUCCESS;
}
基本思路就是:打开镜像文件读取4 Bytes,然后创建这个大小的 Memory Disk。接下来读取 4 Bytes的扇区位置,然后再读取512字节的扇区内容,将这个内容存放在内存中的对应位置。
在Shell 下执行这个程序后,使用 map -r 即可看到新生成的内存盘。
UEFI 代码和编译后的EFI 文件,同时内置了一个64MB的磁盘镜像。
前面提到的 Windows 下的磁盘镜像扫描工具。内置了一个 64MB的空磁盘镜像。
参考:
1. https://www.lab-z.com/vt/