关闭

perf_event源码分析(一)——cmd_record

3153人阅读 评论(0) 收藏 举报

cmd_record是perf用户层的一个核心工具,为之后的report, annotate等工具提供profile,监测数据都记录在perf.data中,后面就是进行上层分析,这里对cmd_record()的分析主要是关注perf.data文件内容组成。

parse_events()会根据用户在命令行上的指定来配置监测事件,为每一个事件分配一个perf_evsel结构体,其中记录了事件名称与配置,最后将这个perf_evsel挂在静态全局变量evsel_list的entries链表上。对于每个事件,如果监测x个线程在y个core上运行的信息,就要做x*y个文件描述符,分别对应这个xy个监测信息,所以evsel->fd是xyarray这样类似二维数组结构,讲每个事件的名称、配置记录在perf_trace_event_type events[event_count]数组中,其中event->event_id = evesel->attr.config。

在完成用户指定事件相关操作之后,下面进入__cmd_record()。perf_session_new()函数是关键,它会被record和report分别以写、读方式进行调用,对于record在其中会调用perf_session__create_kernel_maps():(perf_session => machine)

1.        先做一个动态共享目标dso *kernel,kernel->short_name = "[kernel]", kernel->long_name = "[kernel.kallsyms]", kernel->kernel = DSO_TYPE_KERNEL, 读/sys/kernel/notes,将其中的id写入kernel->build_id[20]数组中。最后把这个dso挂到machine->kernel_dsos链表中。<=machine__create_kernel()

2.        从/proc/kallsyms中读出内核映射的起始地址(0),初始化map *machine->vmlinux[2]数组,map->start = start, map->dso = kernel(上面做的那个dso *kernel),此外,在这两个map结构体之后申请一个kmap结构体,这个kmap结构体的kmaps指针指向&machine->kmaps这个map_groups结构体地址。最后将这两个map结构体根据类型(MAP__FUNCTION, MAP__VARIABLE)和映射地址(map->start)挂到machine->kmaps->maps[type]红黑树上。<= __machine__create_kernel_maps()

3.        读/proc/modules文件,得到各个模块名和起始地址,首先从machine->kernel_dsos链表上根据模块名查找是否已经插入,如果是一个新模块名,就创建一个dso结构体,dso->name = module_name, dso->kernel = DSO_TYPE_USER,然后将这个dso挂到machine->kernel_dsos链表上,读/sys/module/module_name/notes/.note.gnu.build-id,记录到dso->build_id[]数组中。最后将这个dso封装成一个map结构体并挂到machine->kmaps.maps[MAP__FUNCTION]红黑树上。

4.        到目录/lib/modules/3.0.0/kernel/下,查找是否有/proc/modules中同名的.ko文件,即将/lib/modules/3.0.0/kernel/目录下所有.ko文件名与machine->kmaps.maps[MAP__FUNCTION]红黑树中map->dso->name做比较,若匹配,就记录这个.ko文件的绝对路径名到dso->long_name。map->end是根据各个映射的相对位置计算出来的prev->end = cur->start - 1。

 

回到__cmd_record(),接下来open_counters()会根据用户指定来配置个事件属性,并且利用这个事件作为sys_perf_event_open系统调用的参数,这是用户层转入内核层的接口,系统调用会返回一个文件描述符,关于这个系统调用,留在以后分析。上面调用系统调用的总次数是线程数乘以处理器数乘以事件数。文件描述符对应的文件占用的是内核空间,为了减少内核空间消耗,需要尽快将监测数据dump到用户空间,这里利用mmap将内核空间数据直接映射到用户地址空间。

perf_session__write_header(),在perf.data文件头部写入数据:

f_header = (struct perf_file_header){
	.magic = PERF_MAGIC,
	.size = sizeof(f_header),
	.attr_size = sizeof(f_attr),
	.attrs = {
		.offset = header->attr_offset,
		.size = evlist->nr_entries * sizeof(f_attr),
         },
	.data = {
		.offset = header->data_offset,
		.size = header->data_size,
	},
	.event_types = {
		.offset = header->event_offset,
		.size = header->event_size,
	},
};


具体内容可以利用hd -x perf.data查看。

perf_event__synthesize_kernel_mmap(process_synthesized_event, session, machine, "_text"),函数kallsyms__parse(filename, &args,find_symbol_cb)打开/proc/kallsyms文件,找到_text段对应的起始地址0xffffffff81000000。这里构造一个mmap_event,event->mmap.filename = "[kernel.kallsyms]_text",event->mmap.pgoff = args.start,event->mmap.start = 0, event->mmap.len = 0xffffffffa003fff。

perf_event__synthesize_modules()会把machine->kmaps.maps[MAP__FUNCTION]红黑树上每个映射文件作为mmap_event,event->mmap.filename = dso->long_name,如"/lib/modules/3.0.0/kernel/drivers/net/e1000e/e1000e.ko",event->start/len也是根据map结构体中数据进行填充。

根据是否进行全系统采样,调用perf_event__synthesize_thread_map()和perf_event__synthesize_threads(),如果只监测某一进程,就打开/proc/pid/status文件,得到执行命令,以及线程组内其他线程pid,据此往perf.data中写入一个comm_event;打开/proc/pid/maps文件,将这个进程使用到的所有可执行文件做成一个个的mmap_event,写入perf.data。如果是全系统采样,就打开/proc目录,对所有进程都进行如上操作。

这样,用户空间的DSO文件映射就记录在perf.data文件中,接下来后期处理如cmd_report()会通过fetch_mapped_event()函数来取得这个进程的动态共享文件中的各个符号。

$ sleep 10000 &
$ cat /proc/18140/maps
00400000-00407000 r-xp 00000000 08:01 1430929				/bin/sleep
00606000-00607000 rw-p 00006000 08:01 1430929				/bin/sleep
02104000-02125000 rw-p 00000000 00:00 0					[heap]
7f22c7726000-7f22c78a0000 r-xp 00000000 08:01 801437		/lib/x86_64-linux-gnu/libc-2.13.so
7f22c78a0000-7f22c7aa0000 ---p 0017a000 08:01 801437		/lib/x86_64-linux-gnu/libc-2.13.so
7f22c7aa0000-7f22c7aa4000 r--p 0017a000 08:01 801437		/lib/x86_64-linux-gnu/libc-2.13.so
7f22c7aa4000-7f22c7aa5000 rw-p 0017e000 08:01 801437		/lib/x86_64-linux-gnu/libc-2.13.so
7f22c7aa5000-7f22c7aaa000 rw-p 00000000 00:00 0
7f22c7aaa000-7f22c7ac9000 r-xp 00000000 08:01 801438		/lib/x86_64-linux-gnu/ld-2.13.so
7f22c7b36000-7f22c7cad000 r--p 00000000 08:01 1832159		/usr/lib/locale/locale-archive
7f22c7cad000-7f22c7cb0000 rw-p 00000000 00:00 0
7f22c7cc7000-7f22c7cc9000 rw-p 00000000 00:00 0
7f22c7cc9000-7f22c7cca000 r--p 0001f000 08:01 801438		/lib/x86_64-linux-gnu/ld-2.13.so
7f22c7cca000-7f22c7ccb000 rw-p 00020000 08:01 801438		/lib/x86_64-linux-gnu/ld-2.13.so
7f22c7ccb000-7f22c7ccc000 rw-p 00000000 00:00 0
7fff2e19d000-7fff2e1be000 rw-p 00000000 00:00 0			[stack]
7fff2e1ff000-7fff2e200000 r-xp 00000000 00:00 0			[vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0		[vsyscall]

$ cat /proc/18140/status


Tips:

利用pmap [pid]可以显示一个进程的内存映射。

 

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:40658次
    • 积分:560
    • 等级:
    • 排名:千里之外
    • 原创:16篇
    • 转载:1篇
    • 译文:0篇
    • 评论:8条
    最新评论