普通视图

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

群晖 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 一直在稳定运行中。不过我清楚地认识到,出问题只是时间问题,我依然要保持备份数据的习惯。

修复 Linux 中的一个噪音问题

2020年5月17日 10:53

  自从安装了 Kubuntu 20.04 后,我被一个噪音问题困扰了很久。系统在播放声音前,总会发出一两声短促的噪音。然后继续正常播放声音。后来经过一些研究,我在不久前解决了这个问题。

  刚安装完操作系统后,我安装了媒体播放器,体验音乐和电影的播放效果。在操作过程中,我注意到系统偶尔会发出的短促噪音。在网上搜索一阵,并且尝试了一些方法后,问题没有得到解决。我便放弃了,只当是驱动程序可能有缺陷。

  不过我渐渐地发现了一个规律:噪音只会在一段安静时间后,再次播放声音前产生。在持续播放声音过程中,从来没有噪音。这让我猜测到了一种可能的原因:系统在播放声音一段时间后可能会关闭音频设备,然后在下一次播放前再打开音频设备。打开音频设备的过程中有可能产生电信号的突变,继而产生短促的噪音。这种设计极有可能是基于省电的考虑。

  于是,我以“Linux save power audio noise”为关键词搜索了一番。在 Redhat 公开的 BUG 管理系统中,我找到了2017年报告的一个 BUG:《Bug 1525104 - Clicking noise when start or stop sound playing》

  这个 BUG 所描述的问题和我的相同,并且 Jeremy 似乎给出了解决问题的办法。

Jeremy Cline 2017-12-13 18:06:49 UTC

 Can you pass snd_hda_intel.power_save=0 on the kernel command line and see if that resolves your issue?

 Thanks

Riku Seppala 2017-12-13 18:25:50 UTC

 Yes, that seems to resolve this for me.

  这个方案虽然可行,但需要通过修改 grub 的配置来修改内核命令行参数。即有风险又麻烦,普通用户不好操作。我决定找一个更容易操作的方法。

  “snd_hda_intel.power_save=0”看起来是把“snd_hda_intel”内核模块的“power_save”参数设置为0。那么问题应该是出在这个内核模块上了。于是我下载了 ubuntu 的内核源码,找到了这个内核模块。经过一番搜索,找到了这部分代码(linux-source-5.4.0/sound/pci/hda/hda_intel.c):

#ifdef CONFIG_PM
static int param_set_xint(const char *val, const struct kernel_param *kp);
static const struct kernel_param_ops param_ops_xint = {
	.set = param_set_xint,
	.get = param_get_int,
};
#define param_check_xint param_check_int

static int power_save = CONFIG_SND_HDA_POWER_SAVE_DEFAULT;
module_param(power_save, xint, 0644);
MODULE_PARM_DESC(power_save, "Automatic power-saving timeout "
		 "(in second, 0 = disable).");

static bool pm_blacklist = true;
module_param(pm_blacklist, bool, 0644);
MODULE_PARM_DESC(pm_blacklist, "Enable power-management blacklist");

/* reset the HD-audio controller in power save mode.
 * this may give more power-saving, but will take longer time to
 * wake up.
 */
static bool power_save_controller = 1;
module_param(power_save_controller, bool, 0644);
MODULE_PARM_DESC(power_save_controller, "Reset controller in power save mode.");
#else
#define power_save	0
#endif /* CONFIG_PM */

  阅读这段代码后,我发现 power_save 参数并非简单地开启或者关闭省电模式,而是一个超时时间。增大超时时间可以降低进入省电模式的频率,从而减少噪音产生的次数。设置0可以直接关闭省电模式。于是问题就变得简单了,我的台式电脑似乎并不那么在乎功耗,所以我决定关闭音频设备的省电模式。用这条命令可暂时关闭省电模式。系统重启后失效。

$ sudo echo 0 > /sys/module/snd_hda_intel/parameters/power_save

  若要永久关闭设备的省电模式,在/etc/modprobe.d/中添加一个“disable_snd_hda_intel_power_save.conf”文件,在文件中添加这一行:

options snd_hda_intel power_save=0

一个更深入的问题

  为什么 Linux 中有这个问题,而 Windows 操作系统中没有?

  Linux 的文档中有一个章节:More Notes on HD-Audio Driver。这一节中介绍了 HD-audio 设备的大致结构。这里引用其中一段:

The HD-audio component consists of two parts: the controller chip and the codec chips on the HD-audio bus. Linux provides a single driver for all controllers, snd-hda-intel. Although the driver name contains a word of a well-known hardware vendor, it’s not specific to it but for all controller chips by other companies. Since the HD-audio controllers are supposed to be compatible, the single snd-hda-driver should work in most cases. But, not surprisingly, there are known bugs and issues specific to each controller type. The snd-hda-intel driver has a bunch of workarounds for these as described below.

  这里说:HD-audio 组件由控制器芯片和解码芯片组成。Linux 提供了控制器芯片的驱动程序。在 snd_hda_intel 模块中,我找到了解码芯片的省电模式的驱动代码(linux-source-5.4.0/sound/pci/hda/hda_codec.c):

static void codec_set_power_save(struct hda_codec *codec, int delay)
{
	struct device *dev = hda_codec_dev(codec);

	if (delay == 0 && codec->auto_runtime_pm)
		delay = 3000;

	if (delay > 0) {
		pm_runtime_set_autosuspend_delay(dev, delay);
		pm_runtime_use_autosuspend(dev);
		pm_runtime_allow(dev);
		if (!pm_runtime_suspended(dev))
			pm_runtime_mark_last_busy(dev);
	} else {
		pm_runtime_dont_use_autosuspend(dev);
		pm_runtime_forbid(dev);
	}
}

  原来音频设备的低功耗是通过控制解码芯片来实现的。于是我猜测电脑主板上应该有这样一个解码芯片:在省电模式中断电,在工作模式中供电。

  我使用的是华硕 B85M-F 型号的主板,在华硕的官网中搜到了此主板的规格说明。此主板搭载了 Realtek® ALC887 8-Channel High Definition Audio CODEC 音频解码芯片。我又在网上找到了这个芯片的数据手册,里面有它的长相和引脚定义。它长这个样子:

debugger

  我在电脑主板上找到了这个芯片,然后把 snd_hda_intel 模块的省电模式打开,用万用表在它的供电引脚上量到了电平的变化。正如我之前猜测的一样:播放声音一段时间后,停止供电。播放声音前,恢复供电。

  最后,我抱着试试看的态度,换了一块硬盘,装上 Windows 10。经过半个多小时的测试,这个音频解码芯片的供电始终没有被切断。看来,Windows下的驱动并没有在低功耗方面理会此芯片。

  到此,这个问题不再扑朔迷离。然而这个问题引发了我更深地思考:当此问题初现的时候,我的第一印象是 Linux 在这些细节上处理的还不够成熟。然而经过以上研究过程,我认为是 Linux 的开发人员追求极致的风格导致了这些琐碎的问题。上文提及的2017年报告的 BUG,到现在依然被开发者们追踪并修改,我为这些开发者们的精神所折服。

——2020年5月17日

使用 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年2月21日 01:12

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

——2018年2月21日 凌晨

三年牡丹

2015年2月19日 02:12

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

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

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

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

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

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

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

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

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

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

  不过我仍然开心着。

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

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

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

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

  它比我想象得坚强很多。

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

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

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

  不可思议……

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

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

  人是物非。

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

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

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

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

——2015年2月19日 凌晨

❌
❌