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-基础知识3

打包和压缩 zip 安装zip软件包 yum -y install zip unzip 压缩打包命令: zip -q -r -d -u 压缩包文件名 目录和文件名列表 -q:不显示命令执行过程-r:递归处理,打包各级子目录和文件-u:把文件增加/替换到压缩包中-d:从压缩包中删除指定的文件 解压:unzip 压缩包名 打包文件 把压缩包从服务器下载到本地 把压缩包上传到服务器(zip

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统         在产品将要上线之前,需要制作不同类型格式的根文件系统         在产品研发阶段,我们还是需要使用nfs的方式挂载根文件系统         优点:可以直接在上位机中修改文件系统内容,延长EMMC的寿命         【1】重启上位机nfs服务         sudo service nfs-kernel-server resta

SWAP作物生长模型安装教程、数据制备、敏感性分析、气候变化影响、R模型敏感性分析与贝叶斯优化、Fortran源代码分析、气候数据降尺度与变化影响分析

查看原文>>>全流程SWAP农业模型数据制备、敏感性分析及气候变化影响实践技术应用 SWAP模型是由荷兰瓦赫宁根大学开发的先进农作物模型,它综合考虑了土壤-水分-大气以及植被间的相互作用;是一种描述作物生长过程的一种机理性作物生长模型。它不但运用Richard方程,使其能够精确的模拟土壤中水分的运动,而且耦合了WOFOST作物模型使作物的生长描述更为科学。 本文让更多的科研人员和农业工作者

MOLE 2.5 分析分子通道和孔隙

软件介绍 生物大分子通道和孔隙在生物学中发挥着重要作用,例如在分子识别和酶底物特异性方面。 我们介绍了一种名为 MOLE 2.5 的高级软件工具,该工具旨在分析分子通道和孔隙。 与其他可用软件工具的基准测试表明,MOLE 2.5 相比更快、更强大、功能更丰富。作为一项新功能,MOLE 2.5 可以估算已识别通道的物理化学性质。 软件下载 https://pan.quark.cn/s/57

【Linux 从基础到进阶】Ansible自动化运维工具使用

Ansible自动化运维工具使用 Ansible 是一款开源的自动化运维工具,采用无代理架构(agentless),基于 SSH 连接进行管理,具有简单易用、灵活强大、可扩展性高等特点。它广泛用于服务器管理、应用部署、配置管理等任务。本文将介绍 Ansible 的安装、基本使用方法及一些实际运维场景中的应用,旨在帮助运维人员快速上手并熟练运用 Ansible。 1. Ansible的核心概念