linux0.12-8-6-sched.c

这篇具有很好参考价值的文章主要介绍了linux0.12-8-6-sched.c。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

[315页]

8-6 sched.c程序

8-6-1 功能描述

sched.c是内核中有关任务(进程)调度管理的程序,其中包括有关调度的基本函数
(sleep_on(0、wakeup()、schedule()等)以及一些简单的系统调用函数(比如getpid())。
系统时钟中断处理过程中调用的定时函数do_timer()也被放置在本程序中。
另外,为了便于软盘驱动器定时处理的编程,Linus也将有关软盘定时操作的几个函数放到这里。

下面是说明schedule函数功能。
(a)schedule()函数负责选择系统中下一个要运行的进程。它首先对所有的任务(进程)进行检测,
唤醒任何一个已经得到信号的任务。具体方法是针对任务数组中的每个任务,检查其报警定时值alarm。
如果任务的alarm时间已经过期(alarm<jiffies),则在它的信号位图中设置值SIGALRM信号,然后清alarm值。
jiffies是系统从开机开始算起的滴答数(10ms/滴答)。在sched.h中定义。如果进程的信号位图中除去被阻塞的信号外
还有其他信号,并且任务处于可中断睡眠状态(TASK_INTERRUPTIBLE),则置任务为就绪状态(TASK_RUNNING)。

(b)随后是调度函数的核心处理部分。这部分代码根据进程的时间片和优先权调度机制,来选择随后要执行的任务。它首先循环
检查任务数组中的所有任务,根据每个就绪态任务剩余执行时间的值counter,选取该值最大的一个任务,并利用switch_to()函数切换到该任务。
若所有就绪任务的该值都等于零,表示此刻所有任务的时间片都已经运行完,于是就根据任务的优先权值prioriyt,重置
每个任务的运行时间片值counter,再重新执行循环检查所有任务的执行时间片值。

sleep_on()和wake_up()两个函数
如果有ROTS和链表知识,应该很好理解。

8-6-2 代码注释

/*
 *  linux/kernel/sched.c
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 * 'sched.c'是主要的内核文件。其中包括有关调度的基本函数(sleep_on、wakeup、schedule等)
 以及一些简单的系统调用函数(比如getpid(),仅从当前任务重获取一个字段)。
 */
 
//下面是调度程序头文件。定义了任务结构task_struct、第1个初始任务的数据。还有一些以宏
// 的形式定义的有关描述符参数设置和获取的嵌入式汇编函数程序。
#include <linux/sched.h>
#include <linux/kernel.h>		//内核头文件。含有一些内核常用函数的原形定义。
#include <linux/sys.h>			//系统调用头文件。含有82个系统调用C函数程序,以'sys_'开头。
#include <linux/fdreg.h>		//软驱头文件。含有软盘控制器参数的一些定义。
#include <asm/system.h>			//系统头文件。定义了设置或修改描述符/中断门等的嵌入式汇编宏。
#include <asm/io.h>				//io头文件。定义硬件端口输入/输出宏汇编语句。
#include <asm/segment.h>		//段操作头文件。定义了有关段寄存器操作的嵌入式汇编函数。
#include <signal.h>				//信号头文件。定义信号符号常量,sigaction结构,操作函数原型。

//该宏取信号nr在信号位图中对应位的二进制数值。信号编号1-31。
#define _S(nr) (1<<((nr)-1))
//除了 SIGKILL 和 SIGSTOP信号以外其他信号都是可阻塞的。
#define _BLOCKABLE (~(_S(SIGKILL) | _S(SIGSTOP)))

//内核调试函数。显示任务号nr的进程号、进程状态和内核堆栈空闲字节数(大约)。
void show_task(int nr,struct task_struct * p)
{
	int i,j = 4096-sizeof(struct task_struct);

	printk("%d: pid=%d, state=%d, father=%d, child=%d, ",nr,p->pid,
		p->state, p->p_pptr->pid, p->p_cptr ? p->p_cptr->pid : -1);
	i=0;
	while (i<j && !((char *)(p+1))[i])//检测指定任务数据结构以后等于0的字节数。 检测堆栈未使用的。
		i++;
	printk("%d/%d chars free in kstack\n\r",i,j);
	printk("   PC=%08X.", *(1019 + (unsigned long *) p));
	if (p->p_ysptr || p->p_osptr) 
		printk("   Younger sib=%d, older sib=%d\n\r", 
			p->p_ysptr ? p->p_ysptr->pid : -1,
			p->p_osptr ? p->p_osptr->pid : -1);
	else
		printk("\n\r");
}
//显示所有任务的任务号、进程号、进程状态和内核堆栈空闲字节数(大约)。
//NR_TASKS是系统能容纳的最大进程(任务)数量(64个),定义在include/kernel/sched.h第6行。
void show_state(void)
{
	int i;

	printk("\rTask-info:\n\r");
	for (i=0;i<NR_TASKS;i++)
		if (task[i])
			show_task(i,task[i]);
}
//PC8253定时芯片的输入时钟频率约为1.193180MHz。Linux内核希望定时器发出中断的频率是
//100Hz,也即每10ms发出一次时钟中断。因此这里LATCH是设置8253芯片的初值,参见438行。
#define LATCH (1193180/HZ)

extern void mem_use(void);//[??]没有任何地方定义和引用该函数。

extern int timer_interrupt(void);//时钟中断处理程序(kernel/system_call.s,176)。
extern int system_call(void);//系统调用中断处理程序(kernel/system_call.s,80)。

//每个任务(进程)在内核态运行时都有自己的内核态堆栈。这里定义了任务的内核态堆栈结构。
//这里定义任务联合(任务结构成员和stack字符数组成员)。因为一个任务的数据结构与其内核态堆栈
//放在同一内存页中,所以从堆栈段寄存器ss可以获得其数据段选择符。
union task_union {
	struct task_struct task;
	char stack[PAGE_SIZE];
};

//设置初始任务的数据。初始数据在include/kernel/sched.h中,第156行开始。
static union task_union init_task = {INIT_TASK,};

//从开机开始算起的滴答数时间值全局变量(10ms/滴答)。系统时钟中断每发送一次即一个滴答。
//前面的限定符volatile,英文解释是易改变的、不稳定的意思。这个限定词的含义是向编译器
//指明变量的内容可能会由于被其他程序修改而变化。通常在程序中声明一个变量时,编译器会
//尽量把它存放在通用寄存器中,例如ebx,以提高访问效率。当CPU把其值放到ebx中后一般就不会
//关系该变量对应内存位置中的内容。若此时其他chengx(例如内核程序或一个中断过程)
//修改了内存中该变量的值,bex中的值并不会随之更新。为了解决这种情况就创建了volatile
//限定符,让代码在引用该变量时一定要从指定内存位置中取得其值。这里即是要求gcc不要对
//jiffies进行优化处理,也不要挪动位置,并且需要从内存中取其值。因为时钟中断处理过程
//等程序会修改它的值。
unsigned long volatile jiffies=0;
unsigned long startup_time=0;//开机时间。从1970:0:0:0开始计时的秒数。
//这个变量用于累计需要调整的时间嘀嗒数。
int jiffies_offset = 0;
/*
为调整时钟而需要增加的时钟嘀嗒数,以获得"精确时间"。这些调整用嘀嗒数
的总和不应该超过1秒。这样做是为了那些对时间精确度要求苛刻的人,他们细化
自己的机器时间与WWV同步 :-)
*/
struct task_struct *current = &(init_task.task);//当前任务指针(初始化指向任务0)。
struct task_struct *last_task_used_math = NULL;//使用过协处理器任务的指针。

//定义任务指针数组。第1想被初始化指向初始任务(任务0)的任务数据结构。
struct task_struct * task[NR_TASKS] = {&(init_task.task), };

//定义用户堆栈,共1K项,容量4K字节。在内核初始化操作过程中被用作内核栈,初始化完成以后
//将被用作任务0的用户态堆栈。在运行任务0之前它是内核栈,以后用作任务0和1的用户态栈。
//下面结构用于设置堆栈ss:esp(数据段选择符,指针),见head.s,第23行。
//ss被设置为内核数据段选择符(0x10),指针esp指在user_stack数组最后一项后面。这是
//因为Intel CPU执行堆栈操作是是先递减堆栈指针sp值,然后再sp指针处保存入栈内容。
long user_stack [ PAGE_SIZE>>2 ] ;

struct {
	long * a;
	short b;
	} stack_start = { & user_stack [PAGE_SIZE>>2] , 0x10 };
/*
将当前协处理器内容保存到老协处理器状态数组中,并将当前任务的协处理器内容加载进协处理器。
 */
void math_state_restore()
{
//如果任务没变则返回(上一个任务就是当前任务)。这里"上一个任务"是指刚被交换出去的任务。
	if (last_task_used_math == current)
		return;
//在发送协处理器命令之前要先发WAIT指令。如果上个任务使用了协处理器,则保存其状态。		
	__asm__("fwait");
	if (last_task_used_math) {
		__asm__("fnsave %0"::"m" (last_task_used_math->tss.i387));
	}
//现在,last_task_used_math指向当前任务,以备当前任务被交换出去时使用。此时如果当前任务
//用过协处理器,则恢复其状态。否则的话说明是第一次使用,于是就向协处理器发初始化命令,
//并设置使用了协处理器标志。
	last_task_used_math=current;
	if (current->used_math) {
		__asm__("frstor %0"::"m" (current->tss.i387));
	} else {
		__asm__("fninit"::);//向协处理器发初始化命令。
		current->used_math=1;//设置使用协处理器标志。
	}
}

/*
 *  'schedule()' 是调度函数。这是个很好的代码!没有任何理由对它进行修噶,因为它可以在所有的环境下工作
 (比如能够对IO-边界处理韩浩的响应等)。只有一件事值得留意,那就是这里的信号处理代码。
 注意!!任务0是个闲置('idle')任务,只有当没有其他任务可以运行时才调用他。
 它不能被杀死,也不能睡眠。任务0中的状态信息'state'是从来不用的。
 */
void schedule(void)
{
	int i,next,c;
	struct task_struct ** p;//任务结构指针的指针。

/* 检测alarm(进程的报警定时值),唤醒任务号已得到信号的可中断任务 */

//从任务数组中最后一个任务喀什循环检测alarm。在循环时跳过空指针项。
	for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
		if (*p) {
		//如果设置过任务超时定时timeout,并且已经超时,则复位超时定时值,并且如果任务处于可中断睡眠
		//状态TASK_INTERRUPTIBLE下,将其置为就绪状态(TASK_RUNNING)。
			if ((*p)->timeout && (*p)->timeout < jiffies) {
				(*p)->timeout = 0;
				if ((*p)->state == TASK_INTERRUPTIBLE)
					(*p)->state = TASK_RUNNING;
			}
		//如果设置过任务的定时值alarm,并且已经过期(alarm<jiffies),则在信号位图中置SIGALRM信号,
		//即向任务发送SIGALARM信号。然后清alarm。该信号的默认操作是终止进程。jiffies是
		//系统从开机开始算起的滴答数(10ms/滴答)。定义在sched.h第139行。
			if ((*p)->alarm && (*p)->alarm < jiffies) {
				(*p)->signal |= (1<<(SIGALRM-1));
				(*p)->alarm = 0;
			}
		//如果信号位图中除被阻塞的信号外还有其他信号,并且任务处于可中断状态,则置任务为就绪状态。
		//其中'~(_BLOCKABLE & (*p)->blocked))'用于忽略被阻塞的信号,但SIGKILL和SIGSTOP
		//不能被阻塞。
			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];
		//这段代码也是从任务数组的最后一个任务开始循环处理,并跳过不含任务的数字槽。比较每个就绪状态
		//任务的counter(任务运行时间的递减滴答计数)值,那一个值大,运行时间还不长,next就指向
		//那个的任务号。
		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;
	}
	//next初始值为0。如果没有任何任务,调度函数会在系统空闲时区执行任务0.
	//此时任务0仅执行sys_pause()系统调用,并又会调用本函数schedule。
	switch_to(next);
}
//pause()系统调用。转换当前任务的状态为可中断的等待状态,并重新调度。
//该系统调用将导致进程进入睡眠状态,直到收到一个信号。该信号用于终止进程或者使用进程调用一个
//信号捕获函数。只有当捕获了一个信号,并且信号捕获处理函数返回,pause()才会返回。此时
//pause()返回值应该是-1,并且errno被置为EINTR。这里还没有实现(知道0.95版)。
int sys_pause(void)
{
	current->state = TASK_INTERRUPTIBLE;
	schedule();
	return 0;
}
//把当前任务置为指定的睡眠状态(可中断的或不可中断的),并让睡眠队列头指针指向当前任务。
//参数state是任务睡眠使用的状态;TASK_UNINTERRUPTIBLE或TASK_INTERRUPTIBLE。
//处于不可中断睡眠状态(TASK_UNINTERRUPTIBLE)的任务需要内核程序利用wake_up()函数明确唤醒之。
//处于可中断睡眠状态(TASK_INTERRUPTIBLE)的任务可以通过信号、任务超时等手段唤醒(置为就绪状态)。
static inline void __sleep_on(struct task_struct **p, int state)
{
	struct task_struct *tmp;
//若指针无效,则退出。(指针所指的对象可以是NULL,但指针本身不会为0)。
//如果当前任务是任务0,则死机(impossible!)。
	if (!p)
		return;
	if (current == &(init_task.task))
		panic("task[0] trying to sleep");
//让tmp指向已经在等待队列上的任务(如果有的话),例如inode->i_wait。并且将睡眠队列头的
//等待指针指向当前任务。这样就把当前任务插入到了*p的等待队列中。然后讲当前任务置为指定的
//等待状态,并执行重新调度。
	tmp = *p;
	*p = current;
	current->state = state;
repeat:	schedule();
//只有当这个等待任务被唤醒时,程序才又会返回到这里,表示进程已被明确地唤醒并执行。
//如果等待队列中海油等待任务,并且队列头指针*p所指向的任务不是当前任务时,说明在
//本任务插入等待队列后还有任务进入等待队里。于是我们应该也要唤醒这个任务,而我们
//自己应按顺序让这些后面进入队列的任务唤醒,因此这里将等待队列头所指任务先置为
//就绪状态,而自己则置为不可中断等待状态,即自己要等待这些后续队列的任务被唤醒
//而执行时来唤醒本任务。然后重新执行调度程序。
	if (*p && *p != current) {
		(**p).state = 0;
		current->state = TASK_UNINTERRUPTIBLE;
		goto repeat;
	}
	//之心到这里,说明本任务真正被唤醒执行。此时等待队里头指针应该指向本任务,若它为空,则表明
	//调度有问题,于是显示警告信息。最后我们让头指针指向我们前面进入队列的任务(*p=tmp).
	//若确实存在这样一个任务,即队列中还有任务(tmp不为空),就唤醒之。最先进入队列的任务在唤醒
	//后运行时最终会把等待队列头指针置为NULL。
	if (!*p)
		printk("Warning: *P = NULL\n\r");
	if (*p = tmp)//1、 有任务 tmp!=0 唤醒下一任务,这样会唤醒链表的所有任务;2、没有任务 tmp==0,将链表头赋值NULL
		tmp->state=0;
}
//将当前任务置为可中断的等待状态(TASK_INTERRUPTIBLE),并放入头指针*p指定的等待队里中。
void interruptible_sleep_on(struct task_struct **p)
{
	__sleep_on(p,TASK_INTERRUPTIBLE);
}
//把当前任务置为不可中断的等待状态(TASK_UNINTERRUPTIBLE),并让睡眠队列头指针指向当前任务。
//只有明确地唤醒时才会返回。该函数提供了进程与中断处理程序之间的同步机制。
void sleep_on(struct task_struct **p)
{
	__sleep_on(p,TASK_UNINTERRUPTIBLE);
}
//唤醒*p指向的任务。*p是任务等待队列头指针。由于新等待任务是插入在等待队列头指针出的,
//因此唤醒的是最后进入等待队列的任务。若该任务已经处于停止或僵死状态,则显示警告信息。
void wake_up(struct task_struct **p)
{
	if (p && *p) {
		if ((**p).state == TASK_STOPPED)//处于停止状态。
			printk("wake_up: TASK_STOPPED");
		if ((**p).state == TASK_ZOMBIE)//处于僵死状态。
			printk("wake_up: TASK_ZOMBIE");
		(**p).state=0;//置为就绪状态TASK_RUNNING。
	}
}

/*
好了,从这里开始是一些有关软盘的子程序,本不应该放在内核的主要部分中的。将它们放在这里是
因为软驱需要定时处理,而放在这里是最方便的。
 */
//下面220 -- 281行代码用于处理软驱定时器。在阅读这段代码之前请先看一下(块设备)一章中有关软盘
//驱动程序(floppy.c)后面的说明,或者到阅读软盘块设备驱动程序时再来看这段代码。
//其中时间单位:1个滴答 = 1/100秒。
//下面的数字wait_motor[]用于存放等待软驱马达启动到正常转速的进程指针。数组索引0-3分别
//对应软驱A--D。数组moff_timer[]存放各软驱在马达停转之前需要维持的时间。
//陈旭中默认启动时间为50个滴答(0.5秒)。数组moff_timer[]存放各软驱在马达停转之前需维持的时间。
//程序中设定为10000个滴答(100秒)。
//220行
static struct task_struct * wait_motor[4] = {NULL,NULL,NULL,NULL};
static int  mon_timer[4]={0,0,0,0};
static int moff_timer[4]={0,0,0,0};
//下面变量对应软驱控制器中当前数字输出寄存器。该寄存器每位的定义如下:
//bit7-4:	分别控制驱动器D-A马达的启动。 1-启动; 0-关闭
//bit3:		1-允许DMA和中断请求; 0-禁止DMA和中断请求。
//bit2:		1-启动软盘控制器; 0-复位软盘控制器。
//bit1-0:	00-11,用于选择控制器的软驱A-D。
//这里设置初值为:允许MDA和中断请求、启动DFC。
unsigned char current_DOR = 0x0C;

//指定软驱启动到正常运行状态所需等待时间。
//参数nr -- 软驱号(0--3),返回值为滴答数。
//局部变量selected是选中软驱标志(blk_drv/floppy.c,123行)。
//mask是所选软驱对应的数字输出寄存器中启动马达位。mask高4位是各软驱启动马达标志。
int ticks_to_floppy_on(unsigned int nr)
{
	extern unsigned char selected;
	unsigned char mask = 0x10 << nr;
//系统最多有4个软驱。首先预先设置好指定软驱nr停转之前需要经过的时间(100秒)。
//然后取当前DOR寄存器值到临时变量Mask中,并把指定软驱的马达启动标志置位。
	if (nr>3)
		panic("floppy_on: nr>3");
	moff_timer[nr]=10000;		/* 100 s = very big :-) */		//停转维持时间。
	cli();				/* use floppy_off to turn it off */		//	关中断。
	mask |= current_DOR;
	//如果当前没有选择软驱,则首先复位其他软驱的选择位,然后指定软驱选择位。
	if (!selected) {
		mask &= 0xFC;
		mask |= nr;
	}
	//如果数字输出寄存器的当前值与要求的值不同,并且如果
	//要求启动的马达还没有启动,则置相应软驱的马达启动定时器值(HZ/2 = 0.5秒或50个滴答)。
	//若已经启动,则再设置启动定时器为2个滴答,能满足下面do_floppy_timer()中先递减后判断的
	//要求。执行本次定时代码的要求即可。此后更新当前数字输出寄存器current_DOR。
	if (mask != current_DOR) {
		outb(mask,FD_DOR);
		if ((mask ^ current_DOR) & 0xf0)
			mon_timer[nr] = HZ/2;
		else if (mon_timer[nr] < 2)
			mon_timer[nr] = 2;
		current_DOR = mask;
	}
	sti();//开中断。
	return mon_timer[nr];//最后返回启动马达所需的时间值。
}
//等待指定软驱马达启动所需的一段时间,然后返回。
//设置指定软驱的马达启动到正常转速所需的延时,然后睡眠等待。在定时中断过程中会一直递减判断
//这里设定的延时值。当延时到期,就会唤醒这里的等待进程。
void floppy_on(unsigned int nr)
{
//关中断。如果马达启动定时还没到,
//就一直把当前进程置为不可中断睡眠状态并放入等待马达运行的队列中。然后开中断。
	cli();
	while (ticks_to_floppy_on(nr))
		sleep_on(nr+wait_motor);
	sti();
}
//置关闭相应软驱马达停转定时器(3秒)。
//若不适用该函数明确关闭知道的软驱马达,则在马达开启100秒之后也会被关闭。
void floppy_off(unsigned int nr)
{
	moff_timer[nr]=3*HZ;
}
//软盘定时处理子程序。更新马达启动定时值和马达关闭停转计时值。该子程序会在时钟定时中断过程
//中被调用,因此系统每经过一个滴答(10ms)就会被调用一次,随时更新马达开启或停转定时器的值。
//如果某一个马达停转定时到,则将数字输出寄存器马达启动位复位。
void do_floppy_timer(void)
{
	int i;
	unsigned char mask = 0x10;

	for (i=0 ; i<4 ; i++,mask <<= 1) {
		if (!(mask & current_DOR))//如果不是DOR指定的马达则跳过。
			continue;
		if (mon_timer[i]) {		//如果马达启动定时到则唤醒进程。
			if (!--mon_timer[i])
				wake_up(i+wait_motor);
		} else if (!moff_timer[i]) {//如果马达停转定时到则复位相应马达启动位,
			current_DOR &= ~mask;//并且更新数字输出寄存器。
			outb(current_DOR,FD_DOR);
		} else
			moff_timer[i]--;//否则马达停转计时递减。
	}
}
//281行

//下面是关于定时器的代码。最多可有64个定时器。
#define TIME_REQUESTS 64

//定时器链表结果和定时器数组。该定时器链表专用于供软驱关闭马达和启动马达定时操作。
//这种类型定时器类似现在Linux系统中的动态定时器(Dynamic Timer),仅供内核使用。
static struct timer_list {
	long jiffies;									//定时滴答数。
	void (*fn)();									//定时处理程序。
	struct timer_list * next;						//链接指向下一个定时器。
} timer_list[TIME_REQUESTS], * next_timer = NULL;	//next_timer是定时器队列头指针。

//添加定时器。输入参数为知道的定时值(滴答数)和相应的处理程序指针。
//软盘驱动程序(floppy.c)利用该函数执行启动或关闭马达的延时操作。
//参数jiffies - 以10毫秒计的滴答数;*fn() - 定时时间到后要执行的函数。
void add_timer(long jiffies, void (*fn)(void))
{
	struct timer_list * p;
//如果定时处理程序指针为空,则退出。否则关中断。
	if (!fn)
		return;
	cli();
	if (jiffies <= 0)//如果定时值<=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;
		//赵老师说:这段程序好像没有考虑周全。如果新插入的定时器值小于原来头一个定时器值时则根本不会进入循环中。
		//解决办法:即如果第1个定时值<=第2个,则第2个定时值扣除第1个的值即可,否则进入下面循环中进行处理。
		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函数处理程序,在sys_call.s中_timer_interrupt(189行)被调用。
//参数cpl是当前特权级0或3,是时钟中断发生时正被执行的代码选择符中的特权级。
//cpl=0时表示中断发生时正在执行内核代码;cpl=3时表示中断发生时正在执行用户代码。
//对于一个进程由于执行时间片用完时,则进行任务切换。并执行一个计时更新工。
void do_timer(long cpl)
{
	static int blanked = 0;
//首先判断是否经过了一定时间而让屏幕黑屏(blankout)。如果blankcount计数不为零,或者黑屏
//延时间隔时间blankinterval为0的话,那么若已经处于黑屏状态(黑屏标志blanked=1),
//则让屏幕恢复显示。若blankcount计数不为零,则递减之,并复位黑屏标志。
	if (blankcount || !blankinterval) {
		if (blanked)
			unblank_screen();
		if (blankcount)
			blankcount--;
		blanked = 0;
	//否则的话若黑屏标志未置为,则让屏幕黑屏,并且设置黑屏标志。
	} else if (!blanked) {
		blank_screen();
		blanked = 1;
	}
	//接着处理硬盘操作超时问题。如果硬盘超时计数递减之后为0,则进行硬盘访问超时处理。
	if (hd_timeout)
		if (!--hd_timeout)
			hd_times_out();//硬盘访问超时处理(blk_drv/hdc,318行)。

	//如果发声计数次数到,则关闭发声。(向0x61口发送命令,复位位0和1。
	//位0控制8253计数器2的工作,位1控制扬声器。)
	if (beepcount)//扬声器发声时间滴答数(char_drv/console.c,950行)。
		if (!--beepcount)
			sysbeepstop();

	//如果当前特权级(cpl)为0(最高,表示是内核程序在工作),则将内核代码运行时间stmie递增;
	//[Linus把内核程序统称为超级用户(supervisor)的程序,见sys_call.s,207行上的英文注释。
	//这种称呼来自Intel CPU手册。]如果cpl>0,则表示是一般用户程序在工作,增加utime。
	if (cpl)
		current->utime++;
	else
		current->stime++;
//如果有定时器存在,则将链表第1个定时器的值减1。如果已等于0,则调用相应的处理程序,并将
//该处理程序指针置为空。然后去掉该项定时器。next_timer是定时器链表的头指针。
	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)();
		}
	}
	//如果当前软盘控制器FDC的数字输出寄存器中马达启动位有置位的,则执行软盘定时程序。
	if (current_DOR & 0xf0)
		do_floppy_timer();
	//如果进程运行时间还没完,则退出。否则置当前任务运行计数值为0。并且若发生时钟中断时正在内核
	//代码中运行则返回,否则调用执行调度函数。
	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);
}
//去当前进程号pid。
int sys_getpid(void)
{
	return current->pid;
}
//取父进程号ppid
int sys_getppid(void)
{
	return current->p_pptr->pid;
}
//取用户号uid。
int sys_getuid(void)
{
	return current->uid;
}
//取有效的用户号euid。
int sys_geteuid(void)
{
	return current->euid;
}
//取组号gid
int sys_getgid(void)
{
	return current->gid;
}
//去有效的组号egid
int sys_getegid(void)
{
	return current->egid;
}
//系统调用功能 -- 降低对CPU的使用优先权(有人会用吗?(-: )。
//应该限制increment为大于0的值,否则可使优先权增大!!!
int sys_nice(long increment)
{
	if (current->priority-increment>0)
		current->priority -= increment;
	return 0;
}
//内核调度程序的初始化子程序。
void sched_init(void)
{
	int i;
	struct desc_struct * p;

	//为了提醒修改内核代码的人。
	if (sizeof(struct sigaction) != 16)
		panic("Struct sigaction MUST be 16 bytes");
	//在全局描述符表中设置初始任务(任务0)的任务状态段描述符和局部数据表描述符。		
	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;
	//清任务数组和描述符表项(注意i=1开始,所以初始任务的描述符还在)。
	for(i=1;i<NR_TASKS;i++) {
		task[i] = NULL;
		p->a=p->b=0;
		p++;
		p->a=p->b=0;
		p++;
	}
	/* 清除标志寄存器中的位NT,这样以后就不会有麻烦 */
	//EFLAGS中的NT标志位用于控制任务的嵌套调用。当NT位置位时,
	//那么当前中断任务执行IRET指令是就会引起任务切换。NT指出TSS中的back_link字段是否有效。NT=0时无效。
	__asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl");//复位NT标志。
	
	//将任务0的TSS段选择符加载到任务寄存器tr。将局部描述符表段选择符加载到局部描述符表寄存器ldtr中。
	//注意!!是将GDT中相应LDT描述符的选择符加载到ldtr。只明确加这一次,
	//以后新任务LDT的加载,是CPU根据TSS中的LDT项自动加载。
	ltr(0);
	lldt(0);
	//下面代码用于初始化8253定时器。通道0,选择工作方式3,二进制计数方式。通道0的
	//输出引脚接在中断控制主芯片的IRQ0上,它每10毫秒发出一个IRQ0请求。
	//LATCH是初始定时计数值。
	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);
}

8-6-3 其他信息

1、 软盘驱动器控制器
先看floppy.c再来理解。

2、可编程定时/技术控制器
linux0.12只是用通道0,
工作方式3,
计数初始值采用二进制,
计数初始值为#define LATCH (1193180/HZ)
即让计数器0每间隔10Ms发出一个方波上升沿信号以产生中断请求信号(IRQ0)。文章来源地址https://www.toymoban.com/news/detail-438556.html

到了这里,关于linux0.12-8-6-sched.c的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux0.12内核源码解读(2)-Bootsect.S

    大家好,我是呼噜噜,在上一篇文章聊聊x86计算机启动发生的事?我们了解了x86计算机启动过程,MBR、0x7c00是什么?其中当bios引导结束后,操作系统接过计算机的控制权后,发生了哪些事?本文将揭开迷雾的序章- Bootsect.S 我们先来回顾一下,上古时期计算机按下电源键的启

    2024年04月12日
    浏览(30)
  • linux0.12-10-5-rs_io.s

    [534页] 该汇编程序实现rs232串行通信中断处理过程。在进行字符的传输和存储过程中,该中断过程主要对终端的读、写缓冲队列进行操作。它把从串行线路上接收到的字符存入串行终端的读缓冲队列read_q中,或把写缓冲队列write_q中需要发送出去的字符通过串行线路发送给远端

    2024年02月06日
    浏览(30)
  • Linux INFO: rcu_sched self-detected stall on CPU

    如果串口持续打印下面的信息,说明代码中出现了异常,程序一直占据了cpu不释放。cpu在调度中检测到了这种异常,在串口中打印出内核异常位置的调用栈。 这种检查内核缺省是打开的,CONFIG_RCU_CPU_STALL_TIMEOUT 参数是时间,如果cpu占据时间超过该参数,则会打印。在我调试的

    2024年02月13日
    浏览(32)
  • Linux0.11内核源码解析-exec.c

    主要实现对二进制可执行文件和shell文件的加载和执行,其中主要的函数是do_execve(),它是系统中断调用int 0x80的功能号__NR_execve()调用,是exec()函数的主要实现以下几点功能: 1.执行对参数和环境参数空间页面的初始化操作,初始化空间页面指针数组,根据执行文件名取执行对

    2024年02月06日
    浏览(27)
  • Linux0.11内核源码解析-truncate.c

    truncate文件只要实现释放指定i节点在设备上占用的所有逻辑块,包括直接块、一次间接块、二次间接块。从而将文件节点对应的文件长度截为0,并释放占用的设备空间。  

    2024年02月12日
    浏览(33)
  • 【项目分析】仿linux0.11的操作系统内核

    系列综述: 💞目的:本系列是个人整理为了 秋招面试 的,整理期间苛求每个知识点,平衡理解简易度与深入程度。 🥰来源:材料主要源于 《操作系统 真象还原》及各大佬博客 进行的,每个知识点的修正和深入主要参考各平台大佬的文章,其中也可能含有少量的个人实验

    2024年02月09日
    浏览(31)
  • 《微信小程序案例12》图片识别功能

    该功能是直接使用百度AI开发平台的动物识别接口,这个接口有两个重要的参数,一是需要获取access_token、二是需要把上传的图片编码为base64。而获取access_token有需要使用另一个接口来获取,获取到后我使用缓存技术把这个acces_token保存起来,并设置一个有效时间。      新用

    2024年02月11日
    浏览(26)
  • 【Linux0.11代码分析】04 之 head.s 启动流程

    系列文章如下: 系列文章汇总:《【Linux0.11代码分析】之 系列文章链接汇总(全)》 . 1.《【Linux0.11代码分析】01 之 代码目录分析》 2.《【Linux0.11代码分析】02 之 bootsect.s 启动流程》 3.《【Linux0.11代码分析】03 之 setup.s 启动流程》 4.《【Linux0.11代码分析】04 之 head.s 启动流程

    2024年02月03日
    浏览(30)
  • Linux0.11内核源码解析-read_write.c

    目录  sys_lseek read write read_write.c主要是实现文件系统调用read(),write()和lseek()三个功能 read和write函数分别是调用file_dev.c/pipe.c/block_dev.c/char_dev.c实现相对应的函数 lseek实现系统调用将对文件句柄对应文件结果体中的当前读写指针进行修改,对于读写指针不能移动的文件和管道文

    2024年02月13日
    浏览(33)
  • 【Linux0.11代码分析】07 之 kernel execve() 函数 实现原理

    系列文章如下: 系列文章汇总:《【Linux0.11代码分析】之 系列文章链接汇总(全)》 https://blog.csdn.net/Ciellee/article/details/130510069 . 1.《【Linux0.11代码分析】01 之 代码目录分析》 2.《【Linux0.11代码分析】02 之 bootsect.s 启动流程》 3.《

    2024年02月03日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包