定製ARM 版本的ubuntu core (16.04)

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  系統結構圖

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 之間的關係如下圖所示。

或許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 命令來跳過這一限制。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章