2020年10月

在 Fedora 上使用 yum 仓库来获取最新的 ZFS 特性。

我是一名 Fedora Linux 用户,我每天都会运行 yum upgrade。虽然这个习惯使我能够运行所有最新的软件(Fedora 的四个基础之一的 “First” ,它也做到了),但它也会提醒 ZFS 存储平台和新内核之间的不兼容性。

作为一名开发者,有时我需要最新的 ZFS 分支的新特性。例如,ZFS 2.0.0 包含了一个令人兴奋的新功能,它大大提高了 ZVOL 同步性能,这对我这个 KVM 用户来说至关重要。但这意味着,如果我想使用 2.0.0 分支,我必须自己构建 ZFS。

起初,我只是在每次内核更新后从它的 Git 仓库中手动编译 ZFS。如果我忘记了,ZFS 就会在下次启动时无法被识别。幸运的是,我很快就学会了如何为 ZFS 设置动态内核模块支持 (DKMS)。然而,这个解决方案并不完美。首先,它没有利用强大的 yum 系统,而这个系统可以帮助解决依赖关系和升级。此外,使用 yum 在你自己的包和上游包之间进行切换是非常容易的。

在本文中,我将演示如何设置 yum 仓库来打包 ZFS。这个方案有两个步骤:

  1. 从 ZFS 的 Git 仓库中创建 RPM 包。
  2. 建立一个 yum 仓库来托管这些包。

创建 RPM 包

要创建 RPM 包,你需要安装 RPM 工具链。yum 提供了一个组来捆绑安装这些工具:

sudo dnf group install 'C Development Tools and Libraries' 'RPM Development Tools'

安装完这些之后,你必须从 ZFS Git 仓库中安装构建 ZFS 所需的所有包。这些包属于三个组:

  1. Autotools,用于从平台配置中生成构建文件。
  2. 用于构建 ZFS 内核和用户态工具的库。
  3. 构建 RPM 包的库。
sudo dnf install libtool autoconf automake gettext createrepo \
    libuuid-devel libblkid-devel openssl-devel libtirpc-devel \
    lz4-devel libzstd-devel zlib-devel \
    kernel-devel elfutils-libelf-devel \
    libaio-devel libattr-devel libudev-devel \
    python3-devel libffi-devel

现在你已经准备好创建你自己的包了。

构建 OpenZFS

OpenZFS 提供了优秀的基础设施。要构建它:

  1. git 克隆仓库,并切换到你希望使用的分支/标签。
  2. 运行 Autotools 生成一个 makefile。
  3. 运行 make rpm,如果一切正常,RPM 文件将被放置在 build 文件夹中。
$ git clone --branch=zfs-2.0.0-rc3 <https://github.com/openzfs/zfs.git> zfs
$ cd zfs
$ ./autogen.sh
$ ./configure
$ make rpm

建立一个 yum 仓库

yum 中,仓库是一个服务器或本地路径,包括元数据和 RPM 文件。用户设置一个 INI 配置文件,yum 命令会自动解析元数据并下载相应的软件包。

Fedora 提供了 createrepo 工具来设置 yum 仓库。首先,创建仓库,并将 ZFS 文件夹中的所有 RPM 文件复制到仓库中。然后运行 createrepo --update 将所有的包加入到元数据中。

$ sudo mkdir -p /var/lib/zfs.repo
$ sudo createrepo /var/lib/zfs.repo
$ sudo cp *.rpm /var/lib/zfs.repo/
$ sudo createrepo --update /var/lib/zfs.repo

/etc/yum.repos.d 中创建一个新的配置文件来包含仓库路径:

$ echo \
"[zfs-local]\\nname=ZFS Local\\nbaseurl=file:///var/lib/zfs.repo\\nenabled=1\\ngpgcheck=0" |\
sudo tee /etc/yum.repos.d/zfs-local.repo

$ sudo dnf --repo=zfs-local list available --refresh

终于完成了!你已经有了一个可以使用的 yum 仓库和 ZFS 包。现在你只需要安装它们。

$ sudo dnf install zfs
$ sudo /sbin/modprobe zfs

运行 sudo zfs version 来查看你的用户态和内核工具的版本。恭喜!你拥有了 Fedora 中的 ZFS


via: https://opensource.com/article/20/10/zfs-dnf

作者:Sheng Mao 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

如果你被困在 Linux 终端,比如说在服务器上,如何从终端下载文件?

在 Linux 中是没有 download 命令的,但是有几个 Linux 命令可用于下载文件。

在这篇终端技巧中,你将学习两种在 Linux 中使用命令行下载文件的方法。

我在这里使用的是 Ubuntu,但除了安装,其余的命令同样适用于所有其他 Linux 发行版。

使用 wget 命令从 Linux 终端下载文件

wget 也许是 Linux 和类 UNIX 系统中使用最多的命令行下载管理器。你可以使用 wget 下载一个文件、多个文件、整个目录甚至整个网站。

wget 是非交互式的,可以轻松地在后台工作。这意味着你可以很容易地在脚本中使用它,甚至构建像 uGet 下载管理器这样的工具。

让我们看看如何使用 wget 从终端下载文件。

安装 wget

大多数 Linux 发行版都预装了 wget。它也可以在大多数发行版的仓库中找到,你可以使用发行版的包管理器轻松安装它。

在基于 Ubuntu 和 Debian 的发行版上,你可以使用 apt 包管理器命令:

sudo apt install wget

使用 wget 下载文件或网页

你只需要提供文件或网页的 URL。它将在你所在的目录下以原始名下载该文件。

wget URL

要下载多个文件,你必须将它们的 URL 保存在一个文本文件中,并将该文件作为输入提供给 wget,就像这样:

wget -i download_files.txt

用 wget 下载不同名字的文件

你会注意到,网页在 wget 中几乎总是以 index.html 的形式保存。为下载的文件提供自定义名称是个好主意。

你可以在下载时使用 -O (大写字母 O) 选项来提供输出文件名:

wget -O filename URL

用 wget 下载一个文件夹

假设你正在浏览一个 FTP 服务器,你需要下载整个目录,你可以使用递归选项 -r

wget -r ftp://server-address.com/directory

使用 wget 下载整个网站

是的,你完全可以做到这一点。你可以用 wget 镜像整个网站。我说的下载整个网站是指整个面向公众的网站结构。

虽然你可以直接使用镜像选项 -m,但最好加上:

  • –convert-links:链接将被转换,使内部链接指向下载的资源,而不是网站。
  • –page-requisites:下载额外的东西,如样式表,使页面在离线状态下看起来更好。
wget -m --convert-links --page-requisites website_address

额外提示:恢复未完成的下载

如果你因为某些原因按 CTRL-C 键中止了下载,你可以用选项 -c 恢复之前的下载:

wget -c

使用 curl 在 Linux 命令行中下载文件

wget 一样,curl 也是 Linux 终端中最常用的下载文件的命令之一。使用 curl 的方法有很多,但我在这里只关注简单的下载。

安装 curl

虽然 curl 并不是预装的,但在大多数发行版的官方仓库中都有。你可以使用你的发行版的包管理器来安装它。

在 Ubuntu 和其他基于 Debian 的发行版上安装 curl,请使用以下命令:

sudo apt install curl

使用 curl 下载文件或网页

如果你在使用 curl 命令时没有在 URL 中带任何选项,它就会读取文件并打印在终端上。

要在 Linux 终端中使用 curl 命令下载文件,你必须使用 -O(大写字母 O)选项:

curl -O URL

在 Linux 中,用 curl 下载多个文件是比较简单的。你只需要指定多个 URL 即可:

curl -O URL1 URL2 URL3

请记住,curl 不像 wget 那么简单。wget 可以将网页保存为 index.htmlcurl 却会抱怨远程文件没有网页的名字。你必须按照下一节的描述用一个自定义的名字来保存它。

用不同的名字下载文件

这可能会让人感到困惑,但如果要为下载的文件提供一个自定义的名称(而不是原始名称),你必须使用 -o(小写 O)选项:

curl -o filename URL

有些时候,curl 并不能像你期望的那样下载文件,你必须使用选项 -L(代表位置)来正确下载。这是因为有些时候,链接会重定向到其他链接,而使用选项 -L,它就会跟随最终的链接。

用 curl 暂停和恢复下载

wget 一样,你也可以用 curl-c 选项恢复暂停的下载:

curl -c URL

总结

和以往一样,在 Linux 中做同一件事有多种方法。从终端下载文件也不例外。

wgetcurl 只是 Linux 中最流行的两个下载文件的命令。还有更多这样的命令行工具。基于终端的网络浏览器,如 elinksw3m 等也可以用于在命令行下载文件。

就个人而言,对于一个简单的下载,我更喜欢使用 wget 而不是 curl。它更简单,也不会让你感到困惑,因为你可能很难理解为什么 curl 不能以预期的格式下载文件。

欢迎你的反馈和建议。


via: https://itsfoss.com/download-files-from-linux-terminal/

作者:Abhishek Prakash 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

如果你的 LVM 不再需要使用某个设备,你可以使用 vgreduce 命令从卷组中删除物理卷。

vgreduce 命令可以通过删除物理卷来缩小卷组的容量。但要确保该物理卷没有被任何逻辑卷使用,请使用 pvdisplay 命令查看。如果物理卷仍在使用,你必须使用 pvmove 命令将数据转移到另一个物理卷。

数据转移后,它就可以从卷组中删除。

最后使用 pvremove 命令删除空物理卷上的 LVM 标签和 LVM 元数据。

将扩展块移动到现有物理卷上

使用 pvs 命令检查是否使用了所需的物理卷(我们计划删除 LVM 中的 /dev/sdc 磁盘)。

# pvs -o+pv_used

PV VG Fmt Attr PSize PFree Used
/dev/sda myvg lvm2 a- 75.00G 14.00G 61.00G
/dev/sdb myvg lvm2 a- 50.00G 45.00G 5.00G
/dev/sdc myvg lvm2 a- 17.15G 12.15G 5.00G

如果使用了,请检查卷组中的其他物理卷是否有足够的空闲 扩展块 extent

如果有的话,你可以在需要删除的设备上运行 pvmove 命令。扩展块将被分配到其他设备上。

# pvmove /dev/sdc

/dev/sdc: Moved: 2.0%
…
/dev/sdc: Moved: 79.2%
…
/dev/sdc: Moved: 100.0%

pvmove 命令完成后。再次使用 pvs 命令检查物理卷是否有空闲。

# pvs -o+pv_used

PV VG Fmt Attr PSize PFree Used
/dev/sda myvg lvm2 a- 75.00G  1.85G 73.15G
/dev/sdb myvg lvm2 a- 50.00G 45.00G 5.00G
/dev/sdc myvg lvm2 a- 17.15G 17.15G 0

如果它是空闲的,使用 vgreduce 命令从卷组中删除物理卷 /dev/sdc

# vgreduce myvg /dev/sdc
Removed "/dev/sdc" from volume group "vg01"

最后,运行 pvremove 命令从 LVM 配置中删除磁盘。现在,磁盘已经完全从 LVM 中移除,可以用于其他用途。

# pvremove /dev/sdc
Labels on physical volume "/dev/sdc" successfully wiped.

移动扩展块到新磁盘

如果你在卷组中的其他物理卷上没有足够的可用扩展。使用以下步骤添加新的物理卷。

向存储组申请新的 LUN。分配完毕后,运行以下命令来在 Linux 中发现新添加的 LUN 或磁盘

# ls /sys/class/scsi_host
host0
# echo "- - -" > /sys/class/scsi_host/host0/scan
# fdisk -l

操作系统中检测到磁盘后,使用 pvcreate 命令创建物理卷。

# pvcreate /dev/sdd
Physical volume "/dev/sdd" successfully created

使用以下命令将新的物理卷 /dev/sdd 添加到现有卷组 vg01 中。

# vgextend vg01 /dev/sdd
Volume group "vg01" successfully extended

现在,使用 pvs 命令查看你添加的新磁盘 /dev/sdd

# pvs -o+pv_used

PV VG Fmt Attr PSize PFree Used
/dev/sda myvg lvm2 a- 75.00G 14.00G 61.00G
/dev/sdb myvg lvm2 a- 50.00G 45.00G 5.00G
/dev/sdc myvg lvm2 a- 17.15G 12.15G 5.00G
/dev/sdd myvg lvm2 a- 60.00G 60.00G 0

使用 pvmove 命令将数据从 /dev/sdc 移动到 /dev/sdd

# pvmove /dev/sdc /dev/sdd

/dev/sdc: Moved: 10.0%
…
/dev/sdc: Moved: 79.7%
…
/dev/sdc: Moved: 100.0%

数据移动到新磁盘后。再次使用 pvs 命令检查物理卷是否空闲。

# pvs -o+pv_used

PV VG Fmt Attr PSize PFree Used
/dev/sda myvg lvm2 a- 75.00G 14.00G 61.00G
/dev/sdb myvg lvm2 a- 50.00G 45.00G 5.00G
/dev/sdc myvg lvm2 a- 17.15G 17.15G 0
/dev/sdd myvg lvm2 a- 60.00G 47.85G 12.15G

如果空闲,使用 vgreduce 命令从卷组中删除物理卷 /dev/sdc

# vgreduce myvg /dev/sdc
Removed "/dev/sdc" from volume group "vg01"

最后,运行 pvremove 命令从 LVM 配置中删除磁盘。现在,磁盘已经完全从 LVM 中移除,可以用于其他用途。

# pvremove /dev/sdc
Labels on physical volume "/dev/sdc" successfully wiped.

via: https://www.2daygeek.com/linux-remove-delete-physical-volume-pv-from-volume-group-vg-in-lvm/

作者:Magesh Maruthamuthu 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

什么是 FOSS?

在过去,我曾多次被问到这个问题,现在是时候解释一下什么是 Linux 和软件世界中的 FOSS 了。

这个区别很重要,因为 FOSS 是一个通用的词汇,它可以根据上下文的不同而有不同的含义。在这里,我讨论的是软件中的 FOSS 原则。

什么是 FOSS?

FOSS 是指 自由和开放源码软件 Free and Open Source Software 。这并不意味着软件是免费的。它意味着软件的源代码是开放的,任何人都可以自由使用、研究和修改代码。这个原则允许人们像一个社区一样为软件的开发和改进做出贡献。

FOSS 的起源

在上世纪 60、70 年代,计算机以硬件为主,硬件价格昂贵。它们主要由大学的学者或实验室的研究人员使用。以前有限的软件都是免费的,或者是带有它们的源代码,用户可以根据自己的需要修改源代码。

在上世纪 70 年代末和 80 年代初,制造商为了不让自己的软件在竞争对手的计算机上运行,停止了分发源代码。

这种限制性的许可导致了那些习惯和喜欢修改软件的人的不便和不喜。上世纪 80 年代中期,Richard Stallman 发起了 自由软件运动 Free Software Movement

Stallman 指明了一个软件要成为 FOSS 的四个基本自由

自由软件自由

为了便于理解,我将它们重新表述:

  • 任何用户应能为任何目的运行软件。
  • 用户应能自由查看软件的源代码,如有需要,应允许用户修改代码。
  • 用户应能自由地将软件的副本分发给他人。
  • 如果用户修改了代码,她/他应该可以自由地将修改后的代码发布给他人。修改后的代码必须开放源代码。

如果有兴趣,我建议阅读这篇关于 FOSS 的历史的文章。

FOSS 中的 “Free” 并不意味着免费

你可能已经注意到了,自由和开源软件中的 “Free” 并不意味着它是免费的,它意味着运行、修改和分发软件的“自由”。

人们经常错误地认为,FOSS 或开源软件不能有价格标签。这是不正确的。

大多数 FOSS 都是免费提供的,原因有很多:

  • 源代码已经向公众开放,所以一些开发者认为没有必要在下载软件时贴上价格标签。
  • 有些项目是由一些志愿者免费提供的。因此,主要的开发者认为对这么多人免费贡献的东西收费是不道德的。
  • 有些项目是由较大的企业或非营利组织支持和/或开发的,这些组织会雇佣开发人员在他们的开源项目上工作。
  • 有些开发者创建开源项目是出于兴趣,或者出于他们对用代码为世界做贡献的热情。对他们来说,下载量、贡献和感谢的话比金钱更重要。

为了避免强调 “免费”,有些人使用了 FLOSS 这个词(LCTT 译注:有时候也写作 F/LOSS)。FLOSS 是 自由和开源软件 Free/Libre Open Source Software 的缩写。单词 Libre(意为自由)与 gartuit/gratis(免费)不同。

“Free” 是言论自由的自由,而不是免费啤酒的免费。

FOSS 项目如何赚钱?

开源项目不赚钱是一个神话。红帽是第一个达到 10 亿美元大关的开源公司。IBM 以 340 亿美元收购了红帽。这样的例子有很多。

许多开源项目,特别是企业领域的项目,都会提供收费的支持和面向企业的功能。这是红帽、SUSE Linux 和更多此类项目的主要商业模式。

一些开源项目,如 Discourse、WordPress 等,则提供其软件的托管实例,并收取一定的费用。

许多开源项目,特别是桌面应用程序,依靠捐赠。VLC、GIMP、Inkscape 等这类开源软件就属于这一类。有资助开源项目的方法,但通常,你会在项目网站上找到捐赠链接。

利用开源软件赚钱可能很难,但也不是完全不可能。

但我不是程序员,我为什么要关心一个软件是否开源?

这是一个合理的问题。你不是一个软件开发者,只是一个普通的计算机用户。即使软件的源代码是可用的,你也不会理解程序的工作原理。

这很好。你不会明白,但有必要技能的人就会明白,这才是最重要的。

你可以这样想。也许你不会理解一份复杂的法律文件。但如果你有看文件的自由,并保留一份副本,你就可以咨询某个人,他可以检查文件中的法律陷阱。

换句话说,开源软件具有透明度。

FOSS 与开源之间的区别是什么?

你会经常遇到 FOSS 和 开源 Open Source 的术语。它们经常被互换使用。

它们是同一件事吗?这很难用“是”和“不是”来回答。

你看,FOSS 中的“Free”一词让很多人感到困惑,因为人们错误地认为它是免费的。企业高管、高层和决策者往往会关注自由和开源中的“免费”。由于他们是商业人士,专注于为他们的公司赚钱,“自由”一词在采用 FOSS 原则时起到了威慑作用。

这就是为什么在上世纪 90 年代中期创立了一个名为 开源促进会 Open Source Initiative 的新组织。他们从自由和开放源码软件中去掉了“自由”一词,并创建了自己的开放源码的定义,以及自己的一套许可证。

开源 Open Source ”一词在软件行业特别流行。高管们对开源更加适应。开源软件的采用迅速增长,我相信 “免费”一词的删除确实起到了作用。

有问题吗?

这正如我在什么是 Linux 发行版一文中所解释的那样,FOSS/开源的概念在 Linux 的发展和普及中起到了很大的作用。

我试图在这篇黑话解释文章中用更简单的语言解释 FOSS 和开源的概念,而试图避免在细节或技术精度上做过多的阐述。

我希望你现在对这个话题有了更好的理解。如果你有问题或建议,欢迎留言并继续讨论。


via: https://itsfoss.com/what-is-foss/

作者:Abhishek Prakash 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在本文的 第一部分 的结尾,我承诺要写关于接口的内容。我不想在这里写有关接口或完整或简短的讲义。相反,我将展示一个简单的示例,来说明如何定义和使用接口,以及如何利用无处不在的 io.Writer 接口。还有一些关于 反射 reflection 半主机 semihosting 的内容。

STM32F030F4P6]

接口是 Go 语言的重要组成部分。如果你想了解更多有关它们的信息,我建议你阅读《高效的 Go 编程》 和 Russ Cox 的文章

并发 Blinky – 回顾

当你阅读前面示例的代码时,你可能会注意到一中打开或关闭 LED 的反直觉方式。 Set 方法用于关闭 LED,Clear 方法用于打开 LED。这是由于在 漏极开路配置 open-drain configuration 下驱动了 LED。我们可以做些什么来减少代码的混乱?让我们用 OnOff 方法来定义 LED 类型:

type LED struct {
    pin gpio.Pin
}

func (led LED) On() {
    led.pin.Clear()
}

func (led LED) Off() {
    led.pin.Set()
}

现在我们可以简单地调用 led.On()led.Off(),这不会再引起任何疑惑了。

在前面的所有示例中,我都尝试使用相同的 漏极开路配置 open-drain configuration 来避免代码复杂化。但是在最后一个示例中,对于我来说,将第三个 LED 连接到 GND 和 PA3 引脚之间并将 PA3 配置为 推挽模式 push-pull mode 会更容易。下一个示例将使用以此方式连接的 LED。

但是我们的新 LED 类型不支持推挽配置,实际上,我们应该将其称为 OpenDrainLED,并定义另一个类型 PushPullLED

type PushPullLED struct {
    pin gpio.Pin
}

func (led PushPullLED) On() {
    led.pin.Set()
}

func (led PushPullLED) Off() {
    led.pin.Clear()
}

请注意,这两种类型都具有相同的方法,它们的工作方式也相同。如果在 LED 上运行的代码可以同时使用这两种类型,而不必注意当前使用的是哪种类型,那就太好了。 接口类型可以提供帮助:

package main

import (
    "delay"

    "stm32/hal/gpio"
    "stm32/hal/system"
    "stm32/hal/system/timer/systick"
)

type LED interface {
    On()
    Off()
}

type PushPullLED struct{ pin gpio.Pin }

func (led PushPullLED) On()  {
    led.pin.Set()
}

func (led PushPullLED) Off() {
    led.pin.Clear()
}

func MakePushPullLED(pin gpio.Pin) PushPullLED {
    pin.Setup(&gpio.Config{Mode: gpio.Out, Driver: gpio.PushPull})
    return PushPullLED{pin}
}

type OpenDrainLED struct{ pin gpio.Pin }

func (led OpenDrainLED) On()  {
    led.pin.Clear()
}

func (led OpenDrainLED) Off() {
    led.pin.Set()
}

func MakeOpenDrainLED(pin gpio.Pin) OpenDrainLED {
    pin.Setup(&gpio.Config{Mode: gpio.Out, Driver: gpio.OpenDrain})
    return OpenDrainLED{pin}
}

var led1, led2 LED

func init() {
    system.SetupPLL(8, 1, 48/8)
    systick.Setup(2e6)

    gpio.A.EnableClock(false)
    led1 = MakeOpenDrainLED(gpio.A.Pin(4))
    led2 = MakePushPullLED(gpio.A.Pin(3))
}

func blinky(led LED, period int) {
    for {
        led.On()
        delay.Millisec(100)
        led.Off()
        delay.Millisec(period - 100)
    }
}

func main() {
    go blinky(led1, 500)
    blinky(led2, 1000)
}

我们定义了 LED 接口,它有两个方法: OnOffPushPullLEDOpenDrainLED 类型代表两种驱动 LED 的方式。我们还定义了两个用作构造函数的 Make*LED 函数。这两种类型都实现了 LED 接口,因此可以将这些类型的值赋给 LED 类型的变量:

led1 = MakeOpenDrainLED(gpio.A.Pin(4))
led2 = MakePushPullLED(gpio.A.Pin(3))

在这种情况下, 可赋值性 assignability 在编译时检查。赋值后,led1 变量包含一个 OpenDrainLED{gpio.A.Pin(4)},以及一个指向 OpenDrainLED 类型的方法集的指针。 led1.On() 调用大致对应于以下 C 代码:

led1.methods->On(led1.value)

如你所见,如果仅考虑函数调用的开销,这是相当廉价的抽象。

但是,对接口的任何赋值都会导致包含有关已赋值类型的大量信息。对于由许多其他类型组成的复杂类型,可能会有很多信息:

$ egc
$ arm-none-eabi-size cortexm0.elf
   text    data     bss     dec     hex filename
  10356     196     212   10764    2a0c cortexm0.elf

如果我们不使用 反射,可以通过避免包含类型和结构字段的名称来节省一些字节:

$ egc -nf -nt
$ arm-none-eabi-size cortexm0.elf
   text    data     bss     dec     hex filename
  10312     196     212   10720    29e0 cortexm0.elf

生成的二进制文件仍然包含一些有关类型的必要信息和关于所有导出方法(带有名称)的完整信息。在运行时,主要是当你将存储在接口变量中的一个值赋值给任何其他变量时,需要此信息来检查可赋值性。

我们还可以通过重新编译所导入的包来删除它们的类型和字段名称:

$ cd $HOME/emgo
$ ./clean.sh
$ cd $HOME/firstemgo
$ egc -nf -nt
$ arm-none-eabi-size cortexm0.elf
   text    data     bss     dec     hex filename
  10272     196     212   10680    29b8 cortexm0.elf

让我们加载这个程序,看看它是否按预期工作。这一次我们将使用 st-flash 命令:

$ arm-none-eabi-objcopy -O binary cortexm0.elf cortexm0.bin
$ st-flash write cortexm0.bin 0x8000000
st-flash 1.4.0-33-gd76e3c7
2018-04-10T22:04:34 INFO usb.c: -- exit_dfu_mode
2018-04-10T22:04:34 INFO common.c: Loading device parameters....
2018-04-10T22:04:34 INFO common.c: Device connected is: F0 small device, id 0x10006444
2018-04-10T22:04:34 INFO common.c: SRAM size: 0x1000 bytes (4 KiB), Flash: 0x4000 bytes (16 KiB) in pages of 1024 bytes
2018-04-10T22:04:34 INFO common.c: Attempting to write 10468 (0x28e4) bytes to stm32 address: 134217728 (0x8000000)
Flash page at addr: 0x08002800 erased
2018-04-10T22:04:34 INFO common.c: Finished erasing 11 pages of 1024 (0x400) bytes
2018-04-10T22:04:34 INFO common.c: Starting Flash write for VL/F0/F3/F1_XL core id
2018-04-10T22:04:34 INFO flash_loader.c: Successfully loaded flash loader in sram
 11/11 pages written
2018-04-10T22:04:35 INFO common.c: Starting verification of write complete
2018-04-10T22:04:35 INFO common.c: Flash written and verified! jolly good!

我没有将 NRST 信号连接到编程器,因此无法使用 -reset 选项,必须按下复位按钮才能运行程序。

Interfaces

看来,st-flash 与此板配合使用有点不可靠(通常需要复位 ST-LINK 加密狗)。此外,当前版本不会通过 SWD 发出复位命令(仅使用 NRST 信号)。软件复位是不现实的,但是它通常是有效的,缺少它会将会带来不便。对于 板卡程序员 board-programmer 来说 OpenOCD 工作得更好。

UART

UART( 通用异步收发传输器 Universal Aynchronous Receiver-Transmitter )仍然是当今微控制器最重要的外设之一。它的优点是以下属性的独特组合:

  • 相对较高的速度,
  • 仅两条信号线(在 半双工 half-duplex 通信的情况下甚至一条),
  • 角色对称,
  • 关于新数据的 同步带内信令 synchronous in-band signaling (起始位),
  • 在传输 words 内的精确计时。

这使得最初用于传输由 7-9 位的字组成的异步消息的 UART,也被用于有效地实现各种其他物理协议,例如被 WS28xx LEDs1-wire 设备使用的协议。

但是,我们将以其通常的角色使用 UART:从程序中打印文本消息。

package main

import (
    "io"
    "rtos"

    "stm32/hal/dma"
    "stm32/hal/gpio"
    "stm32/hal/irq"
    "stm32/hal/system"
    "stm32/hal/system/timer/systick"
    "stm32/hal/usart"
)

var tts *usart.Driver

func init() {
    system.SetupPLL(8, 1, 48/8)
    systick.Setup(2e6)

    gpio.A.EnableClock(true)
    tx := gpio.A.Pin(9)

    tx.Setup(&gpio.Config{Mode: gpio.Alt})
    tx.SetAltFunc(gpio.USART1_AF1)
    d := dma.DMA1
    d.EnableClock(true)
    tts = usart.NewDriver(usart.USART1, d.Channel(2, 0), nil, nil)
    tts.Periph().EnableClock(true)
    tts.Periph().SetBaudRate(115200)
    tts.Periph().Enable()
    tts.EnableTx()

    rtos.IRQ(irq.USART1).Enable()
    rtos.IRQ(irq.DMA1_Channel2_3).Enable()
}

func main() {
    io.WriteString(tts, "Hello, World!
")
}

func ttsISR() {
    tts.ISR()
}

func ttsDMAISR() {
    tts.TxDMAISR()
}

//c:__attribute__((section(".ISRs")))
var ISRs = [...]func(){
    irq.USART1:          ttsISR,
    irq.DMA1_Channel2_3: ttsDMAISR,
}

你会发现此代码可能有些复杂,但目前 STM32 HAL 中没有更简单的 UART 驱动程序(在某些情况下,简单的轮询驱动程序可能会很有用)。 usart.Driver 是使用 DMA 和中断来减轻 CPU 负担的高效驱动程序。

STM32 USART 外设提供传统的 UART 及其同步版本。要将其用作输出,我们必须将其 Tx 信号连接到正确的 GPIO 引脚:

tx.Setup(&gpio.Config{Mode: gpio.Alt})
tx.SetAltFunc(gpio.USART1_AF1)

在 Tx-only 模式下配置 usart.Driver (rxdma 和 rxbuf 设置为 nil):

tts = usart.NewDriver(usart.USART1, d.Channel(2, 0), nil, nil)

我们使用它的 WriteString 方法来打印这句名言。让我们清理所有内容并编译该程序:

$ cd $HOME/emgo
$ ./clean.sh
$ cd $HOME/firstemgo
$ egc
$ arm-none-eabi-size cortexm0.elf
  text       data        bss        dec        hex    filename
  12728        236        176      13140       3354    cortexm0.elf

要查看某些内容,你需要在 PC 中使用 UART 外设。

请勿使用 RS232 端口或 USB 转 RS232 转换器!

STM32 系列使用 3.3V 逻辑,但是 RS232 可以产生 -15 V ~ +15 V 的电压,这可能会损坏你的 MCU。你需要使用 3.3V 逻辑的 USB 转 UART 转换器。流行的转换器基于 FT232 或 CP2102 芯片。

UART

你还需要一些终端仿真程序(我更喜欢 picocom)。刷新新图像,运行终端仿真器,然后按几次复位按钮:

$ openocd -d0 -f interface/stlink.cfg -f target/stm32f0x.cfg -c 'init; program cortexm0.elf; reset run; exit'
Open On-Chip Debugger 0.10.0+dev-00319-g8f1f912a (2018-03-07-19:20)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
debug_level: 0
adapter speed: 1000 kHz
adapter_nsrst_delay: 100
none separate
adapter speed: 950 kHz
target halted due to debug-request, current mode: Thread
xPSR: 0xc1000000 pc: 0x080016f4 msp: 0x20000a20
adapter speed: 4000 kHz
** Programming Started **
auto erase enabled
target halted due to breakpoint, current mode: Thread
xPSR: 0x61000000 pc: 0x2000003a msp: 0x20000a20
wrote 13312 bytes from file cortexm0.elf in 1.020185s (12.743 KiB/s)
** Programming Finished **
adapter speed: 950 kHz
$
$ picocom -b 115200 /dev/ttyUSB0
picocom v3.1

port is        : /dev/ttyUSB0
flowcontrol    : none
baudrate is    : 115200
parity is      : none
databits are   : 8
stopbits are   : 1
escape is      : C-a
local echo is  : no
noinit is      : no
noreset is     : no
hangup is      : no
nolock is      : no
send_cmd is    : sz -vv
receive_cmd is : rz -vv -E
imap is        :
omap is        :
emap is        : crcrlf,delbs,
logfile is     : none
initstring     : none
exit_after is  : not set
exit is        : no

Type [C-a] [C-h] to see available commands
Terminal ready
Hello, World!
Hello, World!
Hello, World!

每次按下复位按钮都会产生新的 “Hello,World!”行。一切都在按预期进行。

要查看此 MCU 的 双向 bi-directional UART 代码,请查看 此示例

io.Writer 接口

io.Writer 接口可能是 Go 中第二种最常用的接口类型,仅次于 error 接口。其定义如下所示:

type Writer interface {
    Write(p []byte) (n int, err error)
}

usart.Driver 实现了 io.Writer,因此我们可以替换:

tts.WriteString("Hello, World!
")

io.WriteString(tts, "Hello, World!
")

此外,你需要将 io 包添加到 import 部分。

io.WriteString 函数的声明如下所示:

func WriteString(w Writer, s string) (n int, err error)

如你所见,io.WriteString 允许使用实现了 io.Writer 接口的任何类型来编写字符串。在内部,它检查基础类型是否具有 WriteString 方法,并使用该方法代替 Write(如果可用)。

让我们编译修改后的程序:

$ egc
$ arm-none-eabi-size cortexm0.elf
   text    data     bss     dec     hex filename
  15456     320     248   16024    3e98 cortexm0.elf

如你所见,io.WriteString 导致二进制文件的大小显着增加:15776-12964 = 2812 字节。 Flash 上没有太多空间了。是什么引起了这么大规模的增长?

使用这个命令:

arm-none-eabi-nm --print-size --size-sort --radix=d cortexm0.elf

我们可以打印两种情况下按其大小排序的所有符号。通过过滤和分析获得的数据(awkdiff),我们可以找到大约 80 个新符号。最大的十个如下所示:

> 00000062 T stm32$hal$usart$Driver$DisableRx
> 00000072 T stm32$hal$usart$Driver$RxDMAISR
> 00000076 T internal$Type$Implements
> 00000080 T stm32$hal$usart$Driver$EnableRx
> 00000084 t errors$New
> 00000096 R $8$stm32$hal$usart$Driver$$
> 00000100 T stm32$hal$usart$Error$Error
> 00000360 T io$WriteString
> 00000660 T stm32$hal$usart$Driver$Read

因此,即使我们不使用 usart.Driver.Read 方法,但它被编译进来了,与 DisableRxRxDMAISREnableRx 以及上面未提及的其他方法一样。不幸的是,如果你为接口赋值了一些内容,就需要它的完整方法集(包含所有依赖项)。对于使用大多数方法的大型程序来说,这不是问题。但是对于我们这种极简的情况而言,这是一个巨大的负担。

我们已经接近 MCU 的极限,但让我们尝试打印一些数字(你需要在 import 部分中用 strconv 替换 io 包):

func main() {
    a := 12
    b := -123

    tts.WriteString("a = ")
    strconv.WriteInt(tts, a, 10, 0, 0)
    tts.WriteString("
")
    tts.WriteString("b = ")
    strconv.WriteInt(tts, b, 10, 0, 0)
    tts.WriteString("
")

    tts.WriteString("hex(a) = ")
    strconv.WriteInt(tts, a, 16, 0, 0)
    tts.WriteString("
")
    tts.WriteString("hex(b) = ")
    strconv.WriteInt(tts, b, 16, 0, 0)
    tts.WriteString("
")
}

与使用 io.WriteString 函数的情况一样,strconv.WriteInt 的第一个参数的类型为 io.Writer

$ egc
/usr/local/arm/bin/arm-none-eabi-ld: /home/michal/firstemgo/cortexm0.elf section `.rodata' will not fit in region `Flash'
/usr/local/arm/bin/arm-none-eabi-ld: region `Flash' overflowed by 692 bytes
exit status 1

这一次我们的空间超出的不多。让我们试着精简一下有关类型的信息:

$ cd $HOME/emgo
$ ./clean.sh
$ cd $HOME/firstemgo
$ egc -nf -nt
$ arm-none-eabi-size cortexm0.elf
   text    data     bss     dec     hex filename
  15876     316     320   16512    4080 cortexm0.elf

很接近,但很合适。让我们加载并运行此代码:

a = 12
b = -123
hex(a) = c
hex(b) = -7b

Emgo 中的 strconv 包与 Go 中的原型有很大的不同。它旨在直接用于写入格式化的数字,并且在许多情况下可以替换沉重的 fmt 包。 这就是为什么函数名称以 Write 而不是 Format 开头,并具有额外的两个参数的原因。 以下是其用法示例:

func main() {
    b := -123
    strconv.WriteInt(tts, b, 10, 0, 0)
    tts.WriteString("
")
    strconv.WriteInt(tts, b, 10, 6, ' ')
    tts.WriteString("
")
    strconv.WriteInt(tts, b, 10, 6, '0')
    tts.WriteString("
")
    strconv.WriteInt(tts, b, 10, 6, '.')
    tts.WriteString("
")
    strconv.WriteInt(tts, b, 10, -6, ' ')
    tts.WriteString("
")
    strconv.WriteInt(tts, b, 10, -6, '0')
    tts.WriteString("
")
    strconv.WriteInt(tts, b, 10, -6, '.')
    tts.WriteString("
")
}

下面是它的输出:

-123
  -123
-00123
..-123
-123
-123
-123..

Unix 流 和 莫尔斯电码 Morse code

由于大多数写入的函数都使用 io.Writer 而不是具体类型(例如 C 中的 FILE ),因此我们获得了类似于 Unix stream 的功能。在 Unix 中,我们可以轻松地组合简单的命令来执行更大的任务。例如,我们可以通过以下方式将文本写入文件:

echo "Hello, World!" > file.txt

> 操作符将前面命令的输出流写入文件。还有 | 操作符,用于连接相邻命令的输出流和输入流。

多亏了流,我们可以轻松地转换/过滤任何命令的输出。例如,要将所有字母转换为大写,我们可以通过 tr 命令过滤 echo 的输出:

echo "Hello, World!" | tr a-z A-Z > file.txt

为了显示 io.Writer 和 Unix 流之间的类比,让我们编写以下代码:

io.WriteString(tts, "Hello, World!
")

采用以下伪 unix 形式:

io.WriteString "Hello, World!" | usart.Driver usart.USART1

下一个示例将显示如何执行此操作:

io.WriteString "Hello, World!" | MorseWriter | usart.Driver usart.USART1

让我们来创建一个简单的编码器,它使用莫尔斯电码对写入的文本进行编码:

type MorseWriter struct {
    W io.Writer
}

func (w *MorseWriter) Write(s []byte) (int, error) {
    var buf [8]byte
    for n, c := range s {
        switch {
        case c == '\n':
            c = ' ' // Replace new lines with spaces.
        case 'a' <= c && c <= 'z':
            c -= 'a' - 'A' // Convert to upper case.
        }
        if c < ' ' || 'Z' < c {
            continue // c is outside ASCII [' ', 'Z']
        }
        var symbol morseSymbol
        if c == ' ' {
            symbol.length = 1
            buf[0] = ' '
        } else {
            symbol = morseSymbols[c-'!']
            for i := uint(0); i < uint(symbol.length); i++ {
                if (symbol.code>>i)&1 != 0 {
                    buf[i] = '-'
                } else {
                    buf[i] = '.'
                }
            }
        }
        buf[symbol.length] = ' '
        if _, err := w.W.Write(buf[:symbol.length+1]); err != nil {
            return n, err
        }
    }
    return len(s), nil
}

type morseSymbol struct {
    code, length byte
}

//emgo:const
var morseSymbols = [...]morseSymbol{
    {1<<0 | 1<<1 | 1<<2, 4}, // ! ---.
    {1<<1 | 1<<4, 6},        // " .-..-.
    {},                      // #
    {1<<3 | 1<<6, 7},        // $ ...-..-

    // Some code omitted...

    {1<<0 | 1<<3, 4},        // X -..-
    {1<<0 | 1<<2 | 1<<3, 4}, // Y -.--
    {1<<0 | 1<<1, 4},        // Z --..
}

你可以在 这里 找到完整的 morseSymbols 数组。 //emgo:const 指令确保 morseSymbols 数组不会被复制到 RAM 中。

现在我们可以通过两种方式打印句子:

func main() {
    s := "Hello, World!
"
    mw := &MorseWriter{tts}

    io.WriteString(tts, s)
    io.WriteString(mw, s)
}

我们使用指向 MorseWriter &MorseWriter{tts} 的指针而不是简单的 MorseWriter{tts} 值,因为 MorseWriter 太大,不适合接口变量。

与 Go 不同,Emgo 不会为存储在接口变量中的值动态分配内存。接口类型的大小受限制,相当于三个指针(适合 slice )或两个 float64(适合 complex128)的大小,以较大者为准。它可以直接存储所有基本类型和小型 “结构体/数组” 的值,但是对于较大的值,你必须使用指针。

让我们编译此代码并查看其输出:

$ egc
$ arm-none-eabi-size cortexm0.elf
   text    data     bss     dec     hex filename
  15152     324     248   15724    3d6c cortexm0.elf
Hello, World!
.... . .-.. .-.. --- --..--   .-- --- .-. .-.. -.. ---.

终极闪烁

Blinky 是等效于 “Hello,World!” 程序的硬件。一旦有了摩尔斯编码器,我们就可以轻松地将两者结合起来以获得终极闪烁程序:

package main

import (
    "delay"
    "io"

    "stm32/hal/gpio"
    "stm32/hal/system"
    "stm32/hal/system/timer/systick"
)

var led gpio.Pin

func init() {
    system.SetupPLL(8, 1, 48/8)
    systick.Setup(2e6)

    gpio.A.EnableClock(false)
    led = gpio.A.Pin(4)

    cfg := gpio.Config{Mode: gpio.Out, Driver: gpio.OpenDrain, Speed: gpio.Low}
    led.Setup(&cfg)
}

type Telegraph struct {
    Pin   gpio.Pin
    Dotms int // Dot length [ms]
}

func (t Telegraph) Write(s []byte) (int, error) {
    for _, c := range s {
        switch c {
        case '.':
            t.Pin.Clear()
            delay.Millisec(t.Dotms)
            t.Pin.Set()
            delay.Millisec(t.Dotms)
        case '-':
            t.Pin.Clear()
            delay.Millisec(3 * t.Dotms)
            t.Pin.Set()
            delay.Millisec(t.Dotms)
        case ' ':
            delay.Millisec(3 * t.Dotms)
        }
    }
    return len(s), nil
}

func main() {
    telegraph := &MorseWriter{Telegraph{led, 100}}
    for {
        io.WriteString(telegraph, "Hello, World! ")
    }
}

// Some code omitted...

在上面的示例中,我省略了 MorseWriter 类型的定义,因为它已在前面展示过。完整版可通过 这里 获取。让我们编译它并运行:

$ egc
$ arm-none-eabi-size cortexm0.elf
   text    data     bss     dec     hex filename
  11772     244     244   12260    2fe4 cortexm0.elf

Ultimate Blinky

反射

是的,Emgo 支持 反射reflect 包尚未完成,但是已完成的部分足以实现 fmt.Print 函数族了。来看看我们可以在小型 MCU 上做什么。

为了减少内存使用,我们将使用 半主机 semihosting 作为标准输出。为了方便起见,我们还编写了简单的 println 函数,它在某种程度上类似于 fmt.Println

package main

import (
    "debug/semihosting"
    "reflect"
    "strconv"

    "stm32/hal/system"
    "stm32/hal/system/timer/systick"
)

var stdout semihosting.File

func init() {
    system.SetupPLL(8, 1, 48/8)
    systick.Setup(2e6)

    var err error
    stdout, err = semihosting.OpenFile(":tt", semihosting.W)
    for err != nil {
    }
}

type stringer interface {
    String() string
}

func println(args ...interface{}) {
    for i, a := range args {
        if i > 0 {
            stdout.WriteString(" ")
        }
        switch v := a.(type) {
        case string:
            stdout.WriteString(v)
        case int:
            strconv.WriteInt(stdout, v, 10, 0, 0)
        case bool:
            strconv.WriteBool(stdout, v, 't', 0, 0)
        case stringer:
            stdout.WriteString(v.String())
        default:
            stdout.WriteString("%unknown")
        }
    }
    stdout.WriteString("
")
}

type S struct {
    A int
    B bool
}

func main() {
    p := &S{-123, true}

    v := reflect.ValueOf(p)

    println("kind(p) =", v.Kind())
    println("kind(*p) =", v.Elem().Kind())
    println("type(*p) =", v.Elem().Type())

    v = v.Elem()

    println("*p = {")
    for i := 0; i < v.NumField(); i++ {
        ft := v.Type().Field(i)
        fv := v.Field(i)
        println("  ", ft.Name(), ":", fv.Interface())
    }
    println("}")
}

semihosting.OpenFile 函数允许在主机端打开/创建文件。特殊路径 :tt 对应于主机的标准输出。

println 函数接受任意数量的参数,每个参数的类型都是任意的:

func println(args ...interface{})

可能是因为任何类型都实现了空接口 interface{}println 使用 类型开关 打印字符串,整数和布尔值:

switch v := a.(type) {
case string:
    stdout.WriteString(v)
case int:
    strconv.WriteInt(stdout, v, 10, 0, 0)
case bool:
    strconv.WriteBool(stdout, v, 't', 0, 0)
case stringer:
    stdout.WriteString(v.String())
default:
    stdout.WriteString("%unknown")
}

此外,它还支持任何实现了 stringer 接口的类型,即任何具有 String() 方法的类型。在任何 case 子句中,v 变量具有正确的类型,与 case 关键字后列出的类型相同。

reflect.ValueOf(p) 函数通过允许以编程的方式分析其类型和内容的形式返回 p。如你所见,我们甚至可以使用 v.Elem() 取消引用指针,并打印所有结构体及其名称。

让我们尝试编译这段代码。现在让我们看看如果编译时没有类型和字段名,会有什么结果:

$ egc -nt -nf
$ arm-none-eabi-size cortexm0.elf
   text    data     bss     dec     hex filename
  16028     216     312   16556    40ac cortexm0.elf

闪存上只剩下 140 个可用字节。让我们使用启用了半主机的 OpenOCD 加载它:

$ openocd -d0 -f interface/stlink.cfg -f target/stm32f0x.cfg -c 'init; program cortexm0.elf; arm semihosting enable; reset run'
Open On-Chip Debugger 0.10.0+dev-00319-g8f1f912a (2018-03-07-19:20)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
debug_level: 0
adapter speed: 1000 kHz
adapter_nsrst_delay: 100
none separate
adapter speed: 950 kHz
target halted due to debug-request, current mode: Thread
xPSR: 0xc1000000 pc: 0x08002338 msp: 0x20000a20
adapter speed: 4000 kHz
** Programming Started **
auto erase enabled
target halted due to breakpoint, current mode: Thread
xPSR: 0x61000000 pc: 0x2000003a msp: 0x20000a20
wrote 16384 bytes from file cortexm0.elf in 0.700133s (22.853 KiB/s)
** Programming Finished **
semihosting is enabled
adapter speed: 950 kHz
kind(p) = ptr
kind(*p) = struct
type(*p) =
*p = {
   X. : -123
   X. : true
}

如果你实际运行此代码,则会注意到半主机运行缓慢,尤其是在逐字节写入时(缓冲很有用)。

如你所见,*p 没有类型名称,并且所有结构字段都具有相同的 X. 名称。让我们再次编译该程序,这次不带 -nt -nf 选项:

$ egc
$ arm-none-eabi-size cortexm0.elf
   text    data     bss     dec     hex filename
  16052     216     312   16580    40c4 cortexm0.elf

现在已经包括了类型和字段名称,但仅在 main.go 文件中 main 包中定义了它们。该程序的输出如下所示:

kind(p) = ptr
kind(*p) = struct
type(*p) = S
*p = {
   A : -123
   B : true
}

反射是任何易于使用的序列化库的关键部分,而像 JSON 这样的序列化 算法 物联网 IoT 时代也越来越重要。

这些就是我完成的本文的第二部分。我认为有机会进行第三部分,更具娱乐性的部分,在那里我们将各种有趣的设备连接到这块板上。如果这块板装不下,我们就换一块大一点的。


via: https://ziutek.github.io/2018/04/14/go_on_very_small_hardware2.html

作者:Michał Derkacz 译者:gxlct008 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

2020 年 10 月 22 日:Canonical 今天宣布发布包含树莓派优化的 Ubuntu 20.10 桌面版和服务器版系统,以支持研究人员、发明家、教育和企业。为全世界最易可访问的硬件带来了全世界最为流行的开放平台。

“这次版本发布,我们庆祝树莓派基金会致力于将开放计算给带给全球人们所做的承诺”,Canonical CEO,Mark Shuttleworth 说到。“我们也很荣幸通过优化树莓派上的 Ubuntu 以支持上述倡议,不管是个人使用,教育用途还是作为他们未来业务的基础。”

树莓派 2、3 和 4 加入 Ubuntu 认证的一系列 X86 和 ARM 设备队列中。Ubuntu 是以公共云和桌面产品而闻名的操作系统(OS),在 AWS、微软 Azure、Google、IBM 和 Oracle Clouds 被广泛使用。戴尔、惠普和联想都对其电脑做了Ubuntu桌面认证。

Ubuntu 20.10 包含了为弹性的 微云 micro cloud ,提供VM的小型服务器集群,按需供给的边缘 Kuberenetes 的 LXD 4.6 和 MicroK8s 1.19。可适用于远程办公室,分公司,仓储和分布的基础设施。

Ubuntu 桌面 20.10

在树莓派桌面支持上方的 Ubuntu 20.10 集成了 GNOME 3.38,此版本改进了应用栅格,移除了常用标签和允许根据用户喜好对应用排列和管理。电源设置中加入了电池百分比开关,私有 WiFi 热点可通过生成的 QR(二维码)进行分享,重启选项已被添加至注销/关机菜单中。

20.10 桌面新增了对 Ubuntu 认证设备的支持。更多的 Ubuntu 工作站现获得了支持指纹识别功能支持。配备屏幕键盘的二合一设备现已获得完整支持,这使得诸如戴尔 XPS 二合一和联想 Yoga 等设备的 Ubuntu 体验得以改善。

树莓派 4GB 或 8GB 内存版本将获得 Ubuntu 桌面的完整支持。“从经典的树莓派开发板到工业级模块,这是树莓派迈向拥有长期支持和安全更新的 Ubuntu LTS(长期支持)的第一步,且符合我们将最好的计算和开源能力带给全球用户的首创精神”树莓派贸易 CEO,Eben Upton 说到。

什么是微云

微云是一种新类别,适用于边缘端按需供给计算的基础设施。微云是分布式的,最小化的, 规模可以为小型,也可以为大型。在 Ubuntu 20.10 中,Canonical 在 Ubuntu 上引入了其微云堆栈,即结合 MAAS、LXD、MicroK8s 和 Ceph 以提供具有弹性的口袋云,加固了 5G RAN,工业 4.0 工厂,V2X 基础设施和智慧城市及健康设施下的关键业务工作负载。

在树莓派上,用户可以从 MicroK8s 开始在边缘端编排高可用的工作负载,或用 LXD 的集群和虚拟机管理能力来构建家庭实验室。Ubuntu 20.10 的发布给用户介绍了一种方式来实验,测试或者通过树莓派完整的云能力的开发。树莓派与 Ubuntu 20.10的组合使得从机器人到人工智能、机器学习的任何事情都成为可能。

Ubuntu 20.10 现已可从此处下载:

ubuntu-20.10-desktop-amd64.iso2.7GDesktop image for 64-bit PC (AMD64) computers (standard download)
ubuntu-20.10-live-server-amd64.iso1.0GServer install image for 64-bit PC (AMD64) computers (standard download)