cyclictest源码分析
舒永嘉
2023-12-01
sudo cyclictest -t1 -n -l 10000
这里的参数含义:-t 指定要在cyclictest 这个进程中要运行的线程个数,-n 代表我们使用clock_nanosleep函数进行休眠(稍后会详细介绍),-l代表每个线程中需要迭代的次数。这里指定cyclictest跑一个迭代10000次的线程。
在process_options() Line:1175中:
[cpp] view plain copy
case 't':
num_threads = atoi(optarg); break; //指定线程数
...
case 'n':
use_nanosleep = MODE_CLOCK_NANOSLEEP;break //指定使用clock_nanosleep休眠
...
case 'l':
max_cycles = atoi(optarg);break; //指定线程迭代次数
main中有个for循环num_threads次(Line:1888 , 代表创建num_threads个线程),每个循环中创建一个struct thread_param结构体par(Line:1925),其结构如下
[cpp] view plain copy
struct thread_param {
int prio;
int policy;
int mode;
int timermode;
int signal;
int clock;
unsigned long max_cycles;
struct thread_stat *stats;
int bufmsk;
unsigned long interval;
int cpu;
int node;
int tnum;
};
然后对其各项字段进行赋值(Line:1928 – 1993),其中赋予par->interval=DEFAULT_INTERVAL(代表线程中每次迭代的休眠时间,默认1000,可以通过-i参数修改),随后pthread_create创建一个新线程(Line:1994),并将par作为参数传递进去。这个线程执行timerthread函数(Line:728):
[cpp] view plain copy
void *timerthread(void *param);
在这个函数中,我们首先需要获取当前的事件,这里使用clock_gettime,它提供了纳秒的精确度:
[cpp] view plain copy
clock_gettime(par->clock, &now); //Line : 790
然后我们把这个当前时间now值赋给next,并将next和之前的interval值相加,确定结束休眠的时间(这个时间是期望的休眠结束时间):
[cpp] view plain copy
next = now;
next.tv_sec += interval.tv_sec;
next.tv_nsec += interval.tv_nsec;
tsnorm(&next);
随后程序就进入了max_cycles次迭代,我们之前指定为10000。这里,我们使用clock_nanosleep等待至next(Line:835),然后再计算睡眠结束时的时间,获取新的当前时间now(Line:874),这个时间也是实际的休眠的结束时间:
[cpp] view plain copy
if ((ret = clock_gettime(par->clock, &now))) {
if (ret != EINTR)
warn("clock_getttime() failed. errno: %d\n", errno);
goto out;
}
然后,计算<strong>实际休眠结束时间now</strong>与<strong>期望休眠结束时间</strong>next之间的差值(Line:883):
diff = calcdiff(now, next);
如果这个差值小于或者大于我们设置的最小误差min和最大误差max,则用这个新值修改它们,并将这轮迭代的误差值累加,方便计算平均值(Line:884 – 891):
[cpp] view plain copy
if (diff < stat->min)
stat->min = diff;
if (diff > stat->max) {
stat->max = diff;
if (refresh_on_max)
pthread_cond_signal(&refresh_on_max_cond);
}
stat->avg += (double) diff;
记入这次迭代的误差,方便显示最近一次的误差ACT(Line:907):
[cpp] view plain copy
stat->act = diff;
一个完整的线程迭代如下:
[cpp] view plain copy
while (!shutdown) {
uint64_t diff;
int sigs, ret;
/* Wait for next period */
// par->mode = MODE_CLOCK_NANOSLEEP = 1
switch (par->mode) {
case MODE_CYCLIC: // 0
case MODE_SYS_ITIMER: // 2
if (sigwait(&sigset, &sigs) < 0)
goto out;
break;
case MODE_CLOCK_NANOSLEEP: // 1
if (par->timermode == TIMER_ABSTIME) { //par->timermode = 1 , TIMER_ABSTIME = 0x01
if ((ret = clock_nanosleep(par->clock, TIMER_ABSTIME, &next, NULL))) {
if (ret != EINTR)
warn("clock_nanosleep failed. errno: %d\n", errno);
goto out;
}
} else {
if ((ret = clock_gettime(par->clock, &now))) {
if (ret != EINTR)
warn("clock_gettime() failed: %s", strerror(errno));
goto out;
}
if ((ret = clock_nanosleep(par->clock, TIMER_RELTIME, &interval, NULL))) {
if (ret != EINTR)
warn("clock_nanosleep() failed. errno: %d\n", errno);
goto out;
}
next.tv_sec = now.tv_sec + interval.tv_sec;
next.tv_nsec = now.tv_nsec + interval.tv_nsec;
tsnorm(&next);
}
break;
case MODE_SYS_NANOSLEEP: //3
if ((ret = clock_gettime(par->clock, &now))) {
if (ret != EINTR)
warn("clock_gettime() failed: errno %d\n", errno);
goto out;
}
if (nanosleep(&interval, NULL)) {
if (errno != EINTR)
warn("nanosleep failed. errno: %d\n", errno);
goto out;
}
next.tv_sec = now.tv_sec + interval.tv_sec;
next.tv_nsec = now.tv_nsec + interval.tv_nsec;
tsnorm(&next);
break;
}
if ((ret = clock_gettime(par->clock, &now))) { //获取当前时间,存在now中
if (ret != EINTR)
warn("clock_getttime() failed. errno: %d\n", errno);
goto out;
}
if (use_nsecs) //use_nsecs = 0
diff = calcdiff_ns(now, next); //now 中是下次循环的真值,而next是理论的值,所以两者的差就是延时!延时赋值给diff
else
diff = calcdiff(now, next);//计算now - next的差值
/*
stat->min = 1000000;
stat->max = 0;
stat->avg = 0.0;
*/
if (diff < stat->min)// 假如延时比min 小,将min 改为这个更小的延时值diff
stat->min = diff;
if (diff > stat->max) {// 假如延时比max 大,将max 改为这个更大的延时值diff
stat->max = diff;
if (refresh_on_max) //refresh_on_max = 0
pthread_cond_signal(&refresh_on_max_cond);
}
stat->avg += (double) diff;// 计算新的平均延时
if (duration && (calcdiff(now, stop) >= 0))
shutdown++;
if (!stopped && tracelimit && (diff > tracelimit)) {
stopped++;
tracemark("hit latency threshold (%d > %d)", diff, tracelimit);
tracing(0);
shutdown++;
pthread_mutex_lock(&break_thread_id_lock);
if (break_thread_id == 0)
break_thread_id = stat->tid;
break_thread_value = diff;
pthread_mutex_unlock(&break_thread_id_lock);
}
stat->act = diff; //保存最近一次的时延
if (par->bufmsk) //par->bufmsk = 0
stat->values[stat->cycles & par->bufmsk] = diff;
/* Update the histogram */ // 更新histogram中存的延时统计数据
if (histogram) { //histogram = 0
if (diff >= histogram) { // 假如延时比histogram大,添加一次溢出
stat->hist_overflow++;
if (stat->num_outliers < histogram)
stat->outliers[stat->num_outliers++] = stat->cycles;
}
else // 如果没有溢出,将histogram 中的相应值加1
stat->hist_array[diff]++;
}
stat->cycles++; // 循环加1
next.tv_sec += interval.tv_sec; // 继续计算下次循环的等待的值
next.tv_nsec += interval.tv_nsec;
//par->mode = MODE_CLOCK_NANOSLEEP = 1
if (par->mode == MODE_CYCLIC) { //define MODE_CYCLIC 0
int overrun_count = timer_getoverrun(timer);
next.tv_sec += overrun_count * interval.tv_sec;
next.tv_nsec += overrun_count * interval.tv_nsec;
}
tsnorm(&next);
if (par->max_cycles && par->max_cycles == stat->cycles)
printf("Break from cycle , max_cycles = %d\n",par->max_cycles);
break;
}
随后,程序会在print_stat(Line:1685)中,将上述的误差数据打印出来(Line:2030):
[cpp] view plain copy
if (use_nsecs)
fmt = "T:%2d (%5d) P:%2d I:%ld C:%7lu "
"Min:%7ld Act:%8ld Avg:%8ld Max:%8ld\n";
else
fmt = "T:%2d (%5d) P:%2d I:%ld C:%7lu "
"Min:%7ld Act:%5ld Avg:%5ld Max:%8ld\n";
fprintf(fp, fmt, index, stat->tid, par->prio,
par->interval, stat->cycles, stat->min, stat->act,
stat->cycles ?
(long)(stat->avg/stat->cycles) : 0, stat->max);
总结
通过分析cyclictest源码,可知它是通过计算期望与实际时延的误差来检测rt系统的性能。这里只是抛砖引玉,研究了下单线程模式下的运行,接下来我会继续研究多线程模式下cyclictest的运作。