Linux 0.11内核之旅(四) :main.c之硬件初始化

紧接着head.s,之后进入c语言环境的main()函数

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调用。

你可能感兴趣的:(Linux,0.11内核之旅,Linux,0.11,main,硬件初始化)