RT-thread内核之线程源码分析

在RT-Thread实时操作系统中,任务采用了线程来实现,线程是RT-Thread中最基本的调度单位,它描述了一个任务执行的上下文关系,也描述了这个任务所处的优先等级。重要的任务能拥有相对较高的优先级,非重要的任务优先级可以放低,并且可以类似Linux一样具备分时的效果。线程控制块是操作系统用于控制线程的一个数据结构,它会存放线程的一些信息,例如优先级,线程名称等,也包含线程与线程之间连接用的链表结构,线程等待事件集合等。

1、线程控制块(rtdef.h)

/**
 * Thread structure
 */
struct rt_thread
{
    /* rt_object *///这里就是rt_object的结构,其实也可以用rt_object parent来定义,估计线程在早些时候并没有这么做,后来也就没改过来 
    char        name[RT_NAME_MAX];                      /**< the name of thread *///对象的名称
    rt_uint8_t  type;                                   /**< type of object *///对象类型,这里为RT_Object_Class_Thread
    rt_uint8_t  flags;                                  /**< thread‘s flags *///对象标志

#ifdef RT_USING_MODULE
    void       *module_id;                              /**< id of application module *///线程所在的模块ID
#endif
    rt_list_t   list;                                   /**< the object list *///内核对象链表节点
    rt_list_t   tlist;                                  /**< the thread list *///线程链表,一般用作就绪队列元素节点

    /* stack point and entry */
    void       *sp;                                     /**< stack point *///线程的栈指针,这里主要指静态线程,因为动态线程在堆上分配内存
    void       *entry;                                  /**< entry *///线程入口函数
    void       *parameter;                              /**< parameter *///入口函数对应的参数
    void       *stack_addr;                             /**< stack address *///线程栈地址
    rt_uint16_t stack_size;                             /**< stack size *///线程栈大小

    /* error code */
    rt_err_t    error;                                  /**< error code */// 线程错误号,用于IPC机制中,标志是否已经获取成功

    rt_uint8_t  stat;                                   /**< thread stat *///线程的当前状态,如就绪、挂起等

    /* priority */
    rt_uint8_t  current_priority;                       /**< current priority *///当前优先级  
    rt_uint8_t  init_priority;                          /**< initialized priority *///初始优先级  
#if RT_THREAD_PRIORITY_MAX > 32                         //在rtconfig.h定义最大线程优先级,默认为32,最大值可为256
    rt_uint8_t  number;                                 //number, high_mask, number_mask与线程调度时获获取当前最高优先级线程的算法有关,这里对算法不做过多解释
    rt_uint8_t  high_mask;
#endif
    rt_uint32_t number_mask;

#if defined(RT_USING_EVENT)                            //与IPC机制事件相关的一些参数
    /* thread event */
    rt_uint32_t event_set;                             //此线程接收到的事件
    rt_uint8_t  event_info;                            //此线程的事件过滤信息,用于过滤事件,只保留感兴趣的事件
#endif

    rt_ubase_t  init_tick;                              /**< thread‘s initialized tick *///线程初始时钟节拍数
    rt_ubase_t  remaining_tick;                         /**< remaining tick *///线程当次运行剩余时钟节拍数,线程运行时,每隔1个tick,init_tick减1。

    struct rt_timer thread_timer;                       /**< built-in thread timer *///线程定时器

    void (*cleanup)(struct rt_thread *tid);             /**< cleanup function when thread exit *///当线程退出时,需要执行的清理函数,相当于线程的析构函数,用于销毁线程时做些后续操作

    rt_uint32_t user_data;                              /**< private user data beyond this thread *///用户数据
};
typedef struct rt_thread *rt_thread_t;

2、线程相关接口

静态线程初始化:静态线程是指,线程控制块、线程运行栈一般都设置为全局变量,在编译时就被确定、被分配处理,内核不负责动态分配内存空间。需要注意的是,用户提供的栈首地址需做系统字节对齐。
rt_err_t rt_thread_init(struct rt_thread *thread,//线程句柄
                        const char       *name,//线程名称,最大长度由rtconfig.h中定义的RT_NAME_MAX宏指定,多余部分会被自动截掉
                        void (*entry)(void *parameter),//线程入口函数
                        void             *parameter,//线程入口函数参数
                        void             *stack_start,//线程栈起始地址
                        rt_uint32_t       stack_size,//线程栈大小,单位是字节
                        rt_uint8_t        priority,//线程的优先级。优先级范围根据系统配置情况(rtconfig.h中的RT_THREAD_PRIORITY_MAX宏定义),如果支持的是256级优先级,那么范围是从0 ~ 255,数值越小优先级越高,0代表最高优先级。
                        rt_uint32_t       tick)//线程的时间片大小。时间片(tick)的单位是操作系统的时钟节拍。当系统中存在相同优先级线程时,这个参数指定线程一次调度能够运行的最大时间长度。这个时间片运行结束时,调度器自动选择下一个就绪态的同优先级线程进行运行。
动态线程创建:调用这个函数时,系统会从动态堆内存中分配一个线程句柄(即TCB,线程控制块)以及按照参数中指定的栈大小从动态堆内存中分配相应的空间。分配出来的栈空间是按照rtconfig.h中配置RT_ALIGN_SIZE方式对齐。
rt_thread_t rt_thread_create(const char *name,//线程名称,最大长度由rtconfig.h中定义的RT_NAME_MAX宏指定,多余部分会被自动截掉
                             void (*entry)(void *parameter),//线程入口函数
                             void       *parameter,//线程入口函数参数
                             rt_uint32_t stack_size,//线程堆大小,单位是字节
                             rt_uint8_t  priority,//线程的优先级。优先级范围根据系统配置情况(rtconfig.h中的RT_THREAD_PRIORITY_MAX宏定义),如果支持的是256级优先级,那么范围是从0 ~ 255,数值越小优先级越高,0代表最高优先级。
                             rt_uint32_t tick)//线程的时间片大小。时间片(tick)的单位是操作系统的时钟节拍。当系统中存在相同优先级线程时,这个参数指定线程一次调度能够运行的最大时间长度。这个时间片运行结束时,调度器自动选择下一个就绪态的同优先级线程进行运行。
线程脱离:而rt_thread_detach()函数操作的对象是使用rt_thread_init()函数初始化的静态线程控制块。线程本身不应调用这个接口脱离线程本身。
rt_err_t rt_thread_detach(rt_thread_t thread);

线程删除:在动态线程运行完成或自动结束的情况下,系统会自动删除线程,不需要再调用rt_thread_delete()函数接口。这个接口不应由线程本身来调用以删除线程自身,一般只能由其他线程调用或在定时器超时函数中调用。
rt_err_t rt_thread_delete(rt_thread_t thread);
调用该函数后,线程对象将会被移出线程队列并且从内核对象管理器中删除,线程占用的堆栈空间也会被释放,收回的空间将重新用于其他的内存分配。 实际上,用rt_thread_delete函数删除线程接口,仅仅是把相应的线程状态更改为RT_THREAD_CLOSE状态,然后放入到rt_thread_defunct队列中;而真正的删除动作(释放线程控制块和释放线程栈)需要到下一次执行idle线程时,由idle线程完成最后的线程删除动作。
线程启动:创建(初始化)的线程对象的状态处于初始态,并未进入就绪线程的调度队列,我们可以调用下面的函数接口启动一个线程:
rt_err_t rt_thread_startup(rt_thread_t thread);
当调用这个函数时,将把线程的状态更改为就绪状态,并放到相应优先级队列中等待调度。如果新启动的线程优先级比当前线程优先级高,将立刻切换到这个线程。

线程挂起:当线程调用rt_thread_delay,调用线程将主动挂起,当调用rt_sem_take,rt_mb_recv等函数时,资源不可使用也将导致调用线程挂起。处于挂起状态的线程,如果其等待的资源超时(超过其设定的等待时间),那么该线程将不再等待这些资源,并返回到就绪状态;或者,当其它线程释放掉该线程所等待的资源时,该线程也会返回到就绪状态。
rt_err_t rt_thread_suspend(rt_thread_t thread);
通常不应该使用这个函数来挂起线程本身,如果确实需要采用rt_thread_suspend函数挂起当前任务,需要在调用rt_thread_suspend()函数后立刻调用rt_schedule()函数进行手动的线程上下文切换。

线程恢复:线程恢复就是让挂起的线程重新进入就绪状态,如果被恢复线程在所有就绪态线程中,位于最高优先级链表的第一位,那么系统将进行线程上下文的切换。
rt_err_t rt_thread_resume(rt_thread_t thread);
线程让出:当前线程的时间片用完或者该线程自动要求让出处理器资源时,它不再占有处理器,调度器会选择下一个最高优先级的线程执行。这时,放弃处理器资源的线程仍然在就绪队列中。
rt_err_t rt_thread_yield(void);
调用该函数后,当前线程首先把自己从它所在的队列中删除,然后把自己挂到与该线程优先级对应的就绪线程链表的尾部,然后激活调度器切换到优先级最高的线程。注:rt_thread_yield()函数和rt_schedule()函数比较相像,但在有相同优先级的其他就绪态线程存在时,系统的行为却完全不一样。  执行rt_thread_yield()函数后,将当前线程被换出,把相同优先级的下一个就绪线程将被执行。  执行rt_schedule()函数后,当前线程并不一定被换出,即使被换出,也不会被放到绪线程链表的尾部,而是在系统中选取就绪的优先级最高的线程执行(如果系统中没有比当前线程优先级更高的线程存在,那么执行完rt_schedule()函数后,系  统将继续执行当前线程)
线程睡眠:在实际应用中,我们有时需要让运行的当前线程延迟一段时间,在指定的时间到达后重新运行,这就叫做“线程睡眠”。
rt_err_t rt_thread_sleep(rt_tick_t tick);
rt_err_t rt_thread_delay(rt_tick_t tick);
这两个函数接口的作用相同,调用它们可以使当前线程挂起一段指定的时间,当这个时间过后,线程会被唤醒并再次进入就绪状态。这个函数接受一个参数,该参数指定了线程的休眠时间(单位是OS Tick时钟节拍)。
线程控制:
rt_err_t rt_thread_control(rt_thread_t thread, rt_uint8_t cmd, void *arg);
指示控制命令cmd当前支持的命令包括
• RT_THREAD_CTRL_CHANGE_PRIORITY - 动态更改线程的优先级;
• RT_THREAD_CTRL_STARTUP - 开始运行一个线程,等同于rt_thread_startup()函数调用;
• RT_THREAD_CTRL_CLOSE - 关闭一个线程,等同于rt_thread_delete()函数调用。

查找线程:
rt_thread_t rt_thread_find(char *name);
查找线程是通过内核对象管理系统来查找的,根据内核对象的类型,找到相应内核对象链表,并遍历它,比较名字,如果找到则返回。

当前线程:在程序的运行过程中,相同的一段代码可能会被多个线程执行,在执行的时候可以通过下面的函数接口获得当前执行的线程句柄rt_thread_t rt_thread_self(void);请不要在中断服务程序中调用此函数,因为它并不能准确获得当前的执行线程。当调度器未启动时,这个接口返回RT_NULL

3、线程状态

线程运行的过程中,一个时间内只允许一个线程在处理器中运行,从运行的过程上划分,线程有多种不同的运行状态,如运行态,非运行态等。在RT-Thread实时操作系统中,线程包含五种状态,操作系统会自动根据它运行的情况而动态调整它的状态。 RT-Thread中的五种线程状态如下所示:

RT_THREAD_INIT 线程初始状态。当线程刚开始创建还没开始运行时就处于这个 状态;在这个状态下,线程不参与调度

RT_THREAD_SUSPEND 挂起态、阻塞态。线程此时被挂起:它可能因为资源不可用而 挂起等待;或线程主动延时一段时间而被挂起。在这个状态下 ,线程不参与调度

RT_THREAD_READY 就绪态。线程正在运行;或当前线程运行完让出处理器后,操 作系统寻找最高优先级的就绪态线程运行

RT_THREAD_RUNNING 运行态。线程当前正在运行,在单核系统中,只有rt_thread_self()函数返回的线程处于这个状态;在多核系统中则不受这个限制。

RT_THREAD_CLOSE 线程结束态。当线程运行结束时将处于这个状态。这个状态的 线程不参与线程的调度。

时间: 2024-10-26 06:16:30

RT-thread内核之线程源码分析的相关文章

Flume-NG启动过程源码分析(三)(原创)

上一篇文章分析了Flume如何加载配置文件的,动态加载也只是重复运行getConfiguration(). 本篇分析加载配置文件后各个组件是如何运行的? 加载完配置文件订阅者Application类会收到订阅信息执行: @Subscribe public synchronized void handleConfigurationEvent(MaterializedConfiguration conf) { stopAllComponents(); startAllComponents(conf)

【Java】【Flume】Flume-NG启动过程源码分析(三)

本篇分析加载配置文件后各个组件是如何运行的? 加载完配置文件订阅者Application类会收到订阅信息执行: @Subscribe public synchronized void handleConfigurationEvent(MaterializedConfiguration conf) { stopAllComponents(); startAllComponents(conf); } MaterializedConfiguration conf就是getConfiguration()

配置一个逻辑CPU专用于实时任务----Kithara RTS工程源码分析

本文以windows实时拓展Kithara RTS安装目录下的smp文件夹内的DedicatedRealTimeTask项目为例,讲解使实时任务以独占一个逻辑CPU的方式运行,并实现任务间的同步. 目前多核计算机已经普及,多数的PC都是多核的.针对这种多核结构,我们设想把计算机划分为不同的硬件区间,其中一部分用于被实时任务专用,另一部分是被windows使用的,两者之间互不干扰,这样实时任务可以实现更好的实时性能.这种硬件划分,一般是按照物理CPU的核心数,即逻辑CPU的数量来配置的.可以配置一

Activity启动流程源码分析之Launcher启动(二)

1.前述 在前一篇文章中我们简要的介绍Activity的启动流程Activity启动流程源码分析之入门(一),当时只是简单的分析了一下流程,而且在上一篇博客中我们也说了Activity的两种启动方式,现在我们就来分析其中的第一种方式--Launcher启动,这种启动方式的特点是会创建一个新的进程来加载相应的Activity(基于Android5.1源码). 2.Activity启动流程时序图 好啦,接下来我们先看一下Launcher启动Activity的时序图: 好啦,接下来我们将上述时序图用代

Android系统默认Home应用程序(Launcher)的启动过程源码分析

在前面一篇文章中,我们分析了Android系统在启动时安装应用程序的过程,这些应用程序安装好之后,还须要有一个Home应用程序来负责把它们在桌面上展示出来,在Android系统中,这个默认的Home应用程序就是Launcher了,本文将详细分析Launcher应用程序的启动过程. Android系统的Home应用程序Launcher是由ActivityManagerService启动的,而ActivityManagerService和PackageManagerService一样,都是在开机时由

嵌入式linux开发uboot移植(三)——uboot启动过程源码分析

嵌入式linux开发uboot移植(三)--uboot启动过程源码分析 一.uboot启动流程简介 与大多数BootLoader一样,uboot的启动过程分为BL1和BL2两个阶段.BL1阶段通常是开发板的配置等设备初始化代码,需要依赖依赖于SoC体系结构,通常用汇编语言来实现:BL2阶段主要是对外部设备如网卡.Flash等的初始化以及uboot命令集等的自身实现,通常用C语言来实现. 1.BL1阶段 uboot的BL1阶段代码通常放在start.s文件中,用汇编语言实现,其主要代码功能如下:

A2dp初始化流程源码分析

蓝牙启动的时候,会涉及到各个profile 的启动.这篇文章分析一下,蓝牙中a2dp profile的初始化流程. 我们从AdapterState.java中对于USER_TURN_ON 消息的处理说起: switch(msg.what) { case USER_TURN_ON: notifyAdapterStateChange(BluetoothAdapter.STATE_TURNING_ON); mPendingCommandState.setTurningOn(true); transit

【Java】【Flume】Flume-NG启动过程源码分析(一)

从bin/flume 这个shell脚本可以看到Flume的起始于org.apache.flume.node.Application类,这是flume的main函数所在. main方法首先会先解析shell命令,如果指定的配置文件不存在就甩出异常. 根据命令中含有"no-reload-conf"参数,决定采用那种加载配置文件方式:一.没有此参数,会动态加载配置文件,默认每30秒加载一次配置文件,因此可以动态修改配置文件:二.有此参数,则只在启动时加载一次配置文件.实现动态加载功能采用了

【Java】【Flume】Flume-NG启动过程源码分析(二)

本节分析配置文件的解析,即PollingPropertiesFileConfigurationProvider.FileWatcherRunnable.run中的eventBus.post(getConfiguration()).分析getConfiguration()方法.此方法在AbstractConfigurationProvider类中实现了,并且这个类也初始化了三大组件的工厂类:this.sourceFactory = new DefaultSourceFactory();this.s