rt-thread启动流程分析

文章目录

    • 上电复位
    • 选择启动模式
    • 进入`$Sub$$main`主函数
    • RTT初始化
      • rt_hw_board_init();
        • HAL_Init();
        • SystemClock_Config();
        • rt_hw_pin_init();
        • stm32_hw_usart_init();
        • rt_console_set_device(RT_CONSOLE_DEVICE_NAME);
        • rt_system_heap_init((void *)HEAP_BEGIN, (void *)HEAP_END);
        • rt_components_board_init();
      • rt_show_version();
      • rt_system_timer_init();
      • rt_system_scheduler_init();
      • rt_application_init();
        • main_thread_entry();
        • rt_components_init();
      • rt_system_timer_thread_init();
      • rt_thread_idle_init();
        • rt_thread_idle_entry();
          • rt_thread_idle_excute();
      • rt_system_scheduler_start();

经常习惯使用裸机系统,所以在做集成的时候往往会有很多不方便的地方。
在现在火热的物联网生态情况下,学习一门RTOS是有必要的,这里博主选择RT-Thread开始学习,是因为手上刚好有一块潘多拉L475的板子,配合着RTT的官方文档,这里记录下自己的学习之路。

裸机思维:

上电复位

51单片机老师说:系统上电后,有外部RC保证NRST引脚处于一定时长的低电平时间,使单片机完成复位操作……
那么在stm32的数据手册中一定也会有同样的介绍:(这里选了F103的手册,因为博主觉得这些部分都是一样的,找个F103的中文手册也方便大家阅读)
rt-thread启动流程分析_第1张图片
rt-thread启动流程分析_第2张图片
emm……啥?供电电压不够自己就能把复位脚拉低了?
于是满文档找复位,原来内部电路中有一个脉冲发生器,内部把NRST接地20us执行了复位操作。
rt-thread启动流程分析_第3张图片
rt-thread启动流程分析_第4张图片
好吧,看来以后设计这里的外部上拉和对地电容可以只考虑消除干扰,不用考虑为复位时间去计算了。

选择启动模式

先看看手册中关于boot的介绍:
rt-thread启动流程分析_第5张图片
系统从存储器的0x00000004地址开始执行代码,查询中断向量表,即执行Reset所指向的函数:
rt-thread启动流程分析_第6张图片这里我们的boot跳线就是00了,直接从Flash启动,所以我们的Code虽然存储在0x80000000开始的地方,但是会被映射到0x00000000处,即执行0x80000004处的代码。在线debug走一波:
rt-thread启动流程分析_第7张图片
此时已经复位过了,可以看到除了SP和PC已经加载了,其他都已经被清0或者值F了。
仔细可以发现,PC指针和实际的值并不相同,差了1。
这是因为,此时的汇编是Thumb指令,可以在编译产生的内存映像文件(.map)中看到:
rt-thread启动流程分析_第8张图片
因为ARM中的指令至少是半字(16bit)对齐的,比如0x00和0x01两个地址共同表示一个16bit数,我们只需要访问0x00就能知道这个数了,0x01就失去了意义。
所以这里的最低位就被用来区分是Thumb指令集还是ARM指令集,0表示是ARM指令集,1表示为Thumb指令集,这里0x080000004地址中存储的0x0800039D表示的则是:
我指向了一个Thumb指令集,地址是0x0800039C,请寻址吧!
于是单片机就开始执行Reset_Handler中的指令:

; Reset handler
Reset_Handler    PROC
                 EXPORT  Reset_Handler             [WEAK]
        IMPORT  SystemInit
        IMPORT  __main

                 LDR     R0, =SystemInit
                 BLX     R0
                 LDR     R0, =__main
                 BX      R0
                 ENDP

即执行SystemInit__main函数。

SystemInit中,和RTT没有什么关系,主要就是进行RCC配置、设置时钟源等操作。

void SystemInit(void)
{
  /* FPU settings ------------------------------------------------------------*/
  #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
    SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2));  /* set CP10 and CP11 Full Access */
  #endif

  /* Reset the RCC clock configuration to the default reset state ------------*/
  /* Set MSION bit */
  RCC->CR |= RCC_CR_MSION;

  /* Reset CFGR register */
  RCC->CFGR = 0x00000000U;

  /* Reset HSEON, CSSON , HSION, and PLLON bits */
  RCC->CR &= 0xEAF6FFFFU;

  /* Reset PLLCFGR register */
  RCC->PLLCFGR = 0x00001000U;

  /* Reset HSEBYP bit */
  RCC->CR &= 0xFFFBFFFFU;

  /* Disable all interrupts */
  RCC->CIER = 0x00000000U;

  /* Configure the Vector Table location add offset address ------------------*/
#ifdef VECT_TAB_SRAM
  SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
#else
  SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
#endif
}

注意这里执行的RCC->CFGR = 0x00000000U;,默认使用了HSI时钟,后面肯定还会有配置HSE使用外部时钟保证性能的代码。

进入$Sub$$main主函数

执行完SystemInit就是__main了。
MDK的扩展功能中,有sub和super两个关键字:

为了在进入 main() 之前完成 RT-Thread 系统功能初始化,我们使用了 MDK 的扩展功能 $Sub$$ 和 $Super$$。
可以给 main 添加 $Sub$$ 的前缀符号作为一个新功能函数 $Sub$$main,
这个 $Sub$$main 可以先调用一些要补充在 main 之前的功能函数(这里添加 RT-Thread 系统初始化功能),
再调用 $Super$$main 转到 main() 函数执行,这样可以让用户不用去管 main() 之前的系统初始化操作。
即:
__main会跳转到 $Sub$$main, $Sub$$main 执行完就完了。并不会再去执行我们写的main函数。
所以正常情况下我们需要在 $Sub$$main 的最后一行,调用一次 $Super$$main,转而运行我们写的main函数。

看不太懂?没关系,我们把工程中的所有submain和supermain都找出来~(删掉了各种和编译器相关的define方便阅读):

int $Sub$$main(void)
{
    rt_hw_interrupt_disable();
    rtthread_startup();
    return 0;
}

/* the system main thread */
void main_thread_entry(void *parameter)
{
    extern int main(void);
    extern int $Super$$main(void);

    /* RT-Thread components initialization */
    rt_components_init();
    
    $Super$$main(); /* for ARMCC. */
}

通过断点监视,我们发现__main指向了0x08000188的位置,没事,那就跟踪嘛~
rt-thread启动流程分析_第9张图片
实在不行在汇编里打断点嘛~
这里先跳转到0x08000190,继续往下1A0处会跳转到1A6,1BA跳转1C4,1E2跳1F0,208跳200,这里一直重复,取消了这几个重复的断点,就GG了……
rt-thread启动流程分析_第10张图片
没事,再来嘛~
重新设置断点,21A跳1CA了,然后一直在这之间跳,这谁TM写的循环吧,到底要循环多少次啊!!!
断点全删了,直接打断点到21C,跳过这堆循环了……
此时的lr寄存器为19F,又跳转到19E去了,往下继续,然后1A0跳1A6,1BA跳220,又循环22E跳228了……
rt-thread启动流程分析_第11张图片
循环完了到230开始,一直到23A,此时LR寄存器为19F,跳到了19E,往下运行到1A2时,跳转到248:
在这里插入图片描述
再跳转到832:
在这里插入图片描述
再跳转到888:
在这里插入图片描述
88A跳838,84A跳3B8:
在这里插入图片描述
3C0跳84F:
rt-thread启动流程分析_第12张图片
87A跳24D:
rt-thread启动流程分析_第13张图片
24E跳23C:
在这里插入图片描述
23E跳7B56:
在这里插入图片描述
7B5E跳243:
rt-thread启动流程分析_第14张图片
242跳252:
rt-thread启动流程分析_第15张图片
252跳89E:
rt-thread启动流程分析_第16张图片
于是进入了sub_main:
rt-thread启动流程分析_第17张图片
虽然不知道前面一通汇编操作猛如虎是在干什么,不过对于C语言层面来说,执行完SystemInit后面就是执行的$Sub$$main了。

RTT初始化

RTT的初始化过程写在$Sub$$main中的,即先禁用全局中断,再执行初始化内容。

int $Sub$$main(void)
{
    rt_hw_interrupt_disable();
    rtthread_startup();
    return 0;
}

禁用全局中断是用汇编写的:

;/*
; * rt_base_t rt_hw_interrupt_disable();
; */
rt_hw_interrupt_disable    PROC
    EXPORT  rt_hw_interrupt_disable
    MRS     r0, PRIMASK
    CPSID   I
    BX      LR
    ENDP

该函数放在\rt-thread\libcpu\arm\cortex-m4\context_rvds.S中。
rtthread_startup的内容如下:

int rtthread_startup(void)
{
    rt_hw_interrupt_disable();

    /* board level initialization
     * NOTE: please initialize heap inside board initialization.
     */
    rt_hw_board_init();

    /* show RT-Thread version */
    rt_show_version();

    /* timer system initialization */
    rt_system_timer_init();

    /* scheduler system initialization */
    rt_system_scheduler_init();

#ifdef RT_USING_SIGNALS
    /* signal system initialization */
    rt_system_signal_init();
#endif

    /* create init_thread */
    rt_application_init();

    /* timer thread initialization */
    rt_system_timer_thread_init();

    /* idle thread initialization */
    rt_thread_idle_init();

#ifdef RT_USING_SMP
    rt_hw_spin_lock(&_cpus_lock);
#endif /*RT_USING_SMP*/

    /* start scheduler */
    rt_system_scheduler_start();

    /* never reach here */
    return 0;
}

rt_hw_board_init();

/* 板级初始化:需在该函数内部进行系统堆的初始化 */
先设置中断向量表偏移,这里的NVIC_VTOR_MASK应该是和OTA相关的,这里先埋个坑,后面学了OTA再回来补。

void rt_hw_board_init()
{
    /* NVIC Configuration */
#define NVIC_VTOR_MASK              0x3FFFFF80
#ifdef  VECT_TAB_RAM
    /* Set the Vector Table base location at 0x10000000 */
    SCB->VTOR  = (0x10000000 & NVIC_VTOR_MASK);
#else  /* VECT_TAB_FLASH  */
    /* Set the Vector Table base location at 0x08000000 */
    SCB->VTOR  = (0x08000000 & NVIC_VTOR_MASK);
#endif

    /* HAL_Init() function is called at the beginning of program after reset and before
     * the clock configuration.
     */
    HAL_Init();
    SystemClock_Config();	
    /* First initialize pin port,so we can use pin driver in other bsp driver */
    rt_hw_pin_init();
    /* Second initialize the serial port, so we can use rt_kprintf right away */
    stm32_hw_usart_init();
    rt_console_set_device(RT_CONSOLE_DEVICE_NAME);
    rt_system_heap_init((void *)HEAP_BEGIN, (void *)HEAP_END);
    rt_components_board_init();
}

HAL_Init();

然后是HAL_Init,根据注释可以知道,这个是配置Flash缓存、NVIC中断分组、时钟源等的操作。
不过RTT工程的HAL_InitTick函数中直接return了OK,没有内容,打开了L475的裸机例程,发现里面是有内容的,这里暂且再挖一个坑,后面对比了不同再来研究。
HAL_MspInit();函数没有内容。

/**
  * @brief  Configure the Flash prefetch, the Instruction and Data caches,
  *         the time base source, NVIC and any required global low level hardware
  *         by calling the HAL_MspInit() callback function to be optionally defined in user file
  *         stm32l4xx_hal_msp.c.
  *
  * @note   HAL_Init() function is called at the beginning of program after reset and before
  *         the clock configuration.
  *
  * @note   In the default implementation the System Timer (Systick) is used as source of time base.
  *         The Systick configuration is based on MSI clock, as MSI is the clock
  *         used after a system Reset and the NVIC configuration is set to Priority group 4.
  *         Once done, time base tick starts incrementing: the tick variable counter is incremented
  *         each 1ms in the SysTick_Handler() interrupt handler.
  *
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_Init(void)
{
  HAL_StatusTypeDef  status = HAL_OK;

  /* Configure Flash prefetch, Instruction cache, Data cache */
  /* Default configuration at reset is:                      */
  /* - Prefetch disabled                                     */
  /* - Instruction cache enabled                             */
  /* - Data cache enabled                                    */
#if (INSTRUCTION_CACHE_ENABLE == 0)
   __HAL_FLASH_INSTRUCTION_CACHE_DISABLE();
#endif /* INSTRUCTION_CACHE_ENABLE */

#if (DATA_CACHE_ENABLE == 0)
   __HAL_FLASH_DATA_CACHE_DISABLE();
#endif /* DATA_CACHE_ENABLE */

#if (PREFETCH_ENABLE != 0)
  __HAL_FLASH_PREFETCH_BUFFER_ENABLE();
#endif /* PREFETCH_ENABLE */

  /* Set Interrupt Group Priority */
  HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);

  /* Use SysTick as time base source and configure 1ms tick (default clock after Reset is MSI) */
  if (HAL_InitTick(TICK_INT_PRIORITY) != HAL_OK)
  {
    status = HAL_ERROR;
  }
  else
  {
    /* Init the low level hardware */
    HAL_MspInit();
  }

  /* Return function status */
  return status;
}

作为一个经常使用F103和STD标准库的弱鸡,其实在F103的SystemInit中也有如下函数实现和上述相同的功能,这里不再展开。

  /* Configure the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers */
  /* Configure the Flash Latency cycles and enable prefetch buffer */
  SetSysClock();

SystemClock_Config();

然后是SystemClock_Config();,代码放在了Drivers文件夹下的drv_clock.c文件中,这里应该是设置时钟树,即各个总线的速度,设置Sys_Tick的中断优先级为最高;好像还顺便设置了串口的时钟源。

void SystemClock_Config(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct   = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct   = {0};
    RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
    HAL_StatusTypeDef status;

    /* Initializes the CPU, AHB and APB busses clocks */
    SystemCoreClockUpdate();

    /* Initializes the CPU, AHB and APB busses clocks */
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE | RCC_OSCILLATORTYPE_LSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    RCC_OscInitStruct.LSEState = RCC_LSE_ON;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLM = 1;
    RCC_OscInitStruct.PLL.PLLN = 20;
    RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;
    RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
    RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
    status = HAL_RCC_OscConfig(&RCC_OscInitStruct);
    if (status != HAL_OK)
    {
        RT_ASSERT(0);
    }

    /**Initializes the CPU, AHB and APB busses clocks 
    */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
  {
    
  }

    PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_SDMMC1
                                         | RCC_PERIPHCLK_LPTIM1
                                         | RCC_PERIPHCLK_USART1
                                         | RCC_PERIPHCLK_USART2
                                         | RCC_PERIPHCLK_USART3
                                         | RCC_PERIPHCLK_LPUART1
                                         | RCC_PERIPHCLK_USB;
    PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK2;
    PeriphClkInit.Usart2ClockSelection = RCC_USART2CLKSOURCE_PCLK1;
    PeriphClkInit.Usart3ClockSelection = RCC_USART3CLKSOURCE_PCLK1;
    PeriphClkInit.Uart4ClockSelection = RCC_UART4CLKSOURCE_PCLK1;
    PeriphClkInit.Lpuart1ClockSelection = RCC_LPUART1CLKSOURCE_LSE;
    PeriphClkInit.Lptim1ClockSelection = RCC_LPTIM1CLKSOURCE_LSE;
    PeriphClkInit.UsbClockSelection = RCC_USBCLKSOURCE_PLLSAI1;
    PeriphClkInit.Sdmmc1ClockSelection = RCC_SDMMC1CLKSOURCE_PLLSAI1;
    PeriphClkInit.PLLSAI1.PLLSAI1Source = RCC_PLLSOURCE_HSE;
    PeriphClkInit.PLLSAI1.PLLSAI1M = 1;
    PeriphClkInit.PLLSAI1.PLLSAI1N = 12;
    PeriphClkInit.PLLSAI1.PLLSAI1P = RCC_PLLP_DIV7;
    PeriphClkInit.PLLSAI1.PLLSAI1Q = RCC_PLLQ_DIV2;
    PeriphClkInit.PLLSAI1.PLLSAI1R = RCC_PLLR_DIV2;
    PeriphClkInit.PLLSAI1.PLLSAI1ClockOut = RCC_PLLSAI1_48M2CLK;

    PeriphClkInit.PLLSAI2.PLLSAI2Source = RCC_PLLSOURCE_HSE;
    PeriphClkInit.PLLSAI2.PLLSAI2M = 1;
    PeriphClkInit.PLLSAI2.PLLSAI2N = 40;
    PeriphClkInit.PLLSAI2.PLLSAI2P = RCC_PLLP_DIV7;
    PeriphClkInit.PLLSAI2.PLLSAI2R = RCC_PLLR_DIV2;
    PeriphClkInit.PLLSAI2.PLLSAI2ClockOut = RCC_PLLSAI2_SAI2CLK;
    if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
    {
        RT_ASSERT(0);
    }

    /* Configure the main internal regulator output voltage */
    if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1) != HAL_OK)
    {
        RT_ASSERT(0);
    }

    /* Configure the Systick interrupt time */
    HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq() / RT_TICK_PER_SECOND);
    /* Configure the Systick */
    HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
    /* SysTick_IRQn interrupt configuration */
    HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}

rt_hw_pin_init();

调用rtt的设备注册函数,具体实现先埋坑。

int rt_hw_pin_init(void)
{
    return rt_device_pin_register("pin", &_stm32_pin_ops, RT_NULL);
}

stm32_hw_usart_init();

调用rtt的串口注册函数。

int stm32_hw_usart_init(void)
{
    struct stm32_uart* uarts[] =    {        &uart1,    };
    int i;

    for (i = 0; i < sizeof(uarts) / sizeof(uarts[0]); i++)
    {
        struct stm32_uart *uart = uarts[i];
        rt_err_t result;

        /* register UART device */
        result = rt_hw_serial_register(&uart->serial,
                              uart->uart_name,
                              RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX,
                              uart);
        RT_ASSERT(result == RT_EOK);
        (void)result;
    }

    return 0;
}

rt_console_set_device(RT_CONSOLE_DEVICE_NAME);

看函数名称,应该是设置console的的设备。

#define RT_CONSOLE_DEVICE_NAME "uart1"

根据传入参数我们知道,把串口1用于调试窗口;而前面已经进行了串口1的设备注册,所以这里设置后,就可以正常使用rt_kprintf进行打印了。

/**
 * This function will set a device as console device.
 * After set a device to console, all output of rt_kprintf will be
 * redirected to this new device.
 *
 * @param name the name of new console device
 *
 * @return the old console device handler
 */
rt_device_t rt_console_set_device(const char *name)
{
    rt_device_t new, old;

    /* save old device */
    old = _console_device;

    /* find new console device */
    new = rt_device_find(name);
    if (new != RT_NULL)
    {
        if (_console_device != RT_NULL)
        {
            /* close old console device */
            rt_device_close(_console_device);
        }

        /* set new console device */
        rt_device_open(new, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_STREAM);
        _console_device = new;
    }

    return old;
}
RTM_EXPORT(rt_console_set_device);

rt_system_heap_init((void *)HEAP_BEGIN, (void *)HEAP_END);

看函数名是内存堆的初始化。
看传入的参数begin应该是指IRAM1中的剩余空间的起始点。

#define HEAP_BEGIN    (&Image$$RW_IRAM1$$ZI$$Limit)
#define HEAP_END                STM32_SRAM_END
#define STM32_SRAM_END          (0x20000000 + STM32_SRAM_SIZE * 1024)
/**
 * @ingroup SystemInit
 *
 * This function will initialize system heap memory.
 *
 * @param begin_addr the beginning address of system heap memory.
 * @param end_addr the end address of system heap memory.
 */
void rt_system_heap_init(void *begin_addr, void *end_addr)
{
    struct heap_mem *mem;
    rt_ubase_t begin_align = RT_ALIGN((rt_ubase_t)begin_addr, RT_ALIGN_SIZE);
    rt_ubase_t end_align   = RT_ALIGN_DOWN((rt_ubase_t)end_addr, RT_ALIGN_SIZE);

    RT_DEBUG_NOT_IN_INTERRUPT;

    /* alignment addr */
    if ((end_align > (2 * SIZEOF_STRUCT_MEM)) &&
        ((end_align - 2 * SIZEOF_STRUCT_MEM) >= begin_align))
    {
        /* calculate the aligned memory size */
        mem_size_aligned = end_align - begin_align - 2 * SIZEOF_STRUCT_MEM;
    }
    else
    {
        rt_kprintf("mem init, error begin address 0x%x, and end address 0x%x\n",
                   (rt_ubase_t)begin_addr, (rt_ubase_t)end_addr);

        return;
    }

    /* point to begin address of heap */
    heap_ptr = (rt_uint8_t *)begin_align;

    RT_DEBUG_LOG(RT_DEBUG_MEM, ("mem init, heap begin address 0x%x, size %d\n",
                                (rt_ubase_t)heap_ptr, mem_size_aligned));

    /* initialize the start of the heap */
    mem        = (struct heap_mem *)heap_ptr;
    mem->magic = HEAP_MAGIC;
    mem->next  = mem_size_aligned + SIZEOF_STRUCT_MEM;
    mem->prev  = 0;
    mem->used  = 0;


    /* initialize the end of the heap */
    heap_end        = (struct heap_mem *)&heap_ptr[mem->next];
    heap_end->magic = HEAP_MAGIC;
    heap_end->used  = 1;
    heap_end->next  = mem_size_aligned + SIZEOF_STRUCT_MEM;
    heap_end->prev  = mem_size_aligned + SIZEOF_STRUCT_MEM;

    rt_sem_init(&heap_sem, "heap", 1, RT_IPC_FLAG_FIFO);

    /* initialize the lowest-free pointer to the start of the heap */
    lfree = (struct heap_mem *)heap_ptr;
}


rt_components_board_init();

rt_components_board_init() 函数执行的比较早,主要初始化相关硬件环境,执行这个函数时将会遍历通过
INIT_BOARD_EXPORT(fn) 申明的初始化函数表,并调用各个函数。

/**
 * RT-Thread Components Initialization for board
 */
void rt_components_board_init(void)
{
    const init_fn_t *fn_ptr;

    for (fn_ptr = &__rt_init_rti_board_start; fn_ptr < &__rt_init_rti_board_end; fn_ptr++)
    {
        (*fn_ptr)();
    }
}

rt_show_version();

版本显示函数就比较简单了,前面已经设置好了rt_kprintf的使用,这里打印就行了。

/**
 * This function will show the version of rt-thread rtos
 */
void rt_show_version(void)
{
    rt_kprintf("\n \\ | /\n");
    rt_kprintf("- RT -     Thread Operating System\n");
    rt_kprintf(" / | \\     %d.%d.%d build %s\n",
               RT_VERSION, RT_SUBVERSION, RT_REVISION, __DATE__);
    rt_kprintf(" 2006 - 2018 Copyright by rt-thread team\n");
}
RTM_EXPORT(rt_show_version);

rt_system_timer_init();

系统定时器初始化,参考rtt官方的文档手册,因为定时器是一个双向链表(有next和last指针),所以这里要对头元素进行init,即last=自己,next也等于自己,不init的话,last和next都指向0,显然是要GG的~

/**
 * @ingroup SystemInit
 *
 * This function will initialize system timer
 */
void rt_system_timer_init(void)
{
    int i;

    for (i = 0; i < sizeof(rt_timer_list) / sizeof(rt_timer_list[0]); i++)
    {
        rt_list_init(rt_timer_list + i);
    }
}

rt_system_scheduler_init();

RT-Thread 的线程调度器是抢占式的,主要的工作就是从就绪线程列表中查找最高优先级线程,保证最高优先级的线程能够被运行,最高优先级的任务一旦就绪,总能得到CPU 的使用权。

上面是官方文档说的,之前好像也在官方文档看到过说(但是现在没找到了,不知道是不是自己记错了,反正我暂时是这样理解的):由于RTT的线程调度是抢占式的,所以会有一个优先级链表,把优先级高的放在前面,然后从前往后遍历,找到就绪的就执行。
所以针对这个双向链表,未初始化的时候指针都是指向0的,这里执行的操作依然让指针指向自己,以保证不会GG。

/**
 * @ingroup SystemInit
 * This function will initialize the system scheduler
 */
void rt_system_scheduler_init(void)
{
    rt_scheduler_lock_nest = 0;

    RT_DEBUG_LOG(RT_DEBUG_SCHEDULER, ("start scheduler: max priority 0x%02x\n",
                                      RT_THREAD_PRIORITY_MAX));

    for (offset = 0; offset < RT_THREAD_PRIORITY_MAX; offset ++)
    {
        rt_list_init(&rt_thread_priority_table[offset]);
    }

    /* initialize ready priority group */
    rt_thread_ready_priority_group = 0;

    /* initialize thread defunct */
    rt_list_init(&rt_thread_defunct);
}

rt_application_init();

RTT的应用初始化,即在应用层创建线程了。创建一个名为“main”的线程,对应函数为main_thread_entry,设置相应的栈空间、优先级并启动。

#define RT_MAIN_THREAD_STACK_SIZE 2048
#define RT_MAIN_THREAD_PRIORITY 10
void rt_application_init(void)
{
    rt_thread_t tid;

    tid = rt_thread_create("main", main_thread_entry, RT_NULL,
                           RT_MAIN_THREAD_STACK_SIZE, RT_MAIN_THREAD_PRIORITY, 20);
    RT_ASSERT(tid != RT_NULL);
  
    rt_thread_startup(tid);
}

main_thread_entry();

main_thread_entry内容如下,其实就是直接调用main函数:

/* the system main thread */
void main_thread_entry(void *parameter)
{
    extern int main(void);
    extern int $Super$$main(void);

    /* RT-Thread components initialization */
    rt_components_init();

    $Super$$main(); /* for ARMCC. */

}

rt_components_init();

rt_components_init() 函数会在操作系统运行起来之后创建的 main 线程里被调用执行,这个时候硬件环境和操作系统已经初始化完成,可以执行应用相关代码。rt_components_init() 函数会遍历通过剩下的其他几个宏申明的初始化函数表。

/**
 * RT-Thread Components Initialization
 */
void rt_components_init(void)
{
    const init_fn_t *fn_ptr;

    for (fn_ptr = &__rt_init_rti_board_end; fn_ptr < &__rt_init_rti_end; fn_ptr ++)
    {
        (*fn_ptr)();
    }
}

rt_system_timer_thread_init();

SOFT_TIMER 模式可配置,通过宏定义 RT_USING_TIMER_SOFT 来决定是否启用该模式。该模式被启用后,系统会在初始化时创建一个 timer 线程,然后 SOFT_TIMER 模式的定时器超时函数在都会在 timer 线程的上下文环境中执行。可以在初始化 / 创建定时器时使用参数 RT_TIMER_FLAG_SOFT_TIMER 来指定设置 SOFT_TIMER 模式。

因为我们没有使用软定时器功能,所以这个的#ifdef RT_USING_TIMER_SOFT并不会成立,相当于一个空函数。

/**
 * @ingroup SystemInit
 *
 * This function will initialize system timer thread
 */
void rt_system_timer_thread_init(void)
{
#ifdef RT_USING_TIMER_SOFT
    int i;

    for (i = 0;
         i < sizeof(rt_soft_timer_list) / sizeof(rt_soft_timer_list[0]);
         i++)
    {
        rt_list_init(rt_soft_timer_list + i);
    }

    /* start software timer thread */
    rt_thread_init(&timer_thread,
                   "timer",
                   rt_thread_timer_entry,
                   RT_NULL,
                   &timer_thread_stack[0],
                   sizeof(timer_thread_stack),
                   RT_TIMER_THREAD_PRIO,
                   10);

    /* startup */
    rt_thread_startup(&timer_thread);
#endif
}

rt_thread_idle_init();

创建一个空闲线程,作为一个优先级最低的线程,用于CPU空闲时运行及进行RTT的其他处理。

/**
 * @ingroup SystemInit
 *
 * This function will initialize idle thread, then start it.
 *
 * @note this function must be invoked when system init.
 */
void rt_thread_idle_init(void)
{
    int i;
    char tidle_name[RT_NAME_MAX];

    for (i = 0; i < _CPUS_NR; i++)
    {
        rt_sprintf(tidle_name, "tidle%d", i);
        rt_thread_init(&idle[i],
                tidle_name,
                rt_thread_idle_entry,
                RT_NULL,
                &rt_thread_stack[i][0],
                sizeof(rt_thread_stack[i]),
                RT_THREAD_PRIORITY_MAX - 1,
                32);
        /* startup */
        rt_thread_startup(&idle[i]);
    }
}

rt_thread_idle_entry();

idle线程主要是负责执行idle_hook程序,如看门狗喂狗,和RTT的资源释放等操作。


static void rt_thread_idle_entry(void *parameter)
{
    while (1)
    {
#ifdef RT_USING_IDLE_HOOK
        rt_size_t i;

        for (i = 0; i < RT_IDEL_HOOK_LIST_SIZE; i++)
        {
            if (idle_hook_list[i] != RT_NULL)
            {
                idle_hook_list[i]();
            }
        }
#endif

        rt_thread_idle_excute();
    }
}
rt_thread_idle_excute();

rt_thread_idle_excute中执行线程的删除、对象内存释放等操作。

void rt_thread_idle_excute(void)
{
    /* Loop until there is no dead thread. So one call to rt_thread_idle_excute
     * will do all the cleanups. */
    while (_has_defunct_thread())
    {
        rt_base_t lock;
        rt_thread_t thread;

        RT_DEBUG_NOT_IN_INTERRUPT;

        /* disable interrupt */
        lock = rt_hw_interrupt_disable();

        /* re-check whether list is empty */
        if (_has_defunct_thread())
        {
            /* get defunct thread */
            thread = rt_list_entry(rt_thread_defunct.next,
                                   struct rt_thread,
                                   tlist);

            /* remove defunct thread */
            rt_list_remove(&(thread->tlist));

            /* lock scheduler to prevent scheduling in cleanup function. */
            rt_enter_critical();

            /* invoke thread cleanup */
            if (thread->cleanup != RT_NULL)
                thread->cleanup(thread);


            /* if it's a system object, not delete it */
            if (rt_object_is_systemobject((rt_object_t)thread) == RT_TRUE)
            {
                /* unlock scheduler */
                rt_exit_critical();

                /* enable interrupt */
                rt_hw_interrupt_enable(lock);

                return;
            }

            /* unlock scheduler */
            rt_exit_critical();
        }
        else
        {
            /* enable interrupt */
            rt_hw_interrupt_enable(lock);

            /* may the defunct thread list is removed by others, just return */
            return;
        }

        /* enable interrupt */
        rt_hw_interrupt_enable(lock);

#ifdef RT_USING_HEAP
        /* release thread's stack */
        RT_KERNEL_FREE(thread->stack_addr);
        /* delete thread object */
        rt_object_delete((rt_object_t)thread);
#endif
    }
}

rt_system_scheduler_start();

线程调度开始则是获取最高优先级的线程,然后启动线程调度器,并开始进行线程切换。

/**
 * @ingroup SystemInit
 * This function will startup scheduler. It will select one thread
 * with the highest priority level, then switch to it.
 */
void rt_system_scheduler_start(void)
{
    register struct rt_thread *to_thread;
    rt_ubase_t highest_ready_priority;

    to_thread = _get_highest_priority_thread(&highest_ready_priority);

    rt_current_thread = to_thread;

    rt_schedule_remove_thread(to_thread);

    /* switch to new thread */

    rt_hw_context_switch_to((rt_uint32_t)&to_thread->sp);

    /* never come back */
}

至此,RTT就启动完成,后面就是各种任务调度了。

你可能感兴趣的:(rt-thread)