您的位置:首页 > 运维架构 > Linux

linux 0.11 内核学习 -- sched.c,调度进程。

2010-01-28 21:57 573 查看
/*

* 2010-1-21

* 该文件时内核中有关任务调度的函数程序,其中包含基本函数sleep_on,

* wakeup,schedule等,以及一些简单的系统调用。同时将软盘的几个操作

* 函数也放置在这里。

*

* schedule函数首先对所有的任务检查,唤醒任何一个已经得到信号的任务,

* 具体的方法是针对任务数组中的每个任务,检查其警报定时值alarm。如果任务

* 的alarm已经超期(alarm < jiffies),则在它的信号位图中设置SIGALARM,然后

* 情书alarm值。jiffies是系统自从开机之后算起的滴答数。在scheed.h中定义,

* 如果进程信号的位图中除去被阻塞的信号之外还有其他信号,并且任务处于可

* 中断睡眠状态,则置任务为就绪状态。

* 随后是调度函数的核心处理,这部分代码根据进程时间片和优先权的调度机制,

* 来选择将要执行的程序。他首先是循环检查任务数组中的所有任务。根据每个就绪

* 任务剩余执行时间值counter中选取一个最大的,利用switch_to函数完成任务

* 转换。如果所有的就绪任务的该值都是0,则表示此刻所有任务的时间片都已运行完。

* 于是就根据任务的优先权值priority,重置每个任务的运行时间counter。在重新

* 循环检查所有的任务重的执行的时间片值。

* 另一个值得一说的是sleep_on函数,该函数虽短,却要比schedule函数难理解,

* 简单的讲,sleep_on函数主要的功能是当一个进程所请求的资源正在忙,或者是

* 不在内存中被切换出去,放在等待队列中等待一段时间。切换回来后在继续执行。

* 放入等待队列的方式是,利用了函数中的tmp指针为各个正在等待任务的联系。

* 还有一个函数interrupt_sleep_on,该函数的主要功能是在进程调度之前,把当前

* 任务设置为可中断等待状态,并在本任务被唤醒之后还需要查看队列上是否还有

* 后来的等待任务,如果有,先调度他们。

*

*/



/*

* 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)) // 取nr(1-32)对应的位的二进制数值,取出的

// 并不是一位

// 定义除了SIGKILL和SIGSTOP之外,其他信号位全是阻塞的

#define _BLOCKABLE (~(_S(SIGKILL) | _S(SIGSTOP)))



//----------------------------------------------------------------------

// show_task

// 显示任务号nr,pid,进程状态和内核堆栈空闲字节

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);

}



//---------------------------------------------------------------------

// show_stat

// 显示所有任务的信息

void show_stat(void)

{

int i;



for (i=0;i<NR_TASKS;i++)

if (task[i]) // task是一个指针数组,如果存在”任务“

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

{

// 定义任务联合(任务结构成员和stack 字符数组程序成员),联合体是共享内存的

struct task_struct task; // 因为一个任务数据结构与其堆栈放在同一内存页中,所以

char stack[PAGE_SIZE]; // 从堆栈段寄存器ss 可以获得其数据段选择符

};



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 { // 该结果用于设置堆栈ss:esp

long * a;

short b;

} stack_start = { & user_stack [PAGE_SIZE>>2] , 0x10 };



//---------------------------------------------------------------------

// math_state_restore

/*

* '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"); // 在发送协处理器指令之前首先发出wait指令

if (last_task_used_math) // 如果上个使用协处理器的进程存在

{

// 保存状态

__asm__("fnsave %0"::"m" (last_task_used_math->tss.i387));

}

last_task_used_math=current; // 现在last_task_used_math指向当前任务

if (current->used_math) // 如果当前的任务使用过协处理器

{

__asm__("frstor %0"::"m" (current->tss.i387));// 恢复状态

} else // 没有使用过协处理器

{

__asm__("fninit"::); // 初始化协处理器

current->used_math=1; // 设置使用协处理器标志

}

}



//--------------------------------------------------------------------

// schedule

/*

* '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 */

/* 检查alarm,唤醒任何已得到信号的可中断任务 */

// 从任务数组最后开始检查alarm

for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)

if (*p) // 任务存在?

{

if ((*p)->alarm && (*p)->alarm < jiffies)

/*

* 下面是对(*p)->alarm < jiffies理解:

* 如果不调用alarm()函数,alarm的值就是0,但是调用了alarm函数

* 之后,比如说alarm(5),就是5秒后报警

*/

{

// 在位图信号中置位AIGALARM

(*p)->signal |= (1<<(SIGALRM-1));

// 将alarm值置位0

(*p)->alarm = 0;

}

// 如果信号位图中除被阻塞的信号外还有其他信号,即是该

// 进程申请的自愿被别的进程释放,或者是其他条件导致该

// 进程能够被执行,并且该任务处于的是可中断编程。linux

// 内核进程转换见文档<进程运行状态图.txt>。此时将该进程

// 进程状态置位就绪。

if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) &&

(*p)->state==TASK_INTERRUPTIBLE)

(*p)->state=TASK_RUNNING;

}



/* this is the scheduler proper: */

/* 这是调度的主要部分 */

while (1) // 循环,直到存在counter的值大于0

{

c = -1;

next = 0;

i = NR_TASKS;

p = &task[NR_TASKS];

////////////////////////////////////////////////

// 下面的代码是寻找最大counter值的进程。counter

// 值的含义见文档 <linux0.11task_struct中counter解释.txt>

while (--i) {

if (!*--p)

continue;

if ((*p)->state == TASK_RUNNING && (*p)->counter > c)

c = (*p)->counter, next = i;

}

////////////////////////////////////////////////

if (c) break; // 如果进程存在。退出,去执行switch_to

// 否则更新counter值

for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)

if (*p)

(*p)->counter = ((*p)->counter >> 1) +

(*p)->priority;

}

switch_to(next);

}



//--------------------------------------------------------------------

// sys_pause

// 将当前任务设置为可中断,执行调度函数

int sys_pause(void)

{

current->state = TASK_INTERRUPTIBLE;

schedule();

return 0;

}



//-------------------------------------------------------------------

// sleep_on

// struct task_struct **p是等待队列头指针

// 该函数就是首先将当前任务设置为TASK_UNINTERRUPTIBLE,并让睡眠队列

// 头指针指向当前任务,执行调度函数,直到有明确的唤醒时,该任务才重新

// 开始执行。

void 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;

*p = current;

current->state = TASK_UNINTERRUPTIBLE; // 设置状态

schedule(); // 执行调度,直到明确的唤醒

// 肯能存在多个任务此时被唤醒,那么如果还存在等待任务

// if (tmp),则将状态设置为”就绪“

if (tmp)

tmp->state=0;

}



//--------------------------------------------------------------------------

// interruptible_sleep_on

// struct task_struct **p是等待队列对头

void interruptible_sleep_on(struct task_struct **p)

{

/*

* 首先需要明确的是TASK_INTERRUPTIBLE。它是指当进

* 程处于可中断等待状态时,系统不会调度该进行执行。

*/

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 != current表示当前任务不是原来保存的任务,即是

* 有新的任务插入到等待队列中了。由于本任务是不可中断的

*,所以首先执行其他的任务

*/

(**p).state=0;

goto repeat;

}

/*

* 下面的代码错误,应该是*p = tmp,让队列头指针指向队列中其他任务

*/

*p=NULL;

// 原因同上。sleep_on

if (tmp)

tmp->state=0;

}

//----------------------------------------------------------------

// wake_up

// 唤醒任务p

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.

*/

/*

* 由于软盘需要时钟,所以就将软盘的程序放到这里了

*/

// 利用下面的数组来存储的是马达运转或者是停止的滴答数,下列的

// 数组在函数do_floppy_timer中更新

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; // 数字输出寄存器(初值:允许dma和中断请求,启动fdc)

// oxoc -- 0000,1100



//----------------------------------------------------------------

// ticks_to_floppy_on

// 指定软盘到正常运转所需的滴答数

// nr -- 软盘驱动号,该函数返回的是滴答数

int ticks_to_floppy_on(unsigned int nr)

{

extern unsigned char selected; // 当前选中软盘号

// ox10 -- 0001, 0000

unsigned char mask = 0x10 << nr; // 所选软盘对应的数字输出

// 寄存器启动马达比特位

/*

* 数字输出端口(数字控制端口)是一个8位寄存器,它控制驱动器马达开启

* ,驱动器选择,启动和复位FDC,以及允许和禁止DMA及中断请求。

* FDC的主状态寄存器也是一个8位寄存器,用户反映软盘控制器FDC和软盘

* 驱动器FDD的基本状态。通常,在CPU想FDC发送命令之前或者是从FDC获得

* 操作结果之前,都要读取主状态寄存器的状态位,以判定当前的数据是否

* 准备就绪,以及确定数据的传输方向

*

*/



if (nr>3) // 最多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; // mask =

// 如果不是当前软驱,则首先复位其它软驱的选择位,然后置对应软驱选择位

if (!selected)

{

mask &= 0xFC; // 0xfc -- 1111,1100

mask |= nr;

}

if (mask != current_DOR) // 如果数字输出寄存器的当前值和要求不同

// 即是需要的状态还没有到达

{

outb(mask,FD_DOR); // 于是向FDC数字输出端口输出新值

if ((mask ^ current_DOR) & 0xf0) // 如果需要启动的马达还没有

// 启动,则置相应的软驱马达定

// 时器值(50个滴答数)

mon_timer[nr] = HZ/2;

else if (mon_timer[nr] < 2)

mon_timer[nr] = 2;

current_DOR = mask; // 更新数字输出寄存器的值current_DOR,即是反映

// 当前的状态

}

sti(); // 开中断

return mon_timer[nr];

}



//--------------------------------------------------------------

// floppy_on

// 等待指定软驱马达启动所需时间,启动时间

void floppy_on(unsigned int nr)

{

cli(); // 关中断

while (ticks_to_floppy_on(nr))// 还没有到时间?

sleep_on(nr+wait_motor); // 为不可中断睡眠状态并放在

// 等待马达运行队列中

sti(); // 开中断

}



//---------------------------------------------------------------

// floppy_off

// 初始化数组moff_timer,即是表示置相应软驱马达停转定时器(3秒)

void floppy_off(unsigned int nr)

{

moff_timer[nr]=3*HZ;

}



//----------------------------------------------------------------

// do_floppy_timer

// 软盘定时处理子程序。更新马达启动定时值和马达关闭停转计时值。该子程序

// 是在时钟定时中断被调用,因此每一个滴答(10ms)被嗲用一次,更新马达开启

// 或者是停止转动定时器值。如果在某一个马达停转时刻到达,那么则将数字输出

// 寄存器马达启动复位标志

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;



//----------------------------------------------------------------------

// add_timer

// 增加定时器。输入参数为指定的定时器的滴答数和相应的处理函数指针。

// jiffies -- 以10ms计时的滴答数

// *fn() -- 定时时间到达时执行函数

void add_timer(long jiffies, void (*fn)(void))

{

struct timer_list * p;



if (!fn) // 如果处理函数为空

return; // 退出

cli(); // 关中断



if (jiffies <= 0) // 定时器到达

(fn)(); // 执行函数fn

else { // 否则,从定时器数组中,找到一个空闲项,使用fn来标识

for (p = timer_list ; p < timer_list + TIME_REQUESTS ; p++)

if (!p->fn)

break;

// 当上面的循环结束时,可能存在p == timer_list + TIME_REQUESTS

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(); // 开中断

}





//------------------------------------------------------------------------

// do_timer

// 时钟中断处理函数,在/kernel/system_call.s中_timer_interrupt_中被调用

// 参数cpl是当前的特权级0或3,0标识在内核代码段运行

// 对于一个进程由于时间片用完,则进行内核任务切换。并进行及时更新工作

void do_timer(long cpl)

{

extern int beepcount; // 扬声器发声时间滴答数

extern void sysbeepstop(void); // 关闭扬声器



if (beepcount)

if (!--beepcount) // 如果扬声器计数次数到

sysbeepstop(); // 关闭发生器



if (cpl) // 如果实在内核程序,即是超级用户

current->utime++; // 超级用户时间增加

else

current->stime++; // 普通用户运行时间增加



// 如果有用户定时器存在的话,则将第一个定时器的值减去1,如果已经等于0

// 那么调用函数fn,并将函数指针置为空值,然后去掉该定时器。注意的是上述

// 链表已经排序,同时在寻找空闲结点时,是通过fn是否为空来判断的,所以

// 将fn置位null

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)();

}

}

// 如果当前软盘控制器FDC的数字输出寄存器马达启动位有置位,则执行相应的

// 软盘定时程序

if (current_DOR & 0xf0)

do_floppy_timer();

if ((--current->counter)>0) return; // 如果进程运行时间还没有完,退出

current->counter=0;

if (!cpl) return; // 超级用户程序,不依赖counter值来调度

schedule();

}



//----------------------------------------------------------------------

// sys_alarm

// 如果已经设置了alarm的值,那么返回的是旧值,如果没有设置返回0

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);

}



//-----------------------------------------------------------------------

// sys_getpid

// 取得当前的进程号

int sys_getpid(void)

{

return current->pid;

}



//----------------------------------------------------------------------

// sys_getppid

// 取得父进程进程号

int sys_getppid(void)

{

return current->father;

}



//-----------------------------------------------------------------------

// sys_getuid

// 取得用户号uid

int sys_getuid(void)

{

return current->uid;

}



//------------------------------------------------------------------------

// sys_geteuid

// 取得用户号euid

int sys_geteuid(void)

{

return current->euid;

}



//----------------------------------------------------------------------

// sys_getgid

// 取得组号gid

int sys_getgid(void)

{

return current->gid;

}



//----------------------------------------------------------------------

// sys_getegid

// 取得进程的egid,有关egid的解释,参见文档<Linux 关于SUID和SGID的解释.txt>

int sys_getegid(void)

{

return current->egid;

}



//--------------------------------------------------------------------

// sys_nice

// 改变进程优先级

int sys_nice(long increment)

{

if (current->priority-increment>0)

current->priority -= increment;

return 0;

}



//----------------------------------------------------------------------

// sched_init

// 调度程序初始化,即是初始化进程0,init

void sched_init(void)

{

int i;

struct desc_struct * p;



if (sizeof(struct sigaction) != 16) // sigaction中存放的是信号状态结构

panic("Struct sigaction MUST be 16 bytes");

// 设置初始任务(任务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;

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 */

/* nt标志置位的话,那么当前的中断任务执行iret命令时引起任务的切换 */

__asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl");

ltr(0); // 将任务0的tss加载到寄存器

lldt(0); // 将局部描述符表加载到局部描述符表寄存器

// 是将gdt和相应的ldt描述符的选择符加载到ldtr。只是明确的加载这一次

// 以后任务的ldt加载,是cpu根据tss中的ldt自动加载的

// 初始化8253定时器

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);

}



/*

* 下面解释linux的init进程的整体认识

* 1.在系统的启动阶段时,bootsect.s文件只是见将系统加载到内存中,内核都

* 没有加载完成,谈不上对于进程管理的影响。setup.s中加载了全局的gdtr,

* 但是此时的gdt只是为了让程序运行在保护模式下,没有什么作用。在setup.s

* 文件中设置的gdt的格式如下 :

* -----------

* | 0 0 0 0 |

* -----------

* | code seg|

* ------------

* | data seg|

* -----------

* | ...... |

* 在head.s文件中还需要改写该gdt。经过该文件的修改后新生成的gdt如下:

* -----------

* | 0 0 0 0 |

* -----------

* | code |

* -----------

* | data |

* -----------

* | system | -- do not use in linux

* -----------

* | | -|

* ----------- |--剩下的各项是预留给其他任务,用于放置ldt和tss

* | | -|

*

* 在main.c文件中调用函数sched_init,此函数加载进程init。

* 加载init进程的ldt和tdd段

* |

* 由程序来执行加载ldt和tss段寄存器的任务

* |

* 即实现init进程的加载,即是手工创建的第一次进程。

* 此时的gdt大概上讲是:

* ----------

* | 0 0 0 0 |

* -----------

* | code |

* -----------

* | data |

* -----------

* | system |

* -----------

* | ldt0 |

* -----------

* | tss0 |

* -----------

* | ... |

* 此后,进程使用fork系统调用来产生新的进程,同时进程调度开始起作用。

*

*/



参考《linux内核完全注释》和网上相关文章
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: