Ipmi 之Boot Option

您所在的位置:网站首页 hp服务器ipmi设置 Ipmi 之Boot Option

Ipmi 之Boot Option

2023-05-25 21:25| 来源: 网络整理| 查看: 265

IPMI BOOt OPTION 简介 最近在调试ipmi 相关的boot option功能,大体的功能已经可以支持了。但是还离商业正是版本的功能还差很远。这里之前不了解是怎么个实现原理,现在清楚了,所以还是记下来吧。这个功能主要实现的就是bios和bmc可以联动的管理bios的启动项。服务器里面bmc上电启动后可以通过web界面设置bios的启动项,比如从硬盘,pxe,或者CDRom等方式启动,这样bios在启动过程中,会通过ipmi 命令的方式来获取bmc设置的启动方式,然后根据bootorder中的顺序来调整启动顺序,达到可以通过指定的启动方式来启动。同样bios起来后,可以通过setup界面来调整启动顺序,调整后的结果也要通知给bmc,bmc可以获取到bios一次性或者永久性的启动方式,大体原理就是如此。 下面我们先看看ipmi 规范中关于boot option的介绍:

ipmi boot option 相关的命令 这里面其实就是两个命:

(1)Set System Boot Options Command

这个命令主要是bios 去通知bmc bios设置的启动项。也就是bios端修改boot order后同步给bmc时所使用的命令。在ipmi规范中,这个命令对应的netfc Lun cmd分别为 00,00 08,Request Data部分包含了很多内容,也就是我们要去写的内容。详细的见下图:

上图列出了param 0-5分别代表的内容和含义,这里不做详细的介绍了。需要了解的人可以自己去看,这里我们修改了bios的启动项后主要是要给bmc 发送param 5 boot flags的数据内容。这里需要注意:发送set boot option 命令时,要写数据给bmc,其中request Data中的 param valid bit必须要置0,表示unlock,bios端要去写。

(2)Get System Boot Options Command 命令数据的详细格式如下:

如上图所示,可以通过这个命令来获取对应param 中的数据。

实现代码及原理 (1)bios启动过程中,读取bmc 设置的boot option,并根据boot option 调整boot order 在bios启动过程中,到Bds阶段,会遍历所有的BootOption,并创建BootOrder variable,然后会根据BootOrder中的顺序,来获取第一启动项,并通过variable Boot000#(# 代码启动顺序的id,数值0-N),下面详细的看一下遍历系统启动项,已经注册Boot000#的过程。 首先从BdsEntry为入口: BdsEntry–>PlatformBootManagerAfterConsole–>EfiBootManagerConnectAll/EfiBootManagerRefreshAllBootOption 首先函数EfiBootManagerConnectAll会遍历所有的handle,并根据handle遍历所有的controller,然后依次调用ConnectController函数,将没有执行的驱动执行一次,这样防止遗漏可以作为启动项的设备驱动没有加载。EfiBootManagerConnectAll执行完之后,调用函数EfiBootManagerRefreshAllBootOption,会创建Boot000#每一个启动项的variable。下面详细的看看这个函数的代码:

/** The function enumerates all boot options, creates them and registers them in the BootOrder variable. **/ VOID EFIAPI EfiBootManagerRefreshAllBootOption ( VOID ) { EFI_STATUS Status = 0; EFI_BOOT_MANAGER_LOAD_OPTION *NvBootOptions; UINTN NvBootOptionCount; EFI_BOOT_MANAGER_LOAD_OPTION *BootOptions; UINTN BootOptionCount; UINTN Index;// // Optionally refresh the legacy boot option // if (mBmRefreshLegacyBootOption != NULL) { mBmRefreshLegacyBootOption (); }BootOptions = BmEnumerateBootOptions (&BootOptionCount); NvBootOptions = EfiBootManagerGetLoadOptions (&NvBootOptionCount, LoadOptionTypeBoot);// // Mark the boot option as added by BDS by setting OptionalData to a special GUID // for (Index = 0; Index < BootOptionCount; Index++) { BootOptions[Index].OptionalData = AllocateCopyPool (sizeof (EFI_GUID), &mBmAutoCreateBootOptionGuid); BootOptions[Index].OptionalDataSize = sizeof (EFI_GUID); }// // Remove invalid EFI boot options from NV // for (Index = 0; Index < NvBootOptionCount; Index++) { if (((DevicePathType (NvBootOptions[Index].FilePath) != BBS_DEVICE_PATH) || (DevicePathSubType (NvBootOptions[Index].FilePath) != BBS_BBS_DP) ) && BmIsAutoCreateBootOption (&NvBootOptions[Index]) ) { // // Only check those added by BDS // so that the boot options added by end-user or OS installer won’t be deleted // if (EfiBootManagerFindLoadOption (&NvBootOptions[Index], BootOptions, BootOptionCount) == -1) { Status = EfiBootManagerDeleteLoadOptionVariable (NvBootOptions[Index].OptionNumber, LoadOptionTypeBoot); // // Deleting variable with current variable implementation shouldn’t fail. // ASSERT_EFI_ERROR (Status); } } }// // Add new EFI boot options to NV // for (Index = 0; Index < BootOptionCount; Index++) { if (EfiBootManagerFindLoadOption (&BootOptions[Index], NvBootOptions, NvBootOptionCount) == -1) { EfiBootManagerAddLoadOptionVariable (&BootOptions[Index], (UINTN) -1); // // Try best to add the boot options so continue upon failure. // } }EfiBootManagerFreeLoadOptions (BootOptions, BootOptionCount); EfiBootManagerFreeLoadOptions (NvBootOptions, NvBootOptionCount); }在这个

函数内首先调用BmEnumerateBootOptions获取系统可以作为启动项的设备个数,这个函数很关键,通过这个函数我们可以知道,是如何把uefi下面的启动项都找出来的。下面看看详细的地代码: (如何寻找启动项的代码)

// // Skip the logical partitions // if (BlkIo->Media->LogicalPartition) { continue; } // // Skip the fixed block io then the removable block io // if (BlkIo->Media->RemovableMedia == ((Removable == 0) ? FALSE : TRUE)) { continue; } BootType = BdsGetBootTypeFromDevicePathEx (DevicePathFromHandle (Handles[Index])); Description = BmGetBootDescription (Handles[Index]); BootOptions = ReallocatePool ( sizeof (EFI_BOOT_MANAGER_LOAD_OPTION) * (*BootOptionCount), sizeof (EFI_BOOT_MANAGER_LOAD_OPTION) * (*BootOptionCount + 1), BootOptions ); ASSERT (BootOptions != NULL); Status = EfiBootManagerInitializeLoadOptionEx ( &BootOptions[(*BootOptionCount)++], LoadOptionNumberUnassigned, LoadOptionTypeBoot, LOAD_OPTION_ACTIVE, Description, DevicePathFromHandle (Handles[Index]), NULL, 0, BootType ); ASSERT_EFI_ERROR (Status); FreePool (Description); }

BootType = BdsGetBootTypeFromDevicePathEx (DevicePathFromHandle (Handles[Index])); Status = EfiBootManagerInitializeLoadOptionEx ( &BootOptions[(*BootOptionCount)++], LoadOptionNumberUnassigned, LoadOptionTypeBoot, LOAD_OPTION_ACTIVE, Description, DevicePathFromHandle (Handles[Index]), NULL, 0, BootType ); ASSERT_EFI_ERROR (Status); FreePool (Description);

Description = BmGetBootDescription (Handles[Index]); BootOptions = ReallocatePool ( sizeof (EFI_BOOT_MANAGER_LOAD_OPTION) * (*BootOptionCount), sizeof (EFI_BOOT_MANAGER_LOAD_OPTION) * (*BootOptionCount + 1), BootOptions ); ASSERT (BootOptions != NULL); Status = EfiBootManagerInitializeLoadOptionEx ( &BootOptions[(*BootOptionCount)++], LoadOptionNumberUnassigned, LoadOptionTypeBoot, LOAD_OPTION_ACTIVE, Description, DevicePathFromHandle (Handles[Index]), NULL, 0, BootType ); ASSERT_EFI_ERROR (Status); FreePool (Description);

从上面的代码可知,如何判断一个设备可不可以作为启动设备主要分为4中情况: (可以作为boot option设备的条件) 1:支持热插拔的设备,作为启动媒介其安装了blockio,如usb,cdrom等 2:正常的支持blockio的固定的设备作为启动设备,如硬盘等。 3:不支持blockio 但是支持SimpleFileSystem的设备,也可以作为一种启动设备。 4:就是仅仅支持LoadFile protocol的设备,也可以作为启动设备。

根据上面的条件,后代码逻辑就是先找到install blockio的handle,有了handle之后,如果能成功的locate 到blockio那么这个hangle就是正确的,然后通过函数 DevicePathFromHandle (Handles[Index])拿到对应设备的DevicePatch。DevicePath每个设备驱动在创建的时候,都会建立。可以通过DevicePath来区分是什么设备。最后通过DevicePatch拿到对应的启动类型BootType(DeviceType),也可以叫DeviceType。(通过这个BootType就可以区分出是硬盘,还是U盘,还是pxe等启动类型。)这里面关键的函数就是DevicePathFromHandle和BdsGetBootTypeFromDevicePathEx详细的代码实现如下:

switch (DevicePathSubType (TempDevicePath)) { case MSG_ATAPI_DP: BootType = BDS_EFI_MESSAGE_ATAPI_BOOT; break; case MSG_USB_DP: BootType = BDS_EFI_MESSAGE_USB_DEVICE_BOOT; break; case MSG_SCSI_DP: BootType = BDS_EFI_MESSAGE_SCSI_BOOT; break; case MSG_SATA_DP: BootType = BDS_EFI_MESSAGE_SATA_BOOT; break; case MSG_MAC_ADDR_DP: case MSG_VLAN_DP: case MSG_IPv4_DP: case MSG_IPv6_DP: BootType = BDS_EFI_MESSAGE_MAC_BOOT; break; default: BootType = BDS_EFI_MESSAGE_MISC_BOOT; break; } return BootType; default: break; } TempDevicePath = NextDevicePathNode (TempDevicePath);

NewOptionOrder = AllocatePool (OptionOrderSize + sizeof (UINT16)); ASSERT (NewOptionOrder != NULL); if (OptionOrderSize != 0) { CopyMem (NewOptionOrder, OptionOrder, Position * sizeof (UINT16)); CopyMem (&NewOptionOrder[Position + 1], &OptionOrder[Position], OptionOrderSize - Position * sizeof (UINT16)); } NewOptionOrder[Position] = OptionNumber; Status = gRT->SetVariable ( OptionOrderName, &gEfiGlobalVariableGuid, EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_NON_VOLATILE, OptionOrderSize + sizeof (UINT16), NewOptionOrder ); FreePool (NewOptionOrder);} if (OptionOrder != NULL) { FreePool (OptionOrder); }return Status; } /** Create the Boot####, Driver####, SysPrep####, PlatformRecovery#### variable from the load option.@param LoadOption Pointer to the load option. @retval EFI_SUCCESS The variable was created. @retval Others Error status returned by RT->SetVariable. **/ EFI_STATUS EFIAPI EfiBootManagerLoadOptionToVariable ( IN CONST EFI_BOOT_MANAGER_LOAD_OPTION *Option ) { EFI_STATUS Status; UINTN VariableSize; UINT8 *Variable; UINT8 *Ptr; CHAR16 OptionName[BM_OPTION_NAME_LEN]; CHAR16 *Description; CHAR16 NullChar; EDKII_VARIABLE_LOCK_PROTOCOL *VariableLock; UINT32 VariableAttributes;if ((Option->OptionNumber == LoadOptionNumberUnassigned) || (Option->FilePath == NULL) || ((UINT32) Option->OptionType >= LoadOptionTypeMax) ) { return EFI_INVALID_PARAMETER; }// // Convert NULL description to empty description // NullChar = L’\0’; Description = Option->Description; if (Description == NULL) { Description = &NullChar; } /* UINT32 Attributes; UINT16 FilePathListLength; CHAR16 Description[]; EFI_DEVICE_PATH_PROTOCOL FilePathList[]; UINT8 OptionalData[]; TODO: FilePathList[] IS: A packed array of UEFI device paths. The first element of the array is a device path that describes the device and location of the Image for this load option. The FilePathList[0] is specific to the device type. Other device paths may optionally exist in the FilePathList, but their usage is OSV specific. Each element in the array is variable length, and ends at the device path end structure. */ VariableSize = sizeof (Option->Attributes) + sizeof (UINT16) + StrSize (Description) + GetDevicePathSize (Option->FilePath) + Option->OptionalDataSize + sizeof (UINT32);Variable = AllocatePool (VariableSize); ASSERT (Variable != NULL);Ptr = Variable; WriteUnaligned32 ((UINT32 *) Ptr, Option->Attributes); Ptr += sizeof (Option->Attributes);WriteUnaligned16 ((UINT16 *) Ptr, (UINT16) GetDevicePathSize (Option->FilePath)); Ptr += sizeof (UINT16);CopyMem (Ptr, Description, StrSize (Description)); Ptr += StrSize (Description);CopyMem (Ptr, Option->FilePath, GetDevicePathSize (Option->FilePath)); Ptr += GetDevicePathSize (Option->FilePath);CopyMem (Ptr, Option->OptionalData, Option->OptionalDataSize); Ptr += Option->OptionalDataSize; WriteUnaligned32 ((UINT32 *) Ptr, Option->BootType);UnicodeSPrint (OptionName, sizeof (OptionName), L"%s%04x", mBmLoadOptionName[Option->OptionType], Option->OptionNumber); VariableAttributes = EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_NON_VOLATILE; if (Option->OptionType == LoadOptionTypePlatformRecovery) { // // Lock the PlatformRecovery#### // Status = gBS->LocateProtocol (&gEdkiiVariableLockProtocolGuid, NULL, (VOID **) &VariableLock); if (!EFI_ERROR (Status)) { Status = VariableLock->RequestToLock (VariableLock, OptionName, &gEfiGlobalVariableGuid); ASSERT_EFI_ERROR (Status); } VariableAttributes = EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS; }return gRT->SetVariable ( OptionName, &gEfiGlobalVariableGuid, VariableAttributes, VariableSize, Variable ); }

从上面的代码可知,将BootOption中的Attributes,Description,FilePath,OptionalData,BootType保存到了Variable中。这里BootType原本是没有的,是我自己加的。通过这个BootType为key去修改BootOrder的。 当EfiBootManagerRefreshAllBootOption函数执行完之后,系统下有几个启动项,以及顺序如何就存在了BootOrder和Boot000#中。这个时候我们就调用我们自己实现的修改BootOrder的函数,来将启动顺序修改成指定的Order。

读取bmc的Boot Option 并根据BootType修改BootOrder 下面是我们自己实现的代码:

EFI_STATUS IpmiSetBootOption( VOID ) { EFI_STATUS Status; IPMI_BOOT_INFO_ACKNOWLEDGE Param4; GET_BOOT_FLAG_RESPONSE BootFlags; UINT32 OldBootType = 0xFFFF; UINT32 OldBootTypeSize = sizeof(UINT32);DEBUG ((-1, " IPMI Boot: Entered SetIpmiBootOption \n")); // // Locate the IPMI Transport Protocol and return if status is not success // Status = gBS->LocateProtocol ( &gEfiDxeIpmiTransportProtocolGuid, NULL, (VOID **)&gIpmiTransport ); DEBUG((-1, "IPMI Boot: LocateProtocol gEfiDxeIpmiTransportProtocolGuid. Status: %r\n", Status)); if ( EFI_ERROR (Status) ) { return Status; } // // Read the boot info acknowledge bytes from BMC // Status = IpmiGetBootInfoAcknowledgeData (&Param4); DEBUG((-1, "GetBootInfoAcknowledgeData Status: %r\n", Status)); // // Support IPMI boot only if BootInitiatorAcknowledgeData is zero // or BiosOrPostBit is set in BootInitiatorAcknowledgeData // if ((Param4.BootInitiatorAcknowledgeData.RawData == 0) || (Param4.BootInitiatorAcknowledgeData.BitFields.BiosOrPostBit)) { gIpmiForceBootDevice = 0; // // Read the boot flag bytes from BMC // Status = IpmiGetBootFlags (&BootFlags); DEBUG((-1, "IpmiGetBootFlags Status: %r\n", Status)); DEBUG ((-1, " (%a)(%d)(BootFlagValid:%d)\n",__func__,__LINE__,BootFlags.Param5.BootFlagValid)); if ( BootFlags.Param5.BootFlagValid ) { // // Update the IpmiBoot global variables // UpdateIpmiBootGlobalVariables (BootFlags); Status = gRT->GetVariable (L"IpmiBootTypeFlag", &gEfiGlobalVariableGuid, NULL, (VOID *)&OldBootTypeSize, (VOID *)&OldBootType); DEBUG((-1, "Get Variable IpmiBootTypeFlag Status: %r, OldBootType:%04x \n", Status,OldBootType)); if((Status == EFI_NOT_FOUND && gUefiBootType != 0xFFFF) || (Status == EFI_SUCCESS && gUefiBootType != OldBootType && OldBootType != 0xFFFF && gUefiBootType != 0xFFFF) ){ // // If persistent boot, save the Boot flags into NVRAM to use it in further boots // if ( gPersistentBoot ) { DEBUG((-1, "begin to set %04x to be first setup order. \n",gUefiBootType)); Status = IpmiForceChangeBootOrder(gUefiBootType); DEBUG((-1, "IpmiForceChangeBootOrder Status: %r\n", Status)); if(Status == EFI_SUCCESS){ Status = gRT->SetVariable ( L"IpmiBootTypeFlag", &gEfiGlobalVariableGuid, EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_NON_VOLATILE, sizeof(UINT32), &gUefiBootType); DEBUG((-1, "Set Variable IpmiBootTypeFlag Status: %r\n", Status)); ASSERT(Status == EFI_SUCCESS); } }else { // // If Non Persistent boot, Clear the BootFlags NVRAM data stored in previous persistent boots. // Status = gRT->SetVariable ( L"BootNext", &gEfiGlobalVariableGuid, EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_NON_VOLATILE, sizeof(UINT16), &gBootNextId); DEBUG((-1, "IpmiForceCreat BootNext Variable Status: %r\n", Status)); if(Status == EFI_SUCCESS){ Status = gRT->SetVariable ( L"IpmiBootTypeFlag", &gEfiGlobalVariableGuid, EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_NON_VOLATILE, sizeof(UINT32), &gUefiBootType); DEBUG((-1, "Set Variable IpmiBootTypeFlag Status: %r\n", Status)); ASSERT(Status == EFI_SUCCESS); } } // // Write to Boot Info Acknowledge "BIOS/POST has handled boot info" // Status = IpmiSetBootInfoAck (BOOT_INFO_HANDLED_BY_BIOS); DEBUG((-1, "IpmiSetBootInfoAck Status: %r\n", Status)); // // Clear the BMC BootFlags // if ( !EFI_ERROR(Status)) { Status = IpmiClearBootFlags (); DEBUG((-1, "IpmiClearBootFlags Status: %r\n", Status)); } } }} DbgPrint(DEBUG_INFO,“(%a)(%d)(%r)\n”,func,LINE,Status); return Status; }

由上面的代码可知,主要的函数就是通过函数IpmiGetBootFlags拿到bmc要设置的启动项类型,然后根据启动项类型,去修改BootOrder。 IpmiForceChangeBootOrder函数就是实现修改BootOrder的主要函数,详细的实现如下:

EFI_STATUS IpmiForceChangeBootOrder(UINT32 BootType) { EFI_BOOT_MANAGER_LOAD_OPTION *BootOptions = NULL; EFI_BOOT_MANAGER_LOAD_OPTION *UpdateBootOptions = NULL; EFI_BOOT_MANAGER_LOAD_OPTION *FreeBootOptions = NULL; EFI_BOOT_MANAGER_LOAD_OPTION TempBootOptions[1]; EFI_STATUS Status = 0; BOOLEAN Found = FALSE; UINTN BootOptionCount = 0; UINTN Index = 0; UINTN TargetIndex = 0;ZeroMem(TempBootOptions,sizeof(EFI_BOOT_MANAGER_LOAD_OPTION)); DbgPrint(DEBUG_INFO,“(%a) (%d) Boottype:[%04x]\n”,func,LINE,BootType); BootOptions = EfiBootManagerGetLoadOptions (&BootOptionCount, LoadOptionTypeBoot);if(BootOptions != NULL && BootOptionCount > 0){ UpdateBootOptions = BootOptions; FreeBootOptions = BootOptions; for (Index = 0; Index < BootOptionCount; Index++) { DbgPrint(DEBUG_INFO,“(%a) (%d) (BootOptions[%d].Attributes:[0x%08x]\n”,func,LINE,Index,BootOptions[Index].Attributes); if(FindMatchSetupType(&BootOptions[Index],BootType)){ TargetIndex = Index; Found = TRUE; DbgPrint(DEBUG_INFO,“(%a) (%d) Find TargetIndex [%d] \n”,func,LINE,Index); } }if(TargetIndex >= 0 && TargetIndex LocateProtocol ( &gEfiDxeIpmiTransportProtocolGuid, NULL, (VOID **)&IpmiTransport );DEBUG((-1, " (%a) LocateProtocol gEfiDxeIpmiTransportProtocolGuid. Status: %r\n",func, Status)); if ( EFI_ERROR (Status) ) { return Status; }BootOptions = EfiBootManagerGetLoadOptions (&BootOptionCount, LoadOptionTypeBoot); if(BootOptions != NULL && BootOptionCount > 0){ FreeBootOptions = BootOptions; for (Index = 0; Index < BootOptionCount; Index++) { if (((BootOptions[Index].Attributes) == LOAD_OPTION_ACTIVE)) { DbgPrint(DEBUG_INFO,“(%a) (%d) (BootOptions[%d].Attributes:0x%08x\n”,func,LINE,Index,BootOptions[Index].Attributes,BootOptions[Index].Description); BootType = BootOptions[Index].BootType; break; } } EfiBootManagerFreeLoadOptions (FreeBootOptions, BootOptionCount); } ASSERT(BootType != 0xFFFF); Status = gRT->GetVariable (L"IpmiBootTypeFlag", &gEfiGlobalVariableGuid, NULL, &BootTypeSize, &NvBootType); DbgPrint(DEBUG_INFO,“(%a)(%d)(NvBootType:0x%04x)(%r)\n”,func,LINE,NvBootType,Status);if(NvBootType != BootType && NvBootType != 0xFFFF){ DevSel = IpmiBootTypeToDeviceSelector(BootType); Status = IpmiSetBootFlags(DevSel,IpmiTransport); DbgPrint(DEBUG_INFO,“(%a)(%d) IpmiSetBootFlags (%r)\n”,func,LINE,Status); if(Status == EFI_SUCCESS){ Status = gRT->SetVariable ( L"IpmiBootTypeFlag", &gEfiGlobalVariableGuid, EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_NON_VOLATILE, sizeof(UINT32), &BootType); DEBUG((-1, “Set Variable IpmiBootTypeFlag Status: %r\n”, Status)); ASSERT(Status == EFI_SUCCESS); //DumpBootOptions(); } }DbgPrint(DEBUG_INFO,“(%a)(%d) (%r)\n”,func,LINE,Status); return Status; } 上面的

代码逻辑很简单,首先遍历一下当前的BootOrder中的BootOption,找出第一个属性为LOAD_OPTION_ACTIVE的BootType,也就是第一启动项。找到之后判断一下如果这个BootType和之前的不一样,那就调用通知的函数,也就是给bmc发送命令的函数IpmiSetBootFlags,如果发送成功,更新一下对应的variable。至此,bmc和bios联动修改BootOption的功能就完成了。 懂的人知道,这里只是一个初级的功能,还有很多高级功能,boot groups内还能调整优先级的功能等等,还没有实现。服务器功能我们还欠缺很多,我们在补课。。。

注意:系统的启动是根据BootOrder中的数字顺序来决定的。BootOrder variable中存的每一项是一个UINT16的数字,假设BootOrder存的数字为0001000000020003,这一串数字表示有4个启动项,分别为0001/0000/0002/0003,第一启动项为vairbale Boot0001为名字的启动项,第二启动项Boot0000为名字的variable 对应的启动项。而BootNext内保存的也就是000#这个数字,表示下一次系统从哪个启动项启动,如果BootNext存在且数值为0003 那么系统就会从Boot0003的启动项启动。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3