紧接着head.s,之后进入c语言环境的main()函数
如下:
void main(void) /* This really IS void, no error here. */
{ /* The startup routine assumes (well, ...) this */
/*
* Interrupts are still disabled. Do necessary setups, then
* enable them
*/
ROOT_DEV = ORIG_ROOT_DEV;
drive_info = DRIVE_INFO;
memory_end = (1<<20) + (EXT_MEM_K<<10);
memory_end &= 0xfffff000;
if (memory_end > 16*1024*1024)
memory_end = 16*1024*1024;
if (memory_end > 12*1024*1024)
buffer_memory_end = 4*1024*1024;
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
main_memory_start += rd_init(main_memory_start, RAMDISK*1024); //将main_memory_start开始的RAMDISK×1024个字节=0
#endif
mem_init(main_memory_start,memory_end); //内存初始化
trap_init(); //idt中断表初始化
blk_dev_init();//块设备初始化
chr_dev_init();//字符设备初始化,为空
tty_init();//tty设备初始化
time_init();//时间初始化
sched_init();//调度程序初始化
buffer_init(buffer_memory_end);//缓冲区初始化,创建管理缓冲区的双向链表,此处缓冲区大小为3M
hd_init();//硬盘以及硬盘中断初始化
floppy_init();//软盘以及软盘中断初始化
sti();//打开中断
move_to_user_mode();//指令实现从内核模式切换到用户模式(任务0)
//以下代码在用户模式(任务0)中执行
if (!fork()) { /* we count on this going ok *///触发系统中断0x80,调用system_call中断函数
//fork返回值为0的进程(子进程)执行init();
init();
}
/*
* NOTE!! For any other task 'pause()' would mean we have to get a
* signal to awaken, but task0 is the sole exception (see 'schedule()')
* as task 0 gets activated at every idle moment (when no other tasks
* can run). For task0 'pause()' just means we go check if some other
* task can run, and if not we return here.
*/
for(;;) pause(); //任务0进入pause
}
main函数做了以下事情,本文只描述至move_to_user_mode函数之前,也就是硬件初始化这一部分,move_to_user_mode()以及fork请看下一篇博客。
1.获取之前的BOIS调用的得到的,放在内存中指定位置的,根目录对应的硬盘的设备名/dev/hd1,以及此设备设备信息。
2.根据内存大小,选择缓冲区buffer大小。这里默认内存为16MB,选择buffer大小为4MB-1MB=3MB。
3.内存初始化,对内存标志数组mem_map[]进行赋值,这里的默认值为:mem_map[0~768]=USED=100,也即是1MB~4MB的区域的标志为已使用,因为buffer缓冲区使用了,mem_map[768~3840]=0,也即是4MB到16MB区域标志为未使用,注意1MB以下的区域属于内核使用, 不参与计算,所以mem_map[0]是从1MB开始记录的。
void mem_init(long start_mem, long end_mem)
{
int i;
HIGH_MEMORY = end_mem;
for (i=0 ; i>= 12;
while (end_mem-->0) //4M~16M 的值为0 mem_map[768]~[3840] = 0
mem_map[i++]=0;
}
4.IDT表内容填充,IDT是中断描述表,当中断产生时,CPU根据IDTR寄存器找到此表的地址,然后根据中断号选择,此表中的中断处理函数,然后跳转到相应的中断处理函数去执行,最后返回。
void trap_init(void)//设置idt内容
{
int i;
set_trap_gate(0,÷_error); //中断号 ,中断处理函数地址
set_trap_gate(1,&debug);
set_trap_gate(2,&nmi);
set_system_gate(3,&int3); /* int3-5 can be called from all */
set_system_gate(4,&overflow);
set_system_gate(5,&bounds);
set_trap_gate(6,&invalid_op);
set_trap_gate(7,&device_not_available);
set_trap_gate(8,&double_fault);
set_trap_gate(9,&coprocessor_segment_overrun);
set_trap_gate(10,&invalid_TSS);
set_trap_gate(11,&segment_not_present);
set_trap_gate(12,&stack_segment);
set_trap_gate(13,&general_protection);
set_trap_gate(14,&page_fault);
set_trap_gate(15,&reserved);
set_trap_gate(16,&coprocessor_error);
// 下面将int17-48 的陷阱门先均设置为reserved,以后每个硬件初始化时会重新设置自己的陷阱门。
for (i=17;i<48;i++)
set_trap_gate(i,&reserved);
set_trap_gate(45,&irq13);// 设置协处理器的陷阱门。
outb_p(inb_p(0x21)&0xfb,0x21);// 允许主8259A 芯片的IRQ2 中断请求。
outb(inb_p(0xA1)&0xdf,0xA1);// 允许从8259A 芯片的IRQ13 中断请求。
set_trap_gate(39,¶llel_interrupt);// 设置并行口的陷阱门。
}
此处填充了,IDT表的前48项内容,都属于应对异常处理的中断处理函数,最后打开8259A(可编程控制的)相应中断允许位,允许中断源产生中断。
5.块设备初始化,只是初始化了块设备的请求数组,置为默认值。
// 初始化请求数组,将所有请求项置为空闲项(dev = -1)。有32 项(NR_REQUEST = 32)。
void blk_dev_init (void)
{
int i;
for (i = 0; i < NR_REQUEST; i++)
{
request[i].dev = -1;
request[i].next = NULL;
}
}
6.字符设备初始化,为空为以后扩展做准备。
void chr_dev_init (void)
{
}
7.tty终端设备初始化,使我们能够使用tty终端与linux系统进行交互。
void tty_init(void)
{
rs_init();//串口硬件,中断初始化
con_init();//初始化控制台终端, 包括键盘,显示器的初始化
}
rs_init(),串口初始化,设置IDT里对应的tty中断处理函数,初始化串口1和串口2硬件,最后打开串口1和串口2的中断源。
void rs_init(void)
{
set_intr_gate(0x24,rs1_interrupt); //设置串口1的中断函数和中断号,IRQ4
set_intr_gate(0x23,rs2_interrupt); //设置串口2的中断函数和中断号,IRQ3
init(tty_table[1].read_q.data); // 初始化串口1
init(tty_table[2].read_q.data); // 初始化串口2
outb(inb_p(0x21)&0xE7,0x21); //打开串口1和串口2的中断源IRQ4,IRQ3
}
init()初始化函数,初始化串口的硬件,配置一些串口相关的参数。
初始化串行端口
// port: 串口1 - 0x3F8,串口2 - 0x2F8。
static void init (int port)
{
outb_p (0x80, port + 3); /* set DLAB of line control reg */
/* 设置线路控制寄存器的DLAB 位(位7) */
outb_p (0x30, port); /* LS of divisor (48 -> 2400 bps */
/* 发送波特率因子低字节,0x30->2400bps */
outb_p (0x00, port + 1); /* MS of divisor */
/* 发送波特率因子高字节,0x00 */
outb_p (0x03, port + 3); /* reset DLAB */
/* 复位DLAB 位,数据位为8 位 */
outb_p (0x0b, port + 4); /* set DTR,RTS, OUT_2 */
/* 设置DTR,RTS,辅助用户输出2 */
outb_p (0x0d, port + 1); /* enable all intrs but writes */
/* 除了写(写保持空)以外,允许所有中断源中断 */
(void) inb (port); /* read data port to reset things (?) */
/* 读数据口,以进行复位操作(?) */
}
con_init(),主要初始化显示器,获取显示器参数,判断显示模式,获取显示缓冲区地址,打印显示模式到屏幕的右上角,最后设置键盘的中断处理函数,打开键盘的中断源。
*/
void con_init(void)
{
register unsigned char a;
char *display_desc = "????";
char *display_ptr;
video_num_columns = ORIG_VIDEO_COLS; //获取boot阶段的显示器参数
video_size_row = video_num_columns * 2;
video_num_lines = ORIG_VIDEO_LINES;
video_page = ORIG_VIDEO_PAGE;
video_erase_char = 0x0720;
if (ORIG_VIDEO_MODE == 7) /* Is this a monochrome display? */ //判断显示器模式
{
video_mem_start = 0xb0000;
video_port_reg = 0x3b4;
video_port_val = 0x3b5;
if ((ORIG_VIDEO_EGA_BX & 0xff) != 0x10)
{
video_type = VIDEO_TYPE_EGAM;
video_mem_end = 0xb8000;
display_desc = "EGAm";
}
else
{
video_type = VIDEO_TYPE_MDA;
video_mem_end = 0xb2000;
display_desc = "*MDA";
}
}
else /* If not, it is color. */
{
video_mem_start = 0xb8000;
video_port_reg = 0x3d4;
video_port_val = 0x3d5;
if ((ORIG_VIDEO_EGA_BX & 0xff) != 0x10)
{
video_type = VIDEO_TYPE_EGAC;
video_mem_end = 0xbc000;
display_desc = "EGAc";
}
else
{
video_type = VIDEO_TYPE_CGA;
video_mem_end = 0xba000;
display_desc = "*CGA";
}
}
/* Let the user known what kind of display driver we are using */
display_ptr = ((char *)video_mem_start) + video_size_row - 8;//将判断显示器模式的结果打印在屏幕右上角
while (*display_desc)
{
*display_ptr++ = *display_desc++;
display_ptr++;
}
/* Initialize the variables used for scrolling (mostly EGA/VGA) */
origin = video_mem_start;
scr_end = video_mem_start + video_num_lines * video_size_row;
top = 0;
bottom = video_num_lines;
gotoxy(ORIG_X,ORIG_Y);
set_trap_gate(0x21,&keyboard_interrupt);//初始化键盘中断函数
outb_p(inb_p(0x21)&0xfd,0x21);//打开键盘中断源IRQ1
a=inb_p(0x61);//复位键盘
outb_p(a|0x80,0x61);
outb(a,0x61);
}
所以,tty_init(),主要初始化了3种硬件,串口,显示器,以及键盘,这是tty所使用的三种硬件,缺一不可。
8.系统时间初始化,主要读取CMOS时钟信息,并最终换算成从1970年1-1-0开始的秒数,赋值给startup_time。
static void time_init(void)
{
struct tm time; //读取CMOS的时钟,并换算成从1970-1-1-0 起到开始时的秒数
do {
time.tm_sec = CMOS_READ(0);
time.tm_min = CMOS_READ(2);
time.tm_hour = CMOS_READ(4);
time.tm_mday = CMOS_READ(7);
time.tm_mon = CMOS_READ(8);
time.tm_year = CMOS_READ(9);
} while (time.tm_sec != CMOS_READ(0));
BCD_TO_BIN(time.tm_sec);
BCD_TO_BIN(time.tm_min);
BCD_TO_BIN(time.tm_hour);
BCD_TO_BIN(time.tm_mday);
BCD_TO_BIN(time.tm_mon);
BCD_TO_BIN(time.tm_year);
time.tm_mon--;
startup_time = kernel_mktime(&time);
}
9.调度程序初始化,调度程序与CPU的TSS,LDT调用法息息相关,所以,首先设置了在GDT中的TSS,LDT,也就是task[0]的TSS,LDT,以供CPU任务切换时使用。然后,将后面的也就是GDT的第六个位置开始的63个(因为此内核默认只能有64个任务同时存在,除去task[0]以外63个)TSS,LDT暂时清零,后面这些任务被创建的时候再去初始化。
然后通过ltr汇编指令,将task[0]的TSS装载到TR寄存器。
然后通过lldt汇编指令,将task[0]的LDT装载到LDTR寄存器。
最后初始化定时器,设置定时器时钟中断处理函数,具体的任务切换函数就在此函数中被调用,打开定时器中断,最最后设置0x80,也即系统调用(通过软中断汇编指令swi实现调用)中断的处理函数。
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)); //将tss调用放在gdt的第四个位置
set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt)); //将ldt调用放在gdt的第五个位置
//清任务数组和描述符表项(注意i=1 开始,所以初始任务的描述符还在)。
p = gdt+2+FIRST_TSS_ENTRY;//将gdt后面的位置的内容清零。
for(i=1;ia=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");//标志寄存器32位内容压入堆栈,堆栈栈顶的内容&0xFFFFBFFF,出堆栈
ltr(0);//LTR指令是专门用于装载任务状态段寄存器TR的指令。该指令的操作数是对应TSS段描述符的选择子。LTR指令从GDT中取出相应的TSS段描述符,
lldt(0);//加载ldt到ldtr寄存器
//初始化8253 定时器。
outb_p(0x36,0x43); /* binary, mode 3, LSB/MSB, ch 0 */
outb_p(LATCH & 0xff , 0x40); /* LSB */
outb(LATCH >> 8 , 0x40); /* MSB */
//设置时钟中断处理程序,位于idt表的第0x20位置。
set_intr_gate(0x20,&timer_interrupt);
//允许时钟中断。
outb(inb_p(0x21)&~0x01,0x21);
//设置中断处理函数sysytem_call,位于idt表的第0x80位置
set_system_gate(0x80,&system_call);
}
10.buffer缓冲区初始化,创建管理缓冲区的双向链表,此处缓冲区大小为3M (地址为:1MB~4MB)
void buffer_init(long buffer_end)
{
struct buffer_head * h = start_buffer;
void * b;
int i;
if (buffer_end == 1<<20)
b = (void *) (640*1024);
else
b = (void *) buffer_end;
while ( (b -= BLOCK_SIZE) >= ((void *) (h+1)) ) {
h->b_dev = 0;
h->b_dirt = 0;
h->b_count = 0;
h->b_lock = 0;
h->b_uptodate = 0;
h->b_wait = NULL;
h->b_next = NULL;
h->b_prev = NULL;
h->b_data = (char *) b;
h->b_prev_free = h-1;
h->b_next_free = h+1;
h++;
NR_BUFFERS++;
if (b == (void *) 0x100000)
b = (void *) 0xA0000;
}
h--;
free_list = start_buffer;
free_list->b_prev_free = h;
h->b_next_free = free_list;
for (i=0;i
11.硬盘以及硬盘中断初始化,设置请求回调函数为do_hd_request。
void hd_init(void)
{
blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;//设置 request回调函数
set_intr_gate(0x2E,&hd_interrupt);//设置硬盘中断处理函数
outb_p(inb_p(0x21)&0xfb,0x21);//允许硬盘中断源产生中断
outb(inb_p(0xA1)&0xbf,0xA1);
}
12.软驱初始化与硬盘初始化雷同,设置请求回调函数为do_fd_request。
void floppy_init(void)
{
blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;//设置软盘request函数
set_trap_gate(0x26,&floppy_interrupt);//设置软盘中断处理函数
outb(inb_p(0x21)&~0x40,0x21);//允许软盘产生中断
}
13.打开cpu中断允许位,允许所有之上被打开的中断源可以被CPU响应。
sti();//打开中断
后面的内容将在下一篇博客中介绍,主要有切换至user 模式以及fork调用。