天天看點

定制ARM 版本的ubuntu core (16.04)1.  先從snap 包講起1.2 snap 的構成?2.  關于ubuntu core3. 制作ARM 版本的ubuntu core4.  app 開發相關

1.  先從snap 包講起

ubuntu core 基本上是諸多snap 包堆積起來的一個系統,正如傳統的ubuntu 是debian 堆積起來的一樣。但不同的是ubuntu core 也是一個snap 包, 甚至 kernel, uboot 也分别是一個snap 包。是以,就讓我們先 了解下snap 包 是個甚麼東東吧。

1.1 什麼是snap ?

A

snap

:
  • is a squashFS filesystem containing your app code and a

    snap.yaml

    file containing specific metadata. It has a read-only file-system and, once installed, a writable area.
  • is self-contained. It bundles most of the libraries and runtimes it needs and can be updated and reverted without affecting the rest of the system.
  • is confined from the OS and other apps through security mechanisms, but can exchange content and functions with other snaps according to fine-grained policies controlled by the user and the OS defaults. 

1.2 snap 的構成?

    一個snap 包有兩個部分構成:

1)  目标程式;(也就是沒有snap 概念之前的,你使用的程式, 比如helloword.out)

2) 關于snap 包的配置檔案, snap 的配置檔案大有以下部分:

  • meta/snap.yaml

    - Basic snap details .
  • meta/hooks/

    - Hooks called on specific events.
  • meta/gui/icon.svg

    - Icon for the snap.
  • meta/gui/*.desktop

    - Desktop files for the snap .

隻有snap.yaml 必不可少,其他都是可選的,在特殊的需求下才用的上。 仍何一個snap 包, 

snap.yaml 描述了該snap 生成,安裝運作,及安全相關的 幾乎所有的資訊。

1.3 深入snap.yaml

name: simple
version: 1.0
apps:
    hello:
        command: bin/hello --world
           

一個helloworld 程式,再加一個這樣的yaml 就能生成一個snap 包。從上面的部分可以看出,一個 snap 的yaml 基本分成兩個部分,一個是Header部分, 用以聲明snap 的基本資訊。另一個是main block 部分, 用以聲明snap 的編譯,生成,和相關權限的内容。

我們先看Header部分的相關文法:

# The suggested snap name, constrained to the [a-z0-9] charset and inner
# dashes. The final name when the snap is installed is defined by the
# snap-declaration assertion associated with the snap, if any.
name: <snap name>

# Version of the software packed inside the snap. Has no semantic value
# in the system (no greater/lower-than rules are ever applied to it).
version: <version>

# More details about what is contained in the snap.
summary: <line>
description: <text>

# Type of snap, defaults to "app".
type: app | core | gadget | kernel

# List of architectures the snap may run on. Defaults to [all].
architectures:
    - all | amd64 | i386 | armhf

# Snaps can be setup to follow three different confinement policies
confinement:
    - strict | devmode | classic
# This defines the quality grade of the snap
grade:
    - stable | candidate | devel 
           

name 就是你要生成的snap 包的名字。

main block  部分主要由兩個子塊:  “app”  和 “part” 。

"part" 子塊描述了目标在打包時, 代碼如何導入,如何更改, 如何編譯(built);

“app” 子塊描述了從包中如何暴露出app /daemons/service,聲明了他們的權限,和運作時條件;

part 的常用文法如下:

part:

   <part name>:

     # 插件,snapcraft 提供了很多插件,一共不同的編譯打包需求,詳細請參照:https://docs.snapcraft.io/build-snaps/plugins

            plugin: <plugin name form https://docs.snapcraft.io/reference/plugins/>

           # A URL or path to some source tree to build. It can be local (‘./src/foo’) or remote (‘https://foo.org/…’), and can refer to a directory tree or a tarball or a revision control repository (‘git:…’).

           source: < URL or path >

          #In some cases the source string is not enough to identify the version control system or compression algorithm. The

source-type

key can tell Snapcraft exactly how to treat that content.

          source-type:

            -  

git

|

bzr

|

hg

|

svn

|

tar

|

deb

|

rpm

|

zip

        # If present, the shell script defined here is run before the

build

step of the plugin starts.

        # The working directory is the base build directory for the given part. The defined script is run with

/bin/sh

.

    prepare

:

              <command line>  

    #

If present, the shell script defined here is run instead of the

build

step of the plugin.

         #The working directory is the base build directory for the given part. The defined script is run with

/bin/sh

. build: 

              <command line>

         # If present, the shell script defined here is run after the build step of the plugin has finished.

        #  The working directory is the base build directory for the given part. The defined script is run with /bin/sh.

          install:

                <command line>    

snapcraft 提供了很多插件,以滿足不同的打包需求,即如果你指向使用shell 指令, 這時你可以選擇插件nil, 如果你需要做make 相關的動作, 可以選擇插件make, 如果要編譯kernel, 可以選擇kbuild 等插件,每個插件都有自己不同的參數, 細節請參考https://docs.snapcraft.io/reference/plugins/。

app 的常用文法如下:

# List of applications (commands, binaries, daemons) in the snap.

app:

     <app name>:

             # Path to executable (relative to snap base) and arguments to use

             # when this application is run.

             command: <command line>

            # List of plug names this application is associated with.

           # When a plug is connected to one of these slots, this application

           # will be granted the permissions specified for that interface.

          # If attributes are required or the plug name does not match the

          # interface name, more details must be declared under the top-level

          # "plugs" field (see below).

          plugs:

                - <plug name>

          # List of slot names this application is associated with.

          # Same details as described above, but for slots.

          slots:

             - <slot name>

          # If daemon is set, the command is a daemon to run as specified.

          # See systemd documentation for details on those kinds.

          daemon: simple | forking | oneshot

          # Optional command to use for stopping a daemon.

          stop-command: <command line>

          # Optional time to wait for daemon to stop.

          stop-timeout: <n>ns | <n>us | <n>ms | <n>s | <n>m

           # Optional command to run after daemon stops.

           post-stop-command: <command line>

          # Condition to restart the daemon under. Defaults to on-failure.

          # See the systemd.service manual on Restart for details.

          restart-condition: \

              on-failure | on-success | on-abnormal | on-abort | always | never

          # Optional stream abstract socket name or socket path.

         # When defined as a path, it should normally be in one of the snap

         # writable directories. For an abstract socket it must start with

        # @<snap name> or @<snap name>_.

         listen-stream: <path> | @<snap name> | @<snap name>_<suffix>

        # Whether the daemon is socket activated. Defaults to false, and

        # must be defined together with listen-stream if true.

        socket: true | false

相信對以上文法的了解,就可以輕松地看懂, 一個app 如何下載下傳代碼, 如何編譯, 如何生成, 以及有何權限 & 運作時條件了。關于更詳細的文法,請參照如下連結: https://docs.snapcraft.io/build-snaps/syntax

1.4 snapcraft 的工作原理

#  snapcraft  --target-arch=armhf   ## 如果是X86 就不許要要跟參數
 
           

不過snapcraft 的仍何插件在運作時,實際上被分為好幾個階段,

The process of building a snap is made up of running each part through a lifecycle of steps. These steps are, in order:
  1. pull

    Fetch the part’s source, as well as its

    stage-packages

  2. build

    Build this part (e.g. compile it)

  3. stage

    Put the material installed by the build step in the common staging area for potential use by other parts

  4. prime

    Migrate this part’s staged material into the final priming area

  5. snap

                                                                                                                                                                               compress the prime tree into the installable snap file(a squashfs image).

通過對此的描述,相信你會對snap.yaml 的了解更加深入。 我們通過重寫插件的build/install 等階段,來實作我們自己的特殊化編譯打包需求。

1.5 建立一個simple 的snap 包

  1) 建立snap.yaml

     為了簡單期間,我們直接使用

snapcraft init

生成一個yaml 的模闆;

$ snapcraft init

檢視生成的模闆
$ cat snap/snapcraft.yaml
name: my-snap-name # you probably want to 'snapcraft register <name>'
version: '0.1' # just for humans, typically '1.2+git' or '1.3.2'
summary: Single-line elevator pitch for your amazing snap # 79 char long summary
description: |
  This is my-snap's description. You have a paragraph or two to tell the
  most important story about your snap. Keep it under 100 words though,
  we live in tweetspace and your description wants to look good in the Snap
  Store.

grade: devel # must be 'stable' to release into candidate/stable channels
confinement: devmode # use 'strict' once you have the right plugs and slots

parts:
  my-part:
    # See 'snapcraft plugins'
    plugin: nil
           

2) 修改yaml

接下來,我們需要生成一個叫hello 的snap, 它執行會列印“hello,world!” 的字樣。

先修改yaml 如下:

name: hello
version: "2.10"
summary: GNU Hello, the "hello world" snap
description: GNU Hello prints a friendly greeting.
grade: stable
confinement: strict


apps:
  hello:
    command: hello

parts:
  gnu-hello:
    plugin: autotools
    source: http://ftp.gnu.org/gnu/hello/hello-2.10.tar.gz
           

該yaml會從http://ftp.gnu.org/gnu/hello/hello-2.10.tar.gz。下載下傳下來,由autotools插件進行自動解壓編譯,編譯生成hello_2.10_armhf.snap. 在運作時hello 時,直接調用編譯生成的hello 程式(由command 指定的)。

3) 編譯生成snap 包

$ sanpcraft
........

$ ls
 parts  snap   prime  stage  src  hello_2.10_armhf.snap
           

src 是hello源碼部分,parts,prime, stage  是snapcraft 中間目錄,以表示pull, pime, satge 階段。 snap 中包含snap.yaml 等配置檔案。至此我們就生成一個snap 包。

4) 安裝/運作 本地snap 包:

$ snap install --dangerous hello_2.10_armhf.snap
......


$ hello
hello world!
           

一個snap 被安裝, 事實上它是通過/dev/loopx 被挂載在/snap/snap_name 下。

2.  關于ubuntu core

2.1  系統結構圖

定制ARM 版本的ubuntu core (16.04)1.  先從snap 包講起1.2 snap 的構成?2.  關于ubuntu core3. 制作ARM 版本的ubuntu core4.  app 開發相關

ubuntu core  由4 類snap 構成,gadget snap, kernel snap , core snap , app snaps。

gadget snap 是包含了uboot image,系統鏡像的布局,IO 接口的權限,系統的預設屬性設定(usb 自動挂載屬性等),gadget snap 描述了這個裝置幾乎所有的屬性。

kernel snap 包含kernel & ubuntu core 所需要的initrd 的snap。

core snap 包含ubuntu core rootfs 的snap 包

app snap 關于包含app 的snap。

傳統的系統啟動需要uboot,kernel,rootfs, app, ubuntu core 也一樣,隻不過他們都是以snap 包的形式存在于ubuntu core 中的。那麼問題來了? snap 包是一個隻讀的squashfs 檔案系統的檔案,Romcode /SPL  如何讀取它并啟動對應的啟動鏡像呢 ? 要知道SPL甚至uboot 目前都不支援squashfs 的檔案系統哦.

事實上,當用ubuntu-image 指令制作系統啟動鏡像的時候,該指令會将gadget snap & kernel snap 中的啟動image 全部解壓出來,(同時解壓一部分core 的内容) 放到gadget.yaml 中指定的分區中去。這樣當制作好鏡像後,uboot,kernel , 和initrd 的存在形式就和傳統的系統一樣了, 即以啟動檔案的形式存在。當系統啟動到initrd 階段, 由initrd 在writable 中準備一套可啟動的檔案,并啟動它。之後,snapd 啟動,開啟snap 的世界。

不過在系統鏡像裡,仍然儲存了gadget snap 和kernel snap, 這是為了以後這些snap 能夠進行順利的更新,復原做準備的。

在ubuntu core 中, 每一個snap 被安裝後, 該snap 就通過/dev/loopx 挂載到/snap/${snap_name}/ 下的版本檔案夾下, 是一個隻讀的檔案系統。

2.2 Core 的user

制作好的Ubuntu core啟動鏡像是不存在使用者的,在系統第一次啟動時,使用者可以根據需要建立兩種賬戶:

1) SSH user , 隻能用于遠端SSH 登入,不能用于本地console 登入;

2) system user, 既可以用于本地登入, 也可以用于遠端ssh 登入;

建立SSH 賬戶的方法是連結網線, 然後根據需求配置網絡,最後生成ssh 登入的賬戶;

建立system user 參照 :

https://docs.ubuntu.com/core/en/guides/manage-devices/?_ga=2.226522645.905063160.1536053910-560405323.1510796397

一旦建立了 ubuntu core 的賬戶,該使用者就管理了該裝置, 系統将不再允許去建立其他賬戶去管理這個裝置。 root 權限可通過 sudo su 來切換。

2.3  Core 的安全機制之interfaces

ubuntu core 中為了 安全, 幾乎通路仍何重要資源都需要申請,如何申請?

ubuntu core 提供了interface 機制, interfaces 的名字代表了這種資源, 一般地,這種資源需要slots 來提供資源通路權限, plug 來擷取使用資源的權限。 core 中已經提供了大部分通用的,和硬體無關的slots,還有一部分硬體相關的interfaces slot 由 gadget 提供,另外一部分interfaces slot由 snap app 提供。

interfaces 和slots, plug 之間的關系如下圖所示。

定制ARM 版本的ubuntu core (16.04)1.  先從snap 包講起1.2 snap 的構成?2.  關于ubuntu core3. 制作ARM 版本的ubuntu core4.  app 開發相關

或許slots 和plug 之間的關系用插座和插頭來比喻更加合适。

一個需要申請interface 的APP 在安裝後, 如果他的plug 和core/gadget/other snap 提供的slot 沒有連結, 直接執行這個snap 程式,會被APParmor(後續會講)所阻止,傳回沒有權限的錯誤, 即使root 也會如此。 連結interfaces 的指令如下:

#  檢視系統提供的interfaces 連結狀況
$ snap interfaces

# 連結plug & slot
$ snap connect snap_package:interface     core/gadget/other snap:slot 

$ snap disconnect snap_package:interface     core/gadget/other snap:slot 
           

一旦連結成功,将會永久儲存。disconnect 也是如此。

有些interfaces 在安裝時是自動連結,有些需要手動連結。 詳細請參考如下連結:https://docs.snapcraft.io/reference/interfaces

有一些snap 需要通路一個硬體接口,目前ubuntu core 還沒有定義,這時候在安裝時需要加入一些參數來跳過apparmor 的限制。

snap install   --dangerous    xx.snap
           

有些時候,你需要對snap 進行debug, ubuntu 官方提供了snappy-debug 又來debug 你的snap app, 例如你需要debug 一個snap 在security 方面的内容時,可按照如下方式進行:

$  snappy-debug.security scanlog &

# then, run your snap , and there are some debug message for this snap
           

3. 制作ARM 版本的ubuntu core

制作ARM 版本的ubuntu core, 有三個snap 包需要準備:gadget snap, kernel snap , core snap,由于core snap 由ubuntu 官方提供,故至于制作gadget & kernel 的snap 包, 下來我們将會一一描述。

3.1  gadget snap 制作

gadget snap 中包含了uboot 鏡像,以及生成的sdcard 鏡像的分區定義,以及分區的放置内容, 裝置接口的通路權限.... 等功能。

3.1.1  Ubuntu core 對uboot 的更改

 1) 環境變量處在啟動裝置的第一個fat 分區

這是snapd 的要求,snapd 在系統啟動後,會讀取第一個fat 分區的環境變量檔案,并根據相關設定,做出對應的行為,同時還會修改相關環境變量的值。為了支援uboot 環境變量處在EMMC 中, 你需要做如下修改:

/* env is in fat */
#define CONFIG_ENV_IS_IN_FAT   
#define CONFIG_SYS_REDUNDAND_ENVIRONMENT
#define FAT_ENV_INTERFACE              "mmc"
#define FAT_ENV_DEVICE_AND_PART                "0:1"
#define FAT_ENV_FILE                   "uboot.env"
#define CONFIG_ENV_SIZE      (128 * 1024)

#define CONFIG_FAT_WRITE
#define CONFIG_SUPPORT_RAW_INITRD
           

CONFIG_SYS_REDUNDAND_ENVIRONMENT 必須添加, 原因為何?

假設一個情景,當snapd 設定某些環境變量後正在儲存時(一般的檔案儲存需要花費一些時間),突然斷電, 下次啟動時這個狀态并沒有改正過來。為了防止這一點, 你需要加入CONFIG_SYS_REDUNDAND_ENVIRONMENT,讓環境變量儲存時,寫入一個特殊的頭部标記,這告訴snapd 在操作該檔案是都是原子的,進而減小這種風險。

CONFIG_ENV_SIZE 必須為128 K, 這也是固定的;

CONFIG_FAT_WRITE   確定能進行環境變量的儲存;

CONFIG_SUPPORT_RAW_INITRD: 為了支援uboot 能夠導入initrd ,因為 ubuntu core 的啟動,必須要使用initrd.

至此, 就完成了讓環境變量處在啟動裝置的第一分區的任務。
           

2) 添加snapd 等需要的環境變量

我們剛才說過snapd 需要uboot 環境變量, 接下來,我們就看需要什麼變量?

+       "snap_mode=\0" \
+       "initrd_addr=0x88080000\0" \
+       "initrd_file=initrd.img\0" \
+       "kernel_file=kernel.img\0" \
+       "snap_try_kernel=rsb4220-kernel_x1.snap\0" \
+       "snap_try_core=core_5331.snap\0" \

uEnv.txt
loadfdt=load mmc ${bootpart} ${fdtaddr} ${snap_kernel}/dtbs/${fdtfile}
loadkernel=load mmc ${bootpart} ${loadaddr} ${snap_kernel}/${kernel_file}
loadinitrd=load mmc ${bootpart} ${initrd_addr} ${snap_kernel}/${initrd_file}; setenv initrd_size ${filesize}
loadfiles=run loadkernel; run loadinitrd; run loadfdt
snappy_cmdline=rng_core.default_quality=700 net.ifnames=0 init=/lib/systemd/systemd ro panic=-1 fixrtc
snappy_boot=if test "${snap_mode}" = "try"; then setenv snap_mode "trying"; saveenv; if test "${snap_try_core}" != ""; then setenv snap_core "${snap_try_core}"; fi; if test "${snap_try_kernel}" != ""; then setenv snap_kernel "${snap_try_kernel}"; fi; elif test "${snap_mode}" = "trying"; then setenv snap_mode ""; saveenv; fi; mmc dev ${mmcdev}; if mmc rescan; then echo SD/MMC found on device ${mmcdev}; if run loadfiles; then setenv mmcroot "/dev/disk/by-label/writable ${snappy_cmdline} snap_core=${snap_core} snap_kernel=${snap_kernel}"; setenv args_mmc "run finduuid;setenv bootargs console=${console} ${optargs} root=${mmcroot}"; run args_mmc; bootz ${loadaddr} ${initrd_addr}:${initrd_size} ${fdtaddr}; fi; fi
uenvcmd=run snappy_boot
           

為了啟動ubuntu core, 我們需要添加新的啟動方式,其行為定義在snappy_boot 中,snap_mode,

snap_kernel , snap_core 這些都是snapd 需要的,用來啟動正确的kernel & core snap.

snappy_cmdline 是給kernel的傳參,有些參數會被initrd 進行分析,來解壓/挂載 對應snap 包。

panic=-1 告訴kernel,如果出現panic,就自動重新開機,而不是卡在那裡。

mmcroot "/dev/disk/by-label/writable ..." 表明系統最終的rootfs 在writable ,剛制作好的SD卡的writable 中并沒有可運作rootfs,這個工作是由initrd 來負責完成的。是以initrd 必不可少。

至此, 便完成了uboot 源碼部分的更改。

3.1.2  建立gadget.yaml

和其他的snap 包不同, gadget snap 需要一個gadget.yaml 來描述裝置鏡像的分區, 分區中的内容,裝置樹的放置位置,裝置接口的通路權限等問題。總之,gadget.yaml 定義了關于這個device 的一切特殊資訊。 

接下來我們描述下gadget.yaml 的文法:

# Define the format of this file. The default and latest format is zero.
# Clients reading this file must reject it the format is greater than
# the supported one. (optional)
format: <int>

# Default configuration options for defined snaps, applied on installation.
# The snap ID may be discovered via the snap info command.
# Since 2.33 snap ID can be the "system" nick to cover the system
# configuration. (optional)
defaults:
    <snap id>:
        <key>: <value>

# Interface connection instructions for plugs and slots of seeded
# snaps to connect at first boot. snap IDs can be the "system"
# nick as well. Omitting "slot" in an instruction is allowed
# and equivalent then to: slot: system:<plug>
# (since 2.34) (optional)
connections:
   -  plug: <plug snap id>:<plug>
      slot: <slot snap id>:<slot>
     
# If device-tree is specified, `dtbs/<filename>` must exist in kernel or
# gadget snap (depends on origin) and `snap_device_tree_origin` and
# and `snap_device_tree` are made available for u-boot and grub. (optional)
device-tree: <filename>

# Defines where the device tree is. Defaults to gadget. (optional)
device-tree-origin: kernel

# Volumes defining the structure and content for the images to be written
# into one ore more block devices of the gadget device. Each volume in
# in the structure represents a different image for a "disk" in the device.
# (optional)
volumes:

  # Name of volume and output image file. Must match [a-z-]+. (required)
  <volume name>:

    # 2-digit hex code for MBR disk ID or GUID for GPT disk id. (optional)
    id: <id>
                  
    # Bootloader in the volume. Required in one volume. (required/optional)
    bootloader: grub | u-boot

    # Which partitioning schema to use. Defaults to gpt. (optional)
    schema: mbr | gpt | mbr,gpt

    # Structure defines layout of the volume, including partitions,
    # Master Boot Records, or any other relevant content. (required)
    structure:
      - # Structure value is a list.

        # Structure item name. There's an implementation-specific constraint
        # on the maximum length. The maximum length of a partition name
        # for GPT is 36 characters in the UTF-16 character set. (optional)
        name: <name>

        # GPT unique partition id, disallowed on MBR volumes. (optional)
        id: <id>

        # Role defines a special role for this item in the image. (optional)
        # Must be either unset, or one of:
        #   mbr - Master Boot Record of the image.
        #   system-boot - Partition holding the boot assets.
        #   system-data - Partition holding the main operating system data.
        #
        # A structure with role:system-data must either have an implicit
        # file system label, or 'writable'.
        role: mbr | system-boot | system-data

        # Type of structure. May be specified as a two-hex-digit MBR partition
        # type, a GPT partition type GUID, or both on hybrid schemas.  The
        # special value `bare` says to not create a disk partition for this
        # structure. (required)
        type: <mbr type> | <gpt guid> | <mbr type>,<gpt guid> | bare

        # Size for structure item. Maximum of 446 for the mbr role. (required)
        size: <bytes> | <bytes/2^20>M | <bytes/2^30>G

        # The offset from the beginning of the image. Defaults to right after
        # prior structure item. (optional)
        offset: <bytes> | <bytes/2^20>M | <bytes/2^30>G

        # Offset of this structure element (in units of 512-byte sectors) is
        # written to the provided position within the volume in LBA48 pointer
        # format (32-bit little-endian). This position may be specified as a
        # byte-offset relative to the start of another named structure item.
        # (optional)
        offset-write: [<name>+]<bytes> |
                      [<name>+]<bytes/2^20>M |
                      [<name>+]<bytes/2^30>G

        # Filesystem type. Defaults to none. (optional)
        filesystem: none | vfat | ext4

        # Filesystem label. Defaults to name of structure item. (optional)
        filesystem-label: <label>

        # Content to be copied from gadget snap into the structure. This
        # field takes a list of one of the following formats. (required)
        content:

            # Copy source (relative to gadget base directory) into filesystem
            # at target (relative to root). Directories must end in a slash.
            - source: <filename> | <dir>/  # (required)
              target: <filename> | <dir>/  # (required)

            # Dump image (relative to gadget base directory) of the raw data
            # as-is into the structure at offset. If offset is omitted it
            # defaults to right after prior content item. If size is omitted,
            # defaults to size of contained data.
            - image: <filename>                                 # (required)
              offset: <bytes> | <bytes/2^20>M | <bytes/2^30>G   # (optional)
              offset-write: (see respective item above)         # (optional)
              size: <bytes> | <bytes/2^20>M | <bytes/2^30>G     # (optional)
           

我們給出一個example:

device-tree: am335x-boneblack
device-tree-origin: kernel
volumes:
  disk:
    bootloader: u-boot
    schema: mbr
    structure:
      - name: mlo
        type: bare
        size: 131072
        offset: 131072
        content:
          - image: MLO
      - name: u-boot
        type: bare
        size: 786432
        offset: 393216
        content:
          - image: u-boot.img
      - name: system-boot
        type: 0C
        filesystem: vfat
        filesystem-label: system-boot
        size: 128M
           

這個gadget 隻定義了一個分區,那就是system-boot 分區, 它主要預設存放kernel sanp, uboot.env 等檔案。

MLO,uboot.img 類似于被dd 到對應的MMC 的位置上了。這是對上面檔案的翻譯,細節可以參考上面gadget 的文法部分。

3.1.3 建立snap.yaml

我們直接給出一個example:

name: rsb4220
version: 16-0.4
summary: am335x advantech board
description: |
 Bootloader files and partitoning data to create a
 bootable Ubuntu Core image for am335x advantech board.
type: gadget
architectures:
  - armhf
confinement: strict
grade: stable


parts:
  uboot:
    plugin: nil
    source: .
    build: |
      echo "building ..." 
    install: |
      cp u-boot.img $SNAPCRAFT_PART_INSTALL/
      cp uboot.env  $SNAPCRAFT_PART_INSTALL/
      cp uEnv.txt   $SNAPCRAFT_PART_INSTALL/
      cd $SNAPCRAFT_PART_INSTALL/; ln -s uboot.env uboot.conf
slots:
  gpio200:
     interface: gpio
     number: 200
  gpio201:
     interface: gpio
     number: 201
  gpio202:
     interface: gpio
     number: 202
  gpio203:
     interface: gpio
     number: 203
  gpio204:
     interface: gpio
     number: 204
  gpio205:
     interface: gpio
     number: 205
  gpio206:
     interface: gpio
     number: 206
  gpio207:
     interface: gpio
     number: 207
  serial0:
     interface: serial-port
     path: /dev/ttyO0
  serial1:
     interface: serial-port
     path: /dev/ttyO1
  serial2:
     interface: serial-port
     path: /dev/ttyO2
  serial3:
     interface: serial-port
     path: /dev/ttyO3
  serial4:
     interface: serial-port
     path: /dev/ttyO4
  serial5:
     interface: serial-port
     path: /dev/ttyO5
  i2c-0:
     interface: i2c
     path: /dev/i2c-0
           

gadget 的snap.yaml 中的type 必須是gadget, parts 部分是你如何編譯uboot 的部分,該描述隻是将預編譯的uboot 相關的鏡像copy 到snap 包中的對應位置,并沒有做下載下傳編譯的動作。

slots 部分是對這個device 中interfaces 的聲明,否則,無法使用該interface。細節前面已經讨論。

要想了解進一步細節,具體也可參考如下連結: https://github.com/ogra1/beaglebone-gadget

至此,gadget 的snap 就基本完成了,執行以下指令,即可生成對應的snap包

snapcraft --target-arch=armhf
           

Kernel snap & gadget snap 不能上傳到通用的snap store 中,它需要上傳到brand store.

3.2  kernel snap 的制作

kernel snap 和gadget 一樣,是一個特殊的snap, ubuntu-image 在制作鏡像時,會将kernel snap 解包出來,放在system-boot 分區。 ubuntu core 需要kernel 做出一些更改, 例如是能apparmor,snappy,squanfs 的支援等。

3.2.1 ubuntu core 對kernel 的更改

ubuntu core 對kernel 的需求還是挺多的, 具體可以參考https://github.com/snapcore/sample-kernels。

這個kernel 有不同的版本,你可以根據你的版本,選擇最接近的kernel 版本。

這個改動基本分為兩個部分:

1) kernel config 的配置

       根據kernel/configs/snappy/ 下的config 添加自己的config

2) Apparmor 的修正:

       根據該sample-kernel 的修改曆史, 修改apparmor, 由于patch 較多,更改時需要多加注意,一旦改動不當,就會導緻snapd 無法正常運作。

        至此, kernel 源碼部分就已經修改完成。

3.2.2  擷取并定制initrd

ubuntu core 的core snap 中攜帶了initrd, 而正真攜帶initrd 的snap 是kernel snap。 是以在制作kernel snap 前, 需要下載下傳core snap,使用以下指令擷取initrd:

$  UBUNTU_STORE_ARCH=armhf  snap download   core
core_5331.snap
$ unsquashfs  core_5331.snap   ## 解壓core snap
$  cp squashfs-root/boot/initrd.img-core-0.7.43+ppa25     initrd.img
           

當擷取initrd.img 後,就可以根據需求定制你的initrd, 當然你也可以選擇不做仍何定制。接下來我們介紹如何解壓initrd/壓縮initrd的指令。

1) 解壓ubuntu core 提供的 initrd

 參照snapcraft 提供的kernel 插件, 可以用以下指令解壓:

$  xz -dc initrd.img | cpio -id
           

2) 壓縮生成initrd

$   find . | cpio --create --format=newc | lzma > ../initrd.img
           

至此,initrd 制作完成, copy kernel image , dtb, 和 initrd 到同一目錄。

3.2.3  建立snapcraft.yaml

由于我們的kernel image 是預編譯好的, 是以,我們隻提供一個簡單kernel snap 的yaml。

name: rsb4220-kernel
version: 4.4.19-gff01773757
summary: advantech kernel
description: advantech kernel sanp for rsb4220
type: kernel
architectures: [ armhf ]
confinement: strict
grade: stable


parts:
  kernel:
    plugin: nil
    source: .
    build: |
      mkdir -p $SNAPCRAFT_PART_INSTALL/dtbs
    install: |
      cp zImage $SNAPCRAFT_PART_INSTALL/kernel.img
      cp am335x-rsb4220a1.dtb  $SNAPCRAFT_PART_INSTALL/dtbs
      cp initrd.img  $SNAPCRAFT_PART_INSTALL/
      tar xvzf modules.tar.gz  -C $SNAPCRAFT_PART_INSTALL/
           

Kernel snap 的type 必須是kernel,其他均是kernel 建構kernel snap 的基本操作。

執行snapcraft --target-arch=armhf  來生成kernel snap。

3.3 ubuntu core 系統鏡像

根據前面的講解,基本可以制作出你自己的gadget snap & kernel snap. 如何将這些snap 生成最終的sdcard/emmc image将是本章的主要内容,同時還會描述如何建立loacal user。

3.3.1  生成ubuntu core image

ubuntu core 提供了一個生成ubuntu core image 的工具,你可以下面的方式進行下載下傳

snap install  ubuntu-image
           

下載下傳完成後,你需要實作一個斷言(assert)來定義你生成image 中所包含的snap 包,請按照如下連結進行注冊

https://docs.ubuntu.com/core/en/guides/build-device/image-building

根據這個連結,你将會順利生成sdcard/emmc 的image。

3.3.2  建立ubuntu core 的user

當插入SD卡,系統第一次正常啟動後,你會發現系統并沒有登入界面,而是需要你配置自己的網絡,之後根據提示輸入你在ubuntu core 注冊時用的郵箱& passwd。 完成之後,同樣沒有登入界面, 這是為何?

原來,ubuntu core 這種預設的user 被按照上面的方式建立後,隻能通過SSH 登入。而且一旦存在一個user, 将不能建立其他user 來管理這個裝置。

在建立這種預設的 user 時,如果有兩個網口,core将會要求每個網口都要能連結上internet,否則就會配置失敗,或許這是一個bug,在後面的版本中應當被ubuntu core 所修正。

ubuntu core 也支援另外一種user 即system user 的建立。 system user 像普通的ubuntu 一樣,可以直接在本地登入,同時也支援ssh 等的登入。 一旦被建立,将不能再建立其他user 來管理這個裝置。 system user 的建立需要導入一個auto.import.assert的斷言,用以描述建立使用者的資訊。

具體建立方法可以參考如下連結:https://docs.ubuntu.com/core/en/guides/manage-devices/?_ga=2.226522645.905063160.1536053910-560405323.1510796397

文章中說隻要把檔案放入udisk 就會自動産生system user, 但經過筆者在am335x 上測試,是不行的,事實上,這個assert 可以放在仍何被系統挂載的分區的根目錄。(如果調試不okay, 可以将其放在writable分區中)

在建立system user 的斷言時, 所使用的model 斷言必須是之前建立sdcard image 時所用的斷言, 簽名所用的key 可以不一緻, 但必須由之前的model 來定義。

至此,就完成建立 ubuntu core 系統的所有工作, 登入系統後,一切操作就像原來的ubuntu 一樣簡單

,隻不過apt-get install 換成了snap install, 一切都進入了snap 的世界。

4.  app 開發相關

進入snap 世界後, shell 還能用麼?

必須能用,而且還和之前一樣好,同時還支援snap 的運作。 snap 的世界是由snapd 建構起來的,而snapd 隻是ubuntu core 中的一個守護程序。 看到這裡你就會明白,snap 是在shell 之上的概念。 你可以完全使用shell 指令和之前一樣,這也不存在ubuntu core 裡面講的權限, interface 之類的概念, 這些概念隻存在snap 的世界裡, 和shell 無關。

不過ubuntu core 推薦我們使用snap , 因為ubuntu core 就是為snap 而生的。

4.1 建立一個snap app

細節請參照下面的demo:

https://tutorials.ubuntu.com/tutorial/build-a-nodejs-service?backURL=https://docs.snapcraft.io/build-snaps/metadata#0

4.2 ubuntu core 提供的interfaces

接下來一一介紹一些常用IO 口所使用的snap interface。

1) RTC:

你的snap app 中要讀取RTC, 或者要調用hwclock 讀取rtc , 需要兩個接口:time-control & netlink-audit interface. 該slots 被core 提供, 你需要在app 的yaml 中申明plugs 使用這兩個interface。

2) I2C, SPI, UART

這些接口,core 搜提供了對應的i2c, spi, serail interface , 你需要在app 的yaml 中申明plugs 使用對應的interface。

3) gpio

gpio 也需要interface, 才能通路;

4) SD/EMMC/Udisk

需要額外下載下傳udisks2 snap 包,用以通路該裝置上的檔案,關于udisks2的描述可以參考如下連結:https://docs.ubuntu.com/core/en/stacks/disk/udisks2/docs/installation

設定udisk 的自動挂載, 使用下面指令:

snap set udisks2 automount.enable=true
           

 5) watchdog

目前ubuntu core 沒有提供interface 為watchdog,也就是你沒有辦法在strict 模式下,使用watchdog。不過你可以在安裝時指定他為devmode, 這樣讓snap 包處在開發者模式下,使用watchdog, 當然也可以使用shell 指令來跳過這一限制。