perf stat
显示了一些有趣的统计数据,这些数据可以通过检查硬件和软件计数器收集到。在我的研究中,我找不到任何关于perf stat
中什么算是上下文切换的可靠信息。尽管我很努力,我还是无法完全理解内核代码。假设我的ib网络应用程序在事件模式下调用阻塞read
系统调用2000次,perf stat
计数1,241个上下文切换。上下文切换指的是计划入进程或计划出进程,还是两者都指?
__schedule()
函数(kernel/sched/core.c
)将switch_count
计数器加1,每当prev != next
。perf stats
的上下文切换似乎包括非自愿切换和自愿切换。
在我看来,只有当当前上下文运行调度代码并增加task_struct
中的nvcsw
和nivcsw
计数器时,才会计算取消调度事件。
perf stat -- my_application
输出:
1,241 context-switches
同时,如果我只计算sched:sched_switch
事件,输出将接近预期的数字。
perf stat -e sched:sched_switch -- my_application
的输出:
2,168 sched:sched_switch
context-switches
和sched_switch
-事件有区别吗?
我认为只有当一个不同的任务实际运行在一个正在运行你的一个线程的核心上时,你才能得到context-switches
的计数。如果read()
阻塞,但在任何其他任务的用户空间代码在核心上运行之前恢复,则可能不会计数。
仅仅为了系统调用而进入内核显然是不算数的;对于我来说,perf stat ls
只计算较大目录中的一个上下文切换,如果我将ls
用于较小的目录(如/
),则为零。我得到了更高的计数,比如我最近没有访问过的目录的递归ls
的711
。因此,它花费了大量时间等待I/O,并且可能运行下半段中断处理程序。
计数可以是奇数,这意味着它不会分别计算取消调度和重新调度;由于我正在查看最终退出的单线程进程的计数,如果它同时计数两个进程,则计数必须是偶数。
我期望计数完成时,schedule()
决定current
应该改变指向一个新的任务,而不是这个。(current
是Linux内核的每核变量,指向当前任务的task_struct
,例如用户空间线程。)因此,每当进程中的线程发生这种情况时,您将获得1个计数。
的确,OP帮助我们找到了源代码;在__schedule
和kernel/sched/core.c
中。例如在Linux 6.1
static void __sched notrace __schedule(unsigned int sched_mode)
{
struct task_struct *prev, *next;
unsigned long *switch_count;
// and some other declarations I omitted
...
cpu = smp_processor_id();
rq = cpu_rq(cpu); // stands for run queue
prev = rq->curr;
...
switch_count = &prev->nivcsw; // either Num InVoluntary CSWs I think
...
if (!(sched_mode & SM_MASK_PREEMPT) && prev_state) {
...
switch_count = &prev->nvcsw; // or Num Voluntary CSWs
}
next = pick_next_task(rq, prev, &rf);
...
if (likely(prev != next)) {
...
++*switch_count; //// INCREMENT THE SELECTED COUNTER
...
trace_sched_switch(sched_mode & SM_MASK_PREEMPT, prev, next, prev_state);
// then make some function calls to actually do the context switch
...
}
我猜context-switches
perf事件汇总了线程的非自愿和自愿切换。(假设这就是nv
和niv
的含义)