协程
协程,又称作协同程序,是比线程还小的可执行代码序。
一个线程内可以拥有多个协程,这些协程共享线程除了栈之外的所有资源,例如优先级、内核对象等。由于线程内的所有协程共享线程本身的内核对象,所以调度器本身并不知道协程的存在,协程是靠所属线程被调度时执行的。一个线程内部的协程不可被抢占,只能轮转运行,只有当前正在运行的协程主动放弃处理器时,同线程内的其他协程才能获得处理器。当线程被删除时,线程内的所有协程也同时被删除。
SylixOS 内核支持协程,而不是使用第三方库模拟,这样使得 SylixOS 内部的协程管理更加便捷高效。
调用 Lw_Coroutine_Create 函数将在当前线程创建一个协程。需要注意的是,每一个线程的创建都会默认创建一个起始协程,因此线程总是从这个起始协程开始运行。
#include <SylixOS.h>
PVOID Lw_Coroutine_Create(PCOROUTINE_START_ROUTINE pCoroutineStartAddr,
size_t stStackByteSize,
PVOID pvArg);
函数 Lw_Coroutine_Create 原型分析:
- 此函数成功返回协程控制指针,失败返回 LW_NULL 并设置错误号。
- 参数 pCoroutineStartAddr 是协程启动地址。
- 参数 stStackByteSize 是协程栈大小。
- 参数 pvArg 是入口参数。
调用 Lw_Coroutine_Delete 函数将删除一个指定的协程,如果删除的是当前协程系统将直接调用 Lw_Coroutine_Exit 函数退出。当线程中的最后一个协程退出时该线程将退出。
#include <SylixOS.h>
ULONG Lw_Coroutine_Delete(PVOID pvCrcb);
ULONG Lw_Coroutine_Exit(VOID);
函数 Lw_Coroutine_Delete 原型分析:
- 此函数成功返回 0,失败返回错误号。
- 参数 pvCrcb 是协程控制指针。
函数 Lw_Coroutine_Exit 原型分析:
- 此函数成功返回 0,失败返回错误号。
前面我们曾说过,调度器并不知道协程的存在,而协程之间则以轮转的方式运行,这决定了协程的调度必须由用户程序去管理。SylixOS 提供了以下函数来改变协程的调度顺序。
#include <SylixOS.h>
VOID Lw_Coroutine_Yield(VOID);
ULONG Lw_Coroutine_Resume(PVOID pvCrcb);
函数 Lw_Coroutine_Resume 原型分析:
- 此函数成功返回 0,失败返回非 0 值。
- 参数 pvCrcb 是协程控制指针。
调用 Lw_Coroutine_Yield 函数可以主动放弃处理器,调用 Lw_Coroutine_Resume 函数使指定的协程恢复。
调用 Lw_Coroutine_StackCheck 函数可以对协程栈进行检查。
#include <SylixOS.h>
ULONG Lw_Coroutine_StackCheck(PVOID pvCrcb,
size_t *pstFreeByteSize,
size_t *pstUsedByteSize,
size_t *pstCrcbByteSize);
函数 Lw_Coroutine_StackCheck 原型分析:
- 此函数成功返回 0,失败返回错误号。
- 参数 pvCrcb 是协程控制指针。
- 输出参数 pstFreeByteSize 返回空闲栈大小。
- 输出参数 pstUsedByteSize 返回使用栈大小。
- 输出参数 pstCrcbByteSize 返回协程控制块大小。
参数 pstFreeByteSize、pstUsedByteSize、pstCrcbByteSize 可以为 NULL,如果相应的参数为 NULL,则不会关心指定类型的栈情况。
需要注意的是,如果检查协程栈的使用情况,则协程的父系线程必须使用栈检查选项如下表所示。
宏名 | 解释 |
---|---|
LW_OPTION_THREAD_STK_CHK | 运行时对线程栈进行检查 |
LW_OPTION_THREAD_STK_CLR | 在线程建立时栈数据清零 |
LW_OPTION_THREAD_USED_FP | 保存浮点运算器 |
LW_OPTION_THREAD_SUSPEND | 建立线程后阻塞 |
LW_OPTION_THREAD_INIT | 初始化线程 |
LW_OPTION_THREAD_SAFE | 建立的线程为安全模式 |
LW_OPTION_THREAD_DETACHED | 线程不允许被合并 |
LW_OPTION_THREAD_UNSELECT | 此线程不使用 select 功能 |
LW_OPTION_THREAD_NO_MONITOR | 内核跟踪器对此线程不起效 |
LW_OPTION_THREAD_ UNPREEMPTIVE | 任务不可抢占(当前不支持) |
LW_OPTION_THREAD_SCOPE_PROCESS | 进程内竞争(当前不支持) |
下面程序展示了 SylixOS 协程的使用方法。
#include <SylixOS.h>
#include <stdio.h>
VOID coroutine0 (PVOID pvArg)
{
INT i;
for (i = 0; i < 5; i++) {
fprintf(stdout, "coroutine0 running...\n");
Lw_Time_SSleep(1);
}
}
VOID coroutine1 (PVOID pvArg)
{
INT i;
for (i = 0; i < 5; i++) {
fprintf(stdout, "coroutine1 running...\n");
Lw_Time_SSleep(1);
}
}
PVOID tTest (PVOID pvArg)
{
PVOID pcCrcb0, pcCrcb1;
pcCrcb0 = Lw_Coroutine_Create(coroutine0, 4 * 1024, LW_NULL);
if (pcCrcb0 == LW_NULL) {
return (LW_NULL);
}
pcCrcb1 = Lw_Coroutine_Create(coroutine1, 4 * 1024, LW_NULL);
if (pcCrcb1 == LW_NULL) {
return (LW_NULL);
}
Lw_Coroutine_Yield(); /* 使其他协程运行 */
while (1) {
Lw_Time_SSleep(10);
}
return ((PVOID)1);
}
int main (int argc, char *argv[])
{
LW_HANDLE hId;
hId = Lw_Thread_Create("t_test", tTest, LW_NULL, LW_NULL);
if (hId == LW_HANDLE_INVALID) {
return (PX_ERROR);
}
Lw_Thread_Join(hId, LW_NULL);
return (ERROR_NONE);
}
在 SylixOS Shell 下运行程序。
# ./Synergetics
coroutine0 running...
coroutine0 running...
coroutine0 running...
coroutine0 running...
coroutine0 running...
coroutine1 running...
coroutine1 running...
coroutine1 running...
coroutine1 running...
coroutine1 running...
注意:
使用 Lw_Coroutine_Create 函数创建协程时,需要根据不同平台的堆栈大小设置 size_t。
从运行结果可以看出 coroutine0 先获得了执行,并且在 coroutine0 运行期间并没有因为延迟阻塞而被打断,下面我们来分析一下这个过程。
在 tTest 线程中,程序首先创建 coroutine0 协程,然后创建 coroutine1 协程,之前介绍了线程创建的时候都会默认创建一个起始协程,因此线程首先从起始协程开始运行,这里程序调用 Lw_Coroutine_Yield 函数来主动放弃处理器,此时 coroutine0 获得处理器开始运行,由于协程之间是轮转运行,所以在 coroutine0 完成运行之前并不会主动放弃处理器(程序并没有主动调用 Lw_Coroutine_Yield 函数,同时也证明一点调度器感知不到协程的存在),当 coroutine0 运行完成后,自动切换到了 coroutine1 协程运行。这也说明先创建的协程会优先得到处理器,符合先进先出的原则。