You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

程序上下文切换原因及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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 03:22:04