问题: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详细的原理,这里讲的比较详细:
跟踪进程调度原理本质:插桩
就是这么朴实无华!
只不过 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);
}
}