我是如何学习写一个操做系统(六):进程的调度

前言

既然引进了多进程,其实也就是在进程之间来回切换,那么就会有进程之间的调度问题。实则是在可运行进程之间分配有限的处理器时间资源的内核子系统。html

几个简单的CPU调度算法

  • First Come, First Served(FCFS)

其实就是一个先进先出队列了,也就是说先申请的进程,先执行。当CPU空闲时,它会分配给位于队列头部的进程,而且这个运行进程从队列中移去。FCFS调度代码编写简单而且理解容易。算法

可是对于一个须要和用户进行交互的进程,这种调度算法就会形成体验很是很差,由于周转时间须要完成一整个队列的任务,很是的长数组

但FCFS调度算法是非抢占的。一旦 CPU 分配给了一个进程,该进程就会使用 CPU 直到释放 CPU 为止,即程序终止或是请求I/O。函数

<!--more-->this

  • Shortest Job First(SJF)

SJF调度算法就指对短做业或者短进程优先调度的算法,将每一个进程与其估计运行时间进行关联选取估计计算时间最短的做业投入运行。这样就能够缩短周转时间spa

最短做业优先(SJF)调度算法将每一个进程与其下次CPU执行的长度关联起来。当 CPU 变为空闲时,它会被赋给具备最短 CPU 执行的进程。若是两个进程具备一样长度的 CPU 执行那么能够由FCFS来处理。code

  • RR

该算法中,将一个较小时间单元定义为时间量或时间片。时间片的大小一般为 10~100ms。就绪队列做为循环队列。CPU调度程序循环整个就绪队列,为每一个进程分配不超过一个时间片的CPU。htm

为了实现RR调度,咱们再次将就绪队列视为进程的 FIFO 队列。新进程添加到就绪队列的尾部。CPU 调度程序从就绪队列中选择第一个进程,将定时器设置在一个时间片后中断,最后分派这个进程。blog

调度算法的折中

在运行的许多进程里,有的进程更关心响应时间,有的进程更关心周转时间,因此调度算法就须要折中,可是如何折中又是一个问题。队列

Linux0.11 调度算法

schedule

schedule是Linux0.11里最主要的调度算法,可是十分简洁

  • task_struct是用来描述一个进程的结构体

    task_struct的counter是调度算法实现折中的一个关键,它既用来表示分配的时间片,又用来表示进程的优先级

  • 首先从任务数组中最后一个任务开始循环检测一些域

    若是设置过任务的定时值alarm,而且已通过期(alarm<jiffies),则在信号位图中置SIGALRM信号,即向任务发送SIGALARM信号。而后清alarm。还有一些信号量相关的会在后面再提

  • 找到数值最大的一个couter,也就是运行时间最少的一个进程,切换到它的进程

  • 当执行完一回的轮转就会从新分配一次时间片,这时候对于已经轮转过的进程,时间片将会被设置为初值,可是对于那些阻塞的进程,时间片将会增长,也就是进行了一次折中的调度。

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

init

咱们在顺便来看一下sched_init,这个函数内核调度程序的初始化子程序,就是初始化一些中断和描述符

  • 首先调用set_tss_desc和set_ldt_desc设置进程0的tss和ldt

  • 清任务数组和描述符表项

  • 以后初始化8253定时器

  • 最后是设置时钟中断和系统调用中断

void sched_init(void)
{
	int i;
	struct desc_struct * p;

	if (sizeof(struct sigaction) != 16)
		panic("Struct sigaction MUST be 16 bytes");
	set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));
	set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));
	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++;
	}
/* Clear NT, so that we won't have troubles with that later on */
	__asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl");
	ltr(0);
	lldt(0);
	outb_p(0x36,0x43);		/* binary, mode 3, LSB/MSB, ch 0 */
	outb_p(LATCH & 0xff , 0x40);	/* LSB */
	outb(LATCH >> 8 , 0x40);	/* MSB */
	set_intr_gate(0x20,&timer_interrupt);
	outb(inb_p(0x21)&~0x01,0x21);
	set_system_gate(0x80,&system_call);
}

设置描述符

_set_tssldt_desc其实就是根据描述符各个位的做用来进行设置

#define set_tss_desc(n,addr) _set_tssldt_desc(((char *) (n)),((int)(addr)),"0x89")
#define set_ldt_desc(n,addr) _set_tssldt_desc(((char *) (n)),((int)(addr)),"0x82")

#define _set_tssldt_desc(n,addr,type) \
__asm__ ("movw $104,%1\n\t" \
	"movw %%ax,%2\n\t" \
	"rorl $16,%%eax\n\t" \
	"movb %%al,%3\n\t" \
	"movb $" type ",%4\n\t" \
	"movb $0x00,%5\n\t" \
	"movb %%ah,%6\n\t" \
	"rorl $16,%%eax" \
	::"a" (addr), "m" (*(n)), "m" (*(n+2)), "m" (*(n+4)), \
	 "m" (*(n+5)), "m" (*(n+6)), "m" (*(n+7)) \
	)

设置中断

#define _set_gate(gate_addr,type,dpl,addr) \
__asm__ ("movw %%dx,%%ax\n\t" \
	"movw %0,%%dx\n\t" \
	"movl %%eax,%1\n\t" \
	"movl %%edx,%2" \
	: \
	: "i" ((short) (0x8000+(dpl<<13)+(type<<8))), \
	"o" (*((char *) (gate_addr))), \
	"o" (*(4+(char *) (gate_addr))), \
	"d" ((char *) (addr)),"a" (0x00080000))

#define set_intr_gate(n,addr) \
	_set_gate(&idt[n],14,0,addr)

#define set_system_gate(n,addr) \
	_set_gate(&idt[n],15,3,addr)

小结

这一篇主要看了一下Linux0.11里的调度算法,十分的简洁,可是又照顾了响应时间,又照顾了周转时间。

而后提了一下内核调度程序的初始化子程序,其实就是根据以前说的设置一些描述符和中断处理

原文出处:https://www.cnblogs.com/secoding/p/11422525.html

相关文章
相关标签/搜索