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

相关文章

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Linux中压缩、网络传输与系统监控工具的使用完整指南

《Linux中压缩、网络传输与系统监控工具的使用完整指南》在Linux系统管理中,压缩与传输工具是数据备份和远程协作的桥梁,而系统监控工具则是保障服务器稳定运行的眼睛,下面小编就来和大家详细介绍一下它... 目录引言一、压缩与解压:数据存储与传输的优化核心1. zip/unzip:通用压缩格式的便捷操作2.

Linux中SSH服务配置的全面指南

《Linux中SSH服务配置的全面指南》作为网络安全工程师,SSH(SecureShell)服务的安全配置是我们日常工作中不可忽视的重要环节,本文将从基础配置到高级安全加固,全面解析SSH服务的各项参... 目录概述基础配置详解端口与监听设置主机密钥配置认证机制强化禁用密码认证禁止root直接登录实现双因素

MySQL中的表连接原理分析

《MySQL中的表连接原理分析》:本文主要介绍MySQL中的表连接原理分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、背景2、环境3、表连接原理【1】驱动表和被驱动表【2】内连接【3】外连接【4编程】嵌套循环连接【5】join buffer4、总结1、背景

在Linux终端中统计非二进制文件行数的实现方法

《在Linux终端中统计非二进制文件行数的实现方法》在Linux系统中,有时需要统计非二进制文件(如CSV、TXT文件)的行数,而不希望手动打开文件进行查看,例如,在处理大型日志文件、数据文件时,了解... 目录在linux终端中统计非二进制文件的行数技术背景实现步骤1. 使用wc命令2. 使用grep命令

python中Hash使用场景分析

《python中Hash使用场景分析》Python的hash()函数用于获取对象哈希值,常用于字典和集合,不可变类型可哈希,可变类型不可,常见算法包括除法、乘法、平方取中和随机数哈希,各有优缺点,需根... 目录python中的 Hash除法哈希算法乘法哈希算法平方取中法随机数哈希算法小结在Python中,

Java Stream的distinct去重原理分析

《JavaStream的distinct去重原理分析》Javastream中的distinct方法用于去除流中的重复元素,它返回一个包含过滤后唯一元素的新流,该方法会根据元素的hashcode和eq... 目录一、distinct 的基础用法与核心特性二、distinct 的底层实现原理1. 顺序流中的去重

Linux如何快速检查服务器的硬件配置和性能指标

《Linux如何快速检查服务器的硬件配置和性能指标》在运维和开发工作中,我们经常需要快速检查Linux服务器的硬件配置和性能指标,本文将以CentOS为例,介绍如何通过命令行快速获取这些关键信息,... 目录引言一、查询CPU核心数编程(几C?)1. 使用 nproc(最简单)2. 使用 lscpu(详细信

linux重启命令有哪些? 7个实用的Linux系统重启命令汇总

《linux重启命令有哪些?7个实用的Linux系统重启命令汇总》Linux系统提供了多种重启命令,常用的包括shutdown-r、reboot、init6等,不同命令适用于不同场景,本文将详细... 在管理和维护 linux 服务器时,完成系统更新、故障排查或日常维护后,重启系统往往是必不可少的步骤。本文

关于MyISAM和InnoDB对比分析

《关于MyISAM和InnoDB对比分析》:本文主要介绍关于MyISAM和InnoDB对比分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录开篇:从交通规则看存储引擎选择理解存储引擎的基本概念技术原理对比1. 事务支持:ACID的守护者2. 锁机制:并发控制的艺