CSDN开源夏令营 - 第五周工作总结
在实现了GDB和KGTP之间的“set trace-buffer-size”命令解析后,
接下来我们需要根据用户传入的buffer size,来调整buffer的大小。按照[1]的提示,具体实现可以在KGTP接到数据包后,分配一套新的ring buffer,之后把现有ring buffer中的数据拷贝到新的ring buffer中,然后释放现有ring buffer。 其中要注意的问题主要是如果现有ring buffer比新ring buffer大,需要丢弃一部分数据,如果被丢弃的数据正在现在tfind的范围内,可能需要一些特殊的处理。 最后如果实在不能在复杂的情况下比如ring buffer中有数据或者tracepoint运行中处理“set trace-buffer-size”,则允许KGTP报错。
因此,首先我们需要了解一下目前KGTP中的ring buffer的工作原理。
1. Ring Buffer的基本原理
首先,让我们来了解一下“环形缓冲区”是如何工作的,[2]是一个不错的参考。FIFO大家都知道,是一个先进先出的队列,而Ring Buffer则是一个环形的FIFO。使用Ring Buffer可以使得读写并发执行,读线程和写线程可以采用“生产者”和“消费者”的模型来访问缓冲区。当只存在一个读,一个写的时候,不需要任何互斥措施,而当有多个读或者写的时候,则需要一定的同步措施,比如锁或者信号量。
下面先给出[2]中一个简单且有效的环形缓冲区的实现,先有一个直观的认识:
template <class _Type>
class CShareQueue
{
public:
CShareQueue();
CShareQueue(unsigned int bufsize);
virtual ~CShareQueue();
_Type pop_front();
bool push_back( _Type item);
//返回容量
unsigned int capacity() { //warning:需要外部数据一致性
return m_capacity;
}
//返回当前个数
unsigned int size() { //warning:需要外部数据一致性
return m_size;
}
//是否满//warning: 需要外部控制数据一致性
bool IsFull() {
return (m_size >= m_capacity);
}
bool IsEmpty() {
return (m_size == 0);
}
protected:
UINT m_head;
UINT m_tail;
UINT m_size;
UINT m_capacity;
_Type *pBuf;
};
template <class _Type>
CShareQueue<_Type>::CShareQueue() : m_head(0), m_tail(0), m_size(0)
{
pBuf = new _Type[512];//默认512
m_capacity = 512;
}
template <class _Type>
CShareQueue<_Type>::CShareQueue(unsigned int bufsize) : m_head(0), m_tail(0)
{
if( bufsize > 512 || bufsize < 1)
{
pBuf = new _Type[512];
m_capacity = 512;
}
else
{
pBuf = new _Type[bufsize];
m_capacity = bufsize;
}
}
template <class _Type>
CShareQueue<_Type>::~CShareQueue()
{
delete[] pBuf;
pBuf = NULL;
m_head = m_tail = m_size = m_capacity = 0;
}
//前面弹出一个元素
template <class _Type>
_Type CShareQueue<_Type>::pop_front()
{
if( IsEmpty() )
{
return NULL;
}
_Type itemtmp;
itemtmp = pBuf[m_head];
m_head = (m_head + 1) % m_capacity;
--m_size;
return itemtmp;
}
//从尾部加入队列
template <class _Type>
bool CShareQueue<_Type>::push_back( _Type item)
{
if ( IsFull() )
{
return FALSE;
}
pBuf[m_tail] = item;
m_tail = (m_tail + 1) % m_capacity;
++m_size;
return TRUE;
}
#endif // !defined(_DALY_CSHAREQUEUE_H_)
环形环形区和队列缓冲区的区别是什么呢?环形缓冲区所有的push和pop操作都是在一个固定 的存储空间内进行。而队列缓冲区在push的时候,可能会分配存储空间用于存储新元素;在pop时,可能会释放废弃元素的存储空间。所以环形方式相比队列方式,少掉了对于缓冲区元素所用存储空间的分配、释放。这是环形缓冲区的一个主要优势。
让我们再看一下Circular Buffer更加科学的定义,参考[3]。
2. Linux Kernel的Ring Buffer
让我们从这篇文章[4]开始学习,看看Linux内核中的Ring Buffer之争。
先来看看,目前的Ring Buffer实现,可能至少分为三种,Ftrace,LTTng和perf,
One of the outcomes from the Collaboration Summit in April was a plan to create a tracing ring buffer implementation that would work for both Ftrace and LTTng. There was also hope that perhaps the separate ring buffer added for perf could be folded in as well, so that the vision of a single ring buffer implementation in the kernel—from the 2008 Kernel Summit and Linux Plumbers Conference—could come to fruition. To that end, Steven Rostedt posted an RFC for a unified ring buffer, but before that conversation could really get going, it was diverted. It seems that Ingo Molnar thinks there are much bigger issues to resolve in the world of Linux tracing.
而在Linux Kernel中,ring buffer如何定义呢?
A ring buffer is a circular data structure that is often used to hold data that is produced and consumed by separate processes without synchronization. For tracing, the data is produced by the kernel outside of any specific process's context, but the consumer is in user space. The kernel needs to hand out pages from the head of the ring buffer to user space for consumption, while ensuring that it doesn't overwrite that data as it writes to the tail of the buffer.
嗯,由Kernel生产的数据在内核空间,而消费者在用户空间,内核需要把ring buffer开头的页面分发给用户空间的消费者,同时要确保向尾部写的数据不会覆盖掉将要分发的数据。
目前,几种ring buffer的实现貌似还是各自为政,在自己的设计体系下工作良好,unified ring buffer的争论也此起彼伏,而核心问题还是ring buffer的效率以及简单易用,如果有一个这样的实现出现,那么unified也不是太困难的事情。
kfifo是Linux Kernel中一个经典的ring buffer的实现,看这篇文章[5],分析了kfifo的实现后,作者仿照其原理自行实现了一个Ring Buffer,且有测试程序。另外一个比较好的参考[6]。
关于Linux Kernel中的Ring Buffer实现,最权威的是阅读源代码和看内核树中的文档,文档在[6],这里还提到了memory barrier,也涉及了spinlock。源代码的话,看<linux/ring_buffer.h>和<linux/ring_buffer.c>,或者是perf和LTTng中的实现。
关于内核跟踪的Ring Buffer实现,强烈推荐阅读[7], “
Linux内核跟踪之ring buffer的实现”。
由于Linux Kernel涉及到大量的并发,需要考虑到各种竞争和同步,还有其分页的内存机制,因此导致了内核中的Ring Buffer实现及其复杂,想要看懂需要对Linux内核有足够的认识。因此,一遍看Ring Buffer的实现,一遍参考Linux内核相关书籍。
3. KGTP中的Ring Buffer
想要知道KGTP的Ring Buffer是如何实现的,看gtp.c可以找到蛛丝马迹,
/* Sepcial config */
#define GTP_RB // GTP_RB is the own implementation ring buffer of KGTP
#ifdef GTP_FRAME_SIMPLE
/* This is a debug option.
This define is for simple frame alloc record, then we can get how many
memory are wasted by FRAME_ALIGN. */
/* #define FRAME_ALLOC_RECORD */
#undef GTP_RB // GTP_FRAME_SIMPLE and GTP_RB are not used at the same time
#endif
#ifdef GTP_FTRACE_RING_BUFFER
#undef GTP_RB // GTP_FTRACE_RING_BUFFER and GTP_RB are not used at the same time
#endif
#ifdef GTP_FTRACE_RING_BUFFER
#ifndef CONFIG_RING_BUFFER
#define CONFIG_RING_BUFFER
#include "ring_buffer.h"
#include "ring_buffer.c"
#define GTP_SELF_RING_BUFFER // use the modified ring buffer from Linux kernel used by Ftrace
#warning Use the ring buffer inside KGTP.
#endif // end for ifndef CONFIG_RING_BUFFER
#endif
/* Sepcial config */
#ifdef GTP_FTRACE_RING_BUFFER
#ifndef GTP_SELF_RING_BUFFER
#include <linux/ring_buffer.h> // use the standard ring buffer provided by Linux kernel
#endif
#endif
/* GTP_FRAME_SIZE must align with FRAME_ALIGN_SIZE if use GTP_FRAME_SIMPLE. */
#define GTP_FRAME_SIZE 5242880 // 5120*1024=5*1024*1024
#if defined(GTP_FRAME_SIMPLE) || defined(GTP_RB)
#define FRAME_ALIGN_SIZE sizeof(unsigned int)
#define FRAME_ALIGN(x) ((x + FRAME_ALIGN_SIZE - 1) \
& (~(FRAME_ALIGN_SIZE - 1)))
#endif // GTP_FRAME_SIMPLE or GTP_RB defined
#ifdef GTP_FRAME_SIMPLE
#define GTP_FRAME_HEAD_SIZE (FID_SIZE + sizeof(char *) + sizeof(ULONGEST))
#define GTP_FRAME_REG_SIZE (FID_SIZE + sizeof(char *) \
+ sizeof(struct pt_regs))
#define GTP_FRAME_MEM_SIZE (FID_SIZE + sizeof(char *) \
+ sizeof(struct gtp_frame_mem))
#define GTP_FRAME_VAR_SIZE (FID_SIZE + sizeof(char *) \
+ sizeof(struct gtp_frame_var))
#endif // end for ifdef GTP_FRAME_SIMPLE
#ifdef GTP_RB
/* The frame head size: FID_HEAD + count id + frame number + pointer to prev frem */
#define GTP_FRAME_HEAD_SIZE (FID_SIZE + sizeof(u64) + sizeof(ULONGEST) + sizeof(void *))
/* The frame head size: FID_PAGE_BEGIN + count id */
#define GTP_FRAME_PAGE_BEGIN_SIZE (FID_SIZE + sizeof(u64))
#endif // end for ifdef GTP_RB
#ifdef GTP_FTRACE_RING_BUFFER
#define GTP_FRAME_HEAD_SIZE (FID_SIZE + sizeof(ULONGEST))
#endif // end for ifdef GTP_FTRACE_RING_BUFFER
#if defined(GTP_FTRACE_RING_BUFFER) || defined(GTP_RB)
#define GTP_FRAME_REG_SIZE (FID_SIZE + sizeof(struct pt_regs))
#define GTP_FRAME_MEM_SIZE (FID_SIZE + sizeof(struct gtp_frame_mem))
#define GTP_FRAME_VAR_SIZE (FID_SIZE + sizeof(struct gtp_frame_var))
#endif // GTP_FTRACE_RING_BUFFER or GTP_RB defined
以及gtp.h中的gtp_trace_s的相关定义,
#ifdef GTP_FRAME_SIMPLE
/* Next part set it to prev part. */
char **next;
#endif
#ifdef GTP_FTRACE_RING_BUFFER
/* NULL means doesn't have head. */
char *next;
#endif
#ifdef GTP_RB
/* rb of current cpu. */
struct gtp_rb_s *next;
u64 id;
#endif
另外,我们还可以在KGTP的源码树中看到
ring_buffer.c,
ring_buffer.h,这是对Linux的标准实现的修改。
perf_event.c,从perf中取出的有关ring buffer的实现。
gtp_rb.c则是KGTP自行实现的一套Ring Buffer。
在调试“set trace-buffer-size”的过程中,我们发现KGTP默认使用自行实现的Ring Buffer,因此接下来会仔细分析gtp_rb.c。
4. 实现set trace-buffer-size的策略
首先我们知道已有trace buffer的大小size,对于用户通过“set trace-buffer-size”设置的新大小new_size,如果new_size >= size,则只需要分配一套新的Ring Buffer后,拷贝数据。而new_size < size的话,需要舍弃一部分数据,这个在研究了GDB的frame和tfind之后再做分析。
5. 参考链接
[1] http://code.csdn.net/os_camp/18/proposals/28?tab=working
[2] http://blog.csdn.net/yuyin86/article/details/7566033
[3] http://en.wikipedia.org/wiki/Circular_buffer
[4] http://lwn.net/Articles/388978/
[5] http://www.cnblogs.com/Anker/p/3481373.html
[6] https://www.kernel.org/doc/Documentation/circular-buffers.txt