Linux-0.11内核分析02:设备环境初始化及激活进程0

设备环境初始化及激活进程0

1. 设置根设备、硬盘

/*init/main.c
 --------------------------------------------------------------- */
#define DRIVE_INFO (*(struct drive_info *)0x90080)
#define ORIG_ROOT_DEV (*(unsigned short *)0x901FC)
...
void main(void)     /* This really IS void, no error here. */
{
    ROOT_DEV = ORIG_ROOT_DEV;
    drive_info = DRIVE_INFO;
    ...
}

setup.s通过BIOS中断将一些机器数据保存到了内存0x90000以后的地址,以覆盖bootsect.s。这里取出了其中的硬盘参数和根设备号。

2. 设置缓冲区、虚拟盘、主内存在物理内存中的位置

/* init/main.c
 --------------------------------------------------------------- */
#define EXT_MEM_K (*(unsigned short *)0x90002)
...
void main(void)     /* This really IS void, no error here. */
{
    ...
    // 内存大小=1Mb + 扩展内存(k)*1024 byte
    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);
#endif
    ...
}

这里内存大小为16MB,缓冲区大小为4MB位于0~0x3FFFFF,虚拟盘为2MB位于0x400000~0x5FFFFF,主内存为10MB位于0x600000~0xFFFFFF

3. 初始化虚拟盘

/* init/main.c
 --------------------------------------------------------------- */
#define EXT_MEM_K (*(unsigned short *)0x90002)
...
void main(void)     /* This really IS void, no error here. */
{
    ...
    main_memory_start += rd_init(main_memory_start, RAMDISK*1024);
    ...
}

/* kernel/blk_drv/ll_rw_blk.c
 --------------------------------------------------------------- */
struct blk_dev_struct blk_dev[NR_BLK_DEV] = {
    { NULL, NULL },     /* no_dev */
    { NULL, NULL },     /* dev mem */
    { NULL, NULL },     /* dev fd */
    { NULL, NULL },     /* dev hd */
    { NULL, NULL },     /* dev ttyx */
    { NULL, NULL },     /* dev tty */
    { NULL, NULL }      /* dev lp */
};

/* kernel/ramdisk.c
 --------------------------------------------------------------- */
long rd_init(long mem_start, int length)
{
    int i;
    char    *cp;

    blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
    rd_start = (char *) mem_start;
    rd_length = length;
    cp = rd_start;
    /* 将虚拟磁盘区清零 */
    for (i=0; i < length; i++)
        *cp++ = '\0';
    return(length);
}

这里MAJOR_NR=1,表示内存设备,将对应的blk_dev[1].request_fn = DEVICE_REQUEST,其中DEVICE_REQUEST=do_rd_request。然后将虚拟磁盘区清零。

3. 内存管理结构mem_map初始化

系统通过mem_map[]对1MB以上的内存进行分页管理(一页为4KB),记录每一页的使用次数。初始化完成后0~1MB的页面被标记为USED1~16MB的页面被清零,表示没有使用

/* init/main.c
 --------------------------------------------------------------- */
#define EXT_MEM_K (*(unsigned short *)0x90002)
...
void main(void)     /* This really IS void, no error here. */
{
    ...
    mem_init(main_memory_start,memory_end);
    ...
}

/* kernel/blk_drv/ll_rw_blk.c
 --------------------------------------------------------------- */

#define LOW_MEM 0x100000                    // 内存低端(1MB)
#define PAGING_MEMORY (15*1024*1024)        // 分页内存15 MB,主内存区最多15M.
#define PAGING_PAGES (PAGING_MEMORY>>12)    // 分页后的物理内存页面数(3840)
#define MAP_NR(addr) (((addr)-LOW_MEM)>>12) // 指定地址映射为页号
#define USED 100                            // 页面被占用标志.
void mem_init(long start_mem, long end_mem)
{
    int i;
    // 1. 首先将16MB所有内存页面对应的内存映射字节数组项置为已占用状态
    HIGH_MEMORY = end_mem;                  // 设置内存最高端(16MB)
    for (i=0 ; i<PAGING_PAGES ; i++)
        mem_map[i] = USED;

    i = MAP_NR(start_mem);      // 主内存区其实位置处页面号
    end_mem -= start_mem;
    end_mem >>= 12;             // 主内存区中的总页面数
    // 2. 主内存区页面对应字节值清零
    while (end_mem-->0)
        mem_map[i++]=0;
    /* 3. 最后0~1MB的页面被标记为USED,1~16MB的页面被清零,表示没有使用 */
}

4. 异常处理类中断服务程序挂接

首先设置0~16号中断,然后将17~47号中断设置为保留

/* init/main.c
 --------------------------------------------------------------- */
...
void main(void)     /* This really IS void, no error here. */
{
    ...
    trap_init();
    ...
}

/* kernel/traps.c
 --------------------------------------------------------------- */
void trap_init(void)
{
    int i;

    set_trap_gate(0,&divide_error);
    set_trap_gate(1,&debug);
    ...
}

#define _set_gate(gate_addr,type,dpl,addr) \
__asm__ ("movw %%dx,%%ax\n\t" \ // eax=0x00080000 | divide_error低16位
    "movw %0,%%dx\n\t" \    // edx=divide_error高16位 | 0x8000+(dpl<<13)+(type<<8)
                            // edx:eax构成了中断描述符
    "movl %%eax,%1\n\t" \                   // eax给低4字节地址
    "movl %%edx,%2" \                       // edx给高4字节地址
    : \
    : "i" ((short) (0x8000+(dpl<<13)+(type<<8))), \     // 输入的第0个参数
    "o" (*((char *) (gate_addr))), \   // 第1个参数,中断描述符的低4字节地址
    "o" (*(4+(char *) (gate_addr))), \ // 第2个参数,中断描述符的高4字节地址
    "d" ((char *) (addr)),"a" (0x00080000)) // "d"对应edx,"a"对应eax

#define set_trap_gate(n,addr) \
    _set_gate(&idt[n],15,0,addr)

这里设置ISR的方法是通过&idt[n]找到对应的中断描述符,然后将ISR地址写入其中。

5. 初始化块设备请求项结构

初始化请求项管理结构request[32],全部设置为空闲,互不挂接。

/* init/main.c
 --------------------------------------------------------------- */
void main(void)     /* This really IS void, no error here. */
{
    ...
    blk_dev_init();
    ...
}

/* kernel/blk_dev/blk.h
 --------------------------------------------------------------- */
#define NR_REQUEST  32
struct request {
    int dev;        /* -1 if no request */
    int cmd;        /* READ or WRITE */
    int errors;
    unsigned long sector;
    unsigned long nr_sectors;
    char * buffer;
    struct task_struct * waiting;
    struct buffer_head * bh;
    struct request * next;
};

/* kernel/blk_dev/ll_rw_block.c
 --------------------------------------------------------------- */
void blk_dev_init(void)
{
    int i;

    for (i=0 ; i<NR_REQUEST ; i++) {
        request[i].dev = -1;
        request[i].next = NULL;
    }
}

进程想要与块设备进行沟通,必须经过主机内存中的缓冲区。请求项管理结构request[32]就是操作系统管理缓冲区中的缓冲块和块设备上逻辑块之间读写关系的数据结构。

6. 初始化终端设备

主要包括串口、显示器和键盘的初始化。

/* init/main.c
 --------------------------------------------------------------- */
void main(void)     /* This really IS void, no error here. */
{
    ...
    tty_init();
    ...
}

/* kernel/chr_dev/tty_io.c
 --------------------------------------------------------------- */
void tty_init(void)
{
    rs_init();          // kernel/chr_dev/serial.c
    con_init();         // kernel/chr_dev/console.c
}

rs_init函数对串口进行初始化,con_init对显示器和键盘进行初始化。

7. 开机启动时间设置

这里主要设置开机时间

/* init/main.c
 --------------------------------------------------------------- */
void main(void)     /* This really IS void, no error here. */
{
    ...
    time_init();
    ...
}

static void time_init(void)
{
    struct tm time;

    // CMOS的访问速度很慢,为了减少时间误差,在读取了下面循环中的所有数值后,如果此时
    // CMOS中秒值发生了变化,那么就重新读取所有值。这样内核就能把与CMOS时间误差控制在1秒之内。
    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--;                              // tm_mon中月份的范围是0-11
    /* 设定开机时间,从1970年1月1日0时开始计算 */
    startup_time = kernel_mktime(&time);        // 计算开机时间。kernel/mktime.c文件
}

8. 初始化进程0

  • 首先在全局变量中初始化了一个task_union联合体,然后用INIT_TASK初始化其中的task_struct结构体,这个结构体叫init_task.task
  • task[0]设置为init_task.task的地址。
  • tss_struct tsstask_struct的一个成员,它表示当前进程的状态,在初始化好的结构体中,这个成员叫做init_task.task.tss
  • 找到gdt[4],将init_task.task.tss的地址放入其中。
  • 同样desc_struct ldt[3]也是task_struct的一个成员,它表示当前进程的段描述符表,叫做init_task.task.ldt
  • 找到gdt[5],将init_task.task.ldt的地址放入其中。
  • task[0]以后全部设置为NULL,然后将gdt[5]以后全部设置为0。
  • TR寄存器的值设置为4,表示TSS在GDT的第4项;将LDTR寄存器的值设置为5,表示LDT在GDT的第5项。
  • 对时钟中断进行设置,每10ms中断一次。
  • system_call与IDT相挂接,int 0x80设置为系统调用中断入口。
/* init/main.c
 --------------------------------------------------------------- */
void main(void)     /* This really IS void, no error here. */
{
    ...
    sched_init();
    ...
}

/* kernel/sched.c
 --------------------------------------------------------------- */
...
union task_union {
    struct task_struct task;
    char stack[PAGE_SIZE];
};
static union task_union init_task = {INIT_TASK,};
...
struct task_struct * task[NR_TASKS] = {&(init_task.task), };
long user_stack [ PAGE_SIZE>>2 ] ;      // 用户栈
...

void sched_init(void)
{
    int i;
    struct desc_struct * p;                 // 描述符表结构指针

    // 调试用
    if (sizeof(struct sigaction) != 16)         // sigaction 是存放有关信号状态的结构
        panic("Struct sigaction MUST be 16 bytes");

    /* 1.1 将TSS的地址传入gdt[4],TSS已经在INIT_TASK中被初始化好 */
    set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));
    /* 1.2 将LDT的地址传入gdt[5],LDT已经在INIT_TASK中被初始化好 */
    set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));
    /* 2. 将task[1]和gdt[5]以后的项清零 */
    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");        // 复位NT标志
    /* 3.1 将TR设置为4,这样cpu就能够通过gdt[4]找到TSS的地址,从而找到TSS */
    ltr(0);
    /* 3.2 将LDTR设置为5,cpu能够通过gdt[5]找到LDT的地址,从而找到LDT */
    lldt(0);
    /* 4. 设置定时器中断,10ms触发一次。但是由于此时总中断是关的,因此CPU不响应 */
    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);
    /* 5. 设置系统调用总入口 */
    set_system_gate(0x80,&system_call);
}

/* include/linux/sched.h
 --------------------------------------------------------------- */
struct tss_struct {
    long    back_link;  /* 16 high bits zero */
    long    esp0;
    long    ss0;        /* 16 high bits zero */
    long    esp1;
    long    ss1;        /* 16 high bits zero */
    long    esp2;
    long    ss2;        /* 16 high bits zero */
    long    cr3;
    long    eip;
    long    eflags;
    long    eax,ecx,edx,ebx;
    long    esp;
    long    ebp;
    long    esi;
    long    edi;
    long    es;     /* 16 high bits zero */
    long    cs;     /* 16 high bits zero */
    long    ss;     /* 16 high bits zero */
    long    ds;     /* 16 high bits zero */
    long    fs;     /* 16 high bits zero */
    long    gs;     /* 16 high bits zero */
    long    ldt;        /* 16 high bits zero */
    long    trace_bitmap;   /* bits: trace 0, bitmap 16-31 */
    struct i387_struct i387;
};

struct task_struct {
/* these are hardcoded - don't touch */
    long state; /* -1 unrunnable, 0 runnable, >0 stopped */
    long counter;
    long priority;
    long signal;
    struct sigaction sigaction[32];
    long blocked;   /* bitmap of masked signals */
/* various fields */
    int exit_code;
    unsigned long start_code,end_code,end_data,brk,start_stack;
    long pid,father,pgrp,session,leader;
    unsigned short uid,euid,suid;
    unsigned short gid,egid,sgid;
    long alarm;
    long utime,stime,cutime,cstime,start_time;
    unsigned short used_math;
/* file system info */
    int tty;        /* -1 if no tty, so it must be signed */
    unsigned short umask;
    struct m_inode * pwd;
    struct m_inode * root;
    struct m_inode * executable;
    unsigned long close_on_exec;
    struct file * filp[NR_OPEN];
/* ldt for this task 0 - zero 1 - cs 2 - ds&ss */
    struct desc_struct ldt[3];
/* tss for this task */
    struct tss_struct tss;
};
/*
 *  INIT_TASK is used to set up the first task table, touch at
 * your own risk!. Base=0, limit=0x9ffff (=640kB)
 */
#define INIT_TASK \
/* state etc */ { 0,15,15, \
/* signals */   0,{{},},0, \
/* ec,brk... */ 0,0,0,0,0,0, \
/* pid etc.. */ 0,-1,0,0,0, \
/* uid etc */   0,0,0,0,0,0, \
/* alarm */ 0,0,0,0,0,0, \
/* math */  0, \
/* fs info */   -1,0022,NULL,NULL,NULL,0, \
/* filp */  {NULL,}, \
    { \
        {0,0}, \
/* ldt */   {0x9f,0xc0fa00}, \
        {0x9f,0xc0f200}, \
    }, \
/*tss*/ {0,PAGE_SIZE+(long)&init_task,0x10,0,0,0,0,(long)&pg_dir,\
     0,0,0,0,0,0,0,0, \
     0,0,0x17,0x17,0x17,0x17,0x17,0x17, \
     _LDT(0),0x80000000, \
        {} \
    }, \
}
...
#define FIRST_TSS_ENTRY 4
#define FIRST_LDT_ENTRY (FIRST_TSS_ENTRY+1)
#define _TSS(n) ((((unsigned long) n)<<4)+(FIRST_TSS_ENTRY<<3))
#define _LDT(n) ((((unsigned long) n)<<4)+(FIRST_LDT_ENTRY<<3))
#define ltr(n) __asm__("ltr %%ax"::"a" (_TSS(n)))
#define lldt(n) __asm__("lldt %%ax"::"a" (_LDT(n)))

/* include/asm/system.h
 --------------------------------------------------------------- */
#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" \                    // 这里构造段描述符,见P69, P70
    "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")

9. 初始化缓冲区管理结构*

10. 初始化硬盘

将硬盘请求项服务程序do_hd_request()blk_dev控制结构相挂接,然后将硬盘ISRhd_interrupt()与IDT相挂接。

/* init/main.c
 --------------------------------------------------------------- */
void main(void)     /* This really IS void, no error here. */
{
    ...
    hd_init();
    ...
}

/* kernel/blk_dev/hd.c
 --------------------------------------------------------------- */
void hd_init(void)
{
    blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;      // do_hd_request()
    set_intr_gate(0x2E,&hd_interrupt);
    outb_p(inb_p(0x21)&0xfb,0x21);                      // 复位接联的主8259A int2的屏蔽位
    outb(inb_p(0xA1)&0xbf,0xA1);                        // 复位硬盘中断请求屏蔽位(在从片上)
}

11. 初始化软盘

挂接do_fd_request(),初始化软盘中断

/* init/main.c
 --------------------------------------------------------------- */
void main(void)     /* This really IS void, no error here. */
{
    ...
    floppy_init();
    ...
}

/* kernel/floppy.c
 --------------------------------------------------------------- */
void floppy_init(void)
{
    blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;      // =do_fd_request()
    set_trap_gate(0x26,&floppy_interrupt);              // 设置陷阱门描述符
    outb(inb_p(0x21)&~0x40,0x21);                       // 复位软盘中断请求屏蔽位
}

12. 开启中断*

/* init/main.c
 --------------------------------------------------------------- */
void main(void)     /* This really IS void, no error here. */
{
    ...
    sti();
    ...
}

13. 进程0由0特权级翻转到3特权级*

  • 分别将0x17,当前ESP,当前EFLAGS,0x0f,标号1压入栈中。
  • 然后调用iret指令,CPU开始出栈,此时SS=0x17,ESP、EFLAGS不变,CS=0x0f,EIP为标号1的地址。
  • 然后将ds, es, fs, gs也设置为0x17
/* init/main.c
 --------------------------------------------------------------- */
void main(void)     /* This really IS void, no error here. */
{
    ...
    move_to_user_mode();
    ...
}

/* include/system.h
 --------------------------------------------------------------- */
#define move_to_user_mode() \
__asm__ ("movl %%esp,%%eax\n\t" \   // 手工入栈
    "pushl $0x17\n\t" \                // SS
    "pushl %%eax\n\t" \             // ESP
    "pushfl\n\t" \                  // EFLAGS
    "pushl $0x0f\n\t" \                // CS
    "pushl $1f\n\t" \              // EIP
    "iret\n" \                      // 出栈恢复现场
    "1:\tmovl $0x17,%%eax\n\t" \
    "movw %%ax,%%ds\n\t" \
    "movw %%ax,%%es\n\t" \
    "movw %%ax,%%fs\n\t" \
    "movw %%ax,%%gs" \
    :::"ax")

#define sti() __asm__ ("sti"::)

这里CS的段选择符为0x0f=0b01111,表示3特权级,LDT第1项。LDT在INIT_TASK中初始化,其中第1项等于0x00C0 F200 0000 009F,查看段描述符的定义(P69)可看出:
基地址为0,粒度G为1,段限长为0x9F*4096=636KB,DPL为3,TPYE为2。
由于DPL为3,因此是用户特权级3。
SS的段选择符为0x17=0b10111,表示3特权级,LDT第2项。LDT第二项等于0x00C0 FA00 0000 009F,除了TPYE为0xA,其他与CS段类似。然后让DS=ES=FS=GS=SS
这样就把特权级从0翻转到了3。

猜你喜欢

转载自blog.csdn.net/egean/article/details/81000612
今日推荐