操作系统——main.c(1)sched_init()

版权声明:本文为博主jmh原创文章,未经博主允许不得转载。 https://blog.csdn.net/jmh1996/article/details/83066370

今天我们来看看内核main函数调用的sched_init()函数。
main函数

void main(void)	
{		
 	ROOT_DEV = ORIG_ROOT_DEV;
 	drive_info = DRIVE_INFO;
	memory_end = (1<<20) + (EXT_MEM_K<<10);
	memory_end &= 0xfffff000;//4K对齐
	if (memory_end > 16*1024*1024)
		memory_end = 16*1024*1024;//====>最大为16MB
	if (memory_end > 12*1024*1024) 
		buffer_memory_end = 4*1024*1024;//====>缓存区的末尾为4 Mb
	else if (memory_end > 6*1024*1024)
		buffer_memory_end = 2*1024*1024;
	else
		buffer_memory_end = 1*1024*1024;
	main_memory_start = buffer_memory_end;
#ifdef RAMDISK
	//RAMDISK是一个宏,给定这个盘的多少KB字节。
	main_memory_start += rd_init(main_memory_start, RAMDISK*1024);
#endif
	mem_init(main_memory_start,memory_end);
	trap_init();
	blk_dev_init();
	chr_dev_init();
	tty_init();
	time_init();
	sched_init();
	buffer_init(buffer_memory_end);
	hd_init();
	floppy_init();
	sti();
	move_to_user_mode();
	if (!fork()) {		/* we count on this going ok */
		init();
	}
	for(;;) pause();
}

今天主要来看sched_init(),这个所谓的调度初始化函数,用于初始化进程0的基本结构。
sched_init()函数实现在sched.c

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));//FIRST_TSS_ENTRY==4;这是因为gdt 0是NULL,1是内核代码段,2是内核数据段,3是NULL,见head.S里面的gdt_descript
	set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));//FIRST_LDT_ENTRY=FIRST_TSS_ENTRY+1  (==5)
	p = gdt+2+FIRST_TSS_ENTRY;//已经在原来的基础上使用了两项了;gdt定义:extern desc_table gdt;而desc_table又是struct {long a,b};8个字节
	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");//?andl?是什么作用
	ltr(0);//进入进程0
	lldt(0);//进入进程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);
}

首先是一个
struct desc_struct *p。
struct desc_struct的定义为:

typedef struct desc_struct {
	unsigned long a,b;
} desc_table[256];

其实,desc_struct就是保含2个4字节的,共8个字节的单元。而这个desc_struct其实也就是段描述符。a是段描述符的低32位,b是段描述符的高32位。
接下来是设置进程0的tss和ldt。

set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));
set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));

其中FIRST_TSS_ENTRY,FIRST_LDT_ENTRY是两个宏,它们的定义为:

#define FIRST_TSS_ENTRY 4
#define FIRST_LDT_ENTRY (FIRST_TSS_ENTRY+1)

为啥子FIRST_TSS_ENTRY的值定义为4呢? 这是因为所有进程的TSS和LDT都是保存在系统的GDT 全局描述符表里面的。GDT前四项分别是空描述符,内核代码段描述符,内核数据段描述符,以及空描述符。之所以内核数据段后面任然有一个空描述符,设计者主要是想将内核描述符与进程的TSS,LDT分开。这可以从head.S的最后gdt值得到的:

gdt:	
	.quad 0x0000000000000000	/* NULL descriptor */
	.quad 0x00c09a0000000fff	/* 16Mb */
	.quad 0x00c0920000000fff	/* 16Mb */
	.quad 0x0000000000000000	/* TEMPORARY - don't use */
	.fill 252,8,0			/* space for LDT's and TSS's etc */

(前后两项都为NULL描述符)

OK,我们再来看set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));的含义。
先看看参数的含义,再看set_tss_desc的定义。
我们看gdt的定义:
在head.h有为gdt和idt的C语言定义:

extern desc_table idt,gdt;

而desc_table 就是来自刚刚看到的

typedef struct desc_struct {
	unsigned long a,b;
} desc_table[256];

也就是说,dest_table 同时也是一个struct desc_struct *类型,因而idt,gdt也是这种类型。
gdt+FIRST_TSS_ENTRY,就是gdt+4 它其实是gdt的后4x8=32个字节的位置,这个位置恰好也就是这个描述符的首地址。
再看set_tss_desc宏定义:

#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_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")

我们知道,这是一个宏。
首先set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));
会被展开为_set_tssldt_desc(((char *) (gdt+FIRST_TSS_ENTRY)),((int)(&(init_task.task.tss))),"0x89"),也就是addr=&(init_task.task.tss),而n就是gdt+FIRST_TSS_ENTRY。同时将addr转换为int,而将n转换为char*
然后,这又是一个宏定义。
最后这个东西会展开成:

扫描二维码关注公众号,回复: 3923806 查看本文章
__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)) \
	)

先看这个内联汇编的输入部分:
第0个参数:"a" (addr),将addr也就是&(init_task.task.tss)给eax.
第1个参数:"m" (*(n)),一个内存数,将n的值也就是gdt+4这个数,作为一个内存地址,这个其实就是gdt中进程0的TSS描述符的首字节。第0-1个字节共16位应该放着这个段的限长。
第2个参数: "m" (*(n+2)),一个内存数,gdt中进程0的TSS描述符的第2个字节。第2-3字节放着这个基地址的低16位。
第3个参数:"m" (*(n+4)),进程0的tss段描述的第4个字节。这里面放的基地址的中8位。
第4个参数: "m" (*(n+5)),进程0的tss段描述符的第5个字节,里面放的类型
第5个参数:"m" (*(n+6)),进程0的tss段描述符的第6个字节,里面放的段限长,粒度啥的
最后一个参数:"m" (*(n+7)),进程0的tss段描述符的第7个字节,里面放的基地址的高8位。
所以这些参数其实就是构成了这个段描述符的各个部分。

"movw $104,%1\n\t"设置段限长为104个单位。具体多少得看G位。也就是倒数第二个字节的最高位。
"movw %%ax,%2\n\t" \将输入的&(init_task.task.tss)地址给基地址的低16位。
"rorl $16,%%eax\n\t" \,将eax右循环移动16位
"movb %%al,%3\n\t" \,al其中就是地址的中8位,第3个参数。
"movb $" type ",%4\n\t" \,编译后就是"movb $0x89,%4\n\t" \,而0x89=1000 1001。底4位1001是类型域,第5位S为0,说明这是一个系统描述符,而类型又是1001,说明这是一个32位的TSS系统描述符。第6-7位说明这个段的DPL为0,最高位存在位置1 。
"movb $0x00,%5\n\t" \,给第5个参数,这个参数是控制粒度啥的,通过这个最高位为0,可以看出这个段的粒度为1B。所以这个段的限长为104个字节。其实TSS结构的长度就是104字节。
"movb %%ah,%6\n\t" \将基地址的最后的最高8位写到第6个参数。
"rorl $16,%%eax" \,eax再右循环移位就是让eax设置为0了。

经过上面的过程,将gdt里面的第一个TSS描述符就做好了,这个描述符的基地址指向init_task.tss的地址。
同理LDT0也是这样来够着的,只不过LDT0的类型和基地址不同而已了。


经过上面两条语句,我们将TSS0和LDT0就做好了。

set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));//FIRST_TSS_ENTRY==4;这是因为gdt 0是NULL,1是内核代码段,2是内核数据段,3是NULL,见head.S里面的gdt_descript
set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));//FIRST_LDT_ENTRY=FIRST_TSS_ENTRY+1  (==5)

接着:

p = gdt+2+FIRST_TSS_ENTRY;//已经在原来的基础上使用了两项了;gdt定义:extern desc_table gdt;而desc_table又是struct {long a,b};8个字节
	for(i=1;i<NR_TASKS;i++) {
		task[i] = NULL;
		p->a=p->b=0;
		p++;
		p->a=p->b=0;
		p++;
	}

将LDT0上面的所有描述符( T S S i , L D T i TSS_{i},LDT_{i} )都全部清NULL。
接着

	ltr(0);//进入进程0
	lldt(0);//进入进程0

将tss0和ldt0装入tr和ldtr寄存器。
其中ltr(0)的展开为:__asm__("ltr %%ax"::"a" (_TSS(0))),而_TSS(0)展开为:
((((unsigned long) n)<<4)+(FIRST_TSS_ENTRY<<3))
ltr指令只要传入tss在gdt中首字节就可以。
(FIRST_TSS_ENTRY<<3)是前4个内核段的描述符使用的共32个字节。
(((unsigned long) n)<<4),每个进程有TSS和LDT,每个进程会使用掉8x2=16个字节,也就是nx16+32就是第n个进程的TSS描述符的地址。
lldt(0)一样的分析。
最后,挂载时间中断服务程序和系统调用也就是0x80中断服务程序。

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

总结

sched_init()函数完成的工作:

  1. 在gdt表中设置进程0的TSS和LDT描述符,让他们的基地址指向合适的位置。
  2. 将gdt里面进程0的TSS载入tr寄存器,将gdt里面进程0的LDT载入ldtr寄存器
  3. 挂载0x20时间中断,0x80系统调用服务程序。

猜你喜欢

转载自blog.csdn.net/jmh1996/article/details/83066370
今日推荐