蚊章汇 - Stephan Avenwedde
type: channel
link: https://linuxcn.bwsl.wang/author/1435/
description: Stephan Avenwedde
items: 六大 Python 解释器
items: 在 Linux 上用 Doxygen 生成源代码文档
items: 在 GitLab 上构建 CI 流水线
items: 使用 GoogleTest 和 CTest 进行单元测试
items: OpenWrt：一个开源的家用路由器固件替代品
items: 我是如何毁掉我的树莓派的
items: 如何在 Rust 中读取和写入文件
items: 在 Linux 中创建定时器
items: 为 Python 写一个 C++ 扩展模块
items: 使用 VirtualBox 安装 Linux 虚拟机

六大 Python 解释器
type: item
date: 2023-10-18T07:23:26+08:00
description: 观察你的代码在其他解释器下运行的表现或许是一项有趣的尝试。作为最受欢迎的编程语言之一，Python 需要一个解释器来执行其代码所定义的命令。与其他可直接编译成机器代码的语言不同，Python 代码需要解释器读取它并把它转译给进行相关操作的 CPU。那么，哪些解释器有哪些呢？本文将对其中几种进行介绍。解释器简介提到 Python 解释器，我们通常会想到 /usr/bin/python 这个二进制文件。它使你能够执行 .py 文件。然而，解释操作仅仅是其中一环。在 Python 代码真正被 CPU 执行之前，都需要经过以下四个步骤：词法分析 - 将人类编写的源代码转换为一序列逻辑实体，被称为 词法标记 lexical token 。解析 - 解析器会检查词法标记的语法和语义规则生成 抽象语法树 abstract syntax tree （AST）。编译 - 编译器会根据 AST 创建 Python 字节码，这些字节码由非常基础的，和平台无关的指令组成。解释 - 解释器处理字节码并执行特定的操作。如你所见，在任何实质性的操作发生之前，我们需要走过这些步骤。这也解释了深入研究不同解释器的重要性。1、CPython作为 Python 的参考实现，CPython 默认地被许多系统所采用。如其名称所示，CPython 是用 C 语言编写的。这也意味着，我们可以 以 C 语言编写扩展，从而让 Python 打通到广泛使用的 C 语言库代码。CPython 广泛应用于各种平台，包括 ARM 和 RISC。然而，作为 Python 的参考实现，CPython 更注重精细的优化，而非运行速度。2、PystonPyston 是一个从 CPython 解释器衍生出的分支，其中实现了性能优化。该项目定位自己为标准 CPython 解释器在处理大型、真实世界应用时的替代品，并有可能加速高达 30%。由于缺乏兼容的二进制包，Pyston 在下载过程中需要重新编译。3、PyPy采用了 RPython 编写的 PyPy 是一个专为 Python 配备的 即时（JIT） 编译器，RPython 是 Python 的一个静态类型的子集。不同于 CPython 解释器，PyPy 对源代码进行编译，生成 CPU 可直接执行的机器码。PyPy 是 Python 开发者的实验室，在这里他们能更容易地测试新特性。相较于 CPython，PyPy 的执行速度更快。由于 JIT 编译器的特性，长时间运行的应用更能从缓存中受益。PyPy 可以被视为 CPython 的有效替代。虽然其中存在一些缺点，大部分的 C 扩展模块在 PyPy 中也得到支持，但运行速度会相对慢一些。PyPy 扩展模块使用 Python（而不是 C）编写，这使 JIT 编译器能够对其进行优化。只要你的应用程序不依赖于不兼容的模块，PyPy 就是替换 CPython 的理想选择。你可以在项目官网找到一个专门的页面，详细描述 PyPy 与 CPython 的不同之处：PyPy 与 CPython 的差异4、RustPython顾名思义，RustPython 是一个由 Rust 编写的 Python 解释器。尽管 Rust 如今还是一个相对年轻的编程语言，但因其优良特性已逐步受到开发者的推崇，甚至被视为 C 和 C++ 的可能接班人。默认情况下，RustPython 的行为与 CPython 的解释器类似，但它也可以选择启用 JIT 编译器。值得一提的是，Rust 工具链能直接编译为 WebAssembly ，进而允许在浏览器中全面运行解释器。你可以在 这里 看到它的在线演示。5、Stackless PythonStackless Python 自称是 Python 编程语言的增强版本。该项目基本上是 CPython 解释器衍生的一个项目，其为该语言添加了微线程、通道和调度器。微线程可以帮助你将代码组织成可以并行运行的 “ 小任务 tasklet ”。这与采用 greenlet 模块的绿色线程模型相似。通道可以用作 “小任务” 之间的双向通信。Stackless Python 的一个知名用户是大型多人在线角色扮演游戏 Eve Online。6、Micro Python如果你的目标平台是微控制器，那么 MicroPython 将是你的首选。它是一种极简的实现，只需要 16kB 的内存和 256kB 的存储空间。由于其主要面向的是嵌入式环境，MicroPython 的标准库只包含 CPython 丰富的 STL 的一部分。对于开发和测试，或者作为轻量级替代品，MicroPython 也可以在普通的 x86 和 x64 系统上运行。MicroPython 支持 Linux、Windows，以及多种微控制器。性能就其设计而言…

在 Linux 上用 Doxygen 生成源代码文档
type: item
date: 2023-09-26T18:33:09+08:00
description: Doxygen 是一款广泛使用的开源文档生成工具，它通过代码注释来生成文档。在试着熟悉别人的代码时，你总希望他们留下的代码注释能对你理解代码有所帮助。同理，无论为了自己还是其他人，编写代码时写注释是好习惯。所有编程语言都有专门的注释语法，注释可以是一个单词、一行文字、甚至是一整段话。编译器或解释器处理源代码时会忽略注释。注释不能完全取代文档，但是有方法可以使用注释来生成文档。Doxygen 是一个开源的文档生成工具，它能够根据代码注释生成 HTML 或 LaTeX 格式的文档。Doxygen 让你在不用额外操作的情况下创建代码结构概览。尽管 Doxygen 主要是用来给 C++ 生成文档的，它对其它语言同样适用，比如 C、Objective-C、 C#、 PHP、Java 和 Python 等。要使用 Doxygen，你只需要在源代码中使用 Doxygen 能够识别的语法来写注释。Doxygen 会扫描源码文件，然后根据这些特殊注释生成 HTML 或 LaTeX 文档。下面的示例项目会演示如何使用 Doxygen 注释，以及文档是如通过注释生成出来的。示例代码可从 GitHub 上获得，本文中也将引用 Doxygen 手册及文档 的相关章节。在 Linux 上安装 Doxygen在 Fedora 上可以通过软件包的形式安装 Doxygen。打开终端运行命令：sudo dnf install doxygen 在基于 Debian 的操作系统上，可以通过以下命令来安装：sudo apt-get install doxygen 使用安装完 Doxygen 后，你需要在项目中按 Doxygen 可以识别的格式来注释代码，还要提供一个 Doxyfile 配置文件来控制 Doxygen 的一些行为。注意：如果你用的是 GitHub 上的示例项目，你可以忽略下面一步。如果 Doxyfile 文件不存在，你可以用 Doxygen 生成一个标准 Doxyfile 模板文件。切换到项目根目录下，运行：doxygen -g 参数 -g 表示 生成 generate 。现在应该会出现一个名为 Doxyfile 的新文件。通过命令调用 Doxygen：doxygen 现在应该能会有两个新文件夹：html/latex/默认情况下，Doxygen 会同时输出 LaTeX 和 HTML 格式的文档。本文主要关注 HTML 文档。你可以在 Doxygen 官方文档的入门小节中找到关于 LaTeX 格式输出的更多信息。双击 html/index.html 打开 HTML 文件。用空的配置文件生成的文档如下图：现在我们试着修改 Doxyfile 文件，并在源代码中添加特殊注释。Doxyfile 文件在 Doxyfile 文件中可以定义大量的可调选项，本文通过介绍示例项目的 Doxyfile 文件我只能覆盖其中很小的子集。第 35 行：项目名称你可以在这里指定项目名称，它最终会显示在 页眉 header 和浏览器标签上。# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by # double-quotes, unless you are using Doxywizard) that should identify the # project for which the documentation is generated. This name is used in the # title of most generated pages and in a few other places. # The default value is: My Project. PROJECT_NAME = "My Project" 第 47 行：项目简介项目简介会以略小的字号显示在页眉上。# Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = "An example of using Doxygen in C++" 第 926 行：包含子目录允许 Doxygen 查找源代码和文档文件时递归遍历子目录。# The RECURSIVE tag can be …

在 GitLab 上构建 CI 流水线
type: item
date: 2023-08-18T17:45:11+08:00
description: 连续集成 continuous integration （CI）是指代码变更会被自动构建和测试。以下是我为自己的 C++ 项目构建 CI 流水线的过程。本文介绍如何在 GitLab 上配置 CI 流水线。我在前面的文章中介绍了 基于 CMake 和 VSCodium 的构建系统 和 基于 GoogleTest 和 CTest 的单元测试。本文将在此基础上进一步配置 CI 流水线。我会先演示如何布设和运行 CI 流水线，然后再介绍如何配置它。CI 是指提交到代码仓库的代码变更会被自动构建和测试。在开源领域，GitLab 是一个流行的 CI 流水线平台。除了作为中心 Git 仓库外，GitLab 还提供 CI/CD 流水线、 问题跟踪 issue tracking 和 容器注册表 container registry 功能。相关术语在进入正题之前，我先介绍在本文和 GitLab 文档 中会遇到的常见术语。 持续交付 continuous delivery （CD）：自动化供应软件，以供随时交付 持续部署 continuous deployment （CD）：自动化软件发布 流水线 pipeline : CI/CD 的直接构件，它由阶段和作业构成 阶段 stage ：一组作业 作业 job ：某项需要执行的具体任务，比如编译、单元测试等 执行器 runner ：实际执行作业的服务布设 CI 流水线在下面的章节中，我将复用以前的 示例工程。点击 GitLab 仓库页面右上角的 复刻 Fork 按钮复刻代码仓库。设置执行器为了让你对整个流程有所了解，我们先从在本地安装执行器讲起。参照执行器服务 安装指南 安装好服务，然后注册执行器。1、选择 GitLab 项目页面左侧的 设置 Settings ，再选择 CI/CD。2、展开 执行器 Runners 区域，关闭 共享的执行器 Shared runners 选项（黄框处）。特别注意令牌和 URL（绿框处），下一步会用到它们。3、在终端中运行 gitlab-runner register，根据提示输入以下注册信息：GitLab 实例: https://gitlab.com/ （如上图）注册令牌：从执行器区域中获取 （如上图）描述：按需自由填写标签：可以不填执行环境：选 Shell如果有需要，你可以在 ~/.gitlab-runner/config.toml 中修改这些配置。4、用命令 gitlab-runner run 启动执行器。你可以在 GitLab 的项目设置界面执行器区域看到执行器的状态：运行流水线前面已经提过，流水线就是一组由执行器执行的作业。每个推送到 GitLab 的提交都会生成一个附加到该提交的流水线。如果多个提交被一起推送，那么只会为最后一个提交生成流水线。为了演示，我直接在 GitLab 在线编辑器中提交和推送修改。打开 README.md 文件，添加一行数据：现在提交修改。这里注意默认的行为是为提交新建一个分支，为了简便起见，我们择提交到主分支。提交后一会儿后，你就应该改能看到 GitLab 执行器执行的控制台中有输出消息：Checking for jobs... received job=1975932998 repo_url=<https://gitlab.com/hANSIc99/cpp\_testing\_sample.git> runner=Z7MyQsA6 Job succeeded duration_s=3.866619798 job=1975932998 project=32818130 runner=Z7MyQsA6 在 GitLab 项目概览界面左侧选择 CI/CD --> 管道 Pipelines ，查看最近执行的流水线：选中流水线可以在详情界面看到哪些作业失败了，并能查看各个作业的输出。当遇到非零返回值是就认为作业执行失败了。在下面的例子中我通过调用 exit 1 强制让作业执行失败：CI 配置阶段、流水线和作业的配置都在仓库根目录的 .gitlab-ci.yml 文件中。我建议使用 GitLab 内置的流水线编辑器，它会自动对配置进行检查。stages: - build - test build: stage: build script: - cmake -B build -S . - cmake --build build --target Producer artifacts: paths: - build/Producer RunGTest: stage: test script: - cmake -B build -S . - cmake --build build --target GeneratorTest - build/Gene…

使用 GoogleTest 和 CTest 进行单元测试
type: item
date: 2023-08-02T19:15:50+08:00
description: 进行单元测试可以提高代码质量，并且它不会打断你的工作流。本文是 使用 CMake 和 VSCodium 设置一个构建系统 的后续文章。在上一篇文章中我介绍了基于 VSCodium 和 CMake 配置构建系统。本文我将介绍如何通过 GoogleTest 和 CTest 将单元测试集成到这个构建系统中。首先克隆 这个仓库，用 VSCodium 打开，切换到 devops_2 标签。你可以通过点击 main 分支符号（红框处），然后选择 devops_2 标签（黄框处）来进行切换：或者你可以通过命令行来切换：$ git checkout tags/devops_2 GoogleTestGoogleTest 是一个平台无关的开源 C++ 测试框架。单元测试是用来验证单个逻辑单元的行为的。尽管 GoogleTest 并不是专门用于单元测试的，我将用它对 Generator 库进行单元测试。在 GoogleTest 中，测试用例是通过断言宏来定义的。断言可能产生以下结果：成功: 测试通过。非致命失败: 测试失败，但测试继续。致命失败: 测试失败，且测试终止。致命断言和非致命断言通过不同的宏来区分：ASSERT_*： 致命断言，失败时终止。EXPECT_*： 非致命断言，失败时不终止。谷歌推荐使用 EXPECT_* 宏，因为当测试中包含多个的断言时，它允许继续执行。断言有两个参数：第一个参数是测试分组的名称，第二个参数是测试自己的名称。Generator 只定义了 generate(...) 函数，所以本文中所有的测试都属于同一个测试组：GeneratorTest。针对 generate(...) 函数的测试可以从 GeneratorTest.cpp 中找到。引用一致性检查generate(...) 函数有一个 std::stringstream 的引用作为输入参数，并且它也将这个引用作为返回值。第一个测试就是检查输入的引用和返回的引用是否一致。TEST(GeneratorTest, ReferenceCheck){ const int NumberOfElements = 10; std::stringstream buffer; EXPECT_EQ( std::addressof(buffer), std::addressof(Generator::generate(buffer, NumberOfElements)) ); } 在这个测试中我使用 std::addressof 来获取对象的地址，并用 EXPECT_EQ 来比较输入对象和返回对象是否是同一个。检查元素个数本测试检查作为输入的 std::stringstream 引用中的元素个数与输入参数中指定的个数是否相同。TEST(GeneratorTest, NumberOfElements){ const int NumberOfElements = 50; int nCalcNoElements = 0; std::stringstream buffer; Generator::generate(buffer, NumberOfElements); std::string s_no; while(std::getline(buffer, s_no, ' ')) { nCalcNoElements++; } EXPECT_EQ(nCalcNoElements, NumberOfElements); } 乱序重排本测试检查随机化引擎是否工作正常。如果连续调用两次 generate 函数，应该得到的是两个不同的结果。TEST(GeneratorTest, Shuffle){ const int NumberOfElements = 50; std::stringstream buffer_A; std::stringstream buffer_B; Generator::generate(buffer_A, NumberOfElements); Generator::generate(buffer_B, NumberOfElements); EXPECT_NE(buffer_A.str(), buffer_B.str()); } 求和校验与前面的测试相比，这是一个大体量的测试。它检查 1 到 n 的数值序列的和与乱序重排后的序列的和是否相等。 generate(...) 函数应该生成一个 1 到 n 的乱序的序列，这个序列的和应当是不变的。TEST(GeneratorTest, CheckSum){ const int NumberOfElements = 50; int nChecksum_in = 0; int nChecksum_out = 0; std::vector<in…

OpenWrt：一个开源的家用路由器固件替代品
type: item
date: 2023-03-29T18:54:00+08:00
description: OpenWrt 是一个基于 Linux 的开源操作系统，主要针对嵌入式网络设备。如果你在家里阅读这篇文章，你可能是用一个 LTE/5G/DSL/WIFI 路由器联网的。这种设备通常负责在你的本地设备（智能手机、PC、电视等）之间路由数据包，并通过内置的调制解调器提供对 WWW 的访问。你家里的路由器很可能有一个基于网页的界面，用于配置该设备。这种界面往往过于简单，因为它们是为普通用户制作的。如果你想要更多的配置选项，但又不想花钱买一个专业的设备，你应该看看其他的固件，如 OpenWrt。OpenWrt 的特点OpenWrt 是一个基于 Linux 的、针对嵌入式网络设备的开源操作系统。它主要用于替代各种家用路由器上的原始固件。OpenWrt 具备一个好的路由器应该具备的所有有用功能，如 DNS 服务器（dnsmasq），WiFi 接入点（AP）和客户端功能，用于调制解调器功能的 PPP 协议，而且，与标准固件不同，这一切都是可以完全配置的。LuCI 网页界面OpenWrt 可以通过命令行（SSH）或使用 GUI 配置界面（LuCI）进行远程配置。LuCI 是一个用 Lua 编写的轻量级、可扩展的网页 GUI，它可以精确地配置你的设备。除了配置，LuCI 还提供了很多额外的信息，如实时图表、系统日志和网络诊断。LuCI 有一些可选的扩展，以增加更多的配置选择。可写文件系统它的另一个亮点是可写文件系统。原有的固件通常是只读的，而 OpenWrt 配备了一个可写的文件系统，这要归功于一个巧妙的解决方案，它将 OverlayFS 与 SquashFS/JFFS2 文件系统相结合，允许安装软件包以增强功能。在 OpenWrt 文档 中可以找到更多关于文件系统架构的信息。扩展OpenWrt 有一个相关的软件包管理器，opkg，它允许安装额外的服务，比如 FTP 服务器、DLNA 媒体服务器、OpenVPN 服务器、用于实现文件共享的 Samba 服务器、控制电话的 Asterisk 等等。当然，有些扩展需要适当的底层硬件资源。动机你可能想知道为什么要冒着对你的设备造成不可修复的损害和失去保修的风险，而尝试更换路由器制造商的固件。如果你的设备以你想要的方式工作，那么你可能不应该。永远不要碰一个正在运行的系统！但是，如果你想增强功能，或者你的设备缺乏配置选项，那么你应该看看 OpenWrt 是否可以成为一种补救措施。在我的例子中，我想要一个旅行用的路由器，当我在露营地的时候，我可以把它放在一个合适的位置，以便让其它设备与这个本地 WiFi 接入点（AP）保持良好连接。该路由器将作为一个普通的客户端连接到互联网，并广播它的 WiFi 接入点让我的其它设备连接到它。这样我就可以配置我的所有设备与这个路由器的接入点连接，当我在其他地方时我只需要改变路由器的客户端连接。此外，在一些露营地，你只能得到一个单一设备的访问代码，我可以通过这种设置来加强。作为我的旅行路由器，我选择 TP-Link TL-WR902AC 的原因如下：很小两根 WiFi 天线5V 电源（USB）低功耗成本效益高（你以 30 美元左右的价格得到它）为了了解它的尺寸，这里是它在树莓派 4 旁边的样子：尽管这个路由器带来了我所需要的所有硬件功能，但我很快发现，默认的固件并不能让我按照我想要的方式配置它。该路由器主要是作为一个 WiFi 接入点，它可以复制现有的 WiFi 网络或通过板载以太网接口将自己连接到网络。默认的固件对于这些使用情况是非常有限的。（LCTT 译注：此型号国内没有销售，它的特点之一是可以通过插入 3G/4G USB 网卡连接到互联网，但由于它不在国内销售，所以没有支持哪种国内 3G/4G USB 网卡的说明，我 查下来 似乎华为的 E3372h-320 是可用的。有相关实践的同学可以分享一下经验。国内销售的其它类似型号只能通过以太网口或 WiFi 连接到互联网，这种情况下，如果只能通过 3G/4G 连接互联网，那需要另外买一个随身 WiFi /移动路由器。）幸运的是，该路由器能够运行 OpenWrt，所以我决定用它来替换原来的固件。安装当你的 LTE/5G/DSL/WiFi 路由器符合 最低要求 时，很有可能在它上面运行 OpenWrt。下一步，你要查看 硬件表，检查你的设备是否被列为兼容，以及你要选择哪个固件包。OpenWrt 的 TP-Link TL-WR902AC 的页面还包括安装说明，其中描述了如何刷入内部存储器。刷入固件的过程在不同的设备之间可能会有所不同，所以我就不详细介绍了。简而言之，我必须通过将设备连接到一个具有特定 IP 地址的网络接口上的 TFTP 服务器，重命名 OpenWrt 固件文件，然后按复位按钮启动设备。配置一旦刷入成功，你的设备现在…

我是如何毁掉我的树莓派的
type: item
date: 2023-03-21T23:47:17+08:00
description: 多测量几次总比测量一次好。我掉到坑里，希望你可以不用。我想写一篇文章来演示“如何使用树莓派实现莫某的自动化”，或围绕树莓派的其他一些有趣、好奇或有用的应用。正如你可能从标题中意识到的那样，我不能再提供这样的文章了，因为我毁了我心爱的树莓派。树莓派是每个技术爱好者办公桌上的标准设备。因此，大量教程和文章告诉你可以用它做什么。这篇文章反而涵盖了阴暗面：我描述了你最好不要做的事情！电缆颜色在谈到实际的破坏点之前，我想提供一些背景。在房屋内外进行电气工作时，你必须处理不同颜色的电缆。在德国，每栋房子都连接到三相交流电网，你通常会发现以下电缆颜色：零线：蓝色（PE）地线：黄绿色（L1）火线 1：棕色（L2）火线 2：黑色（L3）火线 3：灰色例如，给灯接线时，你接零线（N，蓝色）和火线（L，有 1/3 的机会是棕色），它们之间的电压为 230V 交流电。连接树莓派今年早些时候，我写了一篇关于 OpenWrt，家用路由器固件的开源替代品 的文章。在文章中，我使用了 TP-link 路由器设备。但是，最初的计划是使用我的树莓派 4。我的想法是构建一个旅行路由器，我可以将其安装在我的大篷车中以改善露营地的互联网连接（我是那种离不开互联网的露营者）。为此，我在我的树莓派中添加了一个单独的 USB 无线网卡以连接第二个 Wifi 天线并安装了 OpenWrt。此外，我添加了一个 12V 至 5V DC/DC 转换器来连接大篷车中的 12V 接线。我用桌上的 12V 汽车电池测试了这个设置，它按预期工作。一切设置和配置完成后，我开始将其安装到我的大篷车中。在我的大篷车里，我找到了一根蓝色和一根棕色的电线，将它与 12V 至 5V DC/DC 转换器相连，将保险丝放回，然后……这个芯片，自己爆开了，它才是真正的降压变压器。我非常自信地认为蓝线是在 0V 电位上，棕色的是在 12V 上，我甚至没有测量。后来我了解到，蓝色的线是在 12V 上，而棕色的线是接地（这在汽车电子产品中很常见）。总结自从这次事故后，我的树莓派就再也启动不起来了。由于树莓派的价格飞涨，我不得不寻找替代品。幸运的是，我遇到了 TP-Link 旅行路由器，它也可以运行 Open-WRT 并且可以令人满意地完成它的工作。最后：多测量几次总比测量一次好。via: https://opensource.com/article/23/3/how-i-destroyed-my-raspberry-pi作者：Stephan Avenwedde 选题：lkxed 译者：geekpi 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出

如何在 Rust 中读取和写入文件
type: item
date: 2023-01-15T00:41:00+08:00
description: 跟随这个演示，学习如何在 Rust 中使用文件系统模块。知道如何读写文件对各种用途都很有用。在 Rust 中，这项任务是通过标准库中的文件系统模块（std::fs）完成的。在这篇文章中，我将向你介绍如何使用这个模块。为了演示这项任务，我准备了一些示例代码，也可以在 GitHub 上找到。准备工作在使用 Rust 时，失败的函数会返回 Result 类型。尤其是文件系统模块会返回专门的类型 std::io::Result<T, Error>。有了这些知识，你可以从 main() 函数中返回相同的类型：fn main() -> std::io::Result<()> { /* ...code comes here... */ Rust 文件写入在 Rust 中执行文件的 I/O 操作是相对容易的。写入文件可以简化为一行：use std::fs; fs::write("favorite_websites.txt", b"opensource.com")?; Ok(()) 使用错误传播操作符 (?)，错误信息被传递到调用函数中，随后可以处理错误。由于 main() 是调用栈中唯一的其他函数，如果写操作失败，错误信息将被传递到控制台输出。fs::write 函数的语法是非常先进的。第一个参数是文件路径，它必须是 std::path::Path 类型。第二个参数是内容，它实际上是一个字节切片（[u8]）。Rust 将传递的参数转换为正确的类型。幸运的是，这些类型基本上是下面的例子中所处理的唯一类型。使用文件描述符类型 std::fs::File 可以实现对写操作更简洁的访问：let mut file = fs::File::create("favorite_websites.txt")?; file.write_all(b"opensource.com\n")?; Ok(()) 由于文件类型实现了 Write 特性，所以可以使用相关的方法来写入文件。然而，create 方法可以覆盖一个已经存在的文件。为了获得对文件描述符的更多控制，必须使用 std::fs::OpenOptions 类型。这提供了类似于其他语言中的打开模式：let mut file = fs::OpenOptions::new() .append(true) .open("favorite_websites.txt")?; file.write_all(b"sourceforge.net\n")?; Rust 文件读取适用于写的东西也适用于读。读取也可以通过简单的一行代码来完成：let websites = fs::read_to_string("favorite_websites.txt")?; 以上一行读取文件的内容并返回一个字符串。除了读取字符串，还有 std::fs::read 函数，如果文件包含二进制数据，该函数会将数据读成一个字节向量。下一个例子显示了如何将文件的内容读入内存，随后逐行打印到控制台：let file = fs::File::open("favorite_websites.txt")?; let lines = io::BufReader::new(file).lines(); for line in lines { if let Ok(_line) = line { println!(">>> {}", _line); } } 总结如果你已经熟悉了其他编程语言，你可能已经注意到没有 close- 函数（或类似的）来释放文件句柄。在 Rust 中，当相关变量超出作用域，文件句柄就会被释放。为了定义关闭行为，可以在文件表示的周围应用作用域 ({ })。我建议你熟悉 Read 和 Write 特性，因为你可以在许多其他类型中找到这个特性的实现。via: https://opensource.com/article/23/1/read-write-files-rust作者：Stephan Avenwedde 选题：lkxed 译者：geekpi 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出

在 Linux 中创建定时器
type: item
date: 2023-01-09T23:02:00+08:00
description: 这是一个演示如何创建 POSIX 兼容的间隔定时器的教程。对开发人员来说，定时某些事件是一项常见任务。定时器的常见场景是看门狗、任务的循环执行，或在特定时间安排事件。在这篇文章中，我将演示如何使用 timer\_create(...) 创建一个 POSIX 兼容的间隔定时器。你可以从 GitHub 下载下面样例的源代码。准备 Qt Creator我使用 Qt Creator 作为该样例的 IDE。为了在 Qt Creator 运行和调试样例代码，请克隆 GitHub 上的仓库，打开 Qt Creator，在 “ 文件 File -> 打开文件或项目…… Open File or Project... ” 并选择 “CMakeLists.txt”：在 Qt Creator 中打开项目选择工具链之后，点击 “ 配置项目 Configure Project ”。这个项目包括三个独立的样例（我们在这篇文章中将只会用到其中的两个）。使用绿色标记出来的菜单，可以在每个样例的配置之间切换，并为每个样例激活在终端运行 “ 在终端中运行 Run in terminal ”（用黄色标记）。当前用于构建和调试的活动示例可以通过左下角的“ 调试 Debug ” 按钮进行选择（参见下面的橙色标记）。项目配置线程定时器让我们看看 simple_threading_timer.c 样例。这是最简单的一个。它展示了一个调用了超时函数 expired 的间隔定时器是如何被创建的。在每次过期时，都会创建一个新的线程，在其中调用函数 expired：#include <stdio.h> #include <stdlib.h> #include <time.h> #include <signal.h> #include <unistd.h> #include <string.h> #include <errno.h> void expired(union sigval timer_data); pid_t gettid(void); struct t_eventData{ int myData; }; int main() { int res = 0; timer_t timerId = 0; struct t_eventData eventData = { .myData = 0 }; /* sigevent 指定了过期时要执行的操作 */ struct sigevent sev = { 0 }; /* 指定启动延时时间和间隔时间 * it_value和it_interval 不能为零 */ struct itimerspec its = { .it_value.tv_sec = 1, .it_value.tv_nsec = 0, .it_interval.tv_sec = 1, .it_interval.tv_nsec = 0 }; printf("Simple Threading Timer - thread-id: %d\n", gettid()); sev.sigev_notify = SIGEV_THREAD; sev.sigev_notify_function = &expired; sev.sigev_value.sival_ptr = &eventData; /* 创建定时器 */ res = timer_create(CLOCK_REALTIME, &sev, &timerId); if (res != 0){ fprintf(stderr, "Error timer_create: %s\n", strerror(errno)); exit(-1); } /* 启动定时器 */ res = timer_settime(timerId, 0, &its, NULL); if (res != 0){ fprintf(stderr, "Error timer_settime: %s\n", strerror(errno)); exit(-1); } printf("Press ETNER Key to Exit\n"); while(getchar()!='\n'){} return 0; } void expired(union sigval timer_data){ struct t_eventData *data = timer_data.sival_ptr; printf("Timer fired %d - thread-id: %d\n", ++data->myData, gettid()); } 这种方法的优点是在代码和简单调试方面用量小。缺点是由于到期时创建新线程而增加额外的开销，因此行为不太确定。中断信号定时器超时定时器通知的另一种可…

为 Python 写一个 C++ 扩展模块
type: item
date: 2023-01-03T01:35:18+08:00
description: 使用 C 扩展为 Python 提供特定功能。在前一篇文章中，我介绍了 六个 Python 解释器。在大多数系统上，CPython 是默认的解释器，而且根据民意调查显示，它还是最流行的解释器。Cpython 的独有功能是使用扩展 API 用 C 语言编写 Python 模块。用 C 语言编写 Python 模块允许你将计算密集型代码转移到 C，同时保留 Python 的易用性。在本文中，我将向你展示如何编写一个 C++ 扩展模块。使用 C++ 而不是 C，因为大多数编译器通常都能理解这两种语言。我必须提前说明缺点：以这种方式构建的 Python 模块不能移植到其他解释器中。它们只与 CPython 解释器配合工作。因此，如果你正在寻找一种可移植性更好的与 C 语言模块交互的方式，考虑下使用 ctypes 模块。源代码和往常一样，你可以在 GitHub 上找到相关的源代码。仓库中的 C++ 文件有以下用途：my_py_module.cpp: Python 模块 MyModule 的定义my_cpp_class.h: 一个头文件 - 只有一个暴露给 Python 的 C++ 类my_class_py_type.h/cpp: Python 形式的 C++ 类pydbg.cpp: 用于调试的单独应用程序本文构建的 Python 模块不会有任何实际用途，但它是一个很好的示例。构建模块在查看源代码之前，你可以检查它是否能在你的系统上编译。我使用 CMake 来创建构建的配置信息，因此你的系统上必须安装 CMake。为了配置和构建这个模块，可以让 Python 去执行这个过程：$ python3 setup.py build 或者手动执行：$ cmake -B build $ cmake --build build 之后，在 /build 子目录下你会有一个名为 MyModule. so 的文件。定义扩展模块首先，看一下 my_py_module.cpp 文件，尤其是 PyInit_MyModule 函数：PyMODINIT_FUNC PyInit_MyModule(void) { PyObject* module = PyModule_Create(&my_module); PyObject *myclass = PyType_FromSpec(&spec_myclass); if (myclass == NULL){ return NULL; } Py_INCREF(myclass); if(PyModule_AddObject(module, "MyClass", myclass) < 0){ Py_DECREF(myclass); Py_DECREF(module); return NULL; } return module; } 这是本例中最重要的代码，因为它是 CPython 的入口点。一般来说，当一个 Python C 扩展被编译并作为共享对象二进制文件提供时，CPython 会在同名二进制文件中（<ModuleName>.so）搜索 PyInit_<ModuleName> 函数，并在试图导入时执行它。无论是声明还是实例，所有 Python 类型都是 PyObject 的一个指针。在此函数的第一部分中，module 通过 PyModule_Create(...) 创建的。正如你在 module 详述（my_py_module，同名文件）中看到的，它没有任何特殊的功能。之后，调用 PyType\_FromSpec 为自定义类型 MyClass 创建一个 Python 堆类型 定义。一个堆类型对应于一个 Python 类，然后将它赋值给 MyModule 模块。注意，如果其中一个函数返回失败，则必须减少以前创建的复制对象的引用计数，以便解释器删除它们。指定 Python 类型MyClass 详述在 my\_class\_py\_type.h 中可以找到，它作为 PyType\_Spec 的一个实例：static PyType_Spec spec_myclass = { "MyClass", // name sizeof(MyClassObject) + sizeof(MyClass), // basicsize 0, // itemsize Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, // flags MyClass_slots // slots }; 它定义了一些基本类型信息，它的大小包括 Python 表示的大小（MyClassObject）和普通 C++ 类的大小（MyClass）。MyClassObject 定义如下：typedef struct { PyObject_HEAD in…

使用 VirtualBox 安装 Linux 虚拟机
type: item
date: 2022-10-27T18:43:00+08:00
description: VirtualBox 能帮助任何人（即使是命令行新手）安装一个新的虚拟机。VirtualBox 能让任何人都可以轻松安装 Linux 虚拟机。你不需要有使用命令行的经验，就可以自己安装一个简单的 Linux 虚拟机。在虚拟机方面，我精通很多东西，但这篇文章将向新手展示如何安装一个 Linux 虚拟机。此外，这篇文章还概述了如何使用开源虚拟机管理程序 VirtualBox ，来运行以及安装一个测试目的的 Linux 系统。一些术语在开始之前，你需要了解在本安装教程中的两个操作系统（OS）之间的区别： 主机系统 host system ：这指的是你安装 VirtualBox 的操作系统（即本机的操作系统）。 客体系统 guest system ：这指的是你想要在主机系统之上运行的虚拟化系统。在输入/输出、网络、文件访问、剪贴板、音频和视频方面，主机系统和客体系统都必须能够交互。在本教程中，我将使用 Windows 10 作为 主机系统，Fedora 33 作为 客体系统。安装前的准备当我们谈论虚拟化时，实际上，我们指的是 硬件辅助虚拟化。硬件辅助虚拟化需要兼容的 CPU。过去十年来，几乎每个普通的 x86 CPU 都有这一功能。AMD 公司称这样的 x86 CPU 是具有 AMD 虚拟化技术（AMD-V） 的处理器，英特尔公司则称其是具有 Intel 虚拟化技术（VT-x） 的处理器。虚拟化功能增加了一些额外的 CPU 指令，你可以在 BIOS 中启用或禁用这些指令。在安装虚拟机之前：确保在 BIOS 中启用了虚拟化技术（AMD-V 或 VT-x）。下载并安装好 VirtualBox。准备虚拟机下载你要用的 Linux 发行版的镜像文件。下载 32 位还是 64 位的操作系统镜像都没有关系，因为在 32 位的主机系统上也可以启动 64 位的操作系统镜像（当然内存的使用会受限），反之亦然。注意事项： 如果可以的话，请下载附带有 逻辑卷管理器)（LVM）的 Linux 发行版。LVM 会将文件系统与物理硬盘驱动器解耦。如果你的空间不足时，这能够让你增加客体系统的硬盘驱动器的大小。现在，打开 VirtualBox，然后单击黄色的“ 新建 New ”按钮：接下来，配置客体操作系统允许使用多少内存：我的建议是：不要吝啬分配给客体操作系统使用的内存！当客体操作系统的内存不足时，客体系统将开始从随机存取存储器（RAM）向硬盘驱动器进行内存分页，这样会极大地恶化系统的性能和响应能力。如果底层的主机系统开始分页，你很可能不会注意到。对于具有图形化桌面环境的 Linux 工作站系统，我建议至少分配 4GB 内存。接下来，创建虚拟磁盘：虚拟磁盘的格式选择默认的选项 “VDI（VirtualBox 磁盘镜像）” 就可以了：在以下的窗口中，我建议选择“ 动态分配 dynamically allocated ”，因为这允许你在之后增加虚拟磁盘的大小。如果你选择了“ 固定大小 fixed size ”，磁盘的速度可能会更快，但你将无法修改虚拟磁盘的大小了：建议你使用附带有逻辑卷管理器（LVM）的 Linux 发行版，这样你就可以先创建一个较小的硬盘。如果之后你的客体系统的空间快用完了，你可以按需增加磁盘的大小。注意：我选择的客体系统为 Fedora，在 Fedora 的官网说明：Fedora 至少需要分配 20GB 的空闲磁盘空间。我强烈建议你遵守该规范。在这里，我选择了 8GB，以便稍后演示如何用命令行增加磁盘空间。如果你是 Linux 新手，或者对命令行没有经验，请依旧选择 20GB。创建好硬盘驱动器后，从 VirtualBox 主窗口的列表中选择新创建的虚拟机，然后单击“ 设置 Settings ”。在设置菜单中，点击“ 系统 System ”，然后选择“ 处理器 Processor ”标签。默认情况下，VirtualBox 只向客体系统分配一个 CPU 内核。在现代多核 CPU 计算机上，分配至少两个内核是没有任何问题的，这能显著地加快客体系统的速度：设置网络适配器接下来，要处理的是网络设置。默认情况下， VirtualBox 会创建一个 NAT 连接，这对于大多数情况来说，是没有问题、不用做其他更改的：你也可以创建多个网络适配器。以下是网络适配器最常见的类型：NAT：NAT 适配器能自动执行 网络地址转换。从外部看，主机和客体系统使用着相同的 IP 地址。你无法通过网络从主机系统内访问客体系统。（尽管，你也可以通过定义 端口转发，来访问某些服务。）当你的主机系统可以访问互联网时，则你的客体系统也可以访问互联网。NAT 不再需要进一步的配置。如果你只需要让客体系统接入互联网就可以的话，请选择 “NAT”。 桥接适配器 Bridged adapter ：…