RT-Thread学习笔记

第一节课-初识RT-Thread

课程1介绍,操作系统的两个命令,help和version。
介绍工程代码结构。链接: link.
在这里插入图片描述例程文件夹。
在这里插入图片描述
打开keil目录之后的文件夹说明。
在这里插入图片描述观察系统的启动过程, S u b Sub Submain是系统真正的main函数。
在这里插入图片描述

第二节课-动态内存堆的使用

栈:是由编译器自动分配释放的。
堆:是由程序猿分配和释放的。
在这里插入图片描述在这里插入图片描述在这里插入图片描述

在这里插入图片描述之前分配的内存空间过大,或者之前分配的内存空间过小,可以使用第一个api重新分配内存,原来是数据块不变,如果比之前的小,前面的数据会被截断。
第二calloc个api分配的内存是count*size大小。count个size大小的内存块。而malloc函数分配的是固定字节大小的空间。
在这里插入图片描述

第三节课-线程的创建

线程的概念与组成

在这里插入图片描述
在无限循环结构中,往往要加入让出CPU使用权的API调用。
在这里插入图片描述在这里插入图片描述线程控制块的结构体,创建线程必须要有线程控制块。
在这里插入图片描述在这里插入图片描述

线程的创建

静态线程

静态线程:参数1:线程控制块的地址,所以事先得定义一个线程控制块。参数2:名称,为线程起名字。参数3:函数指针,指向线程的入口代码。参数4:向线程代码中传入相关参数,不传入参数,设置为0。参数5:线程栈空间的起始地址。参数6:栈空间的大小。参数7:线程的优先级,线程执行的优先顺序,数字越小,线程执行的优先级就会越高。参数8:线程的时间片参数,后续解说…。

动态线程

动态线程:参数1:线程的名称。参数2:线程的入口代码。参数3:向线程代码中传入相关参数,不传入参数,设置为0。参数4:栈空间的大小。参数5:线程的优先级,线程执行的优先顺序,数字越小,线程执行的优先级就会越高。参数6:线程的时间片参数,后续解说…。

静态线程&&动态线程的区别

区别:①动态线程不需要输入栈的起始地址,不需要定义线程的控制块,只要指出线程栈的大小。②静态线程的线程控制块和线程栈都需要静态地定义出来,而动态线程则不需要提前定义出来,是运行的时候自动分配的。运行效率上,静态线程的线程控制块和线程栈都在芯片的RAM中,速度没有区别,但是系统在外部RAM中的时候,创建的外部动态线程的线程栈和线程控制块的效率下降。

线程创建完毕之后,调用startup 输入参数为 线程控制块的指针。
在这里插入图片描述示例代码说明:例程中创建了2个线程,线程1和线程2,线程2的优先级高于线程1;线程1为循环模式的动态线程,线程2 为循环模式的静态线程。在命令行输入代码,首先执行的是线程2,再执行线程1。线程1没有转让CPU操作权的操作,因此,一直在执行。执行界面如图所示:
在这里插入图片描述

第四节课-跑马灯线程实例

在这里插入图片描述初始状态:当调用create/init这时的线程状态处于初始状态。
就绪状态:当调用strtup时,线程处于就绪状态,或者当挂起的线程因为得到了需要的自身恢复。
运行状态:处于就绪状态的资源,按照优先级排队,等待线程执行,一旦上一个线程运行完毕,操作系统就让下一个线程运行。
挂起状态:当运行的资源调用了延时,或者suspend时,也称之为阻塞状态
关闭状态:运行中的线程调用exit 进入关闭状态,挂起的线程调用delete/detach时,进入关闭状态。
更多用到的状态是就绪,运行,挂起状态。

在这里插入图片描述
在这里插入图片描述
芯片的引脚序号在drv_gpio.c文件夹中。__STM32_PIN(14, F, 4)把初始化的参数中的PIN设置为14,就得到操作GPIOF4的功能。系统函数的手动编写:

static void led_entry(void *parameter)
{
		//初始化PIN脚
	rt_pin_mode(14,PIN_MODE_OUTPUT);
	//线程中要做什么?
	while(1)
				{
						rt_pin_write(14,PIN_LOW);
					rt_thread_delay(50);
					//是以时钟节拍为单位延时,初试配置100Hz,也就是10ms,
					//rt_thread_mdelay(500);是以1ms为单位。要填入500。
					rt_pin_write(14,PIN_HIGH);
						rt_thread_delay(50);
				}
		
}
void led_test()
{
	//线程有返回值,返回值是一个线程控制块指针,接收这个返回值的目的是判断创建是否OK 
	rt_thread_t tid;
	/* 创建线程1,名称是thread1,入口是thread1_entry*/
	tid1 = rt_thread_create("led",
                            led_entry, RT_NULL,
                            512,
                            10,10);
	
    
    /* 如果获得线程控制块,启动这个线程 */
    if (tid != RT_NULL)
        rt_thread_startup(tid);
}

在命令行窗口中输入list_thread 查看线程栈的使用情况,一般把线程栈大小设定为全部容量的70%。
在这里插入图片描述

第五节课-线程的时间片轮询调度

创建线程的数目和内存有关。
在这里插入图片描述
相同优先级的情况下,系统采用线程时间片的方式进行调度。通过时间片对单个线程的运行时间进行约束,单位是一个系统节拍。
在这里插入图片描述
在这里插入图片描述

示例代码
#include <rtthread.h>

#define THREAD_STACK_SIZE	1024
#define THREAD_PRIORITY	    20
#define THREAD_TIMESLICE    10
/* 线程入口 */
static void thread_entry(void* parameter)
{
    rt_uint32_t value;
    rt_uint32_t count = 0;
    value = (rt_uint32_t)parameter;
    while (1)
    {
        if(0 == (count % 5))
        {           
            rt_kprintf("thread %d is running ,thread %d count = %d\n", value , value , count);      

            if(count > 200)
                return;            
        }
         count++;
     }  
}
//优先级的参数是相同的,时间片的参数是不一样的。
//优先级的参数一样,第一个线程的运行时间是第二个线程运行时间的2倍
int timeslice_sample(void)
{
    rt_thread_t tid;
    /* 创建线程1 */
    tid = rt_thread_create("thread1", 
                            thread_entry, (void*)1, 
                            THREAD_STACK_SIZE, 
                            THREAD_PRIORITY, THREAD_TIMESLICE); 
    if (tid != RT_NULL) 
        rt_thread_startup(tid);
    /* 创建线程2 */
    tid = rt_thread_create("thread2", 
                            thread_entry, (void*)2,
                            THREAD_STACK_SIZE, 
                            THREAD_PRIORITY, THREAD_TIMESLICE-5);
    if (tid != RT_NULL) 
        rt_thread_startup(tid);
    return 0;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(timeslice_sample, timeslice sample);

运行结果如图所示,时间片轮询调度规则,因为线程1的运行时间是10,线程2的运行时间是5,所以在整体上以打印出运行次数为标准,线程1的运行次数是线程2的两倍。
在这里插入图片描述

第六节课-空闲线程及两个常用的钩子函数

空闲线程是一种低优先级的特殊系统线程。系统中没有其他线程运行时,执行空闲线程。核心代码在idle.c文件中。空闲线程的创建和创建普通线程的操作是一致的。
在这里插入图片描述系统中提供了2个API来操作空闲线程。通过api来设置和删除钩子函数。删除钩子函数,这个函数就不会在空闲线程中运行了。
在这里插入图片描述
存放在idlehook_sample.c中,钩子函数执行的相关代码必须保证每时每刻都不会被挂起。不可以使用阻塞类函数在钩子函数中。
空闲的线程可以设置多个钩子函数。最多可以设置4个钩子函数。
在这里插入图片描述
运行代码示例:

#include <rtthread.h>
#include <rthw.h>
#define THREAD_PRIORITY      20
#define THREAD_STACK_SIZE    1024
#define THREAD_TIMESLICE     5
/* 指向线程控制块的指针 */
static rt_thread_t tid = RT_NULL;

/* 空闲函数钩子函数执行次数 */
volatile static int hook_times = 0;
/* 空闲任务钩子函数 */
static void idle_hook()
{
    if (0 == (hook_times % 10000))
    {
        rt_kprintf("enter idle hook %d times.\n", hook_times);
    }
    rt_enter_critical();
    hook_times++;//避免别的线程对计数器变量的干扰。加了临界区的保护
    rt_exit_critical();
}
/* 线程入口 */
static void thread_entry(void *parameter)
{
    int i = 5;
    while (i--)
    {
        rt_kprintf("enter thread1.\n");
        rt_enter_critical();
        hook_times = 0;
        rt_exit_critical();
        /* 休眠500ms */
        rt_kprintf("thread1 delay 50 OS Tick.\n", hook_times);
        rt_thread_mdelay(500);
    }
    rt_kprintf("delete idle hook.\n");
    /* 删除空闲钩子函数 */
    rt_thread_idle_delhook(idle_hook);
    rt_kprintf("thread1 finish.\n");
}
int idle_hook_sample(void)
{
    /* 设置空闲线程钩子 */
    rt_thread_idle_sethook(idle_hook);
    /* 创建线程 */
    tid = rt_thread_create("thread1",
                           thread_entry, RT_NULL, 
                           THREAD_STACK_SIZE, 
                           THREAD_PRIORITY, THREAD_TIMESLICE);
    if (tid != RT_NULL)
        rt_thread_startup(tid);
    return 0;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(idle_hook_sample, idle hook sample);

系统调度钩子函数

存放在scheduler_hook.c文件中。系统在任务切换时调用系统调度钩子函数,当系统的任务进行切换时,调用此函数。
在这里插入图片描述

#include <rtthread.h>

#define THREAD_STACK_SIZE	1024
#define THREAD_PRIORITY	    20
#define THREAD_TIMESLICE    10

/* 针对每个线程的计数器 */
volatile rt_uint32_t count[2];

/* 线程1、2共用一个入口,但入口参数不同 */
static void thread_entry(void* parameter)
{
    rt_uint32_t value;

    value = (rt_uint32_t)parameter;
    while (1)
    {
        rt_kprintf("thread %d is running\n", value);
        rt_thread_mdelay(1000); //延时一段时间
    }
}

static rt_thread_t tid1 = RT_NULL;
static rt_thread_t tid2 = RT_NULL;

static void hook_of_scheduler(struct rt_thread* from, struct rt_thread* to)
{
    rt_kprintf("from: %s -->  to: %s \n", from->name , to->name);
}

int scheduler_hook(void)
{   
    /* 设置调度器钩子 */
    rt_scheduler_sethook(hook_of_scheduler);
    
    /* 创建线程1 */
    tid1 = rt_thread_create("thread1", 
                            thread_entry, (void*)1, 
                            THREAD_STACK_SIZE, 
                            THREAD_PRIORITY, THREAD_TIMESLICE); 
    if (tid1 != RT_NULL) 
        rt_thread_startup(tid1);

    /* 创建线程2 */
    tid2 = rt_thread_create("thread2", 
                            thread_entry, (void*)2, 
                            THREAD_STACK_SIZE, 
                            THREAD_PRIORITY,THREAD_TIMESLICE - 5);
    if (tid2 != RT_NULL) 
        rt_thread_startup(tid2);
    return 0;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(scheduler_hook, scheduler_hook sample);

空间线程的钩子函数,系统调度器的钩子函数,不同点,系统调度器的钩子函数只能设置一个,空间线程的钩子函数可以设置到4个。

第七节课-临界区的保护

临界资源的概念:
在这里插入图片描述
临界区:每个线程访问临界资源的那段代码成为临界区。每次只允许一个线程进入临界区。value是临界资源。
在这里插入图片描述
通过关闭系统调度保护临界区。通过互斥特性保护临界区。
在这里插入图片描述

把调度器锁住,不让其线程进行切换。这里的例程代码呼应了之前保护临界区的操作,内容就在上一节 ,这里的调度器锁住了,但是中断还是可以继续响应。

static void idle_hook()
{
    if (0 == (hook_times % 10000))
    {
        rt_kprintf("enter idle hook %d times.\n", hook_times);
    }
    rt_enter_critical();//临界区的保护操作,避免别的线程对计数器变量的干扰。
    hook_times++;
    rt_exit_critical();
}

在这里插入图片描述线程的调度建立在中断的基础上。关闭中断,系统再也无法调度,线程自身也就不会被其他线程抢占了。红色的表示显示关闭中断,在执行完毕之后,再开启中断。和之前的禁止调度机制一样,其他的
在这里插入图片描述

第八节课-信号量的使用

嵌入式系统中的代码运行包括线程和中断,访问资源有时需要同步,有时需要互斥(同一时刻只能一个线程访问资源)。线程间的通信称之为-进程通信。简称IPC。RTTHread中的IPC机制包括信号量,互斥量,时间,邮箱,消息队列。
在这里插入图片描述
信号量相当于一个红绿灯,当信号量为绿灯(不为零)的时候,挂载的线程可以通过,当信号量的实例为0的时候,(红灯)等待信号量的相关线程被挂起。
在这里插入图片描述
第一个成员表示,信号量是从ipc_object对象继承而来,有自己的一个私有的属性。可以定义 静态信号量和动态信号量。动态信号量后续需要分配内存空间。
在这里插入图片描述

静态信号量的操作:初始化和脱离。

参数1:信号量的指针,定义静态信号量,将其地址传递给第一个参数。参数2:信号量的名称。参数3:初始值。参数4:信号量的标志,(RT_IPC_FLAG_FIFO/RT_IPC_FLAG_PRIO)。两个标志的区别,当信号量不可用的时候,多个线程等待信号量的排列方式,当使用第一个标准的时候,等待的线程将以先进先出的方式。当使用第二个标准的时候,将以优先级的高低来等候,优先级高的先获得信号量。
使用detach,当静态信号量不再使用的时候,将其脱离。

动态信号量的操作:创建和删除。

动态信号量的创建,会有成功和失败两种可能,当我们调用API的时候,先判断得到的指针数值,如果不等于NULL,则操作成功,可以继续操作。
使用delete,当动态信号量不再使用的时候,将其删除。
take 获取信号量的时候先传递指针到参数1,当信号量的值大于1的时候,会立即返回,会把信号量的数值value-1,当调用take的时候值等于0,表示信号量不可以用,当take参数=0立即返回,不等于0的时候,按照时间的滴答时钟来进行等待,假如时钟为10ms,take的数值为5,那么等待50ms。如果是一个负数,一直在等待。
try_take:时间参数为0的信号量,只是能看是否能获取到信号量。得不到的话,返回 time_out。
释放信号量的时候,value的数值+1。
在这里插入图片描述信号量的使用示例:信号量初始值,等待的方式,对应的线程和中断中,调用和释放信号量。

#include <rtthread.h>
#define THREAD_PRIORITY         25
#define THREAD_TIMESLICE        5
/* 指向信号量的指针 */
static rt_sem_t dynamic_sem = RT_NULL;

ALIGN(RT_ALIGN_SIZE)
static char thread1_stack[1024];
static struct rt_thread thread1;
static void rt_thread1_entry(void *parameter)
{
    static rt_uint8_t count = 0;
  
    while(1)
    {
        if(count <= 100)
        {
            count++;           
        }
        else
            return; 
        
        /* count每计数10次,就释放一次信号量 */
         if(0 == (count % 10))
        {
            rt_kprintf("t1 release a dynamic semaphore.\n" ); 
            rt_sem_release(dynamic_sem);            
        }
    }
}

ALIGN(RT_ALIGN_SIZE)
static char thread2_stack[1024];
static struct rt_thread thread2;
static void rt_thread2_entry(void *parameter)
{
    static rt_err_t result;
    static rt_uint8_t number = 0;
    while(1)
    {
        /* 永久方式等待信号量,获取到信号量,则执行number自加的操作 */
        result = rt_sem_take(dynamic_sem, RT_WAITING_FOREVER);
        if (result != RT_EOK)
        {        
            rt_kprintf("t2 take a dynamic semaphore, failed.\n");
            rt_sem_delete(dynamic_sem);
            return;
        }
        else
        {      
            number++;             
            rt_kprintf("t2 take a dynamic semaphore. number = %d\n" ,number);                        
        }
    }   
}

/* 信号量示例的初始化 */
int semaphore_sample()
{
    /* 创建一个动态信号量,初始值是0 */
    dynamic_sem = rt_sem_create("dsem", 0, RT_IPC_FLAG_FIFO);
    if (dynamic_sem == RT_NULL)
    {
        rt_kprintf("create dynamic semaphore failed.\n");
        return -1;
    }
    else
    {
        rt_kprintf("create done. dynamic semaphore value = 0.\n");
    }
    rt_thread_init(&thread1,
                   "thread1",
                   rt_thread1_entry,
                   RT_NULL,
                   &thread1_stack[0],
                   sizeof(thread1_stack), 
                   THREAD_PRIORITY, THREAD_TIMESLICE);
    rt_thread_startup(&thread1);              
    rt_thread_init(&thread2,
                   "thread2",
                   rt_thread2_entry,
                   RT_NULL,
                   &thread2_stack[0],
                   sizeof(thread2_stack), 
                   THREAD_PRIORITY-1, THREAD_TIMESLICE);
    rt_thread_startup(&thread2);
    return 0;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(semaphore_sample, semaphore sample);


第九节课-生产者消费者问题

第十节课-互斥量的使用

在这里插入图片描述在这里插入图片描述
参数成员2 value只有2种状态,锁和不锁。
在这里插入图片描述
静态互斥量:参数1:赋互斥量的指针,给互斥量起名字,赋标志位。flag表示互斥量不可用的时候,互斥量的排队方式,第一种先进先出,第二种优先级排队。
脱离互斥量。
动态互斥量的操作。
获取互斥量。获得互斥量所保护的操作权,时间参数为线程给互斥量进行加锁的等待时间,如果互斥量没有被其他线程锁住,成功获取互斥量。如果互斥量被占用,那么一直等待这个挂载的互斥量。如果在规定的时间内没有等到互斥量,那么会返回一个rt_timeout,可以设置永久等待。当线程成功take到互斥量的时候,线程不会被挂起。
当线程完成对共享资源的访问之后,调用release 释放互斥量。只有之前获取互斥量的线程,才能释放互斥量。只能在线程中使用互斥量,不能再中断中使用,但是信号量可以在中断中使用,而且在take到的情况下才能使用release。
在这里插入图片描述

/*
 * 程序清单:互斥锁例程
 *
 * 互斥锁是一种保护共享资源的方法。当一个线程拥有互斥锁的时候,
 * 可以保护共享资源不被其他线程破坏。线程1对2个number分别进行加1操作
 * 线程2也会对2个number分别进行加1操作。使用互斥量保证2个number值保持一致
 */
#include <rtthread.h>

#define THREAD_PRIORITY         8
#define THREAD_TIMESLICE        5
/* 指向互斥量的指针 */
static rt_mutex_t dynamic_mutex = RT_NULL;
static rt_uint8_t number1,number2 = 0;
ALIGN(RT_ALIGN_SIZE)
static char thread1_stack[1024];
static struct rt_thread thread1;
static void rt_thread_entry1(void *parameter)
{
      while(1)
      {
          /* 线程1获取到互斥量后,先后对number1、number2进行加1操作,然后释放互斥量 */
          rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER);          
          number1++;
          rt_thread_mdelay(10);
          number2++;          
          rt_mutex_release(dynamic_mutex);
       }	    
}

ALIGN(RT_ALIGN_SIZE)
static char thread2_stack[1024];
static struct rt_thread thread2;
static void rt_thread_entry2(void *parameter)
{     
      while(1)
      {
          /* 线程2获取到互斥量后,检查number1、number2的值是否相同,相同则表示mutex起到了锁的作用 */
          rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER);
          if(number1 != number2)
          {
            rt_kprintf("not protect.number1 = %d, mumber2 = %d \n",number1 ,number2);
          }
          else
          {
            rt_kprintf("mutex protect ,number1 = mumber2 is %d\n",number1);            
          }
           number1++;
           number2++;
           rt_mutex_release(dynamic_mutex);
          
          if(number1 >=50)
              return;      
      }	  
}
/* 互斥量示例的初始化 */
int mutex_sample(void)
{
    /* 创建一个动态互斥量 */
    dynamic_mutex = rt_mutex_create("dmutex", RT_IPC_FLAG_FIFO);
    if (dynamic_mutex == RT_NULL)
    {
        rt_kprintf("create dynamic mutex failed.\n");
        return -1;
    }
    rt_thread_init(&thread1,
                   "thread1",
                   rt_thread_entry1,
                   RT_NULL,
                   &thread1_stack[0],
                   sizeof(thread1_stack), 
                   THREAD_PRIORITY, THREAD_TIMESLICE);
    rt_thread_startup(&thread1);
    rt_thread_init(&thread2,
                   "thread2",
                   rt_thread_entry2,
                   RT_NULL,
                   &thread2_stack[0],
                   sizeof(thread2_stack), 
                   THREAD_PRIORITY-1, THREAD_TIMESLICE);
    rt_thread_startup(&thread2);
    return 0;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(mutex_sample, mutex sample);

信号量和互斥量的比较

在这里插入图片描述

第十一节课-线程的优先级反转

在这里插入图片描述
用信号量会存在优先级反转的情况,用互斥量不存在优先级反转的情况。
在这里插入图片描述

/*
 * 程序清单:互斥量使用例程
 *
 * 这个例子将创建 3 个动态线程以检查持有互斥量时,持有的线程优先级是否
 * 被调整到等待线程优先级中的最高优先级。
 *
 * 线程 1,2,3 的优先级从高到低分别被创建,
 * 线程 3 先持有互斥量,而后线程 2 试图持有互斥量,此时线程 3 的优先级应该
 * 被提升为和线程 2 的优先级相同。线程 1 用于检查线程 3 的优先级是否被提升
 * 为与线程 2的优先级相同。
 */
#include <rtthread.h>

/* 指向线程控制块的指针 */
static rt_thread_t tid1 = RT_NULL;
static rt_thread_t tid2 = RT_NULL;
static rt_thread_t tid3 = RT_NULL;
static rt_mutex_t mutex = RT_NULL;
#define THREAD_PRIORITY       10
#define THREAD_STACK_SIZE     512
#define THREAD_TIMESLICE      5
/* 线程 1 入口 */
static void thread1_entry(void *parameter)
{
    /* 先让低优先级线程运行 */
    rt_thread_mdelay(100);
    /* 此时 thread3 持有 mutex,并且 thread2 等待持有 mutex */
    /* 检查 rt_kprintf("the producer generates a number: %d\n", array[set%MAXSEM]); 与 thread3 的优先级情况 */
    if (tid2->current_priority != tid3->current_priority)
    {
        /* 优先级不相同,测试失败 */
        rt_kprintf("the priority of thread2 is: %d\n", tid2->current_priority);
        rt_kprintf("the priority of thread3 is: %d\n", tid3->current_priority);
        rt_kprintf("test failed.\n");
        return;
    }
    else
    {
        rt_kprintf("the priority of thread2 is: %d\n", tid2->current_priority);
        rt_kprintf("the priority of thread3 is: %d\n", tid3->current_priority);			
        //测试是将低优先级的提升为高优先级,如果测试相等,表示测试成功。
        rt_kprintf("test OK.\n");
    }
}
/* 线程 2 入口 */
static void thread2_entry(void *parameter)
{
    rt_err_t result;

    rt_kprintf("the priority of thread2 is: %d\n", tid2->current_priority);

    /* 先让低优先级线程运行 */
    rt_thread_mdelay(50);
    /*
     * 试图持有互斥锁,此时 thread3 持有,应把 thread3 的优先级提升
     * 到 thread2 相同的优先级
     */
    result = rt_mutex_take(mutex, RT_WAITING_FOREVER);

    if (result == RT_EOK)
    {
        /* 释放互斥锁 */
        rt_mutex_release(mutex);
    }
}

/* 线程 3 入口 */
static void thread3_entry(void *parameter)
{
    rt_tick_t tick;
    rt_err_t result;
    rt_kprintf("the priority of thread3 is: %d\n", tid3->current_priority);
    result = rt_mutex_take(mutex, RT_WAITING_FOREVER);
    if (result != RT_EOK)
    {
        rt_kprintf("thread3 take a mutex, failed.\n");
    }
    /* 做一个长时间的循环,500ms */
    tick = rt_tick_get();
    while (rt_tick_get() - tick < (RT_TICK_PER_SECOND / 2)) ;
    rt_mutex_release(mutex);
}

int pri_inversion(void)
{
    /* 创建互斥锁 */
    mutex = rt_mutex_create("mutex", RT_IPC_FLAG_FIFO);
    if (mutex == RT_NULL)
    {
        rt_kprintf("create dynamic mutex failed.\n");
        return -1;
    }
    /* 创建线程 1 */
    tid1 = rt_thread_create("thread1",
                            thread1_entry, 
                            RT_NULL,
                            THREAD_STACK_SIZE, 
                            THREAD_PRIORITY - 1, THREAD_TIMESLICE);
    if (tid1 != RT_NULL)
         rt_thread_startup(tid1);
    /* 创建线程 2 */
    tid2 = rt_thread_create("thread2",
                            thread2_entry, 
                            RT_NULL, 
                            THREAD_STACK_SIZE, 
                            THREAD_PRIORITY, THREAD_TIMESLICE);
    if (tid2 != RT_NULL)
        rt_thread_startup(tid2);
    /* 创建线程 3 */
    tid3 = rt_thread_create("thread3",
                            thread3_entry, 
                            RT_NULL, 
                            THREAD_STACK_SIZE, 
                            THREAD_PRIORITY + 1, THREAD_TIMESLICE);
    if (tid3 != RT_NULL)
        rt_thread_startup(tid3);

    return 0;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(pri_inversion, pri_inversion sample);

目的是让低优先级的资源尽快执行,释放共享资源。
在这里插入图片描述

第十二节课-事件集的使用

在这里插入图片描述
在这里插入图片描述
set中的每一个位都表示一个事件,每一个位都为0,表示事件没有发生。每一个位都为1,表示事件发生。
在这里插入图片描述

静态事件集:初始化和脱离。

参数1表示事件集的指针,参数2表示名称,参数3表示标志位,当事件不可用的时,线程的等待方式,第一种是先入先出,第二种是优先级。
当不需要使用线程时,可以通过detach移除这个线程。

动态事件集:创建和删除
发送事件

向哪一个形参发送事件,可以讲set设置为 0 表示 0x01 表示第一事件发送 0x08表示第三个时间发送,我们可以在线程中使用,也可以在中断服务函数中使用。

接收事件

参数1 表示事件集控制块的指针,指定事件集,参数2 表示事件集中哪一个位感兴趣,如0x01|0x08 表示对第0个和第三个事件感兴趣,用或的形式传递,参数3 事件标志,关联型同步,clear表示线程唤醒之后会清除set中的位,参数4 不为零,根据设置的等待时间保持挂起,如果设置为负数,一直等到事件发生才会返回。参数5保存接收到的数据
在这里插入图片描述

/*
 * 程序清单:事件例程
 *
 * 程序会初始化2个线程及初始化一个静态事件对象
 * 一个线程等待于事件对象上,以接收事件;
 * 一个线程发送事件 (事件3/事件5)
*/
#include <rtthread.h>
#define THREAD_PRIORITY      9
#define THREAD_TIMESLICE     5
#define EVENT_FLAG3 (1 << 3)
#define EVENT_FLAG5 (1 << 5)
/* 事件控制块 */
static struct rt_event event;
ALIGN(RT_ALIGN_SIZE)
static char thread1_stack[1024];
static struct rt_thread thread1;
/* 线程1入口函数 */
static void thread1_recv_event(void *param)
{
    rt_uint32_t e;
    /* 第一次接收事件,事件3或事件5任意一个可以触发线程1,接收完后清除事件标志 */
    if (rt_event_recv(&event, (EVENT_FLAG3 | EVENT_FLAG5),
                      RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR,
                      RT_WAITING_FOREVER, &e) == RT_EOK)
    {
        rt_kprintf("thread1: OR recv event 0x%x\n", e);
    }
    rt_kprintf("thread1: delay 1s to prepare the second event\n");
    rt_thread_mdelay(1000);
    /* 第二次接收事件,事件3和事件5均发生时才可以触发线程1,接收完后清除事件标志 */
    if (rt_event_recv(&event, (EVENT_FLAG3 | EVENT_FLAG5),
                      RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR,
                      RT_WAITING_FOREVER, &e) == RT_EOK)
    {
        rt_kprintf("thread1: AND recv event 0x%x\n", e);
    }
    rt_kprintf("thread1 leave.\n");
}
ALIGN(RT_ALIGN_SIZE)
static char thread2_stack[1024];
static struct rt_thread thread2;
/* 线程2入口 */
static void thread2_send_event(void *param)
{
    rt_kprintf("thread2: send event3\n");
    rt_event_send(&event, EVENT_FLAG3);
    rt_thread_mdelay(200);
    rt_kprintf("thread2: send event5\n");
    rt_event_send(&event, EVENT_FLAG5);
    rt_thread_mdelay(200);
    rt_kprintf("thread2: send event3\n");
    rt_event_send(&event, EVENT_FLAG3);
    rt_kprintf("thread2 leave.\n");
}
int event_sample(void)
{
    rt_err_t result;

    /* 初始化事件对象 */
    result = rt_event_init(&event, "event", RT_IPC_FLAG_FIFO);
    if (result != RT_EOK)
    {
        rt_kprintf("init event failed.\n");
        return -1;
    }
    rt_thread_init(&thread1,
                   "thread1",
                   thread1_recv_event,
                   RT_NULL,
                   &thread1_stack[0],
                   sizeof(thread1_stack),
                   THREAD_PRIORITY - 1, THREAD_TIMESLICE);
    rt_thread_startup(&thread1);
    rt_thread_init(&thread2,
                   "thread2",
                   thread2_send_event,
                   RT_NULL,
                   &thread2_stack[0],
                   sizeof(thread2_stack),
                   THREAD_PRIORITY, THREAD_TIMESLICE);
    rt_thread_startup(&thread2);
    return 0;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(event_sample, event sample);

第十三节课-邮箱的使用

在这里插入图片描述
参数1:邮箱继承自IP对象。msg__pool存放缓冲区地址,size表示邮箱的容量,entry表示邮箱中邮件的数目,in&out_offset表示邮箱的进出偏移量,suspend_sender_thread表示挂起在邮箱上的线程。

在这里插入图片描述

静态邮箱

参数1:邮箱控制块的指针。参数2:邮箱的名称。参数3:邮箱的缓冲区。参数4:邮箱的容量大小。假如size配置为10,缓冲区就要40字节。参数5:表示邮箱没有邮件的时候,接收邮件的等待方式。
不使用的时候,释放邮箱。

动态邮箱

参数1:邮箱的名称。参数2:邮箱的大小。参数3:表示邮箱的标志位。

发送邮件

value是邮件的内容,发送少量的内容,直接将内容复制到value中,小于4个字节。
如果是字符串,把指针复制给value。如果邮箱已经满了,返回邮箱已满。可以在线程中和中断中使用。
api2 多了一个timeout参数,可以指定时间等待。如果已经超时了,会返回超时。可以在线程,不能再中断中使用,因为会造成阻塞。

接收邮件

定义一个存放邮件内容的变量,将变量的地址传给value,timeout超时之前接收到。
在这里插入图片描述示例代码:

/*
 * 程序清单:邮箱例程
 *
 * 这个程序会创建2个动态线程,一个静态的邮箱对象,其中一个线程往邮箱中发送邮件,
 * 一个线程往邮箱中收取邮件。
 */
#include <rtthread.h>
#define THREAD_PRIORITY      10
#define THREAD_TIMESLICE     5
/* 邮箱控制块 */
static struct rt_mailbox mb;
/* 用于放邮件的内存池 */
static char mb_pool[128];
static char mb_str1[] = "I'm a mail!";
static char mb_str2[] = "this is another mail!";
static char mb_str3[] = "over";
ALIGN(RT_ALIGN_SIZE)
static char thread1_stack[1024];
static struct rt_thread thread1;
/* 线程1入口 */
static void thread1_entry(void *parameter)
{
    char *str;
    while (1)
    {
        rt_kprintf("thread1: try to recv a mail\n");
        /* 从邮箱中收取邮件 */
        if (rt_mb_recv(&mb, (rt_uint32_t *)&str, RT_WAITING_FOREVER) == RT_EOK)
        {
            rt_kprintf("thread1: get a mail from mailbox, the content:%s\n", str);
            if (str == mb_str3)
                break;
            /* 延时100ms */
            rt_thread_mdelay(100);
        }
    }
    /* 执行邮箱对象脱离 */
    rt_mb_detach(&mb);
}
ALIGN(RT_ALIGN_SIZE)
static char thread2_stack[1024];
static struct rt_thread thread2;
/* 线程2入口 */
static void thread2_entry(void *parameter)
{
    rt_uint8_t count;

    count = 0;
    while (count < 10)
    {
        count ++;
        if (count & 0x1)
        {
            /* 发送mb_str1地址到邮箱中 */
            rt_mb_send(&mb, (rt_uint32_t)&mb_str1);
        }
        else
        {
            /* 发送mb_str2地址到邮箱中 */
            rt_mb_send(&mb, (rt_uint32_t)&mb_str2);
        }
        /* 延时200ms */
        rt_thread_mdelay(200);
    }
    /* 发送邮件告诉线程1,线程2已经运行结束 */
    rt_mb_send(&mb, (rt_uint32_t)&mb_str3);
}
int mailbox_sample(void)
{
    rt_err_t result;
    /* 初始化一个mailbox */
    result = rt_mb_init(&mb,
                        "mbt",                      /* 名称是mbt */
                        &mb_pool[0],                /* 邮箱用到的内存池是mb_pool */
                        sizeof(mb_pool) / 4,        /* 邮箱中的邮件数目,因为一封邮件占4字节 */
                        RT_IPC_FLAG_FIFO);          /* 采用FIFO方式进行线程等待 */
    if (result != RT_EOK)
    {
        rt_kprintf("init mailbox failed.\n");
        return -1;
    }
    rt_thread_init(&thread1,
                   "thread1",
                   thread1_entry,
                   RT_NULL,
                   &thread1_stack[0],
                   sizeof(thread1_stack),
                   THREAD_PRIORITY, THREAD_TIMESLICE);
    rt_thread_startup(&thread1);
    rt_thread_init(&thread2,
                   "thread2",
                   thread2_entry,
                   RT_NULL,
                   &thread2_stack[0],
                   sizeof(thread2_stack),
                   THREAD_PRIORITY, THREAD_TIMESLICE);
    rt_thread_startup(&thread2);
    return 0;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(mailbox_sample, mailbox sample);

第十四节课-消息队列的使用

线程间的通讯方式。
在这里插入图片描述参数1:继承ipc_object,参数2:指向内存空间,保存发送到消息队列的消息。参数3:最大可以容量消息的大小。参数4:消息队列的容量或者长度。参数5:消息队列中消息的个数,有新的加入+1,被读取-1。参数6:头指针,指向第一条可用。
参数7:尾指针,指向最后一条可用。参数8:指向消息队列中空闲的消息块。

消息长度size详解:假如消息长度指定为1:系统按照字节方式进行改写,32位系统定义为4。所以msg_size最小为4。如果是5的话,那就最小为8。会按照最小来定义。
消息容量max_msgs计算方式:每个消息块的大小+指针的大小。如果是1024 那么max=1024/4+4。前面一个4是size,后面一个4是指针大小4。

在这里插入图片描述

静态消息队列

参数1:指定消息队列控制块的指针。参数2:消息队列的名称。参数3:赋值给内存池,存储。参数4:每个消息块占多少字节。参数5:内存池的大小。参数6:指明等待线程的排队方式。
不需要使用的时候,脱离。

动态消息队列

参数1:名称。参数2:消息的长度。参数3:消息的容量。参数4:消息的排队方式。

发送消息

一般消息:指定一个消息队列控制块的指针,消息的指针赋值给buffer,指明发送消息的长度。
紧急消息:参数和普通一样,会把消息放在链表头部,接收的时候,第一个接收消息。

接收消息

指定消息队列控制块的指针,放到缓冲区中,指定消息的长度,指定超时时间。时间不等0的时候,会挂起。
在这里插入图片描述

/*
 * 程序清单:消息队列例程
 *
 * 这个程序会创建2个动态线程,一个线程会从消息队列中收取消息;一个线程会定时给消
 * 息队列发送 普通消息和紧急消息。
 */
#include <rtthread.h>
/* 消息队列控制块 */
static struct rt_messagequeue mq;
/* 消息队列中用到的放置消息的内存池 */
static rt_uint8_t msg_pool[2048];

ALIGN(RT_ALIGN_SIZE)
static char thread1_stack[1024];
static struct rt_thread thread1;
/* 线程1入口函数 */
static void thread1_entry(void *parameter)
{
    char buf = 0;
    rt_uint8_t cnt = 0;

    while (1)
    {
        /* 从消息队列中接收消息 */
        if (rt_mq_recv(&mq, &buf, sizeof(buf), RT_WAITING_FOREVER) == RT_EOK)
        {
            rt_kprintf("thread1: recv msg from msg queue, the content:%c\n", buf);
            if (cnt == 19)
            {
                break;
            }
        }
        /* 延时50ms */
        cnt++;
        rt_thread_mdelay(50);
    }
    rt_kprintf("thread1: detach mq \n");
    rt_mq_detach(&mq);
}
ALIGN(RT_ALIGN_SIZE)
static char thread2_stack[1024];
static struct rt_thread thread2;
/* 线程2入口 */
static void thread2_entry(void *parameter)
{
    int result;
    char buf = 'A';    
    rt_uint8_t cnt = 0;
    while (1)
    {
        if (cnt == 8)
        {
            /* 发送紧急消息到消息队列中 */
            result = rt_mq_urgent(&mq, &buf, 1);
            if (result != RT_EOK)
            {
                rt_kprintf("rt_mq_urgent ERR\n");
            }
            else
            {
                rt_kprintf("thread2: send urgent message - %c\n", buf);
            }
        }
        else if (cnt >= 20)/* 发送20次消息之后退出 */
        {
            rt_kprintf("message queue stop send, thread2 quit\n");
            break;
        }
        else
        {
            /* 发送消息到消息队列中 */
            result = rt_mq_send(&mq, &buf, 1);
            if (result != RT_EOK)
            {
                rt_kprintf("rt_mq_send ERR\n");
            }
            rt_kprintf("thread2: send message - %c\n", buf);
        }
        buf++;
        cnt++;
        /* 延时5ms */
        rt_thread_mdelay(5);
    }
}
/* 消息队列示例的初始化 */
int msgq_sample(void)
{
    rt_err_t result;
    /* 初始化消息队列 */
    result = rt_mq_init(&mq,
                        "mqt",
                        &msg_pool[0],               /* 内存池指向msg_pool */
                        1,                          /* 每个消息的大小是 1 字节 */
                        sizeof(msg_pool),           /* 内存池的大小是msg_pool的大小 */
                        RT_IPC_FLAG_FIFO);          /* 如果有多个线程等待,按照先来先得到的方法分配消息 */

    if (result != RT_EOK)
    {
        rt_kprintf("init message queue failed.\n");
        return -1;
    }
    rt_thread_init(&thread1,
                   "thread1",
                   thread1_entry,
                   RT_NULL,
                   &thread1_stack[0],
                   sizeof(thread1_stack), 25, 5);
    rt_thread_startup(&thread1);
    rt_thread_init(&thread2,
                   "thread2",
                   thread2_entry,
                   RT_NULL,
                   &thread2_stack[0],
                   sizeof(thread2_stack), 25, 5);
    rt_thread_startup(&thread2);
    return 0;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(msgq_sample, msgq sample);

第十五节课-软件定时器的使用

在这里插入图片描述

在这里插入图片描述软件定时器的控制块。成员1:表示继承对象。成员2:表示链表节点。成员3:超时函数的函数指针。成员4:超时函数的输入参数。不需要的话设置为0。成员5:指定定时器的超时时间。成员6:超时时间节拍计数。

在这里插入图片描述

静态软件定时器

参数1:输入软件控制块的输入地址。参数2:名称。参数3:回调函数。参数4:回调函数的相关参数。参数5:软件定时器的超时时间。参数6:标志位,flag指定哪一种模式。如果是hard模式,可指定hard,soft,第一个flag表示定时器的工作超时函数的运行次数,若果是one shot 表示超市函数只运行一次,如果指定periodic会周期性地调用超市函数。第一个和第二个要和第三个和第四个逻辑或。假如1和3表示中断上下文中运行1次,并且超时函数只运行一次。假如2和4,表示超时函数周期运行,并且在线程中运行的。还需要开rt_using-soft的宏。

动态软件定时器

在这里插入图片描述

/*
* 程序清单:定时器例程
*
* 这个例程会创建两个动态定时器,一个是单次定时,一个是周期性定时
* 并让周期定时器运行一段时间后停止运行
*/
#include <rtthread.h>
/* 定时器的控制块 */
static rt_timer_t timer1;
static rt_timer_t timer2;
static int cnt = 0;
/* 定时器1超时函数 */
static void timeout1(void *parameter)
{
    rt_kprintf("periodic timer is timeout %d\n", cnt);
    /* 运行第10次,停止周期定时器 */
    if (cnt++ >= 9)
    {
        rt_timer_stop(timer1);
        rt_kprintf("periodic timer was stopped! \n");
    }
}

/* 定时器2超时函数 */
static void timeout2(void *parameter)
{
    rt_kprintf("one shot timer is timeout\n");
}
int timer_sample(void)
{
    /* 创建定时器1  周期定时器 */
    timer1 = rt_timer_create("timer1", timeout1,
                             RT_NULL, 10,
                             RT_TIMER_FLAG_PERIODIC);
    /* 启动定时器1 */
    if (timer1 != RT_NULL) rt_timer_start(timer1);

    /* 创建定时器2 单次定时器 */
    timer2 = rt_timer_create("timer2", timeout2,
                             RT_NULL,  30,
                             RT_TIMER_FLAG_ONE_SHOT);
    /* 启动定时器2 */
    if (timer2 != RT_NULL) rt_timer_start(timer2);
    return 0;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(timer_sample, timer sample);

定时器运行结果如图所示:周期性定时器 1 的超时函数,每 10 个 OS Tick 运行 1 次,共运行 10 次(10 次后调用 rt_timer_stop 使定时器 1 停止运行);单次定时器 2 的超时函数在第 30 个 OS Tick 时运行一次。
在这里插入图片描述

第十六节课-内存池的使用

在这里插入图片描述

在这里插入图片描述成员1:表示继承自RTTHread的object的对象。成员2:内存的起始地址。成员3:整个大内存块的大小。成员4:记录小内存块的大小。参数5:记录小内存块的列表。参数6:记录多少小内存块。参数7:空闲内存块的个数。参数8:挂起在内存池的列表。参数9:挂起在内存池的线程数目。
在这里插入图片描述

静态内存池操作

参数1:内存池控制块的地址。参数2:内存池对象的名称。参数3:系统中内存地址。参数4:大内存块的大小。参数5:指定内存池的大小(4的整数倍)。size/block size +指针大小。

动态内存池操作

参数1:内存池名称。参数2:内存池中的内存块数量。参数3:内存块大小。

申请内存块

申请内存块的地址,从哪个内存块中申请地址。
time 超过time参数 返回失败。大于0的时候一直挂起。

释放内存块

使用完毕之后释放内存块,输入内存块的地址。
在这里插入图片描述

/*
 * 程序清单:内存池例程
 * 这个程序会创建一个静态的内存池对象,2个动态线程。
 * 一个线程会试图从内存池中获得内存块,另一个线程释放内存块
 * 内存块
 */
#include <rtthread.h>
static rt_uint8_t *ptr[50];
static rt_uint8_t mempool[4096];
static struct rt_mempool mp;
#define THREAD_PRIORITY      25
#define THREAD_STACK_SIZE    512
#define THREAD_TIMESLICE     5
/* 指向线程控制块的指针 */
static rt_thread_t tid1 = RT_NULL;
static rt_thread_t tid2 = RT_NULL;
/* 线程1入口 */
static void thread1_mp_alloc(void *parameter)
{
    int i;
    for (i = 0 ; i < 50 ; i++)
    {
        if (ptr[i] == RT_NULL)
        {
            /* 试图申请内存块50次,当申请不到内存块时,
               线程1挂起,转至线程2运行 */
            ptr[i] = rt_mp_alloc(&mp, RT_WAITING_FOREVER);//一直申请,没有可用的内存块就一直等待。
            if (ptr[i] != RT_NULL)
                rt_kprintf("allocate No.%d\n", i);
        }
    }
}
/* 线程2入口,线程2的优先级比线程1低,应该线程1先获得执行。*/
static void thread2_mp_release(void *parameter)
{
    int i;
    rt_kprintf("thread2 try to release block\n");
    for (i = 0; i < 50 ; i++)
    {
        /* 释放所有分配成功的内存块 */
        if (ptr[i] != RT_NULL)
        {
            rt_kprintf("release block %d\n", i);
            rt_mp_free(ptr[i]);
            ptr[i] = RT_NULL;
        }
    }
}
int mempool_sample(void)
{
    int i;
    for (i = 0; i < 50; i ++) ptr[i] = RT_NULL;
    /* 初始化内存池对象 */
    rt_mp_init(&mp, "mp1", &mempool[0], sizeof(mempool), 80);
    //4096/(80+4)=48有多少小内存块。
    /* 创建线程1:申请内存池 */
    tid1 = rt_thread_create("thread1", thread1_mp_alloc, RT_NULL,
                            THREAD_STACK_SIZE,
                            THREAD_PRIORITY, THREAD_TIMESLICE);
    if (tid1 != RT_NULL)
        rt_thread_startup(tid1);
    /* 创建线程2:释放内存池*/
    tid2 = rt_thread_create("thread2", thread2_mp_release, RT_NULL,
                            THREAD_STACK_SIZE,
                            THREAD_PRIORITY + 1, THREAD_TIMESLICE);
    if (tid2 != RT_NULL)
        rt_thread_startup(tid2);
    return 0;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(mempool_sample, mempool sample);

  • 6
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值