1 ps 和 list_thread 命令的关系
查看 ps 命令的源码,如下所示。从源码中可以看到 ps 命令执行后调用了 list_thread() 函数,因此这两个命令执行的效果是一致的。
// 文件 rt-thread/components/finsh/msh.c
int cmd_ps(int argc, char **argv)
{
extern long list_thread(void);
extern int list_module(void);
#ifdef RT_USING_MODULE
if ((argc == 2) && (strcmp(argv[1], "-m") == 0))
list_module();
else
#endif
list_thread(); // 调用了 list_thread() 函数
return 0;
}
MSH_CMD_EXPORT_ALIAS(cmd_ps, ps, List threads in the system.);
2 ps / list_thread 命令结果含义解析
本文以 led 线程为例进行分析,创建的 led 处理线程代码如下所示,其中线程的任务栈大小为1024Bytes,优先级为10,时间片大小为20tick。
#include <rtthread.h>
#include "drv_common.h"
#include "rtdevice.h"
#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#define LED_RTTPIN (GET_PIN(A, 8))
rt_thread_t tid1 = RT_NULL; // 线程控制块句柄
static void led_thread_entry(void *param)
{
while (1)
{
rt_pin_write(LED_RTTPIN, PIN_LOW);
rt_thread_mdelay(500);
rt_pin_write(LED_RTTPIN, PIN_HIGH);
rt_thread_mdelay(500);
}
}
void led_init(void)
{
rt_pin_mode(LED_RTTPIN, PIN_MODE_OUTPUT); // LED引脚初始化
// 动态创建线程
tid1 = rt_thread_create("led_thread", led_thread_entry, RT_NULL, 1024, 10, 20);
if (tid1 != RT_NULL)
{
rt_thread_startup(tid1); // 启动线程
}
}
在控制台输入 ps 命令查看线程状态,结果如下所示。
msh >ps
thread pri status sp stack size max used left tick error
led_thread 10 suspend 0x000000ac 0x00000400 16% 0x00000014 000
tshell 20 running 0x000000cc 0x00001000 15% 0x00000005 000
tidle0 31 ready 0x00000058 0x00000100 43% 0x00000008 000
timer 4 suspend 0x00000078 0x00000200 23% 0x00000009 000
main 10 suspend 0x000000b8 0x00000800 14% 0x0000000f 000
msh >
每一列的含义如下:
其中 线程错误码 含义如下:
#define RT_EOK 0 // 无错误
#define RT_ERROR 1 // 普通错误
#define RT_ETIMEOUT 2 // 超时错误
#define RT_EFULL 3 // 资源已满
#define RT_EEMPTY 4 // 无资源
#define RT_ENOMEM 5 // 无内存
#define RT_ENOSYS 6 // 系统不支持
#define RT_EBUSY 7 // 系统忙
#define RT_EIO 8 // IO 错误
#define RT_EINTR 9 // 中断系统调用
#define RT_EINVAL 10 // 非法参数
3 对应源码分析
3.1 线程名字和优先级的打印
本文以动态创建线程为例进行讲述。在使用函数 rt_thread_create() 动态创建线程时会传入线程的名字和优先级, 线程的名字在函数 rt_object_allocate() 里面进行初始化,线程的优先级在函数 _thread_init() 里面进行初始化,代码如下。为什么有一个初始优先级一个当前优先级呢?是因为在运行过程中,使用信号量时可能会发生优先级翻转的问题,解决办法是临时将相应的低优先级线程的优先级提高,此时修改线程的当前优先级,在函数 rt_thread_control() 进行操作。
/* priority init */
RT_ASSERT(priority < RT_THREAD_PRIORITY_MAX);
thread->init_priority = priority;
thread->current_priority = priority;
线程名字和优先级打印的代码如下。其中 maxlen 的大小也是宏定义 RT_NAME_MAX,该宏在 RT-Thread Settings 中可以通过设置内核对象名称的最大长度进行修改,从而控制打印出的线程名字的长度,另外打印的线程的优先级也是线程的当前的优先级。
// 文件 rt-thread/components/finsh/cmd.c 中的函数 list_thread()
rt_kprintf("%-.s %3d ", maxlen, RT_NAME_MAX, thread->name, thread->current_priority);
注:在上述代码中的 %-.s 格式控制符中的 - 表示打印时左对齐,右边填空格,默认是按照右对齐进行打印;.前面的 定义了输出的总宽度,用 maxlen进行限制;.后面的 定义了输出字符的个数,用 RT_NAME_MAX 进行限制,若实际位数大于所定义的精度数,则截去超过的部分。
3.2 线程状态的打印
线程运行的过程中,同一时间内只允许一个线程在处理器中运行,从运行的过程上划分,线程有多种不同的运行状态,如初始状态、挂起状态、就绪状态等。在 RT-Thread 中,线程包含五种状态,操作系统会自动根据它运行的情况来动态调整它的状态。
线程状态打印的代码如下。根据线程控制块句柄中的成员 stat 的不同值来打印出线程当前的状态。
// 文件 rt-thread/components/finsh/cmd.c 中的函数 list_thread()
stat = (thread->stat & RT_THREAD_STAT_MASK);
if (stat == RT_THREAD_READY) rt_kprintf(" ready ");
else if (stat == RT_THREAD_SUSPEND) rt_kprintf(" suspend");
else if (stat == RT_THREAD_INIT) rt_kprintf(" init ");
else if (stat == RT_THREAD_CLOSE) rt_kprintf(" close ");
else if (stat == RT_THREAD_RUNNING) rt_kprintf(" running");
3.3 线程栈空间信息及错误代码的打印
在线程创建和初始化时对线程栈空间的大小和线程当前的栈位置以及线程栈空间全部初始化为 '#' 相关代码如下。
static rt_err_t _thread_init(struct rt_thread *thread,
const char *name,
void (*entry)(void *parameter),
void *parameter,
void stack_start,
rt_uint32_t stack_size,
rt_uint8_t priority,
rt_uint32_t tick)
{
/ init thread list */
rt_list_init(&(thread->tlist));
thread->entry = (void )entry;
thread->parameter = parameter;
/ stack init /
thread->stack_addr = stack_start; // 线程栈的起始地址
thread->stack_size = stack_size; // 线程栈的大小,创建时指定
/ init thread stack */
rt_memset(thread->stack_addr, '#', thread->stack_size); // 将线程栈空间初始化为 '#'
#ifdef ARCH_CPU_STACK_GROWS_UPWARD
thread->sp = (void *)rt_hw_stack_init(thread->entry, thread->parameter,
(void *)((char *)thread->stack_addr),
(void *)_thread_exit);
#else // 向下增长的栈,STM32为向下增长的栈
thread->sp = (void *)rt_hw_stack_init(thread->entry, thread->parameter, (rt_uint8_t *)((char *)thread->stack_addr + thread->stack_size - sizeof(rt_ubase_t)), (void *)_thread_exit); // 计算线程当前的栈位置
#endif /* ARCH_CPU_STACK_GROWS_UPWARD */
/* priority init */
RT_ASSERT(priority < RT_THREAD_PRIORITY_MAX);
thread->init_priority = priority; // 线程初始优先级的初始化
thread->current_priority = priority; // 线程当前优先级的初始化
thread->number_mask = 0;
#if RT_THREAD_PRIORITY_MAX > 32
thread->number = 0;
thread->high_mask = 0;
#endif /* RT_THREAD_PRIORITY_MAX > 32 */
/* tick init */
thread->init_tick = tick; // 线程时间片大小的初始化,时间片大小创建时指定
thread->remaining_tick = tick; // 线程剩余时间片大小的初始化
/* error and flags */
thread->error = RT_EOK; // 线程错误码初始化
thread->stat = RT_THREAD_INIT; // 线程状态初初始化
}
通过分析上面的代码,结合创建的 led_thread 线程,使用在线调试查看线程控制控制块句柄个成员的值,初始化后该线程线程栈的空间分布如下图所示。图中定义的线程栈的总的大小为 1024Bytes,线程栈的起始地址为 0x20002FEC,终止地址为 0x20003340,线程栈的当前的栈位置为 0x20003340,所以已使用的线程栈的大小为 0x200033EC - 0x20003340 = 0xAC = 172(十进制),与上面使用 ps 指令打印出的结果一致。
线程栈空间信息及错误代码打印的相关代码如下所示,结合上图中的线程栈空间的分布图,因为在初始化时将线程栈的空间都初始化为 '#',下面代码中根据线程栈空间内容是否为 '#' 找到了已使用线程栈空间的结束位置,并将其赋值给变量 ptr,所以打印出的第一个参数值得含义为当前线程栈位置距离线程栈栈底的距离。由于使用的线程栈空间不为 '#' ,所以即使申请过的栈空间又进行释放,在再次执行 ps / list_thread 命令时,变量 ptr 的值仍然为找到的不为字符 '#'的位置,所以打印出的第三个参数为在运行过程中线程栈空间的最大使用率,在实际项目中可以根据该值灵活的修改线程栈空间的大小,在设置栈空间大小时要留有一定的余量,通常控制栈空间使用率在 70%~80% 较为合适。
// 文件 rt-thread/components/finsh/cmd.c 中的函数 list_thread()
ptr = (rt_uint8_t *)thread->stack_addr; // 将 ptr 赋值为线程栈空间的起始地址
while (*ptr == '#')ptr ++; // 找到已使用线程栈空间的结束位置
rt_kprintf(" 0x%08x 0x%08x %02d%% 0x%08x %03d\n",
thread->stack_size + ((rt_ubase_t)thread->stack_addr - (rt_ubase_t)thread->sp), // 当前线程栈位置距离线程栈栈底的距离
thread->stack_size, // 线程栈大小
(thread->stack_size - ((rt_ubase_t) ptr - (rt_ubase_t) thread->stack_addr)) * 100 / thread->stack_size, // 线程栈最大使用率
thread->remaining_tick, // 线程栈剩余时间片的大小
thread->error); // 线程错误码
原作者:crystal266