普通视图

发现新文章,点击刷新页面。
昨天以前Bingwhispers

Fedora 48小时初体验

2024年9月8日 22:55

我是一个 Windows 和 Ubuntu 用户,此前一直使用 KDE 桌面环境,最近心血来潮打算体验一下其他 Linux 发行版。我的第一个选择是 Fedora。之所以选择它,是因为 Fedora 的软件包管理系统与 Ubuntu 不一样,默认的桌面环境是 Gnome,与我熟悉的 KDE 也不同,总体的使用体验会有非常大的差异。

本文发布时,Fedora 的最新版本为 Fedora 40,发布于 2024 年 4 月 23 日(Ubuntu 24.04 发布于 2024 年 4 月 25 日)。在安装了 Fedora 40 后,我重度使用了一个周末。在周末即将结束时,将一个周末的使用体验以博文的形式分享出来。

在下载安装镜像文件时,我大概浏览了一下 Fedora 的网站,了解了一些概要信息:

Fedora 有五个分支版本,对应五种应用场景:Fedora Workstation 针对 PC 的桌面版,Fedora Server 服务器版,Fedora Iot 针对嵌入式设备的物联网版,Fedora Cloud 和 Fedora CoreOS 是针对云计算场景的版本。

Fedora 通常每半年发布一个新版本,每个版本的维护时间为 13 个月。所以我可以在今年下半年更新 Fedora 41,或者在明年上半年 Fedora 40 停止维护前升级到 Fedora 42。

系统的安装比较顺利,但是体验并不完美。通过 LiveCD 引导的安装过程非常容易,只需选择安装的磁盘即可。重启后,进入 Inital Setup。选择“Enable Third-Party Repositorise”,点击“Next”,程序卡住。多次弹出程序无响应的对话框,点击“Wait”仍然无效。此时我打算重新启动,但电源菜单中只有“Suspend”选项,无奈我只能通过机箱的电源按钮关机。重启后,Inital Setup 程序顺利执行完毕,进入桌面。后来我在虚拟机中再次安装,遇到了相同的情况,所以这并非偶然。

Fedora 40 默认使用 Gnome 46 桌面环境。它给人的第一印象是“简洁、漂亮”。屏幕上方有一条非常窄的状态栏,左中右分别布置了工作空间、时间和按钮栏。默认壁纸是一副油画风格的抽象画,画中是夕阳照进树林的景色。画面两侧的树木高大,枝叶茂密。画面中间模糊的蓝色让人一时分不清是路面还是小河,配合阳光照进树林形成的丁达尔现象,给人一种宁静深邃的感觉。

Fedora 默认的 UI 主题以白色为主,窗口不同区域通过不同的灰度区域隔开。界面中的特殊元素大多使用蓝色表现,例如:进度条、引导性按钮、开启项和选中项等。

点击键盘的 Super 按键,可以看到位于屏幕底部的 Dash 栏。Dash 栏与 MacOS、Windows 11 的设计类似,居中、合并相同程序的不同窗口。Dash 栏默认不显示,只有在按下 Super 按钮后,才会显示。我都害怕有些心急的新手跳过首次启动指引,会发现不知道如何打开应用程序。

按下 Super 按键后,通过键盘输入也可以进行全局搜索,也可以在中间的窗口区域切换窗口或工作空间,也可以通过 Dash 栏打开其他应用程序。

我浏览了一遍 Setting 应用程序,设置项都非常简洁。它不像 KDE 那样在系统设置中集成非常丰富的设置内容,系统进一步定制通过 Extensions 和 Tweaks 两个应用程序来完成。网络设置非常好用。系统可以自动识别我所有的四个以太网卡和一个 Wi-Fi。我可以非常容易地给这些网卡设置静态 IP 或动态 IP,也非常容易地连接家里的 Wi-Fi。代理和 VPN 的支持也非常丰富。常见的 VPN 协议和代理模式都能很好地支持。遗憾的是,当我创建一个 WireGuard 连接时,遇到一个必现的崩溃问题。创建 WireGuard 连接时,进入 WireGuard 标签页,通过 fwmark 选项右侧的加减号修改 fwmark 选项,当次选项是 1 时,再次点击减号,Setting 程序便崩溃退出了。

Fedora 在显示设备方面支持地非常好。我有两个显示器,分别是 HDMI 接口的 1080P 显示器和 DP 接口的 4K 显示器。两个显示器可以正常分别使用 100% 和 200% 两个不同缩放比,而且切换缩放比不再需要重新启动桌面环境。

鼠标和触摸板的设置中规中居没有太多特殊点。值得一提的是,中文输入法的设置出乎意料得顺利。我只需在键盘选项中添加一个 Input Source,即可输入中文。这一点是非常大的进步,因为我知道有很多人都是在多次尝试安装中文输入法失败后,放弃在真机中安装 Linux 操作系统。虽然安装的比较顺利,但是输入法的使用体验却非常普通。首先是词语联想功能非常弱,当我打出“feichangjianjie”时,第一候选词竟然是“非常简介”。其次,我设置了每个窗口具有独立的输入源,当我以中文输入状态切换到其他窗口,在由其他窗口切换回来时,输入状态会变成英文。这实际上就变为每当我切换到一个窗口,此时的输入状态都是英文状态,完全无法保留之前的状态。

在 Setting 程序中,依次打开 System 和 About 选项,Disk Capacity 栏目中显示的是 Unknow,说明系统没有正确识别出计算机中的存储设备。不过这无伤大雅,我只有一个 SSD,并且所有挂载都正确,这里只是显示异常。

Fedora 预装的应用程序质量非常高,Camera、Contacts、Weather、Clock、Maps,这些程序简单好用,界面简单美观,让我有一种在使用手机系统的错觉。还有一些其他的程序也给了我很多惊喜:Boxes 是一个轻量级的虚拟机软件,它可以满足日常的虚拟机需求。最厉害的是,你可以在 Boxes 中直接下载各种常见的操作系统镜像,非常方便。Disk Usage Analyzer 应用程序有一个很炫的饼状图,可以非常快速直观地展现出各级目录的磁盘占用状况,所有人都值得一试。

这一次对 Fedora 的尝试非常成功,我见到了非常好的软件,见到了多屏多缩放顺利解决,尝试了不同于 Windows 和 KDE 风格的桌面环境。当然目前我所体验到的,仅仅是 Fedora 的冰山一角,进一步了解 Fedora 还需要更久的时间。接下来我会继续使用 Fedora,看看它在长期工作中的持续表现。

群晖 NAS 使用心得(下)

2022年1月27日 16:46

  由于 DS216j 的性能比较弱,很多服务虽然可以用,但是体验并不好。在使用 DS216j 两三年后,我逐渐有了换机器的想法。一方面是想改善现有服务的使用体验,另一方面是觊觎新功能。

  先说说性能问题把:用 Moments 套件打开相册,翻到较早的照片时,照片缩略图需要很久才能打开。看不到缩略图就很难快速找到想要的照片;使用 Video Station 串流影片时,解码能力有限,1080P 以上清晰度的影片最大只能以 720P 来解码;多个服务并发运行时,反应速度有明显降低;更换硬盘需要拆外壳,很不方便。

  新功能稍后再说,总之在 DS216j 向我报告第一块 3TB 的硬盘发生故障,需要更换后,我决定买一台新 NAS。

  就在几天前,我收到了这台新 NAS:DS920+。我本来想先用一周的时间体验和研究一下新机器,等它的各项配置和服务都稳定后,再把 DS216j 上的服务和资料逐渐迁移至 DS920+ 上。然而出乎我意料的是,一切配置都很顺利,包括很多新功能。所以 DS920+ 很快就“转正”了。

  相比于 DS216j,DS920+ 上可用的套件增加了许多。我最感兴趣的是 Docker 和虚拟化。

  我有一些服务平时运行在 PC 的虚拟机中,比如 beancount-fava 和此博客的 Jekyll 工程。但是每次都需要打开虚拟机,切换目录,运行程序,这样还是比较麻烦的。现在我把这些服务都放在 NAS 的 Docker 中运行,就省去了这些繁琐的操作。群晖的 Docker 套件基于标准的 Docker,完整支持 dockerhub。既有适合新手上路的图形界面,也有适合高端玩家的命令行程序。可以说,有了 Docker,只有你想不到的,没有群晖做不到的。

  对于个人用户来说,DSM 上的虚拟机功能比较完整。它的虚拟机可支持 Windows、Linux、DSM 和其他自定义的操作系统。创建好虚拟机,设置基础的硬件信息后,就可以开机了。显示终端被重定向到了一个 Web 页面,这一点和其他 VPS 厂商一样。虚拟机管理器中有简洁明了的存储管理和网络管理,快照、备份等常用功能也都具备。在 DSM 中再安装一个 DSM 的虚拟机听起来确实很有趣,不过除了做一些实验配置以外,我还没有想到有什么实际用途。群晖随机赠送一个 DSM 虚拟证书,每台设备可免费安装一个 DSM 虚拟机。额外的虚拟机需要购买许可。

  默认情况下,DSM 的虚拟机是基础版。专业版是在基础版上增加了集群管理的功能,不过专业版需要额外付费。

  总之,对于个人用户而言,基础版的 DSM 的虚拟化基本满足日常使用。

  群晖还有其他的一些优点。例如,技术支持比较快。我提过两次工单,基本都在第二个工作日解决了。文档比较详细,如果有哪些不懂得地方,大部分问题都可以在官方网站上找到资料。这些优点常常让人心里格外舒坦,并感慨其物超所值。

  同时,我也注意到群晖 NAS 上也存在一些缺点。关于相册服务,从最开始的 Photo Station,到后来的 Moments,再到现在的 Synology Photos,这个服务所对应的套件基本都是推倒重来。虽然每次更新,官方都宣称存储向前兼容。但我实际上每次都被强行改变了使用习惯。另外,DSM 上的套件基本上都是满足功能需求。在易用性和便捷性上,和专业的软件还有一些差距。

  限于篇幅,对于群晖 NAS 的使用体验介绍不能面面俱到。随着我继续使用,它也会给我的数码生活带来更多活力和新奇的体验。

——2022年01月27日

群晖 NAS 使用心得(上)

2022年1月26日 13:56

  2017 年,我购买了第一台 NAS:群晖 DS216j。当时最主要的需求就是数据存储。文档资料、影音视频、家庭照片,这些资料随着时间的推移,越积越多。单纯使用 PC 硬盘管理资料已经无法满足需求了。我曾经考虑过在线网盘,但是大部分网盘产品都需要用户在存储容量、传输带宽和订阅费用三者间取舍。况且,虽然技术上,网盘能够很好地保证数据的安全性。但是仍然有很多非技术性因素,让我对网盘上的数据安全性充满担心——NAS 几乎是必然的选择。

  DS216j 是群晖的入门级产品,价格低,最大支持两块硬盘,性价比非常高。了解群晖 NAS 的读者可能会对我的说法嗤之以鼻:DS216j 如此孱弱的性能如何称得上性价比高?这里的性价比,比的可不是性能啊。群晖“买软件,送硬件”的名号由来已久,我倒认为,用 1200 多人民币的价格,就可以用上群晖提供的绝大部分软件服务,那可谓是非常赚啦。

  我为什么不攒一台 NAS 呢?作为一个软件工程师,我确实可以攒一台机器,网上也有很多自攒 NAS 的教程。但是我在这方面并不想花费太多时间和精力折腾。我需要一台运行稳定、功能成熟的 NAS。我可不想三天两头折腾宕机的 NAS,所以在这方面用金钱换时间和精力是很合算的,况且数据也是无价的。

  群晖的上手非常容易。按照说明书安装硬盘、连接电源线和网线就可以直接开机了。系统安装和软件设置也是傻瓜化操作,跟着向导一路点击下一步即可。所有关键的设置项都有详细的说明,基本不会出现配置错误的情况,非常贴心。设置好硬盘、共享文件夹和用户账户,就可以从局域网中访问 NAS 中的数据了。从拆箱到基本设置完成,大概仅用了 15 分钟左右。

  群晖 NAS 中运行的是 Synology DiskStation Manager (DSM) 操作系统。DSM 是群晖基于 Linux 专为其 NAS 产品开发的操作系统。DSM 中,套件(Package)的概念类似于应用程序。但是和应用程序又有一些区别。一个套件是若干应用程序、数据和配置的集合,它为用户提供了一整套完整的功能。DSM 把各种功能以套件的形式提供给用户。从套件中心下载和安装套件完全都是全自动的,即插即用,非常方便。

  如果说群晖 NAS 的基础功能让我非常满意的话,那么它的套件中心则让我惊喜不已。Photo Station 可以让用户浏览存储在 NAS 上的照片、备份设备上的照片至 NAS;Audio Station 和 Video Station 分别允许用户在线播放存储在 NAS 上的音乐和影片;Download Station 允许用户使用 NAS 从网络上下载文件,实现离线下载功能;Cloud Sync 用来同步其他网络存储服务上的数据,例如 Google Drive、Microsoft OneDrive 等;Synology Drive 可以同步 NAS 和用户设备上的数据。而这些只是我常用的几个套件,群晖的套现中心中还有很多套件,实现各种各样的功能。配合各种优秀的套件,这台 NAS 摇身一变,成为了一台家庭多媒体数据中心。除了官方套件,群晖还支持第三方开发的套件,甚至还有专为群晖 NAS 开发第三方套件的社区。这大大增加了 DSM 的可玩性。

  家庭环境中的 NAS 往往部署在局域网内,且没有固定的公网 IP。所以使用一般手段从公网访问 NAS 非常不方便。群晖为用户提供了 QuickConnect 服务。NAS 和用户通过 QuickConnect 实现内网穿透。上面提到的各种套件,都可以通过 QuickConnect 实现在公网上访问。但是由于所有的数据都依赖 QuickConnect 服务的中转。所以访问速度比较慢。

  最开始,我主要是在家里通过局域网连接 NAS。后来随着越来越多的数据都保存在 NAS 上,再加上使用习惯的养成,我越来越依赖 NAS 上的服务。通过公网访问 NAS 的情况增加了,QuickConnect 已经不能满足需求了。

  于是我向网络运营商申请了公网 IP。在这里解释一下,由于 IPv4 地址资源有限,默认情况下家用固定宽带服务不向用户提供公网 IP 地址,而是运营商的内网 IP 地址。如果设备位于运营商的内网中,则无法通过公网访问。主动申请后,运营商则会给用户提供公网 IP 地址。目前申请公网 IP 是免费的。由于需要频繁的访问 NAS 中的数据,NAS 的上行流量就变得非常重要了。我将原来的 200M 的宽带套餐升级为 500M,上行流量则由原来的 20M 提高到了 100M。对于个人使用来说,已经足够了。

  要想顺畅地从公网访问 NAS,只有公网 IP 还不够,因为它不是固定 IP 地址。每次 PPPoE 拨号,都会从运营商获取不通的 IP 地址。群晖提供了 DDNS 服务。DDNS 服务可以把域名解析到动态变化的 IP 地址上。这样就算每次拨号获取的地址不通,也可以通过固定的域名访问 NAS。通过这些设置,之前的“泉眼无声惜细流”变成了“不尽长江滚滚来”。

  DS216j 有两个盘位。最初我只购买了一块 3TB 的硬盘。随着数据增多,两年后我又买了一块 4TB 的硬盘。我并没有组 Raid,一方面是因为两块硬盘的 Raid 方案都不适合我。另一方面,我对可用性的要求并不是很高,系统出问题后,直接从备份中恢复数据即可。

  我将我的所有数据分为三类。第一类是重要且访问频度高的数据,例如照片和日常文档。这些数据占用空间不大(通常在 200GB 以下),但是丢失后无法恢复。对于这类数据,我每周进行一次备份,保留最近的三次备份,在硬盘和网盘中各保存一份。第二类是访问频度不高,丢失后不容易找回的数据,例如游戏、电子书和音乐。这类数据占用空间略大,通常在 500GB 至 1TB 之间。它和第一类数据采取同样的备份方法,只不过频率调整为半年至一年一次。第三类数据是访问频度低,丢失后较容易找回的数据。这类数据具有收藏属性,占用空间大,往往没有上限。我把这类数据直接存储在普通硬盘上,一旦硬盘存满,就从设备上取下来,贴上标签,断电保存。然后给设备换一块新硬盘。

  幸运的是,直到现在我还没有使用过这些备份,DS216j 一直在稳定运行中。不过我清楚地认识到,出问题只是时间问题,我依然要保持备份数据的习惯。

今非昔比的书店

2020年5月20日 22:45

  今天吃完午饭,我和子晨去了附近的一家书店。这家书店有一个奇怪的名字——言几又。

  我很少去书店,几乎所有的书都是从网上买。之所以会去言几又,是因为小珂单位发了一张预付卡。而书店又恰好离公司非常近,我就期望着多少能买几本书。

  刚迈进书店大门,一股书店特有的书香扑鼻而来。环顾四周,整个书店宽敞明亮,富丽堂皇。天花板上悬吊着形态各异的灯具。众多明亮的日光灯中,间或参杂着几盏外型极具特色但亮度稍弱的装饰灯。这种微弱的黄晕散发自书店的各个角落,给人以温馨舒适的感觉。高大宽阔的书架在灯光的照耀下,反射出雍容华贵的实木光泽。走廊尽头,一面大镜子完全覆盖了整个墙壁。走过去,不仔细看都不容易发现镜子的存在,只感觉置身于无尽的书海中。大厅正中间有一个巨大的旋转楼梯,通向二楼。人们沿着楼梯上上下下,又为书店增添了一些市井的热闹气息。

  在书店的两个角落,各有一个咖啡厅和理发厅。咖啡厅里坐了不少人,有的捧着咖啡和好友低声细语,有的靠在沙发上看书。

  相比起来,理发厅就冷清多了。大概是来书店逛的人们大多都还没做好理发的准备吧。

  “这不知道比其他的书店高到哪里去了!”我这样想。

  逛了一小会,我就发现了一个大问题:整个书店没有明显的导览设施。各类书籍都杂乱地混合摆放在一起。我想买些计算机专业类的书籍,结果绕了一圈,连一本都没找到。问过店员才知道,书店里根本没有专业书籍。

  我这才审视起这家书店的藏书。

  书架都很高,直顶天花板。书架低处摆着新面世的书。这些书并非像图书馆中书脊靠书脊地摆着,而是直接立起来,封面对着读者。这样看起来视觉效果确实很棒,但仔细想想,这种方法的空间里用率非常低,一层并不能摆多少本书。而且,还有不少书架上摆着一些古玩字画等文创产品。很多书架的最上面几层,干脆就摆着一些假书,以充场面,就像很多健身房里的书架一样。

  整个书店的藏书以“励志鸡汤”、经济生活和历史文化等畅销书为主。很难找到名家经典书籍,甚至是四大名著和国外名著小说都没有。在普通书店这些书,往往都占据了相当多的书架空间。

  最后,我和子晨发现喝咖啡才是消费这张预付卡最合算的方法。坐在角落的卡座里,我俩一人点了一杯冰拿铁,吹着空调,悠然地享受起这优美的环境。环顾四周,我想起上次去小寨的汉唐书城。汉唐书城这两年也有了大变样,和言几又很像,都加入了更多休闲和流行元素。在电商和电子书迅猛发展的冲击下,传统图书零售业日渐萧条。为了生存,很多书店不断探索和转型。书店老板也要赚钱养家,这本无可厚非。但是书店曾是文化和知识的代表,如今却将要慢慢褪去她的深韵,加入消费领域的滚滚大军中,不禁令人唏嘘。

  小时候跟着爸爸去图书批发市场进货。大人们谈生意,我就在旁边找喜欢的书翻阅。最后谈妥了生意,临走时老板大多会让我挑几本喜欢的书送给我。我还能记得从书店里走出来,手里捧着书时,自己脸上洋溢的笑。然而这样的书店恐怕再也不会有了。

——2020年05月20日

  周末,我和小珂再来的时候,书店以大扫除的名义暂停营业。两周后的今天,我和小珂第二次来,书店还是大门紧锁。门外有一位店员模样的人告诉我们,这家店已经半个月没有营业了,估计是要关门了。

——2020年06月06日

Put my cellphone away

2020年5月18日 11:31

Recently I found myself very hard to fall asleep. Yesterday I happened to see this article. The author explains why using cellphones before going to bed is harmful to sleep in this article. What he said seems to make sense. So I left my cellphone out of my bedroom before going to bed last night.

At first, I was not used to lying in bed without doing anything, and unconsciously tried to find something to focus on. It took me quite a long time to relax completely.

This morning, I awoke much earlier than usual. I tried to get my cellphone habitually before opening my eyes. However, of cause, I couldn’t. I forgot that I had left it in the living room. I would pick the cellphone up and play around with it if I had the cellphone near me at that time. The alarm had not gone off, which means I still had some time. I closed my eyes, tried to fall asleep again. However, I became wide awake soon.

I felt energetic more than ever. I quickly get washed and changed, made me breakfast of eggs and milk. When I finished all this, there was still plenty of time before going to work. I did some cleanning, read some pages of a book and listened to an English audio program.

I extended the time of the morning by putting my cellphone away. Spending too much time on cellphones isn’t only a waste of time, but also impact the sleep quality.

2020年的新年碎碎念

2020年1月2日 19:50

  昨天晚上,我和小珂出去吃饭。等餐的时候,和小珂闲在聊中谈到一年的变化。事后细想,感慨颇深。

  2019年对我来说很是丰富多彩。我做了很多有意义的事情,也有了很多真心想去做的事情。

  这一年夏天,我和小珂领了结婚证。冬天,我们举办了婚礼。一切都顺顺利利,妥妥当当。到现在,我仍然没有明显地感觉到婚前婚后的变化。这确实是我期望的。这一年,我和小珂相互关心,相互谦让。这样的生活让我很放松。结婚后,我们买了一套一百平的房子。虽然因为首付借了不少钱,而且今年的房价还有下降趋势。但是总体来说,这件事还是让我对未来充满了期待,十分开心。

  九月份,我和小珂去欧洲旅行了。古人说:“读万卷书,行万里路。”在遥远的异国他乡,我们走过罗马斗兽场旁的幽幽小巷;仰观富丽堂皇的圣母百花大教堂;沉浸于柔美的莱茵河与天边灿烂的晚霞。我深切地领会到“过秦、汉之故都,恣观终南、嵩、华之高,北顾黄河之奔流,慨然想见古之豪杰”的那种情怀。旅行确实激发了我内心的浩然之气。

  这一年,我在公司有一半多的时间投入在节点代码重构上。以前的代码充满了不同逻辑模块间的耦合关系,稍作改动都会引起一些潜在的问题。因此工程师十分抗拒修改代码。年初,我在“户外防潮型表面温度”项目中完成了 MSP430 平台上的重构工作,把这些耦合关系都基本消除。年中,我开始了带显示屏的室内环境监测终端产品线的开发。在这个项目中我基本解决了 STM32 + FreeRTOS 的开发问题。比较有意义的是 Bootloader 和低功耗问题的解决。后半年,我在部门中逐渐引入 Git Flow、持续集成、自动化单元测试等技术。年底,依托这些基础设施的提升,我参照敏捷开发和 CMMI,制定了部门内的研发流程。这一流程在部门范围内弥补了研发部流程执行不顺利的问题。

  我家的盆栽植物已经有大大小小21盆了。除过一盆金钱木,其他的盆栽都长势喜人。这盆金钱木也是命途多舛。我本来一直都控制着浇水,它也一直都健健康康的,还分出了三四个旁枝。谁知一次降温突然来临,叶子被冻黄了几片。刚开始我以为是冬天干燥,缺水造成的,所以多浇了几次。结果把它浇烂根了。好在我及时把根挖出来,凉了两天,再重新植回盆里,才把它挽救回来。不过现在的叶子都快掉秃了。希望过了新年它会有新气象。

  2019年也有一些遗憾。

  10月份,我去三星面试嵌入式软件工程师一职。技术面试毫无悬念地通过了,但是人力资源面试没过。回顾整个过程,我觉得最有可能出问题的地方是薪水要求太高了。今年有机会的话还想再试一次。为什么我如此想去三星呢?说实在的,步入三十岁,我慢慢开始担心中年危机的到来。这使我在换工作时更倾向于稳定的环境。

  原本计划要在2019年多看些书,结果达成度并不令我满意。我看了《思考,快与慢》和《Harry Potter and the Philosopher’s Stone》。一年两本的阅读量明显偏少。并不是我没有足够的时间阅读,而是在一些无意义的休闲上浪费了太多时间。今年我给自己定了看10本书的目标。这是一个量化的目标,并且切实可行。通过读书,一方面我可以拓宽知识面,另一方面可以锻炼自控能力。

  2020年也会像2019年一样快快地过去。也许2020年不会像2019年一样精彩,但是2020年一定会比2019年有更多收获。

——2020年1月2日 晚

使用 IAR Embedded Workbench IDE 做单元测试

2019年12月20日 20:00

一、欢迎

  很高兴你能看到这篇文章,我将在这篇文章中介绍如何使用 IAR 做单元测试。

  大部分嵌入式开发者一般都按照“编码——编译——运行——调试”的循环完成开发工作,这很可能只会用到 IAR Embedded Workbench IDE(以下简称“IAR”)极少一部分(同时也是最常用的)功能。实际上,IAR 为开发者提供了很多丰富的功能,使我们能做更多事情。

  IAR 集成了“IAR C-SPY Debugger”,IAR C-SPY Debugger 里面又提供了“C-SPY Simulator”功能。它通过软件完整地模拟了目标处理器,使目标处理器的代码可以脱离硬件环境运行。这样就可以随时做单元测试而不必等待硬件环境就绪。对单元测试而言,这是一个极大的便利。顺便提一下,我使用的 IAR 版本是 IAR Embedded Workbench for ARM 8.32.3.20228。

二、创建工程

  创建一个名为“test”的 C 语言模板工程,然后打开工程选项菜单,在”General Option”选项中的“Library Configuration”选项卡中,按下图所示配置。

debugger

  Semihosting 是 ARM 处理器独有的特性。它能使 ARM 目标机通过调试器和主机通讯,或者使用主机的I/O设备。此功能可以单元测试程序使用 printf 等方法输出测试信息。非常方便。其他类型的处理器也会有相应的机制完成此任务。

  接下来,在“Debugger”选项中将调试驱动选为“Simulator”。如下图所示:

debugger

  打开 main.c 文件,修改其中的代码。

/*main.c*/
#include <stdio.h>
int main()
{
    printf("Hello World\n");
    return 0;
}

  这时候可以按“Ctrl+d”启动调试。进入调试界面后,选择“View”菜单的“Terminal I/O”,打开终端输入输出窗体。按“F5”继续运行代码,可以看到“Hello World”输出信息。这样,一个简单的工程就创建完成了。

三、添加单元测试框架

  C 语言的单元测试框架很多,在这里不一一详述。我选择了Unity,项目地址见文末参考资料。下载“Unity-2.5.0.tar.gz”并解压。分别从“Unity-2.5.0/src”、“Unity-2.5.0/extras/fixture/src”和“Unity-2.5.0/extras/memory/src”中找到下列文件,并复制到 test 的工程目录中。

unity.c
unity.h
unity_fixture.c
unity_fixture.h
unity_fixture_internals.h
unity_internals.h
unity_memory.c
unity_memory.h

  在 test 工程中添加以上的所有“.c”源文件,然后新建一个“foo.c”源文件作为待测文件。在 foo.c 中添加如下内容:

/*foo.c*/
int add(int a, int b)
{
    return a + b;
}

  再次修改 main.c,使用 Unity 框架,为 add 函数添加两个测试用例。

/*main.c*/
#include <stdio.h>
#include "unity_fixture.h"

extern int add(int a, int b);

static void run_all_test(void);

int main()
{
    const char *arg = "test";
    return UnityMain(1, &arg, run_all_test);
}

void run_all_test(void)
{
    RUN_TEST_GROUP(add);
}

TEST_GROUP(add);

TEST_SETUP(add)
{
}

TEST_TEAR_DOWN(add)
{
}

TEST(add, case1)
{
    TEST_ASSERT_EQUAL(2, add(1, 1));
}

TEST(add, case2)
{
    TEST_ASSERT_EQUAL(-3, add(-1, -2));
}

TEST_GROUP_RUNNER(add)
{
    RUN_TEST_CASE(add, case1);
    RUN_TEST_CASE(add, case2);
}

  运行程序后,可以看到终端输出信息。这两个测试用例已经全部通过了。

  如何设计测试用例不在本文的讨论范围内,感兴趣的读者可以阅读 James W. Grenning 著的《Test Driven Development for Embedded C》。这本书从各个方面讲述了在嵌入式领域如何进行测试。

四、拓展

  在 test/settings 路径下,有这么几个文件:

test.Debug.cspy.bat
test.Debug.cspy.ps1
test.Release.cspy.bat
test.Release.cspy.ps1

  Debug 和 release 表示配置,bat 表示 Windows 批处理程序,ps1 表示 Powershell 程序。在终端执行这些脚本可以调用 IAR C-SPY Debugger 运行程序,并且将结果输出至终端。这有助于自动化运行单元测试,我们以后还会详细讨论。

  IAR 还提供了在终端中构建项目的工具,更多内容请参考这里

五、参考资料

  • https://github.com/ThrowTheSwitch/Unity
  • http://www.keil.com/support/man/docs/armcc/armcc_pge1358787046598.htm

微光

2018年7月7日 22:00

  今天已经是我到内蒙古赤峰市出差的第四天了。工作进行得并不顺利。下午设备故障,工程师维修了很久才解决问题。最后完成工作时,天已经黑了。

  赤峰市位于内蒙古的东北方,夏天的夜晚来的要迟一些。晚上九点多,我们才启程返回。

  我们乘坐的是一辆面包车,大概能坐十一二个人。车厢中间的过道上,凌乱地堆放着各种器械和背包。上车没过一会儿,同行的朋友们就在昏暗的车厢中睡着了。有的歪着脑袋靠在车窗上;有的低着头,随着车身的颠簸一起一伏;还有一个最舒服,他横躺在最后一排,把工作服叠起来,枕在头下。司机师傅在我们干活的时候在车里养足了精神,这会儿正聚精会神地开车。

  我见副驾还空着,便小心翼翼地挪过去,坐下。本想和司机攀谈几句,免得他也生出睡意。可是寒暄两句后,却没有了话题。

  我望向窗外。路边的树木唰唰得飞过,树影连在一起,看不清轮廓。尽管已经是九点多,不过天空还有微弱的亮光。不远处是连绵起伏的小山丘。山丘上的葱郁树木在昏暗中显得朦朦胧胧,分不清枝叶。衬在天边的亮光下,似娉婷的少女的背影。枝叶仿佛是她秀美的长辫,垂在腰间。

  我喜欢这样的夜。内心在宁静的环境中慢慢安定下来,思绪在头脑中漫游,心中的牵挂一点点变得清晰。

  转过一个弯,地势突然变得平坦了。山丘背后竟然钻出一丝微光。此时天已经完全黑了,窗外除了这一丝微光,什么也看不到。那微光时隐时现,穿过车窗显出淡淡的黄晕。

  黑夜本没什么可怕,不过这微光的出现竟让我生出一些不安。我猜不出它是什么,也不知道它有多远。这微光竟变成我心里的一份期望,想要接近它,一探究竟。

  车又转过一个弯,正对着那点微光前行。道路笔直,四周无物。车渐渐加速,我的心也加速跳起来。只要路的方向不变,我一定可以接近它。

  恍惚中,我好像睡了过去。

  梦中也有微光,它在我眼前渐渐放大,最后充满了整个视线,无比耀眼。之后那娉婷的少女背影出现在光源中心。

  她摆动长发,慢慢地转过头来。

——2018年7月7日 夜

说看就看的日出

2018年2月21日 01:12

  当我爬起床时,还不到凌晨三点。匆匆忙忙地穿好衣服后,我就出发了。

  前一天,我已经从峨眉山的万年车站爬到了雷洞坪。天黑之前便找了个落脚的地方,住了下来。今天我的目标便是在登顶看日出。说来真是好笑:大过年的,从我产生去成都的念头到买到车票,只用了半个小时。接着,从产生去峨眉山看日出的念头到坐上车,也只用了半个小时。当真是说走就走的旅行,说看就看的日出。

  初春时节,半山腰上的夜晚用春寒料峭形容再贴切不过了。即便穿上了厚厚的羽绒服和冲锋衣,也能感觉到细细的风就像一双魔手,划过脸颊,然后生生地,一点一点地将皮肤上的热量拽走。我打了个冷战,往山顶望了望,迈起了步子。

  峨眉山并不像华山那样陡峭,但是战线却拉得很长。从半山腰的万年寺到金顶有差不多三四十公里的路程。道路蜿蜒曲折,千回百转。抬头看,远处一闪一闪的亮光,稀疏得装扮在半空中。再仔细看,能分辨出缓缓移动的是登山者发出的灯光。而那些静止不动的就不知道是休憩的人还是星光了。

  从雷洞坪到金顶有大概两小时的路程。虽然不远,但是却非常难走。一方面是因为夜晚行进,另一方面是因为前一天爬了大部分的路程,休息一晚后虽然恢复了精力,但是整个小腿肌肉酸疼,十分难受。

  周围很安静,西风“嘶嘶”地细声吹过,卷得四周的草木“哗哗”地低语。四周黑洞洞的,手电筒照过的地方偶然会惊扰到一些小动物。不过他们在我发现之前就“哗啦”一声逃之夭夭。有的甚至反而把我吓一大跳。

  渐渐地,有人从我身边经过。有的还惊奇于我独自一人,停下来攀谈两句,相互叮嘱谨慎小心,然后继续前行。我有时候也从别人身边经过。一路上,能遇到形形色色的各种人。年轻的情侣们大多是男孩负重,同时还一路护着女孩;年长一些的大叔大妈们大多低头不语,看起来身手矫健,行进速度一点也不输年轻人;还有一家三口手牵手的,孩子看起来还睡眼惺忪,被爸妈一步一拽地往前走。有三五成群,欢声笑语的;有两人结伴,细声细语的;唯独不见有像我一样,茕茕孑立,形影相吊的。

  金顶是峨眉山的顶峰,上面有一片平地。当我到达金顶时,天还没有任何一丝要亮的痕迹。不过这里已经聚集了不少等待观看日出的人。我又往回走了一些,坐在了一个石头上,远离了大部分人群。不知是天还没亮,还是大家都期盼着日出,诺大一片地方,如此多人,竟然不觉得喧嚣。

  没过多久,天边就泛起了一丝鱼白,天空顿时被分作上下两半。渐渐地,细细的鱼白往上扩散、延伸。橙色的云霞也慢慢地从天地分界线中爬出来。从刚开始的模糊一片,慢慢地越来越清晰。到最后现出了云彩的轮廓。

  一不留神,天边已经亮堂了。远方天地相接的地方形成了明显的反差。上面的天空是一片浅蓝,下面则是昏暗一片。

  在一个毫不起眼的瞬间,太阳悄无声息地在云层中间探头探脑。人群中顿时引发一阵骚动,人们都在叫嚷着:“日出了,日出了!”随后,所有人仿佛是商量好的一般,又迅速安静了下来。

  太阳一点一点升起来,从一丝丝身影到红彤彤的圆球。天际越发明亮,太阳越发清晰。

  我以为我会很平静地欣赏日出,然而我分明感觉到我心里的弦越绷越紧。

  太阳不断升高,云层似乎再也无法束缚住它。从红彤彤的圆球中渐渐迸发出明亮的黄色光亮。太阳正不断地展示出它惊人的生命力。远远望去,太阳周边的天空也被它带着抖动了起来。很快,太阳便彻底从云层中脱身,蒸腾而上。阳光一下子洒在了山顶所有人的脸上,人群中充满了欢呼声。不远处的情侣激动得热情拥吻。

  这一刻,我觉得生命里一切付出都是值得的。我仿佛换了一个人一样,发自内心的开心。我好像突然重新认识了世界,对事物有了更深的理解。不知这是不是孟子所说的“浩然之气”,但是我确实感觉到内心的宽厚宏博,充乎天地之间。

  美好的东西真的能够让人深陷其中,流连忘返。下山的路上,我一直在问自己:上山是为了什么,下山又是为了什么?

——2018年2月21日 凌晨

Linux进程间通信概览

2018年1月15日 20:45

一、概要

  最近一段时间,工作中经常会处理进程间通信相关的事情。于是将一些经验汇总一下。

  总结起来,Linux下进程间通信主要有以下几种方法:

  1. 信号(Signal)
  2. 管道(Pipe)和命名管道(Named pipe)
  3. 消息队列(Message queue)
  4. 共享内存(Shared memory)
  5. 信号量(Semaphore)
  6. 套接字(Socket)

  在众多的进程间通信的方法里面,每一种方法都有优缺点,如何根据使用情景选择合适的通信方法就非常重要了。

二、信号

  信号是非常基础的进程间通信方法,它从软件层面上对中断机制进行了模仿。所以信号也被称作“软中断信号”。信号用来通知进程发生了异步事件。进程之间可以通过系统调用发送软中断信号。操作系统内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件。Linux支持不可靠信号和可靠信号。

  Linux系统共定义了64种信号,分为两大类:实时信号与标准信号,前32种信号(信号值小于SIGRTMIN)为标准信号(也称之为不可靠信号和非实时信号),后32种(信号值位于SIGRTMIN和SIGRTMAX之间)为实时信号(也称之为可靠信号)。信号很重要的一个特点就是当进程接收到信号后,能够中断当前执行的代码,转而执行信号处理函数。实时信号和标准信号有如下区别:

  • 实时信号没有明确的含义,而是由使用者自己来决定如何使用。而标准信号则一般有确定的用途及含义,并且每种信号都有各自的缺省动作。如按键盘的CTRL+C时,会产生SIGINT信号,进程对该信号的默认反应就是进程终止。
  • 一个进程可以接受多个同样的实时信号,这些实时信号会被缓存在一个队列中,然后按次序被处理。而标准信号则不能。在标准信号没有得到处理的时候,多个标准信号会被合为一个。这也造成了标准信号可能丢失的情况。
  • 实时信号使用sigqueue发送的时候,可以携带附加的数据(int或者pointer)。标准信号不能。
  • 实时信号具有优先级的概念,数值越低的实时信号其优先级越高。在处理的时候,也是数值低的实时信号优先得到处理。
  • 实时信号的默认行为都一样,都是结束当前的进程,这个和标准信号是不一样的。
  • 进程每次处理标准信号后,就将对信号的响应设置为默认动作。在某些情况下,将导致对信号的错误处理。因此,用户如果不希望这样的操作,那么就要在信号处理函数结尾再一次调用signal(),重新安装该信号[注1]

  信号的相关API:

  • signal():安装信号
  • sigaction():安装信号
  • kill():向进程或进程组发送信号
  • sigqueue():发送信号
  • alarm():调用进程指定时间后发出SIGALARM信号
  • setitimer():设置定时器,计时达到后给进程发送SIGALRM信号
  • abort():向进程发送SIGABORT信号,默认进程会异常退出
  • raise():向进程自身发送信号
  • sigemptyset():信号集全部清0
  • sigfillset():信号集全部置1
  • sigaddset():向信号集中加入signum信号
  • sigdelset():向信号集中删除signum信号
  • sigismember():判定信号signum是否存在信号集中
  • sigprocmask():把信号集中的信号添加到进程信号集
  • sigpending():获取已发送到进程,却被阻塞的所有信号
  • sigsuspend():替换进程的原有掩码,并暂停进程执行,直到收到信号再恢复原有掩码并继续执行进程

  值得注意的是:

  • 由于信号会中断主进程的运行,所以在信号处理函数中多任务的并发控制也会经常使用。当主进程获取某个资源并加锁后,如果在它解锁前接收到了一个信号并进入了信号处理函数,并且在这个信号处理函数中,同样需要获取此资源并加锁。但是主进程还没有解锁,这就会导致死锁。所以,在信号处理函数中进行并发控制需要格外小心。
  • 当进程正阻塞在系统调用(或者库函数)中时,如果一个信号处理函数被调用。此时会出现两种情况:1.信号处理函数返回后,自动重新进入系统调用(库函数调用)。2.系统调用(库函数)失败并且返回EINTR。具体情况请参考Linux man pages。

三、管道

  管道是所有进程间通信手段中最简单的一种。对于进程来说,管道和文件类似。命名管道和管道的区别仅在于创建和打开的方式不同。

  在Linux man pages中,命名管道被称作FIFO。管道和命名管道都提供了单向的进程间通信的通道。管道有一个读取端和一个写入端。数据从写入端写入,从读取端读取。由于管道的特点和创建管道的方法限制,导致管道只能用于具有亲缘关系的进程之间。命名管道(FIFO)和管道非常类似。只不过它在文件系统中有唯一的文件与之对应。读取端使用O_RDONLY标志打开,而写入端则是用O_WRONLY打开。管道和命名管道只能发送无格式的字节流,并且其容纳数据的缓冲区大小是受限制的。

  管道的相关API:

  • pipe():创建并打开匿名管道
  • mkfifo():创建命名管道
  • unlink():销毁命名管道

  管道的读写操作和文件操作基本一致。不过需要注意的是,以阻塞方式打开和读写命名管道时,阻塞方式和普通文件有所区别。具体情况请参考Linux man pages。

四、消息队列

  消息队列就是一个消息的链表。可以把消息看作一个记录,具有特定的格式以及特定的优先级。对消息队列有写权限的进程可以向中按照一定的规则添加新消息;对消息队列有读权限的进程则可以从消息队列中读走消息。消息队列解决了信号只能通知事件,而不能发送信息的问题。同时又克服了管道只能承载无格式字节流以及缓冲区大小受限等缺点。结构化的消息通信机制使通信双方在约定好数据结构之后,无须对消息再次解析。

  Linux既支持POSIX消息队列,也支持System V 消息队列,相关API如下所示:

操作 POSIX 函数 System V 函数
创建消息队列 mq_open() msgget()
发送消息 mq_send()
mq_timedsend()
msgsnd()
接收消息 mq_receive()
mq_timedreceive()
msgrcv()
操作消息队列 mq_getattr()
mq_setattr()
msgctl()
消息队列通知 mq_notify() -


  消息队列具有内核延续性。所谓内核延续性是指:某个资源创建后会随着内核的运行而一直存在,直到这个资源被有意的回收。相似的概念还有进程延续性和文件系统延续性。

  Linux中POSIX消息队列的描述符直接使用了文件描述符,这意味着可以使用select、poll和epoll来操作消息队列。不过这些并不包含在POSIX标准中,所以这一特性不能保证可移植性。

五、共享内存

  采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的。不过共享内存往往要配合其他的通信方法使用,来达到进程间的同步及互斥。

  Linux同样也支持POSIX共享内存和System V 共享内存,相关API如下所示:

  • shm_open():(POSIX)创建并打开共享内存
  • ftruncate():(POSIX)设置共享内存大小
  • mmap():(POSIX)映射共享内存至虚拟进程空间
  • munmap():(POSIX)取消共享内存映射
  • shm_unlink():(POSIX)删除共享内存
  • close():(POSIX)关闭共享内存描述符
  • fstat():(POSIX)获取共享内存状态
  • fchown():(POSIX)更改共享内存所属
  • fchmod():(POSIX)更改共享内存访问权限
  • shmget():(System V)创建并打开共享内存
  • shmat():(System V)映射共享内存至虚拟进程空间
  • shmctl():(System V)设置共享内存空间
  • shmdt():(System V)取消共享内存映射

  共享内存具有内核延续性。

六、信号量

  信号量使用一个正整数来表示的,通常用在进程间同步上。对信号量有两种操作:1.给信号量加1;2.给信号量减1。当信号量为0时,减1操作会阻塞,直到信号量不为0。

  在POSIX标准中,信号量分两种,匿名信号量和命名信号量。类似命名管道,命名信号量会以文件系统中的一个文件作为名字。所以命名信号量一般用于进程间同步。匿名信号量顾名思义没有名字,所以一般用于线程间同步。当需要用于进程间同步时需要将匿名信号量放置在共享内存中。

  需要注意的是:和匿名管道不同,匿名信号量不能直接用于父子进程之间。

  Linux支持POSIX信号量和System V 信号量,相关API如下所示:

  • sem_open():(POSIX)创建并打开信号量
  • sem_init():(POSIX)初始化信号量
  • sem_getvalue():(POSIX)获取信号量的值
  • sem_post():(POSIX)post操作
  • sem_wait():(POSIX)wait操作
  • sem_close():(POSIX)关闭信号量
  • sem_destroy():(POSIX)销毁信号量
  • sem_unlink():(POSIX)销毁信号量
  • semget():(System V)创建并打开信号量
  • semctl():(System V)控制信号量
  • semop():(System V)操作信号量

  System V 信号量非常难用,在此就不做介绍了。

  命名信号量具有内核延续性,匿名信号量如果没有放在共享内存中的话具有进程延续性,如果放在共享内存中,我猜测应该会跟随共享内存的生命周期。

七、套接字

  是的,你没有看错,套接字不仅用于网络通信,还可用于进程间通信。非但如此,套接字在设计之初便充分考虑到了进程间通信。套接字最早出现在《RFC 147:The Definition of a Socket》标准中,用于ARPANET(The Advanced Research Projects Agency Network)中。之后便发展出了用于TCP/IP协议的网络套接字(Network sockets)和用于进程间通信的Unix域套接字(Unix Domain sockets)。后来伯克利套接字(Berkeley sockets,也称作BSD sockets)基于前两者的基础上,随着4.2 BSD Unix操作系统发展起来。然而后来伯克利套接字慢慢被POSIX套接字所取代。

  Linux实现了POSIX套接字。创建时选择相应的域和协议,便可以使用套接字进行进程间通信。

int socket(int domain, int type, int protocol);

  分别为domain和type传入AF_UNIX和SOCK_SEQPACKET便可创建一个套接字来支持Unix域套接字。它保证了通信的可靠性和有序性。

八、其他

  我们注意到:有POSIX和System V两套标准都支持信号量、消息队列和共享内存。为什么会这样呢?他们两个有什么区别呢?应该如何选择呢?

  System V实际上指的是Unix System V。这里V是罗马数字五,读作“System Five”。1983年,UNIX System V是第一个发布的商业版的Unix操作系统。随着Unix操作系统的成功,Unix中的各种特性成为操作系统界的事实标准。后来直接使用“System V”来指代此标准。而POSIX标准虽然起源于1988年,但是直到2001年才被IEEE标准委员会所接纳。Linux的初版于1991年发布,所以最早支持了System V标准。不过由于POSIX在很多地方都优秀于System V,所以POSIX标准也逐渐被大部分人接受。

  • 按照Linux man pages的说法,System V是比较古老的接口,有很多设计不合理的地方。POSIX吸取了很多经验,提供了更加友好的接口设计。但是由于POSIX比较年轻,有些平台并没有完整的实现,尤其是比较古老的系统。
  • 按照《UNIX, Third Edition: The Textbook》上的说法,POSIX的进程间通信是线程安全的,而System V的不是。我还未对此作验证。
  • 由于POSIX标准的设计目标就是可移植性,所以POSIX的进程间通信方式更具可移植性。
  • POSIX提供了更多的功能,比如支持select,poll等方法。

注1:Linux支持不可靠信号,但是对不可靠信号机制做了改进:在调用完信号处理函数后,不必重新调用该信号的安装函数(信号安装函数是在可靠机制上的实现)。因此,Linux下的不可靠信号问题主要指的是信号可能丢失。

参考:

  • Linux Man Pages
  • Wikipedia

三年牡丹

2015年2月19日 02:12

  小时候是在地质八队长大的。说那个院子依山傍水一点也不为过。后墙依着秦岭的小土丘,西边靠着太平河,仅有半条街那么远。

  前前后后搬过几次家,后来稳定在了一排小窑洞里。一大一小的套间,外面有个厨房。厨房门口有个方形的小花坛。

  有一年夏天,朋友送给我一株牡丹。我随手插在了花坛里,浇了浇水。

  牡丹出乎意料地活了。记忆中,它抽出了不少的新叶。每天放学回家,还没放下书包就蹲在花坛边看它。忍不住用手摸嫩嫩的叶子,又怕太用力伤了它。

  来年春天,那是个周末。早上挨了母亲一顿打,她中场休息时,我被罚站在花坛前。猛然看到那株牡丹竟然长出了一个花蕾。

  花蕾小如黄豆一样夹在叶柄。顶端还没有裂开,看不到是什么颜色的花。但它分明就是一个花蕾!

  一时间我就忘了还在挨骂中,蹲下来贴近了仔细看。我没有用手碰,生怕一下就碰掉在地上。愣了半天才想起来浇水。那时候什么也不懂,“你多喝点水”就是所有的关心手段了。

  在开花的憧憬中过了几天。不知道为什么,那朵花蕾最终还是没有开出花朵。在我发现它掉落在地上的那天中午,我伤心地蹲在地上很久,内心的苦闷让我有把它拔出来的冲动。

  最终,我还是又给它浇了浇水。我知道,即使这样,我还是打心眼儿里喜欢它。

  春去秋来,它又掉了叶子。似乎那个凋落的花骨朵让我觉得它很脆弱,我对它的照顾更加多了。埋蛋壳似乎都觉得还不够,我甚至给它打过一个鸡蛋,埋在旁边的土里。当然,被发现免不了又是一顿打。

  不过我仍然开心着。

  那年,下了一场很大的雨。有多大?后来据说太平河的河水已经漫到了桥面上。我逃了自习课,跑回家,在旁边的葡萄树上绑了一把雨伞,希望为它遮风挡雨。

  第二天雨停了,伞被吹不见了。

  它还在,只不过叶子都没了。我很担心,非常担心。

  好在第二年春天,它又抽出了嫩叶。

  它比我想象得坚强很多。

  已经不记得它开花时我有多开心,反正当时不知道从哪里偷了一个破旧的竹扫把,用这扫把给它做了个小篱笆。

  我蹲在牡丹前面,看不够。真是艳丽无比。

  母亲说,拿这个牡丹写一篇作文吧。我竟然开心地答应了。

  不可思议……

  我只见过它开过这一次花。那年冬,我家搬到了西安,我也转学过来,上六年级。忙着插班考试,走得匆忙,没有带上它。

  若干年后,我回去过那个院子。能拆的都被拆了。

  人是物非。

  我觉得它始终都是属于我的。直到我看了那句“念桥边红药,年年知为谁生?”

  姜夔当年21岁,写下这首《扬州慢》。

  那一年,我也差不多那么大。我倒想知道,当年的姜夔如何加上曲子,唱出内心的凄美怅惘。

  走时,环顾空荡荡的院子,好一个“清角吹寒,都在空城”!

——2015年2月19日 凌晨

❌
❌