线程

描述

线程是操作系统最基本的调度单位,在其他RTOS中可能称之为任务。玄武OS的线程, 除了最基本的运行、睡眠、退出操作外,还支持冻结与解冻,迁移等操作。

线程的状态

img

  • 待命(standby) :线程已被初始化,但未指定主函数。
  • 就绪(ready) :线程已加入到就绪队列中。
  • 运行(running) :线程正在运行,每个CPU中只可能存在一个线程正在运行。
  • 睡眠(sleeping) :线程正在睡眠。
  • 阻塞(blocking) :线程正在等待,可与 睡眠 态组合。
  • 冻结(frozen) :线程已被冻结。
  • 退出(exiting) :线程即将结束。
  • 迁移(migrating) :线程正处于迁移到别的CPU的过程中。

线程的创建、初始化与删除、销毁

玄武OS内核提供静态初始化和动态创建两种方式建立线程:

  • 静态初始化与销毁
    • 初始化:xwosal_thrd_init()
    • 销毁:xwosal_thrd_destroy()
    • 静态是指用户预先定义对象,这些对象在编译时由编译器分配内存;
    • 静态初始化线程还需预先定义栈数组,作用域应该为全局。 栈数组的首地址与大小,必须要满足CPU的ABI规则,例如ARM,就要求8字节对齐, 因此在定义栈数组时需要使用__xwcc__aligned(8)来修饰,且大小是8的倍数。
  • 动态创建与删除
    • 创建:xwosal_thrd_create()
    • 删除:xwosal_thrd_delete()
    • 动态是指程序在运行时,通过内存分配函数从某个内存区域上申请分配一块内存, 并把这块内存初始化为所需要的对象。使用完毕后,需要通过删除来释放内存;
    • 动态方式创建的线程,其栈内存地址对齐问题由操作系统内核处理。

线程属性

线程在创建时,可通过参数attr设定其属性,取值:

  • XWSDOBJ_ATTR_PRIVILEGED:表示线程拥有系统特权, 通常CPU的一些敏感操作需要开启特权后才可进行, 例如关闭打开全局中断,访问某些特殊寄存器等。
    • 在ARMv6m/ARMv7m中,是通过CONTROL寄存器的bit0(nPRIV)来实现的;
    • 在Embedded PowerPC中,是通过MSR寄存器的bit17(PR)来实现的。

示例

  • 应用模块:xwam/example/thread/create
  • 用法:
    • 在配置文件xwbd/电路板名称/cfg/xwam.h中 定义宏XWAMCFG_example_thread_create1
    • 在初始化流程中(例如:xwos_main())调用 example_thread_create_start()启动模块。

线程的睡眠

线程睡眠常用来做精度要求不高的延迟,玄武OS内核提供两种睡眠方式:

  • 睡眠超时的时间是以当前系统tick时间作为初始值进行计算的。 这种方式使用简单,只需提供需要睡眠的tick时间即可,但精度略低一些。
    • API:xwosal_cthrd_sleep()
  • 睡眠的时间起点由调用者提供,这种方式通常用来做较精确的周期性延迟。 第一次调用时,需要提供一个时间原点和周期增量,然后这个函数每次返回时 都会在时间原点上增加一次周期增量,以便下一次调用,由此循环调用可形成 更精准的周期性延迟。
    • API:xwosal_cthrd_sleep_from()

示例

  • 应用模块:xwam/example/thread/sleep
  • 用法:
    • 在配置文件xwbd/电路板名称/cfg/xwam.h中 定义宏XWAMCFG_example_thread_sleep1
    • 在初始化流程中(例如:xwos_main())调用 example_thread_sleep_start()启动模块。

线程的冻结与解冻

冻结

线程的冻结操作,是用来支持系统的一些特殊的功能。在以下情况, 玄武OS内核要求线程进入冻结状态:

  • 系统准备进入低功耗模式。如果此时线程还在运行,很有可能因其正在访问硬件资源、 占用锁,导致系统关闭硬件、清理资源发生异常。因此线程需要运行到一个 特殊的“点”后冻结,这个点称为冻结点。在进入冻结点,线程需要释放锁或资源。

  • 把线程迁移到另一个CPU。

解冻

线程的解冻一般不由用户来操作,系统完成特殊功能后会自动对线程进行解冻:

  • 系统退出低功耗模式时;
  • 线程迁移操作已经完成。

线程冻结的示例

玄武OS的线程被要求进入冻结时,会中断阻塞态或睡眠态,这些函数会返回 错误码-EINTR或-ERESTARTSYS。然后线程会释放掉占用的锁和资源,再执行到 冻结点进行冻结。

支持冻结的线程的主函数需要这样写:

xwer_t foo_task_thread(void *arg)
{
        xwer_t rc;
        bool was_frz;

        /* 初始化... */

        /* 冻结点在函数xwosal_cthrd_frz_shld_stop()内部:
           + 此函数会判读当前线程是否需要冻结,若是,在函数内部冻结线程。
           + 此函数同时还判断线程是否需要退出,若是,返回false。
           + 此函数的参数用来告诉用户刚才线程是否被冻结过,若是,was_frz
             被置为true,否则为false。
        */
        while (!xwosal_cthrd_frz_shld_stop(&was_frz)) {
                rc = fsm(); /* 状态机 */
        }
        /* 省略 ... */
        return rc;
}

上面的例子比较简单,在不需要释放的或资源的场合可以使用。如果线程冻结前需要 做一些清理操作,可以这样来改写主函数中的循环:

xwer_t foo_task_thread(void *arg)
{
        xwer_t rc;

        /* 初始化... */
        /* xwosal_cthrd_shld_stop()只判断线程是否需要退出 */
        while (!xwosal_cthrd_shld_stop()) {
                rc = fsm(...); /* 在状态机中阻塞在某个同步对象或锁上 */
                if ((-EINTR == rc) || (-ERESTARTSYS == rc)) {
                        /* 系统会中断阻塞态或睡眠态时,线程会收到
                           错误码-EINTR或-ERESTARTSYS */
                        if (xwosal_cthrd_shld_frz()) { /* 判断是否需要冻结 */
                                /* 释放资源... */
                                free_resource();
                                xwosal_cthrd_freeze(); /* 冻结点 */
                                /* 线程解冻后,代码回到这里 */
                                /* 重新请求资源... */
                                alloc_resource();
                        } else {
                                /* 处理其他原因导致的中断 */
                                /* + 在玄武OS中,目前只有线程需要冻结时会返回
                                     -EINTR
                                   + 在Linux中,除了冻结,signal也会导致线程
                                     收到-EINTR或-ERESTARTSYS
                                   + 为了更好的通用性,这里的else不要省略。
                                 */
                        }
                }
        }
        /* 省略 ... */
        return rc;
}

线程的退出与返回值

玄武OS支持线程退出操作。线程退出后,操作系统内核会回收其占用的资源。

线程的退出

线程退出通常有两种方式:

  • 主函数直接return
xwer_t foo_task_thrd(void * arg)
{
        xwer_t rc;
        /* 省略 ... */
        return rc;
}
  • 主函数中调用xwosal_cthrd_exit()

此API的用法类似于POSIX中的函数pthread_exit(),调用的线程会立即终止并抛出返回值。

xwer_t foo_task_thrd(void *arg)
{
        xwer_t rc;
        /* 省略 ... */
        xwosal_cthrd_exit(rc); /* 线程在此处结束,并抛出返回值rc */
        /* 后面的代码不再执行 ... */
}

线程的返回值

当一个线程(父)需要获取另一个线程(子)的返回值,可使用这一组API:

xwer_t xwosal_thrd_terminate(xwid_t tid, xwer_t * trc); /* 父线程使用 */
void xwosal_cthrd_wait_exit(void); /* 子线程使用 */
  • 父线程使用xwosal_thrd_terminate()通知子线程返回,并阻塞等待 子线程返回,xwosal_thrd_terminate()返回时表示子线程已经退出,并且 可以通过参数trc传递缓冲区地址来获取子线程的返回值;
  • 子线程使用xwosal_cthrd_wait_exit()阻塞等待 父线程调用xwosal_thrd_terminate()
  • 父线程中调用 xwosal_thrd_terminate() 还有几个附加的效果:
    • 使得子线程xwosal_cthrd_shld_stop()xwosal_cthrd_frz_shld_stop() 返回false,线程循环条件不再成立;
    • 中断子线程的阻塞态和睡眠态。

示例:

xwid_t child_thrd_id;
xwid_t parent_thrd_id;

/* 省略创建线程的过程 ... */

xwer_t child_thrd(void *arg)
{
        xwer_t rc;
        /* 省略... */
        /* 在此处等待父线程调用xwosal_thrd_terminate() */
        xwosal_cthrd_wait_exit();
        return rc;
}

xwer_t parent_thrd(void *arg)
{
        xwer_t rc, childrc;

        /* 通知子线程退出,并阻塞等待子线程退出。
           子线程的返回值可由&childrc获得,并且仅当(XWOK == rc)时有效。 */
        rc = xwosal_thrd_terminate(child_thrd_id, &childrc);
        /* ... */
}

线程的迁移

在多核系统中,玄武OS的线程只会在某个CPU上被调度,玄武OS内核并不会自动对线程做均衡处理。 线程会和CPU存在绑定关系。如果需要改变这种绑定关系(例如引入用户自己的线程均衡服务), 需要对线程进行迁移,可以使用API:xwosal_thrd_migrate()

迁移流程

  • 假定条件:线程正在CPU-A上,准备迁移到CPU-B上
  • 流程:
    • 用户在任意CPU的任意上下文调用API:xwosal_thrd_migrate()
      • 系统向CPU-A发送调度器服务中断,提出“迁移出”的申请;
      • CPU-A切换至调度器服务中断,向线程设置冻结标志,并中断线程的阻塞态和睡眠态, 然后退出中断上下文;
      • CPU-A中线程被重新调度,并运行到冻结点;
      • 线程在冻结点向CPU-A发送调度器服务中断,执行冻结操作;
      • 线程冻结后,CPU-A向CPU-B申请调度器服务中断,提出“迁移进”的申请;
      • CPU-B切换至调度器服务中断,把线程加入到自己的调度器中,解除线程的冻结状态, 并加入就绪列表中;
      • 迁移完成,线程开始在CPU-B中调度。

配置

/* <cfg/xwos.h> */

/* SMP系统 */
#define XWSMPCFG_SD_TCB_MEMSLICE  1 // 是否启用xwos_tcb对象的memslice缓存,
                                    // 取值:1|0

/* UP系统 */
#define XWUPCFG_SD_THRD_EXIT      1 // 是否支持线程退出,取值:1|0

API参考

  • 头文件:xwos/osal/thread.h
  • 注释:见头文件