3

我有一个程序 P1 有 N (100) 个线程。最初,除了线程 0,所有线程都处于阻塞状态(使用信号量)。

// Program - P1
    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++;
    }
} 

我在程序 P1 中使用timer_create()创建了一个计时器(并且所有线程都使用相同的计时器),将计时器设置为间隔 T。当计时器间隔到期时,调用计时器处理程序将信号发送到下一个线程 i+1 以唤醒。

这是我的程序的工作方式

Step 1: Thread 0 does some work, sets timer ,goes into block state ( releasing CPU )

Step 2: On timer expiration, timer handler called 

Step 3: Thread 1 is awaken, does some work, sets timer , goes into block state ( releasing CPU )

Step 4: On timer expiration, timer handler called 
Step 5: Thread 2 is awaken, does some work, sets timer , goes into block state ( releasing CPU )
:

:

:


Step 2n-1: next thread  Thread n-1 is awaken, does some work, sets timer , goes into block state ( releasing CPU )

Step 2n:  On timer expiration, timer handler called 

Step 2n+1: next thread  Thread 0 is awaken, does some work, sets timer , goes into block state ( releasing CPU )

这很好用,我的所有线程都被计时器处理程序唤醒并以循环顺序运行。

我有另一个程序 P2 连续运行很长时间(所以 vruntime 非常高),甚至在 P1 开始运行并且 P2 与 P1 位于同一个 CPU 内核(所有线程)之前。

// 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
   }
}

因此,当没有正在运行的线程时,P2 应该正在运行。

所以,我的期望是在 Step-1 线程 0 正在释放 CPU,P2 应该被调度并且当计时器到期并且在 Step-3 中下一个线程唤醒时,P2 应该根据 CFS 调度策略立即被抢占(因为唤醒线程的 vruntime 是与 P2 相比非常低)。这意味着,当所有线程都处于阻塞状态、CPU 空闲且下一个线程尚未唤醒时,我希望 P2 被安排在 Step1-Step3 、 Step3-Step5 之间。

为了获得上下文切换中涉及的所有进程,我在内核中进行了更改(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);

这样我就可以获得所有已安排和取消安排的流程详细信息。根据以上信息,我计算了 P2 在每次运行中获得了多少时间才被抢占。

这是我的一些发现,我无法理解为什么?

  1. 在 CPU 频率 =2.2GHz 的情况下,如果我将定时器间隔设置为 1700ns 甚至更少,则 P2 不会在两个线程之间进行调度。为什么即使没有其他进程/线程正在运行并且 CPU 空闲 1700 ns 也没有调度 P2?

  2. CPU 频率 =3.4GHz 时,如果我将定时器间隔设置为 1000ns 甚至更少,则 P2 不会在两个线程之间进行调度。为什么即使没有其他进程正在运行并且 CPU 空闲 1000 ns,P2 也没有被调度?

不同的 CPU 频率和不同的定时器间隔,其他进程 P2 没有被调度?

CPU频率和定时器到期时间之间有什么关系吗?它与上下文切换时间有关吗?

我的最后一个问题 - 如果一个上下文切换正在进行中并且一个新的更高优先级的进程准备好,当前正在运行的上下文切换是否会完成,执行一段时间,然后安排下一个更高优先级的进程,或者它将停止当前的上下文切换并安排下一个更高优先级的进程?

我正在使用 Core-i7 @3.40 GHz,Ubuntu-16.04,cgroups 被禁用,P1 和 P2 在同一个终端中运行。cpupower 用于设置用户频率。

提前致谢。

4

0 回答 0