文章写的有些长,把相关的、用到的函数都列出来了,看完应该能对进程调度相关的代码有一定了解
kernel/sched.c : 385
该函数只有36~40行与进程调度有关,只想了解进程调度的同学可以忽略该函数其他部分
void sched_init(void)
{
int i;
struct desc_struct * p;
if (sizeof(struct sigaction) != 16)
panic("Struct sigaction MUST be 16 bytes");
//以下两行初始化init任务(任务0)的任务状态段描述符和局部数据表描述符
//详细介绍见下文
set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));
set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));
p = gdt+2+FIRST_TSS_ENTRY;//指向gtd表中init任务的后一个任务的任务状态段描述符
for(i=1;i//清gdt表中init任务后所有描述符
task[i] = NULL;
p->a=p->b=0;
p++;
p->a=p->b=0;
p++;
}
/* Clear NT, so that we won't have troubles with that later on */
//这行内联汇编利用堆栈做中间变量将标志寄存器的NT位清除,以屏蔽任务切换
//1.首先用pushfl指令将标志寄存器压栈
//2.然后通过栈顶指针sp来修改刚才压栈的标志寄存器
//3.然后将栈顶的内容弹出到标志寄存器中,完成修改
__asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl");
//加载任务寄存器和局部描述符表寄存器
//详细介绍见下文
ltr(0);
lldt(0);
//下面的代码用于对8253定时器初始化操作,使其每10ms发出一个定时中断
outb_p(0x36,0x43); //模式设置
outb_p(LATCH & 0xff , 0x40);//设置定时值的低字节(LATCH见下文)
outb(LATCH >> 8 , 0x40); //定时值的高字节
set_intr_gate(0x20,&timer_interrupt);//设置定时中断处理函数,中断处理函数_timer_interrupt下文中有详解
outb(inb_p(0x21)&~0x01,0x21); //修改中断控制器屏蔽码以开启时钟中断
set_system_gate(0x80,&system_call); //设置系统调用中断门,与本文无关
}
在include/asm/system.h中最后两行定义
set_tss_desc()和set_ldt_desc()是两个宏函数,该宏函数把传入的gdt的偏移地址转换成(char*)型后,逐字节设置描述符
源码如下
#define _set_tssldt_desc(n,addr,type) \
__asm__ ("movw $104,%1\n\t" \
"movw %%ax,%2\n\t" \
"rorl $16,%%eax\n\t" \
"movb %%al,%3\n\t" \
"movb $" type ",%4\n\t" \
"movb $0x00,%5\n\t" \
"movb %%ah,%6\n\t" \
"rorl $16,%%eax" \
::"a" (addr), "m" (*(n)), "m" (*(n+2)), "m" (*(n+4)), \
"m" (*(n+5)), "m" (*(n+6)), "m" (*(n+7)) \
)
#define set_tss_desc(n,addr) _set_tssldt_desc(((char *) (n)),addr,"0x89")
#define set_ldt_desc(n,addr) _set_tssldt_desc(((char *) (n)),addr,"0x82")
在include/linux/sched.h : 153定义
1. 将传入的任务号计算出该任务的任务状态段/局部描述符表在gdt中的偏移后
2. 用 ltr/lldt 汇编指令将任务号对应的 任务状态段/局部描述符表 加载到对应的 任务状态寄存器TR/局部描述符表寄存器LDT
#define FIRST_TSS_ENTRY 4
#define FIRST_LDT_ENTRY (FIRST_TSS_ENTRY+1)
#define _TSS(n) ((((unsigned long) n)<<4)+(FIRST_TSS_ENTRY<<3))
#define _LDT(n) ((((unsigned long) n)<<4)+(FIRST_LDT_ENTRY<<3))
#define ltr(n) __asm__("ltr %%ax"::"a" (_TSS(n)))
#define lldt(n) __asm__("lldt %%ax"::"a" (_LDT(n)))
在kernel/sched.c : 46行定义
设置时钟芯片8253的定时初值
linux0.11是通过时钟中断来进行任务调度的
Linux希望的中断频率是100,即10ms发出一次时钟中断
//HZ在include/linux/sched.h定义为100,1.193180MHZ为定时芯片8253的输入时钟频率
#define LATCH (1193180/HZ)
10ms触发中断 –> 进入中断处理函数_timer_interrupt –> _do_timer –> schedule()调度函数 –> 任务切换switch_on()
定义在kernel/system_call.s : 176
_timer_interrupt:
push %ds # save ds,es and put kernel data space
push %es # into them. %fs is used by _system_call
push %fs
pushl %edx # we save %eax,%ecx,%edx as gcc doesn't
pushl %ecx # save those across function calls. %ebx
pushl %ebx # is saved as we use that in ret_sys_call
pushl %eax
#以上将寄存器压栈,保护现场
#以下5行,将ds、es指向内核数据段,将fs指向局部数据段
movl $0x10,%eax
mov %ax,%ds
mov %ax,%es
movl $0x17,%eax
mov %ax,%fs
incl _jiffies #系统启动后的时钟滴答值+1
movb $0x20,%al # EOI to interrupt controller #1
outb %al,$0x20 #发送EOI以结束硬件中断
#以下三行取当前特权级别,并压栈作为调用_do_timer的参数
movl CS(%esp),%eax
andl $3,%eax # %eax is CPL (0 or 3, 0=supervisor)
pushl %eax
call _do_timer # 'do_timer(long CPL)' does everything from
addl $4,%esp # task switching to accounting ...
jmp ret_from_sys_call
定义在kernel/sched.c : 305
void do_timer(long cpl)
{
extern int beepcount;
extern void sysbeepstop(void);
//蜂鸣器
if (beepcount)
if (!--beepcount)
sysbeepstop();
//根据当前特权级,将相应的运行时间递增。
if (cpl)
current->utime++;
else
current->stime++;
//如果有定时器正在使用,则将定时器链表的第一个定时器的定时值减一,如果定时值为0,则执行其处理程序并删除该定时器
if (next_timer) {
next_timer->jiffies--;
while (next_timer && next_timer->jiffies <= 0) {
void (*fn)(void);//利用函数指针临时保存当前定时器的处理函数
fn = next_timer->fn;
next_timer->fn = NULL;
next_timer = next_timer->next;
(fn)(); //执行该定时器的处理函数
}
}
//软盘相关,与本文无关
if (current_DOR & 0xf0)
do_floppy_timer();
//如果当前进程时间片不为0,则退出继续执行当前进程
if ((--current->counter)>0) return;
current->counter=0;
//如果当前特权级表示发生中断时正在内核态运行,则返回(内核任务不可被抢占)
if (!cpl) return;
//执行调度函数
schedule();
}
定义在kernel/sched.c : 104
void schedule(void)
{
int i,next,c;
struct task_struct ** p;
/* check alarm, wake up any interruptible tasks that have got a signal */
//遍历任务数组,如果任务设置过定时值alarm并且已经超时,则把信号位图中的SIGALRM置位
//LAST_TASK和FIRST_TASK在include/linux/sched.h第7、8行定义,分别指向任务数组的最后一个和第一个元素
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;
}
/* this is the scheduler proper: */
while (1) {
c = -1;
next = 0; //保存选出的任务的任务号
i = NR_TASKS;//include/linux/sched.h : 4 将该宏定义为64(任务数组长度最大64)
p = &task[NR_TASKS];
//遍历任务数组,选出就绪态的、时间片最大的任务
while (--i) {
if (!*--p)
continue;
if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
c = (*p)->counter, next = i;
}
//如果所有任务时间片都是0,则执行下面代码为所有任务重新分配时间片,否则跳出当前while(1)循环
if (c) break;
//遍历任务数组重新分配时间片,新分配的时间片为counter/2+优先级,所以优先级越高分配到的时间片越大
for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
if (*p)
(*p)->counter = ((*p)->counter >> 1) +
(*p)->priority;
}
switch_to(next);
}
在include/linux/sched.h : 171定义
该函数将当前函数切换到所传参数任务号对应的任务数组中的任务。
#define switch_to(n) {\
struct {long a,b;} __tmp; \
__asm__("cmpl %%ecx,_current\n\t" \ //ecx传入的任务号对应的任务是否为当前任务
"je 1f\n\t" \ //若是,则跳出
"movw %%dx,%1\n\t" \ //新任务TSS选择符赋值给第一个参数__tmp.b
"xchgl %%ecx,_current\n\t" \ //交换指令,交换后ecx为被切换的任务,current为要切换到的任务
"ljmp %0\n\t" \ //跳转至__tmp(新任务的段选择符),切换到该TSS对应的进程(__tmp.a实际上没用),实际上的指令是ljmp __tmp.b低字节:__tmp.a;a做偏移地址为0
"cmpl %%ecx,_last_task_used_math\n\t" \ //以下为再次切换回来后检查是否使用过协处理器
"jne 1f\n\t" \
"clts\n" \
"1:" \
::"m" (*&__tmp.a),"m" (*&__tmp.b), \ //
"d" (_TSS(n)),"c" ((long) task[n])); \ //_TSS(n)传入给dx,任务号n对应的任务传入给ecx
}
以下为对switch_on()函数的详细介绍
该函数一共两个难点
1. 怎么跳转到新任务
2. 任务切换后,怎么执行到协处理器检测代码
刚开始写博客,写的不好大家见谅
有写的不对的地方希望大家不吝赐教
有疑惑也欢迎大家共同探讨