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]可以显示一个进程的内存映射。