Linux thread scheduling strategy

There are time-sharing scheduling, time-slice round-robin scheduling and first-in-first-out scheduling in the system

Learning this is mainly to solve the delay between several instructions within 1-2ms in linux multithreading;
1. For example, it has been processed before: send a can command to a board, and then need to send a movement command to another module, and the interval between the two commands should not exceed 4ms;
2. For example, there is another one now: send a serial port rtu command (blocked) to one module, then send a serial port rtu command (blocked) to another module, and then send a can move command to a board, and the three The interval between them is fixed, and the deviation does not exceed 2ms;

The default scheduling method of linux system

For the Linux x86 platform, CFS is generally used: completely fair scheduling algorithm. The reason why it is called completely fair is that the operating system performs dynamic calculations based on the ratio of the CPU occupied by each thread. The operating system hopes that each process can use the CPU resource on an average basis.

When we create a thread, the default is the scheduling algorithm SCHED_OTHER, and the default priority is 0. (I have verified this on multiple platforms and boards)

The Linux system also supports two real-time scheduling strategies:

  1. SCHED_FIFO: Scheduling according to the priority of the process, once the CPU is preempted, it will continue to run until it voluntarily gives up or is preempted by a higher priority process;
  2. SCHED_RR: On the basis of SCHED_FIFO, the concept of time slice is added. When a process seizes the CPU and runs for a certain period of time, the scheduler will put the process in the CPU, at the end of the current priority process queue, and then select another process with the same priority to execute;

Linux thread priority

https://zhuanlan.zhihu.com/p/387806696
Linux thread priority
This picture shows the priority in the kernel, divided into two sections.

The value 0-99 in front is a real-time task, and the value 100-139 in the back is a normal task.

The lower the value, the higher the priority of the task. The above are the priorities from the kernel point of view.
However, the kernel does not directly use the value set by the application layer, but after a certain calculation, the priority value (0 ~ 139) used in the kernel is obtained.

For real-time tasks

When we create a thread, we can set the priority value (0 ~ 99) in the following way:

struct sched_param param;
param.__sched_priority = xxx;

When the thread creation function enters the kernel level, the kernel calculates the real priority value through the following formula:

kernel priority = 100 – 1 – param.__sched_priority

The opposite of the kernel angle!

SCHED_FIFO : 0-99
SCHED_RR: 0-99

For common tasks

Adjusting the priority of ordinary tasks is achieved through the nice value. There is also a formula in the kernel to convert the nice value passed in from the application layer into a priority value from the perspective of the kernel:

kernel prifoity = 100 + 20 + nice

The legal value of nice is: -20 ~ 19.
Therefore, from the perspective of the application layer, the smaller the priority value of the transmission person, the higher the priority of the thread; the larger the value, the lower the priority.
It is identical to the kernel angle!

Test Code Description

// filename: test.c
#define _GNU_SOURCE
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sched.h>
#include <pthread.h>

// Used to print the current thread information: what is the scheduling strategy? What is the priority?
void get_thread_info(const int thread_index)
{<!-- -->
    int policy;
    struct sched_param param;

    printf("\\
====> thread_index = %d \\
", thread_index);

    pthread_getschedparam(pthread_self(), &policy, &param);
    if (SCHED_OTHER == policy)
        printf("thread_index %d: SCHED_OTHER \\
", thread_index);
    else if (SCHED_FIFO == policy)
        printf("thread_index %d: SCHED_FIFO \\
", thread_index);
    else if (SCHED_RR == policy)
        printf("thread_index %d: SCHED_RR \\
", thread_index);

    printf("thread_index %d: priority = %d \\
", thread_index, param.sched_priority);
}

// thread function,
void *thread_routine(void *args)
{<!-- -->

\t\t
    // The parameter is: thread index number. Four threads, indexed from 1 to 4, are used in printing messages.
    int thread_index = *(int *)args;
    
    // In order to ensure that all threads are created, let the thread sleep for 1 second.
    sleep(1);

    // Print thread-related information: scheduling policy, priority.
    get_thread_info(thread_index);

    long num = 0;
    for (int i = 0; i < 20; i ++ )
    {<!-- -->
        for (int j = 0; j < 5000000; j++ )
        {<!-- -->
            // Meaningless, purely to simulate CPU-intensive calculations.
            float f1 = ((i + 1) * 345.45) * 12.3 * 45.6 / 78.9 / ((j + 1) * 4567.89);
            float f2 = (i + 1) * 12.3 * 45.6 / 78.9 * (j + 1);
            float f3 = f1 / f2;
            
        }usleep(100000);
        
        // Print count information, in order to see that a thread is executing
        printf("thread_index %d: num = %ld \\
", thread_index, num ++ );
    }
    
    // end of thread execution
    printf("thread_index %d: exit \\
", thread_index);
    return 0;
}

int main(void)
{<!-- -->
    // Create a total of four threads: 0 and 1-real-time threads, 2 and 3-normal threads (non-real-time)
    int thread_num = 4;
    
    // The assigned thread index number will be passed to the thread parameter
    int index[4] = {<!-- -->1, 2, 3, 4};

    // Used to save the id numbers of 4 threads
    pthread_t ppid[4];
    
    // Used to set the properties of 2 real-time threads: scheduling policy and priority
    pthread_attr_t attr[2];
    struct sched_param param[2];

    // Real-time thread, must be created by root user
    if (0 != getuid())
    {<!-- -->
        printf("Please run as root \\
");
        //exit(0);
    }

    // create 4 threads
    for (int i = 0; i <thread_num; i ++ )
    {<!-- -->
\t 
cpu_set_t mask;
int cpus = sysconf(_SC_NPROCESSORS_CONF);
CPU_ZERO( &mask);
CPU_SET(0, & mask);
if (pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask) < 0)
{<!-- -->
printf("set thread affinity failed! \\
");
}
        if (i <= 1) // the first 2 create real-time threads
        {<!-- -->
            // Initialize thread properties
            pthread_attr_init( &attr[i]);
            
            // Set the scheduling strategy to: SCHED_FIFO SCHED_RR
            int res = pthread_attr_setschedpolicy( &attr[i], SCHED_FIFO);
            if(res != 0) printf("i =1 or 2 \\
");
            // Set the priority to 51, 52.
            param[i].__sched_priority = 51 + i;
            res = pthread_attr_setschedparam( &attr[i], &param[i]);
            if(res != 0) printf("i =1 or 2 \\
");
            // Set thread attributes: Do not inherit the scheduling policy and priority of the main thread.
            pthread_attr_setinheritsched( &attr[i], PTHREAD_EXPLICIT_SCHED);
            
            // create thread
            pthread_create( &ppid[i], &attr[i],(void *)thread_routine, (void *)&index[i]);
        }
        else // the latter two create normal threads
        {<!-- -->
            pthread_create( &ppid[i], 0, (void *)thread_routine, (void *) &index[i]);
        }
        
    }

    // Wait for 4 threads to finish executing
    for (int i = 0; i < 4; i ++ )
        pthread_join(ppid[i], 0);

    for (int i = 0; i < 2; i ++ )
        pthread_attr_destroy( &attr[i]);
}

Compile:

gcc -o test test.c -lpthread

expected:

The ones with high real-time priority are executed first, and the ones with normal priority are executed later

note:

1. May need to use root privileges
2. If it is a multi-core film, you need to set several threads to run on one core to run as expected, otherwise synchronization may occur

1. The process of RR scheduling and FIFO scheduling is a real-time process, and the process of time-sharing scheduling is a non-real-time process.

2. When the real-time process is ready, if the current cpu is running a non-real-time process, the real-time process will immediately preempt the non-real-time process.

3. Both the RR process and the FIFO process use real-time priority as the weight standard for scheduling, and RR is an extension of FIFO. In FIFO, if the priority of two processes is the same, which one of the two processes with the same priority will be executed is determined by its position in the queue, which leads to some injustice (the priority is the same, why To let you run all the time?), if the scheduling policy of two tasks with the same priority is set to RR, it ensures that the two tasks can be executed in a loop and fairness is guaranteed.

Scheduling strategy

Each thread in the system is associated with a scheduling policy and priority, and the scheduler schedules threads according to the scheduling policy and priority, so as to determine which thread will get CPU time in the next scheduling;

For common scheduling strategies (SCHED_OTHER, SCHED_IDLE, SCHED_BATCH), the priority has no effect, in fact it must be 0, so that the real-time measurement thread can immediately preempt the normal thread;

For real-time scheduling policies (SCHED_FIFO, SCHED_RR), the priority needs to be set to a value from 1 (minimum) to 99 (maximum);

The scheduler maintains a list of threads to be scheduled for each priority. When scheduling is required, the scheduler accesses the non-empty list with the highest priority, and then selects a thread from the head of the list to schedule and run;

The scheduling policy of the thread determines which position in the list a schedulable thread should be placed in;

All scheduling supports preemption. If a high-priority thread is ready to run, it will preempt the currently running thread, which causes the current thread to be re-added to the linked list waiting for scheduling; The order of schedulable threads in a priority list;

Other functions for querying and setting mode and priority

pseudocode:
view policy

int ***class ***::get_thread_policy( pthread_attr_t & amp;attr )
{<!-- -->
     int policy;
     int rs = pthread_attr_getschedpolicy( & amp;attr, & amp;policy );
     if(rs != 0)
     {<!-- -->
         apl_error("[%s] set err! \\
",__func__);
     }
     switch ( policy )
      {<!-- -->
         case SCHED_FIFO:
                  cout << "policy = SCHED_FIFO" << endl;
                 break;
         case SCHED_RR:
                  cout << "policy = SCHED_RR" << endl;
                 break;
         case SCHED_OTHER:
                  cout << "policy = SCHED_OTHER" << endl;
                 break;
         default:
                  cout << "policy = UNKNOWN" << endl;
                 break;
      }
     return policy;
}

The maximum and minimum priority of the pattern before querying

void ***class***::show_thread_priority( pthread_attr_t & amp;attr, int policy )
{<!-- -->
     int priority = sched_get_priority_max( policy );
      cout << "max_priority = " << priority << endl;
      priority = sched_get_priority_min( policy );
      cout << "min_priority = " << priority << endl;
}

query priority

int ***class ***::getThreadPriority( pthread_attr_t & amp;attr )
{<!-- -->
     struct sched_param param;
     int rs = pthread_attr_getschedparam( & amp;attr, & amp;param );
     if(rs != 0)
     {<!-- -->
         apl_error("[%s] set err! \\
",__func__);
     }
     apl_info("[%s] priority:%d \\
" ,__func__ ,param.sched_priority);
     return param.sched_priority;
}

set priority

void ***class***::setThreadPolicy( pthread_attr_t & amp;attr, int policy )
{<!-- -->
     int rs = pthread_attr_setschedpolicy( &attr, policy );
     if(rs != 0)
     {<!-- -->
         apl_error("[%s] set err! \\
",__func__);
     }
     getThreadPolicy( attr );
}

set mode

// Set the thread scheduling strategy to SCHED_FIFO
 int res = pthread_attr_setschedpolicy( & amp;(pUnit->startMethodTaskAttr), SCHED_FIFO);
  if(res != 0) apl_error("[%s] pthread_attr_setschedpolicy set err! \\
",__func__);
// Set thread priority to 99
                  param.sched_priority = 99;
                  res = pthread_attr_setschedparam( &(pUnit->startMethodTaskAttr), &param);

Settings do not inherit
// Set thread attributes: do not inherit the scheduling policy and priority of the main thread
pthread_attr_setinheritsched( & amp;(*Attr), PTHREAD_EXPLICIT_SCHED);

Other

SCHED_FIFO: first in first out scheduling

The priority of the SCHED_FIFO thread must be greater than 0. When it runs, it will preempt the thread of the running common policy (SCHED_OTHER, SCHED_IDLE, SCHED_BATCH); the SCHED_FIFO policy is an algorithm without time slices, and the following rules need to be followed:

1) If a SCHED_FIFO thread is preempted by a high-priority thread, it will be added to the head of the priority waiting list so that it can continue to run when all high-priority threads are blocked;

2) When a blocked SCHED_FIFO thread becomes runnable, it will be added to the tail of the same priority list;

3) If the priority of the thread is changed through the system call, there are different processing methods according to different situations:

a) If the priority is increased, the thread will be added to the end of the corresponding new priority. Therefore, this thread may preempt the currently running thread of the same priority;

b) If the priority has not changed, then the position of the thread in the list remains unchanged;

c) If the priority is lowered, it will be added to the head of the new priority list;

According to POSIX.1-2008, in addition to using pthread_setschedprio(3), changing the strategy or priority by other means will cause the thread to be added to the tail of the corresponding priority list;

4) If the thread calls sched_yield(2), it will be added to the end of the list;

SCHED_FIFO will keep running until it is blocked by an IO request, or is preempted by a higher priority thread, or sched_yield() is called;

SCHED_RR: round-robin scheduling

SCHED_RR is a simple enhancement of SCHED_FIFO. In addition to the total amount of time occupied by the thread, the rules applicable to SCHED_FIFO also apply to SCHED_RR; if the running time of the SCHED_RR thread is greater than or equal to the total time, it will be added to the corresponding priority list tail; if the SCHED_RR thread is preempted, it only runs for the remaining amount of time when it continues to run; the total amount of time can be obtained through the sched_rr_get_interval() function;

SCHED_OTHER: Default Linux time sharing schedule

SCHED_OTHER can only be used for threads with a priority of 0. The SCHED_OTHER policy is a unified standard policy for all threads that do not require real-time scheduling; the scheduler decides which SCHED_OTHER thread to call through the dynamic priority. The dynamic priority is based on the nice value. The nice value As the total amount of time waiting to run but not scheduled to execute increases; this mechanism ensures the fairness of all SCHED_OTHER thread scheduling;

Limit the CPU usage time of real-time threads

If the thread of SCHED_FIFO and SCHED_RR is a non-blocking infinite loop inside, it will always occupy the CPU, so that other threads have no chance to run;

After 2.6.25, there is a new way to limit the running time of real-time threads. You can use RLIMIT_RTTIME to limit the CPU usage time of real-time threads; Linux also provides two proc files to control the CPU time reserved for non-real-time threads;

/proc/sys/kernel/sched_rt_period_us

The value in this file specifies the width value of the total CPU (100%) time, the default value is 1,000,000;

/proc/sys/kernel/sched_rt_runtime_us

sched_rt_runtime_us indicates that all real-time processes can occupy the CPU for the longest time. The default is 1 second. When this time is used up, they must wait for the time indicated by the following parameter sched_rt_period_us (default is 0.95s) before they can be rescheduled.
sched_rt_period_us indicates the next time to schedule the real-time process.
It seems that these two parameters are adjusted and balanced between scheduling real-time processes and non-real-time processes.

The default values of the two files are 1s and 0.95s, which means that every second is regarded as a cycle. In this cycle, the total running time of all real-time processes does not exceed 0.95 seconds, and the remaining at least 0.05 seconds will be reserved for ordinary processes. In other words, the real-time process occupies no more than 95% of the CPU. Before the appearance of these two files, the running time of the real-time process is unlimited. If there is always a real-time process in the TASK_RUNNING state, the normal process will not be able to run all the time. Equivalent to sched_rt_runtime_us equal to sched_rt_period_us.