Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
rt-smart
smart启动流程详解
发布于 2024-05-11 15:32:55 浏览:1074
订阅该版
[tocm] # RT-Smart基于INIT进程的初始化过程 smart初始化中分为内核层和应用层两个部分,整个操作流程可以由以下流程图来概括: ```plantuml @startuml :内核进行内存分配; :初始化定时器; :设置调度算法; :创建main线程并执行bsp下的main函数; :启动任务调度器; :设置简单的环境变量; :解析初始化配置文件; :创建用于接收命令的shell会话框; :轮询接收指令; @enduml ``` ## 内核初始化过程 在内核中,通过汇编将`rtthread_startup`函数的地址加载到x8寄存器中,在`kernel_start`函数中通过`br x8`调用该函数,详细过程可以看libcpu下的entry文件: + 在rtthread_startup函数中进行一些初始化操作,大致流程如下: ```plantuml @startuml :从rtthread_startup()进入,依次执行如下函数; :rt_hw_board_init(); :rt_show_version(); :rt_system_timer_init(); :rt_system_scheduler_init(); :rt_application_init(); :rt_system_timer_thread_init(); :rt_thread_idle_init(); :rt_system_scheduler_start(); @enduml ``` 从上到下依次执行了: + 开始时会跳到一段汇编代码中关中断 + 在函数`rt_hw_board_init`中大致做了如下操作: ```plantuml @startuml :提交设备树起始和结束地址; :对内核地址空间进行映射; :获取内核的起始和结束地址并提交内存区域请求; :调整并更新设备树位置; :提交内存区域; @enduml ``` 在代码中具体的执行如下: + 通过`rt_fdt_commit_memregion_early`函数提交设备树起始和结束地址: ~~~c rt_fdt_commit_memregion_early(&(rt_region_t) { .name = "memheap", .start = (rt_size_t)rt_kmem_v2p(HEAP_BEGIN), .end = (rt_size_t)rt_kmem_v2p(HEAP_END), }, RT_TRUE); ~~~ + 通过`rt_hw_common_setup`函数做一些初始化操作,操作流程如下图: + 对内核地址空间进行映射: ~~~c system_vectors_init(); #ifdef RT_USING_SMART rt_hw_mmu_map_init(&rt_kernel_space, (void*)0xfffffffff0000000, 0x10000000, MMUTable, PV_OFFSET); #else rt_hw_mmu_map_init(&rt_kernel_space, (void*)0xffffd0000000, 0x10000000, MMUTable, 0); #endif ~~~ + 获取内核的起始和结束地址并提交内存区域请求: ~~~c kernel_start = rt_kmem_v2p((void *)&_start) - 64; kernel_end = rt_kmem_v2p((void *)&_end); if (!rt_fdt_commit_memregion_request(&mem_region, &mem_region_nr, RT_TRUE)) { const char *name = "memheap"; while (mem_region_nr --> 0) { if (mem_region->name == name || !rt_strcmp(mem_region->name, name)) { memheap_start = (void *)mem_region->start; memheap_end = (void *)mem_region->end; break; } mem_region++; } } ~~~ + 调整并更新设备树位置: ~~~c if (!rt_fdt_commit_memregion_request(&mem_region, &mem_region_nr, RT_TRUE)) { const char *name = "memheap"; while (mem_region_nr --> 0) { if (mem_region->name == name || !rt_strcmp(mem_region->name, name)) { memheap_start = (void *)mem_region->start; memheap_end = (void *)mem_region->end; break; } mem_region++; } } page_best_start = (rt_size_t)(memheap_end ? : kernel_end); if (memheap_end && fdt_ptr > kernel_start) { rt_memmove(memheap_end - PV_OFFSET, fdt_ptr - PV_OFFSET, fdt_size); fdt_ptr = memheap_end; page_best_start = (rt_size_t)fdt_ptr + fdt_size; } ~~~ + 提交内存区域: ~~~c rt_fdt_commit_memregion_early(&(rt_region_t) { .name = "fdt", .start = (rt_size_t)fdt_ptr, .end = (rt_size_t)(fdt_ptr + fdt_size), }, RT_TRUE); fdt_ptr -= PV_OFFSET; rt_fdt_commit_memregion_early(&(rt_region_t) { .name = "kernel", .start = (rt_size_t)kernel_start, .end = (rt_size_t)kernel_end, }, RT_TRUE); ~~~ + 函数`rt_show_version`就是通过串口打印版本号等信息 + 函数`rt_system_timer_init`初始化定时器 + 函数`rt_system_scheduler_init()`,初始化调度算法,大致流程如下: ```plantuml @startuml :初始化调度器锁; :初始化全局优先级表; :初始化每个CPU核心的数据结构; @enduml ``` + 首先**初始化调度器锁**: ~~~c rt_spin_lock_init(&_mp_scheduler_lock); ~~~ + **初始化全局优先级表**: ~~~c for (offset = 0; offset < RT_THREAD_PRIORITY_MAX; offset ++) { rt_list_init(&rt_thread_priority_table[offset]); } ~~~ 遍历所有优先级,并为每个优先级初始化一个链表,用于存储相同优先级的线程。 + **初始化每个CPU核心的数据结构**: + 为每个CPU核心的优先级表初始化链表: ~~~c for (offset = 0; offset < RT_THREAD_PRIORITY_MAX; offset ++) { rt_list_init(&pcpu->priority_table[offset]); } ~~~ + `rt_application_init`函数,创建一个main线程: + 先创建一个`main`进程: ~~~c tid = rt_thread_create("main", main_thread_entry, RT_NULL, RT_MAIN_THREAD_STACK_SIZE, RT_MAIN_THREAD_PRIORITY, 20); ~~~ + 我们看到这个`main`线程的启动函数为`main_thread_entry`函数,进入这个函数查看: ~~~c static void main_thread_entry(void *parameter) { extern int main(void); RT_UNUSED(parameter); #ifdef RT_USING_COMPONENTS_INIT /* RT-Thread components initialization */ rt_components_init(); #endif /* RT_USING_COMPONENTS_INIT */ #ifdef RT_USING_SMP rt_hw_secondary_cpu_up(); #endif /* RT_USING_SMP */ /* invoke system main function */ #ifdef __ARMCC_VERSION { extern int $Super$$main(void); $Super$$main(); /* for ARMCC. */ } #elif defined(__ICCARM__) || defined(__GNUC__) || defined(__TASKING__) || defined(__TI_COMPILER_VERSION__) main(); #endif /* __ARMCC_VERSION */ } ~~~ 可以看到最终还是调用了`main`函数,也就是bsp下面对应开发板的main函数会被执行。 + 再启动这个线程 ~~~c rt_thread_startup(tid); ~~~ + `rt_system_timer_thread_init`函数,初始化系统定时器 + 初始化定时器线程: ~~~c /* start software timer thread */ rt_thread_init(&_timer_thread, "timer", _timer_thread_entry, RT_NULL, &_timer_thread_stack[0], sizeof(_timer_thread_stack), RT_TIMER_THREAD_PRIO, 10); ~~~ + `rt_thread_idle_init`函数,初始化空闲线程: + 对于每一个cpu都初始化一个线程 ~~~c for (i = 0; i < _CPUS_NR; i++) { #if RT_NAME_MAX > 0 rt_snprintf(idle_thread_name, RT_NAME_MAX, "tidle%d", i); #endif /* RT_NAME_MAX > 0 */ rt_thread_init(&idle_thread[i], #if RT_NAME_MAX > 0 idle_thread_name, #else "tidle", #endif /* RT_NAME_MAX > 0 */ idle_thread_entry, RT_NULL, &idle_thread_stack[i][0], sizeof(idle_thread_stack[i]), RT_THREAD_PRIORITY_MAX - 1, 32); #ifdef RT_USING_SMP rt_thread_control(&idle_thread[i], RT_THREAD_CTRL_BIND_CPU, (void*)i); rt_cpu_index(i)->idle_thread = &idle_thread[i]; #endif /* RT_USING_SMP */ /* startup */ rt_thread_startup(&idle_thread[i]); } ~~~ + `rt_system_scheduler_start`函数,调度器启动函数: + 调用最高优先级的线程到cpu上运行,并更新线程状态: ~~~c rt_hw_local_irq_disable(); /** * for the accessing of the scheduler context. Noted that we don't have * current_thread at this point */ _fast_spin_lock(&_mp_scheduler_lock); /* get the thread scheduling to */ to_thread = _scheduler_get_highest_priority_thread(&highest_ready_priority); RT_ASSERT(to_thread); /* to_thread is picked to running on current core, so remove it from ready queue */ _sched_remove_thread_locked(to_thread); /* dedigate current core to `to_thread` */ RT_SCHED_CTX(to_thread).oncpu = rt_hw_cpu_id(); RT_SCHED_CTX(to_thread).stat = RT_THREAD_RUNNING; ~~~ 至此,cpu内核态初始化完成并开始进行任务调度 ## init初始化过程 在init的初始化中,主要可以分为两个部分,即挂载用户根文件系统和开启shell控制台来接收用户命令,可以用下图描述: ```plantuml @startuml :通过sigaddset初始化一些信号量 ; :通过console_init初始化控制台 ; :通过putenv设置部分环境变量 ; :开启控制台; :挂载根文件系统; :通过sigaction_set设置stop信号处; :run everything; @enduml ``` 在此之前我们先看看做了哪些准备步骤 + 首先是初始化一些信号量: ~~~c sigaddset(&G.delayed_sigset, SIGINT); /* Ctrl-Alt-Del */ sigaddset(&G.delayed_sigset, SIGQUIT); /* re-exec another init */ #ifdef SIGPWR sigaddset(&G.delayed_sigset, SIGPWR); /* halt */ #endif sigaddset(&G.delayed_sigset, SIGUSR1); /* halt */ sigaddset(&G.delayed_sigset, SIGTERM); /* reboot */ sigaddset(&G.delayed_sigset, SIGUSR2); /* poweroff */ #if ENABLE_FEATURE_USE_INITTAB sigaddset(&G.delayed_sigset, SIGHUP); /* reread /etc/inittab */ #endif sigaddset(&G.delayed_sigset, SIGCHLD); /* make sigtimedwait() exit on SIGCHLD */ ~~~ + 接着初始化控制台并设置环境变量: ~~~c console_init(); set_sane_term(); xchdir("/"); setsid(); /* Make sure environs is set to something sane */ putenv((char *) "HOME=/"); putenv((char *) bb_PATH_root_path); putenv((char *) "SHELL=/bin/sh"); putenv((char *) "USER=root"); /* needed? why? */ if (argv[1]) xsetenv("RUNLEVEL", argv[1]); ~~~ **接着跳到`parse_inittab()`函数中解析和处理系统初始化配置文件,这里是重点,我们挂载用户根文件系统和开启shell控制台来接收用户命令都是在这个文件中完成,我们逐个分析在分析之前,我们先来了解一下initlab:** `/etc/inittab` 文件是传统的系统初始化配置文件,用于定义系统启动时的运行级别和相关的初始化进程。这里放一段我的inittab中的代码作为展示: ```shell # Copyright (c) 2006-2023, RT-Thread Development Team # # SPDX-License-Identifier: Apache-2.0 # # Change Logs: # Date Author Notes # 2023-11-13 Shell init ver. # Setup Environment ::sysinit:mkdir -p /dev/shm /dev/pts /proc ::sysinit:mount -a # Run getty & login #console::respawn:/sbin/getty 38400 /dev/console # or without login, a direct access to shell console::respawn:-/bin/ash # using sshd #::once:/bin/dropbear -F -p 80 2>/root/dropbear.log # Restart bussiness ::restart:/sbin/init ``` 可以看到这里做了如下操作: + ::sysinit:mkdir -p /dev/shm /dev/pts /proc 创建了三个目录 + ::sysinit:mount -a 挂载文件系统 + console::respawn:-/bin/ash 在控制台上运行 `/bin/ash` shell + ::restart:/sbin/init 重启init 在`parse_inittab()`中会读取/etc/inittab下的参数: ```c parser_t *parser = config_open2("/etc/inittab", fopen_for_read); ``` 我们知道parse_inittab这个函数首先要执行bin下面的ash应用,在函数中先使用: ~~~c while (config_read(parser, token, 4, 0, PARSE_NORMAL & ~(PARSE_TRIM | PARSE_COLLAPSE))) ~~~ 来不断读取/etc/inittab下的每一行参数并根据config_read中的规则将命令拆分成若干部分放入token中,通过对token的参数处理实现执行指令的方式,在对于`console::respawn:-/bin/ash`命令会调用`new_init_action`函数,在这个函数中会遍历init_action结构体找到是否有和该命令相同的节点,如果没有则在链表尾部创建用于后续的执行。 ### 根文件系统的挂载 在解释根文件系统之前我们得先了解一下`/etc/fstab`这个文件,这个文件是用来存放文件系统的静态信息的文件,我们想要在系统启动时就将某个根文件系统挂载到我们的操作系统中就可以直接通过修改该脚本来实现。接着了解一下这个脚本的基本语法: ~~~C
~~~ + file system:可以是设备的文件路径(如 `/dev/sda1`)。也可以是设备的 UUID(如 `UUID=1234-ABCD`),这更稳定,因为设备名称可能会在系统重启后改变。还可以是卷标(如 `LABEL=mylabel`)。 + dir:文件系统应该被挂载到的目录路径(如 `/`、`/home`、`/mnt/mydrive` 等) + type:文件系统类型 + options:定义文件系统的挂载方式 + dump:决定是否使用 `dump`命令备份文件系统。0 表示不备份,1 表示备份。现代系统通常不使用 dump,所以通常设置为 0 + 用于决定 `fsck` 命令检查文件系统的顺序。根文件系统应该被首先检查(1),其他文件系统可以是 2(表示在根文件系统之后检查),或者 0(表示不检查) 接着继续回到我们的`/etc/inittab`中可以看到会有一条`mount -a命令`,用于挂载系统外的文件我们可以在`/etc/fstab`中看到我们通过mount -a会挂载下面三个文件系统: + **procfs (/proc)** - **用途**:`procfs` 是一个虚拟文件系统,它为用户空间程序提供了内核数据的接口。它通常挂载在 `/proc` 目录下。 - **内容**:`/proc` 目录下包含了大量虚拟文件,这些文件提供了关于系统硬件和正在运行的进程的信息。例如,`/proc/cpuinfo` 提供CPU信息,`/proc/[pid]/` 下的目录为每个进程提供了详细信息。 + **tmpfs (/dev/shm)** - **用途**:`tmpfs` 是一个基于内存的文件系统,它可以用来存储临时文件。在Linux中,`/dev/shm` 通常是一个`tmpfs`挂载点,用于进程间通信(IPC)的共享内存对象(如POSIX共享内存和System V IPC)。 - **内容**:在`/dev/shm`下创建的文件实际上存储在RAM中,因此读写速度非常快,但内容在系统重启后会丢失。 + **ptyfs (/dev/pts)** - **用途**:`ptyfs`(也称为`devpts`)是一个虚拟文件系统,用于管理伪终端(pseudo-terminals, pts)。伪终端允许一个进程(如终端仿真器)模拟一个物理终端,从而允许另一个进程(如shell)与之交互。 - **内容**:`/dev/pts` 目录下包含了伪终端设备的节点。当用户启动一个新的终端仿真器窗口时,一个新的伪终端设备会被创建并在这个目录下显示。 ### 开启shell控制台来接收用户命令 同样我们会看到`console::respawn:-/bin/ash`这个命令, 对于`-/bin/ash`是一个shell 程序,用于执行命令和脚本,在这里会作为一个持续运行的 shell 会话存在,用于接收和执行用户或系统级别的命令。 + 设置STOP信号处理程序 ~~~c memset(&sa, 0, sizeof(sa)); sigfillset(&sa.sa_mask); sigdelset(&sa.sa_mask, SIGCONT); sa.sa_handler = stop_handler; sa.sa_flags = SA_RESTART; sigaction_set(SIGTSTP, &sa); /* pause */ /* Does not work as intended, at least in 2.6.20. * SIGSTOP is simply ignored by init * (NB: behavior might differ under strace): */ sigaction_set(SIGSTOP, &sa); /* pause */ ~~~ + 接着执行系统所有需要执行的操作,操作的目的在源码中已指出,如下: ~~~c /* Now run everything that needs to be run */ /* First run the sysinit command */ run_actions(SYSINIT); check_delayed_sigs(&G.zero_ts); /* Next run anything that wants to block */ run_actions(WAIT); check_delayed_sigs(&G.zero_ts); /* Next run anything to be run only once */ run_actions(ONCE); ~~~ + 通过while(1)循环管理系统服务: ~~~c while (1) { /* (Re)run the respawn/askfirst stuff */ run_actions(RESPAWN | ASKFIRST); /* Wait for any signal (typically it's SIGCHLD) */ check_delayed_sigs(NULL); /* NULL timespec makes it wait */ /* Wait for any child process(es) to exit */ while (1) { pid_t wpid; struct init_action *a; wpid = waitpid(-1, NULL, WNOHANG); if (wpid <= 0) break; a = mark_terminated(wpid); if (a) { message(L_LOG, "process '%s' (pid %u) exited. " "Scheduling for restart.", a->command, (unsigned)wpid); } } /* Don't consume all CPU time - sleep a bit */ sleep1(); } /* while (1) */ ~~~ 至此,shell控制台就创立完成了 至此smart启动时内核层和应用层所作的工作差不多就分析完了,如果有分析错误的地方,欢迎大家指出~
6
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
zhuzhuzhu
这家伙很懒,什么也没写!
文章
2
回答
1
被采纳
0
关注TA
发私信
相关文章
1
rt-smart发布时间
2
rt-smart qemu-vexpress-a9 编译报错
3
rt-smart分支编译rasp4-32bsp报错
4
rt-smart qemu-vexpress-a9 win10编译脚本问题
5
rt-smart qemu-vexpress-a9 linux 下crtl+c
6
rt-smart + pthread 编译报错
7
rt-smart的rt_channel实现问题
8
关于rt-smart的musl-libc
9
RT-Smart Windows 编译 qemu-vexpress-a9 出错
10
用户程序在RT-Smart存在的方式
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
WIZnet_W5500
UART
ota在线升级
PWM
cubemx
freemodbus
flash
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
Debug
编译报错
msh
SFUD
keil_MDK
rt_mq_消息队列_msg_queue
ulog
C++_cpp
at_device
本月问答贡献
踩姑娘的小蘑菇
7
个答案
3
次被采纳
a1012112796
13
个答案
2
次被采纳
张世争
9
个答案
2
次被采纳
rv666
5
个答案
2
次被采纳
用户名由3_15位
11
个答案
1
次被采纳
本月文章贡献
程序员阿伟
9
篇文章
2
次点赞
hhart
3
篇文章
4
次点赞
大龄码农
1
篇文章
5
次点赞
ThinkCode
1
篇文章
1
次点赞
Betrayer
1
篇文章
1
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部