深入操作系统(一)

引言:偶尔在爱课程网上找到一个关于操作系统的高质量课程,课程内容深邃且细致,在此准备做一系列关于此课程的学习历程,有兴趣的朋友可以登录下述网址学习:点击打开链接 本文主要参考了《Linux内核完全注释》,同时鉴于本人没有汇编基础,很多地方可能存在理解性错误,还望能予以斧正,谢谢!



一.什么是操作系统?


1.是计算机硬件和应用之间的一层软件

    1).方便我们使用硬件,比如使用显存...

     2).高效地使用硬件,比如打开多个终端

2.管理硬件

    cpu管理,内存管理,终端管理,磁盘管理,文件管理,网络管理,电源管理,多核管理等


扫描二维码关注公众号,回复: 1507041 查看本文章



二.操作系统启动


1.计算机概要

    1).1946年提出的冯·诺依曼存储程序思想

    2).存储程序主要思想:将程序和数据存放到计算机内部的存储器中,计算机在程序的控制下一步一步进行处理

    3).计算机由五大部件组成:输入设备、输出设备、存储器、运算器、控制器


2.计算机启动的第一段代码

    1).x86 PC刚开机时CPU处于实模式(real-mode)

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

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

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

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

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

该过程的目的即:跳至引导扇区执行

实模式:Intel公司80286及以后的x86兼容处理器的一种操作模式,特殊定义为20位地址内存可访问空间(物理内存和BIOS-ROM)。实模式下处理器没有硬件级的内存保护概念和多道任务的工作模式。由于8086/8088使用寄存器为16位,其实际表示的内存只有64K,因此Intel采用分段寻址模式即:物理地址=(cs)左移4位段地址+(ip)偏移地址


3.0x7c00处存放的代码

    1).引导扇区就是启动设备的第一个扇区

    2).启动设备信息被设置在CMOS中

    3).磁盘第一个扇区上存放着开机后执行的第一段我们可以控制的程序

CMOS:互补金属氧化物半导体(64B-128B)。是主板上的一块可读写的并行或串行FLASH芯片,是用来保存BIOS的硬件配置和用户对某些参数的设定


4.bootsect.s

到此bootsect执行完成,总结其流程大致是:

    1).在PC机加电,ROM BIOS自检后,将引导扇区代码bootsect加载到内存地址0x7c00开始执行,之后bootsect将自己移动到内存的绝对地址0x90000处继续执行。

    2).把磁盘第二个扇区开始的四个扇区的setup模块加载到内存紧接着bootsect后面的0x90200,利用int 0x13中断读取磁盘参数表中当前启动引导盘的参数,在屏幕上显示logo.

    3).再将磁盘上setup模块后面的system加载到内存0x10000开始处

    4).最后长跳转到setup程序开始处执行setup模块


.globl begtext, begdata, begbss, endtext, enddata, endbss    // 定义了6个全局标识符
.text    // 文本段
begtext:
.data    // 数据段
begdata:
.bss     // 未初始化数据段
begbss:
.text    // 文本段

SETUPLEN = 4    // 程序的扇区数(setup-sectors)值

BOOTSEG = 0x07c0    // bootsect 的原始地址(段地址)

INITSEG = 0x9000    // 将bootsect移到这里;

SETUPSEG = 0x9020    // 程序从这里开始

ENDSEG = SYSSEG + SYSSIZE    // 停止加载的段地址

entry start    // 关键字entry告诉链接器"程序入口"
start:
    mov ax, #BOOTSEG    mov ds, ax    // 将ds段寄存器置为0x7c0
    mov ax, #INITSEG    mov es, ax    // 将es段寄存器置为0x900
    mov cx, #256                      // 设置移动计数值256字
    sub si, si    sub di, di          // 源地址   ds:si = 0x07c0:0x0000
                                      // 目的地址    es:di = 0x9000:0x0000 
    rep   movw                        // 从源地址处移动512字节到目的地址
    jmpi  go, INITESG                 // 跳转到段地址INITSEG,段内地址go的位置

go:    // cs = 0x9000
    mov ax,cs
    mov ds,ax
    mov es,ax

    mov ss,ax    // 设置堆栈
    mov sp,#0xFF00


// 利用BIOS中断 int 0x13 将setup模块从磁盘第2个扇区读到0x90300开始处,共读入4个扇区(因此此时第一个扇区已经被bootsect占据)
// 如果读出错,复位驱动器,并重试
// ah = 0x02    -读磁盘扇区到内存
// al = 需要读出的扇区数量(SETUPLEN = 4)
// ch = 柱面号
// cl = 开始扇区
// dh = 磁头号
// dl = 驱动器号
// es:bx = 内存地址

load_setup:    // 读入setup模块
    mov dx,#0x0000    mov cx,#0x0002    mov bx,#0x0200
    mov ax,#0x0200+SETUPLEN    int 0x13    // BIOS中断
    jnc ok_load_setup
    mov dx,#0x0000
    mov ax,#0x0000    // 复位
    int 0x13
    j    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            // 共显示24个字符
    mov bp,#msg1    mov as,#1301    int 0x10    // 显示字符
    // 将 system 模块加载到0x10000(64KB)开始处
    mov ax,#SYSSEG    // SYSSEG = 0x1000
    mov es,ax
    call read_it    // 读入system 模块
    jmpi 0,SETUPSE    // 转交控制权给setup

read_it:
    mov ax,es    cmp ax,#ENDSEG    jb okl_read
    ret
okl_read:
    mov ax,sectors
    sub ax,sread    // sread为当前磁道已读扇区数,ax未读扇区数
    call read_track    // 读磁道


其中各个模块在磁盘上的分布情况入下图所示



5.setup.s

start:
    mov ax,#INITSEG    mov ds,ax    // 将ds 设置成 0x9000
    mov ah,#0x03
    xor bh,bh    
    int 0x10      //取出光标位置
    mov [0],dx
    mov ah,#0x88    
    int 0x15      // 获取物理内存大小
    mov [2],ax    // 9000左移4位再加2,即:0x90002(扩展内存数)
    ......
    cli           // 不允许中断
    mov ax,#0x0000    
    cld

// 将system模块全部移动到0地址
do_move:
    mov es,ax    add ax,#0x1000
    cmp ax,#0x9000    jz end_move
    mov ds,ax    sub di,di
    mov cx,#0x8000
    rep
    movsw
    jmp do_move


mov ax,#0x0001
mov cr0,ax   // 切换到保护模式
jmpi  0,8    // 实际是查询gdt表,跳至指定地址(实际跳转到0地址处)


cr0寄存器:将0置给PE,启动保护模式


保护模式:由于实模式下访问内存的大小限制,因此引入保护模式,又称为虚拟地址保护模式。保护模式下,段被一系列的"描述符表"定义,段寄存器存储的是指向这些表的指针。而定义内存段的表又两种:全绝描述符表(GDT)和局部描述符表(LDT),对于每一个操作系统都必须定义一个GDT,而每一个正在运行的任务都会有一个相应的LDT。每一个描述符的长度是8个字节。总的来说就是通过程序内部的地址(虚拟地址)由操作系统转化成物理地址去访问,进而起到了保护的作业

IDT:中断描述符表,类似于GDT,记录了0~255的中断信号和调用函数间的关系


保护模式下的地址翻译:

实模式下:cs左移4位+ip

保护模式下:根据cs查表+ip


同理在保护模式下中断处理函数:

// 因此setup需要临时初始化gdt,idt表
end_move:
    mov ax,#SETUPSEG    move ds,ax
    lidt idt_48
    lgdt qdt 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表设置完成

据此总结一下setup的作用:

    1).读入系统的硬件参数

    2).将system模块移到0地址处

    3).初始化gdt,idt表并开启保护模式

    4).跳转至0地址处,开始执行system模块



6.system模块

Makefile:相当于程序编译过程中的批处理文件

system由许多的文件编译而成,这里用到了Makefile,具体的教程请参考链接点击打开链接

// linux/Makefile
disk: Image
 dd bs=8192 if=Image of=/dev/PS0
Image: boot/bootsect  boot/setup tools/system tools/build
 tools/build boot/bootsect boot/setup tools/system> Image tools/system: boot/head.o  init/main.o $(DRIVERS) …    // 编译
$(LD) boot/head.oinit/main.o $(DRIVERS) … -o tools/system    // 链接


system第一个模块 -- head.s

// 32位汇编代码
startup_32:
    movl $0x10,%eax    mov %ax,%ds    mov %ax,%es
    mov %as,%fs    mov %as,%gs    // 指向gdt的0x10项(数据段)
    lss _stack_start,%esp    // 设置系统堆栈
    call setup_idt    // 正式创建idt表项
    call setup_gdt    // 正式创建gdt表项
    xorl %eax,%eax
l:incl %eax
    movl %eax,0x000000 cmpl %eax,0x100000
    je lb    // 开启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).该程序处于内存开始的0地址段

2).加载各个数据段寄存器,重新设置中断描述符表idt


成功设置页表之后,head.s要跳出执行main.c文件

// 从汇编跳入至C函数
after_page_tables:
    pushl $0    pushl $0    pushl $0    // main函数的三个参数   
    pushl $L6     // main的返回地址(该处不会被执行)
    pushl $_main    
jmp set_paging    // 设置页表 ret
L6: jmp L6
setup_paging:    
    ret    // 出栈


进入main函数

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();    // 新建的子进程中执行
    }
}


展开其中的mem_init函数

# 内存的初始化
void mem_init(long start_mem, long end_mem)    // 这两个参数从0x90002(setup初始化系统硬件的参数)
{
    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;    // 左移12位,每4K一页
    while(end_mem-- > 0)
        mem_map[i++] = 0;    // 标记未使用
} 


最后总结,整个内核在内存中的位置和移动后的位置情况入下图所示:


猜你喜欢

转载自blog.csdn.net/adorkable_thief/article/details/80585505
今日推荐