linux内和分析之sched.c程序

2024-04-20 20:38
文章标签 分析 linux 程序 sched

本文主要是介绍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程序的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/921261

相关文章

Linux join命令的使用及说明

《Linuxjoin命令的使用及说明》`join`命令用于在Linux中按字段将两个文件进行连接,类似于SQL的JOIN,它需要两个文件按用于匹配的字段排序,并且第一个文件的换行符必须是LF,`jo... 目录一. 基本语法二. 数据准备三. 指定文件的连接key四.-a输出指定文件的所有行五.-o指定输出

Linux jq命令的使用解读

《Linuxjq命令的使用解读》jq是一个强大的命令行工具,用于处理JSON数据,它可以用来查看、过滤、修改、格式化JSON数据,通过使用各种选项和过滤器,可以实现复杂的JSON处理任务... 目录一. 简介二. 选项2.1.2.2-c2.3-r2.4-R三. 字段提取3.1 普通字段3.2 数组字段四.

Linux kill正在执行的后台任务 kill进程组使用详解

《Linuxkill正在执行的后台任务kill进程组使用详解》文章介绍了两个脚本的功能和区别,以及执行这些脚本时遇到的进程管理问题,通过查看进程树、使用`kill`命令和`lsof`命令,分析了子... 目录零. 用到的命令一. 待执行的脚本二. 执行含子进程的脚本,并kill2.1 进程查看2.2 遇到的

Nginx分布式部署流程分析

《Nginx分布式部署流程分析》文章介绍Nginx在分布式部署中的反向代理和负载均衡作用,用于分发请求、减轻服务器压力及解决session共享问题,涵盖配置方法、策略及Java项目应用,并提及分布式事... 目录分布式部署NginxJava中的代理代理分为正向代理和反向代理正向代理反向代理Nginx应用场景

Linux云服务器手动配置DNS的方法步骤

《Linux云服务器手动配置DNS的方法步骤》在Linux云服务器上手动配置DNS(域名系统)是确保服务器能够正常解析域名的重要步骤,以下是详细的配置方法,包括系统文件的修改和常见问题的解决方案,需要... 目录1. 为什么需要手动配置 DNS?2. 手动配置 DNS 的方法方法 1:修改 /etc/res

Linux创建服务使用systemctl管理详解

《Linux创建服务使用systemctl管理详解》文章指导在Linux中创建systemd服务,设置文件权限为所有者读写、其他只读,重新加载配置,启动服务并检查状态,确保服务正常运行,关键步骤包括权... 目录创建服务 /usr/lib/systemd/system/设置服务文件权限:所有者读写js,其他

Linux下利用select实现串口数据读取过程

《Linux下利用select实现串口数据读取过程》文章介绍Linux中使用select、poll或epoll实现串口数据读取,通过I/O多路复用机制在数据到达时触发读取,避免持续轮询,示例代码展示设... 目录示例代码(使用select实现)代码解释总结在 linux 系统里,我们可以借助 select、

Redis中的有序集合zset从使用到原理分析

《Redis中的有序集合zset从使用到原理分析》Redis有序集合(zset)是字符串与分值的有序映射,通过跳跃表和哈希表结合实现高效有序性管理,适用于排行榜、延迟队列等场景,其时间复杂度低,内存占... 目录开篇:排行榜背后的秘密一、zset的基本使用1.1 常用命令1.2 Java客户端示例二、zse

Linux挂载linux/Windows共享目录实现方式

《Linux挂载linux/Windows共享目录实现方式》:本文主要介绍Linux挂载linux/Windows共享目录实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录文件共享协议linux环境作为服务端(NFS)在服务器端安装 NFS创建要共享的目录修改 NFS 配

Redis中的AOF原理及分析

《Redis中的AOF原理及分析》Redis的AOF通过记录所有写操作命令实现持久化,支持always/everysec/no三种同步策略,重写机制优化文件体积,与RDB结合可平衡数据安全与恢复效率... 目录开篇:从日记本到AOF一、AOF的基本执行流程1. 命令执行与记录2. AOF重写机制二、AOF的