RT-Thread内核启动流程

2023-11-11

一般了解一份代码大多从启动部分开始,同样这里也采用这种方式,先寻找启动的源头。RT-Thread 支持多种平台和多种编译器,而 rtthread_startup() 函数是 RT-Thread 规定的统一启动入口。一般执行顺序是:系统先从启动文件开始运行,例如,STM32平台的汇编语言编写的启动文件,然后进入 RT-Thread 的启动 rtthread_startup() ,最后进入用户入口 main()。如下图(此图是使用RT-Thread官网的图片)所示。

 

下面我们就以开发工具使用MDK为例来讲解系统的启动流程。以 MDK-ARM 为例,用户程序入口为 main() 函数,位于 main.c 文件中。系统启动后先从汇编代码 startup_stm32f103xe.s 开始运行,然后跳转到 C 代码,进行 RT-Thread 系统启动,最后进入用户程序入口 main()。

为了在进入 main() 之前完成 RT-Thread 系统功能初始化,我们使用了 MDK 的扩展功能 $Sub$$ 和 $Super$$。可以给 main 添加 $Sub$$ 的前缀符号作为一个新功能函数$Sub$$main,这个 $Sub$$main 可以先调用一些要补充在 main 之前的功能函数(这里添加 RT-Thread 系统启动,进行系统一系列初始化),再调用 $Super$$main 转到 main() 函数执行,这样可以让用户不用去管 main() 之前的系统初始化操作。
在系统的components.c文件中可以看到int $Sub$$main(void)函数的定义;代码如下:

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

从上面的代码可以知道,只调用了rtthread_startup()函数执行;此函数的内容如下代码所示;我们会逐个分析每个代码的作用是什么。

/* 此函数是在系统开始的时候执行,根据你使用的编译环境的不同而从不同的函数中调用此函数
 * 主要完成以下相关的工作:
 * 1.禁止中断,全局方式禁止中断;
 * 2.板级初始化,例如,初始化系统时钟,初始化打印log使用的串口等工作;
 * 3.打印系统版本信息;
 * 4.初始化系统中硬Timer使用到的链表;
 * 5.初始化系统的线程调度器;
 * 6.创建main线程,此线程启动时会调用我们自己定义的main()函数;
 * 7.如果使用软Timer,则初始化使用到的链表,并创建软Timer使用的线程;
 * 8.创建系统使用中的IDLE线程;并开启它;
 * 9.开启线程调度器;注意:此函数不会返回的,因为最后开启了线程调度器;
 * 备注:此函数一般情况下是板级厂商提供;因为有很多工作是和板子相关;
 */
int rtthread_startup(void)
{
    //1.禁止中断,全局方式禁止中断;
    rt_hw_interrupt_disable();

    /* board level initialization
     * NOTE: please initialize heap inside board initialization.
     * 2.板级初始化,例如,初始化系统时钟,初始化打印log使用的串口等工作;
     * 此函数是和项目使用芯片和板子强相关的,根据项目情况进行处理。
     */
    rt_hw_board_init();

    //3.打印系统版本信息;
    rt_show_version();

    //4.初始化系统中硬Timer使用到的链表;
    rt_system_timer_init();

    //5.初始化系统的线程调度器;
    rt_system_scheduler_init();

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

    //6.创建main线程,此线程启动时会调用我们自己定义的main()函数;
    rt_application_init();

    //7.如果使用软Timer,则初始化使用到的链表,并创建软Timer使用的线程;
    rt_system_timer_thread_init();

    //8.创建系统使用中的IDLE线程;并开启它;
    rt_thread_idle_init();

    //9.开启线程调度器;注意:此函数不会返回的,因为最后开启了线程调度器;
    rt_system_scheduler_start();

    /* 程序永远也不会执行到这里,否则,系统就出错啦!!! */
    return 0;
}

int rtthread_startup(void)函数注意完成以下几项工作;

1.禁止中断,全局方式禁止中断;会在启动线程调度器的时候去开启全局中断;
2.板级初始化,例如,初始化系统时钟,初始化打印log使用的串口等工作;
3.打印系统版本信息;
4.初始化系统中硬Timer使用到的链表;
5.初始化系统的线程调度器;
6.创建main线程,此线程启动时会调用我们自己定义的main()函数;
7.如果使用软Timer,则初始化使用到的链表,并创建软Timer使用的线程;
8.创建系统使用中的IDLE线程;并开启它;
9.开启线程调度器;注意:此函数不会返回的,因为最后开启了线程调度器;
备注:此函数一般情况下是板级厂商提供;因为有很多工作是和板子相关;

下面我们逐个分析函数的作用;

1.禁止全局中断

rt_hw_interrupt_disable();

;/*
; * rt_base_t rt_hw_interrupt_disable();
; * 全局关中断函数;并返回当前PRIMASK寄存器中断的值
; */
rt_hw_interrupt_disable    PROC
    EXPORT  rt_hw_interrupt_disable
    MRS     r0, PRIMASK
    CPSID   I
    BX      LR
    ENDP

上面的这个函数是通过汇编语言进行编写的,通过操作PRIMASK寄存器,禁止中断。

2.系统板级初始化;主要初始化系统时钟

void rt_hw_board_init()
{
    //首先,应该配置系统时钟;

    /* 更新系统时钟 */
    SystemCoreClockUpdate();

    /* 配置SysTick系统定时器;并开启它 */
    _SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND);

    /* Call components board initial (use INIT_BOARD_EXPORT()) 
     * 初始化组件相关的初始化;由于,Nano版本的软件默认不使用,我先不介绍
    */
#ifdef RT_USING_COMPONENTS_INIT
    rt_components_board_init();
#endif

    /* 如果用户使用自定义main()函数,并且使用动态内存,则初始化动态内存要使用的RAM空间 */
#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
    rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get());
#endif
}

函数主要做了以下几个工作:初始化系统时钟,更新系统时钟;配置SysTick系统定时器,此定时器用来提供系统时钟节拍;如果使用初始化组件,则调用执行;如果使用动态内存堆,则初始化内存堆的空间。这个函数和具体的平台强相关的,还应该初始化打印log使用的串口,方便调试工作。

3.打印系统版本号

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 - 2019 Copyright by rt-thread team\n");
}

函数主要使用rt_kprintf()系统函数打印系统的版本信息。

4.初始化系统中硬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);
    }
}

此函数只做了一项工作,就是初始化系统中硬Timer的链表头,使用此链表来管理系统中所有的Timer。

5.初始化线程调度器

void rt_system_scheduler_init(void)
{
    register rt_base_t offset;

    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]);
    }

    rt_current_priority = RT_THREAD_PRIORITY_MAX - 1;
    rt_current_thread = RT_NULL;

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

#if RT_THREAD_PRIORITY_MAX > 32
    /* initialize ready table */
    rt_memset(rt_thread_ready_table, 0, sizeof(rt_thread_ready_table));
#endif

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

此函数的具体内容还没有做解读,只是知道是初始化线程调度器。

6.创建main线程,此线程启动时会调用我们自己定义的main()函数

void rt_application_init(void)
{
    rt_thread_t tid;

    //动态方式创建main线程
#ifdef RT_USING_HEAP
    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);
#else   //静态方式创建main线程
    rt_err_t result;

    tid = &main_thread;
    result = rt_thread_init(tid, "main", main_thread_entry, RT_NULL,
                            main_stack, sizeof(main_stack), RT_MAIN_THREAD_PRIORITY, 20);
    RT_ASSERT(result == RT_EOK);

    /* if not define RT_USING_HEAP, using to eliminate the warning */
    (void)result;
#endif

    //开启线程
    rt_thread_startup(tid);
}

此函数做的内容很简单,就是创建一个名称为main的线程,并启动它。注意,启动它之后,线程的处理函数不会立即得到执行,因为还没有开启线程调度器。注意,我们自己写的main函数,就会从此线程的处理函数中被调用的。

7.初始化软Timer相关的工作

void rt_system_timer_thread_init(void)
{
#ifdef RT_USING_TIMER_SOFT
    int i;

    /* 初始化软Timer使用到的链表 */
    for (i = 0; i < sizeof(rt_soft_timer_list) / sizeof(rt_soft_timer_list[0]); i++)
    {
        rt_list_init(rt_soft_timer_list + i);
    }

    /* 创建软Timer使用到的线程 */
    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);                         //时间片大小,单位tick

    /* 开启线程 */
    rt_thread_startup(&timer_thread);
#endif
}

如果开启了RT_USING_TIMER_SOFT宏,则可以使用系统中的软Timer组件,就是从这里初始化使用到的链表,并创建一个线程,专门用来执行软Timer定时器超时函数。也就是说软Timer的超时函数时在线程环境进行执行的。而硬Timer的处理环境是中断上下文中。后面我们会详细介绍内核中的Timer组件。

8.创建IDLE线程

void rt_thread_idle_init(void)
{
    /* 初始化IDLE任务 */
    rt_thread_init(&idle,
                   "tidle",
                   rt_thread_idle_entry,
                   RT_NULL,
                   &rt_thread_stack[0],
                   sizeof(rt_thread_stack),
                   RT_THREAD_PRIORITY_MAX - 1,      //IDLE线程的优先级是最低的
                   32);

    /* 启动IDLE任务 */
    rt_thread_startup(&idle);
}

创建IDLE线程,此线程一定要被创建,是系统级别的线程。建议不要在其回调函数中做过多工作,默认就是清理僵尸线程占用的内存空间。

9.开启线程调度器

void rt_system_scheduler_start(void)
{
    register struct rt_thread *to_thread;
    register rt_ubase_t highest_ready_priority;

#if RT_THREAD_PRIORITY_MAX > 32
    register rt_ubase_t number;

    number = __rt_ffs(rt_thread_ready_priority_group) - 1;
    highest_ready_priority = (number << 3) + __rt_ffs(rt_thread_ready_table[number]) - 1;
#else
    highest_ready_priority = __rt_ffs(rt_thread_ready_priority_group) - 1;
#endif

    /* get switch to thread */
    to_thread = rt_list_entry(rt_thread_priority_table[highest_ready_priority].next,
                              struct rt_thread,
                              tlist);

    rt_current_thread = to_thread;

    /* switch to new thread */
    rt_hw_context_switch_to((rt_uint32_t)&to_thread->sp);

    /* never come back */
}

开启线程调度器,主要工作就是找到优先级最高的线程,并执行它。注意,不会从此函数中返回的,因为已经启动了线程调度器,系统就会找到最高优先级的线程进行执行。

注意:因为上面创建了名称为main的线程,下面开启了线程调度器,调度器开启之后就会执行main线程的处理函数,我们来看函数的内容。

void main_thread_entry(void *parameter)
{
    extern int main(void);
    extern int $Super$$main(void);

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

    /* invoke system main function */
#if defined(__CC_ARM) || defined(__CLANG_ARM)
    $Super$$main(); /* for ARMCC. */
#elif defined(__ICCARM__) || defined(__GNUC__)
    //调用我们自己写的int main(void)函数;
    main();
#endif
}

看代码就知道,调用执行用户写的int main()函数。

本文完毕!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

RT-Thread内核启动流程 的相关文章

  • RTTHREAD软件包目录

    RTTHREAD软件包目录 这边统计下RTTHREAD软件包的各项内容 IOT 包名技术标签依赖平台备注abup fotaOTATCP UDP本软件包是用于 Abup FOTA 升级的固件下载器agile jsmnjsonC库jsmn是一个
  • RT-Thread uart2串口dma idle接收不断帧

    硬件STM32F407 IDE使用RT Thread Studio uart2串口使用这两个引脚 功能 IO端口 UART2 TX PA2 UART2 RX PA3 UART2 DMA接收配置 先使能DMA接收 RX缓冲区可以稍微调大些 b
  • RT-Thread 中龙芯1C的网络lwip升级到2.1.0

    RT Thread 龙芯1C 智龙开发板 的网络lwip升级到2 1 0 1 硬件平台 智龙开发板V3 42 2 软件平台 RT Thread 4 0 0 其中LWIP 2 1 0 3 问题描述 一直使用 RT Thread 软件平台 配套
  • 使用RT-Thread Studio 建立 L476 Nucleo 项目工程并完成相关功能

    使用RT Thread Studio 建立 L476 Nucleo 项目工程并完成相关功能 1 新建RTT工程 2 添加cube对应的驱动 Nucleo 板上 X2 低速时钟有 X3调整时钟无 UART2串口配置 PA2 PA3 用户按键
  • apple mobile device服务无法启动,错误1053 解决

    本文转载自 https www cnblogs com relax p 3476741 html 作者 relax 转载请注明该声明 我不想安装iTunes 于是下了iTunes64安装包 解压后得到6个文件 安装完 AppleMobile
  • ArtPi 认识RTT Studio建立LED工程

    1 认识RTT Studio建立LED工程 软件IDE RT Thread Studio 版本 2 1 1 硬件平台 ART Pi CPU STM32H750XB 开发板基本外设功能实现 串口 uart4 PA0 PI9 Red LED P
  • 内核7-线程间同步

    目录 1 信号量 1 1 信号量机制 1 2 信号量的使用场合 1 2 1 线程同步 1 2 2 锁 1 2 3 中断与线程的同步 1 2 4 资源计数 1 3 信号量控制块 1 4 函数 1 4 1 rt sem init 函数 1 4
  • 【RTT驱动框架分析07】- adc驱动框架分析+adc中断唤醒adc驱动

    ADC adc应用开发 访问 ADC 设备 应用程序通过 RT Thread 提供的 ADC 设备管理接口来访问 ADC 硬件 相关接口如下所示 函数 描述 rt device find 根据 ADC 设备名称查找设备获取设备句柄 rt a
  • 【FreeRTOS】队列的使用

    作者主页 凉开水白菜 作者简介 共同学习 互相监督 热于分享 多加讨论 一起进步 专栏资料 https pan baidu com s 1nc1rfyLiMyw6ZhxiZ1Cumg pwd free 点赞 收藏 再看 养成习惯 订阅的粉丝
  • RT-Thread 应用篇 — 在STM32L051上使用 RT-Thread (二、无线温湿度传感器 之 CubeMX配置)

    应用篇 在STM32L051上使用RT Thread 第二篇 使用STM32CubeMX 进行对应外设配置 同时做一些简单测试 目录 前言 一 使用 STM32CubeMX 配置 1 1 基础步骤 1 2 修改配置 1 3 踩坑记录 二 初
  • 什么是真正的实时操作系统

    转自 http club topsage com thread 513248 1 1 html 1 首先说一下实时的定义及要求 参见 Donal Gillies 在 Realtime Computing FAQ 中提出定义 实时系统指系统的
  • 基于stm32驱动bh1750光照传感器的一种超简单的编程方法

    基于stm32驱动bh2750光照传感器的一种超简单的编程方法 目录 基于stm32驱动bh1750光照传感器的一种超简单的编程方法 前言 一 搭载RT thread需要的环境 二 获取RT thread官方源码 并新建一个工程 三 下载b
  • RT-Thread微秒延时?

    今天继续做之前的东西 使用了RT thread操作系统 程序中有AT24C02的相关操作AT24C02的操作的接口是IIC接口 所以又涉及到IIC 程序用的模拟的IIC IIC的时序挺严格的 有微秒的延时操作 由于模拟IIC程序中的延时操作
  • FreeRTOS 源码注释(prvCheckTasksWaitingTermination)

    FreeRTOS 中删除任务是分两步的 第一步 vTaskDelete 将其从各个工作链表中断开 放到xTasksWaitingTermination 链表中 第二步 Idle Task 调用这个函数真正的删除任务 代码很简单 不需要注释了
  • 野火 RT1052 移植网卡功能(LAN8720A)

    野火 RT1052 移植网卡功能 LAN8720A 开发环境 RT Thread v4 0 2 master SOC i MX RT1050 Board 野火 RT1052 目的 在 RT Thread 系统上进行网络通讯 背景描述 1 首
  • RT-Thread之ENV工具

    快速索引 ENV工具简介 准备工作 1 Git 安装并设置环境变量 2 ENV 前往RT Thread官网下载 3 RTT源码 GitHub OSChina 云盘 ENV工具打开方式 scons 编译项目 1 进入 BSP 目录 选择 st
  • 配置 RT-Thread 的工程目录

    1 前言 RT Thread 基于 Scons 的包管理非常方便让我们使用 RT Thread 进行开发 但在实际工程中将应用代码写到 RT Thread 官方提供的 bsp 目录下面会非常不便于使用 无法使用自己 git 工具进行代码管理
  • Unity打开工程时卡住的问题

    自从Unity升级了一个版本后 Unity打开工程卡住的问题越来越严重了 具体表现为 选择工程后 Unity窗口消失 但进程还在 有时候等个几分钟能出来 有时候等10分钟都不见得能出来 直观感受上看 似乎是Unity加载工程的时候某一步卡了
  • BSP制作

    STM32系列驱动介绍 在RT Thread实时操作系统中 各种各样的设备驱动是通过一套I O设备管理框架来实现的 设备管理框架给上层应用提供了一套标准的设备操作API 开发者通过调用这些标准设备操作API 可以高效地完成和底层硬件外设的交
  • RT-Thread 内核基础(六)

    RT Thread内核配置示例 RT Thread的一个重要特性是高度可裁剪性 支持对内核进行精细调整 对组件进行灵活拆卸 配置主要是通过修改工程目录下的rtconfig h文件来进行 用户可以通过打开 关闭该文件中的宏定义来对代码进行条件

随机推荐

  • gbk to utf8 utf8 to gbk

    My Study About My Learn or Study etc GBK和UTF8之间的转换 By Cnangel on October 8 2012 10 10 AM No Comments 关于GBK和UTF 8之间的转换 很多
  • osg学习(七十一)如何给顶点着色器传递顶点数据

    缩放不会影响传递到着色器中顶点坐标缩放 osg会自动向着色器传递osg Vertex osg ModelViewProjectionMatrix等变量 不需要再定义 在着色器中直接使用即可 设置顶点数据 osg Geometry cpp v
  • 2579 启蒙练习-跑步问题

    有二个人在n米的椭圆形的跑道跑步 他们从同一个起点出发 两个人运动方向相同时 每a秒相遇一次 两个人运动方向相反时 每b秒相遇一次 求二人的速度 v1 v2 分别是多少 本题数据保证 n a b v1 v2 都会是整数 收起 输入 三个数
  • SQL Server 基础语法1(超详细!)

    文章目录 创建数据库 增加次要数据库文件 删除次要数据库文件 删除数据库 建立表格 新增列 改变长度 删除表 查询表 删除列 创建数据库 create database school 数据库名 on 数据文件 name school dat
  • SQL Server 基础操作(五)导入和导出数据表

    导入数据表 1 选择需要导数据的数据库右击 任务 导入数据 2 选择数据源 数据源代表数据表从哪里导入到当前的数据库中 填写数据源服务器名称 本地导入 1433 远程导入 IP 1433 3 选择导入的目标数据库 选择导入到那个数据库中 4
  • hive数据仓库课后答案

    第一章 数据仓库的简介 一 填空题 1 数据仓库的目的是构建面向 分析 的集成化数据环境 2 Hive是基于 Hadoop 的一个数据仓库工具 3 数据仓库分为3层 即 源数据层 数据应用层 和数据仓库层 4 数据仓库层可以细分为 明细层
  • k8s部署SpringCloud应用

    一 准备工作 将v2目录上传到 root 目录 下载地址 链接 https pan baidu com s 1oqED4Kew5BeLFqms6U6ISw 提取码 lzx9 springcloud1 项目 用k8s部署 eureka eur
  • (JAVA练习)输入,输出二维数组

    题目 输入 输出二维数组 解答 import java util Scanner public class Erweishuzu public static void main String args 二维数组练习 Scanner sc n
  • element-ui 中dialog居中

    标题element ui 中dialog居中 el dialog display flex flex direction column margin 0 important position absolute top 50 left 50
  • 一款强大的浏览器翻译插件 - 沉浸式的翻译

    起因 前一段时间谷歌翻译宣布跑路 不再对大陆用户提供服务 听闻这一噩耗我不由得心里一惊 燕子 啊不是 谷歌没有你我可咋活呀 对于没太大工作需求 顶多遇上几个不认识单词或需要翻译网页的我来说 Chrome 自带的谷歌翻译可以说是我最常用的翻译
  • micropython源码分析之qstr

    前言 最近在研究micropython的源码编译过程 简单记录下关于qstr部分内容 本篇文章基于micropython1 18版本源码 1 19版本及之后可能会略有差异 标识符与相应对象的联系 Micropython中有很多标识符 例如l
  • 工作笔记:TrueCrypt编译记录

    工作笔记 TrueCrypt编译记录 TrueCrypt的最新版本6 2可以从官方网站上下载 我从这里下载了一个6 1的 http freedos pri ee truecrypt 在TrueCrypt官方网站上很多旧版本都没了 这里却很全
  • 关于Python中中文文本文件使用二进制方式读取后的解码UnicodeDecodeError问题

    最近老猿在进行文件操作的验证测试 发现对于中文文本文件如果使用二进制方式打开 返回的类型是bytes 如果要转换成可读的字符串信息需要进行解码 可是老猿使用decode 或decode UTF 8 解码后报错 Traceback most
  • 从零开始SpringCloud Alibaba实战(79)——Spring-Boot+AOP+统计单次请求方法的执行次数和耗时

    文章目录 前言 代码 ThreadLocal方案 前言 作为工程师 不能仅仅满足于实现了现有的功能逻辑 还必须深入认识系统 一次请求 流经了哪些方法 执行了多少次DB操作 访问了多少次文件操作 调用多少次API操作 总共有多少次IO操作 多
  • Java技术体系平台

    Java SE Java Standard Edition 标准版 支持面向桌面级应用 如Windows下的应用程序 的Java平台 提供了完整的Java核心API 此版本以前称为J2SE Java EE Java Enterprise E
  • CMSIS 到底是什么?

    CMSIS 到底是什么 先来看看ARM公司对CMSIS的定义 ARM Cortex 微控制器软件接口标准 CMSIS 是 Cortex M 处理器系列的与供应商无关的硬件抽象层 CMSIS 可实现与处理器和外设之间的一致且简单的软件接口 从
  • 【网络自定向下的学习】——TCP3次握手和4次挥手详解

    目录 前言 一 可靠数据传输 1 确认应答机制 2 超时重传机制 二 建立连接 三次握手 1 建立连接的过程 2 为什么会有三次握手 3 三次握手可以携带数据吗 4 什么是半连接队列 三 断开连接 4次挥手 1 4次挥手的过程 2 为什么连
  • 浅谈ChatGPT与企业数字化转型

    ChatGPT作为当今一个现象级的爆款概念 它的出现 会与企业数字化碰撞出怎么样的花火 很多数字化转型中的企业 咨询师 也都把目光转向ChatGPT 以及ChatGPT背后的大模型 也许 ChatGPT会给数字化转型带来新一轮的发展 助推剂
  • .net 抽奖概率计算

    公司需要做一个大转盘抽奖的活动 其实最关键的地方就是奖品的概率计算了 不过前两天做的这个计算规则挺简单 设置每个奖品的概率 所有奖品概率之和 乘以 随机值 0 1之间的double类型小数 抽中值 然后循环判断每个奖品的概率 直到大于抽中值
  • RT-Thread内核启动流程

    一般了解一份代码大多从启动部分开始 同样这里也采用这种方式 先寻找启动的源头 RT Thread 支持多种平台和多种编译器 而 rtthread startup 函数是 RT Thread 规定的统一启动入口 一般执行顺序是 系统先从启动文