本文主要是介绍linux内和分析之sched.c程序,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
该内核程序主要包含进程调度程序的实现。进程调度采用了基于优先级的时间片轮转算法。
#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>
这是一个宏,用来取得信号的二进制数值。
输入:信号编号,1-32
输出:信号的二进制数值
#define _S(nr) (1<<((nr)-1))
被阻塞的信号掩码,其中有两个信号不能被阻塞SIGKLL和SIGSTOP。这两个信号的
位为0,其他位为1
#define _BLOCKABLE (~(_S(SIGKILL) | _S(SIGSTOP)))
显示制定任务的信息:包括进程id,任务号,任务当前状态以及指定任务内核态堆栈空闲的字节数
void
show_task (int nr, struct task_struct *p)
{
任务的内核态堆栈的大小不能大于4KB,计算除去task结构所剩余的栈空间
int i, j = 4096 - sizeof (struct task_struct);
打印任务的进程id,当前状态,任务号
printk ("%d: pid=%d, state=%d, ", nr, p->pid, p->state);
开始计算除task结构所占用的栈空间外,还剩余的空闲内核栈空间
i = 0;
while (i < j && !((char *) (p + 1))[i])
i++;
打印计算结果
printk ("%d (of %d) chars free in kernel stack/n/r", i, j);
}
打印所有任务的信息
NR_TASKS表示系统允许的最大任务数量
void
show_stat (void)
{
int i;
遍历task数组,打印信息
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);
一个联合体,该联合体既可以用字节形式表示,也可以用任务结构表示。
我们知道一个任务的内核堆栈大小是4KB,即一页大小。
union task_union
{
struct task_struct task;
char stack[PAGE_SIZE];
};
定义初始化任务数组
static union task_union init_task = { INIT_TASK, };
定义变量,从开机起经过的滴答数,10ms为一个滴答,将变量申明为volatile是因为
防止编译器优化而导致的变量值不一致的情况。优化后变量值很有可能直接来自寄存器
我们使用此变量申明修饰符,保证每次从内存中取变量的值。
long volatile jiffies = 0;
从开机后经历的秒数。从1970年1月1日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), };
定义任务堆栈,大小为1024个4字节的项组成。
long user_stack[PAGE_SIZE >> 2];
定义内核数据段的一个结构体,该结构体中包括
一个内核堆栈指针和一个段选择符
0x10:内核数据段段选择符
struct
{
long *a;
short b;
}
stack_start =
{
&user_stack[PAGE_SIZE >> 2], 0x10};
当任务发生切换的时候,用来保存上一个任务的协处理器上下文环境,恢复当前任务的
协处理器上下文环境。
void
math_state_restore ()
{
先判断当前换入的任务是不是上次被换出的任务,如果是直接返回
if (last_task_used_math == current)
return;
对协处理器发送命令前,应该先执行fwait指令。
__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;
}
}
核心进程调度程序
void
schedule (void)
{
int i, next, c;
struct task_struct **p;
从最够一个任务开始遍历
for (p = &LAST_TASK; p > &FIRST_TASK; --p)
如果任务数组中所指的任务不为NULL
if (*p)
{
判断定时器是否到期,如果到期需要在信号位图中置SIGALARM位,并且将定时器清0
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;
}
如果最大时间片不为0,那么就切换到该任务去运行
if (c)
break;
如果所有任务的时间片都为0,那么重新计算各个任务的时间片,计算原则是根据优先级进行计算
然后从新选择时间片最大的任务去运行。
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;
}
在指定任务上睡眠,任务0不能睡眠。如果cpu上没有任务运行时就运行任务0,该函数是
不可中断的。
void
sleep_on (struct task_struct **p)
{
struct task_struct *tmp;
if (!p)
return;
如果当前任务为任务0,死机。
if (current == &(init_task.task))
panic ("task[0] trying to sleep");
将该任务挂靠在tmp指针上,等下次被调度的时候在引用该指针
tmp = *p;
*p = current;
置当前任务为不可中断状态
current->state = TASK_UNINTERRUPTIBLE;
进程调度
schedule ();
该进程下次被调度到的时候,将任务的状态置为0,唤醒。
if (tmp)
tmp->state = 0;
}
该函数与上面的那个sleep函数的区别在于,如果下次再次调度到
睡眠的进程的时候,判断如果当前进程是睡眠的进程,需要重新进行
调度。
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;
}
将任务数组中的任务置为就绪唤醒状态,然后置该任务数组项为0.
void
wake_up (struct task_struct **p)
{
if (p && *p)
{
(**p).state = 0;
*p = NULL;
}
}
定义了timer list的最大长度
#define TIME_REQUESTS 64
定义timer list结构
static struct timer_list
{
系统开机开始算起的滴答数
long jiffies;
定时器的回调函数
void (*fn) ();
指向下一个timer节点
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 ();
如果滴答数到期,回调timeout函数
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;
对定时器链表进行排序,从小到大,并且将后面的滴答数递减他前面节点的滴答数,
也就是说,还有多少个滴答才会到下一个定时器expire.
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 ();
}
时钟中断处理函数调用此C函数。
void
do_timer (long cpl)
{
extern int beepcount;
extern void sysbeepstop (void);
if (beepcount)
if (!--beepcount)
sysbeepstop ();
如果当前任务的CPL为3,将用户时间递增,否则递增系统时间。
if (cpl)
current->utime++;
else
current->stime++;
将滴答数递减,如果滴答数小于等于0了,就需要回调超时回调函数然后将
超时节点这个资源归还给系统。即next_timer->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) ();
}
}
这部分是对软驱的处理,在这里不讲
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;
}
取进程id
int
sys_getppid (void)
{
return current->father;
}
取用户id
int
sys_getuid (void)
{
return current->uid;
}
取有效用户id
int
sys_geteuid (void)
{
return current->euid;
}
取组id
int
sys_getgid (void)
{
return current->gid;
}
取有效组id
int
sys_getegid (void)
{
return current->egid;
}
设置进程nice值,即降低优先级
int
sys_nice (long increment)
{
if (current->priority - increment > 0)
current->priority -= increment;
return 0;
}
在main.c中调用,对schedle进行初始化。
void
sched_init (void)
{
int i;
struct desc_struct *p;
if (sizeof (struct sigaction) != 16)
panic ("Struct sigaction MUST be 16 bytes");
向gdt中设置任务状态段描述符
set_tss_desc (gdt + FIRST_TSS_ENTRY, &(init_task.task.tss));
向gdt总设置局部描述符表的描述符
set_ldt_desc (gdt + FIRST_LDT_ENTRY, &(init_task.task.ldt));
描述符结构,8字节。
typedef struct desc_struct
{
unsigned long a, b;
}desc_table[256];
一共256项。
确定描述符标的偏移值。之前设置过tss和第一个ldt的描述符。
为任务数组清0,将128个描述符表项清0
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++;
}
清除标志寄存器中的NT标志位,嵌套标志位,如果中断处理程序调用iret指令,就会引起任务切换
__asm__ ("pushfl ; andl $0xffffbfff,(%esp) ; popfl");
将任务0的任务状态段的选择符加入到任务寄存器中
ltr (0);
将任务0的局部表选择符加载到局部描述符寄存器中。
lldt (0);
初始化定时器。
outb_p (0x36, 0x43);
outb_p (LATCH & 0xff, 0x40);
outb (LATCH >> 8, 0x40);
set_intr_gate (0x20, &timer_interrupt);
outb (inb_p (0x21) & ~0x01, 0x21);
set_system_gate (0x80, &system_call);
}
这篇关于linux内和分析之sched.c程序的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!