Yocto理论篇 | Yocto共享状态缓存

  • Post author:
  • Post category:其他



一般OpenEmbedded构建系统从头开始构建一切,除非

BitBake

可以确定部件不需要重新构建。从根本上说,从头构建是有吸引力的,因为它意味着所有部件都是全新构建的,不存在可能导致问题的过时数据。当开发人员遇到问题时,他们通常会默认从头开始构建,这样他们从一开始就有一个已知的状态。


从零开始构建镜像对这个过程来说既是一个优点,也是一个缺点。如前一段所述,从头开始构建可以确保一切都是最新的,并且从已知的状态开始。然而,从头开始建设也需要更长的时间,因为它通常意味着重建不一定需要重建的东西。


Yocto Project实现了支持增量构建的共享状态代码。共享状态代码的实现回答了以下问题,这些问题是开放式增量构建支持系统中的基本障碍:


  • 系统的哪些部分发生了变化,哪些部分没有发生变化?

  • 如何删除和替换已更改的软件?

  • 不需要从头开始重建的预构建组件在可用时如何使用?


对于第一个问题,构建系统通过创建任务输入的校验和(或签名)来检测给定任务的“inputs”中的更改。如果校验和发生变化,系统假设输入已更改,需要重新运行任务。对于第二个问题,共享状态(sstate)代码跟踪哪些任务向构建过程添加了哪些输出。这意味着可以删除、升级或以其他方式操作给定任务的输出。第三个问题的部分解决方案解决了第二个问题,假设构建系统可以从远程位置获取sstate对象,并在认为它们是有效的情况下安装它们。


注意:


  • 构建系统不会将

    PR

    信息作为共享状态包的一部分进行维护。因此,存在影响维护共享状态提要的考虑因素。

  • 生成系统中支持增量生成的代码不是简单的代码。


下面将详细介绍整个增量构建体系结构、校验和(签名)以及共享状态。


1 总体架构


在确定需要构建系统的哪些部分时,BitBake是基于每个任务而不是按配方工作的。您可能会想知道为什么使用每个任务比每个配方优先级更高。为了帮助解释,考虑启用IPK打包后端,然后切换到DEB。在这种情况下,

do_install



do_package

任务输出仍然有效。但是,使用按配方的方法,构建将不包括

.deb

文件。因此,您必须使整个构建无效并重新运行它。重新运行所有内容并不是最好的解决方案。而且,在这种情况下,核心是必须“taught”很多关于具体任务的知识。这种方法不能很好地扩展,并且不允许用户在不接触打包的登台核心的情况下轻松地在层中添加新任务或作为外部配方。



2 校验和(签名)


共享状态代码使用校验和(任务输入的唯一签名)来确定任务是否需要再次运行。因为任务输入的更改会触发重新运行,因此流程需要检测给定任务的所有输入。对于shell任务,这是相当容易的,因为构建过程为每个任务生成一个“run”shell脚本,并且可以创建一个校验和,可以很好地了解任务的数据何时更改。


使问题复杂化的是,有些东西不应该包括在校验和中。首先,有一个给定任务的实际特定构建路径—

WORKDIR

。工作目录是否更改并不重要,因为它不应影响目标包的输出。此外,构建过程的目标是使本机包或跨包可重定位。


注意:本机包和交叉包都在构建主机上运行。但是,跨包为目标体系结构生成输出。因此校验和需要排除

WORKDIR

。排除工作目录的简单方法是将

WORKDIR

设置为某个固定值,并为“run”脚本创建校验和。另一个问题来自“run”脚本,这些脚本包含可能被调用或可能未被调用的函数。增量构建解决方案包含用于计算shell函数之间依赖关系的代码。这段代码用于将“run”脚本缩减到最小值集,从而缓解了这个问题,并使“run”脚本更具可读性。


到目前为止,shell脚本的解决方案已经存在。Python任务呢?即使这些任务比较困难,也可以采用同样的方法。这个过程需要弄清楚Python函数访问的变量以及它调用的函数。同样,增量构建解决方案包含的代码首先确定变量和函数的依赖关系,然后为用作任务输入的数据创建校验和。




WORKDIR

的例子一样,存在依赖关系应该被忽略的情况。对于这些情况,可以使用如下行指示生成过程忽略依赖项:

PACKAGE_ARCHS[vardepsexclude] = "MACHINE"           


此示例确保

PACKAGE_ARCHS

变量不依赖于

MACHINE

的值,即使它引用了

MACHINE

的值。


同样,在某些情况下,您需要添加BitBake无法找到的依赖项。您可以使用以下行来完成此操作:

PACKAGE_ARCHS[vardeps] = "MACHINE"


这个例子显式地添加

MACHINE

变量作为

PACKAGE_ARCHS

的依赖项。


作为一个例子,考虑一个内嵌Python的例子,BitBake无法找出依赖关系。在调试模式下运行(即使用

-DDD

)时,BitBake会在发现无法确定依赖关系的内容时生成输出。


到目前为止,仅讨论对任务的直接投入。基于直接输入的信息在代码中称为“basehash”。但是,任务的间接输入问题仍然存在——已经构建并存在于

Build Directory

中的项目。特定任务的校验和(或签名)需要添加特定任务所依赖的所有任务的哈希值。选择要添加的依赖项是一个策略决策。然而,其效果是生成一个主校验和,它结合了basehash和任务依赖项的散列。


在代码级别上,存在多种方法可以影响basehash和依赖任务哈希。在BitBake配置文件中,可以为BitBake提供一些额外的信息,以帮助它构造basehash。下面的语句有效地生成了一个全局变量依赖排除列表(即变量从未包含在任何校验和中):

BB_HASHBASE_WHITELIST ?= "TMPDIR FILE PATH PWD BB_TASKHASH BBPATH DL_DIR \
     SSTATE_DIR THISDIR FILESEXTRAPATHS FILE_DIRNAME HOME LOGNAME SHELL TERM \
     USER FILESPATH STAGING_DIR_HOST STAGING_DIR_TARGET COREBASE PRSERV_HOST \
     PRSERV_DUMPDIR PRSERV_DUMPFILE PRSERV_LOCKDOWN PARALLEL_MAKE \
     CCACHE_DIR EXTERNAL_TOOLCHAIN CCACHE CCACHE_DISABLE LICENSE_PATH SDKPKGSUFFIX"


前面的例子排除了

WORKDIR

,因为该变量实际上是作为

TMPDIR

中的路径构造的,

TMPDIR

位于白名单中。


决定通过依赖链包含哪些依赖任务哈希的规则更为复杂,通常使用Python函数来完成。

meta/lib/oe/sstatesig.py

显示了两个示例,并说明了如何在需要时将自己的策略插入到系统中。这个文件定义了

OE-Core

使用的两个基本签名生成器:“OEBasic”和“OEBasicHash”。默认情况下,BitBake中启用了一个虚拟的“noop”签名处理程序。这意味着行为与以前的版本没有变化。默认情况下,OE-Core通过中的此设置使用“OEBasicHash”签名处理程序

bitbake.conf

文件:

BB_SIGNATURE_HANDLER ?= "OEBasicHash" 


“OEBasicHash”

BB_SIGNATURE_HANDLER

与 “OEBasic” 版本相同,但将任务哈希添加到stamp文件中。这将导致更改任务哈希的任何元数据更改,从而自动导致任务再次运行。这就消除了对

PR

值的需要,对元数据的更改会自动在构建中产生涟漪。


还值得注意的是,这些签名生成器的最终结果是使一些依赖项和哈希信息可用于构建。这些信息包括:



  • BB_BASEHASH_task-



    taskname


    :配方中每个任务的基本哈希。


  • BB_BASEHASH_



    filename



    :



    taskname


    :每个依赖任务的基哈希。


  • BBHASHDEPS_



    filename



    :



    taskname


    :每个任务的任务相关性。


  • BB_TASKHASH

    :当前正在运行的任务的哈希值。


3 共享状态


解决前面讨论的共享状态和半个状态的校验和问题。问题的另一半是能够在构建期间使用校验和信息,以及能够重用或重建特定组件。



sstate

类是如何“capture”给定任务的快照的相对通用的实现。其思想是构建过程不关心任务输出的来源。输出可以是新生成的,也可以从某个地方下载和解包。换句话说,构建过程不需要担心它的起源。


存在两种类型的输出。一种是在

WORKDIR

中创建一个目录。一个很好的例子是

do_install



do_package

的输出。另一种类型的输出发生在将一组数据合并到共享目录树(如sysroot)中时。


Yocto项目团队试图将实现的细节隐藏在sstate类中。从用户的角度来看,将共享状态包装添加到任务中非常简单,这是一个取自

deploy

类的

do_deploy

示例:

DEPLOYDIR = "${WORKDIR}/deploy-${PN}"
SSTATETASKS += "do_deploy"
do_deploy[sstate-inputdirs] = "${DEPLOYDIR}"
do_deploy[sstate-outputdirs] = "${DEPLOY_DIR_IMAGE}"

python do_deploy_setscene () {
    sstate_setscene(d)
}
addtask do_deploy_setscene
do_deploy[dirs] = "${DEPLOYDIR} ${B}"
do_deploy[stamp-extra-info] = "${MACHINE_ARCH}"       


下表说明了前面的示例:




  • SSTATETASKS

    中添加“do_deploy”会在

    do_deploy

    任务之前和之后添加一些必需的sstate相关处理(在

    sstate

    类中实现)。


  • do_deploy[sstate-inputdirs] = "${DEPLOYDIR}"

    声明当正常运行时(即不使用sstate缓存时),

    do_deploy

    将其输出放在

    ${DEPLOYDIR}

    中。此输出将成为共享状态缓存的输入。


  • do_deploy[sstate-outputdirs] = "${DEPLOY_DIR_IMAGE}"

    这一行将共享状态缓存的内容复制到

    ${DEPLOY_DIR_IMAGE}



注意:如果

do_deploy

不在共享状态缓存中,或者如果它的输入校验和(签名)与缓存输出时不同,那么任务将运行以填充共享状态缓存,然后共享状态缓存的内容将复制到

${DEPLOY_DIR_IMAGE}

。如果

do_deploy

在共享状态缓存中,并且其签名指示缓存的输出仍然有效(即如果没有相关的任务输入发生更改),则共享状态缓存的内容将直接由

do_deploy_setscene

任务复制到

${DEPLOY_DIR_IMAGE}

,而跳过

do_deploy

任务。


  • 以下任务定义是使先前设置生效所需的粘合逻辑:
python do_deploy_setscene () {
     sstate_setscene(d)
}
addtask do_deploy_setscene           



sstate_setscene()

将上述标志作为输入,并通过共享状态缓存加速

do_deploy

任务(如果可能)。如果任务被加速,

sstate_setscene()

将返回True。否则,它将返回False,并运行正常的

do_deploy

任务。



  • do_deploy[dirs] = "${DEPLOYDIR} ${B}"

    这一行在

    do_deploy

    任务运行之前创建

    ${DEPLOYDIR}



    ${B}

    ,并且还将

    do_deploy

    的当前工作目录设置为

    ${B}



注意:在

sstate-inputdirs



sstate-outputdirs

相同的情况下,可以使用

sstate-plaindirs

。例如,要保留

do_package

任务的

${PKGD}



${PKGDEST}

输出,请使用以下命令:



  • do_deploy[stamp-extra-info] = "${MACHINE_ARCH}"

    这一行将额外的元数据附加到

    stamp

    文件中。在这种情况下,元数据使任务特定于机器的体系结构。


  • sstate-inputdirs



    sstate-outputdirs

    也可以用于多个目录。例如,以下命令将

    PKGDESTWORK



    SHLIBWORK

    声明为共享状态输入目录(填充共享状态缓存),将

    PKGDATA_DIR



    SHLIBSDIR

    声明为相应的共享状态输出目录:
do_package[sstate-inputdirs] = "${PKGDESTWORK} ${SHLIBSWORKDIR}"
do_package[sstate-outputdirs] = "${PKGDATA_DIR} ${SHLIBSDIR}"         

  • 这些方法还包括在操作共享状态目录结构时获取锁文件的能力,以防文件的添加或删除是敏感的:
do_package[sstate-lockfile] = "${PACKAGELOCK}"


在后台,共享状态代码通过在

SSTATE_DIR



SSTATE_MIRRORS

中查找共享状态文件来工作。下面是一个例子:

SSTATE_MIRRORS ?= "\
file://.* http://someserver.tld/share/sstate/PATH;downloadfilename=PATH \n \
file://.* file:///some/local/dir/sstate/PATH"


注意:共享状态目录(

SSTATE_DIR

)被组织成两个字符的子目录,其中子目录名基于哈希的前两个字符。如果镜像的共享状态目录结构与

SSTATE_DIR

具有相同的结构,则必须在URI中指定“PATH”以使生成系统能够映射到相应的子目录。共享状态包的有效性可以通过查看文件名来检测,因为文件名包含本节前面描述的任务校验和(或签名)。如果找到有效的共享状态包,生成过程将下载它并使用它来加速任务。构建过程在任务加速阶段使用

*_setscene

任务。BitBake在主执行代码之前经过这个阶段,并尝试加速它可以找到共享状态包的任何任务。如果任务的共享状态包可用,则使用共享状态包。这意味着任务及其依赖的任何任务都不会执行。


作为一个真实的例子,其目的是在构建基于IPK的映像时,只有

do_package_write_ipk

任务才会获取和提取它们的共享状态包。因为没有使用sysroot,所以它永远不会被提取。这也是为什么基于任务的方法比基于配方的方法更受欢迎的另一个原因,后者必须安装每个任务的输出。