UEFI原理与编程第二章学习-其它类型的工程模块

其它类型工程模块

   常用的工程模块除标准应用程序工程模块外,还有Shell应用程序工程模块、使用main函数的应用程序工程模块、库模块和驱动模块。下文依次对这几个模块进行简单的介绍。

  • 1、Shell应用程序工程模块

    从02_01-4可以看出,标准应用程序处理名师行参数很不方便?。而在Shell中执行的命令通常都会带有命令行参数。EDK2提供了一种特殊的应用程序工程模块,即Shell应用程序工程模块。该模块以 INTN ShellAppMain(IN UINTN Argc, IN CHAER16 ** Argv)作为入口函数。

       简单的Shell应用程序工程模块示例:

    //Shell应用程序工程模块入口函数
    #include<Uefi.h>
    #include<Librry/UefiBootServicesTableLib.h>
    INTN ShellAppMain (IN UINTN Argc, IN CHAR16 **Argv)
    {
    	gST->ConOut->OutputString(gST-> ConOut, L"HelloWorld\n");
        return 0;
    }
    
    

    对比:

    //标准应用程序工程模块入口函数
    #include<Uefi.h>
    EFI_STATUS UefiMain(IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLLE *SystemTable)
    {
        SystemTable->ConOut->OutputString(SystemTable->ConOut, L"HelloWorld\n");
        return EFI_SUCCESS;
    }
    
    

       对比可以发现,Shell应用程序工程模块入口函数的参数没有SystemTable,故要通过gST使用系统表。入口函数ShellAppMain的第一个参数Argc是命令行参数的个数,第二个参数Argv是命令行参数参数列表。Argv列表中的每个参数都是Unicode字符串(CHAR16*类型的字符串)。UEFI中使用的字符串多为Unicode字符串。

       标准应用程序工程模块由工程文件和源文件组成,Shell应用工程的组成也一样。介绍完源文件编写后看一下Shell应用程序工程文件的编写。

    1. [Defines]块。在[Defines]块中需要把MODULE_TYPE设置成UEFI_APPLICATION;ENTRY_POINT设置为ShellCEntryLib(标准应用程序中ENTRY_POINT的值为UefiMain,同时在源程序中开发者还需要实现UefiMain函数),开发者必须提供由ENTRY_POINT指定的入口函数。在Shell应用程序工程模块中ENTRY_POINT的值必须为ShellCEntryLib,且源程序中开发者必须提供ShellAppMain函数。
    2. [Packages]块。在[Packages]块中必须列出MdePkg/MdePkg.dec和ShellPkg/ShellPkg.dec。
    3. [LibraryClasses]块。在[LibraryClasses]块中必须列出ShellCEntryLib,通常还要列出UefiBootServiceTableLib(BS服务?)和UefiLib。

       Shell应用程序工程模块完整的工程文件如下:

    [Defines]
    	INF_VERSION = 0x00010001
    	BASE_NAME = Main
    	FILE_GUID = 4ea97c46-7291-4dfd-b442-747010f3ce5g
    	MODULE_TYPE = UEFI_APPLICATION
    	VERSION_STRING = 0.1
    	ENTRY_POINT = ShellCEntryLib
    [Sources]
    	Main.c
    [Packages]
    	MdePkg/MdePkg.dec
    	ShellPkg/ShellPkg.dec
    [LibraryClasses]
    	ShellCEntryLib
    	UefiBootServicesTableLib
    	UefiLib
    

       ShellCEntryLib的源码位于ShellPkg\Library\UefiShellCEntryLib\UefiShellCEntryLib.c,源代码如下:

    //ShellCEntryLib库提供的应用程序入口函数,该函数最终调用用户入口函数ShellAppMain
    EFI_STATUS EFIAPI ShellCEntryLib(
    	IN EFI_HANDLE ImageHandle, 
    	IN EFI_SYSTEM_TABLE *SystemTable)
    {
    	INTN ReturnFormMain;
    	EFI_SHELL_PARAMETERS_PROTOCOL *EfiShellPagemetersProtocol //parameter:参数
    	EFI_SHELL_INTERFACE *EfiShellInterface;
    	EFI_STATUS Status;
    	ReturnFormMain = -1;
    	EfiShellParametersProtocol = NULL;
    	EfiShellInterface = NULL;
    	
    	//获取命令行参数
    	Status = SystemTable->BootServices->OpenProtocol(ImageHandle, &gEfiShellParametersProtocolGuid, (VOID **)&EfiShellParametersProtocol, ImageHandle, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
    	if(!EFI_ERROR(Status))
    	{
    		//通过Shell_2.0接口获得命令行参数
    		//调用用户的入口函数
    		ReturnFromMain = ShellAppMain (EfiShellParametersProtocol->Argc, EfiShellParametersProtocol->Argv);
    	}
    	else
    	{
    		//打开Shell_2.0 Protocol失败,尝试Shell 1.0
    		Status = SystemTable->BootServices->OpenProtocol(
    			ImageHandle, 
    			&gEfiShellInterfaceGUID, 
    			(VOID **)&EfiShellInterface, 
    			NULL, 
    			EFI_OPEN_PROTOCOL_GET_PROTOCOL);
    			
    			if(!EFI_ERROR(Status))
    			{
    				//通过Shell 1.0接口获取命令行参数
    				//调用用户的入口函数
    				ReturnFromMain = ShellAppMain (EfiShellInterface->Argc,
    				EfiShellInterface->Argv);
    			}
    			else
    				ASSERT(FALSE);
    			
    	}
    	
    	if(0 == ReturnFormMain)
    		return EFI_SUCCESS;
    	else
    		return EFI_UNSUPPORYED;
    	
    }
    
    

       可以看出,ShellCEntryLib函数的输入参数与UefiMain函数的输入参数完全相同,即ShellCEntryLib函数就是标准的 UEFI_application 入口函数。在ShellCEntryLib中,首先打开了EFI_SHELL_PARAMETERS_PROTOCOL,通过该数据结构得到参数个数Argc以及命令行参数数组Argv,然后调用ShellAppMain函数。在可执行文件被UEFI通过Load Image Protocol载入UEFI系统是,会在可执行文件的Image Handle上安装Shell Protocol和Shell Parameter Protocol。?EFI_SHELL_PARAMETERS_PROTOCOL数据结构的编码如下:

    //@filePath ShellPkg\Include\Protocol\EfiShellParameters.h
    typedef struct _EFI_SHELL_PARAMETERS_PROTOCOL
    {
        CHAR16 **Argv;//命令行参数数组,第一个参数是可执行文件的全路径 \?
        UINTN Argc;//Argv数组元素的个数
        SHELL_FILE_HANDLE StdIn;//标准输入句柄
        SHELL_FILE_HANDLE StOut;//标准输出句柄
        SHELL_FILE_HANDLE StdErr;//标准错误句柄
        /*
        句柄:
        Windows编程的基础。句柄指的是使用一个唯一的4字节型整数值,来标识应用程序中
    	的不同对象和同类中的不同示例。应用程序能通过句柄访问相应对象的信息,但这种句柄
    	与指针不同,应用程序不能通过句柄直接阅读文件中的信息。句柄是Windows系统用来标识
    	应用程序中建立的或是使用的资源的唯一整数。
        */
    } EFI_SHELL_PARAMETERS_PROTOCOL;
    
    
  • 2、使用main函数的应用程序工程模块

      EDK2也提供了使用main函数的应用程序工程模块,通常在此类应用程序中都会使用C标准库(StdLib)中的函数。下例是一个使用main函数的应用程序工程模块的简单示例。

    使用main函数的应用程序工程模块的源文件:

    #include<stdio.h>
    int main(int argc,char **argv)
    {
        printf("HelloWorld\n");
        return 0;
    }
    
    

    在工程文件中要进行如下设置:

    • [Defines]块,设置MODULE_TYPE为UEFI_APPLICATION;ENTRY_POINT为ShellCEntryLib。
    • [Source]块,设置ENTRY_POINT为ShellCEntryLib。
    • [Packages]块,列出MdePkg/MdePkg.dec、ShellPkg/ShellPkg.dec和StdLib/StdLib.dec。
    • [LibraryClasses]块,列出ShellCEntryLib库(提供ShellCEntryLib函数)、LibC库(提供ShellAppMain函数)及LibStdio库(提供printf函数)。
    • [Sources]块,列出源文件main.c。

       Shell应用程序工程模块使用了ShellCEntryLib,自己实现的ShellAppMain函数。而在使用main函数的应用程序工程模块中使用了StdLib库,StdLib库中提供了ShellAppMain函数,此时开发者只需要实现int main(int Argc, char** Argv)作为程序的入口函数供ShellAppMain调用即可。而真正的模块入口函数是ShellCEntryLib,调用过程为ShellCEntryLib->ShellAppMain->main。

    使用main函数的应用程序工程模块的工程文件:

    [Defines]
    	INF_VERSION = 0x00010001
    	BASE_NAME = Main
    	File_GUID = 4ea89cq23-1239-123j-129381d2jj21e
    	MODULE_TYPE = UEFI_APPLICATION
    	VERSION_STRING = 0.1
    	ENTRY_POINT = ShellCEntryLib
    [Sources]
    	main.c
    [Packages]
    	MdePkg/MdePkg.dec
    	ShellPkg/ShellPkg.dec
    	StdLib/StdLib.dec
    [LibraryClasses]
    	LibC
    	LibStdio
    	ShellCEntryLib
    

       如果程序中用到了printf()等标准C语言库的函数,那么一定要用该类型的应用程序工程模块。ShellCEntryLib函数中会调用StdLib的ShellAppMain(),这个函数会对StdLib进行初始化。StdLib的初始化完成后才可以调用StdLib库。/.

       通常使用main函数的应用程序工程模块在AppPkg环境下才能编译,所以需要将main.inf添加到AppPkg\AppPkg.dsc文件的[Components]中。如下:

    ## @file AppPkg.dsc
    [Components]
    	uefi\book\infs\main\main.inf
    
    

    然后使用如下命令编译该工程:

    build -p AppPkg\AppPkg.dsc -m uefi\book\infs\main\main.inf
    
    
  • 3、库模块

       开发大型工程的时候经常会用到库,比如开发视频解码程序时,会用到zlib库。在库模块的工程文件中,需要设置MODULE_TYPE为BASE;设置LIBRARY_CLASS为库的名称,比如zlib。同时不要设置ENTRY_POINT。[Packages]块列出库引用道德包,[LibraryClasses]列出包所依赖的其他库

    zlib库的工程文件:

    [Defines]
    	INF_VERSION = 0x00010001
    	BASE_NAME = zlib
    	FILE_GUID = 213asd41-eord-4352-1dcs-123sasdq123
    	VERSION_STRING = 1.0
    	MODULE_TYPE = BASE
    	LIBRARY_CLASS = zlib
    [Sources]
    	adler32.c
    	#此处不一一列举zlib中的源文件
    [Packages]
    	MdePkg/MdePkg.dec
    	MdeModulePkg/MdeModulePkg.dec
    	StdLib/StdLib.dec
    [LibraryClasses]
    	MemoryAllocationLib
    	BaseLib
    	UefiBootServicesTableLib
    	BaseMemoryLib
    	UefiLib
    	
    

    有的库仅能被某些特定的模块调用,编写时需要在工程文件中声明库的适用范围,声明方法为在[Defines]块的LIBRARY_CLASS变量中定义,格式如下:

    LIBRARY_CLASS = 库名 | 适用模块1 适用模块2

    如果想使zlib库仅能被应用程序工程模块调用,那么LIBRARY_CLASS需设置为:

    LIBRARY_CLASS = zlib | UEFI_APPLICATION

    为了使编写的库能被其他模块调用,还要在包的 .dsc 文件中声明该库。例如,要使AppPkg中的模块能调用zlib库,需要将zlib|zlib-1.2.6/zlib.inf(假设zlib-1.2.6在EDK2的根目录下)放到AppPkg\AppPkg.dsc文件的[LibraryClasses]中。如下所示:

    [LibraryClasses]
    	zlib|zlib-1.2.6/zlib.inf
    	
    

    在需要调用zlib的工程模块文件的[LibraryClasses]中添加zlib即可。

    [LibraryClasses]
    	zlib
    	
    

    如果库使用前需要初始化,则需要在库的工程文件中指定CONSTRUCTOR和DESTRUCTOR,CONSTRUCTOR函数会加入到ProcessLibraryConstructorList中,这个CONSTRUCTOR函数会在,在ENTRY_POINT之前执行;DESTRUCTOR函数会加入到ProcessLibraryDestructorList中,这个DESTRUCTOR就会在ENTRY_POINT之后执行。

    例如,如果zlib库在被调用前需要在InitializeLib()中初始化,程序结束之前需要调用LibDextructor()清理zlib库所占用的资源。工程文件中需要做以下设置:

    [Defines]
    	CONSTRUCTOR = InitializeLib
    	DESTRUCTOR = LibDestructor
    	
    

    还需要在库的源文件中提供这两个函数。zlib库中需要提供的构造函数和析构函数:

    RETURN_STATUS EFI_API InitializeLib()
    {
    	EFI_STATUS Status;
    	...//初始化库
    	return Status;
    }
    
    RETURN_STATUS EFI_API LibDestructor()
    {
    	EFI_STATUS Status;
    	...//清理库所占用的资源
    	return Status;
    }
    
    
  • 4、UEFI驱动模块

       在UEFI中,驱动分为两类:一类是符合UEFI驱动模型的驱动,模块类型为UEFI_DRIVER,这类模型称为UEFI驱动;另一类是不遵循UEFI驱动模型的驱动,模块类型包括DXE_DRIVER、DXE_SAL_DRIVER、DXE_SMM_DRIVER、DXE_RUNTIME_DRIVER,这类称为DXE驱动。(DEX为UEFI系统启动的第三步,其主要功能为运行驱动、配置系统环境

    驱动程序模块入口函数原型为:

    typedef EFI_STATUS API (*UEFI_ENTRYPOINT)(
        IN EFI_HANDLE ImageHandle,
        IN EFI_SYSTEM_TABLE *SystemTable);\?
    

       驱动与应用程序的最大区别就是驱动会常驻内存,而应用程序执行完毕后就会从内存中清除。驱动程序模块的工程文件中主要进行如下设置

    • [Defines]块,将MODULE_TYPE设置为UEFI_DRIVER。其余宏变量如INF_VERSION、BASE_NAME等保持基础即可。
    • [Sources]块,通常含有ComponentName.c,在这个文件中定义了驱动的名字,驱动安装后,这个名字会显示给用户。
    • [LibraryClasses]块,必须包含UefiDriverEntryPoint。

    DiskIo驱动的工程文件:

    //@file MdeModulePkg/Universal/Disk/DiskIoDxe/DiskIoDxe.inf
    [Defines]
    	INF_VERSION = 0x00010001
    	BASE_NAME = DiskIoDxe
    	FILE_GUID = UEFI_DRIVER
    	VERSION_STRING = 1.0
    	ENTRY_POINT = InitializeDiskIo
    	
    [Sources]
    	ComponentName.c
    	DiskIo.h
    	DiskIo.c
    	
    [Packages]
    	MdePkg/MdePkg.dec
    	
    [LibraryClasses]
    	UefiDriverEntryPoint
    	UefiBootServicesTableLib
    	MemoryAllocationLib
    	BaseMemoryLib
    	BaseLib
    	UefiLib
    	DebugLib
    	
    [Protocols]
    	gEfiDiskIoProtocolGuid		## BY_START
    	gEfiBlockIoProtocolGuid		## TO_START
    
    
  • 5、模块 工程文件小结

    本篇主要分析了模块的工程文件( .inf )文件,.inf文件帮助系统组织和编译工程。.inf文件有[Defines]、[Sources]、[Packages]、[LibraryClasses]、[Protocols]等几个部分组成,其中:

    • [Defines]部分定义了模块类型(MODULE_TYPE)、模块的名字(BASE_NAME)、模块的版本号(VERSION_STRING)及入口函数(ENTRY_POINT)等信息。
    • [Sources]部分定义了本模块包含的源文件或目标文件。
    • [Packages]部分指明了要引用的包,包中的头文件可以在库的源文件中引用。?
    • [LibraryClasses]部分列出了需要连接的库。
    • [Protocols]部分列出来本模块的Protocol。
    • [BuildOptions]部分列出了编译本模块中的源文件时需要用到的编译选项。
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值