zoukankan      html  css  js  c++  java
  • [No000031]操作系统 Operating Systems 之Open the OS!

    从打开电源开始…

    这神秘的黑色背后发生着什么?…

    打开电源,计算机执行的第一句指令什么?

    计算模型(图灵机) 我们要 关注 指针IP 及其 指向的内容

    看看x86 PC

    (1) 刚开机时CPU 处于实模式(和保护模式对应,实模式的寻址CS:IP(CS 左移4 位+IP) ,和保护模式不一样!)

    (2) 开机时,CS=0xFFFF; IP=0x0000

    (3) 寻址0xFFFF0(ROM BIOS 映射区)

    (4) 检查RAM ,键盘,显示器,软硬磁盘

    (5) 将磁盘0 磁道0 扇区读入0x7c00 处

    (6) 设置cs=0x07c0 ,ip=0x0000

    0x7c00 处存放的代码

    就是从磁盘引导扇区读入的那512 个字节

    • 引导扇区就是 启动设备的第一个扇区(开机时按住del 键可进入启动设备设置界面,可以设置为光盘启动!)
    • 启动设备信息被设置在CMOS(CMOS: 互补金属氧化物半导体(64B-128B) 。用来存储实时钟和硬件配置信息。) 中…
    • 因此,硬盘的第一个扇区上存放着开机后执行的第一段我们可以控制的程序。
    • 操作系统的故事从这里开始…

    引导扇区代码: bootsect.s

    .globl begtext,begdata,begbss,endtext,enddata,endbss
    
    .text // 文本段 .text 等是伪操作符,告诉编译器产生文本段,.text 用于标识文本段的开始位置。此处的.text 、.data 、.bss 表明这3 个段重叠,不分段!
    
    begtext:
    
    .data // 数据段
    
    begdata:
    
    .bss // 未初始化数据段
    
    begbss:
    
    entry start // 关键字entry 告诉链接器 "程序入口"
    
    start:
    
    mov ax, #BOOTSEG     mov ds, ax//此条语句就是0x7c00处存放的语句!
    
    mov ax, #INITSEG     mov es, ax
    
    mov cx, #256
    
    sub si, si           sub di,di//将0x07c0:0x0000 处的256 个字移动到0x9000:0x0000处
    
    rep movw
    
    jmpi go, INITSEG
    
    BOOTSEG = 0x07c0
    
    INITSEG = 0x9000
    
    SETUPSEG = 0x9020

    jmpi go, INITSEG

    jmpi (jump intersegment 段间跳转): cs=INITSEG, ip=go

    go: mov ax,cs //cs=0x9000
    
    mov ds,ax         mov es,ax          mov ss,ax       mov sp,#0xff00//为call 做准备!
    
    load_setup: // 载入setup 模块
    
    mov dx,#0x0000     mov cx,#0x0002     mov bx,#0x0200
    
    mov ax,#0x0200+SETUPLEN     int 0x13 //BIOS 中断 0x13 是BIOS 读磁盘扇区的中断: ah=0x02- 读磁盘,al=扇区数量(SETUPLEN=4) ,ch= 柱面号,cl= 开始扇区,dh= 磁头号,dl= 驱动器号,es:bx=内存地址
    jnc ok_load_setup
    
    mov dx,#0x0000
    
    mov ax,#0x0000 // 复位
    
    int 0x13
    
    j load_setup //重读

    读入setup 模块后: ok_load_setup

    Ok_load_setup: // 载入setup 模块

    mov dl,#0x00     mov ax,#0x0800 //ah=8 获得磁盘参数

    int 0x13     mov ch,#0x00     mov sectors,cx

    mov ah,#0x03     xor bh,bh     int 0x10 // 读光标

    mov cx,#24     mov bx,#0x0007//7 是显示属性!

    mov bp,#msg1     mov ax,#1301     int 0x10 // 显示字符

    mov ax,#SYSSEG //SYSSEG=0x1000

    mov es,ax

    call read_it // 读入system 模块

    jmpi 0,SETUPSEG//转入0x9020:0x0000执行setup.s

    bootsect.s据 中的数据 // 在文件末尾
    
    sectors: .word 0 // 磁道扇区数
    
    msg1:.byte 13,10
    
      .ascii "Loading system..."
    
      .byte 13,10,13,10

    boot 工作: 读setup ,读system…

    read_it // 读入system

    为什么读入system 模块还需要定义一个函数?

      system 模块可能很大,要跨越磁道!

    read_it: mov ax,es cmp ax,#ENDSEG  jb ok1_read    ENDSEG=SYSSEG+SYSSIZE               SYSSIZE=0x8000 // 该变量可根据              Image 大小设定( 编译操作系统时)

      ret

    ok1_read:

      mov ax,sectors

      sub ax,sread //sread 是当前磁道已读扇区数,ax 未读扇区数

      call read_track // 读磁道...

    引导扇区的末尾 //BIOS 用以识别引导扇区

    .org 510

      .word 0xAA55 //扇区的最后两个字节

    否则会打出非引导设备

    可以转入setup 执行了,jmpi 0, SETUPSEG

    Power On…

    setup 模块,即setup.s

    根据名字就可以想到: setup 将完成OS启动前的设置

    start: mov ax,#INITSEG mov ds,ax mov ah,#0x03

    xor bh,bh int 0x10// 取光标位置dx mov [0],dx   取出光标位置( 包括其他硬件参数)到0x90000

    mov ah,#0x88 int 0x15 mov [2],ax ... //扩展内存大小 SYSSEG = 0x1000

    cli /// 不允许中断

    mov ax,#0x0000 cld

    do_move: mov es,ax add ax,#0x1000

    cmp ax,#0x9000 jz end_move

    mov ds,ax sub di,di

    sub si,si

    mov cx,#0x8000

    rep //将system 模块移到0

    movsw

    jmp do_move

    内存地址

    长度

    名称

    0x90000

    2

    光标位置

    0x90002

    2

    扩展内存数

    0x901FC

    2

    根设备号

    0x9000C

    2

    显卡参数

    将setup 移到0 地址处...

    • 但0 地址处是有重要内容的
    • 以后不调用int 指令了吗?
    • 因为操作系统要让硬件进入保护模式了…
    • 保护模式下int n 和cs:ip 解释不再和实模式一样

    end_move: mov ax,#SETUPSEG mov ds,ax

    lidt idt_48         lgdt gdt_48// 设置保护模式下的中断和寻址

    进入保护模式的命令 ...

    idt_48:.word 0 .word 0,0 // 保护模式中断函数表, 又一个函数表

    gdt_48:.word 0x800 .word 512+gdt,0x9

    gdt: .word 0,0,0,0

    .word 0x07FF, 0x0000, 0x9A00, 0x00C0

    .word 0x07FF, 0x0000, 0x9200, 0x00C0

    用GDT 将cs:ip 变成物理地址

    保护模式下的地址翻译和中断处理

    进入保护模式

    call empty_8042 mov al,#0xD1 out #0x64,al

      //8042 是键盘控制器,其输出端口P2 用来控制A20 地址线

    call empty_8042 mov al,#0xDF out #0x60,al

      // 选通A20 地址线 call empty_8042

    初始化8259( 中断控制) // 一段非常机械化的程序

    mov ax,#0x0001 mov cr0,ax

    jmpi 0,8

    D1 表示写数据到8042 的P2

    jmpi 0,8 //gdt中的8

    跳到system 模块执行...

    • system 模块( 目标代码) 中的第一部分代码? head.s
    • system 由许多文件编译而成,为什么是head.s?

    disk: Image

      dd bs=8192 if=Image of=/dev/PS0 if=input file /dev/PS0是软驱A

    Image: boot/bootsect boot/setup tools/system tools/build

    tools/build boot/bootsect boot/setup tools/system > Image linux/Makefile

    tools/system: boot/head.o init/main.o $(DRIVERS) …

    $(LD) boot/head.o init/main.o $(DRIVERS) … -o tools/system

    明白为什么head.s 就这样一个名字了吧?

    head.s // 一段在保护模式下运行的代码

    setup 是进入保护模式,head 是进入之后的初始化

    stratup_32: movl $0x10,%eax mov %ax,%ds mov %ax,%es

      mov %as,%fs mov %as,%gs // 指向gdt 的0x10 项( 数据段) idt_48:.word 0 word 0,0

      lss _stack_start,%esp // 设置栈( 系统栈) 现在忽略中断_        idt: .fill 256,8,0

      call setup_idt struct{long *a; short b;}stack_start={&user_stack[PAGE_SIZE>>2],0x10};

      call setup_gdt 和前面的代码不一样了? 因为是32 位汇编代码!

       xorl %eax,%eax

    1:incl %eax

    movl %eax,0x000000 cmpl %eax,0x100000

    je 1b //0 地址处和1M 地址处相同(A20 没开启) ,就死循环

    jmp after_page_tables // 页表,什么东东?

    setup_idt: lea ignore_int,%edx

    movl $0x00080000,%eax movw %dx,%ax

    lea _idt,%edi movl %eax,(%edi)

    关于汇编…head.s的汇编和前面不一样?

    1. as86 汇编:能产生16 位代码的Intel 8086(386) 汇编

      mov ax, cs //cs -> ax, 目标操作数在前

    2. GNU as 汇编:产生32 位代码,使用AT&T 系统V 语法

    AT&T 美国电话电报公司,包含贝尔实验室等,1983 年AT&T UNIX 支持组发布了系统V

      movl var, %eax //(var) ->%eax

    movb -4(%ebp), %al // 取出一字节

    (3) 内嵌汇编,gcc 编译x.c 会产生中间结果as 汇编文件x.s

    __asm__(" 汇编语句": 输出 : 输入 : 破坏部分描述);

    __asm__("movb %%fs:%2, %%al" :"=a"(_res) : "0"(seg), "m"(*(addr)) );

    0 或空表示使用与相应输出一样的寄存器

    a 表示使用eax ,并编号%0

    %2 表示addr ,m表示使用内存

    after_page_tables // 设置了页表之后

    setup 是进入保护模式,head

    after_page_tables:

    pushl $0 pushl $0 pushl $0 pushl $L6

    pushl $_main jmp set_paging

    L6: jmp L6 //将来学到!

    setup_paging: 设置页表 ret

    简单的几句程序,控制流却很复杂

    setup_paging 执行ret 后? 会执行函数main()

    进入main() 后的栈为0 ,0 ,0 ,L6

    main() 函数的三个参数是0 ,0 ,0

    main() 函数返回时进入L6 ,死循环...

    进入main 函数

    在init/main.c 中 //开始C 语言程序了!

    void main(void)

    {  mem_init();

    trap_init();

    blk_dev_init();

    chr_dev_init();

    tty_init();

    time_init();

    sched_init();

    buffer_init();

    hd_init();

    floppy_init();

    sti();

    move_to_user_mode();

    if(!fork()){init();}

    }

    为什么是void?

    三个参数分别是envp,argv,argc但此处main 并没使用

    此处的main 只保留传统main 的形式和命名main 表示C 语言函数的入口!

    main 的工作就是xx_init: 内存、中断、设备、时钟、CPU 等内容的初始化

    看一看mem_init…

    在linux/mm/memory.c 中

    void mem_init(long start_mem,long end_mem) //这两个参数从哪里来?

    {

    int i;

    for(i=0; i<PAGING_PAGES; i++)

    mem_map[i] = USED;

    i = MAP_NR(start_mem);

    end_mem -= start_mem;

    end_mem >>= 12;

    while(end_mem -- > 0) //干了些什么?

    mem_map[i++] = 0; }

    管理硬件? 如何管理?就是用数据结构+ 算法…

  • 相关阅读:
    QT学习:08 QString
    QT学习:07 字符编码的问题
    QT学习:06 常用的全局变量与宏定义
    QT学习:05 元对象系统
    QT学习:04 代码化的界面绘制
    QT学习:03 信号与槽
    QT学习:02 界面布局管理
    HTTP权威指南之URL与资源
    系统安装注意事项
    HTTP权威指南之web基础
  • 原文地址:https://www.cnblogs.com/Chary/p/No000031.html
Copyright © 2011-2022 走看看