出自https://blog.csdn.net/changqing1990/article/details/82841830
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: # Snaps can be setup to follow three different confinement policies confinement: # This defines the quality grade of the snap grade:
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 thebuild
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:
pull
Fetch the part’s source, as well as itsstage-packages
build
Build this part (e.g. compile it)stage
Put the material installed by the build step in the common staging area for potential use by other partsprime
Migrate this part’s staged material into the final priming area
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
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 参照 :
一旦创建了 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 */
-
-
-
-
-
-
-
-
-
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:
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 命令来跳过这一限制。