分类 技术 下的文章

你在使用 Linux 命令行时曾经得到过“ 拒绝访问 Permission denied ”的错误提示吗?这可能是因为你正在尝试执行一个需要 root 权限的操作。例如,下面的截图展示了当我尝试复制一个二进制文件到一个系统目录时产生的错误。

shell 的拒绝访问

那么该怎么解决这个错误?很简单,使用 sudo 命令。

用 sudo 运行命令

用户运行此命令后会被提示输入他们(自己)的登录密码。一旦输入了正确的密码,操作将会成功执行。

毫无疑问,sudo 是任何在 Linux 上使用命令行的人都必须知道的命令。但是,为了更负责、更有效地使用该命令,你还是要知道一些相关(及深入)的细节。这正是我们将会在这篇文章中讨论的。

在我们继续之前,值得提一下的是,这篇文章所提到的所有命令指示都已经在 Ubuntu 14.04 LTS 下的 4.3.11 版 Bash 下通过测试。

什么是 sudo

正如你们大部分人所知道的,sudo 用来执行需要提升权限(通常是作为 root 用户)的命令。在这篇文章之前的简介部分已经讨论过这样的一个例子。然而,如果你想的话,你能用 sudo 以其它(非 root )用户运行命令。

这是由工具提供的 -u 命令行选项所实现的。举个例子,如下例所展示的那样,我(himanshu)尝试将一个在其他用户(howtoforge)的 Home 目录中的文件重命名,但是得到一个“访问拒绝”的错误。然后我加上 sudo -u howtoforge 后用同样的“mv”命令,命令成功执行了:

什么是 sudo

任何人都能用 sudo 吗?

不是。一个用户要能使用 sudo ,应该在 /etc/sudoers 文件里有一条跟该用户相关的信息。下述摘自 Ubuntu 网站的一段能讲得更清楚:

/etc/sudoers 文件控制了谁能以哪个用户的身份在哪个机器上运行什么命令,还可以控制特别的情况,例如对于特定的命令是否需要输入密码。这个文件由 别名 aliases (基本变量)和 用户标识 user specifications (控制谁能运行什么命令)组成。

如果你正在使用 Ubuntu,让一个用户能运行 sudo 命令很容易:你所需要做的就是把账户类型改成 管理员 administrator 。这可直接在 系统设置 System Settings -> 用户账户 User Accounts 里完成。

sudo 用户

首先解锁该窗口:

unlocking window

然后选择你想改变用户类型的用户,然后将类型改成 管理员 administrator

choose sudo accounts

然而,如果你不使用 Ubuntu,或者你的发行版没有提供这个特性,你可以手动编辑 /etc/sudoers 文件来实现此改变。要在文件中添加这样的一行:

[user]    ALL=(ALL:ALL) ALL

无需赘言,[user] 应该用你想提升 sudo 权限的用户的用户名所代替。在这里值得提到的一件重要的事情是,官方建议通过 visudo 命令编辑该文件 —— 你需要做的就是运行下述命令:

sudo visudo

为了说清究竟是怎么一回事,这里有段从 visudo 手册里的摘要:

visudo 以安全的模式编辑 sudoers 文件。visudo 锁定 sudoers 文件以防多个编辑同时进行,提供基本的检查(sanity checks)和语法错误检查。如果 sudoers 文件现在正在被编辑,你将会收到一个信息提示稍后再试。

关于 visudo 的更多信息,前往这里

什么是 sudo 会话

如果你经常使用 sudo 命令,你肯定注意到过当你成功输入一次密码后,可以不用输入密码再运行几次 sudo 命令。但是一段时间后,sudo 命令会再次要求你的密码。

这种现象跟运行 sudo 命令数目无关,跟时间有关。是的,sudo 默认在输入一次密码后 15 分钟内不会再次要求密码。15 分钟后,你会再次被要求输入密码。

然而,如果你想的话,你能改变这种现象。用以下命令打开 /etc/sudoers 文件:

sudo visudo

找到这一行:

Defaults env_reset

env_reset

然后在这行最后添加以下变量:

Defaults env_reset,timestamp_timeout=[new-value]

[new-value] 为想要 sudo 会话持续的时间数。例如,设数值为 40。

sudo timeout value

如果你希望每次使用 sudo 命令时都要求输入密码,你可以把这个变量赋值为 0 。想要 sudo 会话永远不过时,应赋值为 -1。

注意将 timestamp_timeout 的值赋为 “-1” 是强烈不推荐的。

sudo 密码

你可能注意过,当 sudo 要求输入密码然后你开始输入时,不会显示任何东西 —— 甚至连常规的星号都没有。虽然这不是什么大问题,不过一些用户就是希望显示星号。

好消息是那有可能也很容易做到。所有你需要做的就是在 /etc/sudoers 文件里将下述的行:

Defaults        env_reset

改成

Defaults        env_reset,pwfeedback

然后保存文件。

现在,无论什么时候输入 sudo 密码,星号都会显示。

hide the sudo password

一些重要的 sudo 命令行参数

除了 -u 命令行参数(我们已经在这篇教程的开始部分讨论过了),还有其他重要的 sudo 命令行参数值得注意。在这部分,我们将会讨论其中一些。

-k 参数

考虑下这种情况:输入密码后你刚刚运行了几个 sudo 驱动的命令。现在,如你所知,sudo 会话默认保持 15 分钟。假设在这会话期间,你需要让某些人访问你的终端,但你不想让他们可以使用 sudo ,你将会怎么做?

还好,有 -k 命令行参数允许用户取消 sudo 权限。这是 sudo 帮助页面 man page 对此的解释:

-k, --reset-timestamp

不带任何命令使用时,撤销用户缓存的凭据。换句话讲,下一次使用 sudo 将会要求输入密码。使用这个参数不需要密码,也可以放到一个 .logout 文件中来撤销 sudo 权限。

当与一个命令,或者一个可能需要密码的操作一起用时,这个参数将会导致 sudo 忽略用户缓存的凭据。结果是 sudo 要求输入密码(如果这是被安全策略所要求的),而且不会更新用户缓存的凭据。

-s 参数

有时你的工作要求你运行一堆需要 root 权限的命令,你不想每次都输入密码。你也不想通过改变 /etc/sudoers 文件调整 sudo 会话的过期时限。

这种情况下,你可以用 sudo-s 参数。这是 sudo 帮助页面对此的解释:

-s, --shell

如果设置了 SHELL 环境变量或者调用用户的密码数据库指定了 shell,就运行该 shell 。如果指定了命令,命令将会通过 shell 的 -c 参数将命令传递给该 shell 执行。如果没有指定命令,会执行一个交互式 shell。

所以,基本上这命令参数做的是:

  • 启动一个新的 shell - 至于是哪一个 shell,参照 SHELL 环境变量赋值。如果 $SHELL 是空的,将会用 /etc/passwd 中定义的 shell。
  • 如果你用 -s 参数传递了一个命令名(例如 sudo -s whoami),实际执行的是 sudo /bin/bash -c whoami
  • 如果你没有尝试执行其他命令(也就是说,你只是要运行 sudo -s),你将会得到一个有 root 权限的交互式的 shell。

请记住,-s 命令行参数给你一个有 root 权限的 shell,但那不是 root 环境 —— 还是执行的你自己的 .bashrc 。例如,在 sudo -s 运行的新 shell 里,执行 whoami 命令仍会返回你的用户名,而非 root 。

-i 参数

-i 参数跟我们讨论过的 -s 参数相像。然而,还是有点区别。一个重要的区别是 -i 给你的是 root 环境,意味着你的(用户的).bashrc 被忽略。这就像没有显式地用 root 登录也能成为 root 。此外,你也不用输入 root 用户密码。

重要:请注意 su 命令也能让你切换用户(默认切换到 root )。这个命令需要你输入 root 密码。为了避免这一点,你可以使用 sudo 执行它(sudo su),这样你只需要输入你的登录密码。然而,susudo su 有隐含的区别 —— 要了解它们,以及它们和 sudo -i 的区别,请看这里

总结

我希望现在你至少知道了 sudo 的基本知识,以及如何调整 sudo 的默认行为。请按我们解释过的那样去尝试调整 /etc/sudoers 。同时也浏览一下论坛讨论来更深入了解 sudo 命令。


via: https://www.howtoforge.com/tutorial/sudo-beginners-guide/

作者:Himanshu Arora 译者:ypingcn 校对:jasminepeng

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

这是 LXD 2.0 系列介绍文章的第十篇。

  1. LXD 入门
  2. 安装与配置
  3. 你的第一个 LXD 容器
  4. 资源控制
  5. 镜像管理
  6. 远程主机及容器迁移
  7. LXD 中的 Docker
  8. LXD 中的 LXD
  9. 实时迁移
  10. LXD 和 Juju
  11. LXD 和 OpenStack
  12. 调试,及给 LXD 做贡献

介绍

Juju 是 Canonical 的服务建模和部署工具。 它支持非常广泛的云服务提供商,使您能够轻松地在任何云上部署任何您想要的服务。

此外,Juju 2.0 还支持 LXD,既适用于本地部署,也适合开发,并且可以在云实例或物理机上共同协作。

本篇文章将关注本地使用,通过一个没有任何Juju经验的LXD用户来体验。

要求

本篇文章假设你已经安装了 LXD 2.0 并且配置完毕(看前面的文章),并且是在 Ubuntu 16.04 LTS 上运行的。

设置 Juju

第一件事是在 Ubuntu 16.04 上安装 Juju 2.0。这个很简单:

stgraber@dakara:~$ sudo apt install juju
Reading package lists... Done
Building dependency tree 
Reading state information... Done
The following additional packages will be installed:
 juju-2.0
Suggested packages:
 juju-core
The following NEW packages will be installed:
 juju juju-2.0
0 upgraded, 2 newly installed, 0 to remove and 0 not upgraded.
Need to get 39.7 MB of archives.
After this operation, 269 MB of additional disk space will be used.
Do you want to continue? [Y/n] 
Get:1 http://us.archive.ubuntu.com/ubuntu xenial-updates/main amd64 juju-2.0 amd64 2.0~beta7-0ubuntu1.16.04.1 [39.6 MB]
Get:2 http://us.archive.ubuntu.com/ubuntu xenial-updates/main amd64 juju all 2.0~beta7-0ubuntu1.16.04.1 [9,556 B]
Fetched 39.7 MB in 0s (53.4 MB/s)
Selecting previously unselected package juju-2.0.
(Reading database ... 255132 files and directories currently installed.)
Preparing to unpack .../juju-2.0_2.0~beta7-0ubuntu1.16.04.1_amd64.deb ...
Unpacking juju-2.0 (2.0~beta7-0ubuntu1.16.04.1) ...
Selecting previously unselected package juju.
Preparing to unpack .../juju_2.0~beta7-0ubuntu1.16.04.1_all.deb ...
Unpacking juju (2.0~beta7-0ubuntu1.16.04.1) ...
Processing triggers for man-db (2.7.5-1) ...
Setting up juju-2.0 (2.0~beta7-0ubuntu1.16.04.1) ...
Setting up juju (2.0~beta7-0ubuntu1.16.04.1) ...

安装完成后,我们可以使用 LXD 启动一个新的“控制器”。这意味着 Juju 不会修改你主机上的任何东西,它会在 LXD 容器中安装它的管理服务。

现在我们创建一个“test”控制器:

stgraber@dakara:~$ juju bootstrap localhost test
Creating Juju controller "local.test" on localhost/localhost
Bootstrapping model "admin"
Starting new instance for initial controller
Launching instance
 - juju-745d1be3-e93d-41a2-80d4-fbe8714230dd-machine-0
Installing Juju agent on bootstrap instance
Preparing for Juju GUI 2.1.2 release installation
Waiting for address
Attempting to connect to 10.178.150.72:22
Logging to /var/log/cloud-init-output.log on remote host
Running apt-get update
Running apt-get upgrade
Installing package: curl
Installing package: cpu-checker
Installing package: bridge-utils
Installing package: cloud-utils
Installing package: cloud-image-utils
Installing package: tmux
Fetching tools: curl -sSfw 'tools from %{url_effective} downloaded: HTTP %{http_code}; time %{time_total}s; size %{size_download} bytes; speed %{speed_download} bytes/s ' --retry 10 -o $bin/tools.tar.gz <[https://streams.canonical.com/juju/tools/agent/2.0-beta7/juju-2.0-beta7-xenial-amd64.tgz]>
Bootstrapping Juju machine agent
Starting Juju machine agent (jujud-machine-0)
Bootstrap agent installed
Waiting for API to become available: upgrade in progress (upgrade in progress)
Waiting for API to become available: upgrade in progress (upgrade in progress)
Waiting for API to become available: upgrade in progress (upgrade in progress)
Bootstrap complete, local.test now available.

这会花费一点时间,这时你可以看到一个正在运行的一个新的 LXD 容器:

stgraber@dakara:~$ lxc list juju-
+-----------------------------------------------------+---------+----------------------+------+------------+-----------+
|                         NAME                        |  STATE  |          IPV4        | IPV6 |    TYPE    | SNAPSHOTS |
+-----------------------------------------------------+---------+----------------------+------+------------+-----------+
| juju-745d1be3-e93d-41a2-80d4-fbe8714230dd-machine-0 | RUNNING | 10.178.150.72 (eth0) |      | PERSISTENT | 0         |
+-----------------------------------------------------+---------+----------------------+------+------------+-----------+

在 Juju 这边,你可以确认它是有响应的,并且还没有服务运行:

stgraber@dakara:~$ juju status
[Services] 
NAME STATUS EXPOSED CHARM 

[Units] 
ID WORKLOAD-STATUS JUJU-STATUS VERSION MACHINE PORTS PUBLIC-ADDRESS MESSAGE 

[Machines] 
ID STATE DNS INS-ID SERIES AZ

你也可以在浏览器中访问 Juju 的 GUI 界面:

stgraber@dakara:~$ juju gui
Opening the Juju GUI in your browser.
If it does not open, open this URL:
https://10.178.150.72:17070/gui/97fa390d-96ad-44df-8b59-e15fdcfc636b/

Juju web UI

不过我更倾向使用命令行,因此我会在接下来使用。

部署一个 minecraft 服务

让我们先来一个简单的,部署在一个容器中使用一个 Juju 单元的服务。

stgraber@dakara:~$ juju deploy cs:trusty/minecraft
Added charm "cs:trusty/minecraft-3" to the model.
Deploying charm "cs:trusty/minecraft-3" with the charm series "trusty".

命令返回会很快,然而这不意味着服务已经启动并运行了。你应该使用 juju status 来查看:

stgraber@dakara:~$ juju status
[Services] 
NAME STATUS EXPOSED CHARM 
minecraft maintenance false cs:trusty/minecraft-3 

[Units] 
ID WORKLOAD-STATUS JUJU-STATUS VERSION MACHINE PORTS PUBLIC-ADDRESS MESSAGE 
minecraft/1 maintenance executing 2.0-beta7 1 10.178.150.74 (install) Installing java 

[Machines] 
ID STATE DNS INS-ID SERIES AZ 
1 started 10.178.150.74 juju-97fa390d-96ad-44df-8b59-e15fdcfc636b-machine-1 trusty 

我们可以看到它正在忙于在刚刚创建的 LXD 容器中安装 java。

stgraber@dakara:~$ lxc list juju-
+-----------------------------------------------------+---------+----------------------+------+------------+-----------+
|                         NAME                        |  STATE  |          IPV4        | IPV6 |    TYPE    | SNAPSHOTS |
+-----------------------------------------------------+---------+----------------------+------+------------+-----------+
| juju-745d1be3-e93d-41a2-80d4-fbe8714230dd-machine-0 | RUNNING | 10.178.150.72 (eth0) |      | PERSISTENT | 0         |
+-----------------------------------------------------+---------+----------------------+------+------------+-----------+
| juju-97fa390d-96ad-44df-8b59-e15fdcfc636b-machine-1 | RUNNING | 10.178.150.74 (eth0) |      | PERSISTENT | 0         |
+-----------------------------------------------------+---------+----------------------+------+------------+-----------+

过一会之后,如我们所见服务就部署完毕了:

stgraber@dakara:~$ juju status
[Services] 
NAME STATUS EXPOSED CHARM 
minecraft active false cs:trusty/minecraft-3 

[Units] 
ID WORKLOAD-STATUS JUJU-STATUS VERSION MACHINE PORTS PUBLIC-ADDRESS MESSAGE 
minecraft/1 active idle 2.0-beta7 1 25565/tcp 10.178.150.74 Ready 

[Machines] 
ID STATE DNS INS-ID SERIES AZ 
1 started 10.178.150.74 juju-97fa390d-96ad-44df-8b59-e15fdcfc636b-machine-1 trusty

这时你就可以启动你的 Minecraft 客户端了,将其指向 10.178.150.74,端口是 25565。现在可以在新的 minecraft 服务器上玩了!

当你不再需要它,只需运行:

stgraber@dakara:~$ juju destroy-service minecraft

只要等待几秒就好了。

部署一个更复杂的 web 应用

Juju 的主要工作是建模复杂的服务,并以可扩展的方式部署它们。

为了更好地展示,让我们部署一个 Juju “组合”。 这个组合是由网站,API,数据库,静态 Web 服务器和反向代理组成的基本 Web 服务。

所以这将扩展到 4 个互联的 LXD 容器。

stgraber@dakara:~$ juju deploy cs:~charmers/bundle/web-infrastructure-in-a-box
added charm cs:~hp-discover/trusty/node-app-1
service api deployed (charm cs:~hp-discover/trusty/node-app-1 with the series "trusty" defined by the bundle)
annotations set for service api
added charm cs:trusty/mongodb-3
service mongodb deployed (charm cs:trusty/mongodb-3 with the series "trusty" defined by the bundle)
annotations set for service mongodb
added charm cs:~hp-discover/trusty/nginx-4
service nginx deployed (charm cs:~hp-discover/trusty/nginx-4 with the series "trusty" defined by the bundle)
annotations set for service nginx
added charm cs:~hp-discover/trusty/nginx-proxy-3
service nginx-proxy deployed (charm cs:~hp-discover/trusty/nginx-proxy-3 with the series "trusty" defined by the bundle)
annotations set for service nginx-proxy
added charm cs:~hp-discover/trusty/website-3
service website deployed (charm cs:~hp-discover/trusty/website-3 with the series "trusty" defined by the bundle)
annotations set for service website
related mongodb:database and api:mongodb
related website:nginx-engine and nginx:web-engine
related api:website and nginx-proxy:website
related nginx-proxy:website and website:website
added api/0 unit to new machine
added mongodb/0 unit to new machine
added nginx/0 unit to new machine
added nginx-proxy/0 unit to new machine
deployment of bundle "cs:~charmers/bundle/web-infrastructure-in-a-box-10" completed

几秒后,你会看到 LXD 容器在运行了:

stgraber@dakara:~$ lxc list juju-
+-----------------------------------------------------+---------+-----------------------+------+------------+-----------+
|                         NAME                        |  STATE  |           IPV4        | IPV6 |    TYPE    | SNAPSHOTS |
+-----------------------------------------------------+---------+-----------------------+------+------------+-----------+
| juju-745d1be3-e93d-41a2-80d4-fbe8714230dd-machine-0 | RUNNING | 10.178.150.72 (eth0)  |      | PERSISTENT | 0         |
+-----------------------------------------------------+---------+-----------------------+------+------------+-----------+
| juju-97fa390d-96ad-44df-8b59-e15fdcfc636b-machine-2 | RUNNING | 10.178.150.98 (eth0)  |      | PERSISTENT | 0         |
+-----------------------------------------------------+---------+-----------------------+------+------------+-----------+
| juju-97fa390d-96ad-44df-8b59-e15fdcfc636b-machine-3 | RUNNING | 10.178.150.29 (eth0)  |      | PERSISTENT | 0         |
+-----------------------------------------------------+---------+-----------------------+------+------------+-----------+
| juju-97fa390d-96ad-44df-8b59-e15fdcfc636b-machine-4 | RUNNING | 10.178.150.202 (eth0) |      | PERSISTENT | 0         |
+-----------------------------------------------------+---------+-----------------------+------+------------+-----------+
| juju-97fa390d-96ad-44df-8b59-e15fdcfc636b-machine-5 | RUNNING | 10.178.150.214 (eth0) |      | PERSISTENT | 0         |
+-----------------------------------------------------+---------+-----------------------+------+------------+-----------+

几分钟后,所有的服务应该部署完毕并运行了:

stgraber@dakara:~$ juju status
[Services] 
NAME STATUS EXPOSED CHARM 
api unknown false cs:~hp-discover/trusty/node-app-1 
mongodb unknown false cs:trusty/mongodb-3 
nginx unknown false cs:~hp-discover/trusty/nginx-4 
nginx-proxy unknown false cs:~hp-discover/trusty/nginx-proxy-3 
website false cs:~hp-discover/trusty/website-3 

[Relations] 
SERVICE1 SERVICE2 RELATION TYPE 
api mongodb database regular 
api nginx-proxy website regular 
mongodb mongodb replica-set peer 
nginx website nginx-engine subordinate 
nginx-proxy website website regular 

[Units] 
ID WORKLOAD-STATUS JUJU-STATUS VERSION MACHINE PORTS PUBLIC-ADDRESS MESSAGE 
api/0 unknown idle 2.0-beta7 2 8000/tcp 10.178.150.98 
mongodb/0 unknown idle 2.0-beta7 3 27017/tcp,27019/tcp,27021/tcp,28017/tcp 10.178.150.29 
nginx-proxy/0 unknown idle 2.0-beta7 5 80/tcp 10.178.150.214 
nginx/0 unknown idle 2.0-beta7 4 10.178.150.202 
 website/0 unknown idle 2.0-beta7 10.178.150.202 

[Machines] 
ID STATE DNS INS-ID SERIES AZ 
2 started 10.178.150.98 juju-97fa390d-96ad-44df-8b59-e15fdcfc636b-machine-2 trusty 
3 started 10.178.150.29 juju-97fa390d-96ad-44df-8b59-e15fdcfc636b-machine-3 trusty 
4 started 10.178.150.202 juju-97fa390d-96ad-44df-8b59-e15fdcfc636b-machine-4 trusty 
5 started 10.178.150.214 juju-97fa390d-96ad-44df-8b59-e15fdcfc636b-machine-5 trusty

这时你就可以在 80 端口访问 http://10.178.150.214,并且会看到一个 Juju 学院页面。

Juju Academy web service

清理所有东西

如果你不需要 Juju 创建的容器并且不在乎下次需要再次启动,最简单的方法是:

stgraber@dakara:~$ juju destroy-controller test --destroy-all-models
WARNING! This command will destroy the "local.test" controller.
This includes all machines, services, data and other resources.

Continue [y/N]? y
Destroying controller
Waiting for hosted model resources to be reclaimed
Waiting on 1 model, 4 machines, 5 services
Waiting on 1 model, 4 machines, 5 services
Waiting on 1 model, 4 machines, 5 services
Waiting on 1 model, 4 machines, 5 services
Waiting on 1 model, 4 machines, 5 services
Waiting on 1 model, 4 machines, 5 services
Waiting on 1 model, 4 machines
Waiting on 1 model, 4 machines
Waiting on 1 model, 4 machines
Waiting on 1 model, 4 machines
Waiting on 1 model, 4 machines
Waiting on 1 model, 4 machines
Waiting on 1 model, 2 machines
Waiting on 1 model
Waiting on 1 model
All hosted models reclaimed, cleaning up controller machines

我们用下面的方式确认:

stgraber@dakara:~$ lxc list juju-
+------+-------+------+------+------+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+------+-------+------+------+------+-----------+

总结

Juju 2.0 内置的 LXD 支持使得可以用一种非常干净的方式来测试各种服务。

在 Juju charm store 中有很多预制的“组合”可以用来部署,甚至可以用多个“charm”来组合你想要的架构。

Juju 与 LXD 是一个完美的解决方案,从一个小的 Web 服务到大规模的基础设施都可以简单开发,这些都在你自己的机器上,并且不会在你的系统上造成混乱!

额外信息

Juju 网站: http://www.ubuntu.com/cloud/juju

Juju charm store : https://jujucharms.com

LXD 的主站在: https://linuxcontainers.org/lxd

LXD 的 GitHub 仓库: https://github.com/lxc/lxd

LXD 的邮件列表: https://lists.linuxcontainers.org

LXD 的 IRC 频道: #lxcontainers on irc.freenode.net

如果你不想或者不能在你的机器上安装 LXD ,你可以在 web 上试试在线版的 LXD


作者简介:我是 Stéphane Graber。我是 LXC 和 LXD 项目的领导者,目前在加拿大魁北克蒙特利尔的家所在的Canonical 有限公司担任 LXD 的技术主管。


via: https://www.stgraber.org/2016/06/06/lxd-2-0-lxd-and-juju-1012/

作者:Stéphane Graber 译者:geekpi 校对:wxy

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

简介

Linux 内核提供了多种睡眠状态,各个状态通过设置系统中的不同部件进入低耗电模式来节约能源。目前总共有四种睡眠状态,分别是: 挂起到空闲 suspend to idle 加电待机 power-on standby(standby) 挂起到内存 suspend to ram 挂起到磁盘 suspend to disk 。这些状态分别对应 ACPI 的 4 种状态:S0,S1,S3 和 S4。 挂起到空闲 suspend to idle 是纯软件实现的,用于将 CPU 维持在尽可能深的 idle 状态。 加电待机 power-on standby(standby) 则使设备处于低功耗状态,并且关闭所有非引导 CPU。 挂起到内存 suspend to ram 就更进一步,关闭所有 CPU 并且设置 RAM 进入自刷新模式。 挂起到磁盘 suspend to disk 则是最省功耗的模式,关闭尽可能多的系统,包括关闭内存。然后内存中的内容会被写到硬盘,待唤醒计算机的时候将硬盘中的内容重新恢复到内存中。

这篇博文主要介绍 挂起到空闲 suspend to idle 的实现。如上所说,它主要通过软件实现。一般平台的挂起过程包括冻结用户空间并将外围设备调至低耗电模式。但是,系统并不是直接关闭和热插拔掉 CPU,而是静静地强制将 CPU 进入 空闲 idle 状态。随着外围设备进入了低耗电模式,除了唤醒相关的中断外不应有其他中断产生。唤醒中断包括那些设置用于唤醒系统的计时器(比如 RTC,普通计时器等)、或者电源开关、USB 和其它外围设备等。

在冻结过程中,当系统进入空闲状态时会调用一个特殊的 cpu 空闲函数。这个 enter_freeze() 函数可以和调用使 cpu 空闲的 enter() 函数一样简单,也可以复杂得多。该函数复杂的程度由将 SoC 置为低耗电模式的条件和方法决定。

先决条件

platform_suspend_ops

一般情况,为了支持 S2I,系统必须实现 platform_suspend_ops 并提供最低限度的挂起支持。这意味着至少要完成 platform_suspend_ops 中的 valid() 函数。如果 挂起到空闲 suspend to idle 挂起到内存 suspend to ram 都要支持,valid 函数中应使用 suspend_valid_only_mem

不过,最近内核增加了对 S2I 的自动支持。Sudeep Holla 提出了一个变更,可以让系统不需要满足 platform_suspend_ops 条件也能提供 S2I 支持。这个补丁已经被接收并将合并在 4.9 版本中,该补丁可从这里获取: https://lkml.org/lkml/2016/8/19/474

如果定义了 suspend_ops,那么可以通过查看 /sys/power/state 文件得知系统具体支持哪些挂起状态。如下操作:

# cat /sys/power/state
freeze mem

这个示例的结果显示该平台支持 S0( 挂起到空闲 suspend to idle )和 S3( 挂起到内存 suspend to ram )。按 Sudeep 的变更,那些没有实现 platform_suspend_ops 的平台将只显示 freeze 状态。

唤醒中断

一旦系统处于某种睡眠状态,系统必须要接收某个唤醒事件才能恢复系统。这些唤醒事件一般由系统的设备产生。因此一定要确保这些设备驱动使用唤醒中断,并且将自身配置为接收唤醒中断后产生唤醒事件。如果没有正确识别唤醒设备,系统收到中断后会继续保持睡眠状态而不会恢复。

一旦设备正确实现了唤醒接口的调用,就可用来生成唤醒事件。请确保 DT 文件正确配置了唤醒源。下面是一个配置唤醒源示例,该文件来自(arch/arm/boot/dst/am335x-evm.dts):

     gpio_keys: volume_keys@0 {
               compatible = “gpio-keys”;
               #address-cells = <1>;
               #size-cells = <0>;
               autorepeat;

               switch@9 {
                       label = “volume-up”;
                       linux,code = <115>;
                       gpios = <&gpio0 2 GPIO_ACTIVE_LOW>;
                       wakeup-source;
               };

               switch@10 {
                       label = “volume-down”;
                       linux,code = <114>;
                       gpios = <&gpio0 3 GPIO_ACTIVE_LOW>;
                       wakeup-source;
               };
       };

如上所示,有两个 gpio 键被配置为唤醒源,在系统挂起期间,其中任何一个键被按下都会产生一个唤醒事件。

可替代 DT 文件配置的另一个唤醒源配置就是设备驱动,如果设备驱动自身在代码里面配置了唤醒支持,那么就会使用该默认唤醒配置。

实施

冻结功能

如果系统希望能够充分使用 挂起到空闲 suspend to idle ,那么应该在 CPU 空闲驱动代码中定义 enter_freeze() 函数。enter_freeze()enter() 的函数原型略有不同。因此,不能将 enter() 同时指定给 enterenter_freeze。至少,系统会直接调用 enter()。如果没有定义 enter_freeze(),系统会挂起,但是不会触发那些只有当 enter_freeze() 定义了才会触发的函数,比如 tick_freeze()stop_critical_timing() 都不会发生。这会导致计时器中断唤醒系统,但不会导致系统恢复,因为系统处理完中断后会继续挂起。

在挂起过程中,中断越少越好(最好一个也没有)。

下图显示了能耗和时间的对比。图中的两个尖刺分别是挂起和恢复。挂起前后的能耗尖刺是系统退出空闲态进行记录操作,进程调度,计时器处理等。因延迟的缘故,系统进入更深层次空闲状态需要花费一段时间。

blog-picture-one

能耗使用时序图

下图为 ftrace 抓取的 4 核 CPU 在系统挂起和恢复操作之前、之中和之后的活动。可以看到,在挂起期间,没有请求或者中断被处理。

blog-picture-2

Ftrace 抓取的挂起/恢复活动图

空闲状态

你必须确定哪个空闲状态支持冻结。在冻结期间,电源相关代码会决定用哪个空闲状态来实现冻结。这个过程是通过在每个空闲状态中查找谁定义了 enter_freeze() 来决定的。CPU 空闲驱动代码或者 SoC 挂起相关代码必须确定哪种空闲状态实现冻结操作,并通过给每个 CPU 的可应用空闲状态指定冻结功能来进行配置。

例如, Qualcomm 会在平台挂起代码的挂起初始化函数处定义 enter_freeze 函数。这个工作是在 CPU 空闲驱动已经初始化后进行,以便所有结构已经定义就位。

挂起/恢复相关驱动支持

你可能会在第一次成功挂起操作后碰到驱动相关的 bug。很多驱动开发者没有精力完全测试挂起和恢复相关的代码。你甚至可能会发现挂起操作并没有多少工作可做,因为 pm_runtime 已经做了你要做的挂起相关的一切工作。由于用户空间已经被冻结,设备此时已经处于休眠状态并且 pm_runtime 已经被禁止。

测试相关

测试 挂起到空闲 suspend to idle 可以手动进行,也可以使用脚本/进程等实现自动挂起、自动睡眠,或者使用像 Android 中的 wakelock 来让系统挂起。如果手动测试,下面的操作会将系统冻结。

/ # echo freeze > /sys/power/state
[  142.580832] PM: Syncing filesystems … done.
[  142.583977] Freezing user space processes … (elapsed 0.001 seconds) done.
[  142.591164] Double checking all user space processes after OOM killer disable… (elapsed 0.000 seconds)
[  142.600444] Freezing remaining freezable tasks … (elapsed 0.001 seconds) done.
[  142.608073] Suspending console(s) (use no_console_suspend to debug)
[  142.708787] mmc1: Reset 0x1 never completed.
[  142.710608] msm_otg 78d9000.phy: USB in low power mode
[  142.711379] PM: suspend of devices complete after 102.883 msecs
[  142.712162] PM: late suspend of devices complete after 0.773 msecs
[  142.712607] PM: noirq suspend of devices complete after 0.438 msecs
< system suspended >
….
< wake irq triggered >
[  147.700522] PM: noirq resume of devices complete after 0.216 msecs
[  147.701004] PM: early resume of devices complete after 0.353 msecs
[  147.701636] msm_otg 78d9000.phy: USB exited from low power mode
[  147.704492] PM: resume of devices complete after 3.479 msecs
[  147.835599] Restarting tasks … done.
/ #

在上面的例子中,需要注意 MMC 驱动的操作占了 102.883ms 中的 100ms。有些设备驱动在挂起的时候有很多工作要做,比如将数据刷出到硬盘,或者其他耗时的操作等。

如果系统定义了 冻结 freeze ,那么系统将尝试挂起操作,如果没有冻结功能,那么你会看到下面的提示:

/ # echo freeze > /sys/power/state 
sh: write error: Invalid argument
/ #

未来的发展

目前在 ARM 平台上的 挂起到空闲 suspend to idle 有两方面的工作需要做。第一方面工作在前面 platform_suspend_ops 小节中提到过,是总允许接受冻结状态以及合并到 4.9 版本内核中的工作。另一方面工作是冻结功能的支持。

如果你希望设备有更好的响应及表现,那么应该继续完善冻结功能的实现。然而,由于很多 SoC 会使用 ARM 的 CPU 空闲驱动,这使得 ARM 的 CPU 空闲驱动完善它自己的通用冻结功能的工作更有意义了。而事实上,ARM 正在尝试添加此通用支持。如果 SoC 供应商希望实现他们自己的 CPU 空闲驱动或者需要在进入更深层次的冻结休眠状态时提供额外的支持,那么只有实现自己的冻结功能。


via: http://www.linaro.org/blog/suspend-to-idle/

作者:Andy Gross 译者:beyondworld 校对:jasminepeng

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

本文作者:

A. Jesse Jiryu Davis 是纽约 MongoDB 的工程师。他编写了异步 MongoDB Python 驱动程序 Motor,也是 MongoDB C 驱动程序的开发领袖和 PyMongo 团队成员。 他也为 asyncio 和 Tornado 做了贡献,在 http://emptysqua.re 上写作。

Guido van Rossum 是主流编程语言 Python 的创造者,Python 社区称他为 BDFL (仁慈的终生大独裁者 (Benevolent Dictator For Life))——这是一个来自 Monty Python 短剧的称号。他的主页是 http://www.python.org/~guido/

使用协程

我们将从描述爬虫如何工作开始。现在是时候用 asynio 去实现它了。

我们的爬虫从获取第一个网页开始,解析出链接并把它们加到队列中。此后它开始傲游整个网站,并发地获取网页。但是由于客户端和服务端的负载限制,我们希望有一个最大数目的运行的 worker,不能再多。任何时候一个 worker 完成一个网页的获取,它应该立即从队列中取出下一个链接。我们会遇到没有那么多事干的时候,所以一些 worker 必须能够暂停。一旦又有 worker 获取一个有很多链接的网页,队列会突增,暂停的 worker 立马被唤醒干活。最后,当任务完成后我们的程序必须马上退出。

假如你的 worker 是线程,怎样去描述你的爬虫算法?我们可以使用 Python 标准库中的同步队列。每次有新的一项加入,队列增加它的 “tasks” 计数器。线程 worker 完成一个任务后调用 task_done。主线程阻塞在 Queue.join,直到“tasks”计数器与 task_done 调用次数相匹配,然后退出。

协程通过 asyncio 队列,使用和线程一样的模式来实现!首先我们导入它

try:
    from asyncio import JoinableQueue as Queue
except ImportError:
    # In Python 3.5, asyncio.JoinableQueue is
    # merged into Queue.
    from asyncio import Queue

我们把 worker 的共享状态收集在一个 crawler 类中,主要的逻辑写在 crawl 方法中。我们在一个协程中启动 crawl,运行 asyncio 的事件循环直到 crawl 完成:

loop = asyncio.get_event_loop()

crawler = crawling.Crawler('http://xkcd.com',
                           max_redirect=10)

loop.run_until_complete(crawler.crawl())

crawler 用一个根 URL 和最大重定向数 max_redirect 来初始化,它把 (URL, max_redirect) 序对放入队列中。(为什么要这样做,请看下文)

class Crawler:
    def __init__(self, root_url, max_redirect):
        self.max_tasks = 10
        self.max_redirect = max_redirect
        self.q = Queue()
        self.seen_urls = set()

        # aiohttp's ClientSession does connection pooling and
        # HTTP keep-alives for us.
        self.session = aiohttp.ClientSession(loop=loop)

        # Put (URL, max_redirect) in the queue.
        self.q.put((root_url, self.max_redirect))

现在队列中未完成的任务数是 1。回到我们的主程序,启动事件循环和 crawl 方法:

loop.run_until_complete(crawler.crawl())

crawl 协程把 worker 们赶起来干活。它像一个主线程:阻塞在 join 上直到所有任务完成,同时 worker 们在后台运行。

    @asyncio.coroutine
    def crawl(self):
        """Run the crawler until all work is done."""
        workers = [asyncio.Task(self.work())
                   for _ in range(self.max_tasks)]

        # When all work is done, exit.
        yield from self.q.join()
        for w in workers:
            w.cancel()

如果 worker 是线程,可能我们不会一次把它们全部创建出来。为了避免创建线程的昂贵代价,通常一个线程池会按需增长。但是协程很廉价,我们可以直接把他们全部创建出来。

怎么关闭这个 crawler 很有趣。当 join 完成,worker 存活但是被暂停:他们等待更多的 URL,所以主协程要在退出之前清除它们。否则 Python 解释器关闭并调用所有对象的析构函数时,活着的 worker 会哭喊到:

ERROR:asyncio:Task was destroyed but it is pending!

cancel 又是如何工作的呢?生成器还有一个我们还没介绍的特点。你可以从外部抛一个异常给它:

>>> gen = gen_fn()
>>> gen.send(None)  # Start the generator as usual.
1
>>> gen.throw(Exception('error'))
Traceback (most recent call last):
  File "<input>", line 3, in <module>
  File "<input>", line 2, in gen_fn
Exception: error

生成器被 throw 恢复,但是它现在抛出一个异常。如过生成器的调用堆栈中没有捕获异常的代码,这个异常被传递到顶层。所以注销一个协程:

    # Method of Task class.
    def cancel(self):
        self.coro.throw(CancelledError)

任何时候生成器暂停,在某些 yield from 语句它恢复并且抛出一个异常。我们在 task 的 step 方法中处理注销。

    # Method of Task class.
    def step(self, future):
        try:
            next_future = self.coro.send(future.result)
        except CancelledError:
            self.cancelled = True
            return
        except StopIteration:
            return

        next_future.add_done_callback(self.step)

现在 task 知道它被注销了,所以当它被销毁时,它不再抱怨。

一旦 crawl 注销了 worker,它就退出。同时事件循环看见这个协程结束了(我们后面会见到的),也就退出。

loop.run_until_complete(crawler.crawl())

crawl 方法包含了所有主协程需要做的事。而 worker 则完成从队列中获取 URL、获取网页、解析它们得到新的链接。每个 worker 独立地运行 work 协程:

    @asyncio.coroutine
    def work(self):
        while True:
            url, max_redirect = yield from self.q.get()

            # Download page and add new links to self.q.
            yield from self.fetch(url, max_redirect)
            self.q.task_done()

Python 看见这段代码包含 yield from 语句,就把它编译成生成器函数。所以在 crawl 方法中,我们调用了 10 次 self.work,但并没有真正执行,它仅仅创建了 10 个指向这段代码的生成器对象并把它们包装成 Task 对象。task 接收每个生成器所 yield 的 future,通过调用 send 方法,当 future 解决时,用 future 的结果做为 send 的参数,来驱动它。由于生成器有自己的栈帧,它们可以独立运行,带有独立的局部变量和指令指针。

worker 使用队列来协调其小伙伴。它这样等待新的 URL:

    url, max_redirect = yield from self.q.get()

队列的 get 方法自身也是一个协程,它一直暂停到有新的 URL 进入队列,然后恢复并返回该条目。

碰巧,这也是当主协程注销 worker 时,最后 crawl 停止,worker 协程暂停的地方。从协程的角度,yield from 抛出CancelledError 结束了它在循环中的最后旅程。

worker 获取一个网页,解析链接,把新的链接放入队列中,接着调用task_done减小计数器。最终一个worker遇到一个没有新链接的网页,并且队列里也没有任务,这次task_done的调用使计数器减为0,而crawl正阻塞在join方法上,现在它就可以结束了。

我们承诺过要解释为什么队列中要使用序对,像这样:

# URL to fetch, and the number of redirects left.
('http://xkcd.com/353', 10)

新的 URL 的重定向次数是10。获取一个特别的 URL 会重定向一个新的位置。我们减小重定向次数,并把新的 URL 放入队列中。

# URL with a trailing slash. Nine redirects left.
('http://xkcd.com/353/', 9)

我们使用的 aiohttp 默认会跟踪重定向并返回最终结果。但是,我们告诉它不要这样做,爬虫自己来处理重定向,以便它可以合并那些目的相同的重定向路径:如果我们已经在 self.seen_urls 看到一个 URL,说明它已经从其他的地方走过这条路了。

Figure 5.4 - Redirects

crawler 获取“foo”并发现它重定向到了“baz”,所以它会加“baz”到队列和 seen_urls 中。如果它获取的下一个页面“bar” 也重定向到“baz”,fetcher 不会再次将 “baz”加入到队列中。如果该响应是一个页面,而不是一个重定向,fetch 会解析它的链接,并把新链接放到队列中。

    @asyncio.coroutine
    def fetch(self, url, max_redirect):
        # Handle redirects ourselves.
        response = yield from self.session.get(
            url, allow_redirects=False)

        try:
            if is_redirect(response):
                if max_redirect > 0:
                    next_url = response.headers['location']
                    if next_url in self.seen_urls:
                        # We have been down this path before.
                        return

                    # Remember we have seen this URL.
                    self.seen_urls.add(next_url)

                    # Follow the redirect. One less redirect remains.
                    self.q.put_nowait((next_url, max_redirect - 1))
             else:
                 links = yield from self.parse_links(response)
                 # Python set-logic:
                 for link in links.difference(self.seen_urls):
                    self.q.put_nowait((link, self.max_redirect))
                self.seen_urls.update(links)
        finally:
            # Return connection to pool.
            yield from response.release()

如果这是多进程代码,就有可能遇到讨厌的竞争条件。比如,一个 worker 检查一个链接是否在 seen_urls 中,如果没有它就把这个链接加到队列中并把它放到 seen_urls 中。如果它在这两步操作之间被中断,而另一个 worker 解析到相同的链接,发现它并没有出现在 seen_urls 中就把它加入队列中。这(至少)导致同样的链接在队列中出现两次,做了重复的工作和错误的统计。

然而,一个协程只在 yield from 时才会被中断。这是协程比多线程少遇到竞争条件的关键。多线程必须获得锁来明确的进入一个临界区,否则它就是可中断的。而 Python 的协程默认是不会被中断的,只有它明确 yield 时才主动放弃控制权。

我们不再需要在用回调方式时用的 fetcher 类了。这个类只是不高效回调的一个变通方法:在等待 I/O 时,它需要一个存储状态的地方,因为局部变量并不能在函数调用间保留。倒是 fetch 协程可以像普通函数一样用局部变量保存它的状态,所以我们不再需要一个类。

fetch 完成对服务器响应的处理,它返回到它的调用者 workwork 方法对队列调用 task_done,接着从队列中取出一个要获取的 URL。

fetch 把新的链接放入队列中,它增加未完成的任务计数器,并停留在主协程,主协程在等待 q.join,处于暂停状态。而当没有新的链接并且这是队列中最后一个 URL 时,当 work 调用task\_done,任务计数器变为 0,主协程从join` 中退出。

与 worker 和主协程一起工作的队列代码像这样(实际的 asyncio.Queue 实现在 Future 所展示的地方使用 asyncio.Event 。不同之处在于 Event 是可以重置的,而 Future 不能从已解决返回变成待决。)

class Queue:
    def __init__(self):
        self._join_future = Future()
        self._unfinished_tasks = 0
        # ... other initialization ...

    def put_nowait(self, item):
        self._unfinished_tasks += 1
        # ... store the item ...

    def task_done(self):
        self._unfinished_tasks -= 1
        if self._unfinished_tasks == 0:
            self._join_future.set_result(None)

    @asyncio.coroutine
    def join(self):
        if self._unfinished_tasks > 0:
            yield from self._join_future

主协程 crawl yield from join。所以当最后一个 worker 把计数器减为 0,它告诉 crawl 恢复运行并结束。

旅程快要结束了。我们的程序从 crawl 调用开始:

loop.run_until_complete(self.crawler.crawl())

程序如何结束?因为 crawl 是一个生成器函数,调用它返回一个生成器。为了驱动它,asyncio 把它包装成一个 task:

class EventLoop:
    def run_until_complete(self, coro):
        """Run until the coroutine is done."""
        task = Task(coro)
        task.add_done_callback(stop_callback)
        try:
            self.run_forever()
        except StopError:
            pass

class StopError(BaseException):
    """Raised to stop the event loop."""

def stop_callback(future):
    raise StopError

当这个任务完成,它抛出 StopError,事件循环把这个异常当作正常退出的信号。

但是,task 的 add_done_callbockresult 方法又是什么呢?你可能认为 task 就像一个 future,不错,你的直觉是对的。我们必须承认一个向你隐藏的细节,task 是 future。

class Task(Future):
    """A coroutine wrapped in a Future."""

通常,一个 future 被别人调用 set_result 解决。但是 task,当协程结束时,它自己解决自己。记得我们解释过当 Python 生成器返回时,它抛出一个特殊的 StopIteration 异常:

    # Method of class Task.
    def step(self, future):
        try:
            next_future = self.coro.send(future.result)
        except CancelledError:
            self.cancelled = True
            return
        except StopIteration as exc:

            # Task resolves itself with coro's return
            # value.
            self.set_result(exc.value)
            return

        next_future.add_done_callback(self.step)

所以当事件循环调用 task.add_done_callback(stop_callback),它就准备被这个 task 停止。在看一次run_until_complete

    # Method of event loop.
    def run_until_complete(self, coro):
        task = Task(coro)
        task.add_done_callback(stop_callback)
        try:
            self.run_forever()
        except StopError:
            pass

当 task 捕获 StopIteration 并解决自己,这个回调从循环中抛出 StopError。循环结束,调用栈回到run_until_complete。我们的程序结束。

总结

现代的程序越来越多是 I/O 密集型而不是 CPU 密集型。对于这样的程序,Python 的线程在两个方面不合适:全局解释器锁阻止真正的并行计算,并且抢占切换也导致他们更容易出现竞争。异步通常是正确的选择。但是随着基于回调的异步代码增加,它会变得非常混乱。协程是一个更整洁的替代者。它们自然地重构成子过程,有健全的异常处理和栈追溯。

如果我们换个角度看 yield from 语句,一个协程看起来像一个传统的做阻塞 I/O 的线程。甚至我们可以采用经典的多线程模式编程,不需要重新发明。因此,与回调相比,协程更适合有经验的多线程的编码者。

但是当我们睁开眼睛关注 yield from 语句,我们能看到协程放弃控制权、允许其它人运行的标志点。不像多线程,协程展示出我们的代码哪里可以被中断哪里不能。在 Glyph Lefkowitz 富有启发性的文章“Unyielding”:“线程让局部推理变得困难,然而局部推理可能是软件开发中最重要的事”。然而,明确的 yield,让“通过过程本身而不是整个系统理解它的行为(和因此、正确性)”成为可能。

这章写于 Python 和异步的复兴时期。你刚学到的基于生成器的的协程,在 2014 年发布在 Python 3.4 的 asyncio 模块中。2015 年 9 月,Python 3.5 发布,协程成为语言的一部分。这个原生的协程通过“async def”来声明, 使用“await”而不是“yield from”委托一个协程或者等待 Future。

除了这些优点,核心的思想不变。Python 新的原生协程与生成器只是在语法上不同,工作原理非常相似。事实上,在 Python 解释器中它们共用同一个实现方法。Task、Future 和事件循环在 asynico 中扮演着同样的角色。

你已经知道 asyncio 协程是如何工作的了,现在你可以忘记大部分的细节。这些机制隐藏在一个整洁的接口下。但是你对这基本原理的理解能让你在现代异步环境下正确而高效的编写代码。

(题图素材来自:ruth-tay.deviantart.com


via: http://aosabook.org/en/500L/pages/a-web-crawler-with-asyncio-coroutines.html

作者:A. Jesse Jiryu Davis , Guido van Rossum 译者:qingyunha 校对:wxy

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

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu.

不,我的文章没有被 Lorem ipsum 生成器劫持(LCTT 译注:Lorem ipsum,中文又称“乱数假文”,只是一段用来测试排版效果的占位文字,没有实际的含义)。作为本月的 Nooks&Crannies 专栏文章,我发现了一个有趣的小 Python 库,以帮助开发人员为其应用程序生成随机数据。它被称为 Elizabeth

它由 Líkið Geimfari 编写,并在 MIT 许可证下发行,Elizabeth 以 21 个不同本地化信息提供了 18 种数据提供器,可用于生成随机信息(LCTT 译注:不仅是随机数),包括姓名和个人特征、地址、文本数据、交通信息、网络和 Internet 社交媒体数据、数字等等。安装它需要 Python 3.2 或更高版本,您可以使用 pip 或从 git 仓库安装它。

在我的测试机上,我在一个全新安装的 Debian Jessie 上使用 pip 来安装它,要做的就是 apt-get install python3-pip,它将安装 Python 和所需的依赖项。然后 pip install elizabeth,之后就安装好了。

只是为好玩,让我们在 Python 的交互式解释器中为一个人生成一些随机数据:

>>> from elizabeth import Personal
>>> p=Personal('en')
>>> p.full_name(gender="male")
'Elvis Herring'
>>> p.blood_type()
'B+'
>>> p.credit_card_expiration_date()
'09/17'
>>> p.email(gender='male')
'[email protected]'
>>> p.favorite_music_genre()
'Ambient'
>>> p.identifier(mask='13064########')
'1306420450944'
>>> p.sexual_orientation()
'Heterosexual'
>>> p.work_experience()
39
>>> p.occupation()
'Senior System Designer'
>>>

在代码中使用它就像创建一个对象那样,然后调用要你需要填充数据的对应方法。

Elizabeth 内置了 18 种不同的生成工具,添加新的生成器并不困难;你只需要定义从 JSON 值集合中获取数据的例程。以下是一些随机文本字符串生成,再次打开解释器:

>>> from elizabeth import Text
>>> t=Text('en')
>>> t.swear_word()
'Rat-fink'
>>> t.quote()
'Let them eat cake.'
>>> t.words(quantity=20)
['securities', 'keeps', 'accessibility', 'barbara', 'represent', 'hentai', 'flower', 'keys', 'rpm', 'queen', 'kingdom', 'posted', 'wearing', 'attend', 'stack', 'interface', 'quite', 'elementary', 'broadcast', 'holland']
>>> t.sentence()
'She spent her earliest years reading classic literature, and writing poetry.'

使用 Elizabeth 填充 SQLite 或其它你可能需要用于开发或测试的数据库并不困难。其介绍文档给出了使用 Flask 这个轻量级 web 框架的一个医疗应用程序示例。

我对 Elizabeth 印象很深刻 - 它超快、轻量级、易于扩展,它的社区虽然小,但是很活跃。截至本文写作时,项目已有 25 名贡献者,并且提交的问题处理迅速。Elizabeth 的完整文档至少对于美国英语而言易于阅读和遵循,并提供了广泛的 API 参考。

我曾尝试通过修改链接来查找该文档是否有其他语言,但没有成功。因为其 API 在非英语区域中是不同的,所以记录这些变化将对用户非常有帮助。公平地说,通过阅读其代码并找出可用的方法并不难,即使你的 Python 功力并不深厚。对我来说,另一个明显的缺陷是缺乏阿拉伯语或希伯来语区域测试数据。这些是著名的从右到左的语言,对于试图使其应用程序国际化的开发者来说,适当地处理这些语言是一个主要的障碍。像 Elizabeth 这种在此方面可以协助的工具是值得拥有的。

对于那些在应用中需要随机样本数据的开发员而言,Elizabeth 是一个有价值的工具,而对于那些试图创建真正多语言、本地化应用程序的开发者来说,它可能是一个宝藏。


作者简介:

D Ruth Bavousett - D Ruth Bavousett 作为一名系统管理员和软件开发人员已经很长时间了,她的专业生涯开始于 VAX 11/780。在她的职业生涯(迄今为止)中,她在解决图书馆需求上有大量的经验,她自 2008 年以来一直是 Koha 开源图书馆自动化套件的贡献者。Ruth 目前在休斯敦的 cPanel 任 Perl 开发人员,她也作为首席员工效力于双猫公司。


via: https://opensource.com/article/17/2/elizabeth-python-library

作者:D Ruth Bavousett 译者:geekpi 校对:jasminepeng

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

本文主要聚焦在如何使用 Yocto 在 Ubuntu 上创建一个最小化的 Linux 发行版。Yocto 项目在嵌入式 Linux 的世界非常著名,这是因为它用起来非常灵活、方便。Yocto 的目标是为嵌入式软硬件开发商创建自己的 Linux 发行版。本文我们将会创建一个可以运行在 QEMU 上的最小化的 Linux,并且在 QEMU 上实际运行。

开发机的基本条件

  • 最少 4-6 GB 内存
  • 最新版的 Ubuntu 系统(本文使用了 16.04 LTS)
  • 磁盘剩余空间至少 60-80 GB
  • 在创建 Linux 发行版之前先安装下面的软件包
  • 下载最新的 Yocto(Poky 是其最小开发环境)稳定分支
apt-get update
apt-get install wget git-core unzip make gcc g++ build-essential subversion sed autoconf automake texi2html texinfo coreutils diffstat python-pysqlite2 docbook-utils libsdl1.2-dev libxml-parser-perl libgl1-mesa-dev libglu1-mesa-dev xsltproc desktop-file-utils chrpath groff libtool xterm gawk fop

Install prerequisites for Yocto

如下所示,开发环境要安装的软件包将近 1GB 大小。

Install the development packages

在这个教程中,系统上克隆的是 poky 的 morty 稳定分支。

 git clone -b morty git://git.yoctoproject.org/poky.git

install poky

进入 poky 目录,然后运行下面的命令为 Yocto 开发环境设置(设置/导出)一些环境变量。

source oe-init-build-env

如下所示,在运行了 open embedded (oe) 的构建环境脚本之后,终端里的路径会自动切换到 build 目录,以便进行之后行发行版的的配置和构建。

Prepare OE build environment

上面的截屏展示了在 conf 目录下创建的文件 local.conf。这是 Yocto 用来设置目标机器细节和 SDK 的目标架构的配置文件。

如下所示,这里设置的目标机器是 qemux86-64

Set the target machine type

如下面截图所示,在 local.conf 中取消下面参数的注释符号。

DL_DIR ?= "${TOPDIR}/downloads"

Configure local.conf file

SSTATE_DIR ?= "${TOPDIR}/sstate-cache"

Set SSTATE_DIR

TMPDIR ?= "${TOPDIR}/tmp"

Set TMPDIR

PACKAGE_CLASSES ?= "package_rpm"
SDKMACHINE ?= "i686"

Set PACKAGE_CLASSES and SDKMACHINE

如下所示,在 local.conf 中为基于 Yocto 的 Linux 设置空密码和后续的一些参数。否则的话用户就不能登录进新的发行版。

EXTRA_IMAGE_FEATURES ?= "debug-tweaks"

Set debug-tweaks option

我们并不准备使用任何图形化工具来创建 Linux OS,比如 toasterhob 已经不再支持了)。

Yocto 编译构建过程

现在运行下面的 bitbake 工具命令开始为选定的目标机器下载和编译软件包。

bitbake core-image-minimal

Start bitbake

非常重要的是要在普通 Linux 用户下运行上面的命令,而不是使用 root 用户。如下面截图所示,当你在 root 用户下运行 bitbake 命令会产生下面所示的错误。

Do not run bitbake as root

再一次运行导出环境变量的脚本(oe-init-build-env),重新执行相同的命令来启动下载和编译过程。

rerun commands

如下所示,构建脚本组件的第一步工作是解析配置(recipe)。

Parse the build recipes

下面的截图展示了构建脚本的解析过程。同时也显示了用来构建你的新的基于 yocto 的发行版的构建系统的细节。

Building proceeds

在下载了 SDK 和必要的库之后,下一步工作是下载并编译软件包。如下截图展示了为构建新发行版而执行的任务。这一步将会执行 2-3 小时,因为首先要下载需要的软件包,然后还要为新的 Linux 发行版编译这些软件包。

Compilation will take several hours

下面的截图表明了任务列表执行完毕。

Compilation

为目标机器类型 qemux86-64 编译好的新镜像位于 build/tmp/deploy/images/qemux86-64

Build complete

如下所示,上面的命令如果运行在 Putty 上会产生一个错误。

command error in putty

通过 rdp 在 Ubuntu 平台上再次运行上面的命令。

Command works fine in rdp

为运行新的基于 Yocto 的 Linux 发行版的 qemu 打开一个新屏幕。

Open Quemu emulator

下面展示了新发行版的登录界面,同时也显示了使用的 yocto 项目的版本号。默认的用户名是 root ,密码为空。

Linux distribution started

最后使用 root 用户名和空密码登录新发行版。如下截图所示,在这个最小版本的 Linux 上运行了基本的命令(dataifconfiguname)。

Test the Linux distribution

本文的目标是理解使用 Yocto 创建新的 Linux 发行版的过程。


via: https://www.howtoforge.com/tutorial/how-to-create-your-own-linux-distribution-with-yocto-on-ubuntu/

作者:Ahmad 译者:Ezio 校对:wxy

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