程序上下文切换原因及perf记录结果解读咨询
进程通信与上下文切换分析
测试用C程序
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> #include <fcntl.h> #include <string.h> #include <locale.h> #include <errno.h> int main(int argc, char* argv[]) { printf("Hello world (pid:%d)!\n", (int)getpid()); int pipefd[2]; int pipe_ret = pipe(pipefd); if (pipe_ret == -1) { printf("%s\n", strerror_l(errno, uselocale((locale_t)0))); exit(1); } int rc1 = fork(); if (rc1 < 0) { // fork failed; exit fprintf(stderr, "fork failed\n"); exit(1); } else if (rc1 == 0) { // child (new process) printf("Hello, I am child (pid:%d)!\n", (int)getpid()); dup2(pipefd[0], STDIN_FILENO); close(pipefd[1]); int j = 0; while (j < 1) { scanf("%d", &j); printf("I am child %d and I have just read %d from the other child.\n",(int)getpid(), j); } } else { // parent goes down this path (main) printf("Hello, I am parent of %d (pid:%d).\n", rc1, (int)getpid()); int rc2 = fork(); if (rc2 < 0) { // fork failed; exit fprintf(stderr, "fork failed\n"); exit(1); } else if (rc2 == 0) { // child (new process) printf("Hello, I am child (pid:%d)!\n", (int)getpid()); dup2(pipefd[1], STDOUT_FILENO); close(pipefd[0]); for (int i = 0; i < 2; i++) { printf("%d\n", i); sleep(1); } } else { printf("Hello, I am parent of %d (pid:%d).\n", rc2, (int)getpid()); wait(NULL); // Wait for any child process to finish wait(NULL); // Wait for the other child process to finish } } return 0; }
测试命令与输出
1. 记录上下文切换事件
执行命令:
perf record -e cs ./5_8
输出:
Hello world (pid:9460)! Hello, I am parent of 9462 (pid:9460). Hello, I am parent of 9463 (pid:9460). Hello, I am child (pid:9463)! Hello, I am child (pid:9462)! I am child 9462 and I have just read 0 from the other child. I am child 9462 and I have just read 1 from the other child. [ perf record: Woken up 1 times to write data ] [ perf record: Captured and wrote 0.002 MB perf.data (13 samples) ]
2. 查看上下文切换顺序
执行命令:
perf script -F comm,pid,event,time -f
输出:
5_8 9460 3151.197904: cs: 5_8 9460 3151.198436: cs: 5_8 9460 3151.198811: cs: 5_8 9460 3151.199271: cs: 5_8 9460 3151.199720: cs: 5_8 9460 3151.200381: cs: 5_8 9460 3151.201129: cs: 5_8 9463 3151.198436: cs: 5_8 9462 3151.201219: cs: 5_8 9463 3152.201512: cs: 5_8 9460 3152.201765: cs: 5_8 9460 3153.202438: cs: 5_8 9460 3153.203312: cs:
问题解答
1. 父进程(PID9460)多次上下文切换的原因
得先明确:perf记录的cs事件,记的是每次上下文切换后被调度到CPU上的进程PID——也就是说,当CPU从进程A切到B时,记录的是B的ID,代表现在轮到B执行了。所以那些标着PID9460的cs条目,实际是其他进程执行完切回父进程,或者父进程被调度器选中运行的场景,结合源码具体原因如下:
- 程序启动后,父进程先执行初始化(打印信息、创建管道),这个过程中如果时间片耗尽,或者被系统后台进程抢占,就会被切出CPU,之后再被切回来,产生多次
cs记录(对应前7条PID9460的条目)。 - 父进程两次调用
fork()创建子进程,fork()本身会触发内核级操作:复制进程地址空间、更新进程表,这个过程中父进程可能被调度暂停,完成后再恢复执行,也会产生上下文切换。 - 父进程最后调用两次
wait(NULL),进入阻塞状态等待子进程退出。当子进程退出时,内核会唤醒父进程,此时从其他进程切换回父进程,就会产生cs记录(对应后3条PID9460的条目)。
2. 上下文切换解读的正确性判断
逐个分析:
- ① 正确:子进程9463写完0后调用
sleep(1),主动放弃CPU进入睡眠状态,内核会调度其他进程执行,这次切换就发生在sleep()调用之后。 - ② 正确:子进程9462读入0并打印后,再次执行
scanf等待下一个数据,此时管道无内容,进程阻塞并触发上下文切换,让出CPU。 - ③ 正确:9463第一次sleep结束被唤醒后,写完1又调用
sleep(1),再次主动放弃CPU,触发切换。 - ④ 正确:9462读完1后,循环条件不满足,直接终止进程,内核回收其资源,不会再给它分配CPU,自然不需要第二次上下文切换。
内容的提问来源于stack exchange,提问作者Seeker
相关产品推荐
相关产品推荐

