0%

上下文切换成本测量

要解决的问题

  • 通过什么方式实现上下文的切换,并且方便测量时间。
  • 如何保证在具有多个CPU的系统中,保证上下文切换的进程处于同一个处理器上

上下文切换的方式

利用两个管道,测量在父子进程之间互相切换的时间成本,具体流程见下图

[管道的介绍](Linux管道通信 | SnowDawn’Home)

3

​ 使用双管道在父子进程之间相互切换

  1. 父进程向管道Pi1 写入获取到的开始时的时间,使用gettimeofday()函数
  2. 父进程从Pi2 读取,但是此时的Pi2 中还没有内容写入,所以父进程陷入等待,系统切换到子进程
  3. 子进程从Pi1 中读取开始的时间,并且记录此时的时间,与开始的时间相减,输出切换的时间
  4. 子进程向Pi2 中写入开始时间,此时切换到等待读取的子进程,再次算出一个切换时间

使切换进程保证在同一个处理器上

使用sched_seaffinity()调用实现,该函数的参数比较复杂,这里只是简单的使用,没有详细的研究与介绍。

测量程序代码(标明在注释中)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/time.h>

#define errExit(msg) \
do \
{ \
perror(msg); \
exit(EXIT_FAILURE); \
} while (0)

int main()
{
cpu_set_t set; // cpu_set_t是一个用来存放CPU的集合的结构体
int parentCPU, childCPU; //
parentCPU = 0;
childCPU = 0; //设置父进程和子进程的CPU核心

CPU_ZERO(&set); //将CPU核心集合清零
int pi1[2]; //父进程和子进程的管道
int pi2[2];
char buf1[30], buf2[30]; //父进程和子进程的管道缓冲区
int p1 = pipe(pi1); //创建父进程和子进程的管道
int p2 = pipe(pi2);
long temptime;
struct timeval start, end; //记录时间
int i;
if (p1 < 0 || p2 < 0) //判断管道是否创建成功
errExit("pipe");
int rc = fork(); //创建子进程
for (i = 0; i < 10; ++i) //循环测试十次
{
if (rc < 0) //判断子进程是否创建成功
{
fprintf(stderr, "fork failed");
exit(1);
}
else if (rc == 0)
{
CPU_SET(childCPU, &set); //将子进程的CPU核心设置为childCPU
if (sched_setaffinity(getpid(), sizeof(set), &set) == -1)
errExit("sched_setaffinity"); //设置CPU核心
read(pi1[0], buf1, 25); //读取父进程的数据
gettimeofday(&end, NULL); //获取结束时间
printf("%d\n", end.tv_usec - atol(buf1)); //输出时间差
gettimeofday(&start, NULL); //获取开始时间
sprintf(buf2, "%ld", start.tv_usec); //将开始时间转换为字符串
write(pi2[1], buf2, 25); // 将开始时间写入管道
}
else
{
CPU_SET(parentCPU, &set); //将父进程的CPU核心设置为parentCPU
if (sched_setaffinity(getpid(), sizeof(set), &set) == -1)
errExit("sched_setaffinity"); //设置CPU核心
gettimeofday(&start, NULL); //获取开始时间
sprintf(buf1, "%ld", start.tv_usec); //将开始时间转换为字符串
write(pi1[1], buf1, 25); // 将开始时间写入管道
read(pi2[0], buf2, 25); // 读取子进程的数据
gettimeofday(&end, NULL); //获取结束时间
printf("%d\n", end.tv_usec - atol(buf2)); //输出时间差
}
}
return 0;
}

测试程序的运行结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
90
15
6
7
5
6
5
6
5
6
5
7
5
6
5
6
5
7
5
398

对测试结果的分析

在十次循环的测量中,开始的一次和最后的一次测量时间较长,具体为何会出现这样的情况有以下猜测:

  • 对于刚创建的子进程,还未进入到系统的缓存,进程之间的切换较慢。
  • 对于最后一次测量上下文切换时间,待解决,择日向老师请教。

另,此测试程序得到的结果并不只是上下文切换的时间,还包括对读和写的系统调用,将字符串转化为数字,设置cpu亲和度的调用等时间,根据之前对系统调用的测量,并且忽略其他细小的时间消耗,该系统的上下文切换时间应该在 4 – 5 微秒。