学堂在线_操作系统_notes_第3-4讲_bootloader启动、中断、异常、系统调用


20220628.No.1823


主机加电后,CPU执行的第一条指令在什么地方?CPU从磁盘上的什么地方读取OS的内容?

CPU加电,电流稳定后,CPU初始化寄存器(代码段寄存器CS当前值 左移4位,再加 指令指针寄存器IP当前值,得到 PC当前值,即第一条指令的内存地址)。CPU加电时,x86-32硬件系统 处于 实模式,地址总线 只有20位 地址空间 可用,即220 Bytes = 210 KB = 1 MB。所以,BIOS启动固件 只能存储 1MB地址空间。

为了从磁盘上读取数据,BIOS启动固件必须提供一些功能:基本I/O程序;系统设置信息(CPU加电时,BIOS里的设置 决定 系统是从磁盘、光盘、或网络启动);开机后硬件自检程序POST;系统自启动程序等。

依靠BIOS启动固件的功能,BIOS能从磁盘(的引导扇区)中把 加载程序bootloader 读取加载到 内存(内存由RAM和ROM组成,无论是否加电,ROM存储的内容都会存在)的ROM中。

然后,BIOS把系统控制权交给 bootloader。bootloader 把OS的代码从磁盘加载到内存,再跳转到OS的起始地址,把控制权交给 OS的Kernel代码。


BIOS系统启动规范固件 的发展

  • BIOS-MBR
  • BIOS-GPT
  • PXE(通过网络从服务器上下载 Kernel镜像 来启动OS)。

UEFI系统启动规范固件(统一可扩展固件接口标准)

为了支持磁盘多分区,可能既要有磁盘上的主引导记录,也要有活动分区里的引导记录。设计这两层引导记录,显得复杂。所以,后来又有了 UEFI系统启动规范固件(统一可扩展固件接口标准)


OS Kernel 与外界(外设、应用程序)打交道,基本只有3种接口(中断(interrupt)、异常(exception)、系统调用(system call))。

  • interrupt:解决 硬件外设连接主机 时可能出现的问题(例如 键盘打字输入的信息需要CPU及时读取)。
  • exception:解决 应用软件处理意想不到的行为 时可能出现的问题(例如 做除法时除以0,或内存出错)。
  • system call:提供接口,既让应用软件方便得到 OS Kernel服务,又不影响 OS Kernel的安全。系统调用(用OS Kernel的服务)与功能调用(用函数库的服务)有区别。

interrupt、exception、system call


system call 的三种最常用的应用程序编程接口(API)

  • Win32 API 用于 Windows;
  • POSIX API 用于 POSIX-based systems (包括UNIX,LINUX,Mac OS X的所有版本);
  • Java API 用于JAVA虚拟机(JVM)。

system call 与函数调用的主要区别:

  • INT和IRET指令用于system call。为保护OS Kernel,系统调用时,存在 堆栈切换和特权级的转换;
  • CALL和RET用于常规调用。常规调用时,没有堆栈切换。

x86-32硬件系统的CPU指令手册:

Intel 64 and IA-32 Architectures Software Developer Manuals
http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html


system call 的时间开销

system call 的时间开销 > 函数调用的时间开销。因为system call 存在 用户态与内核态之间的切换,为了保障OS Kernel的安全性。


system call 示例:

文件复制过程中的系统调用序列

从 源文件 到 目标文件,依次执行——
获取输入文件名
在屏幕显示提示
等待并接收键盘输入
获取输出文件名
在屏幕显示提示
等待并接收键盘输入
打开输入文件
如果文件不存在,出错退出
创建输出文件
如果文件存在,出错退出
循环
读取输入文件
写入输出文件
直到读取结束
关闭输出文件
在屏幕显示完成信息
正常退出


上述操作涉及的 system call 大致有——

// System call numbers
#define SYS_fork    1
#define SYS_exit    2
#define SYS_wait    3
#define SYS_pipe    4
#define SYS_write   5 // 写
#define SYS_read    6 // 读
#define SYS_close   7 // 关
#define SYS_kill    8
#define SYS_exec    9
#define SYS_open   10 // 开
#define SYS_mknod  11
#define SYS_unlink 12
#define SYS_fstat  13
#define SYS_link   14
#define SYS_mkdir  15
#define SYS_chdir  16
#define SYS_dup    17
#define SYS_getpid 18
#define SYS_sbrk   19
#define SYS_sleep  20
#define SYS_procmem 21

在ucore中,库函数read()的功能是读文件

user/libs/file.h: int read(int fd, void * buf, int length)

// 库函数read()的参数和返回值——
// int fd—文件句柄 // 要读取的文件
// void  * buf—数据缓冲区指针 // 文件读取后的存放地址
// int  length—数据缓冲区长度 // 文件的单次读取的最大数据长度
// int return_value:返回读出数据长度

// 库函数read()使用示例——
in sfs_filetest1.c: ret = read(fd, data, len);

system call 库接口示例

sfs_filetest1.c: ret=read(fd,data,len);
 ……
// 向堆栈中压栈:
  8029a1:    8b 45 10                 mov    0x10(%ebp),%eax 
  8029a4:    89 44 24 08              mov    %eax,0x8(%esp)
  8029a8:    8b 45 0c                 mov    0xc(%ebp),%eax
  8029ab:    89 44 24 04              mov    %eax,0x4(%esp)
  8029af:    8b 45 08                 mov    0x8(%ebp),%eax
  8029b2:    89 04 24                 mov    %eax,(%esp)
// 压栈结束后,再函数调用:
  8029b5:    e8 33 d8 ff ff           call   8001ed <read> 

// 全部的system call 都是通过 宏 展开形成相应的函数:
syscall(int num, ...) {
...
    asm volatile (
            "int %1;" // system call 的指令。用户态的函数调用程序执行到此,执行软中断,转成 system call,进入Kermel,转到 汇编程序alltraps()。
            : "=a" (ret)
            : "i" (T_SYSCALL), // system call 的interrupt向量编号
              "a" (num), // read的system call 编号
              "d" (a[0]), // 参数
              "c" (a[1]), // 参数
              "b" (a[2]), // 参数
              "D" (a[3]), // 参数
              "S" (a[4]) // 参数
            : "cc", "memory");
    return ret;

ucore系统调用read(fd, buffer, length)的实现

  1. kern/trap/trapentry.S: alltraps() // 获得 软中断 所需要的相关信息 组成的数据结构tf。
  2. kern/trap/trap.c: trap() // trap()函数 依据T_SYSCALL,转到 系统调用函数syscall()。
    tf->trapno == T_SYSCALL // T_SYSCALL 是 system call 对应的interrupt向量。
  3. kern/syscall/syscall.c: syscall()
    tf->tf_regs.reg_eax ==SYS_read // eax 是 system call 编号。若eax ==SYS_read,说明进入Kernel的是system call,而且这个system call 要调用的功能是read。
  4. kern/syscall/syscall.c: sys_read()
    从 tf->sp 获取 fd, buf, length // 从 堆栈sp 中获取 read()函数的参数。fd是文件句柄,buf是数据缓冲区头指针,length是数据缓冲区长度。
  5. kern/fs/sysfile.c: sysfile_read() // sysfile_read()函数 读取文件,直接操作底下的驱动程序。
    读取文件
  6. kern/trap/trapentry.S: trapret() // trapret()函数 将返回值(读取到的数据长度) 返回给用户态。

功能06H 功能描述:设置闹钟 入口参数:AH=06H CH=BCD码格式的小时 CL=BCD码格式的分钟 DH=BCD码格式的秒 出口参数:CF=0——操作成功,否则,闹钟已设置或时钟已停止 (8)、功能07H 功能描述:闹钟复位 入口参数:AH=07H 出口参数:无 (9)、功能0AH 功能描述:读取天数计数,仅在PS/2有效,在此从略 (10)、功能0BH 功能描述:设置天数计数,仅在PS/2有效,在此从略 (11)、功能80H 功能描述:设置声音源信息 入口参数:AH=80H AL=声音源 =00H——8253可编程计时器,通道2 =01H——盒式磁带输入 =02H——I/O通道上的"Audio In" =03H——声音产生芯片 出口参数:无 8、直接系统服务(Direct System Service) INT 00H —“0”作除数 INT 01H —单步中断 INT 02H —非屏蔽中断(NMI) INT 03H —断点中断 INT 04H —算术溢出错误 INT 05H —打印屏幕和BOUND越界 INT 06H —非法指令错误 INT 07H —处理器扩展无效 INT 08H —时钟中断 INT 09H —键盘输入 INT 0BH —通信口(COM2:) INT 0CH —通信口(COM1:) INT 0EH —磁盘驱动器输入/输出 INT 11H —读取设备配置 INT 12H —读取常规内存大小(返回值AX为内存容量,以K为单位) INT 18H —ROM BASIC INT 19H —重启动系统 INT 1BH —CTRL+BREAK处理程序 INT 1CH —用户时钟服务 INT 1DH —指向显示器参数表指针 INT 1EH —指向磁盘驱动器参数表指针 INT 1FH —指向图形字符模式表指针
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值