C 的学习一直断断续续的,不过半年前因为 syslog-ng 项目,最近一周因为 lighttpd 的模块改造,不得不狠狠地啃了两次 C 语言,虽然工作以来基本都是使用 shell 和 Python,但还是可以总结一些经验如下:
此外,可以先找现有资料,放狗,使用 “xxx 源码解析” 这样的关键字搜一下,通常会有别人已经写过的一些分析资料。… 如果没有?… 那么恭喜你,你走在了时代的前面… “)
而对于 syslog-ng, lighttpd 这样的服务程序,通常分为两种模式,即多线程模式或 poll() 模式,我一般倾向使用 poll——基本上,我现在也觉得 “thread is a bad idea”,若非万不得已,不推荐使用对线程。很多性能方面的问题,可以利用 Linux 操作系统来补足。
服务进程模式,通常都是先 init,然后解析命令行和配置文件,再进入到主循环,使用 poll/epoll 这样的系统调用(不同的系统具体实现不同),反复处理各种请求。有了这个概念,找到主循环中的 poll 所在,就可以找到读代码一个起点。
高级语言都有一套很方便的基本数据结构,如 Python 中的 Int, String, List, Dict, Tuple, File 等,而 C 语言中,目前似乎没有什么标准,这也是灵活性所必须付出的代价。例如 syslog-ng 使用了 glib 库中的数据结构,而 lighttpd 则自己写了一套 array, keyvalue, string, buffer 以及 splay_tree 等。对于理解程序主体,这些数据结构帮助不大,开始的时候,可以先放在一边,只有大概知道是什么意思,在代码中遇到的时候,可以根据其函数名用高级语言的方法想象一下。
现在的很多 C 程序,都在实现时候模拟了面向对象的方法,具体方法就是在结构体中大量使用函数指针形成回调函数。所以在研究这些数据结构的时候,可以用对象化的思想来帮助理解,并问自己一些问题:
Q: 这些数据结构之间的关系是什么?如何用对象化的组装关系(composite)来理解
Q: 代码中为何要使用这样的数据结构?
Q: 在什么地方、以何种方式使用这些数据结构?
Q: 关于 typedef — 何时创建的实例?
a. gdb 法: 了解一些 gdb 的有用的指令。《C in a Nutshell》中最后一章比较经典和实用,可以参考一下 (Nutshell 系列都不错好像,《Linux in a Nutshell》《Python in a Nutshell》都有很有价值的章节和信息)。
gdb 法的问题是会比较慢,特别是在对指令使用不够纯熟的情况下。我觉得比较适合小范围跟踪,即对整个调用流程有一定认识之后,对一些关注的函数或热点,进行比较细致的跟踪
b. 从整体上把握,则可以采用日志法,即打开程序的调试(debug)模式或详细模式,查看其日志输出。另外,可以参考其 log 方法,使用和代码一致的 log 方式,将自己关心的一些值和运行点输出日志
c. 我自己使用的一种土办法:trace。就是在源代码中每个函数进入的时候插入一个宏,打印它所在的文件和自己的函数名,以及栈的深度(SDP++
),然后在输出的时候有一个对应的宏,做(SDP--
)。
这个修改是在源代码中直接修改并需要重新编译的。其思路倒是和 gprof, gdb 这些抓取符号表并在函数入口处插入一个 _mcount()
函数很类似。只是目前我还不知道如何能够实现,所以还是只能通过这种土办法——说它土,是因为我只是写了一个 Python 脚本(确切地说,针对 syslog-ng 和 lighttpd 分别都做了修改),而它只是基于行来进行分析,找到函数的出口和入口,并不是 gcc 那样的语法分析器,所以很难准确,还要花一些时间在跟踪的时候进行校对。
(不知道 ctags 又如何,但我目前发现,有些以宏的方式定义函数,ctags 还是找不到)
(利用 gprof 当然也是一个办法。配合 gprof2dot.py 和 graphivz 可以生成函数调用关系图,但这个图并不包含顺序,而且只是一个统计值,所以一般只用在性能调试的时候)
(有时间可以研究一下 gprof 和 gdb 的实现,以及 ltrace/strace,也许可以结合这个 trace 的思路做点东西出来)
稍后有时间我会写写这个 trace 方法,以及相应的脚本。trace 出来的效果大致如下:
[Intranet root@oplive-test2 /root/lighttpd-1.4.26.roczhou/src] #python ctrace.lighttpd.py call -l /root/ctrace/ctrace.16 | grep -E '^ [0-9]|^debug' | less debug 2 plugins_call_handle_trigger(srv) debug slot in plugin: mod_proxy 1 stat_cache.c# stat_cache_trigger_cleanup, 741 1 fdevent.c# fdevent_poll, 175 1 fdevent_linux_sysepoll.c# fdevent_linux_sysepoll_poll, 86 debug 2 plugins_call_handle_trigger(srv) debug slot in plugin: mod_proxy 1 stat_cache.c# stat_cache_trigger_cleanup, 741 1 fdevent.c# fdevent_poll, 175 1 fdevent_linux_sysepoll.c# fdevent_linux_sysepoll_poll, 86 1 fdevent.c# fdevent_event_next_fdndx, 233 1 fdevent_linux_sysepoll.c# fdevent_linux_sysepoll_event_next_fdndx, 116 1 fdevent.c# fdevent_event_get_revent, 182 1 fdevent_linux_sysepoll.c# fdevent_linux_sysepoll_event_get_revent, 91 1 fdevent.c# fdevent_event_get_fd, 190 1 fdevent_linux_sysepoll.c# fdevent_linux_sysepoll_event_get_fd, 106 1 fdevent.c# fdevent_get_handler, 198 1 fdevent.c# fdevent_get_context, 211 1 network.c# network_server_handle_fdevent, 29 2 connections.c# connection_accept, 1375 3 connections.c# connections_get_new_connection, 43 4 connections.c# connection_init, 738 4 connections.c# connection_init, 738 4 connections.c# connection_init, 738 4 connections.c# connection_init, 738 4 connections.c# connection_init, 738 4 connections.c# connection_init, 738 4 connections.c# connection_init, 738 ............ 4 log.c# log_error_write, 281 4 log.c# log_error_write, 281 debug 5 plugins_call_handle_uri_raw(srv, con) 4 log.c# log_error_write, 281 4 log.c# log_error_write, 281 debug 5 plugins_call_handle_uri_clean(srv, con) debug slot in plugin: mod_access 5 mod_access.c# mod_access_patch_connection, 89 5 log.c# log_error_write, 281 debug slot in plugin: mod_cache 4 log.c# log_error_write, 281 4 stat_cache.c# hashme, 239 4 log.c# log_error_write, 281 4 stat_cache.c# stat_cache_get_entry, 380 5 stat_cache.c# hashme, 239 5 splaytree.c# splaytree_splay, 64 4 status_counter.c# status_counter_set, 59 5 status_counter.c# status_counter_get_counter, 18 debug slot in plugin: mod_proxy 4 log.c# log_error_write, 281 4 log.c# log_error_write, 281 4 log.c# log_error_write, 281 4 log.c# log_error_write, 281 4 log.c# log_error_write, 281 4 log.c# log_error_write, 281 debug 5 plugins_call_handle_docroot(srv, con) debug slot in plugin: mod_cache
第一列是该函数的栈深度,后面是所在文件和函数名,最后是行号(目前还不十分准确),通过缩进之间的关系,就可以了解函数调用的顺序和相互之间的关系了。这种跟踪方式也有利于我们梳理回调,特别是在关系还没搞清楚和不习惯这种思维方式的时候(我刚开始读 syslog-ng 的时候就是)。