systrace跟踪进程切换原理

687 阅读3分钟

systrace-switch.png

问题:systrace上这些线程切换是怎么跟踪的?

systrace、atrace、perfetto 他们之间的关系:

  • systrace 基于Android手机上的可执行文件 atrace

    • 本质上就是个 atrace 数据的可视化工具
    • 同时通过封装atrace命令,集成了一些python工具
  • atrace 又基于 ftrace

    • atrace 直接使用了 ftrace 的 sched_switch 跟踪器,用于跟踪进程调度信息。
    • 同时 atrace 相当于对ftrace做了扩展
      • atrace 在Android源码的一些关键地方插桩打点,生成了各种数据。比如SurfaceFlinger、view绘制体系的关键函数。
      • 并使用了ftrace的内核缓冲区保存这些数据,使用的 ftrace 的底层架构,时间体系等等。
  • perfetto 也可以说是基于 atrace 和 ftrace

    • perfetto 的数据源虽然不限于 atrace 和 ftrace,但其主要功能是这俩数据源提供的

也就是说 systrace、atrace、perfetto 这些工具的进程调度信息都来源于 ftrace 的 sched_switch 跟踪器。

ftrace 跟踪器

ftrace 当前包含多个跟踪器,用于跟踪不同类型的信息,比如进程调度、中断关闭等。可以查看文件 available_tracers 获取内核当前支持的跟踪器列表。在编译内核时,也可以看到内核支持的跟踪器对应的选项。

  • nop跟踪器不会跟踪任何内核活动,将 nop 写入 current_tracer 文件可以删除之前所使用的跟踪器,并清空之前收集到的跟踪信息,即刷新 trace 文件。
  • function跟踪器可以跟踪内核函数的执行情况;可以通过文件 set_ftrace_filter 显示指定要跟踪的函数。
  • function_graph跟踪器可以显示类似 C 源码的函数调用关系图,这样查看起来比较直观一些;可以通过文件 set_grapch_function 显示指定要生成调用流程图的函数。
  • sched_switch跟踪器可以对内核中的进程调度活动进行跟踪。
  • irqsoff跟踪器和 preemptoff 跟踪器分别跟踪关闭中断的代码和禁止进程抢占的代码,并记录关闭的最大时长,preemptirqsoff跟踪器则可以看做它们的组合

对于ftrace跟踪调用栈其原理是:mcount

  • 本质上是一种静态代码插装技术,即在每一个函数入口处通过编译器选项,自动插入对mcount的调用:call_mcount

对于ftrace跟踪进程调度原理是:tracepoint

  • 静态编译进内核,提供一个钩子函数,当tracepoint打开时,会去调用probe函数,通过定义并注册这个probe函数来实现我们的trace功能

关于ftrace详细的原理,这里讲的比较详细:

wenku.baidu.com/view/fa90b1…

跟踪进程调度原理本质:插桩

就是这么朴实无华!

只不过 ftrace 插桩做的高级一些。

高情商的说:静态探针。

首先进程调度(准去的说是线程task调度)的时候,会调用内核的 schedule 函数:

这个函数会让出CPU,并寻找下一个调度的task,同时切换task的上下文(寄存器的堆栈指针等等)。

关键就在 schedule 源码中

schedule 源码中打点,调用了 trace_sched_switch

// https://cs.android.com/android/kernel/superproject/+/common-android-mainline:common/kernel/sched/core.c;l=6710?q=trace_sched_switch&ss=android%2Fkernel%2Fsuperproject
// common/kernel/sched/core.c
static void __sched notrace __schedule(unsigned int sched_mode)
{
	struct task_struct *prev, *next;
	unsigned long *switch_count;
	unsigned long prev_state;
	struct rq_flags rf;
	struct rq *rq;
	int cpu;

	cpu = smp_processor_id();
	rq = cpu_rq(cpu);
	prev = rq->curr;
//....................
	if (likely(prev != next)) {
		rq->nr_switches++;
		/*
		 * RCU users of rcu_dereference(rq->curr) may not see
		 * changes to task_struct made by pick_next_task().
		 */
		RCU_INIT_POINTER(rq->curr, next);
		/*
		 * The membarrier system call requires each architecture
		 * to have a full memory barrier after updating
		 * rq->curr, before returning to user-space.
		 *
		 * Here are the schemes providing that barrier on the
		 * various architectures:
		 * - mm ? switch_mm() : mmdrop() for x86, s390, sparc, PowerPC.
		 *   switch_mm() rely on membarrier_arch_switch_mm() on PowerPC.
		 * - finish_lock_switch() for weakly-ordered
		 *   architectures where spin_unlock is a full barrier,
		 * - switch_to() for arm64 (weakly-ordered, spin_unlock
		 *   is a RELEASE barrier),
		 */
		++*switch_count;

		migrate_disable_switch(rq, prev);
        // PSI内核打点代码,PSI性能统计的代码也是这么的朴实无华!
		psi_sched_switch(prev, next, !task_on_rq_queued(prev));
		//=============== ftrace打点代码 ========================
		trace_sched_switch(sched_mode & SM_MASK_PREEMPT, prev, next, prev_state);

		/* Also unlocks the rq: */
		rq = context_switch(rq, prev, next, &rf);
	} else {
		rq->clock_update_flags &= ~(RQCF_ACT_SKIP|RQCF_REQ_SKIP);

		rq_unpin_lock(rq, &rf);
		__balance_callbacks(rq);
		raw_spin_rq_unlock_irq(rq);
	}
}