A completely decoupled time slice polling framework structure

1. The importance of program framework

Many people, especially beginners, tend to think about writing bit by bit when writing code. They don’t have an overall plan at the beginning, which leads to more and more messy code later and constant bugs.

In the end, the code seems to run without any problems (there may be no problems), but when adding a function, it will waste a lot of time and even cause the entire code to crash.

Therefore, it is very necessary to spend more time on the architectural design of the code at the beginning of a project. After the code structure is determined, you will find that typing the code will be very fast, and you will not be like a headless fly looking for problems during later debugging. Of course, debugging is also a skill.

In the process of learning the real-time operating system, I found that the coupling between the real-time operating system framework and personal business code is very low. You only need to register the business code through a certain interface function and then hand it over to the operating system for hosting. , very convenient.

However, the scheduling of the operating system is too complicated. Here, we use the operating system’s way of thinking to reconstruct the time slice polling framework. To achieve complete decoupling of the framework, users only need to include the header files and do not need to modify the already written library files during use.

2. Program example

Let’s start with a demo. This demo uses a computer to open two threads:One thread simulates the timer interrupt of the microcontroller to generate a time slice polling clock, and the other thread simulates the time slice polling schedule that is always running in the main function. program.

#include <thread>
#include <stdio.h>
#include <windows.h>
#include "timeslice.h"

//Create 5 task objects
TimesilceTaskObj task_1, task_2, task_3, task_4, task_5;

//Specific task function
void task1_hdl()
{
    printf(">> task 1 is running ...\\
");
}

void task2_hdl()
{
    printf(">> task 2 is running ...\\
");
}

void task3_hdl()
{
    printf(">> task 3 is running ...\\
");
}

void task4_hdl()
{
    printf(">> task 4 is running ...\\
");
}

void task5_hdl()
{
    printf(">> task 5 is running ...\\
");
}

//Initialize the task object and add the task to the time slice polling schedule
void task_init()
{
    timeslice_task_init( & amp;task_1, task1_hdl, 1, 10);
    timeslice_task_init( & amp;task_2, task2_hdl, 2, 20);
    timeslice_task_init( & amp;task_3, task3_hdl, 3, 30);
    timeslice_task_init( & amp;task_4, task4_hdl, 4, 40);
    timeslice_task_init( & amp;task_5, task5_hdl, 5, 50);
    timeslice_task_add( & amp;task_1);
    timeslice_task_add( & amp;task_2);
    timeslice_task_add( & amp;task_3);
    timeslice_task_add( & amp;task_4);
    timeslice_task_add( & amp;task_5);
}


//Open two threads to simulate the running process on the microcontroller
void timeslice_exec_thread()
{
    while(true)
    {
        timeslice_exec();
    }
}

void timeslice_tick_thread()
{
    while(true)
    {
        timeslice_tick();
        Sleep(10);
    }
}

int main()
{
    task_init();

    printf(">> task num: %d\\
", timeslice_get_task_num());
    printf(">> task len: %d\\
", timeslice_get_task_timeslice_len( & amp;task_3));

    timeslice_task_del( & amp;task_2);
    printf(">> delet task 2\\
");
    printf(">> task 2 is exist: %d\\
", timeslice_task_isexist( & amp;task_2));

    printf(">> task num: %d\\
", timeslice_get_task_num());

    timeslice_task_del( & amp;task_5);
    printf(">> delet task 5\\
");

    printf(">> task num: %d\\
", timeslice_get_task_num());

    printf(">> task 3 is exist: %d\\
", timeslice_task_isexist( & amp;task_3));
    timeslice_task_add( & amp;task_2);
    printf(">> add task 2\\
");
    printf(">> task 2 is exist: %d\\
", timeslice_task_isexist( & amp;task_2));

    timeslice_task_add( & amp;task_5);
    printf(">> add task 5\\
");

    printf(">> task num: %d\\
", timeslice_get_task_num());

    printf("\\
\\
========timeslice running============\\
");

    std::thread thread_1(timeslice_exec_thread);
    std::thread thread_2(timeslice_tick_thread);

    thread_1.join();
    thread_2.join();


    return 0;
}

The running results are as follows:

As can be seen from the above examples, this framework is very convenient to use. You can even create tasks quickly and add them to the time slice polling framework through just a few simple interfaces without even knowing its principles. It is very easy to use.

3. Time slice polling framework

In fact, this part mainly uses object-oriented thinking, using structures as objects and using structure pointers as parameters to pass. This can save resources and have extremely high operating efficiency.

The most difficult part is the use of intrusive linked lists, which are widely used in some operating system kernels. Here is a reference to the intrusive linked list implementation in RT-Thread real-time operating system.

h file:

#ifndef _TIMESLICE_H
#define _TIMESLICE_H

#include "./list.h"

typedef enum {
    TASK_STOP,
    TASK_RUN
} IsTaskRun;

typedef struct timesilce
{
    unsigned int id;
    void (*task_hdl)(void);
    IsTaskRun is_run;
    unsigned int timer;
    unsigned int timeslice_len;
    ListObj timeslice_task_list;
} TimesilceTaskObj;

void timeslice_exec(void);
void timeslice_tick(void);
void timeslice_task_init(TimesilceTaskObj* obj, void (*task_hdl)(void), unsigned int id, unsigned int timeslice_len);
void timeslice_task_add(TimesilceTaskObj* obj);
void timeslice_task_del(TimesilceTaskObj* obj);
unsigned int timeslice_get_task_timeslice_len(TimesilceTaskObj* obj);
unsigned int timeslice_get_task_num(void);
unsigned char timeslice_task_isexist(TimesilceTaskObj* obj);

#endif

c-file:

#include "./timeslice.h"

static LIST_HEAD(timeslice_task_list);

void timeslice_exec()
{
    ListObj* node;
    TimesilceTaskObj* task;

    list_for_each(node, & timeslice_task_list)
    {

        task = list_entry(node, TimesilceTaskObj, timeslice_task_list);
        if (task->is_run == TASK_RUN)
        {
            task->task_hdl();
            task->is_run = TASK_STOP;
        }
    }
}

void timeslice_tick()
{
    ListObj* node;
    TimesilceTaskObj* task;

    list_for_each(node, & timeslice_task_list)
    {
        task = list_entry(node, TimesilceTaskObj, timeslice_task_list);
        if (task->timer != 0)
        {
            task->timer--;
            if (task->timer == 0)
            {
                task->is_run = TASK_RUN;
                task->timer = task->timeslice_len;
            }
        }
    }
}

unsigned int timeslice_get_task_num()
{
    return list_len( & timeslice_task_list);
}

void timeslice_task_init(TimesilceTaskObj* obj, void (*task_hdl)(void), unsigned int id, unsigned int timeslice_len)
{
    obj->id = id;
    obj->is_run = TASK_STOP;
    obj->task_hdl = task_hdl;
    obj->timer = timeslice_len;
    obj->timeslice_len = timeslice_len;
}

void timeslice_task_add(TimesilceTaskObj* obj)
{
    list_insert_before( & amp;timeslice_task_list, & amp;obj->timeslice_task_list);
}

void timeslice_task_del(TimesilceTaskObj* obj)
{
    if (timeslice_task_isexist(obj))
        list_remove( & amp;obj->timeslice_task_list);
    else
        return;
}


unsigned char timeslice_task_isexist(TimesilceTaskObj* obj)
{
    unsigned char isexist = 0;
    ListObj* node;
    TimesilceTaskObj* task;

    list_for_each(node, & timeslice_task_list)
    {
        task = list_entry(node, TimesilceTaskObj, timeslice_task_list);
        if (obj->id == task->id)
            isexist = 1;
    }

    return isexist;
}

unsigned int timeslice_get_task_timeslice_len(TimesilceTaskObj* obj)
{
    return obj->timeslice_len;
}

4. Bottom-level intrusive doubly linked list

This linked list is widely used in the Linux kernel and is also very classic.

h file:

#ifndef _LIST_H
#define _LIST_H

#define offset_of(type, member) (unsigned long) & amp;((type*)0)->member
#define container_of(ptr, type, member) ((type *)((char *)(ptr) - offset_of(type, member)))

typedef struct list_structure
{
    struct list_structure* next;
    struct list_structure* prev;
} ListObj;

#define LIST_HEAD_INIT(name) { & amp;(name), & amp;(name)}
#define LIST_HEAD(name) ListObj name = LIST_HEAD_INIT(name)

void list_init(ListObj* list);
void list_insert_after(ListObj* list, ListObj* node);
void list_insert_before(ListObj* list, ListObj* node);
void list_remove(ListObj* node);
int list_isempty(const ListObj* list);
unsigned int list_len(const ListObj* list);

#define list_entry(node, type, member) \
    container_of(node, type, member)

#define list_for_each(pos, head) \
    for (pos = (head)->next; pos != (head); pos = pos->next)

#define list_for_each_safe(pos, n, head) \
  for (pos = (head)->next, n = pos->next; pos != (head); \
    pos = n, n = pos->next)

#endif

c-file:

#include "list.h"

void list_init(ListObj* list)
{
    list->next = list->prev = list;
}

void list_insert_after(ListObj* list, ListObj* node)
{
    list->next->prev = node;
    node->next = list->next;

    list->next = node;
    node->prev = list;
}

void list_insert_before(ListObj* list, ListObj* node)
{
    list->prev->next = node;
    node->prev = list->prev;

    list->prev = node;
    node->next = list;
}

void list_remove(ListObj* node)
{
    node->next->prev = node->prev;
    node->prev->next = node->next;

    node->next = node->prev = node;
}

int list_isempty(const ListObj* list)
{
    return list->next == list;
}

unsigned int list_len(const ListObj* list)
{
    unsigned int len = 0;
    const ListObj* p = list;
    while (p->next != list)
    {
        p = p->next;
        len + + ;
    }

    return len;
}

At this point, a brand new, completely decoupled, very convenient and easy-to-use time slice polling framework is completed.