RT-Thread内核学习笔记

news2024/9/28 17:38:18

文章目录

  • RT-Thread
    • 一、线程
      • 1. 线程定义
      • 2. 线程栈
      • 3. 线程函数 rt_thread_entry()
      • 4. 线程控制块 struct rt_thread
      • 5. 线程初始化 rt_thread_init()
      • 6. 就绪列表
      • 7. 调度器
    • 二、对象容器
      • 1. 对象:所有的数据结构都是对象
      • 2. 容器:每当创建一个对象,就会将这个对象放到容器中。
    • 三、空闲线程与阻塞延时
    • 四、创建线程
      • 创建线程-静态内存
      • 创建线程-动态内存
    • 五、RT-Thread移植到STM32F407
    • 六、消息队列
    • 七、信号量
    • 八、互斥量
    • 九、临界区
    • 十、事件
    • 十一、邮箱
    • 十二、定时器

RT-Thread

一、线程

1. 线程定义

在裸机系统中所有代码都运行在一个main函数中,只能按照顺序运行程序,当遇到中断时才会打断当前程序,用以提高系统的实时性。如果遇到软件延时等情况,CPU只能在那空转,大大浪费了CPU的性能。而多线程是多个独立且无法返回的函数,就好比有多个裸机系统的main函数同时运行。如果把裸机系统比做单车道,那么多线程就是多车道。

2. 线程栈

在学习C语言的时候,里面有一个内存分区模型如下:

  • 代码区:存放函数体的二进制代码

  • 全局区:存放全局变量静态变量,以及常量

  • 栈区:由编译器自动分配释放,存放函数的参数值,局部变量

  • 堆区:用new操作符由程序员分配和释放,若程序员不释放,程序结束时操作系统回收

在裸机系统中,全局变量,局部变量等都放在栈区,而我们为了实现多线程,相当于多个main函数,我们必须将栈区划分成一个个独立的、互不干扰的区域

/* 定义线程栈 */
rt_uint8_t rt_flag1_thread_stack[512];

线程栈其实就是一个数组

3. 线程函数 rt_thread_entry()

线程函数是一个线程实现功能的函数,比如点亮一个LED。一般将其称为入口函数

rt_err_t flag1_thread_entry(void *p_arg)
{
    for(;;)
    {
        //一个线程所需执行的功能的代码
    }
    rt_schedule();//线程手动切换
}

4. 线程控制块 struct rt_thread

线程控制块本质是一个C语言的结构体,里面存放了一个线程的属性,用于控制一个线程。

struct rt_thread// (1)
{
    void *sp; /* 线程栈指针 */
    void *entry; /* 线程入口地址 */
    void *parameter; /* 线程形参 */
    void *stack_addr; /* 线程起始地址 */
    rt_uint32_t stack_size; /* 线程栈大小,单位为字节 */

    rt_list_t tlist; /* 线程链表节点 */
};
typedef struct rt_thread *rt_thread_t;// (2)

5. 线程初始化 rt_thread_init()

当我们定义好线程栈、线程函数、线程控制块后需要将其联系起来,通过**rt_thread__init()**函数

rt_err_t rt_thread_init(struct rt_thread *thread,
						void (*entry)(void *parameter),
						void *parameter,
						void *stack_start,
						rt_uint32_t stack_size)
{
    rt_list_init(&(thread->tlist));

    thread->entry = (void *)entry;
    thread->parameter = parameter;

    thread->stack_addr = stack_start;
    thread->stack_size = stack_size;

    /* 初始化线程栈,并返回线程栈指针 */ )
    thread->sp = (void *)rt_hw_stack_init( thread->entry,
    thread->parameter,
    (void *)((char *)thread->stack_addr +␣thread->stack_size - 4) );

    return RT_EOK;
}

6. 就绪列表

线程创建好后,需要将线程添加到就绪列表,表示线程已经就绪,系统可以随时调度

/* 线程就绪列表 */
rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX];

就绪列表本质是一个数组,大小为RT_THREAD_PRIORITY_MAX,这个宏用于表示最大有多少个优先级。有多少个优先级这个数组就有多少个元素。数值越小优先级越高。每个元素的类型为rt_list_t,定义如下

struct rt_list_node
{
    struct rt_list_node *next; /* 指向后一个节点 */
    struct rt_list_node *prev; /* 指向前一个节点 */
};
typedef struct rt_list_node rt_list_t;

定义为链表的一个节点,当同一优先级下有多个线程时,可以通过插入链表的方式插入该优先级下

image-20230219094601465

下面是几个链表相关函数

  • rt_list_init

    rt_inline void rt_list_init(rt_list_t *l)
    {
    	l->next = l->prev = l;
    }
    
  • rt_list_insert_after

    /* 在双向链表头部插入一个节点 */
    rt_inline void rt_list_insert_after(rt_list_t *l, rt_list_t *n)
    {
        l->next->prev = n; /* 第 步 */
        n->next = l->next; /* 第 步 */
        l->next = n; /* 第 步 */
        n->prev = l; /* 第 步 */
    }
    
  • rt_list_insert_before

    rt_inline void rt_list_insert_before(rt_list_t *l, rt_list_t *n)
    {
        l->prev->next = n; /* 第 步 */
        n->prev = l->prev; /* 第 步 */
        l->prev = n; /* 第 步 */
        n->next = l; /* 第 步 */
    }
    

7. 调度器

主要功能是实现线程的切换,从就绪列表中找到优先级最高的线程,然后执行该线程。

二、对象容器

1. 对象:所有的数据结构都是对象

其中线程,信号量,互斥量、事件、邮箱、消息队列、内存堆、内存池、设备和定时器有明显的枚举定义,即为每个对象打上了一个数字标签
对象容器-对象类型枚举定义

enum rt_object_class_type
{
    RT_Object_Class_Thread = 0, /* 对象是线程 */
    RT_Object_Class_Semaphore, /* 对象是信号量 */
    RT_Object_Class_Mutex, /* 对象是互斥量 */
    RT_Object_Class_Event, /* 对象是事件 */
    RT_Object_Class_MailBox, /* 对象是邮箱 */
    RT_Object_Class_MessageQueue, /* 对象是消息队列 */
    RT_Object_Class_MemHeap, /* 对象是内存堆 */
    RT_Object_Class_MemPool, /* 对象是内存池 */
    RT_Object_Class_Device, /* 对象是设备 */
    RT_Object_Class_Timer, /* 对象是定时器 */
    RT_Object_Class_Module, /* 对象是模块 */
    RT_Object_Class_Unknown, /* 对象未知 */
    RT_Object_Class_Static = 0x80/* 对象是静态对象 */
};

为了管理这些对象,专门定义了一个对象数据类型结构体rt_object

struct rt_object
{
    char name[RT_NAME_MAX]; (1) /* 内核对象的名字 */
    rt_uint8_t type; (2) /* 内核对象的类型 */
    rt_uint8_t flag; (3) /* 内核对象的状态 */
    rt_list_t list; (4) /* 内核对象的列表节点 */
};
typedef struct rt_object *rt_object_t; (5) /* 内核对象数据类型重定义 */

每个对象都会有一个结构体,比如之前的线程,称为线程控制块,这些控制块都会包含内核对象

struct rt_thread
{
    /* rt 对象 */
    char name[RT_NAME_MAX]; /* 对象的名字 */
    rt_uint8_t type; /* 对象类型 */
    rt_uint8_t flags; /* 对象的状态 */
    rt_list_t list; /* 对象的列表节点 */
    /* 内核对象 */
    /* 或者直接通过struct rt_object 创建一个内核变量 */
    rt_object_t rtobject_thread;
    
    
    rt_list_t tlist; /* 线程链表节点 */
    void *sp; /* 线程栈指针 */
    void *entry; /* 线程入口地址 */
    void *parameter; /* 线程形参 */
    void *stack_addr; /* 线程起始地址 */
    rt_uint32_t stack_size; /* 线程栈大小,单位为字节 */
};

2. 容器:每当创建一个对象,就会将这个对象放到容器中。

struct rt_object_information
{
    enum rt_object_class_type type;// (1) /* 对象类型 */
    rt_list_t object_list;// (2) /* 对象列表节点头 */
    rt_size_t object_size;// (3) /* 对象大小 */
};

容器本质是一个数组,类型为rt_object_information,包含了对象的类型,列表头结点,大小。可以通过节点将同类型的对象连接起来放在双向链表中。

容器的定义:

static struct rt_object_information rt_object_container [RT_Object_Info_Unknown] = 
{
/* 初始化对象容器 - 线程 */ 
{
    RT_Object_Class_Thread,
    _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Thread), 
    sizeof(struct rt_thread)
},

#ifdef RT_USING_SEMAPHORE
/* 初始化对象容器 - 信号量 */
{
    RT_Object_Class_Semaphore,
    _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Semaphore),
    sizeof(struct rt_semaphore)
},
#endif

#ifdef RT_USING_MUTEX 
/* 初始化对象容器 - 互斥量 */
{
    RT_Object_Class_Mutex,
    _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Mutex),
    sizeof(struct rt_mutex)
},
#endif

#ifdef RT_USING_EVENT
/* 初始化对象容器 - 事件 */
{
    RT_Object_Class_Event,
    _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Event),
    sizeof(struct rt_event)
},
#endif

#ifdef RT_USING_MAILBOX
/* 初始化对象容器 - 邮箱 */
{
    RT_Object_Class_MailBox,
    _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_MailBox),
    sizeof(struct rt_mailbox)
},
#endif

#ifdef RT_USING_MESSAGEQUEUE
/* 初始化对象容器 - 消息队列 */

{
    RT_Object_Class_MessageQueue,
    _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_MessageQueue),
    sizeof(struct rt_messagequeue)
},
#endif

#ifdef RT_USING_MEMHEAP
/* 初始化对象容器 - 内存堆 */
{
    RT_Object_Class_MemHeap,
    _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_MemHeap),
    sizeof(struct rt_memheap)
},
#endif

#ifdef RT_USING_MEMPOOL
/* 初始化对象容器 - 内存池 */
{
    RT_Object_Class_MemPool,
    _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_MemPool),
    sizeof(struct rt_mempool)
},
#endif

#ifdef RT_USING_DEVICE
/* 初始化对象容器 - 设备 */
{
    RT_Object_Class_Device,
    _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Device), 
    sizeof(struct rt_device)
},
#endif

/* 初始化对象容器 - 定时器 */
/*
{
    RT_Object_Class_Timer,
    _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Timer),
    sizeof(struct rt_timer)
},
*/
#ifdef RT_USING_MODULE
/* 初始化对象容器 - 模块 */
{
    RT_Object_Class_Module,
    _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Module),
    sizeof(struct rt_module)
},
#endif

1)对象的类型与容器数组的下标是一致的,数组容量大小为RT_Object_Info_Unknown,由下面的枚举类型rt_object_info_type定义,根据系统中对象的多少来决定,如果所有的宏都被定义,则RT_Object_Info_Unknown值为11,共有12个对象。

/*
* 对象容器数组的下标定义,决定容器的大小
*/
enum rt_object_info_type
{
    RT_Object_Info_Thread = 0, /* 对象是线程 */

    #ifdef RT_USING_SEMAPHORE
    RT_Object_Info_Semaphore, /* 对象是信号量 */
    #endif

    #ifdef RT_USING_MUTEX
    RT_Object_Info_Mutex, /* 对象是互斥量 */
    #endif

    #ifdef RT_USING_EVENT
    RT_Object_Info_Event, /* 对象是事件 */
    #endif

    #ifdef RT_USING_MAILBOX
    RT_Object_Info_MailBox, /* 对象是邮箱 */
    #endif

    #ifdef RT_USING_MESSAGEQUEUE
    RT_Object_Info_MessageQueue, /* 对象是消息队列 */
    #endif

    #ifdef RT_USING_MEMHEAP
    RT_Object_Info_MemHeap, /* 对象是内存堆 */
    #endif

    #ifdef RT_USING_MEMPOOL
    RT_Object_Info_MemPool, /* 对象是内存池 */
    #endif

    #ifdef RT_USING_DEVICE
    RT_Object_Info_Device, /* 对象是设备 */
    #endif

    RT_Object_Info_Timer, /* 对象是定时器 */

    #ifdef RT_USING_MODULE
    RT_Object_Info_Module, /* 对象是模块 */
    #endif

    RT_Object_Info_Unknown, /* 对象未知 */
};

2)_OBJ_CONTAINER_LIST_INIT©

#define _OBJ_CONTAINER_LIST_INIT(c) \
{&(rt_object_container[c].object_list),&(rt_object_container[c].object_list)}

容器的接口

  • rt_object_get_information()获取对象信息

    struct rt_object_information *rt_object_get_information(enum rt_object_class_type type)
    {
    	int index;
    
    	for (index = 0; index < RT_Object_Info_Unknown; index ++)
    	if (rt_object_container[index].type == type) return &rt_object_container[index];
    
    	return RT_NULL;
    }
    
  • rt_object_init()对象初始化

    /**
    * 该函数将初始化对象并将对象添加到对象容器中
    *
    * @param object 要初始化的对象
    * @param type 对象的类型
    * @param name 对象的名字,在整个系统中,对象的名字必须是唯一的
    */
    void rt_object_init(struct rt_object *object, (1)
    					enum rt_object_class_type type, (2)
    					const char *name) (3)
    {
        register rt_base_t temp;
        struct rt_object_information *information;
    
        /* 获取对象信息,即从容器里拿到对应对象列表头指针 */
        information = rt_object_get_information(type); (4)
    
        /* 设置对象类型为静态 */
        object->type = type | RT_Object_Class_Static; (5)
    
        /* 拷贝名字 */
        rt_strncpy(object->name, name, RT_NAME_MAX); (6)
    
        /* 关中断 */
        temp = rt_hw_interrupt_disable(); (7)
    
        /* 将对象插入到容器的对应列表中,不同类型的对象所在的列表不一样 */
        rt_list_insert_after(&(information->object_list), &(object->list)); (8)
    
        /* 使能中断 */
        rt_hw_interrupt_enable(temp);
    }
    

三、空闲线程与阻塞延时

四、创建线程

创建线程-静态内存

在创建一个线程之前,我们需要预先为每一个线程分配一个固定大小的栈空间在SRAM中,也就是一个数组。这就是静态内存。

创建线程-动态内存

创建一个线程之前不需要事先为其分配一个栈空间,在创建线程的时候会一起开辟一个堆空间,堆空间也是在SRAM中的一段内存空间。类比C语言中的动态内存空间分配,这样做的好处是可以更灵活地为线程分配所需空间,节省内存。

在使用方法上静态内存空间与动态内存空间有很大的不同。静态内存空间需要事先分配一个数组,也就是栈空间,而动态内存空间,不需要事先定义线程栈,

静态内存创建一个线程的四个步骤:

  1. 定义线程函数
static void led1_thread_entry(void* parameter)
{
    while (1)
    {
        LED1_ON;
        rt_thread_delay(500); /* 延时 500 个 tick */ (1)

        LED1_OFF;
        rt_thread_delay(500); /* 延时 500 个 tick */
    }
}
  1. 定义线程栈
/* 定义线程控栈时要求 RT_ALIGN_SIZE 个字节对齐 */
ALIGN(RT_ALIGN_SIZE)
/* 定义线程栈 */
static rt_uint8_t rt_led1_thread_stack[1024];
  1. 定义线程控制块
/* 定义线程控制块 */
static struct rt_thread led1_thread;
  1. 初始化线程
rt_thread_init(&led1_thread, /* 线程控制块 */ (1)
            	"led1", /* 线程名字 */ (2)
				led1_thread_entry, /* 线程入口函数 */ (3)
				RT_NULL, /* 线程入口函数参数 */ (4)
				&rt_led1_thread_stack[0], /* 线程栈起始地址 */ (5)
				sizeof(rt_led1_thread_stack), /* 线程栈大小 */ (6)
				3, /* 线程的优先级 */ (7)
				20); /* 线程时间片 */ (8)
  1. 启动线程
rt_thread_startup(&led1_thread);

动态内存创建线程的五个步骤:

  1. 在board.c里可以通过#define(RT_USING_HEAP)来选择是否使用动态内存分配,如果使用动态内存,则下面是board.c文件里有关堆内存初始化的部分代码
#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
#define RT_HEAP_SIZE 1024
/* 从内部 SRAM 里面分配一部分静态内存来作为 rtt 的堆空间,这里配置为 4KB */
static uint32_t rt_heap[RT_HEAP_SIZE];
RT_WEAK void *rt_heap_begin_get(void)
{
	return rt_heap;
}

RT_WEAK void *rt_heap_end_get(void)
{
	return rt_heap + RT_HEAP_SIZE;
}
#endif

void rt_hw_board_init()
{
#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
    rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get());
#endif
}
  1. 定义线程函数
static void led1_thread_entry(void* parameter)
{
    while (1)
    {
        LED1_ON;
        rt_thread_delay(500); /* 延时 500 个 tick */ (1)

        LED1_OFF;
        rt_thread_delay(500); /* 延时 500 个 tick */

    }
}
  1. 定义线程控制块指针
/* 定义线程控制块指针 */
static rt_thread_t led1_thread = RT_NULL;
  1. 创建线程
led1_thread = /* 线程控制块指针 */ (1)
rt_thread_create( "led1", /* 线程名字 */ (2)
                led1_thread_entry, /* 线程入口函数 */ (3)
                RT_NULL, /* 线程入口函数参数 */ (4)
                512, /* 线程栈大小 */ (5)
                3, /* 线程的优先级 */ (6)
                20); /* 线程时间片 */ (7)
  1. 启动线程
if(led1_thread != RT_NULL)//因为线程的内存是由编译器分配的,所以不能够确保分配成功,因此在启动线程之前需要判断一下是否分配成功
{
    rt_thread_startup(led1_thread);
}
else
{
    return -1;
}

五、RT-Thread移植到STM32F407

http://t.csdn.cn/KbvVJ

六、消息队列

消息队列控制块

struct rt_messagequeue
{
    struct rt_ipc_object_parent;
    void *msg_pool;
    rt_uint16_t msg_size;
    rt_uint16_t max_msgs;
    rt_uint16_t entry;
    void *msg_queue_head;
    void *msg_queue_tail;
    void *msg_queue_free;
}
typedef struct rt_messagequeue *rt_mq_t;

定义静态消息队列
struct rt_messagequeue static_mq;
rt_mq_t dynamic_mq;

初始化与脱离

rt_err_t rt_mq_init(rt_mq_t mq, const char *name, void *msgpool, rt_size_t msg_size, rt_size_tpool_size, rt_uint8_t flag);//RT_IPC_FLAG_FIFO,RT_IPC_FLAG_PRIO
rt_err_t rt_mq_detach(rt_mq_t mq);

创建与删除

rt_mq_T rt_mq_create(const char *name,rt_size_t msg_size, rt_size_t max_msgs,rt_uint8_t flag);
rt_err_t rt_mq_delete(rt_mq_t mq);

发送消息

rt_err_t rt_mq_send(rt_mq_t mq, void *buffer, rt_size_t size);
rt_err_t rt_mq_delete(rt_mq_t mq);

接收消息

rt_err_t rt_mq_recv(rt_mq_t mq, void *buffer, rt_size_T size, rt_uint32_t timeout);

七、信号量

线程间通信(Internal Process Communication IPC),RT-Thread中的IPC机制包括信号量、互斥量、时间、邮箱、消息队列。通过IPC机制可以协调多个线程/中断的工作

信号量控制块

struct rt_semaphore
{
    struct rt_ipc_object parent;
    
    rt_uint16_t value;
}

定义静态信号量:struct rt_semaphore static_sem

定义动态信号量:rt_sem_t dynamic_sem

API

信号量的初始化与脱离(静态信号量)

rt_err_t rt_sem_init(rt_sem_t sem, const char* name,rt_uint32_value, rt_uint8_t flag);
rt_err_t rt_sem_detach(rt_sem_t sem);

创建与删除

rt_sem_t rt_sem_create(const char* name,rt_uint32_t value, rt_uint8_t flag);//RT_IPC_FLAG_FIFO RT_IPC_FALG_PRIO

获取信号量

rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t time);//RT_WAITING_FOREVER = -1
rt_err_t rt_sem_trytake(rt_sem_t sem);

释放信号量

rt_err_t rt_sem_release(rt_sem_t sem);

八、互斥量

互斥量控制块

struct rt_mutex
{
    struct_rt_ipc_object_parent;
    rt_uint16_t value;
    rt_uint8_t original_priority;
    rt_uint8_t hold;
    struct rt_thread *owner;
};
typedef struct rt_mutex rt_mutex_t;
定义静态互斥量
struct rt_mutex static mutex;
定义动态互斥量
rt_mutex_t dynamic_mutex;

互斥量的操作

初始化与脱离
rt_err_t rt_mutex_init(rT_mutex_t mutex, const char *name, rt_uint8_t flag);//RT_IPC_FLAG_FIFO RT_IPC_FLAG_PRIO
rt_err_t rt_mutex_detach(rT_mutex_t mutex);
创建与删除
rt_mutex_t rt_mutex_create(const char *name, rt_uint8_t flag);
rt_err_t rt_mutex_delete(rt_mutex_t mutex);
获取互斥量
rt_err_T rt_mutex_take(rt_mutex_t mnutex, rt_int_32_t time);//RT_WAITING_FOREVER=-1
释放互斥量
rt_err_t rt_mutex_release(rt_mutex_t mutex);

九、临界区

每个线程中访问(操作)临界资源的那段代码称为临界区(Critical Section),每次只允许一个线程进入临界区。

比如有两个线程,线程1对全局变量从0到10000对value赋值,线程2对value赋值50000,如果要保证线程1对value的正常赋值,就需要临界区保护

rint32_t value;
void thread1_entry(void *parameter)
{
    while(1)
    {
        rt_uint32_t count;
        for(count=0;count<10000;count++)
        {
            value++;
        }
    }
}
void thread2_entry(void *parameter)
{
    while(1)
    {
        value = 50000;
    }
}

临界段保护的两种方式:

  1. 禁止调度

禁止调度就是把调度器锁住,不让其进行线程切换。这样就能保证当前运行的任务不被换出,知道调度器解锁,所以进制调度是常用的临界区保护方法。

void thread_entry(void *parameter)
{
    while(1)
    {
        /* 调度器上锁,上锁后将不再切换到其他线程,仅响应中断 */
        rt_enter_critical();
        /* 以下进入临界区 */
        
        /* 调度器解锁 */
        rt_exit_critical();
    }
}
  1. 关闭中断

因为线程的调度都是建立在中断的基础上的,所以关闭中断以后,系统将不能再进行调度,线程本身也自然不会被其他线程抢占了

void thread_entry(void* parameter)
{
    rt_base_level;
    while(1)
    {
        /* 关闭中断 */
        level = rt_hw_interrupt_disable();
        /* 以下是临界区 */
        
        /* 打开中断 */
        rt_hw_interrupt_enable(level);
    }
}

十、事件

信号量主要用于一对一的线程同步,当需要一对多、多对一、多对多的时候就需要时间集来处理

RT-Thread中用一个32位无符号整型变量来表示,变量中的一个位代表一个时间。线程通过逻辑与、逻辑或与一个或多个事件建立关联形成一个事件组合。

事件集控制块

struct rt_event
{
	struct rt_ipc_object parent;
	
	rt_uint32_t set;
};
typedef struct rt_event *rt_event_t;
定义静态事件集
struct rt_event static_evt;
定义动态事件集
rt_event_t dynamic_evt;

事件集的操作

初始化与脱离
rt_err_t rt_event_init(rt_event_t event, const char *name, rt_uint8_t flag);//RT_IPC_FLAG_FIFO RT_IPC_FLAG_PRIO
rt_err_t rt_event_detach(rt_event_t event);
创建与删除
rt_event_t rt_event_create(const char *name, rt_uint8_t flag);
rt_err_t rt_event_delete(rt_event_t event);
发送事件
rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set);
接收事件
rt_err_t rt_event_recv(rt_event_t event, rt_uint32_t set, rt_uint8_t option, rt_uint32_t timeout, rt_uint32_t *recved);

十一、邮箱

邮箱用于线程间通信,特点是开销比较低,效率较高。邮箱中的每一封邮件只能容纳固定的四字节内容,线程或者终端服务例程把一封4字节长度的邮件发送到邮箱中,而其他需要的线程可以从邮箱中接收这些邮件并进行处理

邮箱控制块

struct rt_mailbox
{
    struct rt_ipc_object parent;
    rt_uint32_t *msg_pool;
    rt_uint16_t size;
    rt_uint16_t entry;
    rt_uint16_t in_offset;
    rt_uint16_t out_offset;
    rt_list_t suspend_sender_thread;
};
typedef struct rt_mailbox *rt_mailbox_t;
定义静态邮箱
struct rt_mailbox static_mb;
定义动态邮箱
rt_mailbox_t dynamic_mb;

邮箱的操作

初始化与脱离
rt_err_t rt_mb_init(rt_mailbox_t mb, const char *name, void *msgpoll, rt_size_t size, rt_uint8_t flag);RT_IPC_FLAG_FIFO RT_IPC_FLAG_PRIO
rt_err_t rt_mb_detach(rt_mailbox_t mb);
创建与删除
rt_mailbox_t rt_mb_create(const char *name, rt_size_t size, rt_uint8_t flag);
rt_err_t rt_mb_delete(rt_mailbox_t mb);
发送邮件
rt_err_t rt_mb_send(rt_mailbox_t mb, rt_uint32_t value);
rt_err_t rt_mb_send_wait(rt_mailbox_t mb, rt_uint32_t value, rt_uint32_t timeout);
接收邮件
rt_err_T rt_mb_recv(rt_mailbox_t mb, rt_uint32_t *value, rt_int32_t timeout);

十二、定时器

定时器的操作

初始化与脱离(静态方式)

coid rt_timer_init(rt_timer_t timer, const char* name, void(*timeout)(void *parameter), void *parameter, rt_tick_t time, rt_uint8_t flag);
rt_err_t rt_timer_detach(rt_timer_t timer);

创建与删除(动态方式)

rt_timer_t rt_timer_create(const char *name, void(*timeout)(void *parameter), void*parameter, rt_tick_t time, rt_uint8_t flag);

flag取值

1 RT_TIMER_FLAG_ONE_SHOT,单次定时器

2 RT_TIMER_FLAG_PERIODIC,周期定时器

3 RT_TIMER_FLAG_HARD_TIMER,硬件定时器

4 RT_TIMER_FLAG_SOFT_TIMER,软件定时器

1、2、3、4需要从1 2中选一个 3 4 中选一个。通过按位或的方式输入。定时器默认工作在HARD模式下

启动定时器

rt_err_t rt_timer_start(rt_timer_t timer);

停止定时器

rt_err_t rt_timer_stop(rt_timer_t timer);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/381441.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

腾讯云GPU游戏服务器/云主机租用配置价格表出炉!

用于游戏业务的服务器和普通云服务器和主机空间是不同的&#xff0c;游戏服务器对于硬件的配置、网络带宽有更大的要求&#xff0c;一般游戏服务器根据不同的配置和适用场景会有十几元一小时到几十元一小时&#xff0c;而且可以根据不同的按量计费。而普通的云服务器可能需要几…

【技术分享】配置802.1x 本地认证

实验需求 PC1(10.10.10.2/30)直接连接到RouterA的Eth2/0/1端口&#xff0c;RouterA的VLANIF10接口IP地址10.10.10.1/30&#xff08;为PC1上的网关IP地址&#xff09;&#xff0c;配置802.1x协议进行访问控制&#xff0c;认证方式采用本地认证。 实验步骤 RouterA的配置 V200R00…

Impala 在网易大数据的优化和实践

导读&#xff1a; 网易大数据平台的底层数据查询引擎&#xff0c;选用了 Impala 作为 OLAP 查询引擎&#xff0c;不但支撑了网易大数据的交互式查询与自助分析&#xff0c;还为外部客户提供了商业化的产品与服务。今天将为大家分享下 Impala 在网易大数据的优化和实践。 01 Im…

qnx的spi记录

https://www.cnblogs.com/schips/p/protocol_spi_about.html &#xff08;主要&#xff09;https://www.zhihu.com/question/308406342/answer/2901148391https://www.bbsmax.com/A/lk5aa4Pm51/ &#xff08;有基础测试代码&#xff09;https://baijiahao.baidu.com/s?id17460…

聚焦技术前沿 引领行业未来 | 第四届OpenI/O 启智开发者大会深度学习与大模型产业应用专场论坛顺利举办!

为探索人工智能的重要发展方向&#xff0c;深入了解大模型、深度学习领域&#xff0c;推动人工智能的快速发展&#xff0c;2月24日&#xff0c;第四届启智开发者大会以“引领前沿技术&#xff0c;推动产业升级”为主题的“深度学习与大模型产业应用专场”在深圳人才研修院智汇中…

数学知识(算法模板)

数学知识 文章目录数学知识一、质数一、试除法判定质数二、试除法分解质因数三、朴素筛法求素数四、线性筛法求素数二、约数一、试除法求所有约数二、约数个数约数之和三、欧几里得算法三、欧拉函数一、欧拉函数的定义二、筛法求欧拉函数四、快速幂一、快速幂二、快速幂求逆元扩…

Vue中rules表单验证,表单必填*显示位置不对,*显示位置错误

<el-form :model"ruleForm" :rules"rules" ref"ruleForm"><el-form-item label"名称" prop"name"><el-input v-model"ruleForm.name"></el-input></el-form-item> </el-form>…

.NET 使用NLog增强日志输出

引言 不管你是开发单体应用还是微服务应用&#xff0c;在实际的软件的开发、测试和运行阶段&#xff0c;开发者都需要借助日志来定位问题。因此一款好的日志组件将至关重要&#xff0c;在.NET 的开源生态中&#xff0c;目前主要有Serilog、Log4Net和NLog三款优秀的日志组件&…

少儿编程 电子学会图形化编程等级考试Scratch一级真题解析(判断题)2022年12月

2022年12月scratch编程等级考试一级真题 判断题(共10题,每题2分,共20分) 26、可以通过四种方式添加新角色(不包括复制已有角色):选择一个角色、绘制、随机、上传角色 答案:对 考点分析:考查角色添加的方式,角色添加四种方式分别为题目给出的四种,所以正确 27、角…

分布式事务总结

1. 分布式事务产生的背景 1.1 数据库水平拆分 对于大部分的业务而言&#xff0c;在起步阶段&#xff0c;为了快速上线&#xff0c;一般都是单库单表的。但是随着业务的扩张&#xff0c;数据量也随着扩增&#xff0c;单库的性能逐渐变差&#xff0c;就会有数据库的单点压力。因…

大数据常见术语

大数据常见术语一览 主要内容包含以下&#xff08;收藏&#xff0c;转发给你身边的朋友&#xff09; 雪花模型、星型模型和星座模型 事实表 维度表 上钻与下钻 维度退化 数据湖 UV与PV 画像 ETL 机器学习 大数据杀熟 SKU与SPU 即席查询 数据湖 数据中台 ODS&#xff0c;DWD&…

过滤器的创建和执行顺序

过滤器的创建和执行顺序 8.1.1创建并配置过滤器 P143 重点是如何创建并配置&#xff08;xml&#xff09; 1.创建 public class EncodingFilter implements Filter {Overridepublic void init(FilterConfig filterConfig) throws ServletException {}Overridepublic void doFil…

Linux进程间通信:信号量(一)

前提知识 在介绍信号量之前&#xff0c;先来看看一些概念和一些简单的前提知识&#xff1a; 进程间通信的前提是让不同的进程看到同一份资源。于是&#xff0c;就有提出让这种资源成为一种公共资源的方法&#xff0c;方法的提出&#xff0c;导致了一种新的问题的出现&#xf…

MyBatis源码分析(二)SqlSessionFactory的构建及配置文件读取过程

文章目录一、MyBatis配置文件二、SqlSessionFactory的获取1、初始化XML配置的Document以及其他对象2、解析配置文件&#xff08;1&#xff09;配置Environment&#xff08;2&#xff09;存放Mapper&#xff08;3&#xff09;解析Mapper3、构造SqlSessionFactory4、总结未完待续…

测试2年,当初一起入行的朋友很多月薪20k了,自己却还没过万,到底差在了哪里?

说来奇怪&#xff0c;不管是读书还是工作&#xff0c;都存在一个现象&#xff0c;那就是人比人&#xff0c;比死人。读书的时候&#xff0c;不管是老师还是家长口中&#xff0c;总会有一个“别人家的孩子”。同样&#xff0c;到工作中&#xff0c;领导口中总会有一个“别人的员…

Doxygen 详细使用

doxygen的安装和基本使用可参考&#xff1a;Doxygen的安装和基本使用常用选项doxygen的所有选项的参考文档&#xff1a;doxygen官网文档2、样式说明doxygen可以自己自定义样式。手写 .css文件 &#xff08;可以查看doxygen的源码&#xff0c;进行相对应修改&#xff09;在Exper…

推荐一个.Net Core开发的蜘蛛爬虫开源项目

更多开源项目请查看&#xff1a;一个专注推荐.Net开源项目的榜单 如果我们需要抓取网络上的数据&#xff0c;这时候我们就要写爬虫&#xff0c;这里面就涉及到网页的抓取、以及网页分析与数据提取、抓取的性能等知识&#xff0c;今天就给大家推荐一个开源项目&#xff0c;它可以…

java基础复习(练习写博客)

文章目录Java特性和优势java三大版本JDK、JRE、JVM(从前到后包含)Java开发环境搭建步骤Java程序运行机制IDEJava基础语法一、注释、标识符、关键字二、数据类型&#xff08;shift双击问题&#xff09;三、变量、常量、作用域四、运算符五、包机制、JavaDocJava进阶语法一、Scan…

【Java】代码中的安全漏洞解决合集(更新中)

汝之观览&#xff0c;吾之幸也&#xff01;本文主要讲解Java的一些安全漏洞&#xff0c;并且给出浅知的解决方案。 具体国内的风险可查看网址工业和信息化部网络安全威胁和漏洞信息共享平台 1、Spring Framework反射型文件下载漏洞&#xff08;CVE-2020-5421&#xff09; 漏洞…

Linux 进程:fork()与vfork()的对比

目录一、fork函数二、vfork函数1.函数的原理2.函数的隐患3.解决函数隐患的方法在Linux的进程学习中&#xff0c;常使用fork函数来创建子进程&#xff0c;但其实还有一个vfork函数也可以创建子进程。但是这两个函数的实现机制不同&#xff0c;fork函数使用了写实拷贝技术&#x…