我在具有 Ubuntu-12.04 操作系统、3.18.26 内核的 Core2Duo、2.20GHz 系统中工作。
我对 Linux 内核源代码进行了一些更改。
为了让上下文切换中涉及的所有进程(被调度和取消调度),我在内核中进行了更改(kernel/sched/core.c 并在 context_switch 函数中添加了以下打印语句)。
trace_printk(KERN_INFO
"**$$,context_switch,%d,%llu,%llu,%d,%llu,%llu\n",
(int)(prev->pid),
prev->se.vruntime,
prev->se.sum_exec_runtime,
(int)(next->pid),
next->se.vruntime,
next->se.sum_exec_runtime);`
我在同一个 CPU 内核中运行两个不同的进程 P1(具有 100 个线程 - T0、T1、...、T99)和 P2。P2 已经运行了很长时间,所以它的 vruntime 很高。
在 P1 内部,前 100 个线程被创建,除 T0 之外的所有线程都处于阻塞状态(等待信号量)。
- T0 执行一些任务,然后设置一个持续时间为 2000 纳秒的计时器并自愿释放 CPU。由于没有可用的线程,P2 被调度。
- 2000 纳秒后,定时器超时,它唤醒下一个线程 T1,该线程立即抢占 P2。
- T1 执行一些任务,然后设置一个持续时间为 2000 纳秒的计时器并主动释放 CPU。由于没有可用的线程,P2 被调度。
- 2000 纳秒后,定时器超时,它唤醒下一个线程 T2,该线程立即抢占 P2。
这重复并且线程 T0,T1,...T99 以循环方式执行。
所以,执行顺序如下
T0-P2-T1-P2-T2-P2-T3-......T99-P2-T0-P2.....
我的实验结果表明,
当我将计时器间隔设置为 1800 纳秒时,P2 进程平均为 1450 纳秒。
当我将计时器间隔设置为 2000 纳秒时,P2 进程平均为 1600 纳秒。
当我将计时器间隔设置为 2500 纳秒时,P2 进程的平均时间为 2050 纳秒。
当我将定时器间隔设置为 3000 纳秒时,P2 进程平均为 2600 纳秒。
因此,我得出结论,在我的 Core2Duo 系统中,上下文切换时间约为 350-450ns。我这样说对吗?
另一个观察结果是,当我将计时器间隔设置为 1600 纳秒或 1700 纳秒时,尽管 CPU 空闲,但 P2 进程不会在两个线程之间调度 - 这意味着尽管 P2 已准备好,但 CPU 在大约 1200 -1300 纳秒内空闲排队,准备运行。为什么会这样?
这是我的代码片段:
// Program - P2
int main(int argc, char *argv[])
{
cpu_set_t my_set;
CPU_ZERO(&my_set);
CPU_SET(1, &my_set);
sched_setaffinity(0, sizeof(cpu_set_t), &my_set);
while(1){
// does some task
}
}
// Program - P1
// timer handler awakening next thread
static void handler(int sig, siginfo_t *si, void *uc)
{
thread_no++;
ret = sem_post(&sem[(thread_no)%NUM_THREADS]);
if (ret)
{
printf("Error in Sem Post\n");
}
}
void *threadA(void *data_)
{
int turn = (intptr_t)data_;
cpu_set_t my_set;
CPU_ZERO(&my_set);
CPU_SET(1, &my_set);
sched_setaffinity(0, sizeof(cpu_set_t), &my_set);
while(1)
{
ret = sem_wait(&sem[turn]);
if (ret)
{
printf("Error in Sem Post\n");
}
// does some work here
its.it_value.tv_sec = 0;
its.it_value.tv_nsec = DELAY1;
its.it_interval.tv_sec = 0;
its.it_interval.tv_nsec = 0;
ret = timer_settime(timerid, 0, &its, NULL);
if ( ret < 0 )
perror("timer_settime");
}
}
int main(int argc, char *argv[])
{
sa.sa_flags = SA_RESTART;
sa.sa_sigaction = handler;
sigemptyset(&sa.sa_mask);
err = sigaction(SIG, &sa, NULL);
if (0 != err) {
printf("sigaction failed\n"); }
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = SIG;
sev.sigev_value.sival_ptr = &timerid;
ret = timer_create(CLOCKID, &sev, &timerid);
if ( ret < 0 )
perror("timer_create");
sem_init(&sem[0], 0, 1);
for ( i = 1; i < NUM_THREADS; ++i)
{
sem_init(&sem[i], 0, 0);
}
data=0;
while(data < NUM_THREADS)
{
//create our threads
err = pthread_create(&tid[data], NULL, threadA, (void *)(intptr_t)data);
if(err != 0)
printf("\n can't create thread :[%s]", strerror(err));
data++;
}
}
内核跟踪显示,CPU 空闲,有足够的时间可用于上下文切换 - 线程 Ti 到 P2,仍然没有安排 P2,稍后在 Ti 和 T(i+1) 之间发生上下文切换。 为什么在这种情况下,当计时器持续时间小于 1700 纳秒时,linux CFS 不选择下一个进程进行调度?