自从半年前换了 Arch Linux,就一直没有设置 swap,心想最多也就编译的时候 Chrome 标签页「哦哟」一下嘛。
不过实际没有 swap 不能休眠还是有点不踏实,毕竟是台式机,又懒得接 UPS。于是以下就是这周末设置 swap 文件用于休眠的历程(内容 Arch Wiki 上都有提及,就是总结记录一下)。
查看当前 Swap 状态
使用 swapon --show
。
创建多大的 Swap 文件
Swap 文件的大小一般至少是 512M。如果你和我一样是为了让系统能够休眠,那么可以参考 /sys/power/image_size
里的字节数,如果你没有修改过,这个值默认是当前内存大小的五分之二。
/sys/power/image_size
控制的就是休眠镜像的文件大小。系统会尽可能保证镜像大小不超过这个值,即便无法实现,也会尽量将镜像缩小。也就是说,你给它设 0 也是可以的,此时休眠镜像的大小会是最小的。
创建 Swap 文件
首先在一些文件系统上使用 swap 文件是会有问题的,比如早期的 Btrfs 就不支持。
创建 Swap 文件有很多种方法,但最可移植的方法是使用 dd
。比如创建 1G 的 /swapfile
:
# dd if=/dev/zero of=/swapfile bs=1M count=1024 status=progress
# chmod 0600 /swapfile
# mkswap -U clear /swapfile
这里的 chmod
是为了安全,所有人都可读写的 Swap 文件是巨大隐患。
mkswap
的 -U
参数用于设置 swap 的 UUID。但是因为 swap 文件必须使用文件系统路径去指定,所以这里使用特殊的 clear
作为参数去清空它(实际效果是设置了全零的 UUID)。
打开与关闭
创建好 swap 文件后,就可以直接启用了:
# swapon /swapfile
最后把它放进 /etc/fstab
里,这样每次启动就会直接启用:
/swapfile none swap defaults 0 0
如果是要关闭,顾名思义就:
# swapoff /swapfile
关闭以后它就是个普通文件,想删除就可以直接删除了。
使用 Swap 文件进行休眠
故事讲到这里,休眠和混合睡眠应该就都可以创建休眠镜像了。但我们还需要进行额外设置,才能在启动时使用这个休眠镜像。
内核参数
首先需要为内核设置 resume
和 resume_offset
参数,告诉它去哪里找休眠镜像。
resume
参数是 swap 文件所在的分区(例如 /dev/nvme0n1p2
,或者 UUID=4209c845-f495-4c43-8a03-5363dd433153
),可以在 /etc/fstab
里查看,也可以通过 findmnt -no UUID -T /swapfile
获取。
resume_offset
是文件开头在这个分区中的物理偏移量,可以通过 filefrag -v /swapfile
查看。
关于内核参数的设置,如果你和我一样使用的是 GRUB,可以编辑 /etc/default/grub
,把 resume=XXX resume_offset=XXX
追加到 GRUB_CMDLINE_LINUX_DEFAULT
里。最后重新生成一下 grub.cfg
即可:
# grub-mkconfig -o /boot/grub/grub.cfg
如果你有闲情逸致,也可以先不这么做。直接在 GRUB 启动界面按 e
,然后去手动编辑本次启动所使用的参数 😛
initramfs
如果你的 initramfs 没有使用 systemd 钩子(使用的是 base),那么就还需要添加一个 resume 钩子才会尝试从休眠中恢复:
编辑 /etc/mkinitcpio.conf
文件,在 HOOKS=(...)
里的 udev
之后的任何位置加入 resume
。最后重新生成 initramfs 即可:
# mkinitcpio -p linux
参考
最近 MBP 电池彻底坏了不想修,换 Windows 用了几天又很不习惯,于是就装了个 Arch。
用 Arch Linux 得自行装图形界面、使用 Display Manager 进行图形化的登录操作。但这时候就遇到个很好玩的事情:进 Display Manager 时黑屏(如果去 BIOS 里绕一下,有小概率不黑屏),不过在黑屏里 Ctrl+Alt+F2 还是可以切到别的 tty 的。
内核参数、驱动之类的检查一圈都没有问题,把我用的 LightDM 换成别的 Display Manager 也还是如此。最后在万能的 Arch Wiki 找到了解决方法,在 /etc/lightdm/lightdm.conf
里配置一下:
[LightDM]
logind-check-graphical=true
黑屏的原因是,图形驱动还没加载完,LightDM 就启动了:系统启动得太快了,得显式让 LightDM 等待图形设备 🤣
做像素风/复古游戏不免要用上点阵字体,之前我用过一阵子 IPix,但它时有缺字,一些英文、数字、以及标点符号的样子和字间距给人感觉也有点奇怪。于是我就自己做了一个像素字体。
做英文字体和中文字体所需要涉及的字形不在一个数量级上,前者撑死了一百个左右,后者则至少要覆盖常用的几千个汉字,非常体力活。IPix 的字模来自于开源项目 BitmapFont,这个项目的作者收集了很多远古系统里的字模数据(感觉从老系统里提取的字模在版权方面界限还是有点模糊),可以直接生成对应的字形。
这些字模数据中,每一个字符都对应一系列的比特位。比如 16 像素宋体的「人」字在数据文件中对应 01000100010001000100010001000280028002800440044008201010200e4004
,按照每 16 个比特位换行表示后就成了点阵的「人」字:
X
X
X
X
X
X
X
X X
X X
X X
X X
X X
X X
X X
X XXX
X X
有了这样的点阵数据,我们就可以很繁琐方便地制作各种格式的字体文件了。
我把制作好的 TrueType 字体放到了 itch.io 上,有需要的童鞋可以自取:
p.s. 其实市面上有一款非常不错的收费点阵字体 丁卯点阵体,支持 7px 和 9px 的大小。
10 分钟的内容,录了两天。一边解说一边写代码,然后还得控制在 10 分钟左右真是太难了,深刻体会到了舌头打结的快感。
BV1Xi4y1M7Eq
p.s. 上一次 B 站投稿居然已经是在八年前了。
原来除了 Modern C++、Modern CMake,我们还有 Modern SQL,真是佩服这种文艺复兴式的 branding。
窗口函数(Window Function)就是一个例子,它由 SQL:2003 引入,可以用来筛选结果集中与当前行存在指定关联的行。相比子查询,效率更高,用起来也更方便。
例如我们有一张去年全年每日收入的表 revenues
,想根据这张表查一张报表,显示每季度总收入及其环比增长,就可以用窗口函数:
created_at |
revenue |
2019-01-01 |
123.45 |
2019-01-02 |
456.78 |
2019-01-03 |
420.00 |
... |
... |
SELECT
date_part('quarter', created_at) AS quarter,
sum(revenue) AS revenue,
(sum(revenue) /
lag(sum(revenue)) OVER ()) - 1 AS percentage
FROM revenues
GROUP BY 1
ORDER BY 1
得到的结果类似这样:
quarter |
revenue |
percentage |
1 |
4530.50 |
|
2 |
4565.64 |
0.008 |
3 |
4933.01 |
0.080 |
4 |
4731.75 |
-0.041 |
SQL 中的 lag(sum(revenue)) OVER ()
就是对窗口函数的调用了,其中 lag
函数就表示「上一条记录(季度)」。
窗口函数调用的特征是关键词 OVER
OVER
前的部分为窗口函数调用本身,用来指定针对窗口中内容的操作。既可以用 lag
这样专门的窗口函数,也可以用 sum
这种普通的聚合函数。
OVER
后的部分即为对窗口的定义,既可以是直接在括号里写出,也可以用稍后统一定义的窗口名,比如上面的 SQL 也可以写成:
SELECT
date_part('quarter', created_at) AS quarter,
sum(revenue) AS revenue,
(sum(revenue) /
lag(sum(revenue)) OVER w) - 1 AS percentage
FROM revenues
GROUP BY 1
WINDOW w AS ()
ORDER BY 1
窗口的定义
「窗口定义」中的窗口其实英文叫 Frame,即窗框。「窗口函数」中的窗口则是 Window,即窗户。没什么特别含义,应该就是叫着顺口、想着形象而已。
下面的例子里,我们用 PostgreSQL 的聚合函数 array_agg
列出窗口中有哪几行。
所有记录
括号中留空表示窗口中为结果中的所有行:
SELECT
i,
array_agg(i) OVER ()
FROM generate_series(0, 5) AS s(i)
ORDER BY 1
i |
array_agg |
0 |
0,1,2,3,4,5 |
1 |
0,1,2,3,4,5 |
2 |
0,1,2,3,4,5 |
3 |
0,1,2,3,4,5 |
4 |
0,1,2,3,4,5 |
5 |
0,1,2,3,4,5 |
我们可以看到每一行的对应窗口里,都包含了所有其它行。
相同分组
括号中还可以使用 PARTITION BY
指定分组的条件:
SELECT
i,
array_agg(i) OVER (
PARTITION BY i % 2
)
FROM generate_series(0, 5) AS s(i)
ORDER BY 1;
i |
array_agg |
0 |
0,2,4 |
1 |
1,3,5 |
2 |
0,2,4 |
3 |
1,3,5 |
4 |
0,2,4 |
5 |
1,3,5 |
我们可以看到每一行的对应窗口里,都包含了与它 i % 2
值相同的行。
指定范围
可以用 ROWS BETWEEN A AND B
来指定窗口中包含哪些行,例如:
SELECT
i,
array_agg(i) OVER (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
)
FROM generate_series(0, 5) AS s(i)
ORDER BY 1;
i |
array_agg |
0 |
0,1,2,3,4,5 |
1 |
1,2,3,4,5 |
2 |
2,3,4,5 |
3 |
3,4,5 |
4 |
4,5 |
5 |
5 |
这里其实直接读 SQL 就明白了,是要求窗口从当前行开始,一直到最后一条记录结束。
排序
窗口定义里还可以用 ORDER BY
来排序,不过一旦排序,默认的范围就变成了 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRERNT ROW
(从开头到当前行),如果不是想要的范围就需要显式指定。
SELECT
i,
array_agg(i) OVER (
ORDER BY i DESC
)
FROM generate_series(0, 5) AS s(i)
ORDER BY 1;
i |
array_agg |
0 |
5,4,3,2,1,0 |
1 |
5,4,3,2,1 |
2 |
5,4,3,2 |
3 |
5,4,3 |
4 |
5,4 |
5 |
5 |
常见窗口函数
lead
同一分组中,在当前行之后数的第 N 行
lag
同一分组中,在当前行之前的第 N 行
row_number
当前分组中的行号,从 1 开始
rank
当前分组中的排名,如果存在相同名次,会是 1、1、3、4……
dense_rank
当前分组中的排名,如果存在相同名次,会是 1、1、2、3……
参考
- 1
- 2
- 3
- 4
- ...
- 15
- »