CSDN开源夏令营 - 第六周工作总结
这一周是参加夏令营以来收获最大的一周了,因为终于找到了实现“set trace-buffer-size”的关键所在,并且成功实现了这个feature的simple mode(重新分配Ring Buffer后,不进行数据迁移,直接舍弃),而进行数据迁移的我把它叫做normal mode,这个之后有时间了再做。下面从Ring Buffer开始说起,穿插一些在整个代码实现过程中需要注意的点,以及KGTP中关于Linux内核需要学习的东西。
1. KGTP中的Ring Buffer
嗯,是不是又想起了unified trace ring buffer之争,KGTP中的Ring Buffer实现也有三种策略,上周总结提到过,我们再简单回顾一下。
GTP_FRAME_SIMPLE,作为调试使用,直接使用vmalloc分配一块内存区域;GTP_FTRACE_RING_BUFFER,使用Ftrace提供的Ring Buffer的实现;GTP_RB则是KGTP自行实现的一套Ring Buffer,作为默认选项。
具体实现可以参考,gtp_rb.c,ring_buffer.c和ring_buffer.h,由于GTP_FRAME_SIMPLE较简单,因此直接在gtp.c中实现了相关操作。
2. GDBRSP再回顾
GDBRSP从一开始就成为一种阅读代码的障碍,而到现在则是阅读代码的一种线索了,我们再看看这个数据流,
GDB ——> GDBRSP ——> SYSFS ——> KGTP ——> Kernel。GDB的任何指令都需要被转换成Packet,通过RSP协议发送到remote(KGTP中是一个文件,即gtp),然后由stub解析,之后完成相应的trace工作,把trace frame存在Ring Buffer中,等待GDB通过tfind类指令查看。
这里最核心的一个函数则是gtp.c中的gtp_write,它作为一个driver function,完成包解析的工作,并调用相应的handler进行具体的处理。
关于GDBRSP一个比较好的参考在[1],网上有个中文翻译[2]。官方的文档也可以看[3]。另外,为了看懂KGTP的代码,对GDB的包格式也要有一个清晰的了解,参考以下链接,Packets[4],Stop Reply Packets[5],General Query Packets[6],Tracepoint Packets[7],Trace Experiments[8]。
通过Google搜索,发现除了官方文档,相关的post不太多,因此老实看官方文档就好了。
3. Linux内核中的内存分配
想搞懂KGTP中的Ring Buffer是如何被分配的,则需要对Linux Kernel的内存分配有一定的掌握,让我们看一下核心的代码:
#if defined(GTP_FTRACE_RING_BUFFER) \
&& (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,39)) \
&& !defined(GTP_SELF_RING_BUFFER)
if (gtp_frame && gtp_circular_is_changed) {
ring_buffer_free(gtp_frame);
gtp_frame = NULL;
}
gtp_circular_is_changed = 0;
#endif
#ifdef GTP_RB
if (GTP_RB_PAGE_IS_EMPTY) {
if (gtp_rb_page_alloc(gtp_trace_buffer_size) != 0) {
ret = -ENOMEM;
goto out;
}
#endif
#if defined(GTP_FRAME_SIMPLE) || defined(GTP_FTRACE_RING_BUFFER)
if (!gtp_frame) {
#ifdef GTP_FRAME_SIMPLE
gtp_frame = vmalloc(gtp_trace_buffer_size);
#endif
#ifdef GTP_FTRACE_RING_BUFFER
gtp_frame = ring_buffer_alloc(gtp_trace_buffer_size,
gtp_circular ? RB_FL_OVERWRITE
: 0);
#endif
if (!gtp_frame) {
ret = -ENOMEM;
goto out;
}
#endif
跟踪下去,我们需要了解以下函数:
kmalloc/kfree
vmalloc/vfree
kzalloc
kcalloc
不熟的话,自行查阅资料吧,这篇文章[9]可以简单参考。
4. Linux内核中的工作队列
KGTP开启一个内核线程gtpd,来搞定所有的相关工作,看这里:
gtp_wq = create_singlethread_workqueue("gtpd");
if (gtp_wq == NULL)
goto out;
{
struct task_struct *p;
/* Get the task of "gtpd". */
gtpd_task = NULL;
for_each_process (p) {
if (strcmp(p->comm, "gtpd") == 0) {
if (gtpd_task != NULL)
printk(KERN_WARNING "KGTP: system have more than one gtpd.\n");
gtpd_task = p;
}
}
if (gtpd_task == NULL) {
printk(KERN_WARNING "KGTP: cannot get gtpd task.\n");
goto out;
}
gtp_current_pid = gtpd_task->pid;
}
这里比较好玩的是for_each_process这个宏,大家可以看下Linux源码。另外,create_singlethread_workqueue值得深入研究,一篇不错的参考文章[10]。
5. Linux内核中的等待队列
上面说到了work queue,那么如何通知它有哪些工作需要做呢?通过wait queue实现,看代码:
static DECLARE_WAIT_QUEUE_HEAD(gtpframe_wq);
static void
gtp_wq_add_work(unsigned long data)
{
/* Same with prev function, queue_work will wake up sometimes. */
queue_work(gtp_wq, (struct work_struct *)data);
}
具体Tracepoint的信息的收集,需要理解一个核心的数据结构gtp_entry,
struct gtp_entry {
union gtp_entry_u {
#ifdef CONFIG_KPROBES
/* For gtp_entry_kprobe. */
struct gtp_kp {
struct kretprobe kpret;
struct tasklet_struct stop_tasklet;
struct work_struct stop_work;
} kp;
#endif
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,9,0)) && defined CONFIG_UPROBES
/* For gtp_entry_uprobe. */
struct gtp_up {
struct inode *inode;
loff_t offset;
struct uprobe_consumer uc;
} up;
#endif
/* For gtp_entry_watch_static and gtp_entry_watch_dynamic. */
struct {
int type;
int size;
} watch;
} u;
unsigned long flags;
enum gtp_entry_type type;
ULONGEST num;
ULONGEST addr;
/* The actions that set $current help tracepoint get right regs. */
struct action *get_regs;
/* The actions for the condition. */
struct action *cond;
struct list_head action_list;
int step;
struct list_head step_action_list;
atomic_t current_pass;
struct gtpsrc *printk_str;
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30))
/* This is to enable and disable an tracepoint. */
struct tasklet_struct enable_tasklet;
struct work_struct enable_work;
struct tasklet_struct disable_tasklet;
struct work_struct disable_work;
#endif
enum gtp_stop_type reason;
struct gtp_entry *next;
/* Pid for this tracepoint.
KGTP will use this pid to select Kprobe or Uprobe.
And use this pid to get the inode for Uprobe if need.
In default, this pid will bet set to gtp_current_pid. */
pid_t pid;
int disable;
ULONGEST pass;
struct gtpsrc *src;
/* Sometime, it will not same with action
because action will be deleted. */
struct gtpsrc *action_cmd;
};
这里又涉及到了tasklet,代码中的体现,
tasklet_init(&tpe->u.kp.stop_tasklet, gtp_wq_add_work,
(unsigned long)&tpe->u.kp.stop_work);
if (enable && tpe->disable)
tasklet_schedule(>s->tpe->enable_tasklet);
else if (!enable && !tpe->disable)
tasklet_schedule(>s->tpe->disable_tasklet);
关于tasklet的东西我还没有完全想清楚,接下来需要仔细研究一下。
6. set trace-buffer-size的代码实现
目前实现了simple mode,代码就不贴了,大家直接看这里的pull request[11]或者这个commit[12]。接下来时间允许的话,需要实现normal mode,即考虑trace frame数据的迁移,另外KGTP中很多核心的代码,比如涉及到SMP,内核锁,原子操作,Kprobe,Uprobe等等,都需要继续学习。
Fighting!!!
7. 参考链接
[1] http://www.huihoo.org/mirrors/pub/embed/document/debugger/ew_GDB_RSP.pdf
[2] http://blog.csdn.net/hmsiwtv/article/details/8759129
[3] https://sourceware.org/gdb/current/onlinedocs/gdb/Remote-Protocol.html
[4] https://sourceware.org/gdb/current/onlinedocs/gdb/Packets.html
[5] https://sourceware.org/gdb/current/onlinedocs/gdb/Stop-Reply-Packets.html#Stop-Reply-Packets
[6] https://sourceware.org/gdb/current/onlinedocs/gdb/General-Query-Packets.html
[7] https://sourceware.org/gdb/current/onlinedocs/gdb/Tracepoint-Packets.html
[8] https://sourceware.org/gdb/current/onlinedocs/gdb/Starting-and-Stopping-Trace-Experiments.html
[9] http://www.cnblogs.com/dubingsky/archive/2010/04/20/1716158.html
[10] http://bgutech.blog.163.com/blog/static/18261124320116181119889/
[11] https://github.com/teawater/kgtp/pull/36
[12] https://code.csdn.net/Calmdownba/kgtp/commit/51bdefa458678b8d9b679d8188149f4d663f4415