使用Scheme在TeXmacs中画内核代码结构体关系图

本文的代码托管在Github上。

需求

Linux内核的结构体众多,其相互直接的关系也极为复杂。在讲内核源码的时候往往需要作图画出各个结构体之间的关系,非常费时耗力。如果能在TeXmacs中用代码实现自动作图的话,相信会是一个非常有用的功能。

最后实现的效果如图所示:

知识储备

作图相关原语和属性

原语 示例 功能
point (point “0” “0”) 坐标(0,0)处的一个点
line (line (point “0” “0”) (point “0” “1”) (point “1” “1”)) (0,0)->(0,1)->(1,1)的一条折线
cline (cline (point “0” “0”) (point “0” “1”) (point “1” “1”)) (0,0)->(0,1)->(1,1)->(0,0)的一条闭合折线
spline (spline (point “0” “0”) (point “0” “1”) (point “1” “1”)) (0,0)->(0,1)->(1,1)的一条样条曲线
cspline (cspline (point “0” “0”) (point “0” “1”) (point “1” “1”)) (0,0)->(0,1)->(1,1)->(0,0)的一条闭合样条曲线
arc (arc (point “0” “0”) (point “0” “1”) (point “1” “1”)) 过这三点的一条弧
carc (carc (point “0” “0”) (point “0” “1”) (point “1” “1”)) 过这三点的一个圆
text-at (text-at (texmacs-markup) (point “0” “0”)) 这个原语的重要之处在于提供了一种在图片上放置图片的方法,放在其上的图片所处的位置是点(0,0)的右边,其竖直方向上的对称轴正好过点(0,0)
各种属性:
属性 备注
color red, green, blue  
fill-color red, green, blue  
arrow >, », <, o…  

长度的获取及其单位的换算

  • 如何获取:

(box-info (texmacs-markup) "w")

第二个参数的意义:w表示宽度, h表示高度,关于这个函数可以参见代码src/Typeset/Concat/concater.cpp的末尾。

  • 单位的换算: 获取单位换算的方法:输入,按下回车就可以得到。

    1 par = 998400 tmlen
    1 ln = 1066 tmlen
    1 spc = <4740|7109|10663> tmlen
    1 sep = 2133 tmlen
    1 pt = 2125.36 tmlen

    在作图模式中,坐标的宽度单位默认为1cm。

开发

依赖的工具

TeXmacs中的Scheme及其内置的图像库。

开发的方法

可以直接用TeXmacs作为平台开发,不过整个过程会比较痛苦。在TeXmacs中直接可以编辑Scheme文件,编辑之后保存,按下Ctrl-Shift-R后,整个文件就被运行了一次。

设计

由于TeXmacs的文档或者文档片段的实质就是一段Scheme代码,所以需要设计所做图像对应的Scheme代码。初步设计如下:

(struct_graph (tree "task_struct" 
    (tree "signal" (tree "signal_struct" 
                                "cout" 
                                "live" 
                                (tree "shared_pending" (tree "sigpending" 
                                                                          "struct list_head list" 
                                                                          "sigset_t signal")))) 
    (tree "sighand" (tree "sighand_struct" 
                                  "count" 
                                  "action[_NSIG]" 
                                  "siglock" 
                                  "signalfd_wqh"))))

而在TeXmacs,tree可以在一种图形化的树上编辑,极为方便。以上代码可以这样编辑: 以上代码对应的结构体是:

struct task_struct {
    struct signal_struct signal;
    struct sighand_struct sighand;
}
struct signal_struct {
    cout;
    live;
    struct sigpending shared_pending;
}
struct sigpending {
    struct list_head list;
    sigset_t signal;
}
struct sighand_struct {
    count;
    action[_NSIG];
    siglock;
    signalfd_wqh;
}

排版算法

Step 1. 先算出每个结构体的本身的高度和宽度,宽度为(box-info the-body "w"),高度为(box-info the-body "h")

(tree "root" (tree "struct"
    (tree "1" (tree "struct1" "2" "3" "4"))
    (tree "5" (tree "struct2" "6" "7" "8"))
    "9"
)

–>

("root"    (
                (("struct" "1" "5") (width height))
                ("1" ((("struct1" "2" "3" "4") (w1 h1))))
                ("5" ((("struct2" "6" "7" "8") (w2 h2))))
            )
)

Step 2. 再算出其组合总高度和宽度,宽度为(+ struct-width HGAP (max sub-width sub-width ... subwidthn)),高度为(max struct-height (+ sub-height1 VGAP ... VGAP sub-heightn))

("root"    (
                (("struct" "1" "5") (width height) (g_w g_h))
                ("1" ((("struct1" "2" "3" "4") (w1 h1) (g_w1 g_h1))))
                ("5" ((("struct2" "6" "7" "8") (w2 h2) (g_w2 g_h2))))
            )
)

在我的代码中,第一步和第二步实际上是一起进行的。

Step 3. 由根上的结点确定整个画布的大小,再确定作图点的位置

("root"    (
                (("struct" "1" "5") (width height) (g_w g_h) (x y))
                ("1" ((("struct1" "2" "3" "4") (w1 h1) (g_w1 g_h1) (x1 y1))))
                ("5" ((("struct2" "6" "7" "8") (w2 h2) (g_w2 g_h2) (x2 y2))))
            )
)

Step 4. 确定结构体中仍然是结构体的成员的位置,并抽取中其中的连接点

(
 (("struct" "1" "5") (width height) (g_w g_h) (x y))
 ("1" ((("struct1" "2" "3" "4") (w1 h1) (g_w1 g_h1) (x1 y1))))
 ("5" ((("struct2" "6" "7" "8") (w2 h2) (g_w2 g_h2) (x2 y2))))
 )
  • 首先是结构体的位置:sx = x, sy = y + height/2 - (height/2)/(length content)
  • 其次是成员变量的位置: sx = x + width,sy = y + height/2 - (height / (length content)) * ((list-ref)+ 1/2)

Step 5. 从中抽取表格,代码上和上面的类似,just do it recursively.