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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值