0%

c函数的调用实现

其他需要注意的事项

  • 参数(parameters)& 函数返回值(return values)可通过寄存器或位于内存中的栈来传递
  • 不需要保存/恢复(save/restore)所有寄存器

x86启动顺序 - 第一条指令

  • CS = F000H, EIP = 0000FFF0H
  • 实地址是:
    Base + EIP = FFFF0000H + 0000FFF0H = FFFFFFF0H
    这是BIOS的EPROM(Erasable Programmable Read Only Memory)所在地
  • 当CS被新值加载,则地址转换规则将开始起作用
  • 通常第一条指令是一条长跳转指令(这样CS和EIP都会更新)到BIOS代码中执行

x86启动顺序 - 处于实模式的段

  • 段选择子(Segment Selector): CS,DS,SS,…
  • 偏移量(Offset): EIP

x86启动顺序 - 从BIOS到Bootloader

  • BIOS 加载存储设备(比如软盘、硬盘、光盘、USB盘)上的第一个 扇区(主引导扇区,Master Boot Record, or MBR)的512字节到内存的0x7c00 …
  • 然后跳转到 @ 0x7c00的第一条指令开始执行

x86启动顺序 - 从Bootloader到OS

  • bootloader做的事情
    • 使能保护模式(protection mode)& 段机制(segment-level protection)
    • 从硬盘上读取kernel in ELF 格式的ucore kernel (跟在MBR后面的扇区)并放到内存中固定位置
    • 跳转到ucore OS的入口点(entry point)执行,这时控制器到了ucore OS中

x86启动顺序 - 段机制

x86启动顺序 - 使能保护模式

  • 使能保护模式(protection mode),bootloader/OS 要设置CR0的bit 0(PE)
  • 段机制(segment-level protection)在保护模式下是自动使能的

x86启动顺序 - 加载ELF格式的ucore OS kernel

系统调用示例

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    源文件 ----------------> 目标文件
    获取输入文件名
    在屏幕显示提示
    等待并接受键盘输入
    获取输出文件名
    在屏幕显示提示
    等待并接受键盘输入
    打开输入文件
    如果文件不存在,出错并退出
    创建输出文件
    如果文件存在,出错并退出
    循环
    读取输入文件
    写入输出文件
    直到读取结束
    关闭输出文件
    在屏幕显示完成信息
    正常退出
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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);
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      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,0xc(%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>
      syscall(int num, ...){
      ...
      asm volatile(
      "int %1;"
      : "=a" (ret)
      : "i" (T_SYSCALL),
      "a" (num),
      "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()
  2. kern/trap/trap.c: trap()
    tf->trapno = = T_SYSCALL
  3. kern/syscall/syscall.c: syscall()
    tf->tf_regs.reg_eax = = SYS_read
  4. kern/syscall/syscall.c: sys_read()
    从 tf->sp 获取 fd, buf, length
  5. kern/fs/sysfile.c: sysfile_read()
    读取文件
  6. kern/trap/trapentry.S: trapret()

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

标准C库的例子

  • 应用程序调用printf()时,会触发系统调用write()。

系统调用

  • 操作系统服务的编程接口
  • 通常由高级语言编写(C或C++)
  • 程序访问通常是通过高层次的API接口而不是直接进行系统调用
  • 三种最常用的应用程序编程接口(API)
    • Win32 API用于Windows
    • POSIX API用于POSIX-based systems(包括UNIX, LINUX, Mac OS X的所有版本)
    • Java API用于JAVA虚拟机(JVM)

系统调用的实现

  • 每个系统调用对应一个系统调用号
    • 系统调用接口根据系统调用号来维护表的索引
  • 系统调用接口调用内核态中的系统调用功能实现,并返回系统调用的状态和结果
  • 用户不需要知道系统调用实现
    • 需要设置调用参数和获取返回结果
    • 操作系统接口的细节大部分都隐藏在应用编程接口后
      • 通过运行程序支持的库来管理

函数调用和系统调用的不同处

  • 系统调用
    • INT和IRET指令用于系统调用
      • 系统调用时,堆栈切换和特权级的转换
  • 函数调用
    • CALL和RET用于常规调用
      • 常规调用时没有堆栈切换
  • Intel 64 and IA-32 Architecture Software Developer

中断、异常和系统调用的开销

  • 超过函数调用
  • 中断、异常和系统调用
    • 引导机制
    • 建立内核堆栈
    • 验证参数
    • 内核态映射到用户态地址空间
      • 跟新页面映射权限
    • 内核态独立地址空间
      • TLB

背景

  • 为什么需要中断、异常和系统调用
    • 在计算机运行中,内核是被信任的第三方
    • 只有内核可以执行特权指令
    • 方便应用程序
  • 中断和异常希望解决的问题
    • 当外设连接计算机时,会出现什么现象?
    • 当应用程序处理意想不到的行为时,会出现什么现象?
  • 系统调用希望解决的问题
    • 用户应用程序是如何得到系统服务?
    • 系统调用和功能调用的不同之处是什么?

内核的进入与退出

内核的进入与退出图

中断、异常和系统调用

  • 系统调用(system call)
    • 应用程序主动向操作系统发出服务请求
  • 异常(exception)
    • 非法指令或者其他原因导致当前指令执行失败
      (如:内存出错)后的处理请求
  • 中断(hardware interrupt)
    • 来自硬件设备的处理请求

中断、异常和系统调用比较

  • 源头
    • 中断:外设
    • 异常:应用程序意想不到的行为
    • 系统调用: 应用程序请求操作提供服务
  • 响应方式
    • 中断:异步
    • 异常:异步
    • 系统调用:异步或同步
  • 处理机制
    • 中断:持续进行,对用户应用程序是透明的
    • 异常:杀死或者重新执行意想不到的应用程序指令
    • 系统调用:等待和持续

中断处理机制

  • 硬件处理
    • 在CPU初始化时设置中断使能标志
      • 依据内部或外部事件设置中断标志
      • 依据中断向量调用相应中断服务例程
  • 软件
    • 现场保存(编译器)
    • 中断服务处理(服务例程)
    • 清除中断标记(服务例程)
    • 现场恢复(编译器)

中断嵌套

  • 硬件中断服务例程可被打断
    • 不同硬件中断源可能硬件中断处理时出现
    • 硬件中断服务例程中需要临时禁止中断请求
    • 中断请求会保持到CPU做出响应
  • 异常服务例程可被打断
    • 异常服务例程执行时可能出现硬件中断
  • 异常服务例程可嵌套
    • 异常服务例程可能出现缺页

计算机启动流程

计算机启动流程图

CPU初始化

  • CPU 加电稳定后从0xffff0读第一条指令

    • CS:IP = 0xf000:fff0
    • 第一条指令是跳转指令
  • CPU初始状态为16位实模式

    • CS:IP是16位寄存器
    • 指令指针 PC = 16*CS + IP
    • 最大地址空间是1MB

BIOS初始化过程

  • 硬件自检POST
  • 检测系统中内存和显卡等关键部件的存在和工作状态
  • 查找并执行显卡等接口卡BIOS,进行设备初始化
  • 执行系统BIOS,进行系统检测
    • 检测和配置系统中安装的即插即用设备
  • 更新CMOS中的扩展系统配置数据ESCD
  • 按指定启动顺序从软盘、硬盘或光驱启动

主引导记录MBR格式

  • 启动代码:446字节
    • 检查分区表正确性
    • 加载并跳转到磁盘上的引导程序
  • 硬盘分区表:64字节
    • 描述分区状态和位置
    • 每个分区描述信息占据16字节
  • 结束标志字:2字节(55AA)
    • 主引导记录的有效标志

分区引导扇区格式

  • 跳转指令:跳转到启动代码
    • 与平台相关代码
  • 文件卷头:文件系统描述信息
  • 启动代码:跳转到加载程序
  • 结束标志:55AA(0x55 0xAA) 二进制为: 0101010110101010

加载程序(bootloader)

加载程序图

系统启动规范

  • BIOS
    • 固化到计算机主板上的程序
    • 包括系统设置、自检程序和系统自启动程序
    • BIOS-MBR、BIOS-GPT、PXE
  • UEFI(统一可扩展固件接口)
    • 接口标准
    • 在所有平台上一致的操作系统启动服务

计算机体系结构概述

CPU加电之后第一条指令在哪?

CS:IP = 0xf000:fff0.
(CS: 代码段寄存器;IP:指令寄存器)
系统处于实模式
PC = 16 * CS + IP
20位地址空间:1MB

BOIS启动固件

  • 基本输入输出的程序
  • 系统设置信息
  • 开机自检程序
  • 系统自启动程序等

BOIS

  • 将加载程序从磁盘的引导区(512字节)加载到0x7c00.
  • 跳转到 CS:IP = 0000:7c00

加载程序

  • 将操作系统的代码和数据从硬盘加载到内存中.
  • 跳转到操作系统的起始位置

内存位置图

内存位置图

BOIS系统调用

  • BOIS以中断调用的方式提供了基本的I/O功能
    • INT 10h:字符显示
    • INT 13h:磁盘扇区读写
    • INT 15h:检测内存大小
    • INT 16h:键盘输入
  • 只能在x86的实模式下访问

了解ucore编程方法和通用数据结构

面向对象编程方法

  • ucore主要基于C语言设计,采用了一定的面向对象编程方法。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    /lab2/kern/mm/pmm.h
    -------------------
    struct pmm_manager {
    const char *name;
    void (*init)(void);

    void (*init_memmap)(struct Page *base, size_t n);

    struct Page *(*alloc_pages)(size_t n);
    void (*free_pages)(struct Page *base, size_t n);
    size_t (*nr_free_pages)(void);
    void (*check)(void);
    };

通用数据结构

  • 双向循环链表

    1
    2
    3
    4
    5
    typedef struct foo {
    ElemType data;
    struct foo *prev;
    struct foo *next;
    } foo_t;
  • uCore的双向链表结构定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    struct list_entry {
    struct list_entry *prev,*next;
    };

    typedef struct {
    list_entry_t free_list;
    unsigned int nr_free;
    } free_area_t;

    struct Page {
    atomic_t ref;
    .......
    list_entry_t page_link;
    }
  • 链表操作函数

    1
    2
    3
    list_init(list_entry_t *elm)
    list_add_after和list_add_before
    list_del(list_entry_t *listelm)
  • 访问链表节点所在的宿主数据结构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    free_area_t free_data;
    list_entry_t *le = &free_area.free_list;
    while((le=list_next(le)) != &free_area.free_list) {
    struct Page *p = le2page(le, page_link);
    ......
    }

    #define le2page(le, member) to_struct((le), struct Page, member)
    #define to_struct(ptr, type, member) ((type *)((char *)(ptr) - offsetof(type, member)))
    #define offsetof(type, member) ((size_t)(&((type *)0)->member))

了解x86-32硬件-寄存器

08386的寄存器可以分为8组

  1. 通用寄存器
  2. 段寄存器
  3. 指令指针寄存器
  4. 标志寄存器
  5. 控制寄存器
  6. 系统地址寄存器
  7. 调试寄存器
  8. 测试寄存器

通用寄存器

  1. EAX: 累加器
  2. EBX: 基址寄存器
  3. ECX: 计数器
  4. EDX: 数据寄存器
  5. ESI: 源地址指针寄存器
  6. EDI: 目的地指针寄存器
  7. EBP: 基址指针寄存器
  8. ESP: 堆栈指针寄存器

段寄存器

  1. CS: 代码段(Code Segment)
  2. DS: 数据段(Data Segment)
  3. ES: 附加数据段(Extra Segment)
  4. SS: 堆栈段(Stack Segment)
  5. FS: 附加段
  6. GS: 附加段

指令寄存器和标志寄存器

  • EIP: 指令寄存器

    EIP的低16位就是8086的IP,它存储的是下一条要执行指令的内存地址,在分段地址转换中,表示指令的段内偏移地址。

  • EFLAGS: 标志寄存器

    IF(Interrupt Flag):中断标志位,由CLI,STI两条指令来控制;设置IF使CPU可识别外部(可屏蔽)中断请求。复位IF则禁止中断。IF对不可屏蔽外部中断和故障中断的识别没有任何作用。

    CF,PF,ZF,…