36. Library의 constructor와 destructor, NULL Library

이번 장에서는 Library의 constructor(생성자)와 destructor(소멸자)에 대해 배운다.

CONSTRUCTOR

새 라이브러리 모듈을 만든다.

$ vi UefiLessonsPkg/Library/SimpleLibraryWithConstructor/SimpleLibraryWithConstructor.inf
---
[Defines]
  INF_VERSION                    = 1.25
  BASE_NAME                      = SimpleLibraryWithConstructor
  FILE_GUID                      = 96952c1e-86a6-4700-96b0-e7303ac3f92d
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 1.0
  LIBRARY_CLASS                  = SimpleLibrary | UEFI_APPLICATION
  CONSTRUCTOR                    = SimpleLibraryConstructor                  

[Sources]
  SimpleLibraryWithConstructor.c

[Packages]
  MdePkg/MdePkg.dec
  UefiLessonsPkg/UefiLessonsPkg.dec

여기에 CONSTRUCTOR에 생성자 함수라는 이름의 명령문을 추가했다. 이것이 언제 실행되는지 알기 위해 Print 명령문을 추가해보자.

$ vi UefiLessonsPkg/Library/SimpleLibraryWithConstructor/SimpleLibraryWithConstructor.c
---
#include <Library/UefiLib.h>
#include <Library/SimpleLibrary.h>

UINTN Plus2(UINTN number) {
  return number+2;
}

EFI_STATUS
EFIAPI
SimpleLibraryConstructor(
  IN EFI_HANDLE       ImageHandle,
  IN EFI_SYSTEM_TABLE *SystemTable
  )
{
  Print(L"Hello from library constructor!\n");
  return EFI_SUCCESS;
}

이제 새 라이브러리를 사용하는 다른 애플리ㅇ케이션을 만들 필요가 없다. 이제는 단순히 UefiLessonsPkg/UefiLessonsPkg.dsc에서 라이브러리 설정을 변경하면, SimpleLibraryUser가 새로운 라이브러리 버전으로 다시 컴파일된다.

다른 부분은 건드리지 않아도 괜찮다.

[LibraryClasses]
  ...
  #SimpleLibrary|UefiLessonsPkg/Library/SimpleLibrary/SimpleLibrary.inf
  SimpleLibrary|UefiLessonsPkg/Library/SimpleLibraryWithConstructor/SimpleLibraryWithConstructor.inf

만약 이 앱을 빌드하고, OVMF를 실행하면, 다음과 같은 화면이 나올 것이다.

FS0:\> SimpleLibraryUser.efi
Hello from library constructor!
5

생성자를 사용하는 라이브러리의 예로는 UefiBootServicesTableLib 라이브러리가 있다. https://github.com/tianocore/edk2/blob/master/MdePkg/Library/UefiBootServicesTableLib/UefiBootServicesTableLib.inf

우리는 이를 이전 장들에서 모두 사용해 보았으니, 한 번 사용해보도록 하자.

사실, 이 라이브러리가 가지고 있는 것은 생성자일 뿐이다.

이 라이브러리의 작동 방식을 이해하려면, *.c 및 *.h 파일을 확인해보자. https://github.com/tianocore/edk2/blob/master/MdePkg/Library/UefiBootServicesTableLib/UefiBootServicesTableLib.c

EFI_HANDLE         gImageHandle = NULL;
EFI_SYSTEM_TABLE   *gST         = NULL;
EFI_BOOT_SERVICES  *gBS         = NULL;

EFI_STATUS
EFIAPI
UefiBootServicesTableLibConstructor (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  gImageHandle = ImageHandle;
  gST = SystemTable;
  gBS = SystemTable->BootServices;

  return EFI_SUCCESS;
}

https://github.com/tianocore/edk2/blob/master/MdePkg/Library/UefiBootServicesTableLib/UefiBootServicesTableLib.c

extern EFI_HANDLE         gImageHandle;
extern EFI_SYSTEM_TABLE   *gST;
extern EFI_BOOT_SERVICES  *gBS;

이 라이브러리는 UEFI의 main 부분에 대한 바로가기와 같은 몇 가지 전역 변수를 설정한다. 라이브러리 생성자가 Main 앱 코드이전에 실행되므로, 이 라이브러리를 사용하여 gImageHandle/gST/gBS 모든 부분에 접근할 수 있다.

DESTRUCTOR

마찬가지로, 생성자와 소멸자가 모두 존재하는 SimpleLibrary의 다른 버전을 만들 수 있다.

$ vi UefiLessonsPkg/Library/SimpleLibraryWithConstructorAndDestructor/SimpleLibraryWithConstructorAndDestructor.inf
---
[Defines]
  INF_VERSION                    = 1.25
  BASE_NAME                      = SimpleLibraryWithConstructorAndDestructor
  FILE_GUID                      = 96952c1e-86a6-4700-96b0-e7303ac3f92d
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 1.0
  LIBRARY_CLASS                  = SimpleLibrary | UEFI_APPLICATION
  CONSTRUCTOR                    = SimpleLibraryConstructor
  DESTRUCTOR                     = SimpleLibraryDestructor                      

[Sources]
  SimpleLibraryWithConstructorAndDestructor.c

[Packages]
  MdePkg/MdePkg.dec
  UefiLessonsPkg/UefiLessonsPkg.dec
$ vi UefiLessonsPkg/Library/SimpleLibraryWithConstructorAndDestructor/SimpleLibraryWithConstructorAndDestructor.c
---
#include <Library/UefiLib.h>
#include <Library/SimpleLibrary.h>

UINTN Plus2(UINTN number) {
  return number+2;
}

EFI_STATUS
EFIAPI
SimpleLibraryConstructor(
  IN EFI_HANDLE       ImageHandle,
  IN EFI_SYSTEM_TABLE *SystemTable
  )
{
  Print(L"Hello from library constructor!\n");
  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
SimpleLibraryDestructor(
  IN EFI_HANDLE       ImageHandle,
  IN EFI_SYSTEM_TABLE *SystemTable
  )
{
  Print(L"Hello from library destructor!\n");
  return EFI_SUCCESS;
}

UefiLessonsPkg/UefiLessonsPkg.dsc의 끝부분을 바꾸는 것을 잊지 말도록 하자.

[LibraryClasses]
  ...
  #SimpleLibrary|UefiLessonsPkg/Library/SimpleLibrary/SimpleLibrary.inf
  #SimpleLibrary|UefiLessonsPkg/Library/SimpleLibraryWithConstructor/SimpleLibraryWithConstructor.inf
  SimpleLibrary|UefiLessonsPkg/Library/SimpleLibraryWithConstructorAndDestructor/SimpleLibraryWithConstructorAndDestructor.inf

이제 애플리케이션을 실행하면, 처음과 끝에 모두 문자열을 출력할 것이다.

FS0:\> SimpleLibraryUser.efi
Hello from library constructor!
5
Hello from library destructor!

생성자와 소멸자가 모두 있는 라이브러리의 예로, DebugLibConstructor.c를 살펴보자. https://github.com/tianocore/edk2/blob/master/MdePkg/Library/UefiDebugLibConOut/DebugLibConstructor.c

이 라이브러리는 생성자에서 CreateEvent를 사용하고, CloseEvent를 사용하여 소멸자에서 닫는다.

NULL Library

이미 언급했었다시피, OVMF는 Shell 앱자체를 포함하고 있다. 컴파일을 위해 OVMF 패키지 DSC(OvmfPkgX64.dsc) 파일에는 다음 문자열이 포함되어 있다.

[Components]
  ...
  ShellPkg/Application/Shell/Shell.inf {
    <LibraryClasses>
      ShellCommandLib|ShellPkg/Library/UefiShellCommandLib/UefiShellCommandLib.inf
      NULL|ShellPkg/Library/UefiShellLevel2CommandsLib/UefiShellLevel2CommandsLib.inf
      NULL|ShellPkg/Library/UefiShellLevel1CommandsLib/UefiShellLevel1CommandsLib.inf
      NULL|ShellPkg/Library/UefiShellLevel3CommandsLib/UefiShellLevel3CommandsLib.inf
      NULL|ShellPkg/Library/UefiShellDriver1CommandsLib/UefiShellDriver1CommandsLib.inf
      NULL|ShellPkg/Library/UefiShellDebug1CommandsLib/UefiShellDebug1CommandsLib.inf
      NULL|ShellPkg/Library/UefiShellInstall1CommandsLib/UefiShellInstall1CommandsLib.inf
      NULL|ShellPkg/Library/UefiShellNetwork1CommandsLib/UefiShellNetwork1CommandsLib.inf
      ...
      HandleParsingLib|ShellPkg/Library/UefiHandleParsingLib/UefiHandleParsingLib.inf
      PrintLib|MdePkg/Library/BasePrintLib/BasePrintLib.inf
      BcfgCommandLib|ShellPkg/Library/UefiShellBcfgCommandLib/UefiShellBcfgCommandLib.inf

      ... 
  }

여기서 일부 라이브러리 클래스에 NULL 클래스가 있음을 알 수 있다.

NULL 라이브러리 클래스는 개념적으로 "anonymous library, 익명 라이브러리"이다. 모듈이 라이브러리의 함수를 직접 호출하지 않더라도 모듈에 코드를 정적으로 연결할 수 있다.

앱에서 라이브러리 API를 호출할 필요 없이 라이브러리 생성자/소멸자기능만 필요한 경우 유용할 수 있다. https://github.com/tianocore/edk2/blob/master/ShellPkg/Library/UefiShellLevel1CommandsLib/UefiShellLevel1CommandsLib.c

해당 파일을 보면, 이 모듈의 생성자가 shell에 명령을 추가하는 데 사용된다는 것을 알 수 있다.

EFI_STATUS
EFIAPI
ShellLevel1CommandsLibConstructor (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
 ...
  ShellCommandRegisterCommandName(L"stall",  ShellCommandRunStall   , ShellCommandGetManFileNameLevel1, 1, L"", FALSE, gShellLevel1HiiHandle, (EFI_STRING_ID)(PcdGet8(PcdShellSupportLevel) < 3 ? 0 : STRING_TOKEN(STR_GET_HELP_STALL) ));
  ShellCommandRegisterCommandName(L"for",    ShellCommandRunFor     , ShellCommandGetManFileNameLevel1, 1, L"", FALSE, gShellLevel1HiiHandle, (EFI_STRING_ID)(PcdGet8(PcdShellSupportLevel) < 3 ? 0 : STRING_TOKEN(STR_GET_HELP_FOR)   ));
  ShellCommandRegisterCommandName(L"goto",   ShellCommandRunGoto    , ShellCommandGetManFileNameLevel1, 1, L"", FALSE, gShellLevel1HiiHandle, (EFI_STRING_ID)(PcdGet8(PcdShellSupportLevel) < 3 ? 0 : STRING_TOKEN(STR_GET_HELP_GOTO)  ));
  ShellCommandRegisterCommandName(L"if",     ShellCommandRunIf      , ShellCommandGetManFileNameLevel1, 1, L"", FALSE, gShellLevel1HiiHandle, (EFI_STRING_ID)(PcdGet8(PcdShellSupportLevel) < 3 ? 0 : STRING_TOKEN(STR_GET_HELP_IF)    ));
  ShellCommandRegisterCommandName(L"shift",  ShellCommandRunShift   , ShellCommandGetManFileNameLevel1, 1, L"", FALSE, gShellLevel1HiiHandle, (EFI_STRING_ID)(PcdGet8(PcdShellSupportLevel) < 3 ? 0 : STRING_TOKEN(STR_GET_HELP_SHIFT) ));
  ShellCommandRegisterCommandName(L"exit",   ShellCommandRunExit    , ShellCommandGetManFileNameLevel1, 1, L"", TRUE , gShellLevel1HiiHandle, (EFI_STRING_ID)(PcdGet8(PcdShellSupportLevel) < 3 ? 0 : STRING_TOKEN(STR_GET_HELP_EXIT)  ));
  ShellCommandRegisterCommandName(L"else",   ShellCommandRunElse    , ShellCommandGetManFileNameLevel1, 1, L"", FALSE, gShellLevel1HiiHandle, (EFI_STRING_ID)(PcdGet8(PcdShellSupportLevel) < 3 ? 0 : STRING_TOKEN(STR_GET_HELP_ELSE)  ));
  ShellCommandRegisterCommandName(L"endif",  ShellCommandRunEndIf   , ShellCommandGetManFileNameLevel1, 1, L"", FALSE, gShellLevel1HiiHandle, (EFI_STRING_ID)(PcdGet8(PcdShellSupportLevel) < 3 ? 0 : STRING_TOKEN(STR_GET_HELP_ENDIF) ));
  ShellCommandRegisterCommandName(L"endfor", ShellCommandRunEndFor  , ShellCommandGetManFileNameLevel1, 1, L"", FALSE, gShellLevel1HiiHandle, (EFI_STRING_ID)(PcdGet8(PcdShellSupportLevel) < 3 ? 0 : STRING_TOKEN(STR_GET_HELP_ENDFOR)));

  return (EFI_SUCCESS);

위의 방법은 Shell command support를 다른 모듈로 분할하는 꽤나 괜찮은 방법이다. 이 기능을 사용하면 필요한 command support를 통해 Shell 이미지를 쉽게 컴파일할 수 있다.

Last updated