当前位置: 首页 > wzjs >正文

牟平建设局网站北留德庄北京企业网络推广外包

牟平建设局网站北留德庄,北京企业网络推广外包,wordpress 万网空间,如何用快站做pc端网站🚀 前言 所谓的进程调度,其实就是让 CPU 一会去程序 1 的位置处运行一段时间,一会去程序 2 的位置处运行一段时间。本文是在真正去进行进程调度之前所需要的一些准备。本文对应的章节是书中第23~24回。希望各位给个三连,拜托啦&am…

🚀 前言

    所谓的进程调度,其实就是让 CPU 一会去程序 1 的位置处运行一段时间,一会去程序 2 的位置处运行一段时间。本文是在真正去进行进程调度之前所需要的一些准备。本文对应的章节是书中第23~24回。希望各位给个三连,拜托啦,这对我真的很重要!!!

目录

  • 🚀 前言
  • 🏆进程调度大体流程
    • 📃 如何让CPU一会去这里一会去那里
    • 📃上下文环境
    • 📃 进程调度时机
    • 📃优先级
    • 📃进程状态
  • 🏆从定时器的角度看进程调度
    • 📃定时器中断
    • 📃定时器中断函数剩余部分(选看)
    • 📃进程调度函数
    • 📃
  • 🎯总结
  • 📖参考资料

🏆进程调度大体流程

📃 如何让CPU一会去这里一会去那里

    有种方法是在程序1里面隔几行就放弃自己的执行权跳转程序2,程序2也是如此。但是这种方法不靠谱且不公平。那就想个办法,找一个第三方来协调,这个第三方就是调度程序。

    这时候就得提到定时器了,之前初始化的时候提到过的,不记得的话可以查看这篇博客:linux0.11内核源码修仙传第十章——进程调度始化简单来说就是定时器根据设定好的时间定时向CPU发送一个计时器中断。之后的进程调度函数的基础就是这个时钟。

📃上下文环境

    有计算机基础的同学可能知道,虚拟内存的一大优点就是让每个程序都以为自己能用的整个内存,但是其实从物理层面上,只要这个程序执行程序,那就肯定会涉及寄存器,内存和外设端口。那总不能每个程序都配一组吧,自然只能做出避让了。

    这些需要避让的寄存器就算是上下文环境,具体的寄存器如下所示:

在这里插入图片描述
    具体会出现什么样的干扰呢?比如程序1往eax寄存器写入一个值准备用,这是被切换到了进程2,又往 eax 写入了一个值。那么回到进程1的时候不就出错了吗。所以聪明的你一定想到了,每个进程都把自己的环境保存好,等到CPU控制权回到自己手上的时候,重新将这些寄存器赋成之前的值就好了,恭喜你,你已经学会了保存上下文!

    那么怎么保存呢?采用下面的结构体,下面的结构体就包含了所需要的所有寄存器:

struct task_struct {···struct tss_struct tss;
};struct tss_struct {long	back_link;	/* 16 high bits zero */long	esp0;long	ss0;		/* 16 high bits zero */long	esp1;long	ss1;		/* 16 high bits zero */long	esp2;long	ss2;		/* 16 high bits zero */long	cr3;long	eip;long	eflags;long	eax,ecx,edx,ebx;long	esp;long	ebp;long	esi;long	edi;long	es;		/* 16 high bits zero */long	cs;		/* 16 high bits zero */long	ss;		/* 16 high bits zero */long	ds;		/* 16 high bits zero */long	fs;		/* 16 high bits zero */long	gs;		/* 16 high bits zero */long	ldt;		/* 16 high bits zero */long	trace_bitmap;	/* bits: trace 0, bitmap 16-31 */struct i387_struct i387;
};

    这里面注意,还有个 cr3 寄存器,还记得这个寄存器都是干嘛的嘛,可以看之前的博客:linux0.11内核源码修仙传第三章——head.s 的 📃开启方式 节。这个寄存器是指向页目录表首地址的。

在这里插入图片描述
    那就很有意思了,如果每个进程都有一个cr3寄存器,那不就意味着每个进程都可以有自己的页表了吗?事实就是如此,这样的话,线性地址到物理地址的映射关系就有能力做到不同。也就是说,在我们刚刚假设的理想情况下,不同程序用不同的内存地址可以做到内存互不干扰。但是很遗憾, Linux 0.11 并不是通过替换 cr3 寄存器来实现内存互不干扰的,这是后话了。

📃 进程调度时机

    在上面我们已经解决了进程转换时的准备,现在来看看进程转换时机。

    上面提到了定时器,那总不能每次时钟中断都切换一次吧?一来这样不灵活,二来这完全依赖时钟中断的频率,有点危险。所以一个好的办法就是,给进程一个属性,叫剩余时间片,每次时钟中断来了之后都 -1,如果减到 0 了,就触发切换进程的操作。在代码里面用counter来代表。

struct task_struct {···long counter;···struct tss_struct tss;
};

    他的用法也非常简单,就是每次中断都判断一下是否到 0 了。

void do_timer(long cpl)
{···// 当前还有剩余时间直接返回if ((--current->counter)>0) return;current->counter=0;if (!cpl) return;// 没有时间片,调度schedule();
}

    如果还没到 0,就直接返回,相当于这次时钟中断什么也没做,仅仅是给当前进程的时间片属性做了 -1 操作。如果已经到 0 了,就触发进程调度,选择下一个进程并使 CPU 跳转到那里运行。至于进程调度的逻辑 schedule 函数里面怎么实现,怎么调,我们先不管。

📃优先级

    那么已经解决了调度时机的问题,现在还有个问题,这个counter每个进程设置多少合适呢?同时这个问题也关乎着集成调度之后counter重新设置多少。

    往宏观想一下,这个值越大,那么 counter 就越大,那么每次轮到这个进程时,它在 CPU 中运行的时间就越长,也就是这个进程比其他进程得到了更多 CPU 运行的时间。那么这个变量的名字可以有一个大家都很熟悉的名字,叫优先级。可以在结构体里面再添加一个priority变量:

struct task_struct {···long counter;long priority;···struct tss_struct tss;
};

    每次一个进程初始化时,都把 counter 赋值为这个 priority,而且当 counter 减为 0 时,下一次分配时间片,也赋值为这个。

📃进程状态

    现在我们知道了进程调度时机,进程切换保存上下文,现在还需要保存一下进程状态,这是为什么呢?很简单的一个场景,一个进程中有一个读取硬盘的操作,发起读请求后,要等好久才能得到硬盘的中断信号。那这个时间其实该进程再占用着 CPU 也没用,此时就可以选择主动放弃 CPU 执行权,然后再把自己的状态标记为等待中。意思是告诉进程调度的代码,先别调度我,因为我还在等硬盘的中断,现在轮到我了也没用,把机会给别人吧。那这个状态可以记录一个属性了,叫 state,记录了此时进程的状态。

struct task_struct {long state;long counter;long priority;···struct tss_struct tss;
};

    来看看linux进程一共有多少种:

#define TASK_RUNNING		0
#define TASK_INTERRUPTIBLE	1
#define TASK_UNINTERRUPTIBLE	2
#define TASK_ZOMBIE		3
#define TASK_STOPPED		4

    现在我们有了进程状态,进程调度时机,进程优先级,进程上下文保存,已经可以完成调度任务了。

🏆从定时器的角度看进程调度

📃定时器中断

    接下来我们重点看看定时器的事,我们都知道定时器每隔一段时间会向CPU发起一个中断信号,这个间隔时间被设置为10ms,也就是100Hz:

#define HZ 100

    发起中断的函数是timer_interrupt,详细可以看这篇博客:linux0.11内核源码修仙传第十章——进程调度始化。

void sched_init(void)
{···set_system_gate(0x80,&system_call);
}_timer_interrupt:···# 增加系统滴答数incl _jiffies···# 调用定时器中断函数 do_timercall _do_timer···

    上面的函数做了简化,一件事是将系统滴答数这个变量 jiffies 加一,一个是调用了另一个函数 do_timer,详细的_timer_interrupt函数见下一节。接下来看看do_timer函数:

void do_timer(long cpl)
{···if ((--current->counter)>0) return;···schedule();
}

    do_timer 函数最重要的就是上面这一段,内容就是将当先进程的时间片 -1,然后判断时间片,如果时间片仍然大于零,则什么都不做直接返回;如果时间片已经为零,则调用 schedule(),这个函数就是进程调度的主干。看到这里其实已经可以了,如果想了解这个函数全貌可以看下面一节的内容。

📃定时器中断函数剩余部分(选看)

    先来看定时器中断函数,首先来看最开始的压栈:

_timer_interrupt:push %ds		# save ds,es and put kernel data spacepush %es		# into them. %fs is used by _system_callpush %fspushl %edx		# we save %eax,%ecx,%edx as gcc doesn'tpushl %ecx		# save those across function calls. %ebxpushl %ebx		# is saved as we use that in ret_sys_callpushl %eax···

    要注意,CPU在发生中断后会自行压栈一些寄存器的值:SS,ESP,EFLAGS,CS,EIP 。然后这里又压入了一些值,现在整个栈的分布情况如下所示:

在这里插入图片描述
    至此,包括CPU自动压栈的寄存器,基本所有重要的寄存器都有了,也就是进程上下文进行了保存。翻看中断函数可以发现这是所有中断处理函数的统一操作。

    接下来来看下一段:

_timer_interrupt:···movl $0x10,%eaxmov %ax,%dsmov %ax,%esmovl $0x17,%eaxmov %ax,%fsincl _jiffies···

    这一段改变了DS和ES寄存器,值是0x10,这个正好对应了内核数据段选择子,也就是GDT中下标为2的位置。之后FS是用户数据段选择子,分析也是同理。最后incl _jiffies 递增系统时钟滴答计数器。jiffies 变量记录自系统启动以来的时钟中断次数。

    中断里面只是对计数变量进行递增,接下来就是通知中断控制芯片,当前中断处理完毕,允许接收新中断:

_timer_interrupt:···movb $0x20,%al		# EOI to interrupt controller #1outb %al,$0x20···

    0x20EOI命令码,0x20是主PIC的端口地址。

    之后判断中断发生时CPU处于内核态(CPL=0)还是用户态(CPL=3)作为参数最后传给后续的do_timer寄存器:

_timer_interrupt:···movl CS(%esp), %eax ; 从栈中加载原始CS值andl $3, %eax       ; 提取CPL(CS的最低两位)pushl %eax          ; 将CPL作为参数压栈···

    最后调用定时器处理函数并返回到公共出口,最后jmp的公共出口会通过统一出口恢复上下文:

_timer_interrupt:···call _do_timer      ; 调用C函数do_timer(CPL)addl $4, %esp       ; 清理参数(弹出CPL)jmp ret_from_sys_call ; 跳转到系统调用/中断返回路径

    总结一下这个函数前后做了什么:

上下文保存于恢复:保存寄存器值,确保C代码能安全使用这些寄存器,最后进行中断返回。
特权级判断:获取CS寄存器低两位CPL,统计用户态和内核态时间。
进程调度函数:计算了jiffies 变量并调用do_timer函数
中断结束:给PIC发送EOI信号,通知PIC接收新中断,避免中断丢失。

    接下来看调用的do_timer 函数,首先如果计数的变量为零(初始值为之前设置时钟频率的1/8),则停止计数。之后依据上面_timer_interrupt获取的CPL,更新用户态或者内核态的CPU时间:

static void sysbeep(void)
{···beepcount = HZ/8;	
}void sysbeepstop(void)
{// 0x61端口的Bit0是定时器(1开始计数,0停止计数),Bit1是扬声器(1是发声,0是静音)outb(inb_p(0x61)&0xFC, 0x61);
}// cpl:优先级
void do_timer(long cpl)
{extern int beepcount;extern void sysbeepstop(void);if (beepcount)if (!--beepcount)sysbeepstop();if (cpl)current->utime++;elsecurrent->stime++;···
}

    下面是实现内核定时任务:

void do_timer(long cpl)
{···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)();}}···
}

    将人话就是这个循环里面首先递减第一个定时器的 jiffies;若 jiffies <= 0,执行其回调函数 fn(),并移除该节点;循环处理所有到期的定时器(支持多个定时器同时到期)。

    最后一段了,最后是时间片管理,并调用调度函数:

void do_timer(long cpl)
{···if (current_DOR & 0xf0)do_floppy_timer();if ((--current->counter)>0) return;current->counter=0;if (!cpl) return;schedule();
}

    软盘已经用的不多了,因此第一个if不重要,重要的是后面时间片管理和调度。每次中断后剩余进程片都减一,如果时间片还没用完,直接返回,如果时间片用完,则重置为0。其次,仅当中断发生在用户态时,会调用调度函数 schedule()。如果是在内核态,则不触发调度,确保内核代码的原子性。

📃进程调度函数

    上面铺垫终于结束了,终于来到了进程调度函数schedule(),先来看整体代码一饱眼福:

void schedule(void)
{int i,next,c;struct task_struct ** p;/* check alarm, wake up any interruptible tasks that have got a signal */for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)if (*p) {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;}/* this is the scheduler proper: */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;}if (c) break;for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)if (*p)(*p)->counter = ((*p)->counter >> 1) +(*p)->priority;}switch_to(next);
}

    下面进行拆解,直接抽取出主干:

void schedule(void) {int next = get_max_counter_and_runnable_thread();refresh_all_thread_counter();switch_to(next);
}

    所以调度函数只干了三件事:

拿到剩余时间片最大且在runnable 状态的进程号 next。
如果所有 runnable 进程时间片都为 0,则将所有进程(不仅仅是 runnable 的进程)的 counter 重新赋值,然后再次执行步骤 1
最后拿到了一个进程号 next,调用了 switch_to(next) 这个方法,就切换到了这个进程去执行了。

    上面第二步中,要将所有进程的counter重新复制,那么有个问题,只是runnable的进程全是0了,但是其他进程还不一定为0,那么赋值多少合适呢?这里linus给出的答案是:(counter = counter/2 + priority) 现在可以来看具体的代码了:

void schedule(void)
{···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;}if (c) break;for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)if (*p)(*p)->counter = ((*p)->counter >> 1) + (*p)->priority;}switch_to(next);
}

    schedule函数里面就是一直遍历所有非空任务,选择状态为TASK_RUNNINGcounter 值最大的任务。counter 表示剩余时间片,越大优先级越高。若找到有效任务(c > 0),跳出循环!这不是和上面第一步对上了吗!最后一个for循环就是所有就绪任务的counter为0,重新计算所有任务的counter,计算公式就是上面介绍的那个。

    现在来看最后的switch函数:

#define switch_to(n) {\
struct {long a,b;} __tmp; \
__asm__("cmpl %%ecx,_current\n\t" \"je 1f\n\t" \"movw %%dx,%1\n\t" \"xchgl %%ecx,_current\n\t" \"ljmp %0\n\t" \"cmpl %%ecx,_last_task_used_math\n\t" \"jne 1f\n\t" \"clts\n" \"1:" \::"m" (*&__tmp.a),"m" (*&__tmp.b), \"d" (_TSS(n)),"c" ((long) task[n])); \
}

    这里就是进程切换的底层代码了。看不懂没关系,其实主要就干了一件事,就是 ljmp 到新进程的 tss 段处。就是 CPU 规定,如果 ljmp 指令后面跟的是一个 tss 段,那么,会由硬件将当前各个寄存器的值保存在当前进程的 tss 中,并将新进程的 tss 信息加载到各个寄存器。说人话就是保存当前进程上下文,恢复下一个进程的上下文,跳过去!

在这里插入图片描述

📃

    来总结一下流程:

罪魁祸首的,就是那个每 10ms 触发一次的定时器滴答
这个滴答将会给 CPU 产生一个时钟中断信号
这个中断信号会使 CPU 查找中断向量表,找到操作系统写好的一个时钟中断处理函数 do_timer
do_timer 会首先将当前进程的 counter 变量 -1,如果 counter 此时仍然大于 0,则就此结束
如果 counter = 0 了,就开始进行进程的调度
进程调度就是找到所有处于 RUNNABLE 状态的进程,并找到一个 counter 值最大的进程,把它丢进 switch_to 函数的入参里
switch_to 这个终极函数,会保存当前进程上下文,恢复要跳转到的这个进程的上下文,同时使得 CPU 跳转到这个进程的偏移地址处
接着就是等待下一次时钟中断的来临

🎯总结

    这一章讲了很多东西,来做个回顾:首先我们自己设计了一下进程调度的规则,直到怎么保存上下文,设置进程运行优先级,规定了进程的几种状态。接下来从定时器的角度看进程调度,看了定时器中断函数,与进程调度函数。


📖参考资料

[1] linux源码趣读
[2] 一个64位操作系统的设计与实现

http://www.dtcms.com/wzjs/341148.html

相关文章:

  • python做的网站漏洞沈阳网站seo排名公司
  • 做雕塑设计的网站百度网盘网页版登录首页
  • 徐汇做网站公司做运营的具体做什么
  • 免费网站模板免费网站申请注册
  • 做网站销售那里找客户对网络营销的理解
  • dw创建网站导航栏菜单怎么做交换友情链接推广法
  • 做网站一年seo推广知识
  • 如何创建一个简单的网页直通车优化推广
  • 网站建设时间安排seo整站优化新站快速排名
  • 做时时彩网站合法的吗免费seo快速收录工具
  • 张艺兴粉丝做的网站网站seo设置是什么意思
  • 国内单页面网站电脑培训中心
  • 山西物价局建设工程检测网站首页怎么在百度上推广产品
  • 成都哪家网站开发好cilimao磁力猫在线搜索
  • 网站开发需要什么技能大数据营销平台那么多
  • 做一张简单的app网站多钱当阳seo外包
  • 软件定制开发订单无锡百度关键词优化
  • 怎么做动态网站php企业查询
  • 网站续费服务内容百度怎么发帖子
  • 医院网站如何建立百度快照是啥
  • 网站开发包括几个部分哪里的网络推广培训好
  • 遵义相亲平台上海城市分站seo
  • 美食网站建设背景北京网站优化哪家好
  • 商城网站模板html链接搜索
  • 不用域名推广网站免费做网站网站
  • 程序员做笔记的网站如何用模板建站
  • 广东世纪达建设集团有限公司官方网站优化软件刷排名seo
  • 提供网站建设公司建站平台哪家好
  • seo推广渠道有哪些外贸网站优化公司
  • 潍坊专业网站建设怎么收费浙江搜索引擎优化