Ant-Veil

Caspar Blog

Block Device Plugging

| Comments

关于 block 设备的 plug 和 unplug 机制,可以参考这篇内核文档。简单来说就是内核提供了一个 plug 机制,请求被放到一个空队列里之后,可以将队列处于 “plugged” 状态,此时队列并不真正向下层设备发射(issue)请求,而是攒够足够多的请求之后,将队列 “unplug” 之后才发射,在这期间,I/O 的调度算法将有机会对请求进行合并和排序。unplug 的时机既可以是一个手工的 blk_unplug() 操作,也可以由一个 unplug_timer 定时器超时触发。这整个过程听起来有点像在等机场大巴,到站停靠(plug),装载乘客(攒 request),到点发车(unplug_timeout)或者人满发车(blk_unplug)。

很容易想到,unplug_timer 这个定时器的启动会发生在 plug 阶段(见 block/blk-core.c 中的实现):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void blk_plug_device(struct request_queue *q)
{
        WARN_ON(!irqs_disabled());
        /*
         * don't plug a stopped queue, it must be paired with blk_start_queue()
         * which will restart the queueing
         */
        if (blk_queue_stopped(q))
                return;
        if (!queue_flag_test_and_set(QUEUE_FLAG_PLUGGED, q)) {
                mod_timer(&q->unplug_timer, jiffies + q->unplug_delay);
                trace_block_plug(q);
        }
}
EXPORT_SYMBOL(blk_plug_device);

而定时器超时函数见同一个文件下的实现:

1
2
3
4
5
6
void blk_unplug_timeout(unsigned long data)
{
        struct request_queue *q = (struct request_queue *)data;
        trace_block_unplug_timer(q);
        kblockd_schedule_work(q, &q->unplug_work);
}

这个函数是在 blk_queue_make_request 定义的时候设置的(见 block/blk-settings.c):

1
2
3
4
5
6
7
8
9
10
11
void blk_queue_make_request(struct request_queue *q, make_request_fn *mfn)
{
        <snip>
        q->unplug_thresh = 4;           /* hmm */
        q->unplug_delay = msecs_to_jiffies(3);  /* 3 milliseconds */
        if (q->unplug_delay == 0)
                q->unplug_delay = 1;
        q->unplug_timer.function = blk_unplug_timeout;
        q->unplug_timer.data = (unsigned long)q;
        <snip>
}

可以看到除了定义了超时函数,还定义了超时的时间,是 3ms. 这个 3ms 可以用来排查一系列奇怪的 I/O 性能问题。比如说在 iodepth 为 1 的情况下,发现 IOPS 是很固定的 333,那么多半就是因为请求队列没有及时 unplug 而导致定时器超时自动 unplug 了。通过简单计算得知,因为每个队列需要 3ms 传输,那一秒钟只能传送 333 个队列,即 IOPS == 333.

如果要主动 unplug,就需要调用 blk_unplug() 函数,或者 generic_unplug_device() 函数,从通用性来说,调用前者会比较好,看如下代码(block/blk-core.c):

1
2
3
4
5
6
7
8
9
10
void blk_unplug(struct request_queue *q)
{
        /*
         * devices don't necessarily have an ->unplug_fn defined
         */
        if (q->unplug_fn) {
                trace_block_unplug_io(q);
                q->unplug_fn(q);
        }
}

q->unplug_fn 一般不需要自行设置,如果没有定义,会使用默认的函数,即 generic_unplug_device()

看完了这几个函数,可以知道,plug 完之后及时 unplug 通常是避免 IO 延迟过高的良好手段。

Use Non-export Symbol

| Comments

最近在折腾 LIO,里面的模块好多,有核心模块(target_core_mod)、前端驱动模块(vhost-scsi, iscsi, etc)、后端驱动模块(target_core_iblock, target_core_file, etc)。我想要实现某种程度的模块“热升级”,具体来说,比如target_core_mod这个核心模块升级了之后,想要和旧模块共存,新的前端和后端驱动在接入的时候直接使用新模块。

于是想当然地认为只要把编译出来的模块改个名字就好了。然而事情并没那么简单,中间碰到两个问题,首先就是本文要讲到的,导出符号(EXPORT_SYMBOL)重复的问题。其实问题很好理解,我改了个名字之后的模块(比如:target_core_mod_new.ko,在代码中还是会导出相同的符号,所以解决方法就是在新的模块中不导出这些符号,删掉所有的EXPORT_SYMBOL()宏。

可是问题又来了,我的后端驱动模块(比如:target_core_iblock.ko)需要依赖新的模块的函数,在无法导出符号的情况下,我没有办法使用后端驱动模块。稍微搜了一下,找到kallsyms_lookup_name()函数(定义),这个函数传入一个类似 module:symbol 形式的参数,用于读取/proc/kallsyms中的符号表,找到对应的符号,然后返回符号的地址(即函数的入口)。

于是我拿了这个函数开开心心地去改代码了。以target_complete_cmd()这个函数为例,我在后端驱动模块的代码需要调用这个函数的地方:

Linux Kernel 208.5d Panic Issue

| Comments

最近线上碰到的问题,虽说早就有解决方案了,但是陆陆续续还是碰到很多没有升级内核的机器挂掉。记录一下以下内容,仅供参考。

现象是机器运行到一定天数(刚开始反馈集中在 208.5 天左右)就会主动挂掉,报告文章末尾的 CallTrace,所幸社区几年前就有了解决方案:

Upstream Patch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
commit 4cecf6d401a01d054afc1e5f605bcbfe553cb9b9
Author: Salman Qazi <sqazi@google.com>
Date:   Tue Nov 15 14:12:06 2011 -0800

    sched, x86: Avoid unnecessary overflow in sched_clock

    (Added the missing signed-off-by line)

    In hundreds of days, the __cycles_2_ns calculation in sched_clock
    has an overflow.  cyc * per_cpu(cyc2ns, cpu) exceeds 64 bits, causing
    the final value to become zero.  We can solve this without losing
    any precision.

    We can decompose TSC into quotient and remainder of division by the
    scale factor, and then use this to convert TSC into nanoseconds.

    Signed-off-by: Salman Qazi <sqazi@google.com>
    Acked-by: John Stultz <johnstul@us.ibm.com>
    Reviewed-by: Paul Turner <pjt@google.com>
    Cc: stable@kernel.org
    Signed-off-by: Peter Zijlstra <a.p.zijlstra@chello.nl>
    Link: http://lkml.kernel.org/r/20111115221121.7262.88871.stgit@dungbeetle.mtv.corp.google.com
    Signed-off-by: Ingo Molnar <mingo@elte.hu>

Blog 恢复

| Comments

Blog 已经迁移到了 GitHub,准备恢复更新,主要更新一些内核相关的内容(瞬间变得高大上了有没有~lol)。

且看看我这会儿能坚持多久吧。

对了,坚持用 FeedBurner 订阅的用户,跳出来冒个泡呗…… 估计已经没有多少了吧?

Fix Razer Orochi Re-connect Issue

| Comments

Issue

雷蛇八歧大蛇 2013 版鼠标,短时间内不用自动休眠,系统中蓝牙连接断(预期行为),鼠标从休眠恢复后系统中的蓝牙连接却无法自动重连,只能将鼠标置为配对模式,通过系统中的蓝牙工具手工连接。

Environment

1
2
3
4
5
Razer Orochi 蓝牙鼠标
Gentoo + KDE
kernel 3.10 ~ 3.16
bluez 4.101 ~ 5.23
bluedevil 1.3.2 ~ 2.0_rc1

Solution

Gentoo KDE 下折腾 OpenConnect

| Comments

最近呢,推上某奸商推出了 AnyConnect 套餐,这对广大翻墙群众来说绝对是个利好消息啊,可以自动配路由的东东。我毫不犹豫去把自己的绝版廉价套餐换成了 100 元的套餐,然后开始折腾 AnyConnect 配置。

iOS 上配置十分简单,找到 Cisco AnyConnect 这个 App 就搞定。Linux 下要把它配置得很舒服,着实花了一番功夫。

公司 VPN 用的也是 AnyConnect,我就继续用着公司的客户端(Cisco AnyConnect Secure Mobility Client), 直到今天早上我修复了笔记本上的无线网,机器有了两个 IP 为止。有了两个 IP 的 AnyConnect service 居然 segfault 了,看了一下 debug 信息,我觉得对这种闭源工具我还是别折腾了。直接换开源方案 OpenConnect.

Portage 里搜了一下,openconnect 有两个包,networkmanager-openconnect 有 libkeyring-gnome 依赖,我现在是个有洁癖的 KDE 党,果断不能装啊,于是用第二个, openconnect 命令行版…… 不行,不能这么罗里八嗦,反正最后折腾结果如下:

WizNote: 终于找到比较干净的 Qt 的笔记软件

| Comments

Linux 上记笔记,向来很纠结。以前用过一段时间 Zim,后来变成了 KDE 党,只好果断抛弃了。找到 BasKet,功能还是很强大的,可是格式一团糟。无法以 Plain Text 存储文字,拷贝来拷贝去的时候经常格式混乱。也尝试在 Web 端存东西,结果还是发现不习惯,而且离线无法访问。

然后今天工作的时候有同事提到客户报告鄙厂产品的问题,碰到了故障(当然在内核组大牛们的努力下该问题已经解决了),我就好奇去看了一下报告者信息,然后就发现了这个笔记软件:Wiz. 看起来还挺 Geek 的,有手机端有 Linux 端有 Mac 端。于是去 gentoo-zh 的 Overlay 找了一圈(是的,5 年过去了我还在 Gentoo 的不归路上慢慢走着),找到了

看起来样子还不错,还比较清爽,也可以格式化为 Plain Text。功能上比 BasKet 少一些,但是用着还是挺舒服的。

最不能忍受的问题是没有任务栏图标,于是接着搜。

找到了一年前某人的一个没有 Merge 的 Pull Request,然后稍微修改了一下补丁,打到源代码里,发现可以用了。

修改后的 ebuild 请猛戳

最后,有更靠谱的笔记软件请推荐。

MCE 的一些零散记录

| Comments

以前也测过 MCE 相关的东西,但是惭愧,一直不清不楚。这两天稍微整理了一下相关知识,感觉见识还是很浅,记些零碎的东西在这,权当博客复活第一篇。如有理解错误,欢迎指出。

CPU 检测到硬件错误时,内核会报 Machine-check,根据硬件错误是否可以修正(CE, Correctable Error; UCE/UE, Un-correctable Error),内核做出不同反应。CE 的话内核只把相关信息写到一个字符设备 /dev/mcelog 中,UE 的话是会在记录相关信息之余,还会做出不同处理,比如中断当前遇错的应用程序,或者 Panic. [TODO: 这块相关的内核代码还得再看几遍]

/dev/mcelog 中的信息可以用用户空间工具 mcelog 来读取。mcelog 比较有意思的一个参数是 --dmi,可以尝试解析 ADDR 字段以获取诸如内存出厂信息,DIMM 位置等有用的信息,可是很遗憾在实际环境中这些 DIMM 信息基本上都是错的。 (mcelog 的 man page 说了,不要怪 Linux,得怪那稀奇古怪的主板厂商……)

man page 还有一个地方提到:

The kernel prefers old messages over new. If the log buffer overflows only old ones will be kept.

Resolved: 如何更新 Git 补丁

| Comments

使用 git 制作补丁时,经常发现补丁需要修改。如果只是最后一次 commit 需要修改,那就好办,用下面的方法就可以搞定:

1
2
3
4
git reset HEAD^
# edit edit edit
git commit -a -s -c ORIG_HEAD
git format-patch --subject-prefix="PATCH v2"

但是如果是一系列补丁中的中间几个补丁需要修改,该怎么办呢?

笨办法已经被删掉>.<

CM7.1 on DEFY

| Comments

CyanogenMod, the community Android mod, is officially supporting Motorlora Defy in the latest release CM7.1. Now the system seems running well on my device.

Why I want CM7.1 on my machine:

  • try something new
  • I need openvpn
  • 2.3.4 ROM for ME525+ not working well on my device :-(

I followed most of the instructions from the post in CM forum, except that I didn't install gapps-gb-20110828. For some reason, Android Market from 20110828 package continously got crashed once I opened the application. I tried with gapps-gb-20110613 and Android Market worked as normal, so I recommend using this version of gapps if you ever encountered similar issue like me.