sched.c
提供的代码片段包含了与操作系统内核中的进程调度和管理相关的多个函数。schedule
函数首先对所有任务(进程)进行检测,唤醒任何一个已经得到信号的任务。具体方法是针对任务数组中的每个任务,检查其报警定时值alam
。如果任务的alam
时间已经过期(alarm≤jiffies
),则在它的信号位图中设置SIGALRM
信号,然后清alam
值。jiffies是系统从开机开始算起的滴答数(IOms
滴答)。sched..h
中定义。如果进程的信号位图中除去被阻塞的信号外还有其它信号,并且任务处于可中断睡眠状态(TASK INTERRUPTIBLE
),则置任务为就绪状态(TASK RUNNING
)
schedule()
void schedule(void)
{
int i, next, c;
struct task_struct ** p;
/* 检查报警,唤醒任何已接收信号的可中断任务 */
for (p = &LAST_TASK; p > &FIRST_TASK; --p)
if (*p) {
if ((*p)->alarm && (*p)->alarm < jiffies) {
(*p)->signal |= (1 << (SIGALRM - 1));
(*p)->alarm = 0;
}
if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) &&
(*p)->state == TASK_INTERRUPTIBLE)
(*p)->state = TASK_RUNNING;
}
/* 这是调度器的核心部分: */
while (1) {
c = -1;
next = 0;
i = NR_TASKS;
p = &task[NR_TASKS];
while (--i) {
if (!*--p)
continue;
if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
c = (*p)->counter, next = i;
}
if (c) break;
for (p = &LAST_TASK; p > &FIRST_TASK; --p)
if (*p)
(*p)->counter = ((*p)->counter >> 1) + (*p)->priority;
}
switch_to(next);
}
-
目的:此函数实现了核心调度逻辑,根据当前进程的状态和优先级选择下一个要运行的进程。
-
过程:
- 信号处理:检查是否有需要因信号或定时器而唤醒的进程。
- 调度逻辑:
- 通过遍历所有进程来查找最高优先级的可运行进程。
- 如果找不到可运行的进程,则减少所有进程的计数器,使它们再次变为可运行状态。
-
详细解释
schedule
函数
检查报警并唤醒可中断的任务
-
检查报警:
- 循环反向遍历所有任务 (
struct task_struct
指针)。 - 如果一个任务设置了报警,并且当前时间 (
jiffies
) 大于报警时间,则向该任务发送报警信号 (SIGALRM
)。 - 报警时间随后重置为 0。
- 循环反向遍历所有任务 (
-
唤醒可中断的任务:
- 如果一个任务在可中断睡眠状态 (
TASK_INTERRUPTIBLE
) 下接收到信号,则通过将其状态设置为TASK_RUNNING
来唤醒它。
- 如果一个任务在可中断睡眠状态 (
调度逻辑
-
查找下一个要运行的任务:
- 循环搜索具有最高优先级的可运行任务。优先级由任务结构中的
counter
字段决定。 - 循环遍历所有任务,检查它们是否处于
TASK_RUNNING
状态并且counter
值高于当前最高优先级任务。 - 最高优先级任务的索引存储在
next
中。
- 循环搜索具有最高优先级的可运行任务。优先级由任务结构中的
-
退出循环:
- 如果找到具有非零
counter
的任务,循环结束,下一步是切换到该任务。 - 如果没有找到这样的任务,则继续执行下一步。
- 如果找到具有非零
-
调整任务优先级:
- 如果没有找到具有非零
counter
的任务,循环将调整所有任务的优先级。 - 每个任务的
counter
被减半,然后加上任务的priority
。 - 这样可以确保较低优先级的任务最终有机会运行。
- 如果没有找到具有非零
-
切换到下一个任务:
- 一旦找到合适任务或调整优先级后,函数调用
switch_to(next)
切换到选定的任务。
- 一旦找到合适任务或调整优先级后,函数调用
总结
此函数执行两项主要任务:
- 它检查报警并唤醒任何已接收信号的可中断任务。
- 它根据优先级选择下一个要运行的任务,并使用
switch_to
函数切换到该任务。
sys_pause()
- 目的:暂停当前进程直到接收到信号。
- 过程:将当前进程的状态设置为
TASK_INTERRUPTIBLE
并调用schedule()
来释放 CPU。
sleep_on()
sleep_on(
函数的主要功能是当一个进程(或任务)所请求的资源正忙或不在内存中时暂时切换出去,放在等待队列中等待一段时间。当切换回来后再继续运行。放入等待队列的方式是利用了函数中的t即指针作为各个正在等待任务的联系。
- 目的:将当前进程置于不可中断的睡眠状态。
- 过程:
- 将当前进程的状态保存到指定指针中。
- 将当前进程的状态设置为
TASK_UNINTERRUPTIBLE
。 - 调用
schedule()
来释放 CPU。 - 恢复原始进程的状态。
此函数用于改变当前进程的状态至不可中断睡眠状态,并进行进程调度。具体步骤如下:
-
检查参数:首先检查传入的指针
p
是否为空。若为空,则直接返回,不做任何操作。 -
检查当前进程:判断当前进程是否为系统初始化进程
init_task
。如果是,则会触发一个panic
异常,因为初始化进程不应该进入睡眠状态。 -
进程状态交换:使用临时变量
tmp
保存*p
的值,然后将*p
设置为当前进程的结构体指针current
。这意味着原本*p
指向的进程信息被保存在tmp
中,而*p
现在指向了当前进程。 -
设置当前进程状态:将当前进程的状态设置为
TASK_UNINTERRUPTIBLE
,表示当前进程进入不可中断的睡眠状态。在此状态下,进程不会响应信号,直到等待的条件满足。 -
调度器调用:通过调用
schedule()
函数,使其他就绪进程有机会运行。这会导致当前进程挂起,控制权交给其他进程。 -
恢复原进程状态:最后,如果
tmp
不为空(即原本*p
指向的进程存在),则将其状态设为0
。这通常意味着恢复原进程的状态为初始状态或某种未指定状态,具体取决于上下文。
总结来说,这个函数主要用于让当前进程进入不可中断的睡眠状态,并允许其他进程运行。
interruptible_sleep_on()
- 目的:将当前进程置于可中断的睡眠状态。
- 过程:
- 类似于
sleep_on()
,但进程可以被信号中断。 - 不断检查是否应唤醒进程,并重复这一过程直到满足条件为止。
- 类似于
wake_up()
- 目的:唤醒正在睡眠的进程。
- 过程:
- 将由
p
指向的进程的状态设置为0
,表示该进程已准备好运行。 - 清除指针以指示进程已被唤醒。
- 将由
ticks_to_floppy_on()
- 目的:计算打开软盘驱动器电机之前的延迟时间。
- 过程:
- 根据当前软盘驱动器的状态和所需的驱动器编号确定延迟时间。
- 使用
sleep_on()
函数等待指定的延迟时间。
floppy_on()
- 目的:打开指定的软盘驱动器。
- 过程:调用
ticks_to_floppy_on()
计算延迟时间并使用sleep_on()
等待。
floppy_off()
- 目的:关闭指定的软盘驱动器。
- 过程:设置一个定时器,在一定延迟后关闭驱动器。
do_floppy_timer()
- 目的:处理软盘驱动器的定时操作。
- 过程:
- 遍历四个软盘驱动器并根据定时器更新其状态。
add_timer()
- 目的:将定时事件添加到定时器列表中。
- 过程:
- 如果定时器持续时间为非正数,则立即执行回调函数。
- 否则,将定时器添加到列表中,并按定时器到期时间对列表进行排序。
do_timer()
- 目的:处理系统定时器中断。
- 过程:
- 更新当前进程的用户时间和系统时间。
- 处理定时器列表,执行任何已过期的定时器。
- 处理软盘驱动器定时。
- 如果当前进程的计数器达到零,则调度另一个进程。
sys_alarm()
- 目的:为当前进程设置或获取报警定时器。
- 过程:
- 如果提供了新的值,则设置报警定时器。
- 返回先前的报警值(如果有的话)。
sys_getpid()
, sys_getppid()
, sys_getuid()
, sys_geteuid()
, sys_getgid()
, sys_getegid()
- 目的:检索当前进程的各种属性。
- 过程:
- 每个函数返回当前进程的一个特定属性(PID、PPID、UID、EUID、GID、EGID)。
sys_nice()
- 目的:调整当前进程的优先级。
- 过程:
- 如果可能,根据指定的增量降低当前进程的优先级。
sched_init()
sched_init
函数是 Linux 内核启动过程中的一部分,用于初始化调度器相关的数据结构和设置。下面是该函数的详细解释:
初始化描述符表
- 检查
sigaction
结构大小:- 确保
sigaction
结构的大小为 16 字节。如果不满足这一条件,将引发panic
异常。
- 确保
- 设置 TSS 和 LDT 描述符:
- 使用
set_tss_desc
函数设置全局描述符表 (GDT
) 中的第一个TSS
(Task State Segment
) 描述符,指向初始化任务 (init_task
) 的TSS
。 - 使用
set_ldt_desc
函数设置GDT
中的第一个LDT
(Local Descriptor Table
) 描述符,指向初始化任务的LDT
。
- 使用
- 初始化任务描述符表:
- 从
GDT
中第一个TSS
描述符之后的位置开始,循环初始化每个任务的描述符。 - 对于每个任务,将对应的描述符字段
a
和b
清零。
- 从
设置中断和系统调用门
-
清除
NT
标志:- 使用汇编指令清除标志寄存器中的 NT 标志位。这有助于避免后续可能出现的问题。
-
加载
TSS
和LDT
:- 使用
ltr
指令加载空的TSS
段选择符。 - 使用
lldt
- 使用