Slowing down task using a modified FIFO scheduler

J Mårtensson martensson.jacob at gmail.com
Mon Apr 18 10:06:09 EDT 2022


 Hi, I have been experimenting with the FIFO scheduler and tick rate in
order
to reduce execution speed for tasks; I need to run legacy code that runs on
old
slow hardware at approximately the same speed on new faster hardware while
preserving "realtime-like" properties of the code. The general idea
was to add a delay
during each task, tick to mimic the execution speed of the slower system.

At the end of this mail are my modifications in the kernel, where I for the
FIFO scheduler introduces a delay of 500 microseconds for each scheduling
tick, that is
my intention is to use up about 500 microseconds of the scheduling tick for
the
task. Assuming a tick length of 1000 microseconds we effectively reduce the
execution speed to about 1 - 500/1000 or about 50%.

I have made a small test program to verify that my approach works as
expected
which I run in userspace (delay_test.c):

#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <sched.h>
#include <stdint.h>
#include <sys/time.h>

static inline void timespec_diff(struct timespec *a, struct timespec *b,
    struct timespec *result) {
    result->tv_sec  = a->tv_sec  - b->tv_sec;
    result->tv_nsec = a->tv_nsec - b->tv_nsec;
    if (result->tv_nsec < 0) {
        --result->tv_sec;
        result->tv_nsec += 1000000000L;
    }
}

void main(int argc, char *args[])
{
  struct sched_param param;
  param.sched_priority = 99;
  sched_setscheduler(0,SCHED_FIFO,&param);
  struct timespec start,now,diff;
  uint64_t  sleep_delay = 100L;
  clock_gettime(CLOCK_MONOTONIC, &start);

  printf("Start is %lu.%lu\n",start.tv_sec,start.tv_nsec);
  for(;;)
  {
    usleep(sleep_delay);
    clock_gettime(CLOCK_MONOTONIC, &now);
    timespec_diff(&now,&start,&diff);
    printf("Time is %lus
%luns\n",(uint64_t)diff.tv_sec,(uint64_t)diff.tv_nsec);

  }
}


I compile the above program like this (assuming a 2GHz CPU core that is
evacuated and assigned a dedicated Cgroup) $ gcc ....
and create it like this with Cgroup v1
sudo cgcreate -t $USER:$GRP -a $USER:$GRP -g cpuset,memory,freezer:tes
echo "1" | sudo tee /sys/fs/cgroup/cpuset/test/cpuset.cpus > /dev/null
echo "0" | sudo tee /sys/fs/cgroup/cpuset/test/cpuset.mems > /dev/null
echo "1" | sudo tee /sys/fs/cgroup/cpuset/test/cpuset.cpu_exclusive >
/dev/null

and run it with
sudo cgexec -g memory,freezer,cpuset:test ./a.out

The expected output is something like
Start is 1835.474539419
Time is 0s 200000ns
Time is 0s 300000ns
Time is 0s 500000ns
Time is 0s 1000000ns <-- Here scheduler delay kicks i.
Time is 0s 1100000ns
Time is 0s 1200000ns
...

and so on. Instead, I get this
Start is 1835.474539419
Time is 0s 175382ns
Time is 0s 283956ns
Time is 0s 391289ns
Time is 0s 501217ns <-- Around here scheduler delay should have kicked in.
Time is 0s 610443ns <- Instead roughly the same delay is added.
Time is 0s 721963ns
Time is 0s 835387ns
Time is 0s 948290ns
Time is 0s 1061414ns

Things I have tried so far: Turning off SMT, making sure that I am using
CONFIG_HZ_PERIODIC , Isolating tasks using cgroups and isolcpus, making sure
that the system is running at the correct tick rate and changing the
frequency
in combination with the Governor. Is it something I am missing in regards to
doing this slowdown? Any answers or hints would be appreciated.

My own delay method I have added to the kernel (register_time.c):

static uint64_t rdtsc2(void){
    uint32_t hi, lo;
    __asm__ __volatile__ ("xorl %%eax,%%eax\ncpuid" ::: "%rax",
"%rbx", "%rcx", "%rdx");
    __asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
    return  (((uint64_t)hi)<<32) | ((uint64_t)lo) ; }

uint64_t get_khz(void){
    uint64_t aperf,mperf,khz;
    unsigned long flags;

    local_irq_save(flags);
    rdmsrl(MSR_IA32_APERF,aperf);
    rdmsrl(MSR_IA32_MPERF,mperf);
    local_irq_restore(flags);

    khz = div64_u64((cpu_khz * aperf),mperf);

    return khz;
}

uint64_t nanos_to_ticks(uint64_t nanos){
     return (get_khz() * 1000L * nanos)/NSEC_PER_SEC;
}
 /*
 *Sleeps for the specified amount of ticks.
 * Ticks to sleep can be calculated with nanos_to_tick().
 * Going from ticks to nanoseconds is done using ticks_to_nanos().
 */
uint64_t tick_sleep(uint64_t ticks_to_sleep) {
    uint64_t now;
    uint64_t sleep_until;
    uint64_t delta_ticks;

    preempt_disable();
    now = rdtsc2();
    sleep_until = now + ticks_to_sleep;
    while (now < sleep_until)
    {
        now = rdtsc2();
    }
    delta_ticks = now - sleep_until;
    preempt_enable();
    return delta_ticks;
}

The part where I try to add the delay (rt.c):

static void task_tick_rt(struct rq *rq, struct task_struct *p, int queued) {
  struct sched_rt_entity *rt_se = &p->rt;

  update_curr_rt(rq);
  update_rt_rq_load_avg(rq_clock_pelt(rq), rq, 1);

  watchdog(rq, p);

  /*
   * RR tasks need a special form of timeslice management.
   * FIFO tasks have no timeslices.
   * CONFIG_PWM_FRAMEWORK is used to activate the special FIFO
   */
  #ifdef CONFIG_PWM_FRAMEWORK
  if (p->policy == SCHED_FIFO)
  {
      tick_sleep(nanos_to_ticks(500000));
      return;
  }
  #else
  if (p->policy != SCHED_RR)
  {
      return;
  }
  #endif

  if (--p->rt.time_slice)
      return;

  p->rt.time_slice = sched_rr_timeslice;

  /*
   * Requeue to the end of the queue if we (and all of our ancestors) are
not
   * the only element on the queue
   */
  for_each_sched_rt_entity(rt_se)
  {
      if (rt_se->run_list.prev != rt_se->run_list.next)
      {
          requeue_task_rt(rq, p, 0);
          resched_curr(rq);
          return;
      }
  }
}


Best Regards
Jacob M
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.kernelnewbies.org/pipermail/kernelnewbies/attachments/20220418/e2fb96ab/attachment.html>


More information about the Kernelnewbies mailing list