Zephyr_Thread

1. 线程概述

线程是操作系统能够进行运算调度的最小单位. 它被包含在进程之中, 是进程的实际运作单位. 一条线程指的是进程中的一个单一顺序的控制流, 一个进程可以并发多个线程, 每条线程并行执行不同的任务. 在多核或多 CPU, 或支持 Hyper-threading 的 CPU 上使用多线程程序设计的好处是显而易见的, 即提高了程序的执行吞吐率. 在单个 CPU 单核的计算机上,使用多线程技术, 也可以吧进程中负责 I/O 处理, 人机交互而常被阻塞的部分与密集计算的部分分开来执行, 编写专门的 workhorse 线程执行密集计算, 从而提高程序的执行效率.

分以下几部分概述创建, 调度和删除独立可执行线程的内核服务

  1. 生命周期
  2. 调度机制
  3. 自定义数据
  4. 系统线程
  5. 工作队列线程
  6. 配置选项
  7. API 参考

2. 生命周期(Lifecycle)

线程是用于应用程序处理的内核对象, 它太长或太复杂, ISR 无法执行.

2.1. 概念(Concepts)

一个应用程序可以创建任意多个线程. 每个线程由一个线程 id 引用, 该 id 在线程创建时分配.

线程有以下几个关键属性:

  • 栈空间 : 线程栈所需的一段内存空间. 堆栈的大小可根据线程处理实际需要进行调整. 存在用于创建和处理内存堆栈区域的特殊宏.
  • 线程控制块 : 用于线程元数据的私有内核簿记(bookkeeping). 是结构体 struct k_thread 的一个实例.
  • 入口函数 : 线程启动时调用的函数. 该函数最多可接受 3 个参数值.
  • 调度优先级 : 它指示内核调度程序如何分配 CPU 时间给线程.
  • 线程可选项 : 允许线程在特定的环境下接受内核的特殊处理.
  • 启动延时 : 指定内核在启动线程支付那个应该等待多长时间.
  • 执行模式 : 可以是 管理模式 或者 用户模式. 默认情况下, 线程运行在管理模式下, 该模式下的线程可以访问特权 CPU 指令, 整个内存地址空间和外设. 用户模式下线程可访问特权中的一部分. 取决于配置选项 CONFIG_USERSPACE.

2.2. 创建线程(Thread Creation)

线程必须创建之后才能使用. 内核初始化线程控制块和堆栈部分的一端. 线程堆栈的剩余部分通常未初始化.
启动延时设置为 K_NO_WAIT 时表明内核将立即启动线程执行.  否则, 将设置一个超时时间以用于内核在超时时间到期时启动执行线程. 例如, 允许线程使用的硬件可用时启动线程.
内核允许在线程开始执行前取消延时启动. 如果线程已经启动了, 则取消请求是无效的. 已经成功取消延时启动的线程必须重新创建才能使用. 

2.3. 终止线程(Thread Termination)

线程一旦启动, 将永远执行. 但是, 线程可以通过其入口函数返回来同步结束其执行. 称之为线程终止. 
终止的线程负责在返回之前使用它拥有的任何共享资源(如互斥锁和动态分配的内存), 因为内核不会自动回收它们.

Note : 内核目前没有对应用程序重新创建终止线程的能力做出任何声明.

2.4. 中止线程(Thread Aborting)

线程可以通过执行 aborting 异步结束. 如果线程触发致命错误错误(如 : 引用空指针), 内核将自动中止该线程.
线程也可以被其它线程(或它自己)调用 k_thread_abort() 中止. 然而, 通常采用发信号给线程, 让线程自己结束执行.
线程终止时, 内核不会自动回收该线程锁拥有的共享资源.

Note : 内核目前没有对应用程序重新创建中止线程的能力做出任何声明.

2.5. 挂起线程(Thread Suspension)

如果线程被挂起, 它将在一段不确定的时间内暂停执行. 函数 k_thread_suspend() 用于挂起包括调用线程在内的任何线程, 对已经处于挂起的线程再次挂起时不会产生任何效果.
线程一旦挂起, 则不会被调度, 除非另一个线程调用函数 k_thread_resume() 取消挂起.

Note : 线程可以使用函数 k_sleep() 阻止其执行. 然而, 这不同于挂起线程, 因为睡眠时间到了之后线程自动变为可执行.

2.5. 线程的选项(Thread Options)

内核支持一小系列线程选项, 以允许线程在特殊情况下被特殊对待. 这些与线程相关联的选项在线程创建时就被指定了.
不需要任何线程选项的线程的线程可选项的值为 0. 如果线程需要可选项, 可通过名字指定, 使用 '|' 支持多个线程可选项. 

支持以下线程可选项 :

  • K_ESSENTIAL : 将线程标记为必须线程(essential thread). 如果该线程终止或中止, 则内核认为发生致命系统错误. 默认情况下, 线程不会被标记为必须线程.
  • K_FP_REGS 和 K_SSE_REGS : 这两个是 X86 相关的选项, 标记线程使用 CPUs 浮点寄存器和 SSE 寄存器. 在调度这样的线程时, 内核执行额外的步骤保存和恢复这些寄存器的内容. 默认情况下,调度线程时, 内核不会保存和恢复这些寄存器的值.
  • K_USER : 如果 CONFIG_USERSPACE 使能, 线程在用户模式下创建, 该标记的线程可访问特权中的一部分. 参见 User Mode. 否则, 该 Flag 什么也不做.
  • K_INHERIT_PERMS : 如果 CONFIG_USERSPACE 使能, 这个线程将继承所有父线程所拥有的所有内核对象权限, 除父线程对象外.

2.6. 实现(Implementation)

2.6.1. 创建线程
线程是通过定义它自己的堆栈空间和线程控制块, 然后再调用函数 k_thread_create() 创建的. 栈空间必须使用 K_THREAD_STACK_DEFINE 定义, 以确保在内存中正确的设置.
线程的创建函数返回线程 id, 该 id 用来引用线程.

以下代码创建了立刻启动的线程.

#define MY_STACK_SIZE  500
#define MY_PRIORITY     5   

extern void my_entry_point(void *, void *, void *);

K_THREAD_STACK_DEFINE(my_stack_area, MY_STACK_SIZE);
struct k_thread my_thread_data;

k_tid_t my_tid = k_thread_create(&my_thread_data, my_stack_area,
                                            K_THREAD_STACK_SIZEOF(my_stack_area),
                                            my_entry_point,
                                            NULL, NULL, NULL,
                                            MY_PRIORITY, 0, K_NO_WAIT );

除了以上创建方式, 也可以调用 K_THREAD_DEFINE 在编译时创建一个线程. 该宏自动定义堆栈空间, 控制块和线程 id 变量. 示例如下, 跟上述创建效果一样.

#define MY_STACK_SIZE   500
#define MY_PRIORITY     5

extern void my_entry_point(void *, void *, void *);

K_THREAD_DEFINE(my_tid, MY_STACK_SIZE,
                         my_entry_point, NULL, NULL, NULL,
                         MY_PRIORITY, 0, K_NO_WAIT);
2.6.1.1. 用户模式的约束(User Mode Constraints)

仅在 CONFIG_USERSPACE 使能时, 并且一个用户线程创建一个新的线程时, 该节才适用. API k_thread_create() 仍然可以使用, 但是有以下限制必须满足, 否则调用线程将被终止.

  1. 调用线程必须对子线程和堆栈参数都具有授予权限(permissions granted); 两者都由内核作为内核对象跟踪.
  2. 子线程和堆栈对象必须处于未初始化状态, 如: 它当前没有运行, 堆栈内存未使用.
  3. 堆栈大小参数必须等于或小于声明时的堆栈对象的边界.
  4. 线程可选项必须选择 K_USER, 用户线程仅能创建其它的用户线程.
  5. 线程可选项必须不能选择 K_ESSENTIAL, 用户线程不能作为必须线程.
  6. 子线程的优先级必须是一个有效的优先级, 等于或低于父优先级.
2.6.1.2. 删除权限(Dropping Permissions)

如果 CONFIG_USERSPACE 使能时, 运行在 管理模式 的线程可以使用 k_thread_user_mode_enter() API 进入用户模式. 这是一个单向的操作, 并且会复位和清零线程栈空间. 这个线程将被标记为非必要的(non-essential).

2.6.2. 终止线程

线程可以通过入口函数返回来终止自己. 示例代码如下:

void my_entry_point(int unused1, int unused2, 
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值