https://liu-yaohua.github.io

0%

rt-thread内核

嵌入式系统和RT-Thread

在裸机系统中,所有的程序基本都是自己写的,所有的操作都是在一个无限的大循环里面实现。现实生活中的很多中小型的电子产品用的都是裸机系统,而且也能够满足需求。但是为什么还要学习RTOS 编程,偏偏还要整个操作系统进来。一是项目需要,随着产品要实现的功能越来越多,单纯的裸机系统已经不能够完美地解决问题,反而会使编程变得更加复杂,如果想降低编程的难度,我们可以考虑引入RTOS 实现多线程管理,这是使用RTOS 的最大优势。二是学习的需要,必须学习更高级的东西,实现更好的职业规划,为将来走向人生巅峰迎娶白富美做准备,而不是一味的在裸机编程上面死磕。作为一个合格的嵌入式软件工程师,学习是永远不能停歇的事,时刻都得为将来准备。书到用时方恨少,我希望机会来临时你不要有这种感觉。

可偏偏在10 几年前,在中国,有一个天赋异禀,倔强不屈的极客,他叫熊谱翔,编写了RT-Thread 初代内核,并联合中国开源社区的极客不断完善,推陈出新,经过10 几年的发展,如今占据国产RTOS 的鳌头,每年递增数十万的开发者,加上如今AI 和物联网等风口,让RT-Thread 有一统江湖之势,从今年完成A 轮数百万美元的融资就可以看出,在未来不出5 年,RT-Thread 将是你学习和做产品的不二之选。

裸机系统和多线程系统

裸机系统

裸机系统通常分成轮询系统和前后台系统

轮询系统

轮询系统即是在裸机编程的时候,先初始化好相关的硬件,然后让主程序在一个死循环里面不断环,顺序地做各种事情。轮询系统是一种非常简单的软件结构,通常只适用于那些只需要顺序执行代码且不需要外部事件来驱动的就能完成的事情。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int main(void)
{
/* 硬件相关初始化 */
HardWareInit();

/* 无限循环 */
for (;;) {
/* 处理事情1 */
DoSomething1();

/* 处理事情2 */
DoSomething2();

/* 处理事情3 */
DoSomething3();
}

}
前后台系统

相比轮询系统,前后台系统是在轮询系统的基础上加入了中断。外部事件的响应在中断里面完成,事件的处理还是回到轮询系统中完成,中断在这里我们称为前台,main 函数里面的无限循环我们称为后台。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
1 int flag1 = 0;
2 int flag2 = 0;
3 int flag3 = 0;
4
5 int main(void)
6 {
7 /* 硬件相关初始化 */
8 HardWareInit();
9
10 /* 无限循环 */
11 for (;;) {
12 if (flag1) {
13 /* 处理事情1 */
14 DoSomething1();
15 }
16
17 if (flag2) {
18 /* 处理事情2 */
19 DoSomething2();
20 }
21
22 if (flag3) {
23 /* 处理事情3 */
24 DoSomething3();
25 }
26 }
27 }
28
29 void ISR1(void)
30 {
31 /* 置位标志位 */
32 flag1 = 1;
33 /* 如果事件处理时间很短,则在中断里面处理
34 如果事件处理时间比较长,在回到前台处理 */
35 DoSomething1();
36 }
37
38 void ISR2(void)
39 {
40 /* 置位标志位 */
41 flag2 = 1;
42
43 /* 如果事件处理时间很短,则在中断里面处理
44 如果事件处理时间比较长,在回到前台处理 */
45 DoSomething2();
46 }
47
48 void ISR3(void)
49 {
50 /* 置位标志位 */
51 flag3 = 1;
52
53 /* 如果事件处理时间很短,则在中断里面处理
54 如果事件处理时间比较长,在回到前台处理 */
55 DoSomething3();
56 }

多线程系统

相比前后台系统,多线程系统的事件响应也是在中断中完成的,但是事件的处理是在线程中完成的。在多线程系统中,线程跟中断一样,也具有优先级,优先级高的线程会被优先执行。当一个紧急的事件在中断被标记之后,如果事件对应的线程的优先级足够高,就会立马得到响应。相比前后台系统,多线程系统的实时性又被提高了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
1 int flag1 = 0;
2 int flag2 = 0;
3 int flag3 = 0;
4
5 int main(void)
6 {
7 /* 硬件相关初始化 */
8 HardWareInit();
9
10 /* OS 初始化 */
11 RTOSInit();
12
13 /* OS 启动,开始多线程调度,不再返回 */
14 RTOSStart();
15 }
16
17 void ISR1(void)
18 {
19 /* 置位标志位 */
20 flag1 = 1;
21 }
22
23 void ISR2(void)
24 {
25 /* 置位标志位 */
26 flag2 = 2;
27 }
28
29 void ISR3(void)
30 {
31 /* 置位标志位 */
32 flag3 = 1;
33 }
34
35 void DoSomething1(void)
36 {
37 /* 无限循环,不能返回 */
38 for (;;) {
39 /* 线程实体 */
40 if (flag1) {
41
42 }
43 }
44 }
45
46 void DoSomething2(void)
47 {
48 /* 无限循环,不能返回 */
49 for (;;) {
50 /* 线程实体 */
51 if (flag2) {
52
53 }
54 }
55 }
56
57 void DoSomething3(void)
58 {
59 /* 无限循环,不能返回 */
60 for (;;) {
61 /* 线程实体 */
62 if (flag3) {
63
64 }
65 }
66 }

相比前后台系统中后台顺序执行的程序主体,在多线程系统中,根据程序的功能,我们把这个程序主体分割成一个个独立的,无限循环且不能返回的小程序,这个小程序我们称之为线程。每个线程都是独立的,互不干扰的,且具备自身的优先级,它由操作系统调度管理。加入操作系统后,我们在编程的时候不需要精心地去设计程序的执行流,不用担心每个功能模块之间是否存在干扰。加入了操作系统,我们的编程反而变得简单了。整个系统随之带来的额外开销就是操作系统占据的那一丁点FLASH 和RAM。现如今,单片机的FLASH 和RAM是越来越大,完全足以抵挡RTOS 那点开销。

线程

在裸机系统中,系统的主体就是main 函数里面顺序执行的无限循环,这个无限循环里面CPU 按照顺序完成各种事情。在多线程系统中,我们根据功能的不同,把整个系统分割成一个个独立的且无法返回的函数,这个函数我们称为线程。

1
2
3
4
5
6
7
1 void thread_entry (void *parg)
2 {
3 /* 线程主体,无限循环且不能返回 */
4 for (;;) {
5 /* 线程主体代码 */
6 }
7 }

线程栈

在多线程系统中,每个线程都是独立的,互不干扰的,所以要为每个线程都分配独立的栈空间,这个栈空间通常是一个预先定义好的全局数组,也可以是动态分配的一段内存空间,但它们都存在于RAM 中。在多线程系统中,有多少个线程就需要定义多少个线程栈。

1
2
3
/* 定义线程栈 */
rt_uint8_t rt_flag1_thread_stack[512]; (1)
rt_uint8_t rt_flag2_thread_stack[512];

线程控制块

在裸机系统中,程序的主体是CPU 按照顺序执行的。而在多线程系统中,线程的执行是由系统调度的。系统为了顺利的调度线程,为每个线程都额外定义了一个线程控制块,这个线程控制块就相当于线程的身份证,里面存有线程的所有信息,比如线程的栈指针,线程名称,线程的形参等。有了这个线程控制块之后,以后系统对线程的全部操作都可以通过这个线程控制块来实现。

1
2
3
4
5
6
7
8
9
1 struct rt_thread (1)
2 {
3 void *sp; /* 线程栈指针 */
4 void *entry; /* 线程入口地址 */
5 void *parameter; /* 线程形参 */
6 void *stack_addr; /* 线程栈起始地址 */
7 rt_uint32_t stack_size; /* 线程栈大小,单位为字节 */
8 };
9 typedef struct rt_thread *rt_thread_t; (2)

线程创建

线程的栈,线程的函数实体,线程的控制块最终需要联系起来才能由系统进行统一调度。那么这个联系的工作就由线程初始化函数rt_thread_init()来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1 rt_err_t rt_thread_init(struct rt_thread *thread, (1)
2 void (*entry)(void *parameter), (2)
3 void *parameter, (3)
4 void *stack_start, (4)
5 rt_uint32_t stack_size) (5)
6 {
7 rt_list_init(&(thread->tlist)); (6)
8
9 thread->entry = (void *)entry; (7)
10 thread->parameter = parameter; (8)
11
12 thread->stack_addr = stack_start; (9)
13 thread->stack_size = stack_size; (10)
14
15 /* 初始化线程栈,并返回线程栈指针 */ (11)
16 thread->sp =
17 (void *)rt_hw_stack_init( thread->entry,
18 thread->parameter,
19 (void *)((char *)thread->stack_addr + thread->stack_size - 4) );
20
21 return RT_EOK; (12)
22 }

在main 函数中创建两个flag 相关的线程实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1 int main(void)
2 {
3 /* 硬件初始化 */
4 /* 将硬件相关的初始化放在这里,如果是软件仿真则没有相关初始化代码 */
5
6
7 /* 初始化线程 */
8 rt_thread_init( &rt_flag1_thread, /* 线程控制块 */
9 flag1_thread_entry, /* 线程入口地址 */
10 RT_NULL, /* 线程形参 */
11 &rt_flag1_thread_stack[0], /* 线程栈起始地址 */
12 sizeof(rt_flag1_thread_stack) ); /* 线程栈大小,单位为字节
*/
13
14 /* 初始化线程 */
15 rt_thread_init( &rt_flag2_thread, /* 线程控制块 */
16 flag2_thread_entry, /* 线程入口地址 */
17 RT_NULL, /* 线程形参 */
18 &rt_flag2_thread_stack[0], /* 线程栈起始地址 */
19 sizeof(rt_flag2_thread_stack) ); /* 线程栈大小,单位为字节 */

就绪列表

线程创建好之后,我们需要把线程添加到就绪列表里面,表示线程已经就绪,系统随时可以调度。

1
2
1 /* 线程就绪列表 */
2 rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX]; (1)

线程控制块里面有一个tlist 成员,数据类型为rt_list_t,我们将线程插入到就绪列表里面,就是通过将线程控制块的tlist 这个节点插入到就绪列表中来实现的。如果把就绪列表比作是晾衣杆,线程是衣服,那tlist 就是晾衣架,每个线程都自带晾衣架,就是为了把自己挂在各种不同的链表中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1 /* 初始化线程 */
2 rt_thread_init( &rt_flag1_thread, /* 线程控制块 */
3 flag1_thread_entry, /* 线程入口地址 */
4 RT_NULL, /* 线程形参 */
5 &rt_flag1_thread_stack[0], /* 线程栈起始地址 */
6 sizeof(rt_flag1_thread_stack) ); /* 线程栈大小,单位为字节 */
7 /* 将线程插入到就绪列表 */
8 rt_list_insert_before( &(rt_thread_priority_table[0]),&(rt_flag1_thread.tlist) );
9
10 /* 初始化线程 */
11 rt_thread_init( &rt_flag2_thread, /* 线程控制块 */
12 flag2_thread_entry, /* 线程入口地址 */
13 RT_NULL, /* 线程形参 */
14 &rt_flag2_thread_stack[0], /* 线程栈起始地址 */
15 sizeof(rt_flag2_thread_stack) ); /* 线程栈大小,单位为字节 */
16 /* 将线程插入到就绪列表 */
17 rt_list_insert_before( &(rt_thread_priority_table[1]),&(rt_flag2_thread.tlist) );

调度器

调度器是操作系统的核心,其主要功能就是实现线程的切换,即从就绪列表里面找到优先级最高的线程,然后去执行该线程。

调度器在使用之前必须先初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1 /* 初始化系统调度器 */
2 void rt_system_scheduler_init(void)
3 {
4 register rt_base_t offset; (1)
5
6
7 /* 线程就绪列表初始化 */
8 for (offset = 0; offset < RT_THREAD_PRIORITY_MAX; offset ++) (2)
9 {
10 rt_list_init(&rt_thread_priority_table[offset]);
11 }
12
13 /* 初始化当前线程控制块指针 */
14 rt_current_thread = RT_NULL; (3)
15 }

我们把调度器初始化放在硬件初始化之后,线程创建之前:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
1 int main(void)
2 {
3 /* 硬件初始化 */
4 /* 将硬件相关的初始化放在这里,如果是软件仿真则没有相关初始化代码 */
5
6 /* 调度器初始化 */
7 rt_system_scheduler_init();
8
9
10 /* 初始化线程 */
11 rt_thread_init( &rt_flag1_thread, /* 线程控制块 */
12 flag1_thread_entry, /* 线程入口地址 */
13 RT_NULL, /* 线程形参 */
14 &rt_flag1_thread_stack[0], /* 线程栈起始地址 */
15 sizeof(rt_flag1_thread_stack) ); /* 线程栈大小,单位为字节 */
16 /* 将线程插入到就绪列表 */
17 rt_list_insert_before( &(rt_thread_priority_table[0]),&(rt_flag1_thread.tlist) );
18
19 /* 初始化线程 */
20 rt_thread_init( &rt_flag2_thread, /* 线程控制块 */
21 flag2_thread_entry, /* 线程入口地址 */
22 RT_NULL, /* 线程形参 */
23 &rt_flag2_thread_stack[0], /* 线程栈起始地址 */
24 sizeof(rt_flag2_thread_stack) ); /* 线程栈大小,单位为字节 */
25 /* 将线程插入到就绪列表 */
26 rt_list_insert_before( &(rt_thread_priority_table[1]),&(rt_flag2_thread.tlist) );
27 }

初始化调度器后,我们就可以启动调度器啦,调度器启动由函数rt_system_scheduler_start()来完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1 /* 启动系统调度器 */
2 void rt_system_scheduler_start(void)
3 {
4 register struct rt_thread *to_thread;
5
6
7 /* 手动指定第一个运行的线程 */ (1)
8 to_thread = rt_list_entry(rt_thread_priority_table[0].next,
9 struct rt_thread,
10 tlist);
11 rt_current_thread = to_thread; (2)
12
13 /* 切换到第一个线程,该函数在context_rvds.S 中实现,
14 在rthw.h 声明,用于实现第一次线程切换。
15 当一个汇编函数在C 文件中调用的时候,如果有形参,
16 则执行的时候会将形参传人到CPU 寄存器r0。*/
17 rt_hw_context_switch_to((rt_uint32_t)&to_thread->sp); (3)
18 }

临界段

临界段用一句话概括就是一段在执行的时候不能被中断的代码段。在RT-Thread 里面,这个临界段最常出现的就是对全局变量的操作,全局变量就好像是一个枪把子,谁都可以对他开枪,但是我开枪的时候,你就不能开枪,否则就不知道是谁命中了靶子。那么什么情况下临界段会被打断?一个是系统调度,还有一个就是外部中断。在RTThread,系统调度,最终也是产生PendSV 中断,在PendSV Handler 里面实现线程的切换,所以还是可以归结为中断。既然这样,RT-Thread 对临界段的保护就处理的很干脆了,直接把中断全部关了,NMI FAULT 和硬FAULT 除外。

对象容器

在RT-Thread 中,所有的数据结构都称之为对象。其中线程,信号量,互斥量、事件、邮箱、消息队列、内存堆、内存池、设备和定时器在rtdef.h 中有明显的枚举定义,即为每个对象打上了一个数字标签。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1 enum rt_object_class_type
2 {
3 RT_Object_Class_Thread = 0, /* 对象是线程 */
4 RT_Object_Class_Semaphore, /* 对象是信号量 */
5 RT_Object_Class_Mutex, /* 对象是互斥量 */
6 RT_Object_Class_Event, /* 对象是事件 */
7 RT_Object_Class_MailBox, /* 对象是邮箱 */
8 RT_Object_Class_MessageQueue, /* 对象是消息队列 */
9 RT_Object_Class_MemHeap, /* 对象是内存堆 */
10 RT_Object_Class_MemPool, /* 对象是内存池 */
11 RT_Object_Class_Device, /* 对象是设备 */
12 RT_Object_Class_Timer, /* 对象是定时器 */
13 RT_Object_Class_Module, /* 对象是模块 */
14 RT_Object_Class_Unknown, /* 对象未知 */
15 RT_Object_Class_Static = 0x80 /* 对象是静态对象 */
16 };

容器

在rtt 中,每当用户创建一个对象,如线程,就会将这个对象放到一个叫做容器的地方,这样做的目的是为了方便管理,管理什么?在RT-Thread 的组件finsh 的使用中,就需要使用到容器,通过扫描容器的内核对象来获取各个内核对象的状态,然后输出调试信息。

从代码上看,容器就是一个数组,是一个全局变量,数据类型为struct_rt_object_information,在object.c 中定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
1 static struct rt_object_information (1)
2 rt_object_container[RT_Object_Info_Unknown] = { (2)
3 /* 初始化对象容器 - 线程 */ (3)
4 {
5 RT_Object_Class_Thread, (3)-①
6 _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Thread), (3)-②
7 sizeof(struct rt_thread) (3)-③
8 },
9
10 #ifdef RT_USING_SEMAPHORE (4)
11 /* 初始化对象容器 - 信号量 */
12 {
13 RT_Object_Class_Semaphore,
14 _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Semaphore),
15 sizeof(struct rt_semaphore)
16 },
17 #endif
18
19 #ifdef RT_USING_MUTEX (5)
20 /* 初始化对象容器 - 互斥量 */
21 {
22 RT_Object_Class_Mutex,
23 _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Mutex),
24 sizeof(struct rt_mutex)
25 },
26 #endif
27
28 #ifdef RT_USING_EVENT (6)
29 /* 初始化对象容器 - 事件 */
30 {
31 RT_Object_Class_Event,
32 _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Event),
33 sizeof(struct rt_event)
34 },
35 #endif
36
37 #ifdef RT_USING_MAILBOX (7)
38 /* 初始化对象容器 - 邮箱 */
39 {
40 RT_Object_Class_MailBox,
41 _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_MailBox),
42 sizeof(struct rt_mailbox)
43 },
44 #endif
45
46 #ifdef RT_USING_MESSAGEQUEUE (8)
47 /* 初始化对象容器 - 消息队列 */
48 {
49 RT_Object_Class_MessageQueue,
50 _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_MessageQueue),
51 sizeof(struct rt_messagequeue)
52 },
53 #endif
54
55 #ifdef RT_USING_MEMHEAP (9)
56 /* 初始化对象容器 - 内存堆 */
57 {
58 RT_Object_Class_MemHeap,
59 _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_MemHeap),
60 sizeof(struct rt_memheap)
61 },
62 #endif
63
64 #ifdef RT_USING_MEMPOOL (10)
65 /* 初始化对象容器 - 内存池 */
66 {
67 RT_Object_Class_MemPool,
68 _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_MemPool),
69 sizeof(struct rt_mempool)
70 },
71 #endif
72
73 #ifdef RT_USING_DEVICE (11)
74 /* 初始化对象容器 - 设备 */
75 {
76 RT_Object_Class_Device,
77 _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Device),
78 sizeof(struct rt_device)
79 },
80 #endif
81
82 /* 初始化对象容器 - 定时器 */ (12)
83 /*
84 {
85 RT_Object_Class_Timer,
86 _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Timer),
87 sizeof(struct rt_timer)
88 },
89 */
90 #ifdef RT_USING_MODULE (13)
91 /* 初始化对象容器 - 模块 */
92 {
93 RT_Object_Class_Module,
94 _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Module),
95 sizeof(struct rt_module)
96 },
97 #endif
98 };

对象初始化

每创建一个对象,都需要先将其初始化,主要分成两个部分的工作,首先将对象控制块里面与对象相关的成员初始化,然后将该对象插入到对象容器中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
1 /**
2 * 该函数将初始化对象并将对象添加到对象容器中
3 *
4 * @param object 要初始化的对象
5 * @param type 对象的类型
6 * @param name 对象的名字,在整个系统中,对象的名字必须是唯一的
7 */
8 void rt_object_init(struct rt_object *object, (1)
9 enum rt_object_class_type type, (2)
10 const char *name) (3)
11 {
12 register rt_base_t temp;
13 struct rt_object_information *information;
14
15 /* 获取对象信息,即从容器里拿到对应对象列表头指针 */
16 information = rt_object_get_information(type); (4)
17
18 /* 设置对象类型为静态 */
19 object->type = type | RT_Object_Class_Static; (5)
20
21 /* 拷贝名字 */
22 rt_strncpy(object->name, name, RT_NAME_MAX); (6)
23
24 /* 关中断 */
25 temp = rt_hw_interrupt_disable(); (7)
26
27 /* 将对象插入到容器的对应列表中,不同类型的对象所在的列表不一样 */
28 rt_list_insert_after(&(information->object_list), &(object->list)); (8)
29
30 /* 使能中断 */
31 rt_hw_interrupt_enable(temp); (9)
32 }

对象初始化函数在线程初始化函数里面被调用。

空闲线程

线程体内的延时使用的是软件延时,即还是让CPU 空等来达到延时的效果。使用RTOS 的很大优势就是榨干CPU 的性能,永远不能让它闲着,线程如果需要延时也就不能再让CPU 空等来实现延时的效果。RTOS 中的延时叫阻塞延时,即线程需要延时的时候,线程会放弃CPU 的使用权,CPU 可以去干其它的事情,当线程延时时间到,重新获取CPU 使用权,线程继续运行,这样就充分地利用了CPU 的资源,而不是干等着。

当线程需要延时,进入阻塞状态,那CPU 又去干什么事情了?如果没有其它线程可以运行,RTOS 都会为CPU 创建一个空闲线程,这个时候CPU 就运行空闲线程。在RTThread中,空闲线程是系统在初始化的时候创建的优先级最低的线程,空闲线程主体主要是做一些系统内存的清理工作。鉴于空闲线程的这种特性,在实际应用中,当系统进入空闲线程的时候,可在空闲线程中让单片机进入休眠或者低功耗等操作。

空闲线程也是线程,其定义方法和普通线程定义方法一样。

定义空闲线程栈
1
2
3
4
5
6
7
1 #include <rtthread.h>
2 #include <rthw.h>
3
4 #define IDLE_THREAD_STACK_SIZE 512
5
6 ALIGN(RT_ALIGN_SIZE)
7 static rt_uint8_t rt_thread_stack[IDLE_THREAD_STACK_SIZE];
定义空闲线程的线程控制块
1
2
/* 空闲线程的线程控制块 */
1 struct rt_thread idle;
定义空闲线程函数

在RT-Thread 中空闲线程函数主要是做一些系统内存的清理工作,但是为了简单起见,我们实现的空闲线程只是对一个全局变量rt_idletask_ctr 进行计数

1
2
3
4
5
6
7
8
9
10
1 rt_ubase_t rt_idletask_ctr = 0;
2
3 void rt_thread_idle_entry(void *parameter)
4 {
5 parameter = parameter;
6 while (1)
7 {
8 rt_idletask_ctr ++;
9 }
10 }
空闲线程初始化

当定义好空闲线程的栈,线程控制块和函数主体之后,我们需要空闲线程初始化函数将这三者联系在一起,这样空闲线程才能够被系统调度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1 void rt_thread_idle_init(void)
2 {
3
4 /* 初始化线程 */ (1)
5 rt_thread_init(&idle,
6 "idle",
7 rt_thread_idle_entry,
8 RT_NULL,
9 &rt_thread_stack[0],
10 sizeof(rt_thread_stack));
11
12 /* 将线程插入到就绪列表 */ (2)
13 rt_list_insert_before( &(rt_thread_priority_table[RT_THREAD_PRIORITY_MAX-1]),
14 &(idle.tlist) );
15 }

时间片

在RT-Thread 中,当同一个优先级下有两个或两个以上线程的时候,线程支持时间片功能,即我们可以指定线程持续运行一次的时间,单位为tick。假如有两个线程分别为线程2 和线程3,他们的优先级都为3,线程2 的时间片为2,线程3 的时间片为3。当执行到优先级为3 的线程时,会先执行线程2,直到线程2 的时间片耗完,然后再执行线程3。

本次博客主要记录rt-thread的内核学习,下一篇记录应用。