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%.
#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,¶m);
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
and run it with
sudo cgexec -g memory,freezer,cpuset:test ./a.out
The expected output is something like
Time is 0s 300000ns
Time is 0s 1100000ns
Time is 0s 1200000ns
...
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.
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 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.
#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
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,¶m);
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
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 200000nsTime is 0s 300000ns
Time is 0s 500000ns
Time is 0s 1000000ns
<-- Here scheduler delay kicks i.
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 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
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):
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 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;
}
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
_______________________________________________ Kernelnewbies mailing list Kernelnewbies@xxxxxxxxxxxxxxxxx https://lists.kernelnewbies.org/mailman/listinfo/kernelnewbies