github代码
在本作业中,您将测量系统调用和上下文切换的成本。测量系统调用的成本相对容易。例如,您可以反复调用一个简单的系统调用(如执行 0 字节读取),并计算所需时间;将时间除以迭代次数,就能估算出系统调用的成本。
您必须考虑的一件事是计时器的精度和准确性。可以使用的典型计时器是 gettimeofday();阅读手册页了解详细信息。您将看到 gettimeofday() 返回 1970 年以来的时间(以微秒为单位);然而,这并不意味着计时器精确到微秒。测量对 gettimeofday() 的连续调用,以了解计时器的精确度;这将告诉您必须运行多少次空系统调用测试迭代才能获得良好的测量结果。如果 gettimeofday() 对您来说不够精确,您可以考虑使用 x86 机器上可用的 rdtsc 指令。
测量上下文切换的成本比较麻烦。lmbench 基准测试的方法是在单个 CPU 上运行两个进程,并在它们之间设置两个 UNIX 管道;管道只是 UNIX 系统中进程相互通信的众多方式之一。第一个进程向第一个管道发出写操作,并等待第二个管道的读操作;当看到第一个进程在等待从第二个管道读取数据时,操作系统会将第一个进程置于阻塞状态,并切换到另一个进程,后者从第一个管道读取数据,然后向第二个管道写操作。当第二个进程再次尝试从第一个管道读取数据时,它就会阻塞,这样来回循环的通信就继续进行。通过测量这样反复通信的成本,lmbench 可以很好地估算出上下文切换的成本。您可以尝试使用管道或其他通信机制(如 UNIX 套接字)在这里重新创建类似的功能。
在拥有多个 CPU 的系统中,测量上下文切换成本是个难题;在这样的系统中,你需要做的是确保上下文切换进程位于同一个处理器上。幸运的是,大多数操作系统都有将进程绑定到特定处理器的调用;例如,在 Linux 系统中,调用 sched_setaffinity() 就是你要找的。通过确保两个进程位于同一处理器上,就能确保衡量操作系统在同一 CPU 上停止一个进程并恢复另一个进程的成本。
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
69
70
71
72
73
| #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#define ITERATIONS 1000
// Measure the cost of a system call
void measure_system_call() {
struct timeval start, end;
gettimeofday(&start, NULL);
for (int i = 0; i < ITERATIONS; i++) {
read(0, NULL, 0); // Example of a simple system call
}
gettimeofday(&end, NULL);
double time_per_call = ((double)(end.tv_sec - start.tv_sec) * 1000000 + (end.tv_usec - start.tv_usec)) / ITERATIONS;
printf("Estimated cost of a system call: %f microseconds\n", time_per_call);
}
// Measure the precision of gettimeofday()
void measure_gettimeofday_precision() {
struct timeval start, end;
gettimeofday(&start, NULL);
for (int i = 0; i < ITERATIONS; i++) {
gettimeofday(&end, NULL);
}
double precision = ((double)(end.tv_sec - start.tv_sec) * 1000000 + (end.tv_usec - start.tv_usec)) / ITERATIONS;
printf("Precision of gettimeofday(): %f microseconds\n", precision);
}
// Measure the cost of a context switch
void measure_context_switch() {
int pipefd[2];
if (pipe(pipefd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
pid_t pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0) {
// Child process
close(pipefd[1]);
struct timeval start, end;
gettimeofday(&start, NULL);
read(pipefd[0], NULL, 0);
gettimeofday(&end, NULL);
double time_diff = (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_usec - start.tv_usec);
printf("Child process context switch time: %f microseconds\n", time_diff);
exit(EXIT_SUCCESS);
} else {
// Parent process
close(pipefd[0]);
struct timeval start, end;
gettimeofday(&start, NULL);
write(pipefd[1], "", 1);
wait(NULL);
gettimeofday(&end, NULL);
double time_diff = (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_usec - start.tv_usec);
printf("Parent process context switch time: %f microseconds\n", time_diff);
}
}
int main() {
measure_system_call();
measure_gettimeofday_precision();
measure_context_switch();
return 0;
}
|
输出结果如下:
Estimated cost of a system call: 0.333000 microseconds
Precision of gettimeofday(): 0.014000 microseconds
Child process context switch time: 1.000000 microseconds
Parent process context switch time: 160.000000 microseconds