主页

Ext4文件系统

2023-05-12
linux

  • 一个ext4文件系统被分割为多个block groups,block groups的大小在sb.s_blocks_per_group块中指定。
  • 默认情况下一个块的大小是4KiB,此时每个block group会包含32768个块,即一个group的大小为128MiB。
  • ext4中块是最小的分配单位。
  • ext4文件系统采用小端模式,数值的低字节存储在低地址处,高字节存储在高地址处。比如数“12 34 56 78”,小端存放的形式如下“78 56 34 12”。大端存放的形式为“12 34 56 78”。
  • 一个标准的block group布局如下(并非所有的块都是此布局):
  • centos7下使用dumpe2fs获取到的ext4文件系统的信息
  • ext4会保留一些inode作为特殊用途,如下:

ext4文件系统 #

目录 #

  • 目录也是文件,目录有对应的inode和data blocks,目录的data block中存放多个ext4_dir_entry_2,结构如下:
  • 文件类型可以取的值如下:
  • 案例
    • 0D000000 1000 05 01 6673746162 000000
    • 第一块表示inode号
    • 第二块表示此结构体长度为0x0010,即16,因为ext4采用小端存放,所以高位在高地址,低位在低地址。
    • 第三块表示文件名为5字节
    • 第四块表示文件类型为普通文件
    • 第五块是文件名,对应fstab,f的ascii是0x66,s的是0x73,t是0x75。
    • 最后一块是填充位,结构体大小必须是最大对其数的整数倍。
查看更多

PCI桥和PCI设备

2023-05-04
bios

PCI桥与PCI设备的配置空间 #

  • 每一个PCI设备中(包括PCI桥)都包含一个配置空间。这个配置空间由HOST主桥管理,而PCI桥可以转发来自HOST主桥的配置访问。
  • PCI设备的ID号由总线号(Bus Number)、设备号(Device Number)和功能号(Function Number)组成。
  • Bus号在HOST主桥遍历PCI总线树时确定。系统软件使用DFS算法扫描PCI总线树上的所有PCI总线,并依次进行编号。
  • Function号与PCI设备的具体设计有关。
  • X86处理器定义了两个I/O端口寄存器,分别为CONFIG_ADDRESS(0xCF8)和CONFIG_DATA(0xCFC),X86处理器使用这两个I/O端口访问PCI设备的配置空间。
  • PCI agent使用的配置空间如下图所示:
  • 图2-9
    • (这张图要从右往左看)Vendor ID代表PCI设备的生产厂商,Device ID代表这个厂商生产的具体设备。
    • Revision ID寄存器记录PCI设备的版本号。
    • Class Code寄存器记载PCI设备的分类,该寄存器由三个字段组成,分别是Base Class Code、Sub Class Code和Interface,其中Base Class Code将PCI设备分类为显卡、网卡、PCI桥等设备。当Base Class Code是0X06,Sub class code是0x04、interface寄存器为0x00时,表示当前的PCI设备是一个桥。
    • Header Type寄存器,第7位为1表示当前PCI设备是多Function设备,为0表示当前PCI设备是单Function设备,第0~6位表示当前PCI设备的类型,为0表示该设备使用PCI Agent设备的配置空间,为1表示使用PCI桥的配置空间。
    • Subsystem ID和Subsystem Vendor ID:用于区分设备,有些通过4个ID可以确定一个设备。
    • Expansion ROM base address寄存器:存放Option ROM程序的基地址。
    • Capabilities Pointer寄存器:该寄存器存放Capabilities寄存器组的基地址,所有的PCIe设备都要支持Power Management Capability结构和PCI Express Capability结构,所以通过改指针可以找到这两个capability的位置。
    • BAR寄存器保存PCI设备使用的地址空间的基地址。其中每一个设备最多可以有6个基地址空间。

  • PCI 桥的配置空间如下(图2-10)
  • 图2-10
  • PCI桥中只有两组BAR寄存器,这两组寄存器是可选的。如果PCI桥中不存在私有寄存器,可以不使用这两组寄存器设置BAR空间。
  • Primary Bus寄存器保存上游的PCI总线号,Subordinate Bus寄存器存放当前PCI子树中编号最大的PCI总线树,Secondary Bus寄存器存放当前PCI桥使用的总线号。一个PCI桥管理的PCI总线号在Secondary Bus ~ Subordinate Bus之间。这两个寄存器的值由系统软件遍历PCI总线树时设置。
  • I/O Limit和I/O Base寄存器:存放PCI子树中所有PCI设备使用的I/O地址空间的基地址和大小。
  • MemoryLimit和Memory Base寄存器:存放PCI子树中PCI设备使用的MMIO基址和大小。
  • Prefetchable Memory Limit和Prefetchable Memory Base:存放PCI子树中PCI设备使用的可预取MMIO基址和大小。
  • I/O Base Upper 16 Bits和I/O Limit Upper 16寄存器:如果PCI桥支持32位I/O端口,这组寄存器提供I/O端口的高16位地址。

读取slot capabilities #

  • ep设备的SLOT号位于上一级桥的slot capabilities中,slot capabilities位于PCI Express Capability Structure中。PCI Express Capability Structure结构如下图,其Cap ID为0x10。
  • slot号位于slot capabilities的19~31位。

案例 #

  • 下图硬盘的BDF为86:00.0,槽位号为37
  • 其上一级桥的slot capabilities位置如下图,01 28 00 60转换为二进制为0000 0001 0010 1000 0000 0000 0110 0000,其中31~19位为0000 0001 0010 1,将此二进制转为16进制为37。

Protocol

2023-04-15
bios

  • Protocol是UEFI提供的接口函数。
  • 每个Protocol都包含GUID、Protocol接口的结构体、和Protocol接口函数(Protocol服务)。
  • 3个使用Protocol的函数OpenProtocol、HandleProtocol、LocateProtocol找到Protocol。
  • .efi文件(如EFI_DRIVER)加载到内存后被称为Image,ImageHandle是Image的句柄。一般模块的入口函数会有一个ImageHandle入参,该参数指向了内存中的Image。
  • UEFI扫描总线后,会为每个设备建立一个ControllerHandle句柄。即ControllerHandle指向了某个硬件。
  • 每个Handle(对应的结构体为IHANDLE)都会有一个Protocols链表,存放自己的Protocol。所有的IHANDLE通过AllHandles链接起来。
typedef struct {
  UINTN         Signature;
  /// All handles list of IHANDLE
  LIST_ENTRY    AllHandles;
  /// List of PROTOCOL_INTERFACE's for this handle
  LIST_ENTRY    Protocols;
  UINTN         LocateRequest;
  /// The Handle Database Key value when this handle was last created or modified
  UINT64        Key;
} IHANDLE;

OpenProtocol() #

typedef
EFI_STATUS
(EFIAPI *EFI_OPEN_PROTOCOL)(
  IN  EFI_HANDLE                Handle, //安装了此Protocol的Handle
  IN  EFI_GUID                  *Protocol,  //要打开的Protocol的GUID
  OUT VOID                      **Interface  OPTIONAL,  //返回打开的Protocol
  IN  EFI_HANDLE                AgentHandle,    //使用此Protocol的Image(存疑)
  IN  EFI_HANDLE                ControllerHandle, //如果打开的是Protocol是符合UEFI驱动模型的驱动,则此参数为控制Protocol接口的控制器,否则为可选的,可能为NULL
  IN  UINT32                    Attributes  //打开Protocol的参数
  );
  • 对于符合UEFI驱动模型的UEFI驱动而言,ControllerHandle是拥有该驱动的控制器,AgentHandle是拥有该EFI_DRIVER_BINGDING_PROTOCOL实例的句柄;
  • 对于UEFI应用而言,ControllerHandle可以忽略,AgentHandle是该程序的句柄,即UefiMain函数的第一个参数。

HandleProtocol() #

  • HandleProtocol不需要提供AgentHandle、ControllerHandle和Attributes。
  • HandleProtocol的AgentHandle为gDxeCoreImageHandle,ControllerHandle为NULL,Attributes为EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL。
/**
  @retval EFI_SUCCESS           成功返回指定的Protocol
  @retval EFI_UNSUPPORTED       此Handle没有安装此Protocol
**/
typedef
EFI_STATUS
(EFIAPI *EFI_HANDLE_PROTOCOL)(
  IN  EFI_HANDLE               Handle,  //安装了此Protocol的Handle
  IN  EFI_GUID                 *Protocol,   //此Protocol的GUID
  OUT VOID                     **Interface  //返回此Protocol的实例
  );

LocateProtocol() #

  • 当仅有一个Handle安装了某个Protocol时,可以使用LocateProtocol,该函数不需要提供安装了该Protocol的Handle。
  • 如果多个Handle都安装了某个Protocol,就会顺序遍历HANDLE链表,找到第一个该Protocol实例。
typedef
EFI_STATUS
(EFIAPI *EFI_LOCATE_PROTOCOL)(
  IN  EFI_GUID  *Protocol,  //此Protocol的GUID
  IN  VOID      *Registration  OPTIONAL,    //不知道干啥用的,描述时从RegisterProtocolNotify()获得的key
  OUT VOID      **Interface //返回此Protocol的实例
  );

EFI Driver Binding Protocol #

  • 从功能上划分,UEFI驱动分为以下类别:
    • 符合UEFI驱动模型的驱动(UEFI Driver Model):包括总线驱动(Bus Drivers)、设备驱动(Device Drivers)和混合驱动(Hybrid Drivers),一般用来驱动对应的硬件设备。
    • 服务型驱动(Service Drivers):这类驱动不管理任何设备,一般用来产生Protocol。
    • 初始化驱动(Initializing Drivers):不会产生任何句柄,也不会增加任何Protocol到系统数据库,主要用来初始化一些操作,执行完后就从系统内存中卸载。
    • 根桥型驱动(Root Bridge Drivers):用来初始化平台上的根桥控制器,并产生一个设备地址Protocol,以及访问总线设备的Protocol,一般用来通过总线驱动访问设备。

  • 一个完整的符合UEFI驱动模型的驱动程序,大致可分为EFI Driver Binding Protocol和驱动本身提供的服务。前者用来管理驱动,后者才是用户需要使用提供的部分。
  • 所提供的服务一般为多个Protocol。
  • 为了方便用户使用,驱动程序一般还会包括EFI Component Name Protocol,这个Protocol用来显示驱动信息。
  • EFI_DRIVER_BINDING_PROTOCOL的结构体如下:
struct _EFI_DRIVER_BINDING_PROTOCOL {
  EFI_DRIVER_BINDING_SUPPORTED    Supported;  //检查设备控制器是否支持驱动
  EFI_DRIVER_BINDING_START        Start;  //安装驱动并启动设备
  EFI_DRIVER_BINDING_STOP         Stop; //停止设备并卸载驱动
  UINT32        Version;  //版本
  EFI_HANDLE    ImageHandle;  //镜像句柄
  EFI_HANDLE    DriverBindingHandle;
};
  • 接口变量ImageHandle是产生此Protocol实例的镜像句柄,DriverBindingHandle是安装了Protocol实例的句柄。

Supported()接口函数 #

  • Supported()接口函数用来检测给定的设备控制器是否支持某驱动,函数原型如下:
typedef
EFI_STATUS
(EFIAPI *EFI_DRIVER_BINDING_SUPPORTED)(
  //Protocol实例
  IN EFI_DRIVER_BINDING_PROTOCOL            *This,
  //设备控制器句柄
  IN EFI_HANDLE                             ControllerHandle,
  //此参数对设备型驱动无效
  IN EFI_DEVICE_PATH_PROTOCOL               *RemainingDevicePath OPTIONAL
  );

Start()接口函数 #

  • 用来将驱动安装到设备上,并启动硬件设备。一般在此函数中使用InstallProtocolInterface()或InstallMultipleProtocolInterface()函数进行安装。
typedef
EFI_STATUS
(EFIAPI *EFI_DRIVER_BINDING_START)(
  IN EFI_DRIVER_BINDING_PROTOCOL            *This,
  IN EFI_HANDLE                             ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL               *RemainingDevicePath OPTIONAL
  );

Stop()接口函数 #

  • 用于停止硬件设备,并卸载驱动。
typedef
EFI_STATUS
(EFIAPI *EFI_DRIVER_BINDING_STOP)(
  IN EFI_DRIVER_BINDING_PROTOCOL            *This,  //Protocol实例
  IN  EFI_HANDLE                            ControllerHandle, //停止此控制器句柄上对应的驱动
  IN  UINTN                                 NumberOfChildren, //子控制器数量
  IN  EFI_HANDLE                            *ChildHandleBuffer OPTIONAL //子控制器数组
  );

EFI Component Name Protocol #

  • 为了方便用户使用,UEFI驱动通常会提供名字,以便向用户显示驱动信息。
  • 此功能由EFI_COMPONENT_NAME_PROTOCOL和EFI_COMPONENT_NAME2_PROTOCOL实现,这两种Protocol的功能相同,结构体相同,仅语言代码的格式不同,前者使用的是ISO 639-2语言代码,后者使用的是RFC4646语言代码。

UEFI的不同文件

2023-04-15
bios

  • 模块(module)是UEFI上最小的可单独编译的代码单元,它包含INF文件,源代码和二进制文件。INF文件用来描述文件的行为。
  • 包(package)是由模块、平台描述文件(DSC)和包声明文件(DEC)组成,可以不包含模块,也可以包含多个模块。
  • EDK2中定义了很多类型的模块:
    类型 说明
    BASE 常用于库模块的开发
    SEC
    PEI_CORE
    DXE_CORE
    DXE_DRIVER
    DXE_RUNTIME_DRIVER
    DXE_SAL_DRIVER
    DXE_SMM_DRIVER
    UEFI_DRIVER UEFI启动模块
    UEFI_APPLICATION UEFI应用模块

搭建UEFI工程模块 #

  • UEFI编译过程如下:

DSC文件 #

  • dsc文件(description)是平台描述文件,描述了模块、库和组件如何编译。文件中还包含很多的节标志如[Defines]
  • 在dsc文件中,经常用!include来包含其他文件,用#来表示注释。
  • [Defines]中定义了各种变量,必须是dsc文件中的第一个。语法格式如下:
[Defines]
  PLATFORM_NAME                  = MdeModule
  PLATFORM_GUID                  = 587CE499-6CBE-43cd-94E2-186218569478
  PLATFORM_VERSION               = 0.98
  DSC_SPECIFICATION              = 0x00010005
  #可选
  OUTPUT_DIRECTORY               = Build/MdeModule
  SUPPORTED_ARCHITECTURES        = IA32|X64|EBC|ARM|AARCH64|RISCV64|LOONGARCH64
  BUILD_TARGETS                  = DEBUG|RELEASE|NOOPT
  SKUID_IDENTIFIER               = DEFAULT
  • [BuildOptions]中给出编译器和相关的编译参数。

  • [LibraryClasses]用来提供模块所使用的库入口,格式如下:

[LibraryClasses]
  PeiCoreEntryPoint|MdePkg/Library/PeiCoreEntryPoint/PeiCoreEntryPoint.inf
  PeimEntryPoint|MdePkg/Library/PeimEntryPoint/PeimEntryPoint.inf
  • [Components]用来定义模块编译,指定模块的INF文件所在的位置。格式如下:
[Components]
  MdeModulePkg/Application/HelloWorld/HelloWorld.inf
  MdeModulePkg/Application/DumpDynPcd/DumpDynPcd.inf
  
  #这种格式下,{}内的内容只对当前的INF文件有效
  AppPkg/Applications/LibSample/MyLibApp/MyLibApp.inf{
    <LibraryClasses>
    MyLibraryLib|AppPkg/Applications/LibSample/MyLibrary/MyLibrary.inf
  }

INF文件 #

  • inf文件是模块的工程文件,描述了模块的属性,如模块由哪些代码组成、依赖了哪些库等。

  • EDK2的模块一般位于包的子目录下,如果是提供库的模块一般位于Library子目录下,如果是UEFI Application,一般位于Application下。

  • INF文件包含很多节标志,包括[Defines][Sources]等。

  • [Defines]中定义了各种变量,后续编译步骤中需要使用这些变量。

[Defines]
  INF_VERSION                    = 0x00010005
  BASE_NAME                      = Uefi_Main
  FILE_GUID                      = 6937936E-ED34-44ab-AE97-1FA5E7ED2116
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 1.0
  ENTRY_POINT                    = UefiMain
  • [Sources]用于列出模块中所有的源文件和资源文件,这些文件位于inf所在的目录或者子目录中。
[Sources]
  Uefi_Main.c
  • [BuildOptions]和dsc文件的[BuildOptions]语法格式基本相同,INF文件只对本模块有效,DSC对包下所有模块有效。
  • [Protocols]列出了模块时用到的协议,在INF文件中列出的是协议的GUID。
[Protocols]
  gEfiSimpleTextInputExProtocolGuid
  gEfiGraphicsOutputProtocolGuid
  • LibraryClasses列出本模块需要链接的库。模块如果需要添加库,一般需要:
    1. 在INF文件下的[LibraryClasses]中添加库名
    2. 在DES文件的[LibraryClasses]中寻找这个库,如果没有,则需要添加编译此库的INF文件。
[LibraryClasses]
  UefiLib
  ShellCEntryLib
  • [Packages]列出本模块引用的所有包的DEC文件。
[Packages]
  MdePkg/MdePkg.dec
  ShellPkg/ShellPkg.dec

3种入口函数的UEFI应用 #

  • <Uefi.h> 定义了UEFI中的基本数据类型和核心数据结构。

  • <Library/UefiLib.h>提供通用的库函数。

  • <Library/BaseLib.h>提供字符串处理、数学、文件路径处理等相关库函数。

  • <Library/BaseMemoryLib.h> 处理内存的库函数。

  • <Library/DebugLib.h>功能调试输出的库函数。

  • ImageHandle指向了模块自身加载到内存的Image对象。SystemTable是UEFI应用和UEFI内核交互的桥梁,通过它可以获得UEFI提供的各种服务,包括BS服务和CS服务。

  • 在EFI_SYSTEM_TABLE的结构体中,提供了访问BS服务和RT服务的指针。针对一些常用的Protocol的接口,如ConsoleIn(键盘)和ConsoleOut(屏幕)也提供了访问指针。

FDF文件 #

  • Flash Description File FDF文件用来描述固件在Flash中的布局和位置。
  • 一般来说,生成固件的源码中只有一个FDF文件,其作用是规定把哪些包编入Flash中,并确定编译的位置。
  • FDF文件由[Defines] [FD] [FV] [Capsule] [VTF] [Rule] [OptionRom]等几个节组成。
  1. [Defines]
  2. [FD]
    • Firmware Device 即固件设备,常用的BIOS ROM就是一个FD。
  3. [FV]
    • Fireware Volume FV是固件的逻辑区域,相当于FD上的分区。这个节定义了镜像包含的组件和模块。

UEFI启动流程

2023-03-17
bios

BIOS在计算机系统中的作用 #

  • BIOS全称为基本输入输出系统,时存储在主板ROM中的一组程序代码,包括:
    • 加电自检程序,用于开机时对硬件的检测。
    • 系统初始化代码,包括硬件设备的初始化等。
    • 基本的外围I/O处理的子程序代码。
    • CMOS设置程序。

UEFI系统组成 #

  • UEFI提供给操作系统的接口包括启动服务(Boot Services,BS)和运行时服务(Runtime Service,RT)以及丰富的Protocol。
  • 从OS Loader被加载,到OS Loader执行ExitBootServices这段时间内,是从UEFI环境向操作系统过渡的过程。这个过程中,OS Loader通过BS和RT使用UEFI提供的服务,将计算机系统资源逐渐转移到自己手中,这个过程称为TSL。
  • 当OS Loader完全掌握了计算机系统资源时,BS也完成了使命。OS Loader调用ExitBootServices结束BS并回收BS占用的资源,之后计算机系统进入UEFI Runtime阶段。
  • 在Runtime阶段只有RT继续为OS提供服务,BS已经从计算机系统中销毁。

  • UEFI Images 包含可执行代码的二进制文件。
  • UEFI Services 是平台调用接口的集合,允许UEFI程序和操作系统调用。
  • UEFI Protocol 是一种数据结构,包含全局唯一标识符GUID、接口数据结构和服务。
  • UEFI System Table 所有UEFI镜像都会接到一个指向UEFI系统表的指针,通过它可以访问固件提供的UEFI Protocol。
  • 启动服务提供的服务项包括:
    • Event服务:允许程序进行异步操作。
    • Timer(定时器)服务:配合Event提供定时器的功能。
    • 内存管理:提供内存的分配和释放服务,管理系统的内存映射。
    • Protocol服务:
    • Image服务:
    • 其他服务:
  • 运行时服务提供的服务项包括:
    • 系统变量服务:读取或设置系统变量。
    • 时间服务:提供读取和设定系统时间的功能。
    • 内存虚拟地址服务:提供将内存的物理地址转换为虚拟地址的服务。
    • 其他服务:如重启系统、更新BIOS等。

UEFI系统的启动过程 #

1. SEC阶段(Security Phase) #

  • UEFI系统开机或重启进入SEC阶段,它执行以下四种任务:
    • 接收并处理系统启动和重启信号。
    • 初始化临时存储区域:系统运行在SEC阶段时,仅CPU初始化,各种外部设备和内存都没有被初始化,因此系统需要一些临时的RAM区域。 临时RAM只能位于CPU内部。
    • 作为可信系统的根:作为取得对系统控制权的第一部分,SEC阶段是整个可信系统的根。
    • 传递系统参数给下一阶段:SEC阶段的一切工作都是为PEI阶段准备,最终将控制权交给PEI,同时要将现阶段的成果汇报给PEI。汇报的手段是将如下信息作为参数传递到PEI的入口函数。
      • 系统当前状态。
      • 可启动固件的地址和大小。
      • 临时RAM区域的地址和大小。
      • 栈的地址和大小。

2. PEI阶段(Pre-EFI Initialization) #

  • PEI主要是为DXE准备执行环境,PEI的执行流程如下:
  • 从功能上来讲,PEI可以分为:
    • PEI内核(PEI Foundation):负责PEI基础服务和流程。
    • PEIM(PEI Module)派遣器:找到系统中所有的PEI模块,并根据依赖关系按顺序执行PEIM。PEI阶段对系统的初始化主要是由PEIM完成的。
  • PEIM之间的通信是通过PPI(PEIM-to-PEIM Interfaces)完成。
  • 在PEI阶段会初始化内存。

3. DXE阶段(Drier Execution Environment) #

  • DXE阶段内存可以完全被使用,DXE执行阶段的流程如下:
  • DXE从功能上可以分为以下两部分:
    • DXE内核:负责DXE基础服务和执行流程。
    • DXE派遣器:负责调度执行DXE驱动,初始化系统设备。
  • 每个DXE驱动是一个独立的模块,DXE驱动之间通过Protocol通信。Protocol是一个特殊的结构体,每个Protocol对应一个GUID。
  • 当所有的Driver都执行完后,系统完成初始化,调用BDS的入口函数,进入到BDS阶段。

4. BDS阶段(Boot Device Selection) #

  • 主要功能是执行启动策略:
    • 初始化控制台设备。
    • 加载必要的设备驱动。
    • 根据系统设置加载和执行启动项。
  • 用户选中的某个启动项后,OS Loader启动,系统进入TSL阶段。

5. TSL阶段(Transient System Load) #

  • TSL是OS Loader执行的第一阶段,在这个阶段OS Loader作为一个UEFI应用程序运行,系统资源仍由UEFI内核控制。
  • 当启动服务的ExitBootServices服务被调用后,系统进入Run Time阶段。
  • TSL阶段被称为临时系统,它存在的目的就是为操作系统加载器准备执行环境。

6. RT阶段(Run Time) #

  • 系统进入RT阶段后,系统的控制权从UEFI内核转交到OS Loader手中,UEFI占用的各种资源被回收到OS Loader,仅有UEFI运行时服务保留给OS Loader和OS使用。
  • 随着OS Loader的执行,OS最终取得对系统的控制权。

7. AL阶段(After Life) #

  • 在RT阶段,如果系统遇到灾难性错误,固件需要提供错误处理和灾难恢复机制。

GCC和Makefile

2023-02-18
c语言

GCC #

GCC编译 #

#预处理
gcc -E -o hello.i hello.c
#编译(包含预处理)
gcc -S -o hello.s hello.i
#汇编(包含预处理和编译)
gcc -c -o hello.o hello.s
#链接(包含上面的步骤)
gcc -o hello hello.o

预处理 #

  • 源文件中以"#“开头的命令被称为预处理命令,如”#include"、"#define"、"#ifdef"等,预处理是将要包含的文件插入到原文件中,将宏定义展开、根据条件选择要使用的代码,最后将结果输出到一个文件中等待后续处理。

编译 #

  • 编译是将预处理后的文件翻译成汇编代码,用到的工具为cc1。

汇编 #

  • 汇编试讲汇编代码翻译成机器码,在linux上一般表现为ELF目标文件(OBJ文件),用到的工具为as。

链接 #

  • 链接是将生成的OBJ代码和系统库的OBJ文件、库文件链接起来,最终生成了可以在特定平台运行的可执行文件,用到的工具为ld或collect2.

常用选项 #

参数 描述
-E 预处理
-c 把预处理、编译、汇编都做的,但是不链接
-o 指定输出文件
-I 指定头文件目录
-L 指定链接时使用库文件的目录
-l(小L) 指定链接哪一个库文件,如-labc就是链接libabc.so
-v 显示编译器驱动程序、预处理器、编译器办吧等信息。
-Wall 打开所有需要注意的警告信息
#include <xxx> //在标准库的目录开始搜索(包括使用-Idir定义的目录)
#include "xxx" //先从用户的工作目录开始搜索,再搜索标准库目录

#

  • 动态库
#创建动态链接库
gcc -shared -o libsub.so sub.o sub1.o
#使用当前目录下得sub库
gcc -o test main.o -lsub -L ./

#运行时需要指定动态库的位置,可以将libsub.so移动到/lib目录下,或者配置一个环境变量
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/xiaox
  • 静态库
#生成静态库
ar crs libsub.a sub.o sub2.o
#将静态库打包到程序里
gcc -o tet main.o libsub.a

命令 #

#列出头文件目录、库目录(LIBRARY_PATH)等
echo 'main(){}'| gcc -E -v -

Makefile #

  • 格式如下:
target : prerequires1 prerequires2 ...
	command1
	command2
	...

变量 #

A := XXX //立即变量
B = XXX //延时变量,在使用时才生效
C += yyy //拼接字符串
D ?= xxx //如果D之前定义过,则此语句无效;如果之前定义值,此语句定义。

#

A := $(C)
B = $(C)
C = abc
D ?= weidongshan
all:
@echo A = $(A)
@echo B = $(B)
@echo D = $(D)
C += 123

#结果
#A = 立即变量定义式就确定,所以A的值为空
#B = abc 123 延时变量只有在用到时才确定,当执行make时,会解析Makefile里面的所有变量。
#D = weidongshan 因为D在前面没有定义,所以是此值。

通配符 #

  • *.c 表示所有的.c结尾的文件。
  • $@表示target。
  • $<表示第一个依赖文件。
  • $^表示所有的依赖文件。

假想目标.PHONY #

  • target并非只要求是文件,也可以是个标记(假想目标),声明要采取的动作。
clean :
	rm -rf *.o

.PHONY : clean

函数 #

  • foreach
#对于list中的每一个var,执行text的动作
$(foreach var,list,text)

#例
A := a b c
B := $(foreach f, $(A), $(f).c)
all : 
	@echo B = $(B)
  • filter/filter-out
$(filter pattern...,text) # 在text中取出符合patten格式的值
$(filter-out pattern...,text) # 在text中取出不符合patten格式的值

#例
A := a b c dxx
B := $(filter %xx, $(A))

all :
	@echo $(B)
#结果为dxx
  • patsubst
#如果var中的值匹配pattern,就将它替换为replacement
$(patsubst pattern, replacement, $(var))

#例
files := a.c b.c c.c d.c e.c abc
dep_files := $(patsubst %.c,%.d,$(files))
all :
	@echo dep_files = $(dep_files)
#结果为 dep_files = a.d b.d c.d d.d e.d abc

头文件依赖 #

  • 通过命令,自动生成依赖的的头文件,当头文件发生变化时,重新生成对应的文件。
#-M获取c.c依赖的头文件
#-MF将依赖输出的c.d的文件
gcc -M -MF c.d c.c

#

objs = a.o b.o c.o
dep_files := $(patsubst %,.%.d, $(objs))
dep_files := $(wildcard $(dep_files))

test : $(objs)
	gcc -o test $^

%.o : %.c
	gcc -c -o $@ $< -M -MF .$@.d

ifneq ($(dep_files),)
	include $(dep_files)
endif

clean:
	rm *.o test
	rm $(dep_files)

.PHONY: clean