线服务
概念
线(fiber)是一个轻量级的,不能被抢占线程,它作为应用处理过程的一部分执行。它一般被用来实现设备驱动或其他对性能敏感的任务。
线既能够被微内核应用使用,也能够被纳内核应用使用。然而,能与线进行交互的微内核目标类型是非常有限的,更多有关信息请查看微内核线服务。
应用能够使用任意多数量的线。由于每个线都时匿名的,当他们一旦开始运行就不能在被其他的线或任务所直接引用。当线被创建时,必须要指定的属性包括:
- 一段内存区域。该内存区域被用作线的栈和保存执行上线文切换时的信息。
- 一个函数。该函数会在线开始执行时被调用。
- 一对参数。该参数被传递给线的入口函数。
- 优先级。优先级被纳内核调度器使用。
- 一组适用于线的选项。
在系统初始化期间,内核会创建0个或多个系统线。内核所创建的线与应用对于内核功能的配置以及用来生成应用镜像的板卡的配置有关。
线的生命周期
线能够被其他的线创建,也可以由任务创建或者在内核初始化的过程中创建。线在被创建后立即变为可执行的,也可以推迟调度新创建的线到某个时间之后。例如,等待某个线所使用的硬件设备变为可用后再执行。内核还支持延迟启动取消功能,用来避免新创建的线在还没有到达延迟时间时就已经变为不需要执行的情况的出现。
一旦线开始执行,正常情况下它会一直执行下去。线可以通过从入口函数返回来正常终止执行。如果发生了这种自我终止的情况,线自身需要负责在返回之前释放它所拥有的任何系统资源(例如以互斥方式使用的纳内核信号量),因为内核并不会尝试回收这些资源以备再次使用。
线也可以以不正常的方式退出。当线产生致命错误时,例如解引用一个空指针,这时内核会自动取消线的运行。线同样也能够通过调用fiber_abort()
函数来明确的退出。和正常退出方式一样,内核并不会尝试回收线所拥有的系统资源。
Note:内核目前并未就具有重启已终止线能力的应用做出任何声明。
线的调度
纳内核调度器选择系统中的哪一个线程被允许运行,该线程被称为当前上下文(current context)。由于中断服务具有更高的优先级,纳内核调度器允许能够线程仅在没有中断服务执行的情况下执行。
当线程执行时,纳内核的调度器会优先调度线而非任务。当有线需要执行时,调度器会抢占任务的执行。但调度器绝不会抢占为了某个线的执行而抢占另一个线,即使该线具有更高的优先级。
当从一个线进行上下文切换到另一个线,另一个任务或中断服务时,内核会自动保存当前线的CPU寄存器值,并且在该线之后再次被调度时恢复其CPU寄存器值。
线的状态
线包含有隐含状态(state)来决定其是否能够被调度从而执行。该隐含状态包含又所有的能够防止线执行的因素,例如:
- 线还未被创建
- 线正在等待它需要的事件(例如,信号量、超时等)
- 线已经被终止
当一个线的状态不包含任何能够防止它运行的因素时,该线就称为可执行的(executable)。
线的优先级
内核所支持的线的优先级数量几乎是无限的,范围从0(最高优先级)到2^31-1(最低优先级)。不能使用负数作为优先级。
线的优先级在其被创建之后就不能再更改了。
线的调度算法
如果可能,纳内核会选择优先级最高的线作为当前的上线文。当存在多个具有相同优先级的线时,调度器会选择已经等待最长时间的那个。
如果没有任何的可执行线存在,调度器会选择当前任务作为当前上下文。在纳内核应用中,当前任务就是后来任务,然而,微内核应用中的当前任务时由微内核调度器所选择的。当前任务总是处于可执行状态。
一旦有线变为当前上下文,它会一直保持被调度执行状态直到以下的任意一种情况出现:
- 由于线自己调用了内核API导致自身的执行被阻塞(例如,线尝试获取一个已经当前不可用的内核信号量),那么该线会被其他的线抢占。
- 线由于从入口函数返回而停止。
- 线执行了会导致致命错误的操作或调用了
fiber_abort()
函数导致自己被终止。
一旦当前任务变为当前上下文,它会一直保持被调度执行状态直到被其他的线抢占。
Note:当前任务从不会直接地被其他任务抢占,这是由于微内核调度器使用了微内核服务线程来初始化从一个任务到另一个任务的切换。
协作时间片
由于纳调度器不会被抢占的特性,当有线执行一个很长时间的运算时会造成其他无论是高优先级还是相同优先级的线的等待调度的时间过长,超过可接受的范围。
为了解决这个问题,线应该能够不时的自愿放弃CPU,让其他的线获得执行的机会。
线能够通过以下两种方式放弃占用CPU:
- 调用
fiber_yield()
将当前线添加到纳内核调度器科执行线列表的最后,并且重新调度。任何与被调出线具有相同优先级或具有更高优先级的线都会被允许在放弃线再次被调度至CPU之前被执行。如果不存在符合条件的线,调度器会立即重新调度刚才主动放弃执行的线重新获取CPU,这个过程中没有上下文切换。 - 调用
fiber_sleep()
阻塞当前线一定的时间。其他的任何优先级的线会在当前线阻塞后执行,不能保证任何比休眠线优先级更低的线会在睡眠时间到达,休眠线再次唤醒前被调度。
线选项
内核支持若干中线选项(fiber options)用以告知内核需要特殊处理这些线的需求。
在线创建时,一组内内核选项与新创建的线关联。如果线要使用多个选项,他们需要用|
来组合,也就是逻辑与操作。如果不需要使用任何的选项,则给选项赋0即可。
下面列出的选项是由内核域定义的:
USE_FP
让内核在进行上下文切换时,保存线的x87FPU和MMX浮点上线文信息。
USE_SSE
让内核在进行上下文切换时,保存线的SSE浮点寄存器(使用该选项即默认开启了USE_FP
选项)。
使用方法
定义一个线
在创建线时必须指定以下属性:
stack_name
该属性指明了线的栈和其他执行上下文所使用的内存区域。为了保证内存对齐,应该使用如下的形式:
char __stack <stack_name>[<stack_size>];
stack_size
该属性指明了stack_name内存区域的大小,单位为字节。
entry_point
该属性指明了线的入口函数名称,入口函数应具有以下的形式:
void <entry_point>(int arg1, int arg2)
{
/* fiber mainline processing */
...
/* (optional) normal fiber termination */
return;
}
参数:
参数时在线开始执行时传递给入口函数的两个参数。非整形参数能够强转为整形后传递给入口函数。
优先级:
指明了线调度时的优先级。
选项:
线的选项。
示例:从一个任务中创建线
下面的代码展示了当前执行的任务能够创建多个线,每一个线专门处理来自不同通信信道的数据。
#define COMM_STACK_SIZE 512
#define NUM_COMM_CHANNELS 8
struct descriptor {
...;
};
char __stack comm_stack[NUM_COMM_CHANNELS][COMM_STACK_SIZE];
struct descriptor comm_desc[NUM_COMM_CHANNELS] = { ... };
...
void comm_fiber(int desc_arg, int unused);
{
ARG_UNUSED(unused);
struct descriptor *desc = (struct descriptor *) desc_arg;
while (1) {
/* process packet of data from comm channel */
...
}
}
void comm_main(void)
{
...
for (int i = 0; i < NUM_COMM_CHANNELS; i++) {
task_fiber_start(&comm_stack[i][0], COMM_STACK_SIZE,
comm_fiber, (int) &comm_desc[i], 0,
10, 0);
}
...
}
API
下面的API会影响当前执行的线,它们在microkernel.h
和nanokernel.h
中被导出:
fiber_yield()
将CPU让给更高优先级或相同优先级的线。
fiber_sleep()
让出CPU一个指定的时间。
fiber_abort()
终止线的执行。
下面的API会影响指定的线,它们在microkernel.h
和nanokernel.h
导出。
task_fiber_start()
, fiber_fiber_start()
, fiber_start()
创建一个新的线。
task_fiber_delayed_start()
, fiber_fiber_delayed_start()
, fiber_delayed_start()
在指定的时间后创建一个线。
task_fiber_delayed_start_cancel()
, fiber_fiber_delayed_start_cancel()
, fiber_delayed_start_cancel()
如果线还没有开始运行,则取消它的运行。