kernel源码(十二)sched.c
源码
/* * linux/kernel/sched.c * * (C) 1991 Linus Torvalds */ /* * 'sched.c' is the main kernel file. It contains scheduling primitives * (sleep_on, wakeup, schedule etc) as well as a number of simple system * call functions (type getpid(), which just extracts a field from * current-task */ #include <linux/sched.h> #include <linux/kernel.h> #include <linux/sys.h> #include <linux/fdreg.h> #include <asm/system.h> #include <asm/io.h> #include <asm/segment.h> #include <signal.h> #define _S(nr) (1<<((nr)-1)) #define _BLOCKABLE (~(_S(SIGKILL) | _S(SIGSTOP))) void show_task(int nr,struct task_struct * p) { int i,j = 4096-sizeof(struct task_struct); printk("%d: pid=%d, state=%d, ",nr,p->pid,p->state); i=0; while (i<j && !((char *)(p+1))[i]) i++; printk("%d (of %d) chars free in kernel stack\n\r",i,j); } void show_stat(void) { int i; for (i=0;i<NR_TASKS;i++) if (task[i]) show_task(i,task[i]); } #define LATCH (1193180/HZ) extern void mem_use(void); extern int timer_interrupt(void); extern int system_call(void); union task_union { struct task_struct task; char stack[PAGE_SIZE]; }; static union task_union init_task = {INIT_TASK,}; long volatile jiffies=0; long startup_time=0; struct task_struct *current = &(init_task.task); struct task_struct *last_task_used_math = NULL; struct task_struct * task[NR_TASKS] = {&(init_task.task), }; long user_stack [ PAGE_SIZE>>2 ] ; struct { long * a; short b; } stack_start = { & user_stack [PAGE_SIZE>>2] , 0x10 }; /* * 'math_state_restore()' saves the current math information in the * old math state array, and gets the new ones from the current task */ void math_state_restore() { if (last_task_used_math == current) return; __asm__("fwait"); if (last_task_used_math) { __asm__("fnsave %0"::"m" (last_task_used_math->tss.i387)); } last_task_used_math=current; if (current->used_math) { __asm__("frstor %0"::"m" (current->tss.i387)); } else { __asm__("fninit"::); current->used_math=1; } } /* * 'schedule()' is the scheduler function. This is GOOD CODE! There * probably won't be any reason to change this, as it should work well * in all circumstances (ie gives IO-bound processes good response etc). * The one thing you might take a look at is the signal-handler code here. * * NOTE!! Task 0 is the 'idle' task, which gets called when no other * tasks can run. It can not be killed, and it cannot sleep. The 'state' * information in task[0] is never used. */ void schedule(void) { int i,next,c; struct task_struct ** p; /* check alarm, wake up any interruptible tasks that have got a signal */ 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; 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); } int sys_pause(void) { current->state = TASK_INTERRUPTIBLE; schedule(); return 0; } void sleep_on(struct task_struct **p) { struct task_struct *tmp; if (!p) return; if (current == &(init_task.task)) panic("task[0] trying to sleep"); tmp = *p; *p = current; current->state = TASK_UNINTERRUPTIBLE; schedule(); if (tmp) tmp->state=0; } void interruptible_sleep_on(struct task_struct **p) { struct task_struct *tmp; if (!p) return; if (current == &(init_task.task)) panic("task[0] trying to sleep"); tmp=*p; *p=current; repeat: current->state = TASK_INTERRUPTIBLE; schedule(); if (*p && *p != current) { (**p).state=0; goto repeat; } *p=NULL; if (tmp) tmp->state=0; } void wake_up(struct task_struct **p) { if (p && *p) { (**p).state=0; *p=NULL; } } /* * OK, here are some floppy things that shouldn't be in the kernel * proper. They are here because the floppy needs a timer, and this * was the easiest way of doing it. */ static struct task_struct * wait_motor[4] = {NULL,NULL,NULL,NULL}; static int mon_timer[4]={0,0,0,0}; static int moff_timer[4]={0,0,0,0}; unsigned char current_DOR = 0x0C; int ticks_to_floppy_on(unsigned int nr) { extern unsigned char selected; unsigned char mask = 0x10 << nr; if (nr>3) panic("floppy_on: nr>3"); moff_timer[nr]=10000; /* 100 s = very big :-) */ cli(); /* use floppy_off to turn it off */ mask |= current_DOR; if (!selected) { mask &= 0xFC; mask |= nr; } if (mask != current_DOR) { outb(mask,FD_DOR); if ((mask ^ current_DOR) & 0xf0) mon_timer[nr] = HZ/2; else if (mon_timer[nr] < 2) mon_timer[nr] = 2; current_DOR = mask; } sti(); return mon_timer[nr]; } void floppy_on(unsigned int nr) { cli(); while (ticks_to_floppy_on(nr)) sleep_on(nr+wait_motor); sti(); } void floppy_off(unsigned int nr) { moff_timer[nr]=3*HZ; } void do_floppy_timer(void) { int i; unsigned char mask = 0x10; for (i=0 ; i<4 ; i++,mask <<= 1) { if (!(mask & current_DOR)) continue; if (mon_timer[i]) { if (!--mon_timer[i]) wake_up(i+wait_motor); } else if (!moff_timer[i]) { current_DOR &= ~mask; outb(current_DOR,FD_DOR); } else moff_timer[i]--; } } #define TIME_REQUESTS 64 static struct timer_list { long jiffies; void (*fn)(); struct timer_list * next; } timer_list[TIME_REQUESTS], * next_timer = NULL; void add_timer(long jiffies, void (*fn)(void)) { struct timer_list * p; if (!fn) return; cli(); if (jiffies <= 0) (fn)(); else { for (p = timer_list ; p < timer_list + TIME_REQUESTS ; p++) if (!p->fn) break; if (p >= timer_list + TIME_REQUESTS) panic("No more time requests free"); p->fn = fn; p->jiffies = jiffies; p->next = next_timer; next_timer = p; while (p->next && p->next->jiffies < p->jiffies) { p->jiffies -= p->next->jiffies; fn = p->fn; p->fn = p->next->fn; p->next->fn = fn; jiffies = p->jiffies; p->jiffies = p->next->jiffies; p->next->jiffies = jiffies; p = p->next; } } sti(); } void do_timer(long cpl) { extern int beepcount; extern void sysbeepstop(void); if (beepcount) if (!--beepcount) sysbeepstop(); if (cpl) current->utime++; else current->stime++; 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(); if ((--current->counter)>0) return; current->counter=0; if (!cpl) return; schedule(); } int sys_alarm(long seconds) { int old = current->alarm; if (old) old = (old - jiffies) / HZ; current->alarm = (seconds>0)?(jiffies+HZ*seconds):0; return (old); } int sys_getpid(void) { return current->pid; } int sys_getppid(void) { return current->father; } int sys_getuid(void) { return current->uid; } int sys_geteuid(void) { return current->euid; } int sys_getgid(void) { return current->gid; } int sys_getegid(void) { return current->egid; } int sys_nice(long increment) { if (current->priority-increment>0) current->priority -= increment; return 0; } void sched_init(void) { int i; struct desc_struct * p; if (sizeof(struct sigaction) != 16) panic("Struct sigaction MUST be 16 bytes"); 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; for(i=1;i<NR_TASKS;i++) { 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 */ __asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl"); ltr(0); lldt(0); outb_p(0x36,0x43); /* binary, mode 3, LSB/MSB, ch 0 */ outb_p(LATCH & 0xff , 0x40); /* LSB */ outb(LATCH >> 8 , 0x40); /* MSB */ set_intr_gate(0x20,&timer_interrupt); outb(inb_p(0x21)&~0x01,0x21); set_system_gate(0x80,&system_call); }
首先是一个宏定义,这个宏的作用是根据信号编号nr得到信号位图的二进制数值。信号编号为1-32,信号位图为32位二进制数,每一位表示一个特定的信号,比如
00000000 00000000 00000001 00000000,第9位表示SIGKILL信号。
#define _S(nr) (1<<((nr)-1)) //1<<((nr)-1)意义要理解,后面多处都用到此方式。该行的意义是将数字1左移nr-1位,得到一个二进制数,只有一位为1其余位都为0
下面宏表示除了SIGKILL和SIGSTOP信号外其他信号都是可阻塞的,信号编号在include/signal.h中定义
_S(SIGKILL) | _S(SIGSTOP)=...0100 0000 0001 0000 0000。然后取反得到...1011 1111 1110 1111 1111,也就是这些为1的信号位图为可阻塞的。
#define _BLOCKABLE (~(_S(SIGKILL) | _S(SIGSTOP)))
打印任务信息
void show_task(int nr,struct task_struct * p) { int i,j = 4096-sizeof(struct task_struct);//初始阶段i和j都赋值为同一值 printk("%d: pid=%d, state=%d, ",nr,p->pid,p->state); //打印任务号,pid和state i=0; //i赋值0 while (i<j && !((char *)(p+1))[i]) //检测指定任务数据结构(task_struct)后面的内存空间等于0的字节数 i++; printk("%d (of %d) chars free in kernel stack\n\r",i,j);//打印内核栈大小和剩余大小。从这里可看出任务结构和内核栈在同一内存页(4096字节)中,任务结构剩余的空间可作为内核栈 }
打印所有任务的信息。NR_TASKS在头文件中定义为64,也就是最多有64个任务
void show_stat(void) { int i; for (i=0;i<NR_TASKS;i++) if (task[i]) show_task(i,task[i]); }
#define LATCH (1193180/HZ) extern void mem_use(void); extern int timer_interrupt(void); //时钟中断的处理程序,在system_call.s中定义 extern int system_call(void); //系统调用的处理程序,在system_call.s中定义
任务内核堆栈的数据结构(一个任务有内核栈和用户栈,这里是内核态堆栈)。这里定义了一个共用体,共用体中的每一个成员共用一块内存,后存入的值覆盖先存入的值
这里定义了task_union,但是只用到了其task成员,stack成员在本文件中没用到,其实可以只定义task。
union task_union { struct task_struct task; char stack[PAGE_SIZE]; };
定义init_task,其类型为上面定义的task_union,且第二项不给初始值。这里定义的就是0号进程(INIT_TASK中的pid为0)
static union task_union init_task = {INIT_TASK,};
INIT_TASK在头文件中定义
#define INIT_TASK \ /* state etc */ { 0,15,15, \ /* signals */ 0,{{},},0, \ /* ec,brk... */ 0,0,0,0,0,0, \ /* pid etc.. */ 0,-1,0,0,0, \ /* uid etc */ 0,0,0,0,0,0, \ /* alarm */ 0,0,0,0,0,0, \ /* math */ 0, \ /* fs info */ -1,0022,NULL,NULL,NULL,0, \ /* filp */ {NULL,}, \ { \ {0,0}, \ /* ldt */ {0x9f,0xc0fa00}, \ {0x9f,0xc0f200}, \ }, \ /*tss*/ {0,PAGE_SIZE+(long)&init_task,0x10,0,0,0,0,(long)&pg_dir,\ 0,0,0,0,0,0,0,0, \ 0,0,0x17,0x17,0x17,0x17,0x17,0x17, \ _LDT(0),0x80000000, \ {} \ }, \ }
long volatile jiffies=0; long startup_time=0; //开机时间 struct task_struct *current = &(init_task.task); //当前task,这里把上面定义的init_task.task的地址赋给current,表示当前task,这里是task0 struct task_struct *last_task_used_math = NULL; // struct task_struct * task[NR_TASKS] = {&(init_task.task), }; //task数组,只初始化了第一个元素,其他63个未初始化 long user_stack [ PAGE_SIZE>>2 ] ; //用户堆栈数组,长度为PAGE_size右移2位,也就是1kb
下面定义了一个结构,并初始化。用作描述堆栈段,ss=0x10,sp=上面定义的user_stack的最后一个元素(因为堆栈是向下生长的)
也就是在这个结构中,存储了"0x10"作为数据段选择符(https://www.cnblogs.com/zhenjingcool/p/15972761.html中我们介绍过,_gdt预定义了几个段选择符,其中0x10对应数据段选择符,且介绍了其段大小为16M),然后存储了指向user_stack最后一个元素的指针。
也就是说,这里我们的任务0数据段大小为16M,且其用户栈在最顶上,向下生长
struct { long * a; short b; } stack_start = { & user_stack [PAGE_SIZE>>2] , 0x10 };
stack_struct定义后没有在本文件中用到
下面是进程调度的核心代码
void schedule(void) { int i,next,c; struct task_struct ** p; /* check alarm, wake up any interruptible tasks that have got a signal 检查alarm,唤醒所有拥有signal的task*/ for(p = &LAST_TASK ; p > &FIRST_TASK ; --p) //循环遍历task_struct if (*p) { if ((*p)->alarm && (*p)->alarm < jiffies) { //如果该task设置过定时alarm并且alarm定时已经到达,jiffies是系统开机以来的嘀嗒数,每个嘀嗒10ms (*p)->signal |= (1<<(SIGALRM-1)); //给task设置一个signal信号:SIGALRM (*p)->alarm = 0; //清除alarm } if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) && //这里判断信号位图有值且处于可中断阻塞状态,则改该进程为running就绪态。也就是说如果进程处于可中断阻塞状态,且获得了信号位图,则将其唤醒 (*p)->state==TASK_INTERRUPTIBLE) (*p)->state=TASK_RUNNING; } /* this is the scheduler proper: */ //这里,从进程数组的最后一个任务开始计算每个任务的counter,得到一个任务,该任务具有最大的counter值,如果得到这个值则跳出while循环(if c break;),然后执行switch_to(next)转而去执行这个任务-------完成一次进程调度 while (1) { c = -1; next = 0; i = NR_TASKS; p = &task[NR_TASKS];
//该while循环从task数组中获取到一个task,并执行switch_to(是一个宏定义)开始执行这个task。 while (--i) { //从任务数组的最后一个任务开始处理,数组大小为64 if (!*--p) //数组大小为64,如果系统中没有那么多任务,比如刚开始内核启动的时候只有一个task0,这跳过 continue; // if ((*p)->state == TASK_RUNNING && (*p)->counter > c) //如果任务状态为就绪态,且counter>c,则把counter赋给c c = (*p)->counter, next = i;//这里选取所有task中counter最大的一个任务,将该任务号赋给next。后面会调用switch_to(next)转而去执行这个任务 } if (c) break;//运行完上面的while循环之后,有3种结果:1、内核刚启动阶段没有任务,c=-1;2、我们得到counter值最大的一个任务;3、所有task的counter都是0;对于第一种情况,则会执行我们熟悉的idel任务,对于第2种情况,则会执行该task;对于第3种情况,则继续下面的代码 for(p = &LAST_TASK ; p > &FIRST_TASK ; --p) //所有任务重新计算counter值,然后继续while循环,直到得到一个任务去执行。 if (*p) (*p)->counter = ((*p)->counter >> 1) + (*p)->priority; } switch_to(next); }
总结,上面的schedule函数是任务调度的核心,在这里会从任务列表中挑选出一个任务进行执行(得到时间片,cpu开始执行这个任务)。如果没有任务可以运行,则运行任务0,。
对于任务0,则会执行下面的代码(参考https://www.cnblogs.com/zhenjingcool/p/15999035.html main函数中的最后一行 for(;;) pause();),在这里面再次调用schedule函数。
int sys_pause(void) { current->state = TASK_INTERRUPTIBLE;//把当前任务置为可中断的等待状态。让出cpu使用权,以便其他进程有机会执行 schedule(); return 0; }
下面的函数,把当前任务置为不可中断的等待状态,并且让睡眠队列的头指针指向当前任务
当在一个进程中调用了sleep_on函数时,说明该进程想进入sleep状态。具体做法是,传递一个参数p,p指向一个task_struct数组,这个数组是sleep队列。也就是说这个数组包含了所有正在sleep状态的进程。
void sleep_on(struct task_struct **p) { struct task_struct *tmp; if (!p) return; if (current == &(init_task.task)) //如果当前任务是是任务0,则死机。因为把任务0置为睡眠状态是毫无意义的。 panic("task[0] trying to sleep"); tmp = *p; *p = current; current->state = TASK_UNINTERRUPTIBLE; //当前任务设置为不可中断状态,并且放入sleep队列(p) schedule(); //重新调用schedule函数进行任务调度, if (tmp) tmp->state=0; // }
对指针不熟悉的请看这一篇博客:https://www.cnblogs.com/tongye/p/9650573.html
可中断的睡眠,把当前任务置为可中断的睡眠状态。p是一个指向等待队列的指针
void interruptible_sleep_on(struct task_struct **p) { struct task_struct *tmp; if (!p) //如果指针无效,则退出 return; if (current == &(init_task.task)) //如果当前任务是任务0,则死机 panic("task[0] trying to sleep"); tmp=*p; //tmp指向等待队列 *p=current; //等待队列的头指向当前任务 repeat: current->state = TASK_INTERRUPTIBLE; //当前任务置为可中断状态 schedule(); //重新执行schedule if (*p && *p != current) { //当等待任务被唤醒的时候才会执行到这里。这里判断等待队列中是否还有任务在等待,并且队列头指针所指向的任务不是当前任务 (**p).state=0;//等待的任务置为可运行的就绪状态,并且重新执行调度程序 goto repeat; } *p=NULL; // if (tmp) tmp->state=0; //tmp所指向的任务改为可运行的就绪状态 }
唤醒指针p所指向的任务,
void wake_up(struct task_struct **p) { if (p && *p) { (**p).state=0; //置为可运行的就绪状态 *p=NULL; //头指针清空 } }
//下面程序讲解软盘驱动相关知识
static struct task_struct * wait_motor[4] = {NULL,NULL,NULL,NULL};//这个数组存放的是等待软盘驱动的进程的指针,这里有四个软驱A-D static int mon_timer[4]={0,0,0,0};//各个马达启动所需要的的嘀嗒数 static int moff_timer[4]={0,0,0,0};//各个软驱在马达停转之前需要维持的嘀嗒数 unsigned char current_DOR = 0x0C; //寄存器
int ticks_to_floppy_on(unsigned int nr) //nr为软驱号,返回值是启动马达所需要的的嘀嗒数 { extern unsigned char selected; unsigned char mask = 0x10 << nr; //软驱输出寄存器启动马达对应的比特位,nr=0对应0001 0000,nr=1对应0010 0000,nr=3对应1000 0000。分别对应4个马达 if (nr>3) //软驱有4个,超过4个则panic panic("floppy_on: nr>3"); moff_timer[nr]=10000; /* 100 s = very big :-) */设置软驱停转之前的嘀嗒数100秒 cli(); /* use floppy_off to turn it off */关中断 mask |= current_DOR; //取当前寄存器的值放到mask当中 if (!selected) { //如果当前没有选择软驱 mask &= 0xFC; //复位软驱的选择位 mask |= nr; } if (mask != current_DOR) { outb(mask,FD_DOR); if ((mask ^ current_DOR) & 0xf0) mon_timer[nr] = HZ/2; else if (mon_timer[nr] < 2) mon_timer[nr] = 2; current_DOR = mask; //更新当前数字输出寄存器 } sti(); //开中断 return mon_timer[nr]; }
启动马达,nr对应0-3表示第几个马达
void floppy_on(unsigned int nr) { cli(); //关中断 while (ticks_to_floppy_on(nr)) //启动马达所需要的时间是否到达 sleep_on(nr+wait_motor); sti(); }
关闭马达,
void floppy_off(unsigned int nr) { moff_timer[nr]=3*HZ;//3秒(如果不调用这个函数,100秒之后也会自动关闭) }
软盘的处理子程序,这个子程序会在时钟中断时会被调用,因为系统每个嘀嗒数是10毫秒,所以这个函数每10毫秒会被调用一次
void do_floppy_timer(void) { int i; unsigned char mask = 0x10; for (i=0 ; i<4 ; i++,mask <<= 1) { if (!(mask & current_DOR)) //如果不是指定的马达,则跳过 continue; if (mon_timer[i]) { // if (!--mon_timer[i]) //如果马达启动定时时间到 wake_up(i+wait_motor); //唤醒这个进程 } else if (!moff_timer[i]) { //如果马达停转定时时间到了,则复位相应的马达启动位。 current_DOR &= ~mask; outb(current_DOR,FD_DOR);//并且更新数字输出寄存器 } else moff_timer[i]--; } }
#define TIME_REQUESTS 64 //最多有64个定时器 static struct timer_list { //定时器链表 long jiffies; //定时嘀嗒数 void (*fn)(); //函数指针,对应定时处理程序 struct timer_list * next; //下一个定时器 } timer_list[TIME_REQUESTS], * next_timer = NULL; //next_timer定时链表的头指针 //添加定时器 void add_timer(long jiffies, void (*fn)(void)) //输入参数是指定的定时值和相应的处理函数 { struct timer_list * p; if (!fn) return; cli(); if (jiffies <= 0) //如果定时值小于0,则我们立刻调用处理函数 (fn)(); else { for (p = timer_list ; p < timer_list + TIME_REQUESTS ; p++) if (!p->fn) break; if (p >= timer_list + TIME_REQUESTS) panic("No more time requests free"); p->fn = fn; //向定时器链表添加新元素 p->jiffies = jiffies; p->next = next_timer; next_timer = p; while (p->next && p->next->jiffies < p->jiffies) { //链表按照定时值从小到大排序,这个循环就是进行排序 p->jiffies -= p->next->jiffies; fn = p->fn; p->fn = p->next->fn; p->next->fn = fn; jiffies = p->jiffies; p->jiffies = p->next->jiffies; p->next->jiffies = jiffies; p = p->next; } } sti(); }
时钟中断的处理程序,在system_call.s中有定义_timer_interrupt,在_time_interrupt中调用了do_timer函数。当一个进程时间片用完之后,就会进行切换。在https://www.cnblogs.com/zhenjingcool/p/15968968.html这个文章中,我们知道8259A的INT0接收时钟中断,时钟中断发生后,就会调用中断处理程序_timer_interrupt(见sched.c的set_intr_gate(0x20,&timer_interrupt);处),进而执行do_timer函数,进而判断时间片是否用完,用完后执行schedule()函数重新进行进程调度。
void do_timer(long cpl) //cpl当前特权级 { extern int beepcount; //扬声器 extern void sysbeepstop(void); //关闭扬声器,在讲解字符设备的时候会进一步讲解 if (beepcount) if (!--beepcount) sysbeepstop(); if (cpl) //用户态 current->utime++; else current->stime++; //内核态 if (next_timer) { //next_timer是定时链表的头指针,如果存在 next_timer->jiffies--; //定时时间减1 while (next_timer && next_timer->jiffies <= 0) { //如果减到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();//执行软盘定时程序 if ((--current->counter)>0) return; current->counter=0; if (!cpl) return; schedule(); }
设置报警定时时间的值
int sys_alarm(long seconds) { int old = current->alarm; if (old) old = (old - jiffies) / HZ; current->alarm = (seconds>0)?(jiffies+HZ*seconds):0; return (old); }
降低对CPU使用的优先权
int sys_nice(long increment) { if (current->priority-increment>0) current->priority -= increment; return 0; }
内核调度程序的初始化子程序,这个函数在main.c中被调用。
这个函数的作用是初始化任务0,设置任务0的tss,ldt描述符。
void sched_init(void) { int i; struct desc_struct * p; //描述符表的结构指针 if (sizeof(struct sigaction) != 16) panic("Struct sigaction MUST be 16 bytes"); set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));//在全局描述符表中设置任务0的任务状态段tss描述符。FIRST_TSS_ENTRY=4。在讲解head.s的时候我们介绍过gdt中已经有4(0-3)项了,这里gdt+4表示再添加一项,该项就是tss段描述符 set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));//在全局描述符表中设置任务0的ldt描述符。FIRST_LDT_ENTRY=5,也就是说gdt第5项为局部段描述符表ldt的描述符 p = gdt+2+FIRST_TSS_ENTRY; //p指向gdt+6的位置,这个位置当前是空的 for(i=1;i<NR_TASKS;i++) {初始阶段只有一个任务0,这里清除其他任务。以及gdt中第6项后面的项(因为gdt[4]和gdt[5]存放的是0号任务的tss段选择符和ldt段选择符,因此从第6项开始清除) ; 经过这个循环后task数组中只有一个task0,gdt表也只有gdt[0-5],分别是[null,task
0的代码段描述符,task0的数据段描述符,null,task0的tss段描述符、task0的ldt段描述符] ; 这样就完成了任务0的初始化 task[i] = NULL; p->a=p->b=0; //这里重复两次是因为每个任务对应一个tss和一个ldt p++; p->a=p->b=0; p++; } /* Clear NT, so that we won't have troubles with that later on */清除标志寄存器NT,这个标志位作用是控制嵌套调用的 __asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl");//把NT位复位。pushfl作用是将eflags标志寄存器入栈,popfl是弹出eflags ltr(0);//见下面解释 lldt(0); outb_p(0x36,0x43); /* binary, mode 3, LSB/MSB, ch 0 */ outb_p(LATCH & 0xff , 0x40); /* LSB */ outb(LATCH >> 8 , 0x40); /* MSB */ set_intr_gate(0x20,&timer_interrupt);//设置时钟中断门,这里设置之后,每一个嘀嗒(10ms)将会调用一次时间中断处理函数,进而每10ms进行一次进程调度。 outb(inb_p(0x21)&~0x01,0x21); set_system_gate(0x80,&system_call);//设置系统调用中断端口和中断处理函数 }
上面的ltr(0)作用是加载tss段选择子地址到tr寄存器中。它是个宏定义,其定义在sched.h中
#define _TSS(n) ((((unsigned long) n)<<4)+(FIRST_TSS_ENTRY<<3)) #define ltr(n) __asm__("ltr %%ax"::"a" (_TSS(n)))
因为gdt中已经有了任务0的代码段数据段tss段和ldt段,其在gdt中的分布是这样的
(FIRST_TSS_ENTRY<<3)是前4个内核段的描述符使用的共32个字节。
(((unsigned long) n)<<4),每个进程有TSS和LDT,每个进程会使用掉8x2=16个字节,也就是nx16+32就是第n个进程的TSS描述符的地址。
因此,ltr(0)执行完毕后,tr寄存器就指向了tss段。
lldt(0)同样的分析方式。执行完后,局部描述符表寄存器ldtr将指向ldt。