本帖最后由 永不停息 于 2018-12-25 09:34 编辑 ! j5 I/ p# Q5 G$ o5 f
% m/ Y' g- h) i h; m3 Y【RT-Thread内核实现与应用开发实战指南】本书总共分为两个部分,第一部分主要剖析 RT-Thread 内核的实现机理,通过代码从操作系统最基本的线程创建和线程切换开始,一步一步增加代码功能(线程阻塞、时间片、优先级、临界区等)
; F9 e* ^, e8 q由浅入深从开发者的角度教读者怎么从零实现基本的操作系统内核。虽然内容不是很细致,但是能够帮助读者抓住重点,9 @, X' ^3 q- |6 d3 I/ |
让读者能够最快速的了解操作系统的核心,读完这部分然后再去阅读源码会比直接阅读源码事半功倍。第二部分内容着重
3 `+ S4 C" |' L6 z) n! `RT-Thread 的应用,分别介绍操作系统各种机制(信号量、互斥量、时间、邮箱等)的工作机理以及相关的接口函数,读者
: ]1 _- v% V s可以通过这部分去熟悉 RT-Thread 的应用。. A% D( F# S0 ^0 k" e% K
) k9 W/ o: l- L8 @0 ?- g' P3 V
由于本人之前对一些操作系统(FreeRTOS)也有所学习和应用,但是没有深入去学习源码,对于操作系统内核的实现机制
8 n; m+ c" Z5 E# f9 A u2 ^/ \" t并不是很了解,因此这次也将学习重点放在第一部分,下面针对第一部分的线程创建和切换这部分进行总结。
: G' c% j! ], v2 y8 f! e& Y0 _/ x7 Q2 p
对于操作系统而言,线程是最基本的概念,什么是线程呢?线程是程序执行的最小单元,每个线程都拥有独立的栈空间
7 T4 R. {" D4 K, m' C2 L,是系统调度的基本单元。操作系统最核心的地方也在于线程的调度,系统为了方便线程调度,为每个线程都额外定义了# v/ X! ^' p% Q0 v4 c D
线程控制块,线程控制块的定义如下:
5 I4 _# a6 y6 ]& V+ g0 d) D- /**' I& q/ ?" A) o
- * Thread structure
7 I- B) C& J" ~9 A1 ] - *// c6 J- Z% O4 l7 U0 x5 n
- struct rt_thread( J" I' H ?& t: o: G
- {
' {& r* k Z }2 C2 b - /* rt object */
4 P" M8 j' \( t: o5 J6 A, S; n - char name[RT_NAME_MAX]; /**< the name of thread */
1 x5 X$ {5 ]7 ^. f I+ D - rt_uint8_t type; /**< type of object */
& W# W: x5 `" A* K( Z. j - rt_uint8_t flags; /**< thread's flags */
; i% A6 ~( K# S
, C. }# f2 K6 C' k4 ]2 d5 ~- rt_list_t list; /**< the object list */
! `3 a9 V$ l/ X" @9 l% ^ - rt_list_t tlist; /**< the thread list */; c4 |" T) ~. F1 O3 c9 J4 ]8 b
- 6 U% a) \ x3 H3 Q1 J& H7 X
- /* stack point and entry */! y2 z; d6 Q: a, v3 `* B
- void *sp; /**< stack point */
) r3 h! D/ _' S$ n1 c - void *entry; /**< entry */+ S- _& O2 F5 ~/ @ Z( Y
- void *parameter; /**< parameter */! V- |; N( i2 k$ t) W9 i# q
- void *stack_addr; /**< stack address */
! P& N$ F# K- c) ?* ^' l - rt_uint32_t stack_size; /**< stack size */( K1 [3 @0 y6 c/ _/ }
9 t4 w& O! U- T4 r- /* error code */
. y, c1 I/ o* P# h& @' q! O4 h) @ - rt_err_t error; /**< error code */! J2 Z/ D# j: o% ]8 e6 S
- + p9 s+ Q# ]" V( q
- rt_uint8_t stat; /**< thread status */0 l1 h2 D- S2 U: r
" [6 B$ B" }" c# U# C- /* priority */
* ]9 R% h) f5 b0 A# ] - rt_uint8_t current_priority; /**< current priority */ \: e9 `3 y# a# q3 w" D3 a' v( a- Y
- rt_uint8_t init_priority; /**< initialized priority */
复制代码 其中 tlist 成员是线程的链表节点,后面要把线程插入到各种双向链表中便是通过该链表节点实现,该链表节点定义如下:* S- l5 G$ g2 H: l. S; m
- struct rt_list_node
0 T ]. \7 ^. ? - {
: S* l$ |+ i% l9 A9 m/ e2 _ - struct rt_list_node *next; /**< point to next node. */3 o1 j' ]. p$ o `
- struct rt_list_node *prev; /**< point to prev node. */
. s0 M) c+ E9 l, i# ~, l - };
! n9 T- @3 {. [5 R - typedef struct rt_list_node rt_list_t; /**< Type for lists. */
复制代码 线程初始化通过函数 rt_thread_init(struct rt_thread *thread) 实现,主要进行线程控制块相关成员的初始化以及初始化线程栈
4 R1 ?6 |" l {9 z具体实现如下所示:& B/ P) z6 N2 m; X
- static rt_err_t _rt_thread_init(struct rt_thread *thread,
/ C1 m* ~ r6 k3 K8 m; V, ? - const char *name,. o) ]& V9 d* W: r' d& ?
- void (*entry)(void *parameter),
1 S& }' t" |" I - void *parameter,
4 U8 E! w# _1 k; X, {; z - void *stack_start,* ~5 q6 D: h+ u1 m
- rt_uint32_t stack_size,% x7 d$ x8 l x( y4 w' G
- rt_uint8_t priority,
" S( f$ R6 J; G( y: J - rt_uint32_t tick)
7 q5 S' g+ U. O% I- }) T+ @ - {
1 v; E8 D) s1 o2 I8 x' H# k2 a - /* init thread list */
; M" V; e7 x% O/ ^+ ` - rt_list_init(&(thread->tlist));( M! v* N' M% F9 T8 \ r
H! B2 Q. O" K) T# M% {6 `- thread->entry = (void *)entry;
, K' R3 n/ i2 Y$ v5 E - thread->parameter = parameter;
6 z3 b! Z" l6 v) u( m
2 U$ F& |- B3 O3 G, o) {- /* stack init */
& ^$ p2 v G' @/ \, p: \9 d - thread->stack_addr = stack_start;1 s7 m; M' j3 C* ^
- thread->stack_size = stack_size;
. H! j- j! M; u; e
/ K% K, T# t7 b' D0 U4 v$ q- /* init thread stack */5 Q, C7 g2 c% |* x. U( A
- rt_memset(thread->stack_addr, '#', thread->stack_size);
" w2 v2 y# }$ W1 t" Y7 _ - thread->sp = (void *)rt_hw_stack_init(thread->entry, thread->parameter,
8 ^1 R( |8 S% h: ?- \% b - (void *)((char *)thread->stack_addr + thread->stack_size - 4),
4 t, \8 d4 r/ e( m2 L4 | - (void *)rt_thread_exit);
/ ~( W7 o$ m% ~/ H) r/ O - 2 p, j! ^5 p2 x$ ?; I$ }
- /* priority init */
; ?: e _; q: m6 n, y - RT_ASSERT(priority < RT_THREAD_PRIORITY_MAX);! A/ \0 D6 W5 t. M/ ^/ {
- thread->init_priority = priority;: E) v9 O, A5 o+ x$ p
- thread->current_priority = priority;
; [4 \/ E+ m6 q- S. ~& g9 a - # z* M9 k# j- E5 b
- thread->number_mask = 0;1 a" \- l$ O. k4 U, U; _ ]
- $ Q1 z8 ~; r8 b( U% l8 T
- /* tick init */
! h: P6 i, _. p, r Q - thread->init_tick = tick;
( `& ]* R: u' {/ \# k/ K3 o+ l; e - thread->remaining_tick = tick;
' R9 g4 v3 s- R- U: b. D8 c - * M9 ?5 @: ]" q7 a! Y; W
- /* error and flags */( l: n; w% S7 D+ h. \1 s7 {
- thread->error = RT_EOK;1 r/ R- W' M) _! `
- thread->stat = RT_THREAD_INIT;
6 T" t2 _) ?0 C$ V5 p: |0 q# s
- H/ d# E, u/ l n- /* initialize cleanup function and user data */* H7 D4 s( x' g
- thread->cleanup = 0;
! q$ _, x8 |1 Q4 y+ P; q+ V - thread->user_data = 0;9 n8 q" J8 I. q+ g; ^( V
- 2 _0 w' b' ?, @2 w& p3 X
- /* init thread timer */
& k6 Z# z. b. |3 [9 I: X - rt_timer_init(&(thread->thread_timer),
$ I' g! u l5 u# N2 V - thread->name,
7 [) q. m% ]9 Q/ ?5 U% Y - rt_thread_timeout,2 A ~' M/ B" h/ h5 I) d$ q
- thread,
3 b; I% {4 j# D9 |* x" O5 c - 0,
0 R, Z! t0 d- U - RT_TIMER_FLAG_ONE_SHOT);
: } p3 r U2 U/ y# ~* K3 W - 4 v" i! P& X A& h6 |
- RT_OBJECT_HOOK_CALL(rt_thread_inited_hook, (thread));
) X9 O3 M7 d5 ?# e' o
# N6 q' \" m2 z, V) A1 i- return RT_EOK;
$ }& V( T+ s9 Y: G1 F' | ~ - }
复制代码 从上面的函数中可以看出线程栈的初始化由函数 rt_hw_stack_init() 来完成,每个线程的栈由三部分组成:自动加载到 CPU 寄存器的部分、手动加载到 CPU 寄存器的部分以及空闲栈。如下图所示:1 c- d2 q( q# I" H
- Q- v; S/ O8 y; W- w2 g* r+ u
初始化后除了空闲栈部分其他值都变成 0xdeadbeef,线程第一次运行时,自动加载到 CPU 部分的内容要预先设置好,3 ^1 x! L7 l* l/ I) n# ?9 a
包括 PC 指针、R0任务形参,其它设置为 0。
8 f" y% ]' z/ B) J. n! R线程初始化后要插入到就绪列表中,通过函数 rt_list_insert_before() 实现,最后要运行线程则需要启动调度器,
; X+ X" I- k6 B启用调度器通过函数 rt_system_scheduler_start() 实现,具体函数实现如下:# a; j2 z* [2 g J7 o. t! r
- <blockquote>void rt_system_scheduler_start(void)
复制代码 函数中 rt_hw_context_switch_to() 用于启动第一次线程切换,该函数通过触发 PendSV 中断来实现上下文切换,后面的
% O9 J0 }5 N7 R( J) x. h* B$ ?) n上下文切换则通过函数 rt_hw_context_switch() 实现。最后系统调度通过 rt_scheduer() 函数实现,其核心内容也是通过4 l( D* Z4 Z& m1 s
获取下一个要执行的线程然后产生上下文切换,在此不详细说明。
8 z. ], A& Q2 d; U- h- V' P: E, }$ K' P9 b6 Y8 v3 j4 y6 Q
$ M1 [' a1 Z/ S第一次分享帖子,写得不好多多见谅。帖子内容有限,只是说明一下我个人对于其中的一点理解,各位有兴趣的还是得亲
7 H- }% P; x3 F$ P0 }! q' f* G6 ^自去看书,个人觉得书是挺好的,由浅入深,循序渐进,但是看完书后最重要的还是得多实践,要想深入内核原理的还得
$ t; |, H+ [; ?慢慢啃源码。
+ n, o; V4 d; b: @0 W9 R# f0 {. l8 j
' w/ ^/ i) y& E% `+ n' V3 a |