From 88ec7f1be2ff2078b5ac4a4be2767606f42ac583 Mon Sep 17 00:00:00 2001 From: catfood Date: Tue, 13 Dec 2022 13:29:17 +0800 Subject: [PATCH] init commit --- .gitignore | 2 + Makefile | 182 ++++ README.md | 67 ++ boot/Makefrag | 16 + boot/floppy/boot.asm | 288 ++++++ boot/floppy/include/fat12hdr.inc | 43 + boot/floppy/include/load.inc | 34 + boot/floppy/include/pm.inc | 322 ++++++ boot/floppy/loader.asm | 1061 ++++++++++++++++++++ boot/grub/boot.asm | 253 +++++ boot/grub/grub.cfg | 8 + boot/grub/include/fat32.inc | 28 + boot/grub/include/lib.inc | 170 ++++ boot/grub/include/loader.inc | 61 ++ boot/grub/include/pm.inc | 318 ++++++ boot/grub/loader.asm | 1064 ++++++++++++++++++++ boot/mbr/boot.asm | 543 +++++++++++ boot/mbr/grub/grub.cfg | 8 + boot/mbr/include/fat32.inc | 61 ++ boot/mbr/include/lib.inc | 170 ++++ boot/mbr/include/loader.inc | 70 ++ boot/mbr/include/pm.inc | 318 ++++++ boot/mbr/loader.asm | 1013 +++++++++++++++++++ boot/mbr/mbr.asm | 261 +++++ build_img.sh | 24 + fs_flags/Makefrag | 11 + fs_flags/fat32_flag.asm | 6 + fs_flags/orange_flag.asm | 1 + hd/test1.img | Bin 0 -> 51200000 bytes include/assert.h | 16 + include/console.h | 41 + include/const.h | 184 ++++ include/elf.h | 61 ++ include/fat32.h | 189 ++++ include/fs.h | 37 + include/fs_const.h | 120 +++ include/fs_misc.h | 203 ++++ include/global.h | 53 + include/hd.h | 308 ++++++ include/keyboard.h | 147 +++ include/keymap.h | 230 +++++ include/memman.h | 31 + include/proc.h | 176 ++++ include/protect.h | 159 +++ include/proto.h | 141 +++ include/sconst.inc | 52 + include/spinlock.h | 29 + include/stdarg.h | 12 + include/stdio.h | 74 ++ include/string.h | 17 + include/tty.h | 59 ++ include/type.h | 112 +++ include/vfs.h | 91 ++ include/x86.h | 223 +++++ kernel/Makefrag | 74 ++ kernel/assist.c | 257 +++++ kernel/base.c | 783 +++++++++++++++ kernel/clock.c | 45 + kernel/console.c | 327 +++++++ kernel/elf.c | 28 + kernel/exec.c | 248 +++++ kernel/fat32.c | 910 +++++++++++++++++ kernel/file.c | 51 + kernel/fork.c | 238 +++++ kernel/fs.c | 1560 ++++++++++++++++++++++++++++++ kernel/global.c | 85 ++ kernel/hd.c | 672 +++++++++++++ kernel/i8259.c | 75 ++ kernel/kernel.asm | 608 ++++++++++++ kernel/keyboard.c | 443 +++++++++ kernel/ktest.c | 133 +++ kernel/main.c | 472 +++++++++ kernel/memman.c | 405 ++++++++ kernel/pagetbl.c | 375 +++++++ kernel/proc.c | 137 +++ kernel/protect.c | 334 +++++++ kernel/pthread.c | 213 ++++ kernel/spinlock.c | 56 ++ kernel/start.c | 82 ++ kernel/syscall.asm | 361 +++++++ kernel/syscallc.c | 136 +++ kernel/testfunc.c | 53 + kernel/tty.c | 295 ++++++ kernel/vfs.c | 477 +++++++++ lib/Makefrag | 26 + lib/klib.c | 75 ++ lib/kliba.asm | 124 +++ lib/kprintf.c | 34 + lib/printf.c | 33 + lib/printfmt.c | 261 +++++ lib/scanf.c | 72 ++ lib/string.c | 110 +++ mergedep.pl | 86 ++ user/Makefrag | 33 + user/initstart.asm | 19 + user/ptTest.c | 41 + user/shell_0.c | 31 + 实验七-v0.2.pdf | Bin 0 -> 187390 bytes 98 files changed, 20046 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 boot/Makefrag create mode 100644 boot/floppy/boot.asm create mode 100644 boot/floppy/include/fat12hdr.inc create mode 100644 boot/floppy/include/load.inc create mode 100644 boot/floppy/include/pm.inc create mode 100644 boot/floppy/loader.asm create mode 100644 boot/grub/boot.asm create mode 100644 boot/grub/grub.cfg create mode 100644 boot/grub/include/fat32.inc create mode 100644 boot/grub/include/lib.inc create mode 100644 boot/grub/include/loader.inc create mode 100644 boot/grub/include/pm.inc create mode 100644 boot/grub/loader.asm create mode 100644 boot/mbr/boot.asm create mode 100644 boot/mbr/grub/grub.cfg create mode 100644 boot/mbr/include/fat32.inc create mode 100644 boot/mbr/include/lib.inc create mode 100644 boot/mbr/include/loader.inc create mode 100644 boot/mbr/include/pm.inc create mode 100644 boot/mbr/loader.asm create mode 100644 boot/mbr/mbr.asm create mode 100755 build_img.sh create mode 100644 fs_flags/Makefrag create mode 100644 fs_flags/fat32_flag.asm create mode 100644 fs_flags/orange_flag.asm create mode 100644 hd/test1.img create mode 100644 include/assert.h create mode 100644 include/console.h create mode 100644 include/const.h create mode 100644 include/elf.h create mode 100644 include/fat32.h create mode 100644 include/fs.h create mode 100644 include/fs_const.h create mode 100644 include/fs_misc.h create mode 100644 include/global.h create mode 100644 include/hd.h create mode 100644 include/keyboard.h create mode 100644 include/keymap.h create mode 100644 include/memman.h create mode 100644 include/proc.h create mode 100644 include/protect.h create mode 100644 include/proto.h create mode 100644 include/sconst.inc create mode 100644 include/spinlock.h create mode 100644 include/stdarg.h create mode 100644 include/stdio.h create mode 100644 include/string.h create mode 100644 include/tty.h create mode 100644 include/type.h create mode 100644 include/vfs.h create mode 100644 include/x86.h create mode 100644 kernel/Makefrag create mode 100644 kernel/assist.c create mode 100644 kernel/base.c create mode 100644 kernel/clock.c create mode 100644 kernel/console.c create mode 100644 kernel/elf.c create mode 100644 kernel/exec.c create mode 100644 kernel/fat32.c create mode 100644 kernel/file.c create mode 100644 kernel/fork.c create mode 100644 kernel/fs.c create mode 100644 kernel/global.c create mode 100644 kernel/hd.c create mode 100644 kernel/i8259.c create mode 100644 kernel/kernel.asm create mode 100644 kernel/keyboard.c create mode 100644 kernel/ktest.c create mode 100644 kernel/main.c create mode 100644 kernel/memman.c create mode 100644 kernel/pagetbl.c create mode 100644 kernel/proc.c create mode 100644 kernel/protect.c create mode 100644 kernel/pthread.c create mode 100644 kernel/spinlock.c create mode 100644 kernel/start.c create mode 100644 kernel/syscall.asm create mode 100644 kernel/syscallc.c create mode 100644 kernel/testfunc.c create mode 100644 kernel/tty.c create mode 100644 kernel/vfs.c create mode 100644 lib/Makefrag create mode 100644 lib/klib.c create mode 100644 lib/kliba.asm create mode 100644 lib/kprintf.c create mode 100644 lib/printf.c create mode 100644 lib/printfmt.c create mode 100644 lib/scanf.c create mode 100644 lib/string.c create mode 100644 mergedep.pl create mode 100644 user/Makefrag create mode 100644 user/initstart.asm create mode 100644 user/ptTest.c create mode 100644 user/shell_0.c create mode 100644 实验七-v0.2.pdf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a079651 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +obj/ +iso/ \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1a64f96 --- /dev/null +++ b/Makefile @@ -0,0 +1,182 @@ +# +# make的主文件 +# + +# 文件夹 +# OBJ用于存放编译出来的可重定位文件 +OBJDIR := obj +# INC用于存放各种头文件(*.h) +INCDIR := include + +# 编译以及日常工具 +CC := gcc +# 汇编器 +AS := nasm +# 静态库编辑器 +AR := ar +# 链接器 +LD := ld +# 复制文件 +OBJCOPY := objcopy +# 反编译 +OBJDUMP := objdump +# 查询可重定位文件符号表 +NM := nm + +DEFS := + +# gcc的相关命令参数 +# $(DEFS) 定义一些可能的参数 +# -O0 0优化,保证程序按照代码语义走而不被优化,方便调试 +# -fno-builtin 静止使用gcc内置函数,具体查手册 +CFLAGS := $(CFLAGS) $(DEFS) -O0 -fno-builtin +# -I 编译时去指定文件夹查找头文件 +# -MD 一个黑科技暂时可以不需要了解,总之是在头文件依赖变动的时候能够及时更新target +CFLAGS += -I $(INCDIR) -MD +# -fno-stack-protector 禁止栈保护(金丝雀保护机制,内核代码扛不住) +CFLAGS += -fno-stack-protector +# -std=gnu99 规定编译的语言规范为gnu99 +CFLAGS += -std=gnu99 +# -fno-pie 不创建动态链接库 +CFLAGS += -fno-pie +# -static 编译静态程序 +# -m32 编译32位程序 +CFLAGS += -static -m32 +# -g 打开gdb调试信息,能够允许gdb的时候调试 +CFLAGS += -g +# 一车的warning,在编译的时候可能会很有用 +CFLAGS += -Wall -Wno-format -Wno-unused -Werror + +# ld链接器的相关命令参数 +# -m elf_i386 链接的格式为i386 +LDFLAGS := -m elf_i386 +# -nostdlib 不链接gcc的标准库,用库只能用命令行的 +LDFLAGS += -nostdlib + +# 获取gcc的库文件(除法取模会用到) +GCC_LIB := $(shell $(CC) $(CFLAGS) -print-libgcc-file-name) + +# 记录每个OBJDIR里存放的每个子文件夹 +OBJDIRS := + +# 保证all是第一个target,这样make的时候会先执行all +# all的依赖会在kern/Makefrag中填充 +all: + +.DELETE_ON_ERROR: + +# xv6黑科技,获取编译命令,如果命令较新则重新编译所有文件 +.PRECIOUS: $(OBJDIR)/.vars.% \ + $(OBJDIR)/kernel/%.o $(OBJDIR)/kernel/%.d \ + $(OBJDIR)/user/%.o $(OBJDIR)/user/%.d \ + $(OBJDIR)/lib/%.o $(OBJDIR)/lib/%.d +$(OBJDIR)/.vars.%: FORCE + @echo "$($*)" | cmp -s $@ || echo "$($*)" > $@ +.PHONY: FORCE + +include boot/Makefrag +include lib/Makefrag +include kernel/Makefrag +include user/Makefrag +include fs_flags/Makefrag + +# FAT32镜像文件 +IMAGE = $(OBJDIR)/a.img +BUILD_IMAGE_SCRIPT = build_img.sh + +# added by mingxuan 2020-9-12 +# Offset of os_boot in hd +# 活动分区所在的扇区号 +# OSBOOT_SEC = 4096 +# 活动分区所在的扇区号对应的字节数 +# OSBOOT_OFFSET = $(OSBOOT_SEC)*512 +OSBOOT_OFFSET = 1048576 +# FAT32规范规定os_boot的前89个字节是FAT32的配置信息 +# OSBOOT_START_OFFSET = OSBOOT_OFFSET + 90 +OSBOOT_START_OFFSET = 1048666 # for test12.img + +# added by mingxuan 2020-10-29 +# Offset of fs in hd +# 文件系统标志所在扇区号 = 文件系统所在分区的第1个扇区 + 1 +# ORANGE_FS_SEC = 6144 + 1 = 6145 +# 文件系统标志所在扇区 = $(ORANGE_FS_SEC)*512 +ORANGE_FS_START_OFFSET = 3146240 +# FAT32_FS_SEC = 53248 + 1 = 53249 +# 文件系统标志所在扇区 = $(ORANGE_FS_SEC)*512 +FAT32_FS_START_OFFSET = 27263488 + +# oranges文件系统在硬盘上的起始扇区 +# PART_START_SECTOR = 92049 +PART_START_SECTOR = 6144 # modified by mingxuan 2020-10-12 + +# 写入硬盘的起始位置 +# INSTALL_PHY_SECTOR = PART_START_SECTOR + 951 # Why is 951 ? +INSTALL_PHY_SECTOR = 7095 # modified by mingxuan 2020-10-12 +# assert(INSTALL_PHY_SECTOR > PART_START_SECTOR) + +# 写入硬盘的文件大小 +INSTALL_NR_SECTORS = 1000 + +INSTALL_START_SECTOR = $(shell echo $$(($(INSTALL_PHY_SECTOR)-$(PART_START_SECTOR)))) +SUPER_BLOCK_ADDR = $(shell echo $$((($(PART_START_SECTOR)+1)*512))) + +INSTALL_TYPE = INSTALL_TAR + +INSTALL_FILENAME = app.tar + +$(IMAGE): $(OBJDIR)/boot/mbr.bin \ + $(OBJDIR)/boot/boot.bin \ + $(OBJDIR)/boot/loader.bin \ + $(OBJDIR)/kernel/kernel.bin \ + $(BUILD_IMAGE_SCRIPT) \ + $(OBJDIR)/user/$(USER_TAR) \ + $(FS_FLAG_OBJFILES) + @./build_img.sh $@ $(OBJDIR) $(OSBOOT_START_OFFSET) + @dd if=$(OBJDIR)/user/$(USER_TAR) of=$@ bs=512 count=$(INSTALL_NR_SECTORS) seek=$(INSTALL_PHY_SECTOR) conv=notrunc + @dd if=$(OBJDIR)/fs_flags/orange_flag.bin of=$@ bs=1 count=1 seek=$(ORANGE_FS_START_OFFSET) conv=notrunc + @dd if=$(OBJDIR)/fs_flags/fat32_flag.bin of=$@ bs=1 count=11 seek=$(FAT32_FS_START_OFFSET) conv=notrunc + +all: $(IMAGE) + +clean: + @rm -rf $(OBJDIR) + +run: $(IMAGE) + @qemu-system-i386 \ + -boot order=a \ + -drive file=$<,format=raw \ + +gdb: $(IMAGE) + @qemu-system-i386 \ + -boot order=a \ + -drive file=$<,format=raw \ + -s -S \ + +gdb-no-graphic: $(IMAGE) + @qemu-system-i386 \ + -nographic \ + -boot order=a \ + -drive file=$<,format=raw \ + -s -S \ + +# 调试的内核代码elf +KERNDBG := $(OBJDIR)/kernel/kernel.dbg + +monitor: $(IMAGE) + @gdb \ + -ex 'set confirm off' \ + -ex 'target remote localhost:1234' \ + -ex 'file $(KERNDBG)' + +disassemble: $(IMAGE) + @$(OBJDUMP) -S $(KERNDBG) | less + +# 黑科技时间,获取每个.c对应的头文件依赖 +# 挺难整明白的,不建议一开始整明白,反正从xv6上抄的,不明觉厉 +$(OBJDIR)/.deps: $(foreach dir, $(OBJDIRS), $(wildcard $(OBJDIR)/$(dir)/*.d)) + @mkdir -p $(@D) + @perl mergedep.pl $@ $^ + +-include $(OBJDIR)/.deps + +.PHONY: all clean run gdb gdb-no-graphic monitor disassemble \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..66ba9f2 --- /dev/null +++ b/README.md @@ -0,0 +1,67 @@ +# MiniOS简介 +--- +MiniOS是一个面向操作系统开发学习者的、微型的操作系统内核,可以运行在32位x86架构的CPU上。MiniOS专注于对操作系统开发中的核心概念和基础原理的学习与研究,并基于通用硬件对操作系统中的各基本子系统或模块进行实现。 +流行的[Linux](https://github.com/torvalds/linux)、 [FreeBSD](https://github.com/freebsd/freebsd) 等操作系统内核固然很好,然而它们却并不适合内核开发的初学者。一方面,这些操作系统内核已经发展了很多年,积累了十分庞大的代码量(发布于2005年的Linux内核早期版本v2.6.12就已经有大约400万行代码),另一方面,因为应用在生产环境中的需要,这些内核代码中包含了大量和操作系统基本原理无关的细节,初学者很难抓到其中的要领。因此,从一个简单的、代码量较少的操作系统内核入手,使用较短的时间熟悉并掌握操作系统内核开发领域的核心概念和基础原理,等把这些基础性知识掌握到一定程度,再投身于Linux等实用内核的开发,对于内核初学者来说是一个比较现实可行的策略。即使不打算从事内核开发,通过一个易于入手的内核学习一些操作系统相关的基础知识,也会有利于写出更健壮、性能更好的应用程序。 +查看MiniOS的[release_notes](https://github.com/doubleXnine/MiniOS/blob/master/release_notes.txt) 可了解MiniOS的当前开发进展。 + +# MiniOS开发工具 +--- +MiniOS主要基于C语言和x86汇编语言开发,使用的开发工具包括: + +* 汇编器[nasm](https://www.nasm.us/) +* C语言编译器gcc +* GNU二进制工具集[Binutils](http://www.gnu.org/software/binutils/) +* 项目构建工具make +* 调试器gdb + +其中,Binutils是一套对二进制文件进行操作的工具集,包括创建静态库的工具ar,从二进制文件中去除符号表以减小文件体积的工具strip等。 + +# 运行MiniOS +--- +MiniOS当前从软盘中启动,启动流程为: +1. BIOS自检完毕后从软盘引导扇区中加载引导程序(boot.bin)至内存,并将控制权交给引导程序。 +2. 引导程序从软盘中读取加载器(loader.bin)至内存,并将控制器交给加载器。 +3. 加载器运行时会从软盘中读取MiniOS内核(kernel.bin)至内存,然后从CPU的实模式进入保护模式,并将控制权交给内核。 +4. MiniOS开始运行。 + +由于MiniOS是一个面向学习者的操作系统内核,因此目前主要运行在虚拟机中,可选的虚拟机有[Bochs](http://bochs.sourceforge.net/)和[Qemu](https://www.qemu.org/)。 + +**在Bochs中运行MiniOS** +1. 安装Bochs,在Ubuntu系统下可以直接执行命令`sudo apt-get install bochs`进行安装,也可以先下载Bochs的源码再进行编译安装,通过源码进行安装可以选择想要的Bochs版本。 +2. 进入MiniOS源目录,执行`tar zxvf misc/80m.img.tar.gz .`,从硬盘镜像压缩包中解压出硬盘镜像。 +3. 在当前目录下执行`bochs`命令启动Bochs虚拟机,Bochs首先会从bochsrc文件中读取配置信息,然后对Bochs给出的运行提示信息进行确认便可让MiniOS在Bochs内运行。 + +**在Qemu中运行MiniOS** +1. 按照Qemu,在Ubuntu系统下可以直接执行命令`sudo apt-get install qemu-system-x86`进行按照,也可以下载Qemu的源代码进行编译安装。 +2. 进入MiniOS源目录,执行`tar zxvf misc/80m.img.tar.gz .`,从硬盘镜像压缩包中解压出硬盘镜像。 +3. 在当前目录下执行`./launch-qemu.sh`命令启动Qemu虚拟机,之后MiniOS将直接在Qemu内开始运行。Qemu虚拟机没有使用像bochsrc一样的配置文件,配置信息是通过命令行选项指定的,脚本launch-qemu.sh中包含了当前使用的配置选项。 + +# 调试MiniOS +通过使用Bochs或Qemu中自带的调试功能可以对MiniOS进行汇编语言级的调试,但由于汇编程序比较冗长且难以阅读,这种调试方式使用起来不太方便。幸运的是,Bochs和Qemu中都内置了gdb支持,通过和gdb提供的远程调试功能配合,可以对MiniOS进行C源码级的调试。 + +**使用Bochs+gdb调试MiniOS** +1. 从源代码编译安装Bochs,并在编译时打开gdb支持选项。然后在Bochs配置文件中添加gdb配置信息,MiniOS源目录下的bochsrc-gdb文件中已经包含了所需的配置选项。 +2. 在MiniOS源目录下执行`./launch-bochs-gdb.sh`,所运行的shell脚本会在一个新的终端窗口中运行gdb,并加载debug版的内核二进制文件。 +3. 在gdb命令界面执行命令`target remote :2345`和Bochs建立连接。 +4. 用gdb像调试本地程序一样对MiniOS进行调试。 + +**使用Qemu+gdb调试MiniOS** +1. 在启动Qemu时添加命令行选项以启用gdb支持,MiniOS源目录下的脚本文件launch-qemu-gdb.sh中已经添加了所需的配置选项。 +2. 在MiniOS源目录下执行`./launch-bochs-gdb.sh`,所运行的shell脚本会在一个新的终端窗口中运行gdb,并加载debug版的内核二进制文件。 +3. 在gdb命令界面执行命令`target remote :1234`和Qemu建立连接。 +4. 用gdb像调试本地程序一样对MiniOS进行调试。 + +# 常用MiniOS构建选项 +``` +# 编译MiniOS内核和用户程序init,并写入到软盘镜像a.img中 +make image +# 清除所有.o目标文件 +make clean +# 清除所有.o目标文件和可执行文件 +make realclean +``` + +# 参考资料 +* [Orange's](https://github.com/yyu/Oranges) ,由于渊开发的一个微型操作系统,在《一个操作系统的实现》这本书中讲述了Orange's的开发过程。MiniOS是基于Orange's进行开发的。 +* [xv6](https://pdos.csail.mit.edu/6.828/2014/xv6.html) , 由MIT开发的一个用于教学的微型操作系统,xv6由Unix V6改写而来,被应用在MIT的操作系统课程6.828: Operating System Engineering中。 +* [Minix](http://www.minix3.org/) ,最初由Andrew S. Tanenbaum教授开发的一个微内核操作系统,Linus在开发早期的Linux的时候从Minix处继承了很多特性,于渊在开发Orange's的时候也多次借鉴了Minix。 \ No newline at end of file diff --git a/boot/Makefrag b/boot/Makefrag new file mode 100644 index 0000000..ad5d941 --- /dev/null +++ b/boot/Makefrag @@ -0,0 +1,16 @@ +OBJDIRS += boot + +$(OBJDIR)/boot/mbr.bin: boot/mbr/mbr.asm + @echo + as bin $< + @mkdir -p $(@D) + @$(AS) -I ./boot/mbr/include -o $@ $< + +$(OBJDIR)/boot/boot.bin: boot/mbr/boot.asm + @echo + as bin $< + @mkdir -p $(@D) + @$(AS) -I ./boot/mbr/include -o $@ $< + +$(OBJDIR)/boot/loader.bin: boot/mbr/loader.asm + @echo + as bin $< + @mkdir -p $(@D) + @$(AS) -I ./boot/mbr/include -o $@ $< \ No newline at end of file diff --git a/boot/floppy/boot.asm b/boot/floppy/boot.asm new file mode 100644 index 0000000..789912c --- /dev/null +++ b/boot/floppy/boot.asm @@ -0,0 +1,288 @@ + +; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +; boot.asm +; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +; Forrest Yu, 2005 +; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + +;%define _BOOT_DEBUG_ ; 做 Boot Sector 时一定将此行注释掉!将此行打开后用 nasm Boot.asm -o Boot.com 做成一个.COM文件易于调试 + +%ifdef _BOOT_DEBUG_ + org 0100h ; 调试状态, 做成 .COM 文件, 可调试 +%else + org 07c00h ; Boot 状态, Bios 将把 Boot Sector 加载到 0:7C00 处并开始执行 +%endif + +;================================================================================================ +%ifdef _BOOT_DEBUG_ +BaseOfStack equ 0100h ; 调试状态下堆栈基地址(栈底, 从这个位置向低地址生长) +%else +BaseOfStack equ 07c00h ; Boot状态下堆栈基地址(栈底, 从这个位置向低地址生长) +%endif + +%include "load.inc" +;================================================================================================ + + jmp short LABEL_START ; Start to boot. + nop ; 这个 nop 不可少 + +; 下面是 FAT12 磁盘的头, 之所以包含它是因为下面用到了磁盘的一些信息 +%include "fat12hdr.inc" + +LABEL_START: + mov ax, cs + mov ds, ax + mov es, ax + mov ss, ax + mov sp, BaseOfStack + + ; 清屏 + mov ax, 0600h ; AH = 6, AL = 0h + mov bx, 0700h ; 黑底白字(BL = 07h) + mov cx, 0 ; 左上角: (0, 0) + mov dx, 0184fh ; 右下角: (80, 50) + int 10h ; int 10h + + mov dh, 0 ; "Booting " + call DispStr ; 显示字符串 + + xor ah, ah ; ┓ + xor dl, dl ; ┣ 软驱复位 + int 13h ; ┛ + +; 下面在 A 盘的根目录寻找 LOADER.BIN + mov word [wSectorNo], SectorNoOfRootDirectory +LABEL_SEARCH_IN_ROOT_DIR_BEGIN: + cmp word [wRootDirSizeForLoop], 0 ; ┓ + jz LABEL_NO_LOADERBIN ; ┣ 判断根目录区是不是已经读完 + dec word [wRootDirSizeForLoop] ; ┛ 如果读完表示没有找到 LOADER.BIN + mov ax, BaseOfLoader + mov es, ax ; es <- BaseOfLoader + mov bx, OffsetOfLoader ; bx <- OffsetOfLoader 于是, es:bx = BaseOfLoader:OffsetOfLoader + mov ax, [wSectorNo] ; ax <- Root Directory 中的某 Sector 号 + mov cl, 1 + call ReadSector + + mov si, LoaderFileName ; ds:si -> "LOADER BIN" + mov di, OffsetOfLoader ; es:di -> BaseOfLoader:0100 = BaseOfLoader*10h+100 + cld + mov dx, 10h +LABEL_SEARCH_FOR_LOADERBIN: + cmp dx, 0 ; ┓循环次数控制, + jz LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR ; ┣如果已经读完了一个 Sector, + dec dx ; ┛就跳到下一个 Sector + mov cx, 11 +LABEL_CMP_FILENAME: + cmp cx, 0 + jz LABEL_FILENAME_FOUND ; 如果比较了 11 个字符都相等, 表示找到 +dec cx + lodsb ; ds:si -> al + cmp al, byte [es:di] + jz LABEL_GO_ON + jmp LABEL_DIFFERENT ; 只要发现不一样的字符就表明本 DirectoryEntry 不是 +; 我们要找的 LOADER.BIN +LABEL_GO_ON: + inc di + jmp LABEL_CMP_FILENAME ; 继续循环 + +LABEL_DIFFERENT: + and di, 0FFE0h ; else ┓ di &= E0 为了让它指向本条目开头 + add di, 20h ; ┃ + mov si, LoaderFileName ; ┣ di += 20h 下一个目录条目 + jmp LABEL_SEARCH_FOR_LOADERBIN; ┛ + +LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR: + add word [wSectorNo], 1 + jmp LABEL_SEARCH_IN_ROOT_DIR_BEGIN + +LABEL_NO_LOADERBIN: + mov dh, 2 ; "No LOADER." + call DispStr ; 显示字符串 +%ifdef _BOOT_DEBUG_ + mov ax, 4c00h ; ┓ + int 21h ; ┛没有找到 LOADER.BIN, 回到 DOS +%else + jmp $ ; 没有找到 LOADER.BIN, 死循环在这里 +%endif + +LABEL_FILENAME_FOUND: ; 找到 LOADER.BIN 后便来到这里继续 + mov ax, RootDirSectors + and di, 0FFE0h ; di -> 当前条目的开始 + add di, 01Ah ; di -> 首 Sector + mov cx, word [es:di] + push cx ; 保存此 Sector 在 FAT 中的序号 + add cx, ax + add cx, DeltaSectorNo ; 这句完成时 cl 里面变成 LOADER.BIN 的起始扇区号 (从 0 开始数的序号) + mov ax, BaseOfLoader + mov es, ax ; es <- BaseOfLoader + mov bx, OffsetOfLoader ; bx <- OffsetOfLoader 于是, es:bx = BaseOfLoader:OffsetOfLoader = BaseOfLoader * 10h + OffsetOfLoader + mov ax, cx ; ax <- Sector 号 + +LABEL_GOON_LOADING_FILE: + push ax ; ┓ + push bx ; ┃ + mov ah, 0Eh ; ┃ 每读一个扇区就在 "Booting " 后面打一个点, 形成这样的效果: + mov al, '.' ; ┃ + mov bl, 0Fh ; ┃ Booting ...... + int 10h ; ┃ + pop bx ; ┃ + pop ax ; ┛ + + mov cl, 1 + call ReadSector + pop ax ; 取出此 Sector 在 FAT 中的序号 + call GetFATEntry + cmp ax, 0FFFh + jz LABEL_FILE_LOADED + push ax ; 保存 Sector 在 FAT 中的序号 + mov dx, RootDirSectors + add ax, dx + add ax, DeltaSectorNo + add bx, [BPB_BytsPerSec] + jmp LABEL_GOON_LOADING_FILE +LABEL_FILE_LOADED: + + mov dh, 1 ; "Ready." + call DispStr ; 显示字符串 + +; ***************************************************************************************************** + jmp BaseOfLoader:OffsetOfLoader ; 这一句正式跳转到已加载到内存中的 LOADER.BIN 的开始处 + ; 开始执行 LOADER.BIN 的代码 + ; Boot Sector 的使命到此结束 +; ***************************************************************************************************** + + + +;============================================================================ +;变量 +;---------------------------------------------------------------------------- +wRootDirSizeForLoop dw RootDirSectors ; Root Directory 占用的扇区数, 在循环中会递减至零. +wSectorNo dw 0 ; 要读取的扇区号 +bOdd db 0 ; 奇数还是偶数 + +;============================================================================ +;字符串 +;---------------------------------------------------------------------------- +LoaderFileName db "LOADER BIN", 0 ; LOADER.BIN 之文件名 +; 为简化代码, 下面每个字符串的长度均为 MessageLength +MessageLength equ 9 +BootMessage: db "Booting "; 9字节, 不够则用空格补齐. 序号 0 +Message1 db "Ready. "; 9字节, 不够则用空格补齐. 序号 1 +Message2 db "No LOADER"; 9字节, 不够则用空格补齐. 序号 2 +;============================================================================ + + +;---------------------------------------------------------------------------- +; 函数名: DispStr +;---------------------------------------------------------------------------- +; 作用: +; 显示一个字符串, 函数开始时 dh 中应该是字符串序号(0-based) +DispStr: + mov ax, MessageLength + mul dh + add ax, BootMessage + mov bp, ax ; ┓ + mov ax, ds ; ┣ ES:BP = 串地址 + mov es, ax ; ┛ + mov cx, MessageLength ; CX = 串长度 + mov ax, 01301h ; AH = 13, AL = 01h + mov bx, 0007h ; 页号为0(BH = 0) 黑底白字(BL = 07h) + mov dl, 0 + int 10h ; int 10h + ret + + +;---------------------------------------------------------------------------- +; 函数名: ReadSector +;---------------------------------------------------------------------------- +; 作用: +; 从第 ax 个 Sector 开始, 将 cl 个 Sector 读入 es:bx 中 +ReadSector: + ; ----------------------------------------------------------------------- + ; 怎样由扇区号求扇区在磁盘中的位置 (扇区号 -> 柱面号, 起始扇区, 磁头号) + ; ----------------------------------------------------------------------- + ; 设扇区号为 x + ; ┌ 柱面号 = y >> 1 + ; x ┌ 商 y ┤ + ; -------------- => ┤ └ 磁头号 = y & 1 + ; 每磁道扇区数 │ + ; └ 余 z => 起始扇区号 = z + 1 + push bp + mov bp, sp + sub esp, 2 ; 辟出两个字节的堆栈区域保存要读的扇区数: byte [bp-2] + + mov byte [bp-2], cl + push bx ; 保存 bx + mov bl, [BPB_SecPerTrk] ; bl: 除数 + div bl ; y 在 al 中, z 在 ah 中 + inc ah ; z ++ + mov cl, ah ; cl <- 起始扇区号 + mov dh, al ; dh <- y + shr al, 1 ; y >> 1 (其实是 y/BPB_NumHeads, 这里BPB_NumHeads=2) + mov ch, al ; ch <- 柱面号 + and dh, 1 ; dh & 1 = 磁头号 + pop bx ; 恢复 bx + ; 至此, "柱面号, 起始扇区, 磁头号" 全部得到 ^^^^^^^^^^^^^^^^^^^^^^^^ + mov dl, [BS_DrvNum] ; 驱动器号 (0 表示 A 盘) +.GoOnReading: + mov ah, 2 ; 读 + mov al, byte [bp-2] ; 读 al 个扇区 + int 13h + jc .GoOnReading ; 如果读取错误 CF 会被置为 1, 这时就不停地读, 直到正确为止 + + add esp, 2 + pop bp + + ret + +;---------------------------------------------------------------------------- +; 函数名: GetFATEntry +;---------------------------------------------------------------------------- +; 作用: +; 找到序号为 ax 的 Sector 在 FAT 中的条目, 结果放在 ax 中 +; 需要注意的是, 中间需要读 FAT 的扇区到 es:bx 处, 所以函数一开始保存了 es 和 bx +GetFATEntry: + push es + push bx + push ax + mov ax, BaseOfLoader ; ┓ + sub ax, 0100h ; ┣ 在 BaseOfLoader 后面留出 4K 空间用于存放 FAT + mov es, ax ; ┛ + pop ax + mov byte [bOdd], 0 + mov bx, 3 + mul bx ; dx:ax = ax * 3 + mov bx, 2 + div bx ; dx:ax / 2 ==> ax <- 商, dx <- 余数 + cmp dx, 0 + jz LABEL_EVEN + mov byte [bOdd], 1 +LABEL_EVEN:;偶数 + xor dx, dx ; 现在 ax 中是 FATEntry 在 FAT 中的偏移量. 下面来计算 FATEntry 在哪个扇区中(FAT占用不止一个扇区) + mov bx, [BPB_BytsPerSec] + div bx ; dx:ax / BPB_BytsPerSec ==> ax <- 商 (FATEntry 所在的扇区相对于 FAT 来说的扇区号) + ; dx <- 余数 (FATEntry 在扇区内的偏移)。 + push dx + mov bx, 0 ; bx <- 0 于是, es:bx = (BaseOfLoader - 100):00 = (BaseOfLoader - 100) * 10h + add ax, SectorNoOfFAT1 ; 此句执行之后的 ax 就是 FATEntry 所在的扇区号 + mov cl, 2 + call ReadSector ; 读取 FATEntry 所在的扇区, 一次读两个, 避免在边界发生错误, 因为一个 FATEntry 可能跨越两个扇区 + pop dx + add bx, dx + mov ax, [es:bx] + cmp byte [bOdd], 1 + jnz LABEL_EVEN_2 + shr ax, 4 +LABEL_EVEN_2: + and ax, 0FFFh + +LABEL_GET_FAT_ENRY_OK: + + pop bx + pop es + ret +;---------------------------------------------------------------------------- + +times 510-($-$$) db 0 ; 填充剩下的空间,使生成的二进制代码恰好为512字节 +dw 0xaa55 ; 结束标志 diff --git a/boot/floppy/include/fat12hdr.inc b/boot/floppy/include/fat12hdr.inc new file mode 100644 index 0000000..b199a1b --- /dev/null +++ b/boot/floppy/include/fat12hdr.inc @@ -0,0 +1,43 @@ + +; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +; fat12hdr.inc +; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +; Forrest Yu, 2005 +; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + +; FAT12 磁盘的头 +; ---------------------------------------------------------------------- +BS_OEMName DB 'ForrestY' ; OEM String, 必须 8 个字节 + +BPB_BytsPerSec DW 512 ; 每扇区字节数 +BPB_SecPerClus DB 1 ; 每簇多少扇区 +BPB_RsvdSecCnt DW 1 ; Boot 记录占用多少扇区 +BPB_NumFATs DB 2 ; 共有多少 FAT 表 +BPB_RootEntCnt DW 224 ; 根目录文件数最大值 +BPB_TotSec16 DW 2880 ; 逻辑扇区总数 +BPB_Media DB 0xF0 ; 媒体描述符 +BPB_FATSz16 DW 9 ; 每FAT扇区数 +BPB_SecPerTrk DW 18 ; 每磁道扇区数 +BPB_NumHeads DW 2 ; 磁头数(面数) +BPB_HiddSec DD 0 ; 隐藏扇区数 +BPB_TotSec32 DD 0 ; 如果 wTotalSectorCount 是 0 由这个值记录扇区数 + +BS_DrvNum DB 0 ; 中断 13 的驱动器号 +BS_Reserved1 DB 0 ; 未使用 +BS_BootSig DB 29h ; 扩展引导标记 (29h) +BS_VolID DD 0 ; 卷序列号 +BS_VolLab DB 'OrangeS0.02'; 卷标, 必须 11 个字节 +BS_FileSysType DB 'FAT12 ' ; 文件系统类型, 必须 8个字节 +;------------------------------------------------------------------------ + + +; ------------------------------------------------------------------------- +; 基于 FAT12 头的一些常量定义,如果头信息改变,下面的常量可能也要做相应改变 +; ------------------------------------------------------------------------- +FATSz equ 9 ; BPB_FATSz16 +RootDirSectors equ 14 ; 根目录占用空间: RootDirSectors = ((BPB_RootEntCnt * 32) + (BPB_BytsPerSec – 1)) / BPB_BytsPerSec; 但如果按照此公式代码过长 +SectorNoOfRootDirectory equ 19 ; Root Directory 的第一个扇区号 = BPB_RsvdSecCnt + (BPB_NumFATs * FATSz) +SectorNoOfFAT1 equ 1 ; FAT1 的第一个扇区号 = BPB_RsvdSecCnt +DeltaSectorNo equ 17 ; DeltaSectorNo = BPB_RsvdSecCnt + (BPB_NumFATs * FATSz) - 2 + ; 文件的开始Sector号 = DirEntry中的开始Sector号 + 根目录占用Sector数目 + DeltaSectorNo diff --git a/boot/floppy/include/load.inc b/boot/floppy/include/load.inc new file mode 100644 index 0000000..5a57c98 --- /dev/null +++ b/boot/floppy/include/load.inc @@ -0,0 +1,34 @@ + +; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +; load.inc +; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +; Forrest Yu, 2005 +; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + +BaseOfLoader equ 09000h ; LOADER.BIN 被加载到的位置 ---- 段地址 +OffsetOfLoader equ 0100h ; LOADER.BIN 被加载到的位置 ---- 偏移地址 + +BaseOfLoaderPhyAddr equ BaseOfLoader * 10h ; LOADER.BIN 被加载到的位置 ---- 物理地址 (= BaseOfLoader * 10h) + +;modified by xw, 18/6/12 +; BaseOfKernelFile equ 08000h ; KERNEL.BIN 被加载到的位置 ---- 段地址 +BaseOfKernelFile equ 07000h ; +OffsetOfKernelFile equ 0h ; KERNEL.BIN 被加载到的位置 ---- 偏移地址 + +BaseOfEchoFile equ 07E0h ; KERNEL.BIN 被加载到的位置 ---- 段地址 +OffsetOfEchoFile equ 0h ; KERNEL.BIN 被加载到的位置 ---- 偏移地址 + +BaseOfKernelFilePhyAddr equ BaseOfKernelFile * 10h +BaseOfEchoFilePhyAddr equ BaseOfKernelFile * 10h + + +KernelEntryPointPhyAddr equ 0C0030400h ; 注意:1、必须与 MAKEFILE 中参数 -Ttext 的值相等!! edit by visual 2016.5.10 + ; 2、这是个地址而非仅仅是个偏移,如果 -Ttext 的值为 0x400400,则它的值也应该是 0x400400。 + +PageDirBase equ 200000h ; 页目录开始地址: 2M +PageTblBase equ 201000h ; 页表开始地址: 2M + 4K + +PageTblNumAddr equ 500h;页表数量放在这个位置 delete by visual 2016.4.28 + +FMIBuff equ 007ff000h \ No newline at end of file diff --git a/boot/floppy/include/pm.inc b/boot/floppy/include/pm.inc new file mode 100644 index 0000000..ee4c012 --- /dev/null +++ b/boot/floppy/include/pm.inc @@ -0,0 +1,322 @@ + +; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +; pm.inc +; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +; Forrest Yu, 2005 +; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + +; 描述符图示 + +; 图示一 +; +; ------ ┏━━┳━━┓高地址 +; ┃ 7 ┃ 段 ┃ +; ┣━━┫ ┃ +; 基 +; 字节 7 ┆ ┆ ┆ +; 址 +; ┣━━┫ ② ┃ +; ┃ 0 ┃ ┃ +; ------ ┣━━╋━━┫ +; ┃ 7 ┃ G ┃ +; ┣━━╉──┨ +; ┃ 6 ┃ D ┃ +; ┣━━╉──┨ +; ┃ 5 ┃ 0 ┃ +; ┣━━╉──┨ +; ┃ 4 ┃ AVL┃ +; 字节 6 ┣━━╉──┨ +; ┃ 3 ┃ ┃ +; ┣━━┫ 段 ┃ +; ┃ 2 ┃ 界 ┃ +; ┣━━┫ 限 ┃ +; ┃ 1 ┃ ┃ +; ┣━━┫ ② ┃ +; ┃ 0 ┃ ┃ +; ------ ┣━━╋━━┫ +; ┃ 7 ┃ P ┃ +; ┣━━╉──┨ +; ┃ 6 ┃ ┃ +; ┣━━┫ DPL┃ +; ┃ 5 ┃ ┃ +; ┣━━╉──┨ +; ┃ 4 ┃ S ┃ +; 字节 5 ┣━━╉──┨ +; ┃ 3 ┃ ┃ +; ┣━━┫ T ┃ +; ┃ 2 ┃ Y ┃ +; ┣━━┫ P ┃ +; ┃ 1 ┃ E ┃ +; ┣━━┫ ┃ +; ┃ 0 ┃ ┃ +; ------ ┣━━╋━━┫ +; ┃ 23 ┃ ┃ +; ┣━━┫ ┃ +; ┃ 22 ┃ ┃ +; ┣━━┫ 段 ┃ +; +; 字节 ┆ ┆ 基 ┆ +; 2, 3, 4 +; ┣━━┫ 址 ┃ +; ┃ 1 ┃ ① ┃ +; ┣━━┫ ┃ +; ┃ 0 ┃ ┃ +; ------ ┣━━╋━━┫ +; ┃ 15 ┃ ┃ +; ┣━━┫ ┃ +; ┃ 14 ┃ ┃ +; ┣━━┫ 段 ┃ +; +; 字节 0,1┆ ┆ 界 ┆ +; +; ┣━━┫ 限 ┃ +; ┃ 1 ┃ ① ┃ +; ┣━━┫ ┃ +; ┃ 0 ┃ ┃ +; ------ ┗━━┻━━┛低地址 +; + + +; 图示二 + +; 高地址………………………………………………………………………低地址 + +; | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | +; |7654321076543210765432107654321076543210765432107654321076543210| <- 共 8 字节 +; |--------========--------========--------========--------========| +; ┏━━━┳━━━━━━━┳━━━━━━━━━━━┳━━━━━━━┓ +; ┃31..24┃ (见下图) ┃ 段基址(23..0) ┃ 段界限(15..0)┃ +; ┃ ┃ ┃ ┃ ┃ +; ┃ 基址2┃③│②│ ①┃基址1b│ 基址1a ┃ 段界限1 ┃ +; ┣━━━╋━━━┳━━━╋━━━━━━━━━━━╋━━━━━━━┫ +; ┃ %6 ┃ %5 ┃ %4 ┃ %3 ┃ %2 ┃ %1 ┃ +; ┗━━━┻━━━┻━━━┻━━━┻━━━━━━━┻━━━━━━━┛ +; │ \_________ +; │ \__________________ +; │ \________________________________________________ +; │ \ +; ┏━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┓ +; ┃ 7 ┃ 6 ┃ 5 ┃ 4 ┃ 3 ┃ 2 ┃ 1 ┃ 0 ┃ 7 ┃ 6 ┃ 5 ┃ 4 ┃ 3 ┃ 2 ┃ 1 ┃ 0 ┃ +; ┣━━╋━━╋━━╋━━╋━━┻━━┻━━┻━━╋━━╋━━┻━━╋━━╋━━┻━━┻━━┻━━┫ +; ┃ G ┃ D ┃ 0 ┃ AVL┃ 段界限 2 (19..16) ┃ P ┃ DPL ┃ S ┃ TYPE ┃ +; ┣━━┻━━┻━━┻━━╋━━━━━━━━━━━╋━━┻━━━━━┻━━┻━━━━━━━━━━━┫ +; ┃ ③: 属性 2 ┃ ②: 段界限 2 ┃ ①: 属性1 ┃ +; ┗━━━━━━━━━━━┻━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━┛ +; 高地址 低地址 +; +; + +; 说明: +; +; (1) P: 存在(Present)位。 +; P=1 表示描述符对地址转换是有效的,或者说该描述符所描述的段存在,即在内存中; +; P=0 表示描述符对地址转换无效,即该段不存在。使用该描述符进行内存访问时会引起异常。 +; +; (2) DPL: 表示描述符特权级(Descriptor Privilege level),共2位。它规定了所描述段的特权级,用于特权检查,以决定对该段能否访问。 +; +; (3) S: 说明描述符的类型。 +; 对于存储段描述符而言,S=1,以区别与系统段描述符和门描述符(S=0)。 +; +; (4) TYPE: 说明存储段描述符所描述的存储段的具体属性。 +; +; +; 数据段类型 类型值 说明 +; ---------------------------------- +; 0 只读 +; 1 只读、已访问 +; 2 读/写 +; 3 读/写、已访问 +; 4 只读、向下扩展 +; 5 只读、向下扩展、已访问 +; 6 读/写、向下扩展 +; 7 读/写、向下扩展、已访问 +; +; +; 类型值 说明 +; 代码段类型 ---------------------------------- +; 8 只执行 +; 9 只执行、已访问 +; A 执行/读 +; B 执行/读、已访问 +; C 只执行、一致码段 +; D 只执行、一致码段、已访问 +; E 执行/读、一致码段 +; F 执行/读、一致码段、已访问 +; +; +; 系统段类型 类型编码 说明 +; ---------------------------------- +; 0 <未定义> +; 1 可用286TSS +; 2 LDT +; 3 忙的286TSS +; 4 286调用门 +; 5 任务门 +; 6 286中断门 +; 7 286陷阱门 +; 8 未定义 +; 9 可用386TSS +; A <未定义> +; B 忙的386TSS +; C 386调用门 +; D <未定义> +; E 386中断门 +; F 386陷阱门 +; +; (5) G: 段界限粒度(Granularity)位。 +; G=0 表示界限粒度为字节; +; G=1 表示界限粒度为4K 字节。 +; 注意,界限粒度只对段界限有效,对段基地址无效,段基地址总是以字节为单位。 +; +; (6) D: D位是一个很特殊的位,在描述可执行段、向下扩展数据段或由SS寄存器寻址的段(通常是堆栈段)的三种描述符中的意义各不相同。 +; ⑴ 在描述可执行段的描述符中,D位决定了指令使用的地址及操作数所默认的大小。 +; ① D=1表示默认情况下指令使用32位地址及32位或8位操作数,这样的代码段也称为32位代码段; +; ② D=0 表示默认情况下,使用16位地址及16位或8位操作数,这样的代码段也称为16位代码段,它与80286兼容。可以使用地址大小前缀和操作数大小前缀分别改变默认的地址或操作数的大小。 +; ⑵ 在向下扩展数据段的描述符中,D位决定段的上部边界。 +; ① D=1表示段的上部界限为4G; +; ② D=0表示段的上部界限为64K,这是为了与80286兼容。 +; ⑶ 在描述由SS寄存器寻址的段描述符中,D位决定隐式的堆栈访问指令(如PUSH和POP指令)使用何种堆栈指针寄存器。 +; ① D=1表示使用32位堆栈指针寄存器ESP; +; ② D=0表示使用16位堆栈指针寄存器SP,这与80286兼容。 +; +; (7) AVL: 软件可利用位。80386对该位的使用未左规定,Intel公司也保证今后开发生产的处理器只要与80386兼容,就不会对该位的使用做任何定义或规定。 +; + + +;---------------------------------------------------------------------------- +; 描述符类型值说明 +; 其中: +; DA_ : Descriptor Attribute +; D : 数据段 +; C : 代码段 +; S : 系统段 +; R : 只读 +; RW : 读写 +; A : 已访问 +; 其它 : 可按照字面意思理解 +;---------------------------------------------------------------------------- +DA_32 EQU 4000h ; 32 位段 +DA_LIMIT_4K EQU 8000h ; 段界限粒度为 4K 字节 + +DA_DPL0 EQU 00h ; DPL = 0 +DA_DPL1 EQU 20h ; DPL = 1 +DA_DPL2 EQU 40h ; DPL = 2 +DA_DPL3 EQU 60h ; DPL = 3 +;---------------------------------------------------------------------------- +; 存储段描述符类型值说明 +;---------------------------------------------------------------------------- +DA_DR EQU 90h ; 存在的只读数据段类型值 +DA_DRW EQU 92h ; 存在的可读写数据段属性值 +DA_DRWA EQU 93h ; 存在的已访问可读写数据段类型值 +DA_C EQU 98h ; 存在的只执行代码段属性值 +DA_CR EQU 9Ah ; 存在的可执行可读代码段属性值 +DA_CCO EQU 9Ch ; 存在的只执行一致代码段属性值 +DA_CCOR EQU 9Eh ; 存在的可执行可读一致代码段属性值 +;---------------------------------------------------------------------------- +; 系统段描述符类型值说明 +;---------------------------------------------------------------------------- +DA_LDT EQU 82h ; 局部描述符表段类型值 +DA_TaskGate EQU 85h ; 任务门类型值 +DA_386TSS EQU 89h ; 可用 386 任务状态段类型值 +DA_386CGate EQU 8Ch ; 386 调用门类型值 +DA_386IGate EQU 8Eh ; 386 中断门类型值 +DA_386TGate EQU 8Fh ; 386 陷阱门类型值 +;---------------------------------------------------------------------------- + + +; 选择子图示: +; ┏━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┓ +; ┃ 15 ┃ 14 ┃ 13 ┃ 12 ┃ 11 ┃ 10 ┃ 9 ┃ 8 ┃ 7 ┃ 6 ┃ 5 ┃ 4 ┃ 3 ┃ 2 ┃ 1 ┃ 0 ┃ +; ┣━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━╋━━╋━━┻━━┫ +; ┃ 描述符索引 ┃ TI ┃ RPL ┃ +; ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━┻━━━━━┛ +; +; RPL(Requested Privilege Level): 请求特权级,用于特权检查。 +; +; TI(Table Indicator): 引用描述符表指示位 +; TI=0 指示从全局描述符表GDT中读取描述符; +; TI=1 指示从局部描述符表LDT中读取描述符。 +; + +;---------------------------------------------------------------------------- +; 选择子类型值说明 +; 其中: +; SA_ : Selector Attribute + +SA_RPL0 EQU 0 ; ┓ +SA_RPL1 EQU 1 ; ┣ RPL +SA_RPL2 EQU 2 ; ┃ +SA_RPL3 EQU 3 ; ┛ + +SA_TIG EQU 0 ; ┓TI +SA_TIL EQU 4 ; ┛ +;---------------------------------------------------------------------------- + + +;---------------------------------------------------------------------------- +; 分页机制使用的常量说明 +;---------------------------------------------------------------------------- +PG_P EQU 1 ; 页存在属性位 +PG_RWR EQU 0 ; R/W 属性位值, 读/执行 +PG_RWW EQU 2 ; R/W 属性位值, 读/写/执行 +PG_USS EQU 0 ; U/S 属性位值, 系统级 +PG_USU EQU 4 ; U/S 属性位值, 用户级 +;---------------------------------------------------------------------------- + + +; ========================================= +; FLAGS - Intel 8086 Family Flags Register +; ========================================= +; +; |11|10|F|E|D|C|B|A|9|8|7|6|5|4|3|2|1|0| +; | | | | | | | | | | | | | | | | | '--- CF……Carry Flag +; | | | | | | | | | | | | | | | | '--- 1 +; | | | | | | | | | | | | | | | '--- PF……Parity Flag +; | | | | | | | | | | | | | | '--- 0 +; | | | | | | | | | | | | | '--- AF……Auxiliary Flag +; | | | | | | | | | | | | '--- 0 +; | | | | | | | | | | | '--- ZF……Zero Flag +; | | | | | | | | | | '--- SF……Sign Flag +; | | | | | | | | | '--- TF……Trap Flag (Single Step) +; | | | | | | | | '--- IF……Interrupt Flag +; | | | | | | | '--- DF……Direction Flag +; | | | | | | '--- OF……Overflow flag +; | | | | '----- IOPL……I/O Privilege Level (286+ only) +; | | | '----- NT……Nested Task Flag (286+ only) +; | | '----- 0 +; | '----- RF……Resume Flag (386+ only) +; '------ VM……Virtual Mode Flag (386+ only) +; +; 注: see PUSHF POPF STI CLI STD CLD +; + + +; 宏 ------------------------------------------------------------------------------------------------------ +; +; 描述符 +; usage: Descriptor Base, Limit, Attr +; Base: dd +; Limit: dd (low 20 bits available) +; Attr: dw (lower 4 bits of higher byte are always 0) +%macro Descriptor 3 + dw %2 & 0FFFFh ; 段界限 1 (2 字节) + dw %1 & 0FFFFh ; 段基址 1 (2 字节) + db (%1 >> 16) & 0FFh ; 段基址 2 (1 字节) + dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 属性 1 + 段界限 2 + 属性 2 (2 字节) + db (%1 >> 24) & 0FFh ; 段基址 3 (1 字节) +%endmacro ; 共 8 字节 +; +; 门 +; usage: Gate Selector, Offset, DCount, Attr +; Selector: dw +; Offset: dd +; DCount: db +; Attr: db +%macro Gate 4 + dw (%2 & 0FFFFh) ; 偏移 1 (2 字节) + dw %1 ; 选择子 (2 字节) + dw (%3 & 1Fh) | ((%4 << 8) & 0FF00h) ; 属性 (2 字节) + dw ((%2 >> 16) & 0FFFFh) ; 偏移 2 (2 字节) +%endmacro ; 共 8 字节 +; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/boot/floppy/loader.asm b/boot/floppy/loader.asm new file mode 100644 index 0000000..f25167c --- /dev/null +++ b/boot/floppy/loader.asm @@ -0,0 +1,1061 @@ + +; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +; loader.asm +; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +; Forrest Yu, 2005 +; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + +org 0100h + + jmp LABEL_START ; Start + +; 下面是 FAT12 磁盘的头, 之所以包含它是因为下面用到了磁盘的一些信息 +%include "fat12hdr.inc" +%include "load.inc" +%include "pm.inc" + + +; GDT ------------------------------------------------------------------------------------------------------------------------------------------------------------ +; 段基址 段界限 , 属性 +LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符 +LABEL_DESC_FLAT_C: Descriptor 0, 0fffffh, DA_CR | DA_32 | DA_LIMIT_4K ; 0 ~ 4G +LABEL_DESC_FLAT_RW: Descriptor 0, 0fffffh, DA_DRW | DA_32 | DA_LIMIT_4K ; 0 ~ 4G +LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW | DA_DPL3 ; 显存首地址 +; GDT ------------------------------------------------------------------------------------------------------------------------------------------------------------ + +GdtLen equ $ - LABEL_GDT +GdtPtr dw GdtLen - 1 ; 段界限 + dd BaseOfLoaderPhyAddr + LABEL_GDT ; 基地址 + +; GDT 选择子 ---------------------------------------------------------------------------------- +SelectorFlatC equ LABEL_DESC_FLAT_C - LABEL_GDT +SelectorFlatRW equ LABEL_DESC_FLAT_RW - LABEL_GDT +SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT + SA_RPL3 +; GDT 选择子 ---------------------------------------------------------------------------------- + + +BaseOfStack equ 0100h + + +LABEL_START: ; <--- 从这里开始 ************* + mov ax, cs + mov ds, ax + mov es, ax + mov ss, ax + mov sp, BaseOfStack + + mov dh, 0 ; "Loading " + call DispStrRealMode ; 显示字符串 + + ; 得到内存数 + mov ebx, 0 ; ebx = 后续值, 开始时需为 0 + mov di, _MemChkBuf ; es:di 指向一个地址范围描述符结构(Address Range Descriptor Structure) +.MemChkLoop: + mov eax, 0E820h ; eax = 0000E820h + mov ecx, 20 ; ecx = 地址范围描述符结构的大小 + mov edx, 0534D4150h ; edx = 'SMAP' + int 15h ; int 15h + jc .MemChkFail + add di, 20 + inc dword [_dwMCRNumber] ; dwMCRNumber = ARDS 的个数 + cmp ebx, 0 + jne .MemChkLoop + jmp .MemChkOK +.MemChkFail: + mov dword [_dwMCRNumber], 0 +.MemChkOK: + + ; 下面在 A 盘的根目录寻找 KERNEL.BIN + mov word [wSectorNo], SectorNoOfRootDirectory + xor ah, ah ; ┓ + xor dl, dl ; ┣ 软驱复位 + int 13h ; ┛ +LABEL_SEARCH_IN_ROOT_DIR_BEGIN: + cmp word [wRootDirSizeForLoop], 0 ; ┓ + jz LABEL_NO_KERNELBIN ; ┣ 判断根目录区是不是已经读完, 如果读完表示没有找到 KERNEL.BIN + dec word [wRootDirSizeForLoop] ; ┛ + mov ax, BaseOfKernelFile + mov es, ax ; es <- BaseOfKernelFile + mov bx, OffsetOfKernelFile ; bx <- OffsetOfKernelFile 于是, es:bx = BaseOfKernelFile:OffsetOfKernelFile = BaseOfKernelFile * 10h + OffsetOfKernelFile + mov ax, [wSectorNo] ; ax <- Root Directory 中的某 Sector 号 + mov cl, 1 + call ReadSector + + mov si, KernelFileName ; ds:si -> "KERNEL BIN" + mov di, OffsetOfKernelFile ; es:di -> BaseOfKernelFile:???? = BaseOfKernelFile*10h+???? + cld + mov dx, 10h +LABEL_SEARCH_FOR_KERNELBIN: + cmp dx, 0 ; ┓ + jz LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR ; ┣ 循环次数控制, 如果已经读完了一个 Sector, 就跳到下一个 Sector + dec dx ; ┛ + mov cx, 11 +LABEL_CMP_FILENAME: + cmp cx, 0 ; ┓ + jz LABEL_FILENAME_FOUND ; ┣ 循环次数控制, 如果比较了 11 个字符都相等, 表示找到 + dec cx ; ┛ + lodsb ; ds:si -> al + cmp al, byte [es:di] ; if al == es:di + jz LABEL_GO_ON + jmp LABEL_DIFFERENT +LABEL_GO_ON: + inc di + jmp LABEL_CMP_FILENAME ; 继续循环 + +LABEL_DIFFERENT: + and di, 0FFE0h ; else┓ 这时di的值不知道是什么, di &= e0 为了让它是 20h 的倍数 + add di, 20h ; ┃ + mov si, KernelFileName ; ┣ di += 20h 下一个目录条目 + jmp LABEL_SEARCH_FOR_KERNELBIN; ┛ + +LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR: + add word [wSectorNo], 1 + jmp LABEL_SEARCH_IN_ROOT_DIR_BEGIN + +LABEL_NO_KERNELBIN: + mov dh, 2 ; "No KERNEL." + call DispStrRealMode ; 显示字符串 + jmp $ ; 没有找到 KERNEL.BIN, 死循环在这里 + +LABEL_FILENAME_FOUND: ; 找到 KERNEL.BIN 后便来到这里继续 + mov ax, RootDirSectors + and di, 0FFF0h ; di -> 当前条目的开始 + + push eax + mov eax, [es : di + 01Ch] ; ┓ + mov dword [dwKernelSize], eax ; ┛保存 KERNEL.BIN 文件大小 + pop eax + + add di, 01Ah ; di -> 首 Sector + mov cx, word [es:di] + push cx ; 保存此 Sector 在 FAT 中的序号 + add cx, ax + add cx, DeltaSectorNo ; 这时 cl 里面是 LOADER.BIN 的起始扇区号 (从 0 开始数的序号) + mov ax, BaseOfKernelFile + mov es, ax ; es <- BaseOfKernelFile + mov bx, OffsetOfKernelFile ; bx <- OffsetOfKernelFile 于是, es:bx = BaseOfKernelFile:OffsetOfKernelFile = BaseOfKernelFile * 10h + OffsetOfKernelFile + mov ax, cx ; ax <- Sector 号 + +LABEL_GOON_LOADING_FILE: + push ax ; ┓ + push bx ; ┃ + mov ah, 0Eh ; ┃ 每读一个扇区就在 "Loading " 后面打一个点, 形成这样的效果: + mov al, '.' ; ┃ + mov bl, 0Fh ; ┃ Loading ...... + int 10h ; ┃ + pop bx ; ┃ + pop ax ; ┛ + + mov cl, 1 + call ReadSector + pop ax ; 取出此 Sector 在 FAT 中的序号 + call GetFATEntry + cmp ax, 0FFFh + jz LABEL_FILE_LOADED + push ax ; 保存 Sector 在 FAT 中的序号 + mov dx, RootDirSectors + add ax, dx + add ax, DeltaSectorNo + add bx, [BPB_BytsPerSec] +;to support the kernel.bin larger than 64KB +;copied from Orange's_9_h, xw, 18/6/12 + jc .1 ;if bx becomes 0, indicates that the kernel is larger than 64KB + jmp .2 +.1: + push ax ;es += 0x1000, points to next segment + mov ax, es + add ax, 1000h + mov es, ax + pop ax +.2: +;~xw + jmp LABEL_GOON_LOADING_FILE +LABEL_FILE_LOADED: + + call KillMotor ; 关闭软驱马达 + + mov dh, 1 ; "Ready." + call DispStrRealMode ; 显示字符串 + +;;add begin add by liang 2016.04.20 +EXE_LABEL_START: ; <--- 从这里开始 ************* + mov ax, cs + mov ds, ax + mov es, ax + mov ss, ax + mov sp, BaseOfStack + + mov dh, 3 ; "exLoading" + call DispStrRealMode ; 显示字符串 + + + ; 下面在 A 盘的根目录寻找 ECHO + mov word [wSectorNo], SectorNoOfRootDirectory + xor ah, ah ; ┓ + xor dl, dl ; ┣ 软驱复位 + int 13h ; ┛ +EXE_LABEL_SEARCH_IN_ROOT_DIR_BEGIN: + cmp word [wRootDirSizeForLoop], 0 ; ┓ + jz EXE_LABEL_NO_ECHO ; ┣ 判断根目录区是不是已经读完, 如果读完表示没有找到 ECHO + dec word [wRootDirSizeForLoop] ; ┛ + mov ax, BaseOfEchoFile + mov es, ax ; es <- BaseOfEchoFile + mov bx, OffsetOfEchoFile ; bx <- OffsetOfEchoFile 于是, es:bx = BaseOfEchoFile:OffsetOfEchoFile = BaseOfEchoFile * 10h + OffsetOfEchoFile + mov ax, [wSectorNo] ; ax <- Root Directory 中的某 Sector 号 + mov cl, 1 + call ReadSector + + mov si, EchoFileName ; ds:si -> "echo bin" + mov di, OffsetOfEchoFile ; es:di -> BaseOfEchoFile:???? = BaseOfEchoFile*10h+???? + cld + mov dx, 10h +EXE_LABEL_SEARCH_FOR_ECHO: + cmp dx, 0 ; ┓ + jz EXE_LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR ; ┣ 循环次数控制, 如果已经读完了一个 Sector, 就跳到下一个 Sector + dec dx ; ┛ + mov cx, 11 +EXE_LABEL_CMP_FILENAME: + cmp cx, 0 ; ┓ + jz EXE_LABEL_FILENAME_FOUND; ┣ 循环次数控制, 如果比较了 11 个字符都相等, 表示找到 + dec cx ; ┛ + lodsb ; ds:si -> al + cmp al, byte [es:di] ; if al == es:di + jz EXE_LABEL_GO_ON + jmp EXE_LABEL_DIFFERENT +EXE_LABEL_GO_ON: + inc di + jmp EXE_LABEL_CMP_FILENAME ; 继续循环 + +EXE_LABEL_DIFFERENT: + and di, 0FFE0h ; else┓ 这时di的值不知道是什么, di &= e0 为了让它是 20h 的倍数 + add di, 20h ; ┃ + mov si, EchoFileName ; ┣ di += 20h 下一个目录条目 + jmp EXE_LABEL_SEARCH_FOR_ECHO ; ┛ + +EXE_LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR: + add word [wSectorNo], 1 + jmp EXE_LABEL_SEARCH_IN_ROOT_DIR_BEGIN + +EXE_LABEL_NO_ECHO: + mov dh, 5 ; "No ECHO " + call DispStrRealMode ; 显示字符串 + jmp $ ; 没有找到 echo, 死循环在这里 + +EXE_LABEL_FILENAME_FOUND: ; 找到 echo 后便来到这里继续 + mov ax, RootDirSectors + and di, 0FFF0h ; di -> 当前条目的开始 + + push eax + mov eax, [es : di + 01Ch] ; ┓ + mov dword [_dwEchoSize], eax ; ┛保存 echo 文件大小 + pop eax + + add di, 01Ah ; di -> 首 Sector + mov cx, word [es:di] + push cx ; 保存此 Sector 在 FAT 中的序号 + add cx, ax + add cx, DeltaSectorNo ; 这时 cl 里面是 echo 的起始扇区号 (从 0 开始数的序号) + mov ax, BaseOfEchoFile + mov es, ax ; es <- BaseOfEchoFile + mov bx, OffsetOfEchoFile ; bx <- OffsetOfEchoFile 于是, es:bx = BaseOfEchoFile:OffsetOfEchoFile = BaseOfEchoFile * 10h + OffsetOfEchoFile + mov ax, cx ; ax <- Sector 号 + +EXE_LABEL_GOON_LOADING_FILE: + push ax ; ┓ + push bx ; ┃ + mov ah, 0Eh ; ┃ 每读一个扇区就在 "exLoading" 后面打一个点, 形成这样的效果: + mov al, '.' ; ┃ + mov bl, 0Fh ; ┃ exLoading...... + int 10h ; ┃ + pop bx ; ┃ + pop ax ; ┛ + + mov cl, 1 + call ReadSector + pop ax ; 取出此 Sector 在 FAT 中的序号 + call GetFATEntry + cmp ax, 0FFFh + jz EXE_LABEL_FILE_LOADED + push ax ; 保存 Sector 在 FAT 中的序号 + mov dx, RootDirSectors + add ax, dx + add ax, DeltaSectorNo + add bx, [BPB_BytsPerSec] + jmp EXE_LABEL_GOON_LOADING_FILE +EXE_LABEL_FILE_LOADED: + + call KillMotor ; 关闭软驱马达 + mov dh, 4 ; "exReady." + call DispStrRealMode ; 显示字符串 + +;;add end add by liang 2016.04.20 +; 下面准备跳入保护模式 ------------------------------------------- + +; 加载 GDTR + lgdt [GdtPtr] + +; 关中断 + cli + +; 打开地址线A20 + in al, 92h + or al, 00000010b + out 92h, al + +; 准备切换到保护模式 + mov eax, cr0 + or eax, 1 + mov cr0, eax + +; 真正进入保护模式 + jmp dword SelectorFlatC:(BaseOfLoaderPhyAddr+LABEL_PM_START) + + +;============================================================================ +;变量 +;---------------------------------------------------------------------------- +wRootDirSizeForLoop dw RootDirSectors ; Root Directory 占用的扇区数 +wSectorNo dw 0 ; 要读取的扇区号 +bOdd db 0 ; 奇数还是偶数 +dwKernelSize dd 0 ; KERNEL.BIN 文件大小 +_dwEchoSize dd 0 ;echo size add by liang 2016.04.20 +;============================================================================ +;字符串 +;---------------------------------------------------------------------------- +KernelFileName db "KERNEL BIN", 0 ; KERNEL.BIN 之文件名 +EchoFileName db "INIT BIN", 0 ;add by liang 2016.04.20 ;edit by visual 2016.5.16 +; 为简化代码, 下面每个字符串的长度均为 MessageLength +MessageLength equ 9 +LoadMessage: db "Loading " +Message1 db "Ready. " +Message2 db "No KERNEL" +Message3 db "exLoading" ;add by liang 2016.04.20 +Message4 db "exReady. " ;add by liang 2016.04.20 +Message5 db "No ECHO " ;add by liang 2016.04.20 +;============================================================================ + +;---------------------------------------------------------------------------- +; 函数名: DispStrRealMode +;---------------------------------------------------------------------------- +; 运行环境: +; 实模式(保护模式下显示字符串由函数 DispStr 完成) +; 作用: +; 显示一个字符串, 函数开始时 dh 中应该是字符串序号(0-based) +DispStrRealMode: + mov ax, MessageLength + mul dh + add ax, LoadMessage + mov bp, ax ; ┓ + mov ax, ds ; ┣ ES:BP = 串地址 + mov es, ax ; ┛ + mov cx, MessageLength ; CX = 串长度 + mov ax, 01301h ; AH = 13, AL = 01h + mov bx, 0007h ; 页号为0(BH = 0) 黑底白字(BL = 07h) + mov dl, 0 + add dh, 3 ; 从第 3 行往下显示 + int 10h ; int 10h + ret +;---------------------------------------------------------------------------- +; 函数名: ReadSector +;---------------------------------------------------------------------------- +; 作用: +; 从序号(Directory Entry 中的 Sector 号)为 ax 的的 Sector 开始, 将 cl 个 Sector 读入 es:bx 中 +ReadSector: + ; ----------------------------------------------------------------------- + ; 怎样由扇区号求扇区在磁盘中的位置 (扇区号 -> 柱面号, 起始扇区, 磁头号) + ; ----------------------------------------------------------------------- + ; 设扇区号为 x + ; ┌ 柱面号 = y >> 1 + ; x ┌ 商 y ┤ + ; -------------- => ┤ └ 磁头号 = y & 1 + ; 每磁道扇区数 │ + ; └ 余 z => 起始扇区号 = z + 1 + push bp + mov bp, sp + sub esp, 2 ; 辟出两个字节的堆栈区域保存要读的扇区数: byte [bp-2] + + mov byte [bp-2], cl + push bx ; 保存 bx + mov bl, [BPB_SecPerTrk] ; bl: 除数 + div bl ; y 在 al 中, z 在 ah 中 + inc ah ; z ++ + mov cl, ah ; cl <- 起始扇区号 + mov dh, al ; dh <- y + shr al, 1 ; y >> 1 (其实是 y/BPB_NumHeads, 这里BPB_NumHeads=2) + mov ch, al ; ch <- 柱面号 + and dh, 1 ; dh & 1 = 磁头号 + pop bx ; 恢复 bx + ; 至此, "柱面号, 起始扇区, 磁头号" 全部得到 ^^^^^^^^^^^^^^^^^^^^^^^^ + mov dl, [BS_DrvNum] ; 驱动器号 (0 表示 A 盘) +.GoOnReading: + mov ah, 2 ; 读 + mov al, byte [bp-2] ; 读 al 个扇区 + int 13h + jc .GoOnReading ; 如果读取错误 CF 会被置为 1, 这时就不停地读, 直到正确为止 + + add esp, 2 + pop bp + + ret + +;---------------------------------------------------------------------------- +; 函数名: GetFATEntry +;---------------------------------------------------------------------------- +; 作用: +; 找到序号为 ax 的 Sector 在 FAT 中的条目, 结果放在 ax 中 +; 需要注意的是, 中间需要读 FAT 的扇区到 es:bx 处, 所以函数一开始保存了 es 和 bx +GetFATEntry: + push es + push bx + push ax + mov ax, BaseOfKernelFile ; ┓ + sub ax, 0100h ; ┣ 在 BaseOfKernelFile 后面留出 4K 空间用于存放 FAT + mov es, ax ; ┛ + pop ax + mov byte [bOdd], 0 + mov bx, 3 + mul bx ; dx:ax = ax * 3 + mov bx, 2 + div bx ; dx:ax / 2 ==> ax <- 商, dx <- 余数 + cmp dx, 0 + jz LABEL_EVEN + mov byte [bOdd], 1 +LABEL_EVEN:;偶数 + xor dx, dx ; 现在 ax 中是 FATEntry 在 FAT 中的偏移量. 下面来计算 FATEntry 在哪个扇区中(FAT占用不止一个扇区) + mov bx, [BPB_BytsPerSec] + div bx ; dx:ax / BPB_BytsPerSec ==> ax <- 商 (FATEntry 所在的扇区相对于 FAT 来说的扇区号) + ; dx <- 余数 (FATEntry 在扇区内的偏移)。 + push dx + mov bx, 0 ; bx <- 0 于是, es:bx = (BaseOfKernelFile - 100):00 = (BaseOfKernelFile - 100) * 10h + add ax, SectorNoOfFAT1 ; 此句执行之后的 ax 就是 FATEntry 所在的扇区号 + mov cl, 2 + call ReadSector ; 读取 FATEntry 所在的扇区, 一次读两个, 避免在边界发生错误, 因为一个 FATEntry 可能跨越两个扇区 + pop dx + add bx, dx + mov ax, [es:bx] + cmp byte [bOdd], 1 + jnz LABEL_EVEN_2 + shr ax, 4 +LABEL_EVEN_2: + and ax, 0FFFh + +LABEL_GET_FAT_ENRY_OK: + + pop bx + pop es + ret +;---------------------------------------------------------------------------- + + +;---------------------------------------------------------------------------- +; 函数名: KillMotor +;---------------------------------------------------------------------------- +; 作用: +; 关闭软驱马达 +KillMotor: + push dx + mov dx, 03F2h + mov al, 0 + out dx, al + pop dx + ret +;---------------------------------------------------------------------------- + + +; 从此以后的代码在保护模式下执行 ---------------------------------------------------- +; 32 位代码段. 由实模式跳入 --------------------------------------------------------- +[SECTION .s32] + +ALIGN 32 + +[BITS 32] + +LABEL_PM_START: + mov ax, SelectorVideo + mov gs, ax + mov ax, SelectorFlatRW + mov ds, ax + mov es, ax + mov fs, ax + mov ss, ax + mov esp, TopOfStack + + push szMemChkTitle + call DispStr + add esp, 4 + + call DispMemInfo + call getFreeMemInfo ;add by liang 2016.04.13 + call DispEchoSize ;add by liang 2016.04.21 + call SetupPaging + + ;mov ah, 0Fh ; 0000: 黑底 1111: 白字 + ;mov al, 'P' + ;mov [gs:((80 * 0 + 39) * 2)], ax ; 屏幕第 0 行, 第 39 列。 + + call InitKernel + + ;jmp $ + + ;*************************************************************** + jmp SelectorFlatC:KernelEntryPointPhyAddr ; 正式进入内核 * + ;*************************************************************** + ; 内存看上去是这样的: + ; ┃ ┃ + ; ┃ . ┃ + ; ┃ . ┃ + ; ┃ . ┃ + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■┃ + ; ┃■■■■■■Page Tables■■■■■■■■■■■■■■■■■■┃ + ; ┃■■■■■(大小由LOADER决定)■■■■■■■■■■■■■┃ + ; 00101000h ┃■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■┃ PageTblBase + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■┃ + ; 00100000h ┃■■■■Page Directory Table■■■■■■■■■■■■┃ PageDirBase <- 1M + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□┃ + ; F0000h ┃□□□□□□□System ROM□□□□□□□□□□□□□□□□□□□┃ + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□┃ + ; E0000h ┃□□□□Expansion of system ROM □□□□□□□□┃ + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□┃ + ; C0000h ┃□□□Reserved for ROM expansion□□□□□□□┃ + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□┃ B8000h ← gs + ; A0000h ┃□□□Display adapter reserved□□□□□□□□□┃ + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□┃ + ; 9FC00h ┃□□extended BIOS data area (EBDA)□□□□┃ + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■┃ + ; 90000h ┃■■■■■■■LOADER.BIN■■■■■■■■■■■■■■■■■■■┃ somewhere in LOADER ← esp + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■┃ + ; 80000h ┃■■■■■■■KERNEL.BIN■■■■■■■■■■■■■■■■■■■┃ + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■┃ + ; 30000h ┃■■■■■■■■KERNEL■■■■■■■■■■■■■■■■■■■■■■┃ 30400h ← KERNEL 入口 (KernelEntryPointPhyAddr) + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃ ┃ + ; 7E00h ┃ F R E E ┃ + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■┃ + ; 7C00h ┃■■■■■■BOOT SECTOR■■■■■■■■■■■■■■■■■■┃ + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃ ┃ + ; 500h ┃ F R E E ┃ + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□┃ + ; 400h ┃□□□□ROM BIOS parameter area □□□□□□□□┃ + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃◇◇◇◇◇◇◇◇◇◇◇◇◇◇◇◇◇◇┃ + ; 0h ┃◇◇◇◇◇◇Int Vectors◇◇◇◇◇◇┃ + ; ┗━━━━━━━━━━━━━━━━━━┛ ← cs, ds, es, fs, ss + ; + ; + ; ┏━━━┓ ┏━━━┓ + ; ┃■■■■■■┃ 我们使用 ┃□□□□□□┃ 不能使用的内存 + ; ┗━━━┛ ┗━━━┛ + ; ┏━━━┓ ┏━━━┓ + ; ┃ ┃ 未使用空间 ┃◇◇◇┃ 可以覆盖的内存 + ; ┗━━━┛ ┗━━━┛ + ; + ; 注:KERNEL 的位置实际上是很灵活的,可以通过同时改变 LOAD.INC 中的 KernelEntryPointPhyAddr 和 MAKEFILE 中参数 -Ttext 的值来改变。 + ; 比如,如果把 KernelEntryPointPhyAddr 和 -Ttext 的值都改为 0x400400,则 KERNEL 就会被加载到内存 0x400000(4M) 处,入口在 0x400400。 + ; + + + + +; ------------------------------------------------------------------------ +; 显示 AL 中的数字 +; ------------------------------------------------------------------------ +DispAL: + push ecx + push edx + push edi + + mov edi, [dwDispPos] + + mov ah, 0Fh ; 0000b: 黑底 1111b: 白字 + mov dl, al + shr al, 4 + mov ecx, 2 +.begin: + and al, 01111b + cmp al, 9 + ja .1 + add al, '0' + jmp .2 +.1: + sub al, 0Ah + add al, 'A' +.2: + mov [gs:edi], ax + add edi, 2 + + mov al, dl + loop .begin + ;add edi, 2 + + mov [dwDispPos], edi + + pop edi + pop edx + pop ecx + + ret +; DispAL 结束------------------------------------------------------------- + + +; ------------------------------------------------------------------------ +; 显示一个整形数 +; ------------------------------------------------------------------------ +DispInt: + mov eax, [esp + 4] + shr eax, 24 + call DispAL + + mov eax, [esp + 4] + shr eax, 16 + call DispAL + + mov eax, [esp + 4] + shr eax, 8 + call DispAL + + mov eax, [esp + 4] + call DispAL + + mov ah, 07h ; 0000b: 黑底 0111b: 灰字 + mov al, 'h' + push edi + mov edi, [dwDispPos] + mov [gs:edi], ax + add edi, 4 + mov [dwDispPos], edi + pop edi + + ret +; DispInt 结束------------------------------------------------------------ + +; ------------------------------------------------------------------------ +; 显示一个字符串 +; ------------------------------------------------------------------------ +DispStr: + push ebp + mov ebp, esp + push ebx + push esi + push edi + + mov esi, [ebp + 8] ; pszInfo + mov edi, [dwDispPos] + mov ah, 0Fh +.1: + lodsb + test al, al + jz .2 + cmp al, 0Ah ; 是回车吗? + jnz .3 + push eax + mov eax, edi + mov bl, 160 + div bl + and eax, 0FFh + inc eax + mov bl, 160 + mul bl + mov edi, eax + pop eax + jmp .1 +.3: + mov [gs:edi], ax + add edi, 2 + jmp .1 + +.2: + mov [dwDispPos], edi + + pop edi + pop esi + pop ebx + pop ebp + ret +; DispStr 结束------------------------------------------------------------ + +; ------------------------------------------------------------------------ +; 换行 +; ------------------------------------------------------------------------ +DispReturn: + push szReturn + call DispStr ;printf("\n"); + add esp, 4 + + ret +; DispReturn 结束--------------------------------------------------------- + + +; ------------------------------------------------------------------------ +; 内存拷贝,仿 memcpy +; ------------------------------------------------------------------------ +; void* MemCpy(void* es:pDest, void* ds:pSrc, int iSize); +; ------------------------------------------------------------------------ +MemCpy: + push ebp + mov ebp, esp + + push esi + push edi + push ecx + + mov edi, [ebp + 8] ; Destination + mov esi, [ebp + 12] ; Source + mov ecx, [ebp + 16] ; Counter +.1: + cmp ecx, 0 ; 判断计数器 + jz .2 ; 计数器为零时跳出 + + mov al, [ds:esi] ; ┓ + inc esi ; ┃ + ; ┣ 逐字节移动 + mov byte [es:edi], al ; ┃ + inc edi ; ┛ + + dec ecx ; 计数器减一 + jmp .1 ; 循环 +.2: + mov eax, [ebp + 8] ; 返回值 + + pop ecx + pop edi + pop esi + mov esp, ebp + pop ebp + + ret ; 函数结束,返回 +; MemCpy 结束------------------------------------------------------------- + +;;;;;;;;;;;;;;;;;add begin add by liang 2016.04.13;;;;;;;;;;;;;;;;;;;;; + +memtest: + push edi + push esi + + mov esi,0xaa55aa55 + mov edi,0x55aa55aa + mov eax,[esp+8+4] ;start +.mt_loop: + mov ebx,eax + add ebx,0xffc ;检查每4KB最后4B + mov ecx,[ebx] ;保存原值 + mov [ebx],esi ;写入 + xor dword [ebx],0xffffffff ;反转 + cmp edi,[ebx] ;检查反转结果是否正确 + jne .mt_fin + xor dword [ebx],0xffffffff ;再次反转 + cmp esi,[ebx] ;检查是否恢复初始值 + jne .mt_fin + mov [ebx],ecx ;恢复为修改前的值 + add eax,0x1000 ;每循环一次加0x1000 + cmp eax,[esp+8+8] ;未到end,循环 + jbe .mt_loop + pop esi + pop edi + ret + +.mt_fin: + mov [ebx],ecx ;恢复为修改前的值 + pop esi + pop edi + ret + +getFreeMemInfo: + push eax + push ebx + push ecx + push edx + + mov eax,cr0 + or eax,0x60000000 ;禁止缓存 + mov cr0,eax + + mov ebx,0x00000000 ;检查0到32M + mov ecx,0x02000000 + mov edx,FMIBuff ;存于0x007ff000处 + +.fmi_loop: + push ecx + push ebx + call memtest + pop ebx + pop ecx + mov [edx+4],eax ;留出前4B存放dwFMINumber + add edx,4 + add eax,0x1000 + mov ebx,eax + inc dword [dwFMINumber] ;循环次数,即返回值个数 + cmp ebx,ecx + jb .fmi_loop + + mov ebx,[dwFMINumber] + mov edx,FMIBuff + mov [edx],ebx ;前4B存放返回值个数 + mov ebx,[FMIBuff] + push ebx + call DispInt ;打印返回值个数 + add esp,4 + + mov eax,cr0 + and eax,0x9fffffff ;恢复缓存 + mov cr0,eax + + pop edx + pop ecx + pop ebx + pop eax + ret + + +;;;;;;;;;;;;;;;;;;;;;;;;;;add end add by liang 2016.04.13;;;;;;;;;;;;;;;;;;;;;;;;;; + +;;add begin add by liang 2016.04.21 +DispEchoSize: + push eax + mov eax,[dwEchoSize] + push eax + call DispInt + add esp,4 + pop eax + ret +;;add end add by liang 2016.04.21 + +; 显示内存信息 -------------------------------------------------------------- +DispMemInfo: + push esi + push edi + push ecx + + mov esi, MemChkBuf + mov ecx, [dwMCRNumber] ;for(int i=0;i<[MCRNumber];i++) // 每次得到一个ARDS(Address Range Descriptor Structure)结构 +.loop: ;{ + mov edx, 5 ; for(int j=0;j<5;j++) // 每次得到一个ARDS中的成员,共5个成员 + mov edi, ARDStruct ; { // 依次显示:BaseAddrLow,BaseAddrHigh,LengthLow,LengthHigh,Type +.1: ; + push dword [esi] ; + call DispInt ; DispInt(MemChkBuf[j*4]); // 显示一个成员 + pop eax ; + stosd ; ARDStruct[j*4] = MemChkBuf[j*4]; + add esi, 4 ; + dec edx ; + cmp edx, 0 ; + jnz .1 ; } + call DispReturn ; printf("\n"); + cmp dword [dwType], 1 ; if(Type == AddressRangeMemory) // AddressRangeMemory : 1, AddressRangeReserved : 2 + jne .2 ; { + mov eax, [dwBaseAddrLow] ; + add eax, [dwLengthLow] ; + cmp eax, [dwMemSize] ; if(BaseAddrLow + LengthLow > MemSize) + jb .2 ; + mov [dwMemSize], eax ; MemSize = BaseAddrLow + LengthLow; +.2: ; } + loop .loop ;} + ; + call DispReturn ;printf("\n"); + push szRAMSize ; + call DispStr ;printf("RAM size:"); + add esp, 4 ; + ; + push dword [dwMemSize] ; + call DispInt ;DispInt(MemSize); + add esp, 4 ; + + pop ecx + pop edi + pop esi + ret +; --------------------------------------------------------------------------- + +; 启动分页机制 -------------------------------------------------------------- +SetupPaging: + ; 根据内存大小计算应初始化多少PDE以及多少页表 + xor edx, edx + mov eax, [dwMemSize] + mov ebx, 400000h ; 400000h = 4M = 4096 * 1024, 一个页表对应的内存大小 + div ebx + mov ecx, eax ; 此时 ecx 为页表的个数,也即 PDE 应该的个数 + test edx, edx + jz .no_remainder + inc ecx ; 如果余数不为 0 就需增加一个页表 +.no_remainder: + push ecx ; 暂存页表个数 + mov dword[PageTblNumAddr],ecx ;将页表数写进这个物理地址 + + ; 为简化处理, 所有线性地址对应相等的物理地址. 并且不考虑内存空洞. + + ; 首先初始化页目录 + mov ax, SelectorFlatRW + mov es, ax + mov edi, PageDirBase ; 此段首地址为 PageDirBase + xor eax, eax + mov eax, PageTblBase | PG_P | PG_USU | PG_RWW +.1: + stosd + add eax, 4096 ; 为了简化, 所有页表在内存中是连续的. + loop .1 + +;;;;初始化3G处的页目录;;;;;;;;;;;;;;;; //add by visual 2016.5.10 + pop eax ;页表个数 + mov ecx,eax ;重新放到ecx里 + push ecx ;暂存页表个数 + mov ax, SelectorFlatRW + mov es, ax + mov eax, 3072 ;768*4 + add eax, PageDirBase ; + mov edi, eax ; 应该往页目录这个位置写: PageDirBase+768*4,即线性地址3G处 + + xor eax, eax ; 清0 + mov eax, ecx ; + mov ebx, 4096 ; + mul ebx ;跳过前ecx个页表,即PageTblBase+页表数*4096 + add eax, PageTblBase | PG_P | PG_USU | PG_RWW; +.1k: + stosd + add eax, 4096 ; 为了简化, 所有页表在内存中是连续的. + loop .1k +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + ; 再初始化所有页表(最开始处,一一映射的) + pop eax ; 页表个数 + push eax ;暂存页表个数 + mov ebx, 1024 ; 每个页表 1024 个 PTE + mul ebx + mov ecx, eax ; PTE个数 = 页表个数 * 1024 + mov edi, PageTblBase ; 此段首地址为 PageTblBase + xor eax, eax + mov eax, PG_P | PG_USU | PG_RWW +.2: + stosd + add eax, 4096 ; 每一页指向 4K 的空间 + loop .2 + +;;;;初始化3G处的页表;;;;;;;;;;;;;;;; //add by visual 2016.5.10 + ; 再初始化3G后的页表 + pop eax ; 页表个数 + mov ebx, 1024 ; 每个页表 1024 个 PTE + mul ebx + mov ecx, eax ; PTE个数 = 页表个数 * 1024 + + xor eax, eax ; 清0 + mov eax, ecx ; + mov ebx, 4 + mul ebx ;跳过前ecx个页表,即PageTblBase+页表数*4096 + add eax, PageTblBase ; 后面3G对应页表的起始位置为 PageTblBase+页表数*4096 + mov edi, eax + xor eax, eax + mov eax, PG_P | PG_USU | PG_RWW ;从0开始 +.2k: + stosd + add eax, 4096 ; 每一页指向 4K 的空间 + loop .2k +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + ;mov ah, 0Fh ; 0000: 黑底 1111: 白字 + ;mov al, 'P' + ;mov [gs:((80 * 0 + 39) * 2)], ax ; 屏幕第 0 行, 第 39 列。 + + ;启动页表机制 + mov eax, PageDirBase + mov cr3, eax + mov eax, cr0 + or eax, 80000000h + mov cr0, eax + jmp short .3 +.3: + nop + + ret +; 分页机制启动完毕 ---------------------------------------------------------- + + + +; InitKernel --------------------------------------------------------------------------------- +; 将 KERNEL.BIN 的内容经过整理对齐后放到新的位置 +; -------------------------------------------------------------------------------------------- +InitKernel: ; 遍历每一个 Program Header,根据 Program Header 中的信息来确定把什么放进内存,放到什么位置,以及放多少。 + xor esi, esi + mov cx, word [BaseOfKernelFilePhyAddr + 2Ch]; ┓ ecx <- pELFHdr->e_phnum + movzx ecx, cx ; ┛ + mov esi, [BaseOfKernelFilePhyAddr + 1Ch] ; esi <- pELFHdr->e_phoff + add esi, BaseOfKernelFilePhyAddr ; esi <- OffsetOfKernel + pELFHdr->e_phoff +.Begin: + mov eax, [esi + 0] + cmp eax, 0 ; PT_NULL + jz .NoAction + push dword [esi + 010h] ; size ┓ + mov eax, [esi + 04h] ; ┃ + add eax, BaseOfKernelFilePhyAddr ; ┣ ::memcpy( (void*)(pPHdr->p_vaddr), + push eax ; src ┃ uchCode + pPHdr->p_offset, + push dword [esi + 08h] ; dst ┃ pPHdr->p_filesz; + call MemCpy ; ┃ + add esp, 12 ; ┛ +.NoAction: + add esi, 020h ; esi += pELFHdr->e_phentsize + dec ecx + jnz .Begin + + ret +; InitKernel ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + +; SECTION .data1 之开始 --------------------------------------------------------------------------------------------- +[SECTION .data1] + +ALIGN 32 + +LABEL_DATA: +; 实模式下使用这些符号 +; 字符串 +_szMemChkTitle: db "BaseAddrL BaseAddrH LengthLow LengthHigh Type", 0Ah, 0 +_szRAMSize: db "RAM size:", 0 +_szReturn: db 0Ah, 0 +;; 变量 +_dwMCRNumber: dd 0 ; Memory Check Result +_dwDispPos: dd (80 * 6 + 0) * 2 ; 屏幕第 6 行, 第 0 列。 +_dwMemSize: dd 0 +_ARDStruct: ; Address Range Descriptor Structure + _dwBaseAddrLow: dd 0 + _dwBaseAddrHigh: dd 0 + _dwLengthLow: dd 0 + _dwLengthHigh: dd 0 + _dwType: dd 0 +_MemChkBuf: times 256 db 0 +_dwFMINumber: dd 0 ;add by liang 2016.04.13 + + +; +;; 保护模式下使用这些符号 +szMemChkTitle equ BaseOfLoaderPhyAddr + _szMemChkTitle +szRAMSize equ BaseOfLoaderPhyAddr + _szRAMSize +szReturn equ BaseOfLoaderPhyAddr + _szReturn +dwDispPos equ BaseOfLoaderPhyAddr + _dwDispPos +dwMemSize equ BaseOfLoaderPhyAddr + _dwMemSize +dwMCRNumber equ BaseOfLoaderPhyAddr + _dwMCRNumber +ARDStruct equ BaseOfLoaderPhyAddr + _ARDStruct + dwBaseAddrLow equ BaseOfLoaderPhyAddr + _dwBaseAddrLow + dwBaseAddrHigh equ BaseOfLoaderPhyAddr + _dwBaseAddrHigh + dwLengthLow equ BaseOfLoaderPhyAddr + _dwLengthLow + dwLengthHigh equ BaseOfLoaderPhyAddr + _dwLengthHigh + dwType equ BaseOfLoaderPhyAddr + _dwType +MemChkBuf equ BaseOfLoaderPhyAddr + _MemChkBuf +dwFMINumber equ BaseOfLoaderPhyAddr + _dwFMINumber ;add by liang 2016.04.13 +dwEchoSize equ BaseOfLoaderPhyAddr + _dwEchoSize ;add by liang 2016.04.21 + +; 堆栈就在数据段的末尾 +StackSpace: times 1000h db 0 +TopOfStack equ BaseOfLoaderPhyAddr + $ ; 栈顶 +; SECTION .data1 之结束 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + diff --git a/boot/grub/boot.asm b/boot/grub/boot.asm new file mode 100644 index 0000000..09f8fdb --- /dev/null +++ b/boot/grub/boot.asm @@ -0,0 +1,253 @@ +;============================================================================================================================== +BaseOfStack equ 0x07c00 ; Boot状态下堆栈基地址 +DATA_BUF_OFF equ 0x2000 ; 目录被加载的缓冲区地址 +STACK_ADDR equ 0x7bea ; 堆栈栈顶 +OSLOADER_SEG equ 0x09000 ; 起始段地址 +OSLOADER_SEG_OFF equ 0x0100 + +FAT_START_SECTOR equ 0x820 ; FAT表的起始扇区号 DWORD +DATA_START_SECTOR equ 0xd6a ; 数据区起始扇区号 DWORD +DIR_PER_SECTOR equ 0x10 ; 每个扇区所容纳的目录 BYTE + +; 扩展磁盘服务所使用的地址包 +DAP_SECTOR_HIGH equ 4 ; 起始扇区号的高32位 ( 每次调用需要重置 ) DWORD +DAP_SECTOR_LOW equ 8 ; 起始扇区号的低32位 ( 每次调用需要重置 ) DWORD +DAP_BUFFER_SEG equ 10 ; 缓冲区段地址 ( 每次调用需要重置 ) WORD +DAP_BUFFER_OFF equ 12 ; 缓冲区偏移 ( 每次调用需要重置 ) WORD +DAP_RESERVED2 equ 13 ; 保留字节 +DAP_READ_SECTORS equ 14 ; 要处理的扇区数(1 - 127 ) +DAP_RESERVED1 equ 15 ; 保留字节 +DAP_PACKET_SIZE equ 16 ; 包的大小为16字节 + +CURRENT_CLUSTER equ 20 ; 当前正在处理的簇号 DWORD + +; 目录项结构 +OFF_START_CLUSTER_HIGH equ 20 ; 起始簇号高位 WORD +OFF_START_CLUSTER_LOW equ 26 ; 起始簇号低位 WORD + +; 相关常量 +DIR_NAME_FREE equ 0x00 ; 该项是空闲的 +DIR_ENTRY_SIZE equ 32 ; 每个目录项的尺寸 + +; 簇属性 +CLUSTER_MASK equ 0FFFFFFFH ; 簇号掩码 +CLUSTER_LAST equ 0FFFFFF8H ;0xFFFFFFF8-0xFFFFFFFF表示文件的最后一个簇 +;============================================================================================================================== + org 07c00h + + jmp START + nop + + BS_OEM DB 'mkfs.fat' ;文件系统标志 + BPB_BytesPerSec DW 0x200 ;每扇区字节数 + BPB_SecPerClu DB 1 ;每簇扇区数 + BPB_RsvdSecCnt DW 0x20 ;保留扇区数 + BPB_NumFATs DB 2 ;FAT表数 + BPB_RootEntCnt DW 0 ;FAT32不使用 + BPB_TotSec16 DW 0 ;扇区总数 + BPB_Media DB 0xf8 ;介质描述符 + BPB_FATSz16 DW 0 ;每个FAT表的大小扇区数 + BPB_SecPerTrk DW 0x3f ;每磁道扇区数 + BPB_NumHeads DW 0xff ;磁头数 + BPB_HiddSec DD 0 ;分区已使用扇区数 + BPB_TotSec32 DD 0x015791 ;文件系统大小扇区数 + BS_SecPerFAT DD 0x02a5 ;每个FAT表大小扇区数 + BS_Flag DW 0 ;标记 + BS_Version DW 0 ;版本号 + BS_RootClus DD 2 ;根目录簇号 + BS_FsInfoSec DW 1 ;FSINFO扇区号 + BS_BackBootSec DW 6 ;备份引导扇区位置 + BS_Unuse1 DD 0 ;未使用 + BS_Unuse2 DD 0 ;未使用 + BS_Unuse3 DD 0 ;未使用 + BS_DriveNum DB 0x80 ;设备号 + BS_Unuse4 DB 0x01 ;未使用 + BS_ExtBootFlag DB 0x29 ;扩展引导标志 + BS_VolNum DD 0xbe3a8ff5 ;卷序列号 + BS_VolName DB 'MZY hd boot' ;卷标 + +START: + cld + xor ax, ax + mov ds, ax + mov es, ax + mov ss, ax + mov sp, STACK_ADDR + mov bp, BaseOfStack + + mov dword [bp - DAP_SECTOR_HIGH ], 00h + mov byte [bp - DAP_RESERVED1 ], 00h + mov byte [bp - DAP_RESERVED2 ], 00h + mov byte [bp - DAP_PACKET_SIZE ], 10h + mov byte [bp - DAP_READ_SECTORS], 01h + mov byte [bp - DAP_BUFFER_SEG ], 00h + + jmp _SEARCH_LOADER + +_MISSING_LOADER: ; 显示没有装载程序 +_DISK_ERROR: ; 显示磁盘错误信息 + JMP $ + +ReadSector: + pusha + mov ah, 42h + lea si, [BP - DAP_PACKET_SIZE] + mov dl, [BS_DriveNum] + int 13h + jc _DISK_ERROR + popa + ret + +_SEARCH_LOADER: + mov word [bp - DAP_BUFFER_OFF], DATA_BUF_OFF + mov eax, dword [BS_RootClus] + mov dword [bp - CURRENT_CLUSTER], eax + +_NEXT_ROOT_CLUSTER: + dec eax + dec eax + xor ebx, ebx + mov bl, byte [BPB_SecPerClu] + mul ebx + add eax, DATA_START_SECTOR + mov dword [BP - DAP_SECTOR_LOW], eax + mov dl, [BPB_SecPerClu] + +_NEXT_ROOT_SECTOR: + call ReadSector + + mov di, DATA_BUF_OFF + mov bl, DIR_PER_SECTOR + +_NEXT_ROOT_ENTRY: + cmp byte [di], DIR_NAME_FREE + jz _MISSING_LOADER + + push di; + mov si, LoaderName + mov cx, 10 + repe cmpsb + jcxz _FOUND_LOADER + + pop di + add di, DIR_ENTRY_SIZE + dec bl + jnz _NEXT_ROOT_ENTRY + + dec dl + jz _CHECK_NEXT_ROOT_CLUSTER + inc dword [bp - DAP_SECTOR_LOW] + jmp _NEXT_ROOT_SECTOR + +_CHECK_NEXT_ROOT_CLUSTER: + + ; 计算FAT所在的簇号和偏移 + ; FatOffset = ClusterNum*4 + XOR EDX,EDX + MOV EAX,DWORD[BP - CURRENT_CLUSTER] + SHL EAX,2 + XOR ECX,ECX + MOV CX,WORD [ BPB_BytesPerSec ] + DIV ECX ; EAX = Sector EDX = OFFSET + ADD EAX, FAT_START_SECTOR + MOV DWORD [ BP - DAP_SECTOR_LOW ], EAX + + call ReadSector + + ; 检查下一个簇 + MOV DI,DX + ADD DI,DATA_BUF_OFF + MOV EAX,DWORD[DI] ; EAX = 下一个要读的簇号 + AND EAX,CLUSTER_MASK + MOV DWORD[ BP - CURRENT_CLUSTER ],EAX + CMP EAX,CLUSTER_LAST ; CX >= 0FFFFFF8H,则意味着没有更多的簇了 + JB _NEXT_ROOT_CLUSTER + JMP _MISSING_LOADER + +_FOUND_LOADER: + ; 目录结构地址放在DI中 + pop di + xor eax, eax + mov ax, [di + OFF_START_CLUSTER_HIGH] ; 起始簇号高32位 + shl ax, 16 + mov ax, [di + OFF_START_CLUSTER_LOW] ; 起始簇号低32位 + mov dword [ bp - CURRENT_CLUSTER ], eax + mov cx, OSLOADER_SEG ; CX = 缓冲区段地址 + +_NEXT_DATA_CLUSTER: + ; 根据簇号计算扇区号 + DEC EAX + DEC EAX + XOR EBX,EBX + MOV BL, BYTE [ BPB_SecPerClu ] + MUL EBX + ADD EAX, DATA_START_SECTOR + MOV DWORD[ BP - DAP_SECTOR_LOW ], EAX + MOV BL , BYTE [BPB_SecPerClu] + + ; 设置缓冲区 + MOV WORD [ BP - DAP_BUFFER_SEG ], CX + MOV WORD [ BP - DAP_BUFFER_OFF ], OSLOADER_SEG_OFF + +_NEXT_DATA_SECTOR: + ; 读取簇中的每个扇区(内层循环) + ; 注意 : 通过检查文件大小,可以避免读取最后一个不满簇的所有大小 + call ReadSector + + ; 更新地址,继续读取 + MOV AX, WORD [BPB_BytesPerSec] + ADD WORD [BP - DAP_BUFFER_OFF], ax + INC DWORD [BP - DAP_SECTOR_LOW] ; 递增扇区号 + DEC BL ; 内层循环计数 + JNZ _NEXT_DATA_SECTOR + + ; 更新读取下一个簇的缓冲区地址 + MOV CL, BYTE [ BPB_SecPerClu ] + MOV AX, WORD [BPB_BytesPerSec] + SHR AX, 4 + MUL CL + ADD AX, WORD [ BP - DAP_BUFFER_SEG ] + MOV CX, AX ; 保存下一个簇的缓冲区段地址 + + ;==================================================================== + ; 检查是否还有下一个簇(读取FAT表的相关信息) + ; LET N = 数据簇号 + ; THUS FAT_BYTES = N*4 (FAT32) + ; FAT_SECTOR = FAT_BYTES / BPB_BytesPerSec + ; FAT_OFFSET = FAT_BYTES % BPB_BytesPerSec + ;==================================================================== + + ; 计算FAT所在的簇号和偏移 + MOV EAX,DWORD [BP - CURRENT_CLUSTER] + XOR EDX,EDX + SHL EAX,2 + XOR EBX,EBX + MOV BX,WORD [ BPB_BytesPerSec ] + DIV EBX ; EAX = Sector EDX = Offset + + ; 设置缓冲区地址 + ADD EAX, FAT_START_SECTOR + MOV DWORD [ BP - DAP_SECTOR_LOW ], EAX + MOV WORD [BP - DAP_BUFFER_SEG ], 00H + MOV WORD [BP - DAP_BUFFER_OFF ], DATA_BUF_OFF + + ; 读取扇区 + CALL ReadSector + + ; 检查下一个簇 + MOV DI,DX + ADD DI,DATA_BUF_OFF + MOV EAX,DWORD[DI] ; EAX = 下一个要读的簇号 + AND EAX,CLUSTER_MASK + MOV DWORD[ BP - CURRENT_CLUSTER ],EAX + CMP EAX,CLUSTER_LAST ; CX >= 0FFFFFF8H,则意味着没有更多的簇了 + JB _NEXT_DATA_CLUSTER + +_RUN_LOADER: + mov dl, [BS_DriveNum] + jmp OSLOADER_SEG:OSLOADER_SEG_OFF + +LoaderName db "LOADER BIN" ; 第二阶段启动程序 FDOSLDR.BIN + +times 510-($-$$) db 0 ; 填充剩下的空间,使生成的二进制代码恰好为512字节 +dw 0xaa55 ; 结束标志 diff --git a/boot/grub/grub.cfg b/boot/grub/grub.cfg new file mode 100644 index 0000000..1c27541 --- /dev/null +++ b/boot/grub/grub.cfg @@ -0,0 +1,8 @@ +set default=0 +set timeout=5 + +menuentry "myboot" { + set root=(hd0,msdos1) + chainloader +1 + boot +} diff --git a/boot/grub/include/fat32.inc b/boot/grub/include/fat32.inc new file mode 100644 index 0000000..5ac98d9 --- /dev/null +++ b/boot/grub/include/fat32.inc @@ -0,0 +1,28 @@ + BS_OEM DB 'mkfs.fat' + BPB_BytesPerSec DW 0x200 + BPB_SecPerClu DB 1 + BPB_RsvdSecCnt DW 0x20 + BPB_NumFATs DB 2 + BPB_RootEntCnt DW 0 + BPB_TotSec16 DW 0 + BPB_Media DB 0xf8 + BPB_FATSz16 DW 0 + BPB_SecPerTrk DW 0x20 + BPB_NumHeads DW 0x40 + BPB_HiddSec DD 0 + BPB_TotSec32 DD 0x015791 + BS_SecPerFAT DD 0x02a5 + BS_Flag DW 0 + BS_Version DW 0 + BS_RootClus DD 2 + BS_FsInfoSec DW 1 + BS_BackBootSec DW 6 + BS_Unuse1 DD 0 + BS_Unuse2 DD 0 + BS_Unuse3 DD 0 + BS_DriveNum DB 0x80 + BS_Unuse4 DB 0 + BS_ExtBootFlag DB 0x29 + BS_VolNum DD 0xbe3a8ff5 + BS_VolName DB 'MZY hd boot' + diff --git a/boot/grub/include/lib.inc b/boot/grub/include/lib.inc new file mode 100644 index 0000000..1faceea --- /dev/null +++ b/boot/grub/include/lib.inc @@ -0,0 +1,170 @@ + +; ------------------------------------------------------------------------ +; 显示 AL 中的数字 +; ------------------------------------------------------------------------ +DispAL: + push ecx + push edx + push edi + + mov edi, [dwDispPos] + + mov ah, 0Fh ; 0000b: 黑底 1111b: 白字 + mov dl, al + shr al, 4 + mov ecx, 2 +.begin: + and al, 01111b + cmp al, 9 + ja .1 + add al, '0' + jmp .2 +.1: + sub al, 0Ah + add al, 'A' +.2: + mov [gs:edi], ax + add edi, 2 + + mov al, dl + loop .begin + ;add edi, 2 + + mov [dwDispPos], edi + + pop edi + pop edx + pop ecx + + ret +; DispAL 结束------------------------------------------------------------- + + +; ------------------------------------------------------------------------ +; 显示一个整形数 +; ------------------------------------------------------------------------ +DispInt: + mov eax, [esp + 4] + shr eax, 24 + call DispAL + + mov eax, [esp + 4] + shr eax, 16 + call DispAL + + mov eax, [esp + 4] + shr eax, 8 + call DispAL + + mov eax, [esp + 4] + call DispAL + + mov ah, 07h ; 0000b: 黑底 0111b: 灰字 + mov al, 'h' + push edi + mov edi, [dwDispPos] + mov [gs:edi], ax + add edi, 4 + mov [dwDispPos], edi + pop edi + + ret +; DispInt 结束------------------------------------------------------------ + +; ------------------------------------------------------------------------ +; 显示一个字符串 +; ------------------------------------------------------------------------ +DispStr: + push ebp + mov ebp, esp + push ebx + push esi + push edi + + mov esi, [ebp + 8] ; pszInfo + mov edi, [dwDispPos] + mov ah, 0Fh +.1: + lodsb + test al, al + jz .2 + cmp al, 0Ah ; 是回车吗? + jnz .3 + push eax + mov eax, edi + mov bl, 160 + div bl + and eax, 0FFh + inc eax + mov bl, 160 + mul bl + mov edi, eax + pop eax + jmp .1 +.3: + mov [gs:edi], ax + add edi, 2 + jmp .1 + +.2: + mov [dwDispPos], edi + + pop edi + pop esi + pop ebx + pop ebp + ret +; DispStr 结束------------------------------------------------------------ + +; ------------------------------------------------------------------------ +; 换行 +; ------------------------------------------------------------------------ +DispReturn: + push szReturn + call DispStr ;printf("\n"); + add esp, 4 + + ret +; DispReturn 结束--------------------------------------------------------- + + +; ------------------------------------------------------------------------ +; 内存拷贝,仿 memcpy +; ------------------------------------------------------------------------ +; void* MemCpy(void* es:pDest, void* ds:pSrc, int iSize); +; ------------------------------------------------------------------------ +MemCpy: + push ebp + mov ebp, esp + + push esi + push edi + push ecx + + mov edi, [ebp + 8] ; Destination + mov esi, [ebp + 12] ; Source + mov ecx, [ebp + 16] ; Counter +.1: + cmp ecx, 0 ; 判断计数器 + jz .2 ; 计数器为零时跳出 + + mov al, [ds:esi] ; ┓ + inc esi ; ┃ + ; ┣ 逐字节移动 + mov byte [es:edi], al ; ┃ + inc edi ; ┛ + + dec ecx ; 计数器减一 + jmp .1 ; 循环 +.2: + mov eax, [ebp + 8] ; 返回值 + + pop ecx + pop edi + pop esi + mov esp, ebp + pop ebp + + ret ; 函数结束,返回 +; MemCpy 结束------------------------------------------------------------- + diff --git a/boot/grub/include/loader.inc b/boot/grub/include/loader.inc new file mode 100644 index 0000000..8050550 --- /dev/null +++ b/boot/grub/include/loader.inc @@ -0,0 +1,61 @@ +;============================================================================================================================== +BaseOfStack equ 0x0100 +STACK_ADDR equ 0x0ea +SEG_ADDR equ 0x09000 +DATA_BUF_OFF equ 0x09000 + +FAT_START_SECTOR equ 0x820 ; FAT表的起始扇区号 DWORD +DATA_START_SECTOR equ 0xd6a ; 数据区起始扇区号 DWORD +DIR_PER_SECTOR equ 0x10 ; 每个扇区所容纳的目录 BYTE + +; 扩展磁盘服务所使用的地址包 +DAP_SECTOR_HIGH equ 4 ; 起始扇区号的高32位 ( 每次调用需要重置 ) DWORD +DAP_SECTOR_LOW equ 8 ; 起始扇区号的低32位 ( 每次调用需要重置 ) DWORD +DAP_BUFFER_SEG equ 10 ; 缓冲区段地址 ( 每次调用需要重置 ) WORD +DAP_BUFFER_OFF equ 12 ; 缓冲区偏移 ( 每次调用需要重置 ) WORD +DAP_RESERVED2 equ 13 ; 保留字节 +DAP_READ_SECTORS equ 14 ; 要处理的扇区数(1 - 127 ) +DAP_RESERVED1 equ 15 ; 保留字节 +DAP_PACKET_SIZE equ 16 ; 包的大小为16字节 + +CURRENT_CLUSTER equ 20 ; 当前正在处理的簇号 DWORD + +; 目录项结构 +OFF_START_CLUSTER_HIGH equ 20 ; 起始簇号高位 WORD +OFF_START_CLUSTER_LOW equ 26 ; 起始簇号低位 WORD + +; 相关常量 +DIR_NAME_FREE equ 0x00 ; 该项是空闲的 +DIR_ENTRY_SIZE equ 32 ; 每个目录项的尺寸 + +; 簇属性 +CLUSTER_MASK equ 0FFFFFFFH ; 簇号掩码 +CLUSTER_LAST equ 0FFFFFF8H ;0xFFFFFFF8-0xFFFFFFFF表示文件的最后一个簇 + +BaseOfLoader equ 09000h ; LOADER.BIN 被加载到的位置 ---- 段地址 +OffsetOfLoader equ 0100h ; LOADER.BIN 被加载到的位置 ---- 偏移地址 + +BaseOfLoaderPhyAddr equ BaseOfLoader * 10h ; LOADER.BIN 被加载到的位置 ---- 物理地址 (= BaseOfLoader * 10h) + +BaseOfKernelFile equ 06000h ; KERNEL.BIN 被加载到的位置 ---- 段地址 +OffsetOfKernelFile equ 0h ; KERNEL.BIN 被加载到的位置 ---- 偏移地址 + +BaseOfKernelFilePhyAddr equ BaseOfKernelFile * 10h + +BaseOfEchoFile equ 07E0h ; KERNEL.BIN 被加载到的位置 ---- 段地址 +OffsetOfEchoFile equ 0h ; KERNEL.BIN 被加载到的位置 ---- 偏移地址 + +BaseOfEchoFilePhyAddr equ BaseOfKernelFile * 10h + +KernelEntryPointPhyAddr equ 0xC0030400 ; 注意:1、必须与 MAKEFILE 中参数 -Ttext 的值相等!! + ; 2、这是个地址而非仅仅是个偏移 + +PageDirBase equ 200000h ; 页目录开始地址: 2M +PageTblBase equ 201000h ; 页表开始地址: 2M + 4K + +PageTblNumAddr equ 500h;页表数量放在这个位置 delete by visual 2016.4.28 + +FMIBuff equ 007ff000h + +;============================================================================================================================== + diff --git a/boot/grub/include/pm.inc b/boot/grub/include/pm.inc new file mode 100644 index 0000000..6d4c94c --- /dev/null +++ b/boot/grub/include/pm.inc @@ -0,0 +1,318 @@ + + +; 描述符图示 + +; 图示一 +; +; ------ ┏━━┳━━┓高地址 +; ┃ 7 ┃ 段 ┃ +; ┣━━┫ ┃ +; 基 +; 字节 7 ┆ ┆ ┆ +; 址 +; ┣━━┫ ② ┃ +; ┃ 0 ┃ ┃ +; ------ ┣━━╋━━┫ +; ┃ 7 ┃ G ┃ +; ┣━━╉──┨ +; ┃ 6 ┃ D ┃ +; ┣━━╉──┨ +; ┃ 5 ┃ 0 ┃ +; ┣━━╉──┨ +; ┃ 4 ┃ AVL┃ +; 字节 6 ┣━━╉──┨ +; ┃ 3 ┃ ┃ +; ┣━━┫ 段 ┃ +; ┃ 2 ┃ 界 ┃ +; ┣━━┫ 限 ┃ +; ┃ 1 ┃ ┃ +; ┣━━┫ ② ┃ +; ┃ 0 ┃ ┃ +; ------ ┣━━╋━━┫ +; ┃ 7 ┃ P ┃ +; ┣━━╉──┨ +; ┃ 6 ┃ ┃ +; ┣━━┫ DPL┃ +; ┃ 5 ┃ ┃ +; ┣━━╉──┨ +; ┃ 4 ┃ S ┃ +; 字节 5 ┣━━╉──┨ +; ┃ 3 ┃ ┃ +; ┣━━┫ T ┃ +; ┃ 2 ┃ Y ┃ +; ┣━━┫ P ┃ +; ┃ 1 ┃ E ┃ +; ┣━━┫ ┃ +; ┃ 0 ┃ ┃ +; ------ ┣━━╋━━┫ +; ┃ 23 ┃ ┃ +; ┣━━┫ ┃ +; ┃ 22 ┃ ┃ +; ┣━━┫ 段 ┃ +; +; 字节 ┆ ┆ 基 ┆ +; 2, 3, 4 +; ┣━━┫ 址 ┃ +; ┃ 1 ┃ ① ┃ +; ┣━━┫ ┃ +; ┃ 0 ┃ ┃ +; ------ ┣━━╋━━┫ +; ┃ 15 ┃ ┃ +; ┣━━┫ ┃ +; ┃ 14 ┃ ┃ +; ┣━━┫ 段 ┃ +; +; 字节 0,1┆ ┆ 界 ┆ +; +; ┣━━┫ 限 ┃ +; ┃ 1 ┃ ① ┃ +; ┣━━┫ ┃ +; ┃ 0 ┃ ┃ +; ------ ┗━━┻━━┛低地址 +; + + +; 图示二 + +; 高地址………………………………………………………………………低地址 + +; | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | +; |7654321076543210765432107654321076543210765432107654321076543210| <- 共 8 字节 +; |--------========--------========--------========--------========| +; ┏━━━┳━━━━━━━┳━━━━━━━━━━━┳━━━━━━━┓ +; ┃31..24┃ (见下图) ┃ 段基址(23..0) ┃ 段界限(15..0)┃ +; ┃ ┃ ┃ ┃ ┃ +; ┃ 基址2┃③│②│ ①┃基址1b│ 基址1a ┃ 段界限1 ┃ +; ┣━━━╋━━━┳━━━╋━━━━━━━━━━━╋━━━━━━━┫ +; ┃ %6 ┃ %5 ┃ %4 ┃ %3 ┃ %2 ┃ %1 ┃ +; ┗━━━┻━━━┻━━━┻━━━┻━━━━━━━┻━━━━━━━┛ +; │ \_________ +; │ \__________________ +; │ \________________________________________________ +; │ \ +; ┏━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┓ +; ┃ 7 ┃ 6 ┃ 5 ┃ 4 ┃ 3 ┃ 2 ┃ 1 ┃ 0 ┃ 7 ┃ 6 ┃ 5 ┃ 4 ┃ 3 ┃ 2 ┃ 1 ┃ 0 ┃ +; ┣━━╋━━╋━━╋━━╋━━┻━━┻━━┻━━╋━━╋━━┻━━╋━━╋━━┻━━┻━━┻━━┫ +; ┃ G ┃ D ┃ 0 ┃ AVL┃ 段界限 2 (19..16) ┃ P ┃ DPL ┃ S ┃ TYPE ┃ +; ┣━━┻━━┻━━┻━━╋━━━━━━━━━━━╋━━┻━━━━━┻━━┻━━━━━━━━━━━┫ +; ┃ ③: 属性 2 ┃ ②: 段界限 2 ┃ ①: 属性1 ┃ +; ┗━━━━━━━━━━━┻━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━┛ +; 高地址 低地址 +; +; + +; 说明: +; +; (1) P: 存在(Present)位。 +; P=1 表示描述符对地址转换是有效的,或者说该描述符所描述的段存在,即在内存中; +; P=0 表示描述符对地址转换无效,即该段不存在。使用该描述符进行内存访问时会引起异常。 +; +; (2) DPL: 表示描述符特权级(Descriptor Privilege level),共2位。它规定了所描述段的特权级,用于特权检查,以决定对该段能否访问。 +; +; (3) S: 说明描述符的类型。 +; 对于存储段描述符而言,S=1,以区别与系统段描述符和门描述符(S=0)。 +; +; (4) TYPE: 说明存储段描述符所描述的存储段的具体属性。 +; +; +; 数据段类型 类型值 说明 +; ---------------------------------- +; 0 只读 +; 1 只读、已访问 +; 2 读/写 +; 3 读/写、已访问 +; 4 只读、向下扩展 +; 5 只读、向下扩展、已访问 +; 6 读/写、向下扩展 +; 7 读/写、向下扩展、已访问 +; +; +; 类型值 说明 +; 代码段类型 ---------------------------------- +; 8 只执行 +; 9 只执行、已访问 +; A 执行/读 +; B 执行/读、已访问 +; C 只执行、一致码段 +; D 只执行、一致码段、已访问 +; E 执行/读、一致码段 +; F 执行/读、一致码段、已访问 +; +; +; 系统段类型 类型编码 说明 +; ---------------------------------- +; 0 <未定义> +; 1 可用286TSS +; 2 LDT +; 3 忙的286TSS +; 4 286调用门 +; 5 任务门 +; 6 286中断门 +; 7 286陷阱门 +; 8 未定义 +; 9 可用386TSS +; A <未定义> +; B 忙的386TSS +; C 386调用门 +; D <未定义> +; E 386中断门 +; F 386陷阱门 +; +; (5) G: 段界限粒度(Granularity)位。 +; G=0 表示界限粒度为字节; +; G=1 表示界限粒度为4K 字节。 +; 注意,界限粒度只对段界限有效,对段基地址无效,段基地址总是以字节为单位。 +; +; (6) D: D位是一个很特殊的位,在描述可执行段、向下扩展数据段或由SS寄存器寻址的段(通常是堆栈段)的三种描述符中的意义各不相同。 +; ⑴ 在描述可执行段的描述符中,D位决定了指令使用的地址及操作数所默认的大小。 +; ① D=1表示默认情况下指令使用32位地址及32位或8位操作数,这样的代码段也称为32位代码段; +; ② D=0 表示默认情况下,使用16位地址及16位或8位操作数,这样的代码段也称为16位代码段,它与80286兼容。可以使用地址大小前缀和操作数大小前缀分别改变默认的地址或操作数的大小。 +; ⑵ 在向下扩展数据段的描述符中,D位决定段的上部边界。 +; ① D=1表示段的上部界限为4G; +; ② D=0表示段的上部界限为64K,这是为了与80286兼容。 +; ⑶ 在描述由SS寄存器寻址的段描述符中,D位决定隐式的堆栈访问指令(如PUSH和POP指令)使用何种堆栈指针寄存器。 +; ① D=1表示使用32位堆栈指针寄存器ESP; +; ② D=0表示使用16位堆栈指针寄存器SP,这与80286兼容。 +; +; (7) AVL: 软件可利用位。80386对该位的使用未左规定,Intel公司也保证今后开发生产的处理器只要与80386兼容,就不会对该位的使用做任何定义或规定。 +; + + +;---------------------------------------------------------------------------- +; 描述符类型值说明 +; 其中: +; DA_ : Descriptor Attribute +; D : 数据段 +; C : 代码段 +; S : 系统段 +; R : 只读 +; RW : 读写 +; A : 已访问 +; 其它 : 可按照字面意思理解 +;---------------------------------------------------------------------------- +DA_32 EQU 4000h ; 32 位段 +DA_LIMIT_4K EQU 8000h ; 段界限粒度为 4K 字节 + +DA_DPL0 EQU 00h ; DPL = 0 +DA_DPL1 EQU 20h ; DPL = 1 +DA_DPL2 EQU 40h ; DPL = 2 +DA_DPL3 EQU 60h ; DPL = 3 +;---------------------------------------------------------------------------- +; 存储段描述符类型值说明 +;---------------------------------------------------------------------------- +DA_DR EQU 90h ; 存在的只读数据段类型值 +DA_DRW EQU 92h ; 存在的可读写数据段属性值 +DA_DRWA EQU 93h ; 存在的已访问可读写数据段类型值 +DA_C EQU 98h ; 存在的只执行代码段属性值 +DA_CR EQU 9Ah ; 存在的可执行可读代码段属性值 +DA_CCO EQU 9Ch ; 存在的只执行一致代码段属性值 +DA_CCOR EQU 9Eh ; 存在的可执行可读一致代码段属性值 +;---------------------------------------------------------------------------- +; 系统段描述符类型值说明 +;---------------------------------------------------------------------------- +DA_LDT EQU 82h ; 局部描述符表段类型值 +DA_TaskGate EQU 85h ; 任务门类型值 +DA_386TSS EQU 89h ; 可用 386 任务状态段类型值 +DA_386CGate EQU 8Ch ; 386 调用门类型值 +DA_386IGate EQU 8Eh ; 386 中断门类型值 +DA_386TGate EQU 8Fh ; 386 陷阱门类型值 +;---------------------------------------------------------------------------- + + +; 选择子图示: +; ┏━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┓ +; ┃ 15 ┃ 14 ┃ 13 ┃ 12 ┃ 11 ┃ 10 ┃ 9 ┃ 8 ┃ 7 ┃ 6 ┃ 5 ┃ 4 ┃ 3 ┃ 2 ┃ 1 ┃ 0 ┃ +; ┣━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━╋━━╋━━┻━━┫ +; ┃ 描述符索引 ┃ TI ┃ RPL ┃ +; ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━┻━━━━━┛ +; +; RPL(Requested Privilege Level): 请求特权级,用于特权检查。 +; +; TI(Table Indicator): 引用描述符表指示位 +; TI=0 指示从全局描述符表GDT中读取描述符; +; TI=1 指示从局部描述符表LDT中读取描述符。 +; + +;---------------------------------------------------------------------------- +; 选择子类型值说明 +; 其中: +; SA_ : Selector Attribute + +SA_RPL0 EQU 0 ; ┓ +SA_RPL1 EQU 1 ; ┣ RPL +SA_RPL2 EQU 2 ; ┃ +SA_RPL3 EQU 3 ; ┛ + +SA_TIG EQU 0 ; ┓TI +SA_TIL EQU 4 ; ┛ +;---------------------------------------------------------------------------- + + +;---------------------------------------------------------------------------- +; 分页机制使用的常量说明 +;---------------------------------------------------------------------------- +PG_P EQU 1 ; 页存在属性位 +PG_RWR EQU 0 ; R/W 属性位值, 读/执行 +PG_RWW EQU 2 ; R/W 属性位值, 读/写/执行 +PG_USS EQU 0 ; U/S 属性位值, 系统级 +PG_USU EQU 4 ; U/S 属性位值, 用户级 +;---------------------------------------------------------------------------- + + + + +; ========================================= +; FLAGS - Intel 8086 Family Flags Register +; ========================================= +; +; |11|10|F|E|D|C|B|A|9|8|7|6|5|4|3|2|1|0| +; | | | | | | | | | | | | | | | | | '--- CF……Carry Flag +; | | | | | | | | | | | | | | | | '--- 1 +; | | | | | | | | | | | | | | | '--- PF……Parity Flag +; | | | | | | | | | | | | | | '--- 0 +; | | | | | | | | | | | | | '--- AF……Auxiliary Flag +; | | | | | | | | | | | | '--- 0 +; | | | | | | | | | | | '--- ZF……Zero Flag +; | | | | | | | | | | '--- SF……Sign Flag +; | | | | | | | | | '--- TF……Trap Flag (Single Step) +; | | | | | | | | '--- IF……Interrupt Flag +; | | | | | | | '--- DF……Direction Flag +; | | | | | | '--- OF……Overflow flag +; | | | | '----- IOPL……I/O Privilege Level (286+ only) +; | | | '----- NT……Nested Task Flag (286+ only) +; | | '----- 0 +; | '----- RF……Resume Flag (386+ only) +; '------ VM……Virtual Mode Flag (386+ only) +; +; 注: see PUSHF POPF STI CLI STD CLD +; + + +; 宏 ------------------------------------------------------------------------------------------------------ +; +; 描述符 +; usage: Descriptor Base, Limit, Attr +; Base: dd +; Limit: dd (low 20 bits available) +; Attr: dw (lower 4 bits of higher byte are always 0) +%macro Descriptor 3 + dw %2 & 0FFFFh ; 段界限 1 (2 字节) + dw %1 & 0FFFFh ; 段基址 1 (2 字节) + db (%1 >> 16) & 0FFh ; 段基址 2 (1 字节) + dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 属性 1 + 段界限 2 + 属性 2 (2 字节) + db (%1 >> 24) & 0FFh ; 段基址 3 (1 字节) +%endmacro ; 共 8 字节 +; +; 门 +; usage: Gate Selector, Offset, DCount, Attr +; Selector: dw +; Offset: dd +; DCount: db +; Attr: db +%macro Gate 4 + dw (%2 & 0FFFFh) ; 偏移 1 (2 字节) + dw %1 ; 选择子 (2 字节) + dw (%3 & 1Fh) | ((%4 << 8) & 0FF00h) ; 属性 (2 字节) + dw ((%2 >> 16) & 0FFFFh) ; 偏移 2 (2 字节) +%endmacro ; 共 8 字节 +; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/boot/grub/loader.asm b/boot/grub/loader.asm new file mode 100644 index 0000000..08cc938 --- /dev/null +++ b/boot/grub/loader.asm @@ -0,0 +1,1064 @@ + +; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +; loader.asm +; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +; Forrest Yu, 2005 +; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + +org 0100h + + jmp LABEL_START ; Start + +; 下面是 FAT12 磁盘的头, 之所以包含它是因为下面用到了磁盘的一些信息 +%include "fat32.inc" +%include "loader.inc" +%include "pm.inc" + + +; GDT ------------------------------------------------------------------------------------------------------------------------------------------------------------ +; 段基址 段界限 , 属性 +LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符 +LABEL_DESC_FLAT_C: Descriptor 0, 0fffffh, DA_CR | DA_32 | DA_LIMIT_4K ; 0 ~ 4G +LABEL_DESC_FLAT_RW: Descriptor 0, 0fffffh, DA_DRW | DA_32 | DA_LIMIT_4K ; 0 ~ 4G +LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW | DA_DPL3 ; 显存首地址 +; GDT ------------------------------------------------------------------------------------------------------------------------------------------------------------ + +GdtLen equ $ - LABEL_GDT +GdtPtr dw GdtLen - 1 ; 段界限 + dd BaseOfLoaderPhyAddr + LABEL_GDT ; 基地址 + +; GDT 选择子 ---------------------------------------------------------------------------------- +SelectorFlatC equ LABEL_DESC_FLAT_C - LABEL_GDT +SelectorFlatRW equ LABEL_DESC_FLAT_RW - LABEL_GDT +SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT + SA_RPL3 +; GDT 选择子 ---------------------------------------------------------------------------------- + +LABEL_START: ; <--- 从这里开始 ************* + cld + mov ax, cs + mov ds, ax + mov es, ax + mov ss, ax + mov sp, STACK_ADDR + mov bp, BaseOfStack + + mov dword [bp - DAP_SECTOR_HIGH ], 00h + mov byte [bp - DAP_RESERVED1 ], 00h + mov byte [bp - DAP_RESERVED2 ], 00h + mov byte [bp - DAP_PACKET_SIZE ], 10h + mov byte [bp - DAP_READ_SECTORS], 01h + mov word [bp - DAP_BUFFER_SEG ], 0x09000 + + ; 得到内存数 + mov ebx, 0 ; ebx = 后续值, 开始时需为 0 + mov di, _MemChkBuf ; es:di 指向一个地址范围描述符结构(Address Range Descriptor Structure) +.MemChkLoop: + mov eax, 0E820h ; eax = 0000E820h + mov ecx, 20 ; ecx = 地址范围描述符结构的大小 + mov edx, 0534D4150h ; edx = 'SMAP' + int 15h ; int 15h + jc .MemChkFail + add di, 20 + inc dword [_dwMCRNumber] ; dwMCRNumber = ARDS 的个数 + cmp ebx, 0 + jne .MemChkLoop + jmp .MemChkOK +.MemChkFail: + mov dword [_dwMCRNumber], 0 +.MemChkOK: + + jmp _SEARCH_LOADER + +_MISSING_LOADER: +_DISK_ERROR: + jmp $ + +ReadSector: + pusha + mov ah, 42h + lea si, [BP - DAP_PACKET_SIZE] + mov dl, [BS_DriveNum] + int 13h + jc _DISK_ERROR + popa + ret + +_SEARCH_LOADER: + mov word [bp - DAP_BUFFER_OFF], DATA_BUF_OFF + mov eax, dword [BS_RootClus] + mov dword [bp - CURRENT_CLUSTER], eax + +_NEXT_ROOT_CLUSTER: + dec eax + dec eax + xor ebx, ebx + mov bl, byte [BPB_SecPerClu] + mul ebx + add eax, DATA_START_SECTOR + mov dword [BP - DAP_SECTOR_LOW], eax + mov dl, [BPB_SecPerClu] + +_NEXT_ROOT_SECTOR: + call ReadSector + + mov di, DATA_BUF_OFF + mov bl, DIR_PER_SECTOR + +_NEXT_ROOT_ENTRY: + cmp byte [di], DIR_NAME_FREE + jz _MISSING_LOADER + + push di; + mov si, KernelFileName + mov cx, 10 + repe cmpsb + jcxz _FOUND_LOADER + + pop di + add di, DIR_ENTRY_SIZE + dec bl + jnz _NEXT_ROOT_ENTRY + + dec dl + jz _CHECK_NEXT_ROOT_CLUSTER + inc dword [bp - DAP_SECTOR_LOW] + jmp _NEXT_ROOT_SECTOR + +_CHECK_NEXT_ROOT_CLUSTER: + + ; 计算FAT所在的簇号和偏移 + ; FatOffset = ClusterNum*4 + XOR EDX,EDX + MOV EAX,DWORD[BP - CURRENT_CLUSTER] + SHL EAX,2 + XOR ECX,ECX + MOV CX,WORD [ BPB_BytesPerSec ] + DIV ECX ; EAX = Sector EDX = OFFSET + ADD EAX, FAT_START_SECTOR + MOV DWORD [ BP - DAP_SECTOR_LOW ], EAX + + call ReadSector + + ; 检查下一个簇 + MOV DI,DX + ADD DI,DATA_BUF_OFF + MOV EAX,DWORD[DI] ; EAX = 下一个要读的簇号 + AND EAX,CLUSTER_MASK + MOV DWORD[ BP - CURRENT_CLUSTER ],EAX + CMP EAX,CLUSTER_LAST ; CX >= 0FFFFFF8H,则意味着没有更多的簇了 + JB _NEXT_ROOT_CLUSTER + JMP _MISSING_LOADER + +_FOUND_LOADER: + ; 目录结构地址放在DI中 + pop di + xor eax, eax + mov ax, [di + OFF_START_CLUSTER_HIGH] ; 起始簇号高32位 + shl ax, 16 + mov ax, [di + OFF_START_CLUSTER_LOW] ; 起始簇号低32位 + mov dword [ bp - CURRENT_CLUSTER ], eax + mov cx, BaseOfKernelFile ; CX = 缓冲区段地址 + +_NEXT_DATA_CLUSTER: + ; 根据簇号计算扇区号 + DEC EAX + DEC EAX + XOR EBX,EBX + MOV BL, BYTE [ BPB_SecPerClu ] + MUL EBX + ADD EAX, DATA_START_SECTOR + MOV DWORD[ BP - DAP_SECTOR_LOW ], EAX + MOV BL , BYTE [BPB_SecPerClu] + + ; 设置缓冲区 + MOV WORD [ BP - DAP_BUFFER_SEG ], CX + MOV WORD [ BP - DAP_BUFFER_OFF ], OffsetOfKernelFile + +_NEXT_DATA_SECTOR: + ; 读取簇中的每个扇区(内层循环) + ; 注意 : 通过检查文件大小,可以避免读取最后一个不满簇的所有大小 + call ReadSector + + ; 更新地址,继续读取 + MOV AX, WORD [BPB_BytesPerSec] + ADD WORD [BP - DAP_BUFFER_OFF], ax + INC DWORD [BP - DAP_SECTOR_LOW] ; 递增扇区号 + DEC BL ; 内层循环计数 + JNZ _NEXT_DATA_SECTOR + + ; 更新读取下一个簇的缓冲区地址 + MOV CL, BYTE [ BPB_SecPerClu ] + MOV AX, WORD [BPB_BytesPerSec] + SHR AX, 4 + MUL CL + ADD AX, WORD [ BP - DAP_BUFFER_SEG ] + MOV CX, AX ; 保存下一个簇的缓冲区段地址 + + ;==================================================================== + ; 检查是否还有下一个簇(读取FAT表的相关信息) + ; LET N = 数据簇号 + ; THUS FAT_BYTES = N*4 (FAT32) + ; FAT_SECTOR = FAT_BYTES / BPB_BytesPerSec + ; FAT_OFFSET = FAT_BYTES % BPB_BytesPerSec + ;==================================================================== + + ; 计算FAT所在的簇号和偏移 + MOV EAX,DWORD [BP - CURRENT_CLUSTER] + XOR EDX,EDX + SHL EAX,2 + XOR EBX,EBX + MOV BX,WORD [ BPB_BytesPerSec ] + DIV EBX ; EAX = Sector EDX = Offset + + ; 设置缓冲区地址 + ADD EAX, FAT_START_SECTOR + MOV DWORD [ BP - DAP_SECTOR_LOW ], EAX + MOV WORD [BP - DAP_BUFFER_SEG ], 0x09000 + MOV WORD [BP - DAP_BUFFER_OFF ], DATA_BUF_OFF + + ; 读取扇区 + CALL ReadSector + + ; 检查下一个簇 + MOV DI,DX + ADD DI,DATA_BUF_OFF + MOV EAX,DWORD[DI] ; EAX = 下一个要读的簇号 + AND EAX,CLUSTER_MASK + MOV DWORD[ BP - CURRENT_CLUSTER ],EAX + CMP EAX,CLUSTER_LAST ; CX >= 0FFFFFF8H,则意味着没有更多的簇了 + JB _NEXT_DATA_CLUSTER + + jmp EXE_SEARCH_LOADER + +EXE_MISSING_LOADER: +EXE_DISK_ERROR: + jmp $ + +EXE_SEARCH_LOADER: + mov word [bp - DAP_BUFFER_OFF], DATA_BUF_OFF + mov eax, dword [BS_RootClus] + mov dword [bp - CURRENT_CLUSTER], eax + +EXE_NEXT_ROOT_CLUSTER: + dec eax + dec eax + xor ebx, ebx + mov bl, byte [BPB_SecPerClu] + mul ebx + add eax, DATA_START_SECTOR + mov dword [BP - DAP_SECTOR_LOW], eax + mov dl, [BPB_SecPerClu] + +EXE_NEXT_ROOT_SECTOR: + call ReadSector + + mov di, DATA_BUF_OFF + mov bl, DIR_PER_SECTOR + +EXE_NEXT_ROOT_ENTRY: + cmp byte [di], DIR_NAME_FREE + jz EXE_MISSING_LOADER + + push di; + mov si, EchoFileName + mov cx, 10 + repe cmpsb + jcxz EXE_FOUND_LOADER + + pop di + add di, DIR_ENTRY_SIZE + dec bl + jnz EXE_NEXT_ROOT_ENTRY + + dec dl + jz EXE_CHECK_NEXT_ROOT_CLUSTER + inc dword [bp - DAP_SECTOR_LOW] + jmp EXE_NEXT_ROOT_SECTOR + +EXE_CHECK_NEXT_ROOT_CLUSTER: + + ; 计算FAT所在的簇号和偏移 + ; FatOffset = ClusterNum*4 + XOR EDX,EDX + MOV EAX,DWORD[BP - CURRENT_CLUSTER] + SHL EAX,2 + XOR ECX,ECX + MOV CX,WORD [ BPB_BytesPerSec ] + DIV ECX ; EAX = Sector EDX = OFFSET + ADD EAX, FAT_START_SECTOR + MOV DWORD [ BP - DAP_SECTOR_LOW ], EAX + + call ReadSector + + ; 检查下一个簇 + MOV DI,DX + ADD DI,DATA_BUF_OFF + MOV EAX,DWORD[DI] ; EAX = 下一个要读的簇号 + AND EAX,CLUSTER_MASK + MOV DWORD[ BP - CURRENT_CLUSTER ],EAX + CMP EAX,CLUSTER_LAST ; CX >= 0FFFFFF8H,则意味着没有更多的簇了 + JB EXE_NEXT_ROOT_CLUSTER + JMP EXE_MISSING_LOADER + +EXE_FOUND_LOADER: + ; 目录结构地址放在DI中 + pop di + xor eax, eax + mov ax, [di + OFF_START_CLUSTER_HIGH] ; 起始簇号高32位 + shl ax, 16 + mov ax, [di + OFF_START_CLUSTER_LOW] ; 起始簇号低32位 + mov dword [ bp - CURRENT_CLUSTER ], eax + mov cx, BaseOfEchoFile ; CX = 缓冲区段地址 + +EXE_NEXT_DATA_CLUSTER: + ; 根据簇号计算扇区号 + DEC EAX + DEC EAX + XOR EBX,EBX + MOV BL, BYTE [ BPB_SecPerClu ] + MUL EBX + ADD EAX, DATA_START_SECTOR + MOV DWORD[ BP - DAP_SECTOR_LOW ], EAX + MOV BL , BYTE [BPB_SecPerClu] + + ; 设置缓冲区 + MOV WORD [ BP - DAP_BUFFER_SEG ], CX + MOV WORD [ BP - DAP_BUFFER_OFF ], OffsetOfEchoFile + +EXE_NEXT_DATA_SECTOR: + ; 读取簇中的每个扇区(内层循环) + ; 注意 : 通过检查文件大小,可以避免读取最后一个不满簇的所有大小 + call ReadSector + + ; 更新地址,继续读取 + MOV AX, WORD [BPB_BytesPerSec] + ADD WORD [BP - DAP_BUFFER_OFF], ax + INC DWORD [BP - DAP_SECTOR_LOW] ; 递增扇区号 + DEC BL ; 内层循环计数 + JNZ EXE_NEXT_DATA_SECTOR + + ; 更新读取下一个簇的缓冲区地址 + MOV CL, BYTE [ BPB_SecPerClu ] + MOV AX, WORD [BPB_BytesPerSec] + SHR AX, 4 + MUL CL + ADD AX, WORD [ BP - DAP_BUFFER_SEG ] + MOV CX, AX ; 保存下一个簇的缓冲区段地址 + + ;==================================================================== + ; 检查是否还有下一个簇(读取FAT表的相关信息) + ; LET N = 数据簇号 + ; THUS FAT_BYTES = N*4 (FAT32) + ; FAT_SECTOR = FAT_BYTES / BPB_BytesPerSec + ; FAT_OFFSET = FAT_BYTES % BPB_BytesPerSec + ;==================================================================== + + ; 计算FAT所在的簇号和偏移 + MOV EAX,DWORD [BP - CURRENT_CLUSTER] + XOR EDX,EDX + SHL EAX,2 + XOR EBX,EBX + MOV BX,WORD [ BPB_BytesPerSec ] + DIV EBX ; EAX = Sector EDX = Offset + + ; 设置缓冲区地址 + ADD EAX, FAT_START_SECTOR + MOV DWORD [ BP - DAP_SECTOR_LOW ], EAX + MOV WORD [BP - DAP_BUFFER_SEG ], 0x09000 + MOV WORD [BP - DAP_BUFFER_OFF ], DATA_BUF_OFF + + ; 读取扇区 + CALL ReadSector + + ; 检查下一个簇 + MOV DI,DX + ADD DI,DATA_BUF_OFF + MOV EAX,DWORD[DI] ; EAX = 下一个要读的簇号 + AND EAX,CLUSTER_MASK + MOV DWORD[ BP - CURRENT_CLUSTER ],EAX + CMP EAX,CLUSTER_LAST ; CX >= 0FFFFFF8H,则意味着没有更多的簇了 + JB EXE_NEXT_DATA_CLUSTER + +EXE_RUN_LOADER: + mov dl, [BS_DriveNum] + +;;add end add by liang 2016.04.20 +; 下面准备跳入保护模式 ------------------------------------------- + +; 加载 GDTR + lgdt [GdtPtr] + +; 关中断 + cli + +; 打开地址线A20 + in al, 92h + or al, 00000010b + out 92h, al + +; 准备切换到保护模式 + mov eax, cr0 + or eax, 1 + mov cr0, eax + +; 真正进入保护模式 + jmp dword SelectorFlatC:(BaseOfLoaderPhyAddr+LABEL_PM_START) + + +;============================================================================ +;变量 +;---------------------------------------------------------------------------- +wSectorNo dw 0 ; 要读取的扇区号 +bOdd db 0 ; 奇数还是偶数 +dwKernelSize dd 0 ; KERNEL.BIN 文件大小 +_dwEchoSize dd 0 ;echo size add by liang 2016.04.20 +;============================================================================ +;字符串 +;---------------------------------------------------------------------------- +KernelFileName db "KERNEL BIN", 0 ; KERNEL.BIN 之文件名 +EchoFileName db "INIT BIN", 0 ;add by liang 2016.04.20 ;edit by visual 2016.5.16 +; 为简化代码, 下面每个字符串的长度均为 MessageLength +MessageLength equ 9 +LoadMessage: db "Loading " +Message1 db "Ready. " +Message2 db "No KERNEL" +Message3 db "exLoading" ;add by liang 2016.04.20 +Message4 db "exReady. " ;add by liang 2016.04.20 +Message5 db "No ECHO " ;add by liang 2016.04.20 +;============================================================================ + +;---------------------------------------------------------------------------- +; 函数名: DispStrRealMode +;---------------------------------------------------------------------------- +; 运行环境: +; 实模式(保护模式下显示字符串由函数 DispStr 完成) +; 作用: +; 显示一个字符串, 函数开始时 dh 中应该是字符串序号(0-based) +DispStrRealMode: + mov ax, MessageLength + mul dh + add ax, LoadMessage + mov bp, ax ; ┓ + mov ax, ds ; ┣ ES:BP = 串地址 + mov es, ax ; ┛ + mov cx, MessageLength ; CX = 串长度 + mov ax, 01301h ; AH = 13, AL = 01h + mov bx, 0007h ; 页号为0(BH = 0) 黑底白字(BL = 07h) + mov dl, 0 + add dh, 3 ; 从第 3 行往下显示 + int 10h ; int 10h + ret +;---------------------------------------------------------------------------- + + +;---------------------------------------------------------------------------- +; 函数名: KillMotor +;---------------------------------------------------------------------------- +; 作用: +; 关闭软驱马达 +KillMotor: + push dx + mov dx, 03F2h + mov al, 0 + out dx, al + pop dx + ret +;---------------------------------------------------------------------------- + +; 从此以后的代码在保护模式下执行 ---------------------------------------------------- +; 32 位代码段. 由实模式跳入 --------------------------------------------------------- +[SECTION .s32] + +ALIGN 32 + +[BITS 32] + +LABEL_PM_START: + mov ax, SelectorVideo + mov gs, ax + mov ax, SelectorFlatRW + mov ds, ax + mov es, ax + mov fs, ax + mov ss, ax + mov esp, TopOfStack + + push szMemChkTitle + call DispStr + add esp, 4 + + call DispMemInfo + call getFreeMemInfo ;add by liang 2016.04.13 + call DispEchoSize ;add by liang 2016.04.21 + call SetupPaging + + ;mov ah, 0Fh ; 0000: 黑底 1111: 白字 + ;mov al, 'P' + ;mov [gs:((80 * 0 + 39) * 2)], ax ; 屏幕第 0 行, 第 39 列。 + + call InitKernel + + ;jmp $ + + ;*************************************************************** + jmp SelectorFlatC:KernelEntryPointPhyAddr ; 正式进入内核 * + ;*************************************************************** + ; 内存看上去是这样的: + ; ┃ ┃ + ; ┃ . ┃ + ; ┃ . ┃ + ; ┃ . ┃ + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■┃ + ; ┃■■■■■■Page Tables■■■■■■■■■■■■■■■■■■┃ + ; ┃■■■■■(大小由LOADER决定)■■■■■■■■■■■■■┃ + ; 00101000h ┃■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■┃ PageTblBase + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■┃ + ; 00100000h ┃■■■■Page Directory Table■■■■■■■■■■■■┃ PageDirBase <- 1M + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□┃ + ; F0000h ┃□□□□□□□System ROM□□□□□□□□□□□□□□□□□□□┃ + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□┃ + ; E0000h ┃□□□□Expansion of system ROM □□□□□□□□┃ + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□┃ + ; C0000h ┃□□□Reserved for ROM expansion□□□□□□□┃ + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□┃ B8000h ← gs + ; A0000h ┃□□□Display adapter reserved□□□□□□□□□┃ + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□┃ + ; 9FC00h ┃□□extended BIOS data area (EBDA)□□□□┃ + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■┃ + ; 90000h ┃■■■■■■■LOADER.BIN■■■■■■■■■■■■■■■■■■■┃ somewhere in LOADER ← esp + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■┃ + ; 80000h ┃■■■■■■■KERNEL.BIN■■■■■■■■■■■■■■■■■■■┃ + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■┃ + ; 30000h ┃■■■■■■■■KERNEL■■■■■■■■■■■■■■■■■■■■■■┃ 30400h ← KERNEL 入口 (KernelEntryPointPhyAddr) + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃ ┃ + ; 7E00h ┃ F R E E ┃ + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■┃ + ; 7C00h ┃■■■■■■BOOT SECTOR■■■■■■■■■■■■■■■■■■┃ + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃ ┃ + ; 500h ┃ F R E E ┃ + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□┃ + ; 400h ┃□□□□ROM BIOS parameter area □□□□□□□□┃ + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃◇◇◇◇◇◇◇◇◇◇◇◇◇◇◇◇◇◇┃ + ; 0h ┃◇◇◇◇◇◇Int Vectors◇◇◇◇◇◇┃ + ; ┗━━━━━━━━━━━━━━━━━━┛ ← cs, ds, es, fs, ss + ; + ; + ; ┏━━━┓ ┏━━━┓ + ; ┃■■■■■■┃ 我们使用 ┃□□□□□□┃ 不能使用的内存 + ; ┗━━━┛ ┗━━━┛ + ; ┏━━━┓ ┏━━━┓ + ; ┃ ┃ 未使用空间 ┃◇◇◇┃ 可以覆盖的内存 + ; ┗━━━┛ ┗━━━┛ + ; + ; 注:KERNEL 的位置实际上是很灵活的,可以通过同时改变 LOAD.INC 中的 KernelEntryPointPhyAddr 和 MAKEFILE 中参数 -Ttext 的值来改变。 + ; 比如,如果把 KernelEntryPointPhyAddr 和 -Ttext 的值都改为 0x400400,则 KERNEL 就会被加载到内存 0x400000(4M) 处,入口在 0x400400。 + ; + + + + +; ------------------------------------------------------------------------ +; 显示 AL 中的数字 +; ------------------------------------------------------------------------ +DispAL: + push ecx + push edx + push edi + + mov edi, [dwDispPos] + + mov ah, 0Fh ; 0000b: 黑底 1111b: 白字 + mov dl, al + shr al, 4 + mov ecx, 2 +.begin: + and al, 01111b + cmp al, 9 + ja .1 + add al, '0' + jmp .2 +.1: + sub al, 0Ah + add al, 'A' +.2: + mov [gs:edi], ax + add edi, 2 + + mov al, dl + loop .begin + ;add edi, 2 + + mov [dwDispPos], edi + + pop edi + pop edx + pop ecx + + ret +; DispAL 结束------------------------------------------------------------- + + +; ------------------------------------------------------------------------ +; 显示一个整形数 +; ------------------------------------------------------------------------ +DispInt: + mov eax, [esp + 4] + shr eax, 24 + call DispAL + + mov eax, [esp + 4] + shr eax, 16 + call DispAL + + mov eax, [esp + 4] + shr eax, 8 + call DispAL + + mov eax, [esp + 4] + call DispAL + + mov ah, 07h ; 0000b: 黑底 0111b: 灰字 + mov al, 'h' + push edi + mov edi, [dwDispPos] + mov [gs:edi], ax + add edi, 4 + mov [dwDispPos], edi + pop edi + + ret +; DispInt 结束------------------------------------------------------------ + +; ------------------------------------------------------------------------ +; 显示一个字符串 +; ------------------------------------------------------------------------ +DispStr: + push ebp + mov ebp, esp + push ebx + push esi + push edi + + mov esi, [ebp + 8] ; pszInfo + mov edi, [dwDispPos] + mov ah, 0Fh +.1: + lodsb + test al, al + jz .2 + cmp al, 0Ah ; 是回车吗? + jnz .3 + push eax + mov eax, edi + mov bl, 160 + div bl + and eax, 0FFh + inc eax + mov bl, 160 + mul bl + mov edi, eax + pop eax + jmp .1 +.3: + mov [gs:edi], ax + add edi, 2 + jmp .1 + +.2: + mov [dwDispPos], edi + + pop edi + pop esi + pop ebx + pop ebp + ret +; DispStr 结束------------------------------------------------------------ + +; ------------------------------------------------------------------------ +; 换行 +; ------------------------------------------------------------------------ +DispReturn: + push szReturn + call DispStr ;printf("\n"); + add esp, 4 + + ret +; DispReturn 结束--------------------------------------------------------- + + +; ------------------------------------------------------------------------ +; 内存拷贝,仿 memcpy +; ------------------------------------------------------------------------ +; void* MemCpy(void* es:pDest, void* ds:pSrc, int iSize); +; ------------------------------------------------------------------------ +MemCpy: + push ebp + mov ebp, esp + + push esi + push edi + push ecx + + mov edi, [ebp + 8] ; Destination + mov esi, [ebp + 12] ; Source + mov ecx, [ebp + 16] ; Counter +.1: + cmp ecx, 0 ; 判断计数器 + jz .2 ; 计数器为零时跳出 + + mov al, [ds:esi] ; ┓ + inc esi ; ┃ + ; ┣ 逐字节移动 + mov byte [es:edi], al ; ┃ + inc edi ; ┛ + + dec ecx ; 计数器减一 + jmp .1 ; 循环 +.2: + mov eax, [ebp + 8] ; 返回值 + + pop ecx + pop edi + pop esi + mov esp, ebp + pop ebp + + ret ; 函数结束,返回 +; MemCpy 结束------------------------------------------------------------- + +;;;;;;;;;;;;;;;;;add begin add by liang 2016.04.13;;;;;;;;;;;;;;;;;;;;; + +memtest: + push edi + push esi + + mov esi,0xaa55aa55 + mov edi,0x55aa55aa + mov eax,[esp+8+4] ;start +.mt_loop: + mov ebx,eax + add ebx,0xffc ;检查每4KB最后4B + mov ecx,[ebx] ;保存原值 + mov [ebx],esi ;写入 + xor dword [ebx],0xffffffff ;反转 + cmp edi,[ebx] ;检查反转结果是否正确 + jne .mt_fin + xor dword [ebx],0xffffffff ;再次反转 + cmp esi,[ebx] ;检查是否恢复初始值 + jne .mt_fin + mov [ebx],ecx ;恢复为修改前的值 + add eax,0x1000 ;每循环一次加0x1000 + cmp eax,[esp+8+8] ;未到end,循环 + jbe .mt_loop + pop esi + pop edi + ret + +.mt_fin: + mov [ebx],ecx ;恢复为修改前的值 + pop esi + pop edi + ret + +getFreeMemInfo: + push eax + push ebx + push ecx + push edx + + mov eax,cr0 + or eax,0x60000000 ;禁止缓存 + mov cr0,eax + + mov ebx,0x00000000 ;检查0到32M + mov ecx,0x02000000 + mov edx,FMIBuff ;存于0x007ff000处 + +.fmi_loop: + push ecx + push ebx + call memtest + pop ebx + pop ecx + mov [edx+4],eax ;留出前4B存放dwFMINumber + add edx,4 + add eax,0x1000 + mov ebx,eax + inc dword [dwFMINumber] ;循环次数,即返回值个数 + cmp ebx,ecx + jb .fmi_loop + + mov ebx,[dwFMINumber] + mov edx,FMIBuff + mov [edx],ebx ;前4B存放返回值个数 + mov ebx,[FMIBuff] + push ebx + call DispInt ;打印返回值个数 + add esp,4 + + mov eax,cr0 + and eax,0x9fffffff ;恢复缓存 + mov cr0,eax + + pop edx + pop ecx + pop ebx + pop eax + ret + + +;;;;;;;;;;;;;;;;;;;;;;;;;;add end add by liang 2016.04.13;;;;;;;;;;;;;;;;;;;;;;;;;; + +;;add begin add by liang 2016.04.21 +DispEchoSize: + push eax + mov eax,[dwEchoSize] + push eax + call DispInt + add esp,4 + pop eax + ret +;;add end add by liang 2016.04.21 + +; 显示内存信息 -------------------------------------------------------------- +DispMemInfo: + push esi + push edi + push ecx + + mov esi, MemChkBuf + mov ecx, [dwMCRNumber] ;for(int i=0;i<[MCRNumber];i++) // 每次得到一个ARDS(Address Range Descriptor Structure)结构 +.loop: ;{ + mov edx, 5 ; for(int j=0;j<5;j++) // 每次得到一个ARDS中的成员,共5个成员 + mov edi, ARDStruct ; { // 依次显示:BaseAddrLow,BaseAddrHigh,LengthLow,LengthHigh,Type +.1: ; + push dword [esi] ; + call DispInt ; DispInt(MemChkBuf[j*4]); // 显示一个成员 + pop eax ; + stosd ; ARDStruct[j*4] = MemChkBuf[j*4]; + add esi, 4 ; + dec edx ; + cmp edx, 0 ; + jnz .1 ; } + call DispReturn ; printf("\n"); + cmp dword [dwType], 1 ; if(Type == AddressRangeMemory) // AddressRangeMemory : 1, AddressRangeReserved : 2 + jne .2 ; { + mov eax, [dwBaseAddrLow] ; + add eax, [dwLengthLow] ; + cmp eax, [dwMemSize] ; if(BaseAddrLow + LengthLow > MemSize) + jb .2 ; + mov [dwMemSize], eax ; MemSize = BaseAddrLow + LengthLow; +.2: ; } + loop .loop ;} + ; + call DispReturn ;printf("\n"); + push szRAMSize ; + call DispStr ;printf("RAM size:"); + add esp, 4 ; + ; + push dword [dwMemSize] ; + call DispInt ;DispInt(MemSize); + add esp, 4 ; + + pop ecx + pop edi + pop esi + ret +; --------------------------------------------------------------------------- + +; 启动分页机制 -------------------------------------------------------------- +SetupPaging: + ; 根据内存大小计算应初始化多少PDE以及多少页表 + xor edx, edx + mov eax, [dwMemSize] + mov ebx, 400000h ; 400000h = 4M = 4096 * 1024, 一个页表对应的内存大小 + div ebx + mov ecx, eax ; 此时 ecx 为页表的个数,也即 PDE 应该的个数 + test edx, edx + jz .no_remainder + inc ecx ; 如果余数不为 0 就需增加一个页表 +.no_remainder: + push ecx ; 暂存页表个数 + mov dword[PageTblNumAddr],ecx ;将页表数写进这个物理地址 + + ; 为简化处理, 所有线性地址对应相等的物理地址. 并且不考虑内存空洞. + + ; 首先初始化页目录 + mov ax, SelectorFlatRW + mov es, ax + mov edi, PageDirBase ; 此段首地址为 PageDirBase + xor eax, eax + mov eax, PageTblBase | PG_P | PG_USU | PG_RWW +.1: + stosd + add eax, 4096 ; 为了简化, 所有页表在内存中是连续的. + loop .1 + +;;;;初始化3G处的页目录;;;;;;;;;;;;;;;; //add by visual 2016.5.10 + pop eax ;页表个数 + mov ecx,eax ;重新放到ecx里 + push ecx ;暂存页表个数 + mov ax, SelectorFlatRW + mov es, ax + mov eax, 3072 ;768*4 + add eax, PageDirBase ; + mov edi, eax ; 应该往页目录这个位置写: PageDirBase+768*4,即线性地址3G处 + + xor eax, eax ; 清0 + mov eax, ecx ; + mov ebx, 4096 ; + mul ebx ;跳过前ecx个页表,即PageTblBase+页表数*4096 + add eax, PageTblBase | PG_P | PG_USU | PG_RWW; +.1k: + stosd + add eax, 4096 ; 为了简化, 所有页表在内存中是连续的. + loop .1k +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + ; 再初始化所有页表(最开始处,一一映射的) + pop eax ; 页表个数 + push eax ;暂存页表个数 + mov ebx, 1024 ; 每个页表 1024 个 PTE + mul ebx + mov ecx, eax ; PTE个数 = 页表个数 * 1024 + mov edi, PageTblBase ; 此段首地址为 PageTblBase + xor eax, eax + mov eax, PG_P | PG_USU | PG_RWW +.2: + stosd + add eax, 4096 ; 每一页指向 4K 的空间 + loop .2 + +;;;;初始化3G处的页表;;;;;;;;;;;;;;;; //add by visual 2016.5.10 + ; 再初始化3G后的页表 + pop eax ; 页表个数 + mov ebx, 1024 ; 每个页表 1024 个 PTE + mul ebx + mov ecx, eax ; PTE个数 = 页表个数 * 1024 + + xor eax, eax ; 清0 + mov eax, ecx ; + mov ebx, 4 + mul ebx ;跳过前ecx个页表,即PageTblBase+页表数*4096 + add eax, PageTblBase ; 后面3G对应页表的起始位置为 PageTblBase+页表数*4096 + mov edi, eax + xor eax, eax + mov eax, PG_P | PG_USU | PG_RWW ;从0开始 +.2k: + stosd + add eax, 4096 ; 每一页指向 4K 的空间 + loop .2k +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + ;mov ah, 0Fh ; 0000: 黑底 1111: 白字 + ;mov al, 'P' + ;mov [gs:((80 * 0 + 39) * 2)], ax ; 屏幕第 0 行, 第 39 列。 + + ;启动页表机制 + mov eax, PageDirBase + mov cr3, eax + mov eax, cr0 + or eax, 80000000h + mov cr0, eax + jmp short .3 +.3: + nop + + ret +; 分页机制启动完毕 ---------------------------------------------------------- + + + +; InitKernel --------------------------------------------------------------------------------- +; 将 KERNEL.BIN 的内容经过整理对齐后放到新的位置 +; -------------------------------------------------------------------------------------------- +InitKernel: ; 遍历每一个 Program Header,根据 Program Header 中的信息来确定把什么放进内存,放到什么位置,以及放多少。 + xor esi, esi + mov cx, word [BaseOfKernelFilePhyAddr + 2Ch]; ┓ ecx <- pELFHdr->e_phnum + movzx ecx, cx ; ┛ + mov esi, [BaseOfKernelFilePhyAddr + 1Ch] ; esi <- pELFHdr->e_phoff + add esi, BaseOfKernelFilePhyAddr ; esi <- OffsetOfKernel + pELFHdr->e_phoff +.Begin: + mov eax, [esi + 0] + cmp eax, 0 ; PT_NULL + jz .NoAction + push dword [esi + 010h] ; size ┓ + mov eax, [esi + 04h] ; ┃ + add eax, BaseOfKernelFilePhyAddr ; ┣ ::memcpy( (void*)(pPHdr->p_vaddr), + push eax ; src ┃ uchCode + pPHdr->p_offset, + push dword [esi + 08h] ; dst ┃ pPHdr->p_filesz; + call MemCpy ; ┃ + add esp, 12 ; ┛ +.NoAction: + add esi, 020h ; esi += pELFHdr->e_phentsize + dec ecx + jnz .Begin + + ret +; InitKernel ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + +; SECTION .data1 之开始 --------------------------------------------------------------------------------------------- +[SECTION .data1] + +ALIGN 32 + +LABEL_DATA: +; 实模式下使用这些符号 +; 字符串 +_szMemChkTitle: db "BaseAddrL BaseAddrH LengthLow LengthHigh Type", 0Ah, 0 +_szRAMSize: db "RAM size:", 0 +_szReturn: db 0Ah, 0 +;; 变量 +_dwMCRNumber: dd 0 ; Memory Check Result +_dwDispPos: dd (80 * 6 + 0) * 2 ; 屏幕第 6 行, 第 0 列。 +_dwMemSize: dd 0 +_ARDStruct: ; Address Range Descriptor Structure + _dwBaseAddrLow: dd 0 + _dwBaseAddrHigh: dd 0 + _dwLengthLow: dd 0 + _dwLengthHigh: dd 0 + _dwType: dd 0 +_MemChkBuf: times 256 db 0 +_dwFMINumber: dd 0 ;add by liang 2016.04.13 + + +; +;; 保护模式下使用这些符号 +szMemChkTitle equ BaseOfLoaderPhyAddr + _szMemChkTitle +szRAMSize equ BaseOfLoaderPhyAddr + _szRAMSize +szReturn equ BaseOfLoaderPhyAddr + _szReturn +dwDispPos equ BaseOfLoaderPhyAddr + _dwDispPos +dwMemSize equ BaseOfLoaderPhyAddr + _dwMemSize +dwMCRNumber equ BaseOfLoaderPhyAddr + _dwMCRNumber +ARDStruct equ BaseOfLoaderPhyAddr + _ARDStruct + dwBaseAddrLow equ BaseOfLoaderPhyAddr + _dwBaseAddrLow + dwBaseAddrHigh equ BaseOfLoaderPhyAddr + _dwBaseAddrHigh + dwLengthLow equ BaseOfLoaderPhyAddr + _dwLengthLow + dwLengthHigh equ BaseOfLoaderPhyAddr + _dwLengthHigh + dwType equ BaseOfLoaderPhyAddr + _dwType +MemChkBuf equ BaseOfLoaderPhyAddr + _MemChkBuf +dwFMINumber equ BaseOfLoaderPhyAddr + _dwFMINumber ;add by liang 2016.04.13 +dwEchoSize equ BaseOfLoaderPhyAddr + _dwEchoSize ;add by liang 2016.04.21 + +; 堆栈就在数据段的末尾 +StackSpace: times 1000h db 0 +TopOfStack equ BaseOfLoaderPhyAddr + $ ; 栈顶 +; SECTION .data1 之结束 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + diff --git a/boot/mbr/boot.asm b/boot/mbr/boot.asm new file mode 100644 index 0000000..c73fe76 --- /dev/null +++ b/boot/mbr/boot.asm @@ -0,0 +1,543 @@ +;============================================================================================================================== +BaseOfStack equ 0x07c00 ; Boot状态下堆栈基地址 +STACK_ADDR equ 0x7bea ; 堆栈栈顶 + +BaseOfBoot equ 1000h ; added by mingxuan 2020-9-12 +OffsetOfBoot equ 7c00h ; load Boot sector to BaseOfBoot:OffsetOfBoot +OffsetOfActiPartStartSec equ 7e00h ; 活动分区的起始扇区号相对于BaseOfBoot的偏移量 ;added by mingxuan 2020-9-12 + ; 该变量来自分区表,保存在该内存地址,用于在os_boot和loader中查找FAT32文件 + +BOOT_FAT32_INFO equ 0x5A ;位于boot中的FAT32配置信息的长度 + ;added by mingxuan 2020-9-16 + +DATA_BUF_OFF equ 0x2000 ; 目录 被加载的缓冲区地址 + +OSLOADER_SEG equ 0x09000 ; 起始段地址 +OSLOADER_SEG_OFF equ 0x0100 + +;FAT_START_SECTOR equ 0x820 ; FAT表的起始扇区号 DWORD +;FAT_START_SECTOR equ 0x1020 ; FAT表的起始扇区号 DWORD ; for test 2020-9-10, mingxuan +;DATA_START_SECTOR equ 0xd6a ; 数据区起始扇区号 DWORD +;DATA_START_SECTOR equ 0x156a ; 数据区起始扇区号 DWORD ; for test 2020-9-10, mingxuan +;DATA_START_SECTOR equ 0x13a4 ; 数据区起始扇区号 DWORD ; for test 2020-9-13, mingxuan + +DIR_PER_SECTOR equ 0x10 ; 每个扇区所容纳的目录 BYTE + +; 扩展磁盘服务所使用的地址包 +DAP_SECTOR_HIGH equ 4 ; 起始扇区号的高32位 ( 每次调用需要重置 ) DWORD +DAP_SECTOR_LOW equ 8 ; 起始扇区号的低32位 ( 每次调用需要重置 ) DWORD + +DAP_BUFFER_SEG equ 10 ; 缓冲区段地址 ( 每次调用需要重置 ) WORD +DAP_BUFFER_OFF equ 12 ; 缓冲区偏移 ( 每次调用需要重置 ) WORD + +DAP_RESERVED2 equ 13 ; 保留字节 + +DAP_READ_SECTORS equ 14 ; 要处理的扇区数(1 - 127 ) +DAP_RESERVED1 equ 15 ; 保留字节 +DAP_PACKET_SIZE equ 16 ; 包的大小为16字节 + +CURRENT_CLUSTER equ 20 ; 当前正在处理的簇号 DWORD + +; 目录项结构 +OFF_START_CLUSTER_HIGH equ 20 ; 起始簇号高位 WORD +OFF_START_CLUSTER_LOW equ 26 ; 起始簇号低位 WORD + +; 相关常量 +DIR_NAME_FREE equ 0x00 ; 该项是空闲的 +DIR_ENTRY_SIZE equ 32 ; 每个目录项的尺寸 + +; 簇属性 +CLUSTER_MASK equ 0FFFFFFFH ; 簇号掩码 +CLUSTER_LAST equ 0FFFFFF8H ;0xFFFFFFF8-0xFFFFFFFF表示文件的最后一个簇 + +; added by mingxuan 2020-9-12 +BPB_BytesPerSec equ (OffsetOfBoot + 0xb) ;每扇区字节数 +BPB_SecPerClu equ (OffsetOfBoot + 0xd) ;每簇扇区数 +BPB_RsvdSecCnt equ (OffsetOfBoot + 0xe) ;保留扇区数 +BPB_NumFATs equ (OffsetOfBoot + 0x10) ;FAT表数 +BPB_RootEntCnt equ (OffsetOfBoot + 0x11) ;FAT32不使用 +BPB_TotSec16 equ (OffsetOfBoot + 0x13) ;扇区总数 +BPB_Media equ (OffsetOfBoot + 0x15) ;介质描述符 +BPB_FATSz16 equ (OffsetOfBoot + 0x16) ;每个FAT表的大小扇区数(FAT32不使用) +BPB_SecPerTrk equ (OffsetOfBoot + 0x18) ;每磁道扇区数 +BPB_NumHeads equ (OffsetOfBoot + 0x1a) ;磁头数 +BPB_HiddSec equ (OffsetOfBoot + 0x1c) ;分区已使用扇区数 +BPB_TotSec32 equ (OffsetOfBoot + 0x20) ;文件系统大小扇区数 + +BS_SecPerFAT equ (OffsetOfBoot + 0x24) ;每个FAT表大小扇区数 +BS_Flag equ (OffsetOfBoot + 0x28) ;标记 +BS_Version equ (OffsetOfBoot + 0x2a) ;版本号 +BS_RootClus equ (OffsetOfBoot + 0x2c) ;根目录簇号 +BS_FsInfoSec equ (OffsetOfBoot + 0x30) ;FSINFO扇区号 +BS_BackBootSec equ (OffsetOfBoot + 0x32) ;备份引导扇区位置 +BS_Unuse1 equ (OffsetOfBoot + 0x34) ;未使用 +;BS_Unuse2 equ (OffsetOfBoot + 0x40) ;未使用 ;deleted by mingxuan 2020-9-15 +;BS_Unuse3 equ (OffsetOfBoot + 0x41) ;未使用 ;deleted by mingxuan 2020-9-15 +BS_DriveNum equ (OffsetOfBoot + 0x40) ;设备号 +BS_Unuse4 equ (OffsetOfBoot + 0x41) ;未使用 +BS_ExtBootFlag equ (OffsetOfBoot + 0x42) ;扩展引导标志 +BS_VolNum equ (OffsetOfBoot + 0x43) ;卷序列号 +BS_VolName equ (OffsetOfBoot + 0x47) ;卷标 +;============================================================================================================================== + ;org 07c00h ;deleted by mingxuan 2020-9-16 + org (07c00h + BOOT_FAT32_INFO) ;FAT322规范规定第90~512个字节(共423个字节)是引导程序 + ;modified by mingxuan 2020-9-16 + + ;jmp START ;deleted by mingxuan 2020-9-15 + ;nop ;deleted by mingxuan 2020-9-15 + + FAT_START_SECTOR DD 0 ;FAT表的起始扇区号 ;added by mingxuan 2020-9-17 + DATA_START_SECTOR DD 0 ;数据区起始扇区号 ;added by mingxuan 2020-9-17 + +;deleted by mingxuan 2020-9-12 + ;BS_OEM DB 'mkfs.fat' ;文件系统标志 + + ;BPB_BytesPerSec DW 0x200 ;每扇区字节数 + ;BPB_SecPerClu DB 1 ;每簇扇区数 + ;BPB_RsvdSecCnt DW 0x20 ;保留扇区数 + ;BPB_NumFATs DB 2 ;FAT表数 + ;BPB_RootEntCnt DW 0 ;FAT32不使用 + ;BPB_TotSec16 DW 0 ;扇区总数 + ;BPB_Media DB 0xf8 ;介质描述符 + ;BPB_FATSz16 DW 0 ;每个FAT表的大小扇区数 + ;BPB_SecPerTrk DW 0x3f ;每磁道扇区数 + ;BPB_NumHeads DW 0xff ;磁头数 + ;BPB_HiddSec DD 0 ;分区已使用扇区数 + ;BPB_TotSec32 DD 0x015791 ;文件系统大小扇区数 + + ;BS_SecPerFAT DD 0x02a5 ;每个FAT表大小扇区数 + ;BS_Flag DW 0 ;标记 + ;BS_Version DW 0 ;版本号 + ;BS_RootClus DD 2 ;根目录簇号 + ;BS_FsInfoSec DW 1 ;FSINFO扇区号 + ;BS_BackBootSec DW 6 ;备份引导扇区位置 + ;BS_Unuse1 DD 0 ;未使用 + ;BS_Unuse2 DD 0 ;未使用 + ;BS_Unuse3 DD 0 ;未使用 + ;BS_DriveNum DB 0x80 ;设备号 + ;BS_Unuse4 DB 0x01 ;未使用 + ;BS_ExtBootFlag DB 0x29 ;扩展引导标志 + ;BS_VolNum DD 0xbe3a8ff5 ;卷序列号 + ;BS_VolName DB 'MZY hd boot' ;卷标 + +START: + cld + mov ax, cs + mov ds, ax + mov es, ax ;deleted by mingxuan 2020-9-13 + mov ss, ax + + ; 清屏 + ; for test, added by mingxuan 2020-9-15 + ; pusha + ; mov dx, 0184fh ; 右下角: (80, 50) + ; int 10h ; int 10h + ; popa + + ;added by mingxuan 2020-9-13 + ;mov ax, BaseOfBoot + ;mov fs, ax ;es存储BaseOfBoot,用于查找FAT32的配置信息 + + ;FAT_START_SECTOR DD ([fs:OffsetOfActiPartStartSec] + [fs:BPB_RsvdSecCnt]) + ; 计算FAT表的起始扇区号 ; added by mingxuan 2020-9-17 + mov eax, [ OffsetOfActiPartStartSec] + add ax, [ BPB_RsvdSecCnt ] + mov [FAT_START_SECTOR], eax + ; 计算数据区起始扇区号 ; added by mingxuan 2020-9-17 + add eax, [BS_SecPerFAT] + add eax, [BS_SecPerFAT] + mov [DATA_START_SECTOR], eax + + mov sp, STACK_ADDR + mov bp, BaseOfStack + + mov dword [bp - DAP_SECTOR_HIGH ], 00h + ;mov byte [bp - DAP_RESERVED1 ], 00h ;deleted by mingxuan 2020-9-17 + ;mov byte [bp - DAP_RESERVED2 ], 00h ;deleted by mingxuan 2020-9-17 + mov byte [bp - DAP_PACKET_SIZE ], 10h + mov byte [bp - DAP_READ_SECTORS], 01h + mov word [bp - DAP_BUFFER_SEG ], 01000h + + ;for test, added by mingxuan 2020-9-4 + ;call DispStr ; + ;jmp $ + + jmp _SEARCH_LOADER + + +_MISSING_LOADER: ; 显示没有装载程序 + ;for test, added by mingxuan 2020-9-10 + ;call DispStr ; + ;JMP $ ;for test, added by mingxuan 2020-9-10 + + ; 清屏 + ; for test, added by mingxuan 2020-9-15 + ;pusha + ;mov dx, 0184fh ; 右下角: (80, 50) + ;int 10h ; int 10h + ;popa + +_DISK_ERROR: ; 显示磁盘错误信息 + ;for test, added by mingxuan 2020-9-4 + + ; 清屏 + ; for test, added by mingxuan 2020-9-15 + ;pusha + ;mov dx, 0184fh ; 右下角: (80, 50) + ;int 10h ; int 10h + ;popa + + JMP $ + +ReadSector: + pusha + + mov ah, 42h ;ah是功能号,扩展读对应0x42 + lea si, [BP - DAP_PACKET_SIZE] ;使用扩展int13时DAP结构体首地址传给si + + ;mov dl, [BS_DriveNum] ;deleted by mingxuan 2020-9-13 + ;mov es, BaseOfBoot ;modified by mingxuan 2020-9-13 + ;mov dl, [fs:BS_DriveNum] ;modified by mingxuan 2020-9-13 + mov dl, [BS_DriveNum] ;modified by mingxuan 2020-9-17 + ;mov dl, 0x80 + + ;jmp $ + + int 13h + + jc _DISK_ERROR + + ;jmp $ + + ; 清屏 + ; for test, added by mingxuan 2020-9-15 + ;pusha + ;mov dx, 0184fh ; 右下角: (80, 50) + ;int 10h ; int 10h + ;popa + + + popa + ret + +_SEARCH_LOADER: + mov word [bp - DAP_BUFFER_OFF], DATA_BUF_OFF + + ;mov eax, dword [BS_RootClus] ;deleted by mingxuan 2020-9-13 + ;mov es, BaseOfBoot ;modified by mingxuan 2020-9-13 + ;mov eax, dword [fs:BS_RootClus] ;modified by mingxuan 2020-9-13 + ;mov eax, dword [BS_RootClus] ;modified by mingxuan 2020-9-17 + mov eax, [BS_RootClus] + + mov dword [bp - CURRENT_CLUSTER], eax + + ;for test, added by mingxuan 2020-9-4 + ;call DispStr ; + ;jmp $ + +_NEXT_ROOT_CLUSTER: + ; 根据簇号计算扇区号, mingxuan 2020-9-13 + dec eax + dec eax + + xor ebx, ebx + + ;mov bl, byte [BPB_SecPerClu] ;deleted by mingxuan 2020-9-13 + ;mov es, BaseOfBoot ;modified by mingxuan 2020-9-13 + ;mov bl, byte [fs:BPB_SecPerClu] ;modified by mingxuan 2020-9-13 + mov bl, [BPB_SecPerClu] ;modified by mingxuan 2020-9-17 + + ;jmp $ + + mul ebx + + ;add eax, DATA_START_SECTOR ;deleted by mingxuan 2020-9-17 + add eax, [DATA_START_SECTOR] ;modified by mingxuan 2020-9-17 + mov dword [BP - DAP_SECTOR_LOW], eax + + ;mov dl, [BPB_SecPerClu] ;deleted by mingxuan 2020-9-13 + ;mov dl, byte [es:BPB_SecPerClu] ;modified by mingxuan 2020-9-13 + ;mov dl, [fs:BPB_SecPerClu] ;modified by mingxuan 2020-9-15 + mov dl, [BPB_SecPerClu] ;modified by mingxuan 2020-9-17 + ;jmp $ + + +_NEXT_ROOT_SECTOR: + call ReadSector + + ;for test, added by mingxuan 2020-9-4 + ;call DispStr ; + ;jmp $ + + mov di, DATA_BUF_OFF + mov bl, DIR_PER_SECTOR + +_NEXT_ROOT_ENTRY: + cmp byte [di], DIR_NAME_FREE + jz _MISSING_LOADER + + push di; + mov si, LoaderName + ;add si, BOOT_FAT32_INFO ; added by mingxuan 2020-9-16 + ;mov si, 0x7df1 ; for test, added by mingxuan 2020-9-16 + + mov cx, 10 + repe cmpsb + jcxz _FOUND_LOADER + + pop di + add di, DIR_ENTRY_SIZE + dec bl + jnz _NEXT_ROOT_ENTRY + + dec dl + jz _CHECK_NEXT_ROOT_CLUSTER + + inc dword [bp - DAP_SECTOR_LOW] + jmp _NEXT_ROOT_SECTOR + +; Comments, added by mingxuan 2020-9-10 +;==================================================================== +; 检查是否还有下一个簇(读取FAT表的相关信息) +; N = 数据簇号 +; FAT_BYTES(在FAT表中的偏移) = N*4 (FAT32) +; FAT_SECTOR = FAT_BYTES / BPB_BytesPerSec +; FAT_OFFSET = FAT_BYTES % BPB_BytesPerSec +;==================================================================== +_CHECK_NEXT_ROOT_CLUSTER: ; 检查是否还有下一个簇 + + ; 计算FAT表项所在的簇号和偏移 + ; FatOffset(在FAT表中的偏移) = ClusterNum(簇号) * 4 + XOR EDX, EDX + MOV EAX, DWORD[BP - CURRENT_CLUSTER] + SHL EAX, 2 ;FatOffset = ClusterNum * 4 + + XOR ECX, ECX + ;MOV CX, WORD [ BPB_BytesPerSec ] ;deleted by mingxuan 2020-9-13 + ;mov es, BaseOfBoot ;modified by mingxuan 2020-9-13 + ;mov cx, word [fs:BPB_BytesPerSec] ;modified by mingxuan 2020-9-13 + mov cx, word [BPB_BytesPerSec] ;modified by mingxuan 2020-9-17 + DIV ECX ; EAX = Sector EDX = OFFSET + + ; 计算FAT表的起始扇区号 + ; added by mingxuan 2020-9-17 + ;mov ebx, dword [ fs:OffsetOfActiPartStartSec ] + ;add bx, word [ fs:BPB_RsvdSecCnt ] + + ; 设置缓冲区地址 + ;ADD EAX, FAT_START_SECTOR ;deleted by mingxuan 2020-9-17 + ADD EAX, [FAT_START_SECTOR] ;modified by mingxuan 2020-9-17 + ;add eax, ebx ;modified by mingxuan 2020-9-17 + MOV DWORD [ BP - DAP_SECTOR_LOW ], EAX + + call ReadSector + + ; 检查下一个簇 + MOV DI,DX + ADD DI,DATA_BUF_OFF + MOV EAX, DWORD[DI] ; EAX = 下一个要读的簇号 + AND EAX, CLUSTER_MASK + MOV DWORD[ BP - CURRENT_CLUSTER ],EAX + CMP EAX,CLUSTER_LAST ; CX >= 0FFFFFF8H,则意味着没有更多的簇了 + + JB _NEXT_ROOT_CLUSTER + + ;for test, added by mingxuan 2020-9-10 + ;call DispStr ; + + JMP _MISSING_LOADER + +_FOUND_LOADER: + + ;for test, added by mingxuan 2020-9-10 + ;call DispStr ; + ;JMP $ ;for test, added by mingxuan 2020-9-10 + + ; 目录结构地址放在DI中 + pop di + xor eax, eax + mov ax, [di + OFF_START_CLUSTER_HIGH] ; 起始簇号高32位 + shl ax, 16 + mov ax, [di + OFF_START_CLUSTER_LOW] ; 起始簇号低32位 + mov dword [ bp - CURRENT_CLUSTER ], eax + mov cx, OSLOADER_SEG ; CX = 缓冲区段地址 + + ; 清屏 + ; for test, added by mingxuan 2020-9-15 + ;pusha + ;mov dx, 0184fh ; 右下角: (80, 50) + ;int 10h ; int 10h + ;popa + + +_NEXT_DATA_CLUSTER: + ; 根据簇号计算扇区号 + DEC EAX + DEC EAX + XOR EBX,EBX + + ;MOV BL, BYTE [ BPB_SecPerClu ] ;deleted by mingxuan 2020-9-13 + ;mov es, BaseOfBoot ;modified by mingxuan 2020-9-13 + ;mov bl, byte [fs:BPB_SecPerClu] ;modified by mingxuan 2020-9-13 + mov bl, [BPB_SecPerClu] ;modified by mingxuan 2020-9-17 + + MUL EBX + ;ADD EAX, DATA_START_SECTOR ;deleted by mingxuan 2020-9-17 + ADD EAX, [DATA_START_SECTOR] ;modified by mingxuan 2020-9-17 + MOV DWORD[ BP - DAP_SECTOR_LOW ], EAX + + ;MOV BL , BYTE [BPB_SecPerClu] + ;mov bl, byte [fs:BPB_SecPerClu] ;modified by mingxuan 2020-9-13 + mov bl, byte [BPB_SecPerClu] ;modified by mingxuan 2020-9-17 + + ; 设置缓冲区 + MOV WORD [ BP - DAP_BUFFER_SEG ], CX + MOV WORD [ BP - DAP_BUFFER_OFF ], OSLOADER_SEG_OFF + +_NEXT_DATA_SECTOR: + ; 读取簇中的每个扇区(内层循环) + ; 注意 : 通过检查文件大小,可以避免读取最后一个不满簇的所有大小 + call ReadSector + + ; 更新地址,继续读取 + ;MOV AX, WORD [BPB_BytesPerSec] ;deleted by mingxuan 2020-9-13 + ;mov es, BaseOfBoot ;modified by mingxuan 2020-9-13 + ;mov ax, word [fs:BPB_BytesPerSec] ;modified by mingxuan 2020-9-13 + mov ax, word [BPB_BytesPerSec] ;modified by mingxuan 2020-9-17 + + ADD WORD [BP - DAP_BUFFER_OFF], ax + INC DWORD [BP - DAP_SECTOR_LOW] ; 递增扇区号 + DEC BL ; 内层循环计数 + + JNZ _NEXT_DATA_SECTOR + + ;for test, added by mingxuan 2020-9-10 + ;call DispStr ; + ;JMP $ ;for test, added by mingxuan 2020-9-10 + + + ; 更新读取下一个簇的缓冲区地址 + ;MOV CL, BYTE [ BPB_SecPerClu ] + ;mov es, BaseOfBoot ;modified by mingxuan 2020-9-13 + ;mov cl, byte [fs:BPB_SecPerClu] ;modified by mingxuan 2020-9-13 + mov cl, [BPB_SecPerClu] ;modified by mingxuan 2020-9-17 + + ;MOV AX, WORD [BPB_BytesPerSec] ;deleted by mingxuan 2020-9-13 + ;mov es, BaseOfBoot ;modified by mingxuan 2020-9-13 + ;mov ax, word [fs:BPB_BytesPerSec] ;modified by mingxuan 2020-9-13 + mov ax, [BPB_BytesPerSec] ;modified by mingxuan 2020-9-17 + + SHR AX, 4 + MUL CL + ADD AX, WORD [ BP - DAP_BUFFER_SEG ] + MOV CX, AX ; 保存下一个簇的缓冲区段地址 + + ;==================================================================== + ; 检查是否还有下一个簇(读取FAT表的相关信息) + ; LET N = 数据簇号 + ; THUS FAT_BYTES = N*4 (FAT32) + ; FAT_SECTOR = FAT_BYTES / BPB_BytesPerSec + ; FAT_OFFSET = FAT_BYTES % BPB_BytesPerSec + ;==================================================================== + + ; 计算FAT所在的簇号和偏移 + MOV EAX,DWORD [BP - CURRENT_CLUSTER] + XOR EDX,EDX + SHL EAX,2 + XOR EBX,EBX + + ;MOV BX,WORD [ BPB_BytesPerSec ] ;deleted by mingxuan 2020-9-13 + ;mov es, BaseOfBoot ;modified by mingxuan 2020-9-13 + ; mov bx, word [fs:BPB_BytesPerSec] ;modified by mingxuan 2020-9-13 + mov bx, [BPB_BytesPerSec] ;modified by mingxuan 2020-9-17 + + DIV EBX ; EAX = Sector EDX = Offset + + ; 计算FAT表的起始扇区号 + ; added by mingxuan 2020-9-17 + ;mov ebx, dword [ fs:OffsetOfActiPartStartSec ] + ;add bx, word [ fs:BPB_RsvdSecCnt ] + + ; 设置int 13h读取的绝对扇区号 + ;ADD EAX, FAT_START_SECTOR ;deleted by mingxuan 2020-9-17 + ADD EAX, [FAT_START_SECTOR] ;modified by mingxuan 2020-9-17 + ;add eax, ebx ;modified by mingxuan 2020-9-17 + MOV DWORD [ BP - DAP_SECTOR_LOW ], EAX + + ; 设置int 13h写入内存的缓冲区地址 + MOV WORD [BP - DAP_BUFFER_SEG ], 01000H + MOV WORD [BP - DAP_BUFFER_OFF ], DATA_BUF_OFF + + ; 读取扇区 ; 把FAT表读进内存 + CALL ReadSector + + ; 检查下一个簇 + MOV DI,DX + ADD DI,DATA_BUF_OFF + MOV EAX,DWORD[DI] ; EAX = 下一个要读的簇号 + AND EAX,CLUSTER_MASK + MOV DWORD[ BP - CURRENT_CLUSTER ],EAX + CMP EAX,CLUSTER_LAST ; CX >= 0FFFFFF8H,则意味着没有更多的簇了 + + ;for test, added by mingxuan 2020-9-10 + ;call DispStr ; + ;JMP $ ;for test, added by mingxuan 2020-9-10 + + ; 清屏 + ; for test, added by mingxuan 2020-9-15 + ;pusha + ;mov dx, 0184fh ; 右下角: (80, 50) + ;int 10h ; int 10h + ;popa + + + JB _NEXT_DATA_CLUSTER + +_RUN_LOADER: + + ;mov dl, [BS_DriveNum] ;deleted by mingxuan 2020-9-13 + ;mov es, BaseOfBoot ;modified by mingxuan 2020-9-13 + ;mov dl, [fs:BS_DriveNum] ;modified by mingxuan 2020-9-13 + mov dl, [BS_DriveNum] ;modified by mingxuan 2020-9-17 + + ;for test, added by mingxuan 2020-9-10 + ;call DispStr ; + ;jmp $ ;for test 2020-9-10, mingxuan + + ; 清屏 + ; for test, added by mingxuan 2020-9-15 + ;pusha + ;mov dx, 0184fh ; 右下角: (80, 50) + ;int 10h ; int 10h + ;popa + + + jmp OSLOADER_SEG : OSLOADER_SEG_OFF + + +LoaderName db "LOADER BIN" ; 第二阶段启动程序 FDOSLDR.BIN + +; for display +; added by mingxuan 2020-9-4 +;TestMessage: db "#" ; 27字节, 不够则用空格补齐. 序号 0 + +; for display +; added by mingxuan 2020-9-4 +;DispStr: +; pusha + +; mov ax, TestMessage +; mov bp, ax +; mov cx, 1 +; mov ax, 01301h ; AH = 13, AL = 01h +; mov bx, 0007h ; 页号为0(BH = 0) 黑底白字(BL = 07h) + +; int 10h ; int 10h + +; popa + +; ret + + +times 510-($-$$) db 0 ; 填充剩下的空间,使生成的二进制代码恰好为512字节 +dw 0xaa55 ; 结束标志 diff --git a/boot/mbr/grub/grub.cfg b/boot/mbr/grub/grub.cfg new file mode 100644 index 0000000..688cec3 --- /dev/null +++ b/boot/mbr/grub/grub.cfg @@ -0,0 +1,8 @@ +set default=0 +set timeout=20 + +menuentry "hhh" { + set root=(hd0,msdos2) + chainloader +1 + boot +} diff --git a/boot/mbr/include/fat32.inc b/boot/mbr/include/fat32.inc new file mode 100644 index 0000000..03b4245 --- /dev/null +++ b/boot/mbr/include/fat32.inc @@ -0,0 +1,61 @@ + ;deleted by mingxuan 2020-9-16 + ;BS_OEM DB 'mkfs.fat' + ;BPB_BytesPerSec DW 0x200 + ;BPB_SecPerClu DB 1 + ;BPB_RsvdSecCnt DW 0x20 + ;BPB_NumFATs DB 2 + ;BPB_RootEntCnt DW 0 + ;BPB_TotSec16 DW 0 + ;BPB_Media DB 0xf8 + ;BPB_FATSz16 DW 0 + ;BPB_SecPerTrk DW 0x20 + ;BPB_NumHeads DW 0x40 + ;BPB_HiddSec DD 0 + ;BPB_TotSec32 DD 0x015791 + ;BS_SecPerFAT DD 0x02a5 + ;BS_Flag DW 0 + ;BS_Version DW 0 + ;BS_RootClus DD 2 + ;BS_FsInfoSec DW 1 + ;BS_BackBootSec DW 6 + ;BS_Unuse1 DD 0 + ;BS_Unuse2 DD 0 + ;BS_Unuse3 DD 0 + ;BS_DriveNum DB 0x80 + ;BS_Unuse4 DB 0 + ;BS_ExtBootFlag DB 0x29 + ;BS_VolNum DD 0xbe3a8ff5 + ;BS_VolName DB 'MZY hd boot' + +; added by mingxuan 2020-9-12 +;BaseOfBoot equ 1000h ; added by mingxuan 2020-9-12 +;OffsetOfBoot equ 7c00h ; load Boot sector to BaseOfBoot:OffsetOfBoot + +BPB_BytesPerSec equ (OffsetOfBoot + 0xb) ;每扇区字节数 +BPB_SecPerClu equ (OffsetOfBoot + 0xd) ;每簇扇区数 +BPB_RsvdSecCnt equ (OffsetOfBoot + 0xe) ;保留扇区数 +BPB_NumFATs equ (OffsetOfBoot + 0x10) ;FAT表数 +BPB_RootEntCnt equ (OffsetOfBoot + 0x11) ;FAT32不使用 +BPB_TotSec16 equ (OffsetOfBoot + 0x13) ;扇区总数 +BPB_Media equ (OffsetOfBoot + 0x15) ;介质描述符 +BPB_FATSz16 equ (OffsetOfBoot + 0x16) ;每个FAT表的大小扇区数(FAT32不使用) +BPB_SecPerTrk equ (OffsetOfBoot + 0x18) ;每磁道扇区数 +BPB_NumHeads equ (OffsetOfBoot + 0x1a) ;磁头数 +BPB_HiddSec equ (OffsetOfBoot + 0x1c) ;分区已使用扇区数 +BPB_TotSec32 equ (OffsetOfBoot + 0x20) ;文件系统大小扇区数 + +BS_SecPerFAT equ (OffsetOfBoot + 0x24) ;每个FAT表大小扇区数 +BS_Flag equ (OffsetOfBoot + 0x28) ;标记 +BS_Version equ (OffsetOfBoot + 0x2a) ;版本号 +BS_RootClus equ (OffsetOfBoot + 0x2c) ;根目录簇号 +BS_FsInfoSec equ (OffsetOfBoot + 0x30) ;FSINFO扇区号 +BS_BackBootSec equ (OffsetOfBoot + 0x32) ;备份引导扇区位置 +BS_Unuse1 equ (OffsetOfBoot + 0x34) ;未使用 +;BS_Unuse2 equ (OffsetOfBoot + 0x40) ;未使用 ;deleted by mingxuan 2020-9-15 +;BS_Unuse3 equ (OffsetOfBoot + 0x41) ;未使用 ;deleted by mingxuan 2020-9-15 +BS_DriveNum equ (OffsetOfBoot + 0x40) ;设备号 +BS_Unuse4 equ (OffsetOfBoot + 0x41) ;未使用 +BS_ExtBootFlag equ (OffsetOfBoot + 0x42) ;扩展引导标志 +BS_VolNum equ (OffsetOfBoot + 0x43) ;卷序列号 +BS_VolName equ (OffsetOfBoot + 0x47) ;卷标 + diff --git a/boot/mbr/include/lib.inc b/boot/mbr/include/lib.inc new file mode 100644 index 0000000..1faceea --- /dev/null +++ b/boot/mbr/include/lib.inc @@ -0,0 +1,170 @@ + +; ------------------------------------------------------------------------ +; 显示 AL 中的数字 +; ------------------------------------------------------------------------ +DispAL: + push ecx + push edx + push edi + + mov edi, [dwDispPos] + + mov ah, 0Fh ; 0000b: 黑底 1111b: 白字 + mov dl, al + shr al, 4 + mov ecx, 2 +.begin: + and al, 01111b + cmp al, 9 + ja .1 + add al, '0' + jmp .2 +.1: + sub al, 0Ah + add al, 'A' +.2: + mov [gs:edi], ax + add edi, 2 + + mov al, dl + loop .begin + ;add edi, 2 + + mov [dwDispPos], edi + + pop edi + pop edx + pop ecx + + ret +; DispAL 结束------------------------------------------------------------- + + +; ------------------------------------------------------------------------ +; 显示一个整形数 +; ------------------------------------------------------------------------ +DispInt: + mov eax, [esp + 4] + shr eax, 24 + call DispAL + + mov eax, [esp + 4] + shr eax, 16 + call DispAL + + mov eax, [esp + 4] + shr eax, 8 + call DispAL + + mov eax, [esp + 4] + call DispAL + + mov ah, 07h ; 0000b: 黑底 0111b: 灰字 + mov al, 'h' + push edi + mov edi, [dwDispPos] + mov [gs:edi], ax + add edi, 4 + mov [dwDispPos], edi + pop edi + + ret +; DispInt 结束------------------------------------------------------------ + +; ------------------------------------------------------------------------ +; 显示一个字符串 +; ------------------------------------------------------------------------ +DispStr: + push ebp + mov ebp, esp + push ebx + push esi + push edi + + mov esi, [ebp + 8] ; pszInfo + mov edi, [dwDispPos] + mov ah, 0Fh +.1: + lodsb + test al, al + jz .2 + cmp al, 0Ah ; 是回车吗? + jnz .3 + push eax + mov eax, edi + mov bl, 160 + div bl + and eax, 0FFh + inc eax + mov bl, 160 + mul bl + mov edi, eax + pop eax + jmp .1 +.3: + mov [gs:edi], ax + add edi, 2 + jmp .1 + +.2: + mov [dwDispPos], edi + + pop edi + pop esi + pop ebx + pop ebp + ret +; DispStr 结束------------------------------------------------------------ + +; ------------------------------------------------------------------------ +; 换行 +; ------------------------------------------------------------------------ +DispReturn: + push szReturn + call DispStr ;printf("\n"); + add esp, 4 + + ret +; DispReturn 结束--------------------------------------------------------- + + +; ------------------------------------------------------------------------ +; 内存拷贝,仿 memcpy +; ------------------------------------------------------------------------ +; void* MemCpy(void* es:pDest, void* ds:pSrc, int iSize); +; ------------------------------------------------------------------------ +MemCpy: + push ebp + mov ebp, esp + + push esi + push edi + push ecx + + mov edi, [ebp + 8] ; Destination + mov esi, [ebp + 12] ; Source + mov ecx, [ebp + 16] ; Counter +.1: + cmp ecx, 0 ; 判断计数器 + jz .2 ; 计数器为零时跳出 + + mov al, [ds:esi] ; ┓ + inc esi ; ┃ + ; ┣ 逐字节移动 + mov byte [es:edi], al ; ┃ + inc edi ; ┛ + + dec ecx ; 计数器减一 + jmp .1 ; 循环 +.2: + mov eax, [ebp + 8] ; 返回值 + + pop ecx + pop edi + pop esi + mov esp, ebp + pop ebp + + ret ; 函数结束,返回 +; MemCpy 结束------------------------------------------------------------- + diff --git a/boot/mbr/include/loader.inc b/boot/mbr/include/loader.inc new file mode 100644 index 0000000..ef5377a --- /dev/null +++ b/boot/mbr/include/loader.inc @@ -0,0 +1,70 @@ +;============================================================================================================================== +BaseOfStack equ 0x0100 +STACK_ADDR equ 0x0ea +SEG_ADDR equ 0x09000 +DATA_BUF_OFF equ 0x09000 + +BaseOfBoot equ 1000h ; added by mingxuan 2020-9-17 +OffsetOfBoot equ 7c00h ; load Boot sector to BaseOfBoot:OffsetOfBoot +OffsetOfActiPartStartSec equ 7e00h ; 活动分区的起始扇区号相对于BaseOfBoot的偏移量 ;added by mingxuan 2020-9-17 + ; 该变量来自分区表,保存在该内存地址,用于在os_boot和loader中查找FAT32文件 + +;FAT_START_SECTOR equ 0x820 ; FAT表的起始扇区号 DWORD +;FAT_START_SECTOR equ 0x1020 ; FAT表的起始扇区号 DWORD ; for test 2020-9-12, mingxuan +;DATA_START_SECTOR equ 0xd6a ; 数据区起始扇区号 DWORD +;DATA_START_SECTOR equ 0x156a ; 数据区起始扇区号 DWORD ; for test 2020-9-12, mingxuan +;DATA_START_SECTOR equ 0x13a4 ; 数据区起始扇区号 DWORD ; for test 2020-9-16, mingxuan + +DIR_PER_SECTOR equ 0x10 ; 每个扇区所容纳的目录 BYTE + +; 扩展磁盘服务所使用的地址包 +DAP_SECTOR_HIGH equ 4 ; 起始扇区号的高32位 ( 每次调用需要重置 ) DWORD +DAP_SECTOR_LOW equ 8 ; 起始扇区号的低32位 ( 每次调用需要重置 ) DWORD +DAP_BUFFER_SEG equ 10 ; 缓冲区段地址 ( 每次调用需要重置 ) WORD +DAP_BUFFER_OFF equ 12 ; 缓冲区偏移 ( 每次调用需要重置 ) WORD +DAP_RESERVED2 equ 13 ; 保留字节 +DAP_READ_SECTORS equ 14 ; 要处理的扇区数(1 - 127 ) +DAP_RESERVED1 equ 15 ; 保留字节 +DAP_PACKET_SIZE equ 16 ; 包的大小为16字节 + +CURRENT_CLUSTER equ 20 ; 当前正在处理的簇号 DWORD + +; 目录项结构 +OFF_START_CLUSTER_HIGH equ 20 ; 起始簇号高位 WORD +OFF_START_CLUSTER_LOW equ 26 ; 起始簇号低位 WORD + +; 相关常量 +DIR_NAME_FREE equ 0x00 ; 该项是空闲的 +DIR_ENTRY_SIZE equ 32 ; 每个目录项的尺寸 + +; 簇属性 +CLUSTER_MASK equ 0FFFFFFFH ; 簇号掩码 +CLUSTER_LAST equ 0FFFFFF8H ;0xFFFFFFF8-0xFFFFFFFF表示文件的最后一个簇 + +BaseOfLoader equ 09000h ; LOADER.BIN 被加载到的位置 ---- 段地址 +OffsetOfLoader equ 0100h ; LOADER.BIN 被加载到的位置 ---- 偏移地址 + +BaseOfLoaderPhyAddr equ BaseOfLoader * 10h ; LOADER.BIN 被加载到的位置 ---- 物理地址 (= BaseOfLoader * 10h) + +BaseOfKernelFile equ 07000h ; KERNEL.BIN 被加载到的位置 ---- 段地址 +OffsetOfKernelFile equ 0h ; KERNEL.BIN 被加载到的位置 ---- 偏移地址 + +BaseOfKernelFilePhyAddr equ BaseOfKernelFile * 10h + +BaseOfEchoFile equ 07E0h ; KERNEL.BIN 被加载到的位置 ---- 段地址 +OffsetOfEchoFile equ 0h ; KERNEL.BIN 被加载到的位置 ---- 偏移地址 + +BaseOfEchoFilePhyAddr equ BaseOfKernelFile * 10h + +KernelEntryPointPhyAddr equ 0xC0030400 ; 注意:1、必须与 MAKEFILE 中参数 -Ttext 的值相等!! + ; 2、这是个地址而非仅仅是个偏移 + +PageDirBase equ 200000h ; 页目录开始地址: 2M +PageTblBase equ 201000h ; 页表开始地址: 2M + 4K + +PageTblNumAddr equ 500h;页表数量放在这个位置 delete by visual 2016.4.28 + +FMIBuff equ 007ff000h + +;============================================================================================================================== + diff --git a/boot/mbr/include/pm.inc b/boot/mbr/include/pm.inc new file mode 100644 index 0000000..6d4c94c --- /dev/null +++ b/boot/mbr/include/pm.inc @@ -0,0 +1,318 @@ + + +; 描述符图示 + +; 图示一 +; +; ------ ┏━━┳━━┓高地址 +; ┃ 7 ┃ 段 ┃ +; ┣━━┫ ┃ +; 基 +; 字节 7 ┆ ┆ ┆ +; 址 +; ┣━━┫ ② ┃ +; ┃ 0 ┃ ┃ +; ------ ┣━━╋━━┫ +; ┃ 7 ┃ G ┃ +; ┣━━╉──┨ +; ┃ 6 ┃ D ┃ +; ┣━━╉──┨ +; ┃ 5 ┃ 0 ┃ +; ┣━━╉──┨ +; ┃ 4 ┃ AVL┃ +; 字节 6 ┣━━╉──┨ +; ┃ 3 ┃ ┃ +; ┣━━┫ 段 ┃ +; ┃ 2 ┃ 界 ┃ +; ┣━━┫ 限 ┃ +; ┃ 1 ┃ ┃ +; ┣━━┫ ② ┃ +; ┃ 0 ┃ ┃ +; ------ ┣━━╋━━┫ +; ┃ 7 ┃ P ┃ +; ┣━━╉──┨ +; ┃ 6 ┃ ┃ +; ┣━━┫ DPL┃ +; ┃ 5 ┃ ┃ +; ┣━━╉──┨ +; ┃ 4 ┃ S ┃ +; 字节 5 ┣━━╉──┨ +; ┃ 3 ┃ ┃ +; ┣━━┫ T ┃ +; ┃ 2 ┃ Y ┃ +; ┣━━┫ P ┃ +; ┃ 1 ┃ E ┃ +; ┣━━┫ ┃ +; ┃ 0 ┃ ┃ +; ------ ┣━━╋━━┫ +; ┃ 23 ┃ ┃ +; ┣━━┫ ┃ +; ┃ 22 ┃ ┃ +; ┣━━┫ 段 ┃ +; +; 字节 ┆ ┆ 基 ┆ +; 2, 3, 4 +; ┣━━┫ 址 ┃ +; ┃ 1 ┃ ① ┃ +; ┣━━┫ ┃ +; ┃ 0 ┃ ┃ +; ------ ┣━━╋━━┫ +; ┃ 15 ┃ ┃ +; ┣━━┫ ┃ +; ┃ 14 ┃ ┃ +; ┣━━┫ 段 ┃ +; +; 字节 0,1┆ ┆ 界 ┆ +; +; ┣━━┫ 限 ┃ +; ┃ 1 ┃ ① ┃ +; ┣━━┫ ┃ +; ┃ 0 ┃ ┃ +; ------ ┗━━┻━━┛低地址 +; + + +; 图示二 + +; 高地址………………………………………………………………………低地址 + +; | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | +; |7654321076543210765432107654321076543210765432107654321076543210| <- 共 8 字节 +; |--------========--------========--------========--------========| +; ┏━━━┳━━━━━━━┳━━━━━━━━━━━┳━━━━━━━┓ +; ┃31..24┃ (见下图) ┃ 段基址(23..0) ┃ 段界限(15..0)┃ +; ┃ ┃ ┃ ┃ ┃ +; ┃ 基址2┃③│②│ ①┃基址1b│ 基址1a ┃ 段界限1 ┃ +; ┣━━━╋━━━┳━━━╋━━━━━━━━━━━╋━━━━━━━┫ +; ┃ %6 ┃ %5 ┃ %4 ┃ %3 ┃ %2 ┃ %1 ┃ +; ┗━━━┻━━━┻━━━┻━━━┻━━━━━━━┻━━━━━━━┛ +; │ \_________ +; │ \__________________ +; │ \________________________________________________ +; │ \ +; ┏━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┓ +; ┃ 7 ┃ 6 ┃ 5 ┃ 4 ┃ 3 ┃ 2 ┃ 1 ┃ 0 ┃ 7 ┃ 6 ┃ 5 ┃ 4 ┃ 3 ┃ 2 ┃ 1 ┃ 0 ┃ +; ┣━━╋━━╋━━╋━━╋━━┻━━┻━━┻━━╋━━╋━━┻━━╋━━╋━━┻━━┻━━┻━━┫ +; ┃ G ┃ D ┃ 0 ┃ AVL┃ 段界限 2 (19..16) ┃ P ┃ DPL ┃ S ┃ TYPE ┃ +; ┣━━┻━━┻━━┻━━╋━━━━━━━━━━━╋━━┻━━━━━┻━━┻━━━━━━━━━━━┫ +; ┃ ③: 属性 2 ┃ ②: 段界限 2 ┃ ①: 属性1 ┃ +; ┗━━━━━━━━━━━┻━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━┛ +; 高地址 低地址 +; +; + +; 说明: +; +; (1) P: 存在(Present)位。 +; P=1 表示描述符对地址转换是有效的,或者说该描述符所描述的段存在,即在内存中; +; P=0 表示描述符对地址转换无效,即该段不存在。使用该描述符进行内存访问时会引起异常。 +; +; (2) DPL: 表示描述符特权级(Descriptor Privilege level),共2位。它规定了所描述段的特权级,用于特权检查,以决定对该段能否访问。 +; +; (3) S: 说明描述符的类型。 +; 对于存储段描述符而言,S=1,以区别与系统段描述符和门描述符(S=0)。 +; +; (4) TYPE: 说明存储段描述符所描述的存储段的具体属性。 +; +; +; 数据段类型 类型值 说明 +; ---------------------------------- +; 0 只读 +; 1 只读、已访问 +; 2 读/写 +; 3 读/写、已访问 +; 4 只读、向下扩展 +; 5 只读、向下扩展、已访问 +; 6 读/写、向下扩展 +; 7 读/写、向下扩展、已访问 +; +; +; 类型值 说明 +; 代码段类型 ---------------------------------- +; 8 只执行 +; 9 只执行、已访问 +; A 执行/读 +; B 执行/读、已访问 +; C 只执行、一致码段 +; D 只执行、一致码段、已访问 +; E 执行/读、一致码段 +; F 执行/读、一致码段、已访问 +; +; +; 系统段类型 类型编码 说明 +; ---------------------------------- +; 0 <未定义> +; 1 可用286TSS +; 2 LDT +; 3 忙的286TSS +; 4 286调用门 +; 5 任务门 +; 6 286中断门 +; 7 286陷阱门 +; 8 未定义 +; 9 可用386TSS +; A <未定义> +; B 忙的386TSS +; C 386调用门 +; D <未定义> +; E 386中断门 +; F 386陷阱门 +; +; (5) G: 段界限粒度(Granularity)位。 +; G=0 表示界限粒度为字节; +; G=1 表示界限粒度为4K 字节。 +; 注意,界限粒度只对段界限有效,对段基地址无效,段基地址总是以字节为单位。 +; +; (6) D: D位是一个很特殊的位,在描述可执行段、向下扩展数据段或由SS寄存器寻址的段(通常是堆栈段)的三种描述符中的意义各不相同。 +; ⑴ 在描述可执行段的描述符中,D位决定了指令使用的地址及操作数所默认的大小。 +; ① D=1表示默认情况下指令使用32位地址及32位或8位操作数,这样的代码段也称为32位代码段; +; ② D=0 表示默认情况下,使用16位地址及16位或8位操作数,这样的代码段也称为16位代码段,它与80286兼容。可以使用地址大小前缀和操作数大小前缀分别改变默认的地址或操作数的大小。 +; ⑵ 在向下扩展数据段的描述符中,D位决定段的上部边界。 +; ① D=1表示段的上部界限为4G; +; ② D=0表示段的上部界限为64K,这是为了与80286兼容。 +; ⑶ 在描述由SS寄存器寻址的段描述符中,D位决定隐式的堆栈访问指令(如PUSH和POP指令)使用何种堆栈指针寄存器。 +; ① D=1表示使用32位堆栈指针寄存器ESP; +; ② D=0表示使用16位堆栈指针寄存器SP,这与80286兼容。 +; +; (7) AVL: 软件可利用位。80386对该位的使用未左规定,Intel公司也保证今后开发生产的处理器只要与80386兼容,就不会对该位的使用做任何定义或规定。 +; + + +;---------------------------------------------------------------------------- +; 描述符类型值说明 +; 其中: +; DA_ : Descriptor Attribute +; D : 数据段 +; C : 代码段 +; S : 系统段 +; R : 只读 +; RW : 读写 +; A : 已访问 +; 其它 : 可按照字面意思理解 +;---------------------------------------------------------------------------- +DA_32 EQU 4000h ; 32 位段 +DA_LIMIT_4K EQU 8000h ; 段界限粒度为 4K 字节 + +DA_DPL0 EQU 00h ; DPL = 0 +DA_DPL1 EQU 20h ; DPL = 1 +DA_DPL2 EQU 40h ; DPL = 2 +DA_DPL3 EQU 60h ; DPL = 3 +;---------------------------------------------------------------------------- +; 存储段描述符类型值说明 +;---------------------------------------------------------------------------- +DA_DR EQU 90h ; 存在的只读数据段类型值 +DA_DRW EQU 92h ; 存在的可读写数据段属性值 +DA_DRWA EQU 93h ; 存在的已访问可读写数据段类型值 +DA_C EQU 98h ; 存在的只执行代码段属性值 +DA_CR EQU 9Ah ; 存在的可执行可读代码段属性值 +DA_CCO EQU 9Ch ; 存在的只执行一致代码段属性值 +DA_CCOR EQU 9Eh ; 存在的可执行可读一致代码段属性值 +;---------------------------------------------------------------------------- +; 系统段描述符类型值说明 +;---------------------------------------------------------------------------- +DA_LDT EQU 82h ; 局部描述符表段类型值 +DA_TaskGate EQU 85h ; 任务门类型值 +DA_386TSS EQU 89h ; 可用 386 任务状态段类型值 +DA_386CGate EQU 8Ch ; 386 调用门类型值 +DA_386IGate EQU 8Eh ; 386 中断门类型值 +DA_386TGate EQU 8Fh ; 386 陷阱门类型值 +;---------------------------------------------------------------------------- + + +; 选择子图示: +; ┏━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┓ +; ┃ 15 ┃ 14 ┃ 13 ┃ 12 ┃ 11 ┃ 10 ┃ 9 ┃ 8 ┃ 7 ┃ 6 ┃ 5 ┃ 4 ┃ 3 ┃ 2 ┃ 1 ┃ 0 ┃ +; ┣━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━╋━━╋━━┻━━┫ +; ┃ 描述符索引 ┃ TI ┃ RPL ┃ +; ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━┻━━━━━┛ +; +; RPL(Requested Privilege Level): 请求特权级,用于特权检查。 +; +; TI(Table Indicator): 引用描述符表指示位 +; TI=0 指示从全局描述符表GDT中读取描述符; +; TI=1 指示从局部描述符表LDT中读取描述符。 +; + +;---------------------------------------------------------------------------- +; 选择子类型值说明 +; 其中: +; SA_ : Selector Attribute + +SA_RPL0 EQU 0 ; ┓ +SA_RPL1 EQU 1 ; ┣ RPL +SA_RPL2 EQU 2 ; ┃ +SA_RPL3 EQU 3 ; ┛ + +SA_TIG EQU 0 ; ┓TI +SA_TIL EQU 4 ; ┛ +;---------------------------------------------------------------------------- + + +;---------------------------------------------------------------------------- +; 分页机制使用的常量说明 +;---------------------------------------------------------------------------- +PG_P EQU 1 ; 页存在属性位 +PG_RWR EQU 0 ; R/W 属性位值, 读/执行 +PG_RWW EQU 2 ; R/W 属性位值, 读/写/执行 +PG_USS EQU 0 ; U/S 属性位值, 系统级 +PG_USU EQU 4 ; U/S 属性位值, 用户级 +;---------------------------------------------------------------------------- + + + + +; ========================================= +; FLAGS - Intel 8086 Family Flags Register +; ========================================= +; +; |11|10|F|E|D|C|B|A|9|8|7|6|5|4|3|2|1|0| +; | | | | | | | | | | | | | | | | | '--- CF……Carry Flag +; | | | | | | | | | | | | | | | | '--- 1 +; | | | | | | | | | | | | | | | '--- PF……Parity Flag +; | | | | | | | | | | | | | | '--- 0 +; | | | | | | | | | | | | | '--- AF……Auxiliary Flag +; | | | | | | | | | | | | '--- 0 +; | | | | | | | | | | | '--- ZF……Zero Flag +; | | | | | | | | | | '--- SF……Sign Flag +; | | | | | | | | | '--- TF……Trap Flag (Single Step) +; | | | | | | | | '--- IF……Interrupt Flag +; | | | | | | | '--- DF……Direction Flag +; | | | | | | '--- OF……Overflow flag +; | | | | '----- IOPL……I/O Privilege Level (286+ only) +; | | | '----- NT……Nested Task Flag (286+ only) +; | | '----- 0 +; | '----- RF……Resume Flag (386+ only) +; '------ VM……Virtual Mode Flag (386+ only) +; +; 注: see PUSHF POPF STI CLI STD CLD +; + + +; 宏 ------------------------------------------------------------------------------------------------------ +; +; 描述符 +; usage: Descriptor Base, Limit, Attr +; Base: dd +; Limit: dd (low 20 bits available) +; Attr: dw (lower 4 bits of higher byte are always 0) +%macro Descriptor 3 + dw %2 & 0FFFFh ; 段界限 1 (2 字节) + dw %1 & 0FFFFh ; 段基址 1 (2 字节) + db (%1 >> 16) & 0FFh ; 段基址 2 (1 字节) + dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 属性 1 + 段界限 2 + 属性 2 (2 字节) + db (%1 >> 24) & 0FFh ; 段基址 3 (1 字节) +%endmacro ; 共 8 字节 +; +; 门 +; usage: Gate Selector, Offset, DCount, Attr +; Selector: dw +; Offset: dd +; DCount: db +; Attr: db +%macro Gate 4 + dw (%2 & 0FFFFh) ; 偏移 1 (2 字节) + dw %1 ; 选择子 (2 字节) + dw (%3 & 1Fh) | ((%4 << 8) & 0FF00h) ; 属性 (2 字节) + dw ((%2 >> 16) & 0FFFFh) ; 偏移 2 (2 字节) +%endmacro ; 共 8 字节 +; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/boot/mbr/loader.asm b/boot/mbr/loader.asm new file mode 100644 index 0000000..ff0a467 --- /dev/null +++ b/boot/mbr/loader.asm @@ -0,0 +1,1013 @@ + +; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +; loader.asm +; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +; Forrest Yu, 2005 +; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + +org 0100h + + jmp LABEL_START ; Start + +; 下面是 FAT12 磁盘的头, 之所以包含它是因为下面用到了磁盘的一些信息 +%include "fat32.inc" +%include "loader.inc" +%include "pm.inc" + + +; GDT ------------------------------------------------------------------------------------------------------------------------------------------------------------ +; 段基址 段界限 , 属性 +LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符 +LABEL_DESC_FLAT_C: Descriptor 0, 0fffffh, DA_CR | DA_32 | DA_LIMIT_4K ; 0 ~ 4G +LABEL_DESC_FLAT_RW: Descriptor 0, 0fffffh, DA_DRW | DA_32 | DA_LIMIT_4K ; 0 ~ 4G +LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW | DA_DPL3 ; 显存首地址 +; GDT ------------------------------------------------------------------------------------------------------------------------------------------------------------ + +GdtLen equ $ - LABEL_GDT +GdtPtr dw GdtLen - 1 ; 段界限 + dd BaseOfLoaderPhyAddr + LABEL_GDT ; 基地址 + +; GDT 选择子 ---------------------------------------------------------------------------------- +SelectorFlatC equ LABEL_DESC_FLAT_C - LABEL_GDT +SelectorFlatRW equ LABEL_DESC_FLAT_RW - LABEL_GDT +SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT + SA_RPL3 +; GDT 选择子 ---------------------------------------------------------------------------------- + +FAT_START_SECTOR DD 0 ;FAT表的起始扇区号 ;added by mingxuan 2020-9-17 +DATA_START_SECTOR DD 0 ;数据区起始扇区号 ;added by mingxuan 2020-9-17 + +LABEL_START: ; <--- 从这里开始 ************* + + ;for test + ;added by mingxuan 2020-9-11 + ; 清屏 + ;mov ax, 0600h ; AH = 6, AL = 0h + ;mov bx, 0700h ; 黑底白字(BL = 07h) + ;mov cx, 0 ; 左上角: (0, 0) + ;mov dx, 0184fh ; 右下角: (80, 50) + ;int 10h ; int 10h + + ;for test + ;added by mingxuan 2020-9-11 + ;mov dh, 1 + ;call DispStrRealMode + ;jmp $ + + cld + mov ax, cs + mov ds, ax + mov es, ax ;deleted by mingxuan 2020-9-16 + mov ss, ax + + ;added by mingxuan 2020-9-16 + mov ax, BaseOfBoot + mov fs, ax ;fs存储BaseOfBoot,用于查找FAT32的配置信息 + + ; 计算FAT表的起始扇区号 ; added by mingxuan 2020-9-17 + mov eax, [ fs:OffsetOfActiPartStartSec] + add ax, [ fs:BPB_RsvdSecCnt ] + mov [FAT_START_SECTOR], eax + + ; 计算数据区起始扇区号 ; added by mingxuan 2020-9-17 + add eax, [ fs:BS_SecPerFAT ] + add eax, [ fs:BS_SecPerFAT ] + mov [DATA_START_SECTOR], eax + + mov sp, STACK_ADDR + mov bp, BaseOfStack + + mov dword [bp - DAP_SECTOR_HIGH ], 00h + mov byte [bp - DAP_RESERVED1 ], 00h + mov byte [bp - DAP_RESERVED2 ], 00h + mov byte [bp - DAP_PACKET_SIZE ], 10h + mov byte [bp - DAP_READ_SECTORS], 01h + mov word [bp - DAP_BUFFER_SEG ], 0x09000 + + ; 得到内存数 + mov ebx, 0 ; ebx = 后续值, 开始时需为 0 + mov di, _MemChkBuf ; es:di 指向一个地址范围描述符结构(Address Range Descriptor Structure) +.MemChkLoop: + mov eax, 0E820h ; eax = 0000E820h + mov ecx, 20 ; ecx = 地址范围描述符结构的大小 + mov edx, 0534D4150h ; edx = 'SMAP' + int 15h ; int 15h + jc .MemChkFail + add di, 20 + inc dword [_dwMCRNumber] ; dwMCRNumber = ARDS 的个数 + cmp ebx, 0 + jne .MemChkLoop + jmp .MemChkOK +.MemChkFail: + mov dword [_dwMCRNumber], 0 +.MemChkOK: + + ;jmp _SEARCH_LOADER ;deleted by mingxuan 2020-9-16 + jmp _SEARCH_KERNEL ;modified by mingxuan 2020-9-16 + +;_MISSING_LOADER: ;deleted by mingxuan 2020-9-16 +_MISSING_KERNEL: ;modified by mingxuan 2020-9-16 +_DISK_ERROR: + ;for test + ;added by mingxuan 2020-9-16 + ; 清屏 + ;mov ax, 0600h ; AH = 6, AL = 0h + ;mov bx, 0700h ; 黑底白字(BL = 07h) + ;mov cx, 0 ; 左上角: (0, 0) + ;mov dx, 0184fh ; 右下角: (80, 50) + ;int 10h ; int 10h + + jmp $ + +ReadSector: + pusha + mov ah, 42h + lea si, [BP - DAP_PACKET_SIZE] + + ;mov dl, [BS_DriveNum] ;deleted by mingxuan 2020-9-16 + mov dl, [fs:BS_DriveNum];modified by mingxuan 2020-9-16 + + int 13h + jc _DISK_ERROR + popa + ret + +;_SEARCH_LOADER: ;deleted by mingxuan 2020-9-16 +_SEARCH_KERNEL: ;modified by mingxuan 2020-9-16 + mov word [bp - DAP_BUFFER_OFF], DATA_BUF_OFF + + ;mov eax, dword [BS_RootClus] ;deleted by mingxuan 2020-9-16 + mov eax, dword [fs:BS_RootClus] ;modified by mingxuan 2020-9-16 + + mov dword [bp - CURRENT_CLUSTER], eax + +_NEXT_ROOT_CLUSTER: + dec eax + dec eax + xor ebx, ebx + + ;mov bl, byte [BPB_SecPerClu] ;deleted by mingxuan 2020-9-16 + mov bl, byte [fs:BPB_SecPerClu] ;modified by mingxuan 2020-9-16 + + mul ebx + ;add eax, DATA_START_SECTOR + add eax, [DATA_START_SECTOR] ;modified by mingxuan 2020-9-17 + mov dword [BP - DAP_SECTOR_LOW], eax + + ;mov dl, [BPB_SecPerClu] ;deleted by mingxuan 2020-9-16 + mov dl, [fs:BPB_SecPerClu] ;modified by mingxuan 2020-9-16 + +_NEXT_ROOT_SECTOR: + call ReadSector + + mov di, DATA_BUF_OFF + mov bl, DIR_PER_SECTOR + +_NEXT_ROOT_ENTRY: + cmp byte [di], DIR_NAME_FREE + ;jz _MISSING_LOADER ;deleted by mingxuan 2020-9-16 + jz _MISSING_KERNEL ;modified by mingxuan 2020-9-16 + + push di; + + ;cmpsb将DS:SI和ES:DI中的字符串进行比较, 故需要修改一下es, mingxuan + ;mov ax, ds ;added by mingxuan 2020-9-16 + ;mov es, ax ;added by mingxuan 2020-9-16 + + mov si, KernelFileName + mov cx, 10 + repe cmpsb ;将DS:SI和ES:DI中的字符串进行比较 + + ;把es再改回BaseOfBoot,用于索引FAT32的配置信息, mingxuan + ;mov ax, BaseOfBoot ;added by mingxuan 2020-9-16 + ;mov es, ax ;added by mingxuan 2020-9-16 + + ;jcxz _FOUND_LOADER + jcxz _FOUND_KERNEL ;modified by mingxuan 2020-9-16 + + pop di + add di, DIR_ENTRY_SIZE + dec bl + jnz _NEXT_ROOT_ENTRY + + dec dl + jz _CHECK_NEXT_ROOT_CLUSTER + inc dword [bp - DAP_SECTOR_LOW] + jmp _NEXT_ROOT_SECTOR + +_CHECK_NEXT_ROOT_CLUSTER: + + ; 计算FAT所在的簇号和偏移 + ; FatOffset = ClusterNum*4 + XOR EDX,EDX + MOV EAX,DWORD[BP - CURRENT_CLUSTER] + SHL EAX,2 + XOR ECX,ECX + + ;MOV CX,WORD [ BPB_BytesPerSec ] ;deleted by mingxuan 2020-9-16 + MOV CX,WORD [ fs:BPB_BytesPerSec ];modified by mingxuan 2020-9-16 + + DIV ECX ; EAX = Sector EDX = OFFSET + ;ADD EAX, FAT_START_SECTOR + ADD EAX, [FAT_START_SECTOR] ;modified by mingxuan 2020-9-17 + MOV DWORD [ BP - DAP_SECTOR_LOW ], EAX + + call ReadSector + + ; 检查下一个簇 + MOV DI,DX + ADD DI,DATA_BUF_OFF + MOV EAX,DWORD[DI] ; EAX = 下一个要读的簇号 + AND EAX,CLUSTER_MASK + MOV DWORD[ BP - CURRENT_CLUSTER ],EAX + CMP EAX,CLUSTER_LAST ; CX >= 0FFFFFF8H,则意味着没有更多的簇了 + JB _NEXT_ROOT_CLUSTER + + ;JMP _MISSING_LOADER ;deleted by mingxuan 2020-9-16 + JMP _MISSING_KERNEL ;modified by mingxuan 2020-9-16 + +;_FOUND_LOADER: +_FOUND_KERNEL: ;modified by mingxuan 2020-9-16 + ; 目录结构地址放在DI中 + pop di + xor eax, eax + mov ax, [di + OFF_START_CLUSTER_HIGH] ; 起始簇号高32位 + shl ax, 16 + mov ax, [di + OFF_START_CLUSTER_LOW] ; 起始簇号低32位 + mov dword [ bp - CURRENT_CLUSTER ], eax + mov cx, BaseOfKernelFile ; CX = 缓冲区段地址 + +_NEXT_DATA_CLUSTER: + ; 根据簇号计算扇区号 + DEC EAX + DEC EAX + XOR EBX,EBX + + ;MOV BL, BYTE [ BPB_SecPerClu ] ;deleted by mingxuan 2020-9-16 + MOV BL, BYTE [ fs:BPB_SecPerClu ];modified by mingxuan 2020-9-16 + + MUL EBX + ;ADD EAX, DATA_START_SECTOR + ADD EAX, [DATA_START_SECTOR] ;modified by mingxuan 2020-9-17 + MOV DWORD[ BP - DAP_SECTOR_LOW ], EAX + + ;MOV BL , BYTE [BPB_SecPerClu] ;deleted by mingxuan 2020-9-16 + MOV BL , BYTE [fs:BPB_SecPerClu] ;modified by mingxuan 2020-9-16 + + ; 设置缓冲区 + MOV WORD [ BP - DAP_BUFFER_SEG ], CX + MOV WORD [ BP - DAP_BUFFER_OFF ], OffsetOfKernelFile + +_NEXT_DATA_SECTOR: + ; 读取簇中的每个扇区(内层循环) + ; 注意 : 通过检查文件大小,可以避免读取最后一个不满簇的所有大小 + call ReadSector + + ; 更新地址,继续读取 + ;MOV AX, WORD [BPB_BytesPerSec] ;deleted by mingxuan 2020-9-16 + MOV AX, WORD [fs:BPB_BytesPerSec] ;modified by mingxuan 2020-9-16 + + ADD WORD [BP - DAP_BUFFER_OFF], ax + INC DWORD [BP - DAP_SECTOR_LOW] ; 递增扇区号 + DEC BL ; 内层循环计数 + JNZ _NEXT_DATA_SECTOR + + ; 更新读取下一个簇的缓冲区地址 + ;MOV CL, BYTE [ BPB_SecPerClu ] ;deleted by mingxuan 2020-9-16 + MOV CL, BYTE [ fs:BPB_SecPerClu ] ;modified by mingxuan 2020-9-16 + + ;MOV AX, WORD [BPB_BytesPerSec] ;deleted by mingxuan 2020-9-16 + MOV AX, WORD [fs:BPB_BytesPerSec] ;modified by mingxuan 2020-9-16 + + SHR AX, 4 + MUL CL + ADD AX, WORD [ BP - DAP_BUFFER_SEG ] + MOV CX, AX ; 保存下一个簇的缓冲区段地址 + + ;==================================================================== + ; 检查是否还有下一个簇(读取FAT表的相关信息) + ; LET N = 数据簇号 + ; THUS FAT_BYTES = N*4 (FAT32) + ; FAT_SECTOR = FAT_BYTES / BPB_BytesPerSec + ; FAT_OFFSET = FAT_BYTES % BPB_BytesPerSec + ;==================================================================== + + ; 计算FAT所在的簇号和偏移 + MOV EAX,DWORD [BP - CURRENT_CLUSTER] + XOR EDX,EDX + SHL EAX,2 + XOR EBX,EBX + + ;MOV BX,WORD [ BPB_BytesPerSec ] ;deleted by mingxuan 2020-9-16 + MOV BX,WORD [ fs:BPB_BytesPerSec ];modified by mingxuan 2020-9-16 + + DIV EBX ; EAX = Sector EDX = Offset + + ; 设置缓冲区地址 + ;ADD EAX, FAT_START_SECTOR + ADD EAX, [FAT_START_SECTOR] ;modified by mingxuan 2020-9-17 + MOV DWORD [ BP - DAP_SECTOR_LOW ], EAX + MOV WORD [BP - DAP_BUFFER_SEG ], 0x09000 + MOV WORD [BP - DAP_BUFFER_OFF ], DATA_BUF_OFF + + ; 读取扇区 + CALL ReadSector + + ; 检查下一个簇 + MOV DI,DX + ADD DI,DATA_BUF_OFF + MOV EAX,DWORD[DI] ; EAX = 下一个要读的簇号 + AND EAX,CLUSTER_MASK + MOV DWORD[ BP - CURRENT_CLUSTER ],EAX + CMP EAX,CLUSTER_LAST ; CX >= 0FFFFFF8H,则意味着没有更多的簇了 + JB _NEXT_DATA_CLUSTER + + +;;add end add by liang 2016.04.20 +; 下面准备跳入保护模式 ------------------------------------------- + + ;for test + ;added by mingxuan 2020-9-11 + ;mov dh, 1 + ;call DispStrRealMode + ;jmp $ + + +; 加载 GDTR + lgdt [GdtPtr] + +; 关中断 + cli + +; 打开地址线A20 + in al, 92h + or al, 00000010b + out 92h, al + +; 准备切换到保护模式 + mov eax, cr0 + or eax, 1 + mov cr0, eax + +; 真正进入保护模式 + jmp dword SelectorFlatC:(BaseOfLoaderPhyAddr+LABEL_PM_START) + + +;============================================================================ +;变量 +;---------------------------------------------------------------------------- +wSectorNo dw 0 ; 要读取的扇区号 +bOdd db 0 ; 奇数还是偶数 +dwKernelSize dd 0 ; KERNEL.BIN 文件大小 +_dwEchoSize dd 0 ;echo size add by liang 2016.04.20 +;============================================================================ +;字符串 +;---------------------------------------------------------------------------- +KernelFileName db "KERNEL BIN", 0 ; KERNEL.BIN 之文件名 +EchoFileName db "INIT BIN", 0 ;add by liang 2016.04.20 ;edit by visual 2016.5.16 +; 为简化代码, 下面每个字符串的长度均为 MessageLength +MessageLength equ 9 +LoadMessage: db "Loading " +Message1 db "Ready. " +Message2 db "No KERNEL" +Message3 db "exLoading" ;add by liang 2016.04.20 +Message4 db "exReady. " ;add by liang 2016.04.20 +Message5 db "No ECHO " ;add by liang 2016.04.20 +;============================================================================ + +;---------------------------------------------------------------------------- +; 函数名: DispStrRealMode +;---------------------------------------------------------------------------- +; 运行环境: +; 实模式(保护模式下显示字符串由函数 DispStr 完成) +; 作用: +; 显示一个字符串, 函数开始时 dh 中应该是字符串序号(0-based) +DispStrRealMode: + mov ax, MessageLength + mul dh + add ax, LoadMessage + mov bp, ax ; ┓ + mov ax, ds ; ┣ ES:BP = 串地址 + mov es, ax ; ┛ + mov cx, MessageLength ; CX = 串长度 + mov ax, 01301h ; AH = 13, AL = 01h + mov bx, 0007h ; 页号为0(BH = 0) 黑底白字(BL = 07h) + mov dl, 0 + add dh, 3 ; 从第 3 行往下显示 + int 10h ; int 10h + ret +;---------------------------------------------------------------------------- + + +;---------------------------------------------------------------------------- +; 函数名: KillMotor +;---------------------------------------------------------------------------- +; 作用: +; 关闭软驱马达 +KillMotor: + push dx + mov dx, 03F2h + mov al, 0 + out dx, al + pop dx + ret +;---------------------------------------------------------------------------- + +; 从此以后的代码在保护模式下执行 ---------------------------------------------------- +; 32 位代码段. 由实模式跳入 --------------------------------------------------------- +[SECTION .s32] + +ALIGN 32 + +[BITS 32] + +LABEL_PM_START: + mov ax, SelectorVideo + mov gs, ax + mov ax, SelectorFlatRW + mov ds, ax + mov es, ax + mov fs, ax + mov ss, ax + mov esp, TopOfStack + + push szMemChkTitle + call DispStr + add esp, 4 + + call DispMemInfo + call getFreeMemInfo ;add by liang 2016.04.13 + call DispEchoSize ;add by liang 2016.04.21 + call SetupPaging + + ; added by mingxuan 2020-9-16 + ; for test + mov ah, 0Fh ; 0000: 黑底 1111: 白字 + mov al, 'P' + mov [gs:((80 * 0 + 39) * 2)], ax ; 屏幕第 0 行, 第 39 列。 + + call InitKernel + + ;jmp $ + + ;*************************************************************** + jmp SelectorFlatC:KernelEntryPointPhyAddr ; 正式进入内核 * + ;*************************************************************** + ; 内存看上去是这样的: + ; ┃ ┃ + ; ┃ . ┃ + ; ┃ . ┃ + ; ┃ . ┃ + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■┃ + ; ┃■■■■■■Page Tables■■■■■■■■■■■■■■■■■■┃ + ; ┃■■■■■(大小由LOADER决定)■■■■■■■■■■■■■┃ + ; 00101000h ┃■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■┃ PageTblBase + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■┃ + ; 00100000h ┃■■■■Page Directory Table■■■■■■■■■■■■┃ PageDirBase <- 1M + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□┃ + ; F0000h ┃□□□□□□□System ROM□□□□□□□□□□□□□□□□□□□┃ + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□┃ + ; E0000h ┃□□□□Expansion of system ROM □□□□□□□□┃ + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□┃ + ; C0000h ┃□□□Reserved for ROM expansion□□□□□□□┃ + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□┃ B8000h ← gs + ; A0000h ┃□□□Display adapter reserved□□□□□□□□□┃ + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□┃ + ; 9FC00h ┃□□extended BIOS data area (EBDA)□□□□┃ + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■┃ + ; 90000h ┃■■■■■■■LOADER.BIN■■■■■■■■■■■■■■■■■■■┃ somewhere in LOADER ← esp + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■┃ + ; 80000h ┃■■■■■■■KERNEL.BIN■■■■■■■■■■■■■■■■■■■┃ + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■┃ + ; 30000h ┃■■■■■■■■KERNEL■■■■■■■■■■■■■■■■■■■■■■┃ 30400h ← KERNEL 入口 (KernelEntryPointPhyAddr) + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃ ┃ + ; 7E00h ┃ F R E E ┃ + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■┃ + ; 7C00h ┃■■■■■■BOOT SECTOR■■■■■■■■■■■■■■■■■■┃ + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃ ┃ + ; 500h ┃ F R E E ┃ + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□┃ + ; 400h ┃□□□□ROM BIOS parameter area □□□□□□□□┃ + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃◇◇◇◇◇◇◇◇◇◇◇◇◇◇◇◇◇◇┃ + ; 0h ┃◇◇◇◇◇◇Int Vectors◇◇◇◇◇◇┃ + ; ┗━━━━━━━━━━━━━━━━━━┛ ← cs, ds, es, fs, ss + ; + ; + ; ┏━━━┓ ┏━━━┓ + ; ┃■■■■■■┃ 我们使用 ┃□□□□□□┃ 不能使用的内存 + ; ┗━━━┛ ┗━━━┛ + ; ┏━━━┓ ┏━━━┓ + ; ┃ ┃ 未使用空间 ┃◇◇◇┃ 可以覆盖的内存 + ; ┗━━━┛ ┗━━━┛ + ; + ; 注:KERNEL 的位置实际上是很灵活的,可以通过同时改变 LOAD.INC 中的 KernelEntryPointPhyAddr 和 MAKEFILE 中参数 -Ttext 的值来改变。 + ; 比如,如果把 KernelEntryPointPhyAddr 和 -Ttext 的值都改为 0x400400,则 KERNEL 就会被加载到内存 0x400000(4M) 处,入口在 0x400400。 + ; + + + + +; ------------------------------------------------------------------------ +; 显示 AL 中的数字 +; ------------------------------------------------------------------------ +DispAL: + push ecx + push edx + push edi + + mov edi, [dwDispPos] + + mov ah, 0Fh ; 0000b: 黑底 1111b: 白字 + mov dl, al + shr al, 4 + mov ecx, 2 +.begin: + and al, 01111b + cmp al, 9 + ja .1 + add al, '0' + jmp .2 +.1: + sub al, 0Ah + add al, 'A' +.2: + mov [gs:edi], ax + add edi, 2 + + mov al, dl + loop .begin + ;add edi, 2 + + mov [dwDispPos], edi + + pop edi + pop edx + pop ecx + + ret +; DispAL 结束------------------------------------------------------------- + + +; ------------------------------------------------------------------------ +; 显示一个整形数 +; ------------------------------------------------------------------------ +DispInt: + mov eax, [esp + 4] + shr eax, 24 + call DispAL + + mov eax, [esp + 4] + shr eax, 16 + call DispAL + + mov eax, [esp + 4] + shr eax, 8 + call DispAL + + mov eax, [esp + 4] + call DispAL + + mov ah, 07h ; 0000b: 黑底 0111b: 灰字 + mov al, 'h' + push edi + mov edi, [dwDispPos] + mov [gs:edi], ax + add edi, 4 + mov [dwDispPos], edi + pop edi + + ret +; DispInt 结束------------------------------------------------------------ + +; ------------------------------------------------------------------------ +; 显示一个字符串 +; ------------------------------------------------------------------------ +DispStr: + push ebp + mov ebp, esp + push ebx + push esi + push edi + + mov esi, [ebp + 8] ; pszInfo + mov edi, [dwDispPos] + mov ah, 0Fh +.1: + lodsb + test al, al + jz .2 + cmp al, 0Ah ; 是回车吗? + jnz .3 + push eax + mov eax, edi + mov bl, 160 + div bl + and eax, 0FFh + inc eax + mov bl, 160 + mul bl + mov edi, eax + pop eax + jmp .1 +.3: + mov [gs:edi], ax + add edi, 2 + jmp .1 + +.2: + mov [dwDispPos], edi + + pop edi + pop esi + pop ebx + pop ebp + ret +; DispStr 结束------------------------------------------------------------ + +; ------------------------------------------------------------------------ +; 换行 +; ------------------------------------------------------------------------ +DispReturn: + push szReturn + call DispStr ;printf("\n"); + add esp, 4 + + ret +; DispReturn 结束--------------------------------------------------------- + + +; ------------------------------------------------------------------------ +; 内存拷贝,仿 memcpy +; ------------------------------------------------------------------------ +; void* MemCpy(void* es:pDest, void* ds:pSrc, int iSize); +; ------------------------------------------------------------------------ +MemCpy: + push ebp + mov ebp, esp + + push esi + push edi + push ecx + + mov edi, [ebp + 8] ; Destination + mov esi, [ebp + 12] ; Source + mov ecx, [ebp + 16] ; Counter +.1: + cmp ecx, 0 ; 判断计数器 + jz .2 ; 计数器为零时跳出 + + mov al, [ds:esi] ; ┓ + inc esi ; ┃ + ; ┣ 逐字节移动 + mov byte [es:edi], al ; ┃ + inc edi ; ┛ + + dec ecx ; 计数器减一 + jmp .1 ; 循环 +.2: + mov eax, [ebp + 8] ; 返回值 + + pop ecx + pop edi + pop esi + mov esp, ebp + pop ebp + + ret ; 函数结束,返回 +; MemCpy 结束------------------------------------------------------------- + +;;;;;;;;;;;;;;;;;add begin add by liang 2016.04.13;;;;;;;;;;;;;;;;;;;;; + +memtest: + push edi + push esi + + mov esi,0xaa55aa55 + mov edi,0x55aa55aa + mov eax,[esp+8+4] ;start +.mt_loop: + mov ebx,eax + add ebx,0xffc ;检查每4KB最后4B + mov ecx,[ebx] ;保存原值 + mov [ebx],esi ;写入 + xor dword [ebx],0xffffffff ;反转 + cmp edi,[ebx] ;检查反转结果是否正确 + jne .mt_fin + xor dword [ebx],0xffffffff ;再次反转 + cmp esi,[ebx] ;检查是否恢复初始值 + jne .mt_fin + mov [ebx],ecx ;恢复为修改前的值 + add eax,0x1000 ;每循环一次加0x1000 + cmp eax,[esp+8+8] ;未到end,循环 + jbe .mt_loop + pop esi + pop edi + ret + +.mt_fin: + mov [ebx],ecx ;恢复为修改前的值 + pop esi + pop edi + ret + +getFreeMemInfo: + push eax + push ebx + push ecx + push edx + + mov eax,cr0 + or eax,0x60000000 ;禁止缓存 + mov cr0,eax + + mov ebx,0x00000000 ;检查0到32M + mov ecx,0x02000000 + mov edx,FMIBuff ;存于0x007ff000处 + +.fmi_loop: + push ecx + push ebx + call memtest + pop ebx + pop ecx + mov [edx+4],eax ;留出前4B存放dwFMINumber + add edx,4 + add eax,0x1000 + mov ebx,eax + inc dword [dwFMINumber] ;循环次数,即返回值个数 + cmp ebx,ecx + jb .fmi_loop + + mov ebx,[dwFMINumber] + mov edx,FMIBuff + mov [edx],ebx ;前4B存放返回值个数 + mov ebx,[FMIBuff] + push ebx + call DispInt ;打印返回值个数 + add esp,4 + + mov eax,cr0 + and eax,0x9fffffff ;恢复缓存 + mov cr0,eax + + pop edx + pop ecx + pop ebx + pop eax + ret + + +;;;;;;;;;;;;;;;;;;;;;;;;;;add end add by liang 2016.04.13;;;;;;;;;;;;;;;;;;;;;;;;;; + +;;add begin add by liang 2016.04.21 +DispEchoSize: + push eax + mov eax,[dwEchoSize] + push eax + call DispInt + add esp,4 + pop eax + ret +;;add end add by liang 2016.04.21 + +; 显示内存信息 -------------------------------------------------------------- +DispMemInfo: + push esi + push edi + push ecx + + mov esi, MemChkBuf + mov ecx, [dwMCRNumber] ;for(int i=0;i<[MCRNumber];i++) // 每次得到一个ARDS(Address Range Descriptor Structure)结构 +.loop: ;{ + mov edx, 5 ; for(int j=0;j<5;j++) // 每次得到一个ARDS中的成员,共5个成员 + mov edi, ARDStruct ; { // 依次显示:BaseAddrLow,BaseAddrHigh,LengthLow,LengthHigh,Type +.1: ; + push dword [esi] ; + call DispInt ; DispInt(MemChkBuf[j*4]); // 显示一个成员 + pop eax ; + stosd ; ARDStruct[j*4] = MemChkBuf[j*4]; + add esi, 4 ; + dec edx ; + cmp edx, 0 ; + jnz .1 ; } + call DispReturn ; printf("\n"); + cmp dword [dwType], 1 ; if(Type == AddressRangeMemory) // AddressRangeMemory : 1, AddressRangeReserved : 2 + jne .2 ; { + mov eax, [dwBaseAddrLow] ; + add eax, [dwLengthLow] ; + cmp eax, [dwMemSize] ; if(BaseAddrLow + LengthLow > MemSize) + jb .2 ; + mov [dwMemSize], eax ; MemSize = BaseAddrLow + LengthLow; +.2: ; } + loop .loop ;} + ; + call DispReturn ;printf("\n"); + push szRAMSize ; + call DispStr ;printf("RAM size:"); + add esp, 4 ; + ; + push dword [dwMemSize] ; + call DispInt ;DispInt(MemSize); + add esp, 4 ; + + pop ecx + pop edi + pop esi + ret +; --------------------------------------------------------------------------- + +; 启动分页机制 -------------------------------------------------------------- +SetupPaging: + ; 根据内存大小计算应初始化多少PDE以及多少页表 + xor edx, edx + mov eax, [dwMemSize] + mov ebx, 400000h ; 400000h = 4M = 4096 * 1024, 一个页表对应的内存大小 + div ebx + mov ecx, eax ; 此时 ecx 为页表的个数,也即 PDE 应该的个数 + test edx, edx + jz .no_remainder + inc ecx ; 如果余数不为 0 就需增加一个页表 +.no_remainder: + push ecx ; 暂存页表个数 + mov dword[PageTblNumAddr],ecx ;将页表数写进这个物理地址 + + ; 为简化处理, 所有线性地址对应相等的物理地址. 并且不考虑内存空洞. + + ; 首先初始化页目录 + mov ax, SelectorFlatRW + mov es, ax + mov edi, PageDirBase ; 此段首地址为 PageDirBase + xor eax, eax + mov eax, PageTblBase | PG_P | PG_USU | PG_RWW +.1: + stosd + add eax, 4096 ; 为了简化, 所有页表在内存中是连续的. + loop .1 + +;;;;初始化3G处的页目录;;;;;;;;;;;;;;;; //add by visual 2016.5.10 + pop eax ;页表个数 + mov ecx,eax ;重新放到ecx里 + push ecx ;暂存页表个数 + mov ax, SelectorFlatRW + mov es, ax + mov eax, 3072 ;768*4 + add eax, PageDirBase ; + mov edi, eax ; 应该往页目录这个位置写: PageDirBase+768*4,即线性地址3G处 + + xor eax, eax ; 清0 + mov eax, ecx ; + mov ebx, 4096 ; + mul ebx ;跳过前ecx个页表,即PageTblBase+页表数*4096 + add eax, PageTblBase | PG_P | PG_USU | PG_RWW; +.1k: + stosd + add eax, 4096 ; 为了简化, 所有页表在内存中是连续的. + loop .1k +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + ; 再初始化所有页表(最开始处,一一映射的) + pop eax ; 页表个数 + push eax ;暂存页表个数 + mov ebx, 1024 ; 每个页表 1024 个 PTE + mul ebx + mov ecx, eax ; PTE个数 = 页表个数 * 1024 + mov edi, PageTblBase ; 此段首地址为 PageTblBase + xor eax, eax + mov eax, PG_P | PG_USU | PG_RWW +.2: + stosd + add eax, 4096 ; 每一页指向 4K 的空间 + loop .2 + +;;;;初始化3G处的页表;;;;;;;;;;;;;;;; //add by visual 2016.5.10 + ; 再初始化3G后的页表 + pop eax ; 页表个数 + mov ebx, 1024 ; 每个页表 1024 个 PTE + mul ebx + mov ecx, eax ; PTE个数 = 页表个数 * 1024 + + xor eax, eax ; 清0 + mov eax, ecx ; + mov ebx, 4 + mul ebx ;跳过前ecx个页表,即PageTblBase+页表数*4096 + add eax, PageTblBase ; 后面3G对应页表的起始位置为 PageTblBase+页表数*4096 + mov edi, eax + xor eax, eax + mov eax, PG_P | PG_USU | PG_RWW ;从0开始 +.2k: + stosd + add eax, 4096 ; 每一页指向 4K 的空间 + loop .2k +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + ;mov ah, 0Fh ; 0000: 黑底 1111: 白字 + ;mov al, 'P' + ;mov [gs:((80 * 0 + 39) * 2)], ax ; 屏幕第 0 行, 第 39 列。 + + ;启动页表机制 + mov eax, PageDirBase + mov cr3, eax + mov eax, cr0 + or eax, 80000000h + mov cr0, eax + jmp short .3 +.3: + nop + + ret +; 分页机制启动完毕 ---------------------------------------------------------- + + + +; InitKernel --------------------------------------------------------------------------------- +; 将 KERNEL.BIN 的内容经过整理对齐后放到新的位置 +; -------------------------------------------------------------------------------------------- +InitKernel: ; 遍历每一个 Program Header,根据 Program Header 中的信息来确定把什么放进内存,放到什么位置,以及放多少。 + xor esi, esi + mov cx, word [BaseOfKernelFilePhyAddr + 2Ch]; ┓ ecx <- pELFHdr->e_phnum + movzx ecx, cx ; ┛ + mov esi, [BaseOfKernelFilePhyAddr + 1Ch] ; esi <- pELFHdr->e_phoff + add esi, BaseOfKernelFilePhyAddr ; esi <- OffsetOfKernel + pELFHdr->e_phoff +.Begin: + mov eax, [esi + 0] + cmp eax, 0 ; PT_NULL + jz .NoAction + push dword [esi + 010h] ; size ┓ + mov eax, [esi + 04h] ; ┃ + add eax, BaseOfKernelFilePhyAddr ; ┣ ::memcpy( (void*)(pPHdr->p_vaddr), + push eax ; src ┃ uchCode + pPHdr->p_offset, + push dword [esi + 08h] ; dst ┃ pPHdr->p_filesz; + call MemCpy ; ┃ + add esp, 12 ; ┛ +.NoAction: + add esi, 020h ; esi += pELFHdr->e_phentsize + dec ecx + jnz .Begin + + ret +; InitKernel ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + +; SECTION .data1 之开始 --------------------------------------------------------------------------------------------- +[SECTION .data1] + +ALIGN 32 + +LABEL_DATA: +; 实模式下使用这些符号 +; 字符串 +_szMemChkTitle: db "BaseAddrL BaseAddrH LengthLow LengthHigh Type", 0Ah, 0 +_szRAMSize: db "RAM size:", 0 +_szReturn: db 0Ah, 0 +;; 变量 +_dwMCRNumber: dd 0 ; Memory Check Result +_dwDispPos: dd (80 * 6 + 0) * 2 ; 屏幕第 6 行, 第 0 列。 +_dwMemSize: dd 0 +_ARDStruct: ; Address Range Descriptor Structure + _dwBaseAddrLow: dd 0 + _dwBaseAddrHigh: dd 0 + _dwLengthLow: dd 0 + _dwLengthHigh: dd 0 + _dwType: dd 0 +_MemChkBuf: times 256 db 0 +_dwFMINumber: dd 0 ;add by liang 2016.04.13 + + +; +;; 保护模式下使用这些符号 +szMemChkTitle equ BaseOfLoaderPhyAddr + _szMemChkTitle +szRAMSize equ BaseOfLoaderPhyAddr + _szRAMSize +szReturn equ BaseOfLoaderPhyAddr + _szReturn +dwDispPos equ BaseOfLoaderPhyAddr + _dwDispPos +dwMemSize equ BaseOfLoaderPhyAddr + _dwMemSize +dwMCRNumber equ BaseOfLoaderPhyAddr + _dwMCRNumber +ARDStruct equ BaseOfLoaderPhyAddr + _ARDStruct + dwBaseAddrLow equ BaseOfLoaderPhyAddr + _dwBaseAddrLow + dwBaseAddrHigh equ BaseOfLoaderPhyAddr + _dwBaseAddrHigh + dwLengthLow equ BaseOfLoaderPhyAddr + _dwLengthLow + dwLengthHigh equ BaseOfLoaderPhyAddr + _dwLengthHigh + dwType equ BaseOfLoaderPhyAddr + _dwType +MemChkBuf equ BaseOfLoaderPhyAddr + _MemChkBuf +dwFMINumber equ BaseOfLoaderPhyAddr + _dwFMINumber ;add by liang 2016.04.13 +dwEchoSize equ BaseOfLoaderPhyAddr + _dwEchoSize ;add by liang 2016.04.21 + +; 堆栈就在数据段的末尾 +StackSpace: times 1000h db 0 +TopOfStack equ BaseOfLoaderPhyAddr + $ ; 栈顶 +; SECTION .data1 之结束 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + diff --git a/boot/mbr/mbr.asm b/boot/mbr/mbr.asm new file mode 100644 index 0000000..ad80488 --- /dev/null +++ b/boot/mbr/mbr.asm @@ -0,0 +1,261 @@ +BaseOfBoot equ 1000h ; 段地址,mbr加载loader到这个段 +OffsetOfBoot equ 7c00h ; load Boot sector to BaseOfBoot:OffsetOfBoot +OffsetOfActiPartStartSec equ 7e00h ; 活动分区的起始扇区号相对于BaseOfBoot的偏移量 ;added by mingxuan 2020-9-12 + ; 该变量来自分区表,保存在该内存地址,用于在os_boot和loader中查找FAT32文件 + +org 07c00h + +LABEL_START: + mov ax, cs + mov ds, ax + mov es, ax + mov ss, ax + mov sp, 7c00h + + ; 清屏 + mov ax, 0600h ; AH = 6, AL = 0h + mov bx, 0700h ; 黑底白字(BL = 07h) + mov cx, 0 ; 左上角: (0, 0) + mov dx, 0184fh ; 右下角: (80, 50) + int 10h ; int 10h + + mov dh, 0 ; + call DispStr ; + + + xor ah, ah ; ┓ + xor dl, dl ; ┣ 软驱复位 + int 13h ; ┛ + + ;循环读取分区表 + mov bx, 0 ;每次循环累加16 + mov dh, 1 ;每次循环累加1 + + jmp CHECK_PARTITION ;added by mingxuan 2020-9-29 + +;added by mingxuan 2020-9-29 +LABLE_END_EXTENDED: + + mov byte [EndInExt], 0 ;将EndInExt置0,复位 + + ; 检查扩展分区表的第二项是否是空项。如果是空项则表明当前逻辑分区就是最后一个。 + add bx, 16 + mov cl, [es:7c00h+446+bx+4] ;分区类型 + ;sub bx, 16 ;deleted by mingxuan 2020-9-30 + + cmp cl, 0 + jz RESET_SecOffset_SELF + +CHECK_PARTITION: + mov dl, [es:7c00h+446+bx] ;分区活跃标志地址 + + ;mov ax, word [es:7c00h+446+bx+8] ;分区起始扇区地址 ;deletd by mingxuan 2020-9-12 + mov eax, dword [es:7c00h+446+bx+8] ;分区起始扇区地址 ;modified by mingxuan 2020-9-12 + ;修改为eax的原因: 分区表用4个字节来表示起始扇区,而不是2个字节, mingxuan + + ;add ax, [SecOffset] ;deletd by mingxuan 2020-9-12 + ;add eax, [SecOffset] ;modified by mingxuan 2020-9-29 + ;deleted by mingxuan 2020-9-29 + mov cl, [es:7c00h+446+bx+4] ;分区类型 + + cmp cl, 5 ;extended partition type = 5 + ;jz LABLE_EXTENDED + jz LABLE_IN_EXTENDED ;modified by mingxuan 2020-9-29 + + add eax, [SecOffset_SELF] ;added by mingxuan 2020-9-29 + + add byte [CurPartNo], 1 + add byte [CurPartNum], 1 ; added by mingxuan 2020-9-29 ;deleted by mingxuan 2020-9-30 + + cmp dl, 80h + jz LABLE_ACTIVE + + ; 检查当前的分区表是否是扩展分区的最后一个逻辑分区 + mov cl, [EndInExt] + cmp cl, 1 ;added by mingxuan 2020-9-29 + jz LABLE_END_EXTENDED ;added by mingxuan 2020-9-29 + + cmp dh, 4 + jz LABLE_NOT_FOUND + + inc dh + add bx, 16 + jmp CHECK_PARTITION + + +RESET_SecOffset_SELF: + + ; SecOffset_SELF置0的目的是以后又恢复到搜索主分区 + mov edx, 0 + mov [SecOffset_SELF], edx + + mov dl, [EXTNum] + mov al, 16 + mul dl + mov bx, ax + + mov ax, 0 ;added by mingxuan 2020-9-30 + mov es, ax ;added by mingxuan 2020-9-30 + + jmp CHECK_PARTITION + + +; added by mingxuan 2020-9-29 +LABLE_IN_EXTENDED: ;在分区表中发现扩展分区后,跳转到这里来执行 + + ; 此时的eax有两种情况: + ; 1) 当第一次执行该程序时,eax是从主分区表里获得的扩展分区起始的绝对地址 + ; 2) 之后的执行, eax是从扩展分区表的第二项获得的起始地址(相对于整个扩展分区起始地址偏移量) + ; 从扩展分区表的第二项获得的起始地址是相对于整个扩展分区起始地址偏移量,所以要加上基地址 + ; 特殊情况: 当第一次进入该过程时,SecOffset_EXT的值是0。此时是从主分区表里获得的起始地址,这个地址是绝对地址。 + add eax, [SecOffset_EXT] ;modified by mingxuan 2020-9-29 + + ; FirstInExt是标志位,判断是否是第一次进入该过程 + mov cl, [FirstInExt] + ; 若为1,表示不是第一次进入该过程。 + cmp cl, 1 + jz LABLE_EXTENDED + + ; 以下是第一次进入该过程要执行的语句(仅执行一遍) + ; 要记录下扩展分区是主分区表的第几项 + add byte [CurPartNum], 1 ;added by mingxuan 2020-9-30 + mov cl, [CurPartNum] + mov [EXTNum], cl + + ; 当第一次进入该过程时,要记录下扩展分区的起始地址,该地址是以后所有逻辑分区的偏移量的基地址 + mov [SecOffset_EXT], eax + mov byte [FirstInExt], 1 + +LABLE_EXTENDED: + + ;mov [SecOffset], ax ;deleted by mingxuan 2020-9-12 + ;mov [SecOffset], eax ;deleted by mingxuan 2020-9-12 + ;要记录下每个逻辑分区的起始地址,因为每次找自身时需要用这个地址做基地址。(该变量每次都要更新) + mov [SecOffset_SELF], eax ;added by mingxuan 2020-9-29 + + add byte [EbrNum], 1 + cmp byte [EbrNum], 1 + jz ._add_CurPartNo +._read_ebr: + mov cl, 1 + mov bx, BaseOfBoot + mov es, bx + mov bx, OffsetOfBoot + call ReadSector + mov bx, 0 + mov dh, 1 + + ; 将EndInExt置1,表示此时正在扫描扩展分区,用于之后程序判断当前是否是扩展分区的终点 + mov [EndInExt], dh ;added by mingxuan 2020-9-29 + + jmp CHECK_PARTITION +._add_CurPartNo: + add byte [CurPartNo], 1 + jmp ._read_ebr + + +LABLE_ACTIVE: + + mov cl, 1 ;要读取的扇区个数 + + mov bx, BaseOfBoot + mov es, bx + + mov dword [es:OffsetOfActiPartStartSec], eax ;此时eax中存放活动分区的起始扇区号 + ;added by mingxuan 2020-9-12 + + mov bx, OffsetOfBoot ;对应扇区会被加载到内存的 es:bx 处 + + call ReadSector + + ;mov dh, 1 + ;call DispStr + ;mov dh, 2 + ;call DispStr + + ; mov ah,0h + ; int 16h + + jmp BaseOfBoot:OffsetOfBoot + +LABLE_NOT_FOUND: + mov dh, 3 + call DispStr + jmp $ + + +;SecOffset dw 0 ;deletd by mingxuan 2020-9-12 +;SecOffset dd 0 ;modifed by mingxuan 2020-9-29 +SecOffset_SELF dd 0 ;modifed by mingxuan 2020-9-29 +SecOffset_EXT dd 0 ;modifed by mingxuan 2020-9-29 + +EbrNum db 0 + +FirstInExt db 0 ;added by mingxuan 2020-9-29 +EndInExt db 0 ;added by mingxuan 2020-9-29 +EXTNum db 0 ;added by mingxuan 2020-9-29 +CurPartNum db 0 ;added by mingxuan 2020-9-29 + +MessageLength equ 27 +BootMessage: db "Finding active partition..." ; 27字节, 不够则用空格补齐. 序号 0 + +Message1 db " partition " +CurPartNo db "0" + db ": active" +;Message2 db "press any key to continue " +Message3 db "active partition not found!" + +DispStr: + push ax + push dx + mov ax, MessageLength + mul dh + add ax, BootMessage + mov bp, ax ; ┓ + mov ax, ds ; ┣ ES:BP = 串地址 + mov es, ax ; ┛ + mov cx, MessageLength ; CX = 串长度 + mov ax, 01301h ; AH = 13, AL = 01h + mov bx, 0007h ; 页号为0(BH = 0) 黑底白字(BL = 07h) + mov dl, 0 + int 10h ; int 10h + pop dx + pop ax + ret + + +;---------------------------------------------------------------------------- +; 函数名: ReadSector (使用扩展int13 ah=42) +;---------------------------------------------------------------------------- +; 作用: +; 从第 ax 个 Sector 开始, 将 cl 个 Sector 读入 es:bx 中 + +DAPS: + DB 0x10 ; size of packet + DB 0 ; Always 0 + D_CL DW 1 ; number of sectors to transfer + D_BX DW OffsetOfBoot ; transfer buffer (16 bit segment:16 bit offset) + D_ES DW BaseOfBoot + LBA_Lo DD 1 ; lower 32-bits of 48-bit starting LBA + LBA_Hi DD 0 ; upper 32-bits of 48-bit starting LBAs + +ReadSector: + mov [D_CL], cl + mov [D_BX], bx + mov [D_ES], es + ;mov [LBA_Lo], ax ;deleted by mingxuan 2020-9-17 + mov [LBA_Lo], eax ;modified by mingxuan 2020-9-17 + ;修改为eax的原因: 分区表用4个字节来表示起始扇区,而不是2个字节, mingxuan + mov dl, 0x80 + +.GoOnReading: + mov ah, 42h + mov si, DAPS + int 13h + jc .GoOnReading ; 如果读取错误 CF 会被置为 1, 这时就不停地读, 直到正确为止 + + ret + + +times 444 -($-$$) db 0 +dw 0x0000 \ No newline at end of file diff --git a/build_img.sh b/build_img.sh new file mode 100755 index 0000000..b9cbd09 --- /dev/null +++ b/build_img.sh @@ -0,0 +1,24 @@ +if [ $# -ne 3 ] ;then + echo "usage: $0 \$(IMAGE) \$(OBJDIR) \$(OSBOOT_START_OFFSET)" + exit 1 +fi + +IMAGE=$1 +OBJDIR=$2 +OSBOOT_START_OFFSET=$3 + +cp ./hd/test1.img ${IMAGE} +dd if=${OBJDIR}/boot/mbr.bin of=${IMAGE} bs=1 count=446 conv=notrunc + +loop_device=`losetup -f` +sudo losetup -P ${loop_device} ${IMAGE} +sudo mkfs.vfat -F 32 ${loop_device}p1 +dd if=${OBJDIR}/boot/boot.bin of=${IMAGE} bs=1 count=420 seek=${OSBOOT_START_OFFSET} conv=notrunc + +mkdir -p iso +sudo mount ${loop_device}p1 iso/ +sudo cp ${OBJDIR}/boot/loader.bin iso/ -v +sudo cp ${OBJDIR}/kernel/kernel.bin iso/ -v +sudo umount iso/ + +sudo losetup -d ${loop_device} \ No newline at end of file diff --git a/fs_flags/Makefrag b/fs_flags/Makefrag new file mode 100644 index 0000000..210b5cb --- /dev/null +++ b/fs_flags/Makefrag @@ -0,0 +1,11 @@ +OBJDIRS += fs_flags + +FS_FLAG_SRCFILES:= fs_flags/orange_flag.asm \ + fs_flags/fat32_flag.asm + +FS_FLAG_OBJFILES:= $(patsubst %.asm, $(OBJDIR)/%.bin, $(FS_FLAG_SRCFILES)) + +$(OBJDIR)/fs_flags/%.bin: fs_flags/%.asm + @echo + as obj $< + @mkdir -p $(@D) + @$(AS) -o $@ $< \ No newline at end of file diff --git a/fs_flags/fat32_flag.asm b/fs_flags/fat32_flag.asm new file mode 100644 index 0000000..3c0ce7b --- /dev/null +++ b/fs_flags/fat32_flag.asm @@ -0,0 +1,6 @@ +BLANK1 DB 0 +BLANK2 DB 0 +BLANK3 DB 0 + +FAT32_FLAG1 DD 0x4f44534d +FAT32_FLAG2 DD 0x302e3553 \ No newline at end of file diff --git a/fs_flags/orange_flag.asm b/fs_flags/orange_flag.asm new file mode 100644 index 0000000..1125b9b --- /dev/null +++ b/fs_flags/orange_flag.asm @@ -0,0 +1 @@ +ORANGE_FLAG DB 0x11 \ No newline at end of file diff --git a/hd/test1.img b/hd/test1.img new file mode 100644 index 0000000000000000000000000000000000000000..c43e8f12499552583888f687ae42622c36386f49 GIT binary patch literal 51200000 zcmeFtF%AI%5QfnoS(K7(6-y}*^;Y5tiamhV0n`!~p|mbQ;}R}}3)ounrk!t!d5QT) zf&KOIN{-bmr*)N#lgsGy$EKg6E{B;U=R>dav8L@z1Ox~WAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5;&-vYaNPSeF8<79jLq^sqisLNsQ$@%&F^t)}^ z?nXg?009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!CuT z9Xb#I00000@_%iF1P2ZrIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxiJ?9hP#00000kpF8VBsg&3 zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)V22I_00000fc#$@A;EzI2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede06TOb000000ObGL2nh}xIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!1K6Ph0RR9103iR@ zMo4hrz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)9Ka48(*O_x0T8@|giz6-1{dGFB8*TY0DWkn zpc?_Z#SkDsfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXb2z)h--g1YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA Vz<>b*1`HT5V8DO@0|pG7fdE&K5o7=W literal 0 HcmV?d00001 diff --git a/include/assert.h b/include/assert.h new file mode 100644 index 0000000..5a9ebbb --- /dev/null +++ b/include/assert.h @@ -0,0 +1,16 @@ +#ifndef MINIOS_ASSERT_H +#define MINIOS_ASSERT_H + +void _warn(const char*, int, const char*, ...); +void _panic(const char*, int, const char*, ...) __attribute__((noreturn)); + +#define warn(...) _warn(__FILE__, __LINE__, __VA_ARGS__) +#define panic(...) _panic(__FILE__, __LINE__, __VA_ARGS__) + +#define assert(x) \ + do { if (!(x)) panic("assertion failed: %s", #x); } while (0) + +// 静态assert,如果不符合条件就会直接在编译期报错 +#define static_assert(x) switch (x) case 0: case (x):; + +#endif /* MINIOS_ASSERT_H */ \ No newline at end of file diff --git a/include/console.h b/include/console.h new file mode 100644 index 0000000..783ca48 --- /dev/null +++ b/include/console.h @@ -0,0 +1,41 @@ +/*************************************************************************//** + ***************************************************************************** + * @file console.h + * @brief + * @author Forrest Y. Yu + * @date 2005 + ***************************************************************************** + *****************************************************************************/ + +/********************************************************** +* console.h //added by mingxuan 2019-5-17 +***********************************************************/ + +#ifndef _ORANGES_CONSOLE_H_ +#define _ORANGES_CONSOLE_H_ + + +/* CONSOLE */ +typedef struct s_console +{ + unsigned int crtc_start; /* set CRTC start addr reg */ + unsigned int orig; /* start addr of the console */ + unsigned int con_size; /* how many words does the console have */ + unsigned int cursor; + int is_full; + unsigned int current_line; +}CONSOLE; + + +#define SCR_UP 1 /* scroll upward */ +#define SCR_DN -1 /* scroll downward */ + +#define SCR_SIZE (80 * 25) +#define SCR_WIDTH 80 + +#define DEFAULT_CHAR_COLOR (MAKE_COLOR(BLACK, WHITE)) +#define GRAY_CHAR (MAKE_COLOR(BLACK, BLACK) | BRIGHT) +#define RED_CHAR (MAKE_COLOR(BLUE, RED) | BRIGHT) + + +#endif /* _ORANGES_CONSOLE_H_ */ \ No newline at end of file diff --git a/include/const.h b/include/const.h new file mode 100644 index 0000000..284d050 --- /dev/null +++ b/include/const.h @@ -0,0 +1,184 @@ + +/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + const.h +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + Forrest Yu, 2005 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + +#ifndef _ORANGES_CONST_H_ +#define _ORANGES_CONST_H_ + +/*最大整数定义*/ +#define MAX_UNSIGNED_INT 0xFFFFFFFF //最大的无符号整形 +#define MAX_INT 0x7FFFFFFF //最大的整形数 + +/* Color */ +/* + * e.g. MAKE_COLOR(BLUE, RED) + * MAKE_COLOR(BLACK, RED) | BRIGHT + * MAKE_COLOR(BLACK, RED) | BRIGHT | FLASH + */ +//added by mingxuan 2019-5-19 +#define BLACK 0x0 /* 0000 */ +#define WHITE 0x7 /* 0111 */ +#define RED 0x4 /* 0100 */ +#define GREEN 0x2 /* 0010 */ +#define BLUE 0x1 /* 0001 */ +#define FLASH 0x80 /* 1000 0000 */ +#define BRIGHT 0x08 /* 0000 1000 */ +#define MAKE_COLOR(x,y) ((x<<4) | y) /* MAKE_COLOR(Background,Foreground) */ + +/* Boolean */ +#define TRUE 1 +#define FALSE 0 + +/* GDT 和 IDT 中描述符的个数 */ +#define GDT_SIZE 128 +#define IDT_SIZE 256 + +/* 权限 */ +#define PRIVILEGE_KRNL 0 +#define PRIVILEGE_TASK 1 +#define PRIVILEGE_USER 3 +/* RPL */ +#define RPL_KRNL SA_RPL0 +#define RPL_TASK SA_RPL1 +#define RPL_USER SA_RPL3 + +/* 8259A interrupt controller ports. */ +#define INT_M_CTL 0x20 /* I/O port for interrupt controller */ +#define INT_M_CTLMASK 0x21 /* setting bits in this port disables ints */ +#define INT_S_CTL 0xA0 /* I/O port for second interrupt controller */ +#define INT_S_CTLMASK 0xA1 /* setting bits in this port disables ints */ + +/* 8253/8254 PIT (Programmable Interval Timer) */ +#define TIMER0 0x40 /* I/O port for timer channel 0 */ +#define TIMER_MODE 0x43 /* I/O port for timer mode control */ +#define RATE_GENERATOR 0x34 /* 00-11-010-0 : + * Counter0 - LSB then MSB - rate generator - binary + */ +#define TIMER_FREQ 1193182L/* clock frequency for timer in PC and AT */ +#define HZ 100 /* clock freq (software settable on IBM-PC) */ + +/* Hardware interrupts */ +#define NR_IRQ 16 /* Number of IRQs */ +#define CLOCK_IRQ 0 +#define KEYBOARD_IRQ 1 +#define CASCADE_IRQ 2 /* cascade enable for 2nd AT controller */ +#define ETHER_IRQ 3 /* default ethernet interrupt vector */ +#define SECONDARY_IRQ 3 /* RS232 interrupt vector for port 2 */ +#define RS232_IRQ 4 /* RS232 interrupt vector for port 1 */ +#define XT_WINI_IRQ 5 /* xt winchester */ +#define FLOPPY_IRQ 6 /* floppy disk */ +#define PRINTER_IRQ 7 +#define AT_WINI_IRQ 14 /* at winchester */ +#define MOUSE_IRQ 12 //added by mingxuan 2019-5-19 + +/* system call */ +//#define NR_SYS_CALL 23 //last modified by xw, 18/6/19 +#define NR_SYS_CALL 28 //modified by mingxuan 2019-5-17 + +/* TTY */ +//added by mingxuan 2019-5-19 +#define NR_CONSOLES 3 /* consoles */ + +/*页表相关*/ +#define PageTblNumAddr 0x500 //页表数量放在这个位置,必须与load.inc中一致 add by visual 2016.5.11 +#define KernelPageTblAddr 0x200000 //内核页表物理地址,必须与load.inc中一致 add by visual 2016.5.17 +/*线性地址描述*/ //edit by visual 2016.5.25 +#define KernelSize 0x800000 //内核的大小//add by visual 2016.5.10 +#define K_PHY2LIN(x) ((x)+0xC0000000) //内核中物理地址转线性地址 //add by visual 2016.5.10 +#define K_LIN2PHY(x) ((x)-0xC0000000) //added by xw, 18/8/27 +#define num_4B 0x4 //4B大小 +#define num_1K 0x400 //1k大小 +#define num_4K 0x1000 //4k大小 +#define num_4M 0x400000 //4M大小 +#define TextLinBase ((u32)0x0) //进程代码的起始地址,这是参考值,具体以elf描述为准 +#define TextLinLimitMAX (TextLinBase+0x20000000) //大小:512M,这是参考值,具体以elf描述为准, +#define DataLinBase TextLinLimitMAX //进程数据的起始地址,这是参考值,具体以elf描述为准 +#define DataLinLimitMAX (DataLinBase+0x20000000) //大小:512M,这是参考值,具体以elf描述为准,但是代码和数据长度总和不能超过这个值 +#define VpageLinBase DataLinLimitMAX //保留内存起始地址 +#define VpageLinLimitMAX (VpageLinBase+0x8000000-num_4K) //大小:128M-4k +#define SharePageBase VpageLinLimitMAX //共享页线性地址,执行fork\pthread的时候用,共享页必须4K对齐 +#define SharePageLimit (SharePageBase+num_4K) //大小:4k +#define HeapLinBase SharePageLimit //堆的起始地址 +#define HeapLinLimitMAX (HeapLinBase+0x40000000) //大小:1G +#define StackLinLimitMAX HeapLinLimitMAX //栈的大小: 1G-128M-4K(注意栈的基址和界限方向) +#define StackLinBase (ArgLinBase-num_4B) //=(StackLinLimitMAX+1G-128M-4K-4B)栈的起始地址,放在参数位置之前(注意堆栈的增长方向) +#define ArgLinBase (KernelLinBase-0x1000) //参数存放位置起始地址,放在3G前,暂时还没没用到 +#define ArgLinLimitMAX KernelLinBase //=(ArgLinBase+0x1000)大小:4K。 +#define KernelLinBase 0xC0000000 //内核线性起始地址(有0x30400的偏移) +#define KernelLinLimitMAX (KernelLinBase+0x40000000) //大小:1G + +/***************目前线性地址布局***************************** edit by visual 2016.5.25 +* 进程代码 0 ~ 512M ,限制大小为512M +* 进程数据 512M ~ 1G,限制大小为512M +* 进程保留内存(以后可能存放虚页表和其他一些信息) 1G ~ 1G+128M,限制大小为128M,共享页放在这个位置 +* 进程堆 1G+128M ~ 2G+128M,限制大小为1G +* 进程栈 2G+128M ~ 3G-4K,限制大小为 1G-128M-4K +* 进程参数 3G-4K~3G,限制大小为4K +* 内核 3G~4G,限制大小为1G +***********************************************************/ + +//#define ShareTblLinAddr (KernelLinLimitMAX-0x1000) //公共临时共享页,放在内核最后一个页表的最后一项上 + +/*分页机制常量的定义,必须与load.inc中一致*/ //add by visual 2016.4.5 +#define PG_P 1 // 页存在属性位 +#define PG_RWR 0 // R/W 属性位值, 读/执行 +#define PG_RWW 2 // R/W 属性位值, 读/写/执行 +#define PG_USS 0 // U/S 属性位值, 系统级 +#define PG_USU 4 // U/S 属性位值, 用户级 +#define PG_PS 64 // PS属性位值,4K页 + +/* AT keyboard */ +/* 8042 ports */ +//added by mingxuan 2019-5-19 +#define KB_DATA 0x60 /* I/O port for keyboard data + * Read : Read Output Buffer + * Write: Write Input Buffer + * (8042 Data & 8048 Command) + */ +#define KB_CMD 0x64 /* I/O port for keyboard command + * Read : Read Status Register + * Write: Write Input Buffer + * (8042 Command) + */ +#define KB_STA 0x64 +#define KEYSTA_SEND_NOTREADY 0x02 +#define KBSTATUS_IBF 0x02 +#define KBSTATUS_OBF 0x01 + +#define wait_KB_write() while(in_byte(KB_STA) & KBSTATUS_IBF) +#define wait_KB_read() while(in_byte(KB_STA) & KBSTATUS_OBF) + +#define KEYCMD_WRITE_MODE 0x60 +#define KBC_MODE 0x47 + +#define KEYCMD_SENDTO_MOUSE 0xd4 +#define MOUSECMD_ENABLE 0xf4 +#define KBCMD_EN_MOUSE_INTFACE 0xa8 + +#define LED_CODE 0xED +#define KB_ACK 0xFA + +/* VGA */ +//added by mingxuan 2019-5-19 +#define CRTC_ADDR_REG 0x3D4 /* CRT Controller Registers - Addr Register */ +#define CRTC_DATA_REG 0x3D5 /* CRT Controller Registers - Data Register */ +#define START_ADDR_H 0xC /* reg index of video mem start addr (MSB) */ +#define START_ADDR_L 0xD /* reg index of video mem start addr (LSB) */ +#define CURSOR_H 0xE /* reg index of cursor position (MSB) */ +#define CURSOR_L 0xF /* reg index of cursor position (LSB) */ +#define V_MEM_BASE 0xB8000 /* base of color video memory */ +#define V_MEM_SIZE 0x8000 /* 32K: B8000H -> BFFFFH */ + +#define STD_IN 0 +#define STD_OUT 1 +#define STD_ERR 2 + +/* max() & min() */ +//added by mingxuan 2019-5-19 +#define max(a,b) ((a) > (b) ? (a) : (b)) +#define min(a,b) ((a) < (b) ? (a) : (b)) + +#endif /* _ORANGES_CONST_H_ */ diff --git a/include/elf.h b/include/elf.h new file mode 100644 index 0000000..2568085 --- /dev/null +++ b/include/elf.h @@ -0,0 +1,61 @@ +/********************************************************** +* elf.h //add by visual 2016.5.16 +***********************************************************/ + +#define EI_NIDENT 16 + +/************************************ +* elf 头 +*****************************************/ +typedef struct{ + u8 e_ident[EI_NIDENT]; //ELF魔数,ELF字长,字节序,ELF文件版本等 + u16 e_type; //ELF文件类型,REL, 可执行文件,共享目标文件等 + u16 e_machine; //ELF的CPU平台属性 + u32 e_version; //ELF版本号 + u32 e_entry; //ELF程序的入口虚拟地址 + u32 e_phoff; //program header table(program头)在文件中的偏移 + u32 e_shoff; //section header table(section头)在文件中的偏移 + u32 e_flags; //用于标识ELF文件平台相关的属性 + u16 e_ehsize; //elf header(本文件头)的长度 + u16 e_phentsize; //program header table 中每一个条目的长度 + u16 e_phnum; //program header table 中有多少个条目 + u16 e_shentsize; //section header table 中每一个条目的长度 + u16 e_shnum; //section header table 中有多少个条目 + u16 e_shstrndx; //section header table 中字符索引 +}Elf32_Ehdr; + +/******************************************* +* program头(程序头) +**********************************************/ +typedef struct{ + u32 p_type; //该program 类型 + u32 p_offset; //该program 在文件中的偏移量 + u32 p_vaddr; //该program 应该放在这个线性地址 + u32 p_paddr; //该program 应该放在这个物理地址(对只使用物理地址的系统有效) + u32 p_filesz; //该program 在文件中的长度 + u32 p_memsz; //该program 在内存中的长度(不一定和filesz相等) + u32 p_flags; //该program 读写权限 + u32 p_align; //该program 对齐方式 +}Elf32_Phdr; + + +/********************************************* +* section头(段头) +************************************************/ +typedef struct +{ + u32 s_name; //该section 段的名字 + u32 s_type; //该section 的类型,代码段,数据段,符号表等 + u32 s_flags; //该section 在进程虚拟地址空间中的属性 + u32 s_addr; //该section 的虚拟地址 + u32 s_offset; //该section 在文件中的偏移 + u32 s_size; //该section 的长度 + u32 s_link; //该section 头部表符号链接 + u32 s_info; //该section 附加信息 + u32 s_addralign; //该section 对齐方式 + u32 s_entsize; //该section 若有固定项目,则给出固定项目的大小,如符号表 +}Elf32_Shdr; + +void read_Ehdr(u32 fd,Elf32_Ehdr *File_Ehdr,u32 offset); +void read_Phdr(u32 fd,Elf32_Phdr *File_Phdr,u32 offset); +void read_Shdr(u32 fd,Elf32_Shdr *File_Shdr,u32 offset); \ No newline at end of file diff --git a/include/fat32.h b/include/fat32.h new file mode 100644 index 0000000..c15639f --- /dev/null +++ b/include/fat32.h @@ -0,0 +1,189 @@ +/********************************************************** +* fat32.h //added by mingxuan 2019-5-17 +***********************************************************/ + +#ifndef FAT32_H +#define FAT32_H + +#define TRUE 1//是 +#define FALSE 0//否 + +#define OK 1//正常返回 +#define SYSERROR 2//系统错误 +#define VDISKERROR 3//虚拟磁盘错误 +#define INSUFFICIENTSPACE 4//虚拟磁盘空间不足 +#define WRONGPATH 5//路径有误 +#define NAMEEXIST 6//文件或目录名已存在 +#define ACCESSDENIED 7//读写权限不对拒绝访问 + +#define C 1//创建 //added by mingxuan 2019-5-18 +#define R 5//读 +#define W 6//写 +#define RW 2 //读写 //added by mingxuan 2019-5-18 + +#define F 1//文件 +#define D 0//目录 + +typedef int STATE;//函数返回状态 + +typedef unsigned char BYTE;//字节 +typedef unsigned short WORD;//双字节 +typedef unsigned long DWORD;//四字节 +typedef unsigned int UINT;//无符号整型 +typedef char CHAR;//字符类型 + +typedef unsigned char* PBYTE; +typedef unsigned short* PWORD; +typedef unsigned long* PDWORD;//四字节指针 +typedef unsigned int* PUINT;//无符号整型指针 +typedef char* PCHAR;//字符指针 + +typedef struct//定义目录项:占32个字节 +{ + BYTE filename[8];//文件名:占8个字节 + BYTE extension[3];//扩展名:占3个字节 + BYTE proByte;//属性字节:占1个字节 + BYTE sysReserved;//系统保留:占1个字节 + BYTE createMsecond;//创建时间的10毫秒位:占1个字节 + WORD createTime;//文件创建时间:占2个字节 + WORD createDate;//文件创建日期:占2个字节 + WORD lastAccessDate;//文件最后访问日期:占2个字节 + WORD highClusterNum;//文件的起始簇号的高16位:占2个字节 + WORD lastModifiedTime;//文件的最近修改时间 + WORD lastModifiedDate;//文件的最近修改日期 + WORD lowClusterNum;//文件的起始簇号的低16位:占2个字节 + DWORD filelength;//文件的长度:占4个字节 +}Record,*PRecord; + +typedef struct//定义长文件名目录项:占32个字节 +{ + BYTE proByte;//属性字节 + BYTE name1[10];//长文件名第1段 + BYTE longNameFlag;//长文件名目录项标志,取值0FH + BYTE sysReserved;//系统保留 + BYTE name2[19];//长文件名第2段 +}LONGNAME; + +typedef struct{//文件类型 + CHAR parent[256];//父路径 + CHAR name[256];//文件名 + DWORD start;//起始地址 + DWORD off;//总偏移量,以字节为单位 + DWORD size;//文件的大小,以字节为单位 + UINT flag;//文件读写标志 +}File,*PFile; + +typedef struct{//动态数组元素类型,用于存储文件或目录的基本信息 + CHAR fullpath[256];//绝对路径 + CHAR name[256];//文件名或目录名 + UINT tag;//1表示文件,0表示目录 +}DArrayElem; + +typedef struct{//动态数组类型 + DArrayElem *base;//数组基地址 + UINT offset;//读取数组时的偏移量 + UINT used;//数组当前已使用的容量 + UINT capacity;//数组的总容量 + UINT increment;//当数组容量不足时,动态增长的步长 +}DArray; + +typedef struct{//文件或目录属性类型 + BYTE type;//0x10表示目录,否则表示文件 + CHAR name[256];//文件或目录名称 + CHAR location[256];//文件或目录的位置,绝对路径 + DWORD size;//文件的大小或整个目录中(包括子目录中)的文件总大小 + CHAR createTime[20];//创建时间型如:yyyy-MM-dd hh:mm:ss类型 + CHAR lastModifiedTime[20];//最后修改时间 + union{ + CHAR lastAccessDate[11];//最后访问时间,当type为文件值有效 + UINT contain[2];//目录中的文件个数和子目录的个数,当type为目录时有效 + }share; +}Properties; + +STATE CreateVDisk(DWORD size); +STATE FormatVDisk(PCHAR path,PCHAR volumelabel); +STATE LoadVDisk(PCHAR path); +STATE CreateDir(const char *dirname); +STATE CreateFile(const char *filename); +STATE OpenFile(const char *filename,int mode); +STATE CloseFile(int fd); +STATE OpenDir(const char *dirname); + +STATE ReadFile(int fd,void *buf, int length); +STATE WriteFile(int fd, const void *buf, int length); +STATE CopyFileIn(PCHAR sfilename,PCHAR dfilename); +STATE CopyFileOut(PCHAR sfilename,PCHAR dfilename); +STATE DeleteFile(const char *filename); +STATE DeleteDir(const char *dirname); +STATE ListAll(PCHAR dirname,DArray *array); +STATE IsFile(PCHAR path,PUINT tag); +STATE GetFileLength(PCHAR filename,PDWORD length); +STATE Rename(PCHAR path,PCHAR newname); +STATE CopyFile(PCHAR sfilename,PCHAR dpath); +STATE CopyDir(PCHAR sdirname,PCHAR dpath); +STATE Move(PCHAR sfilename,PCHAR dpath); +STATE GetProperty(PCHAR filename,Properties *properties); +STATE GetParenetDir(PCHAR name,PCHAR parentDir); +STATE GetVDiskFreeSpace(PDWORD left); +STATE GetVDiskSize(PDWORD totalsize); +STATE GetCurrentPath(PCHAR path); + +DArray* InitDArray(UINT initialCapacity,UINT capacityIncrement); +void AddElement(DArray *array,DArrayElem element); +DArrayElem* NextElement(DArray *array); +void DestroyDArray(DArray *array); + +void TimeToBytes(WORD result[]);//t是一个WORD类型的数组,长度为2,第0个元素为日期,第1个元素为时间 +void BytesToTime(WORD date,WORD time,WORD result[]);//t是一个表示时间的WORD数组,长度为6,分别表示年、月、日、时、分、秒 +void TimeToString(WORD result[],PCHAR timestring); +STATE IsFullPath(PCHAR path);//判断path是否是一个绝对路径 +void ToFullPath(PCHAR path,PCHAR fullpath);//将path转换成绝对路径,用fullpath返回 +void GetParentFromPath(PCHAR fullpath,PCHAR parent);//从一个绝对路径中得到它的父路径(父目录) +void GetNameFromPath(PCHAR path,PCHAR name);//从一个路径中得到文件或目录的名称 +void MakeFullPath(PCHAR parent,PCHAR name,PCHAR fullpath);//将父路径和文件名组合成一个绝对路径 +void FormatFileNameAndExt(PCHAR filename,PCHAR name,PCHAR ext);//将一个文件名转换成带空格的文件名和后缀名的形式,为了写入目录项。 +void FormatDirNameAndExt(PCHAR dirname,PCHAR name,PCHAR ext);//将一个目录名转换成带空格的目录名和后缀的形式,为了写入目录项。 +void ChangeCurrentPath(PCHAR addpath); + +void GetNameFromRecord(Record record,PCHAR fullname);//从目录项中得到文件或目录的全名 +STATE PathToCluster(PCHAR path, PDWORD cluster);//将抽象的路径名转换成簇号 +STATE FindSpaceInDir(DWORD parentCluster,PCHAR name,PDWORD sectorIndex,PDWORD off_in_sector);//在指定的目录中寻找空的目录项 +STATE FindClusterForDir(PDWORD pcluster);//为目录分配簇 +STATE ReadRecord(DWORD parentCluster,PCHAR name,PRecord record,PDWORD sectorIndex,PDWORD off_in_sector);//获得指定的目录项的位置(偏移量) +STATE WriteRecord(Record record,DWORD sectorIndex,DWORD off_in_sector); +STATE FindClusterForFile(DWORD totalClusters,PDWORD clusters);//为一个文件分配totalClusters个簇 +STATE WriteFAT(DWORD totalClusters,PDWORD clusters);//写FAT1和FAT2 +STATE GetNextCluster(DWORD curClusterIndex,PDWORD nextClusterIndex); +STATE AddCluster(DWORD startCluster,DWORD num);//cluster表示该文件或目录所在目录的簇,num表示增加几个簇 +void CreateRecord(PCHAR filename,BYTE type,DWORD startCluster,DWORD size,PRecord precord);//创建一个目录项 +void GetFileOffset(PFile pfile,PDWORD sectorIndex,PDWORD off_in_sector,PUINT isLastSector);//将文件当前位置转换成相对虚拟磁盘文件起始地址的偏移量,便于读写。 +STATE AllotClustersForEmptyFile(PFile pfile,DWORD size);//为一个打开的空文件分配簇。 +STATE NeedMoreCluster(PFile pfile,DWORD size,PDWORD number);//判断是否需要更多的簇,如果需要就返回需要的簇的个数 +STATE ClearRecord(DWORD parentCluster,PCHAR name,PDWORD startCluster);//删除记录项 +void ClearFATs(DWORD startcluster);//删除簇号 +STATE FindNextRecord(PDWORD cluster,PDWORD off,PCHAR name,PUINT tag); +void DeleteAllRecord(DWORD startCluster); +void GetContent(DWORD startCluster,PDWORD size,PDWORD files,PDWORD folders); +void ClearClusters(DWORD cluster); +void ReadSector(BYTE*,DWORD sectorIndex); +void WriteSector(BYTE*,DWORD sectorIndex); +void GetNextSector(PFile pfile,DWORD curSectorIndex,PDWORD nextSectorIndex,PUINT isLastSector); + +void init_fs_fat(); + +int rw_sector_fat(int io_type, int dev, unsigned long long pos, int bytes, int proc_nr, void* buf); +int rw_sector_sched_fat(int io_type, int dev, int pos, int bytes, int proc_nr, void* buf); + +void DisErrorInfo(STATE state); + +int sys_CreateFile(void *uesp); +int sys_DeleteFile(void *uesp); +int sys_OpenFile(void *uesp); +int sys_CloseFile(void *uesp); +int sys_WriteFile(void *uesp); +int sys_ReadFile(void *uesp); +int sys_OpenDir(void *uesp); +int sys_CreateDir(void *uesp); +int sys_DeleteDir(void *uesp); +int sys_ListDir(void *uesp); +#endif \ No newline at end of file diff --git a/include/fs.h b/include/fs.h new file mode 100644 index 0000000..5401c9a --- /dev/null +++ b/include/fs.h @@ -0,0 +1,37 @@ +/** + * fs.h + * This file contains APIs of filesystem, it's used inside the kernel. + * There is a seperate header file for user program's use. + * This file is added by xw. 18/6/17 + */ + +#ifndef FS_H +#define FS_H + +/* APIs of file operation */ +#define O_CREAT 1 +#define O_RDWR 2 + +#define SEEK_SET 1 +#define SEEK_CUR 2 +#define SEEK_END 3 + +#define MAX_PATH 128 +#define MAX_FILENAME_LEN 12 + +void init_fs(); + +//added by mingxuan 2019-5-17 +int real_open(const char *pathname, int flags); +int real_close(int fd); +int real_read(int fd, void *buf, int count); +int real_write(int fd, const void *buf, int count); +int real_unlink(const char *pathname); +int real_lseek(int fd, int offset, int whence); + +//added by mingxuan 2020-10-30 +void read_super_block(int dev); +struct super_block* get_super_block(int dev); +int get_fs_dev(int drive, int fs_type); + +#endif /* FS_H */ diff --git a/include/fs_const.h b/include/fs_const.h new file mode 100644 index 0000000..5744c92 --- /dev/null +++ b/include/fs_const.h @@ -0,0 +1,120 @@ +/** + * fs_const.h + * This file contains consts and macros associated with filesystem. + * The code is added by zcr, and the file is added by xw. 18/6/17 + */ + +/* TTY */ +#define NR_CONSOLES 3 /* consoles */ + +/* max() & min() */ +#define max(a,b) ((a) > (b) ? (a) : (b)) +#define min(a,b) ((a) < (b) ? (a) : (b)) + +/* macros for messages */ +#define FD u.m3.m3i1 +#define PATHNAME u.m3.m3p1 +#define FLAGS u.m3.m3i1 +#define NAME_LEN u.m3.m3i2 +#define CNT u.m3.m3i2 +#define REQUEST u.m3.m3i2 +#define PROC_NR u.m3.m3i3 +#define DEVICE u.m3.m3i4 +#define POSITION u.m3.m3l1 +#define BUF u.m3.m3p2 +#define OFFSET u.m3.m3i2 +#define WHENCE u.m3.m3i3 + +#define RETVAL u.m3.m3i1 + + +#define DIOCTL_GET_GEO 1 + +/* Hard Drive */ +#define SECTOR_SIZE 512 +#define SECTOR_BITS (SECTOR_SIZE * 8) +#define SECTOR_SIZE_SHIFT 9 + +/* major device numbers (corresponding to kernel/global.c::dd_map[]) */ +#define NO_DEV 0 +#define DEV_FLOPPY 1 +#define DEV_CDROM 2 +#define DEV_HD 3 +#define DEV_CHAR_TTY 4 +#define DEV_SCSI 5 + +/* make device number from major and minor numbers */ +#define MAJOR_SHIFT 8 +#define MAKE_DEV(a,b) ((a << MAJOR_SHIFT) | b) + +/* separate major and minor numbers from device number */ +#define MAJOR(x) ((x >> MAJOR_SHIFT) & 0xFF) +#define MINOR(x) (x & 0xFF) + +#define INVALID_INODE 0 +#define ROOT_INODE 1 + + +#define MAX_DRIVES 2 +#define NR_PART_PER_DRIVE 4 // 每块硬盘(驱动器)只能有4个主分区, mingxuan +#define NR_SUB_PER_PART 16 // 每个扩展分区最多有16个逻辑分区, mingxuan +#define NR_SUB_PER_DRIVE (NR_SUB_PER_PART * NR_PART_PER_DRIVE) //每块硬盘(驱动器)最多有16 * 4 = 64个逻辑分区, mingxuan +#define NR_PRIM_PER_DRIVE (NR_PART_PER_DRIVE + 1) // 表示的是hd[0~4]这5个分区,因为有些代码中我们把整块硬盘(hd0)和主分区(hd[1~4])放在一起看待, mingxuan + +/** + * @def MAX_PRIM + * Defines the max minor number of the primary partitions. + * If there are 2 disks, prim_dev ranges in hd[0-9], this macro will + * equals 9. + */ +// MAX_PRIM定义的是主分区的最大值,比如若有两块硬盘,那第一块硬盘的主分区为hd[1~4],第二块硬盘的主分区为hd[6~9] +// 所以MAX_PRIM为9,我们定义的hd1a的设备号应大于它,这样通过与MAX_PRIM比较,我们就可以知道一个设备是主分区还是逻辑分区 +// mingxuan +#define MAX_PRIM (MAX_DRIVES * NR_PRIM_PER_DRIVE - 1) // MAX_PRIM = 2 * 4 - 1 = 9 +#define MAX_SUBPARTITIONS (NR_SUB_PER_DRIVE * MAX_DRIVES) // MAX_SUBPARTITIONS = 64 * 2 = 128 + +/* name of drives */ +// added by mingxuan 2020-10-27 +#define PRIMARY_MASTER 0x0 +#define PRIMARY_SLAVE 0x1 +#define SECONDARY_MASTER 0x2 +#define SECONDARY_SLAVE 0x3 + +/* device numbers of hard disk */ +#define MINOR_hd0 0x0 // added by mingxuan 2020-10-9 +#define MINOR_hd1 0x1 // added by mingxuan 2020-10-12 +#define MINOR_hd2 0x2 // added by mingxuan 2020-10-12 + +#define MINOR_hd1a 0x10 +#define MINOR_hd2a (MINOR_hd1a + NR_SUB_PER_PART) // MINOR_hd2a = 16 + 16 + +#define MAJOR_FAT 0x4 //modified by mingxuan 2020-10-22 + +#define MINOR_BOOT MINOR_hd2 //modified by mingxuan 2020-10-12 + +#define P_PRIMARY 0 +#define P_EXTENDED 1 + +#define ORANGES_PART 0x99 /* Orange'S partition */ +#define NO_PART 0x00 /* unused entry */ +#define EXT_PART 0x05 /* extended partition */ + +#define NR_FILE_DESC 128 /* FIXME */ //modified by mingxuan 2019-5-19 +#define NR_INODE 64 /* FIXME */ +#define NR_SUPER_BLOCK 8 + + +/* INODE::i_mode (octal, lower 32 bits reserved) */ +#define I_TYPE_MASK 0170000 +#define I_REGULAR 0100000 +#define I_BLOCK_SPECIAL 0060000 +#define I_DIRECTORY 0040000 +#define I_CHAR_SPECIAL 0020000 +#define I_NAMED_PIPE 0010000 + +#define is_special(m) ((((m) & I_TYPE_MASK) == I_BLOCK_SPECIAL) || \ + (((m) & I_TYPE_MASK) == I_CHAR_SPECIAL)) + +#define NR_DEFAULT_FILE_SECTS 2048 /* 2048 * 512 = 1MB */ + +#define FSBUF_SIZE 0x100000 //added by mingxuan 2019-5-17 \ No newline at end of file diff --git a/include/fs_misc.h b/include/fs_misc.h new file mode 100644 index 0000000..2966527 --- /dev/null +++ b/include/fs_misc.h @@ -0,0 +1,203 @@ +/** + * fs_misc.h + * This file contains miscellaneous defines and declarations associated with filesystem. + * The code is added by zcr, and the file is added by xw. 18/6/17 + */ + +#ifndef FS_MISC_H +#define FS_MISC_H + +#include "fat32.h" + +/** + * @struct dev_drv_map fs.h "include/sys/fs.h" + * @brief The Device_nr.\ - Driver_nr.\ MAP. + */ +struct dev_drv_map { + int driver_nr; /**< The proc nr.\ of the device driver. */ +}; + +/** + * @def MAGIC_V1 + * @brief Magic number of FS v1.0 + */ +#define MAGIC_V1 0x111 + +/** + * @struct super_block fs.h "include/fs.h" + * @brief The 2nd sector of the FS + * + * Remember to change SUPER_BLOCK_SIZE if the members are changed. + */ +struct super_block { + u32 magic; /**< Magic number */ + u32 nr_inodes; /**< How many inodes */ + u32 nr_sects; /**< How many sectors */ + u32 nr_imap_sects; /**< How many inode-map sectors */ + u32 nr_smap_sects; /**< How many sector-map sectors */ + u32 n_1st_sect; /**< Number of the 1st data sector */ + u32 nr_inode_sects; /**< How many inode sectors */ + u32 root_inode; /**< Inode nr of root directory */ + u32 inode_size; /**< INODE_SIZE */ + u32 inode_isize_off; /**< Offset of `struct inode::i_size' */ + u32 inode_start_off; /**< Offset of `struct inode::i_start_sect' */ + u32 dir_ent_size; /**< DIR_ENTRY_SIZE */ + u32 dir_ent_inode_off;/**< Offset of `struct dir_entry::inode_nr' */ + u32 dir_ent_fname_off;/**< Offset of `struct dir_entry::name' */ + + /* + * the following item(s) are only present in memory + */ + int sb_dev; /**< the super block's home device */ + int fs_type; //added by mingxuan 2020-10-30 +}; + +/** + * @def SUPER_BLOCK_SIZE + * @brief The size of super block \b in \b the \b device. + * + * Note that this is the size of the struct in the device, \b NOT in memory. + * The size in memory is larger because of some more members. + */ +//#define SUPER_BLOCK_SIZE 56 +#define SUPER_BLOCK_SIZE 64 //modified by mingxuan 2020-10-30 + +/** + * @struct inode + * @brief i-node + * + * The \c start_sect and\c nr_sects locate the file in the device, + * and the size show how many bytes is used. + * If size < (nr_sects * SECTOR_SIZE) , the rest bytes + * are wasted and reserved for later writing. + * + * \b NOTE: Remember to change INODE_SIZE if the members are changed + */ +struct inode { + u32 i_mode; /**< Accsess mode */ + u32 i_size; /**< File size */ + u32 i_start_sect; /**< The first sector of the data */ + u32 i_nr_sects; /**< How many sectors the file occupies */ + u8 _unused[16]; /**< Stuff for alignment */ + + /* the following items are only present in memory */ + int i_dev; + int i_cnt; /**< How many procs share this inode */ + int i_num; /**< inode nr. */ +}; + +/** + * @def INODE_SIZE + * @brief The size of i-node stored \b in \b the \b device. + * + * Note that this is the size of the struct in the device, \b NOT in memory. + * The size in memory is larger because of some more members. + */ +#define INODE_SIZE 32 + +/** + * @struct dir_entry + * @brief Directory Entry + */ +struct dir_entry { + int inode_nr; /**< inode nr. */ + char name[MAX_FILENAME_LEN]; /**< Filename */ +}; + +/** + * @def DIR_ENTRY_SIZE + * @brief The size of directory entry in the device. + * + * It is as same as the size in memory. + */ +#define DIR_ENTRY_SIZE sizeof(struct dir_entry) + +/** + * @struct file_desc + * @brief File Descriptor + */ + +//added by mingxuan 2019-5-17 +union ptr_node{ + struct inode* fd_inode; /**< Ptr to the i-node */ + PFile fd_file; //指向fat32的file结构体 +}; + +struct file_desc { + int fd_mode; /**< R or W */ + int fd_pos; /**< Current position for R/W. */ + //struct inode* fd_inode; /**< Ptr to the i-node */ //deleted by mingxuan 2019-5-17 + + //added by mingxuan 2019-5-17 + union ptr_node fd_node; + int flag; //用于标志描述符是否被使用 + int dev_index; +}; + + +/** + * Since all invocations of `rw_sector()' in FS look similar (most of the + * params are the same), we use this macro to make code more readable. + */ +#define RD_SECT(dev,sect_nr,fsbuf) rw_sector(DEV_READ, \ + dev, \ + (sect_nr) * SECTOR_SIZE, \ + SECTOR_SIZE, /* read one sector */ \ + proc2pid(p_proc_current),/*TASK_A*/ \ + fsbuf); + +#define WR_SECT(dev,sect_nr,fsbuf) rw_sector(DEV_WRITE, \ + dev, \ + (sect_nr) * SECTOR_SIZE, \ + SECTOR_SIZE, /* write one sector */ \ + proc2pid(p_proc_current), \ + fsbuf); + +//modified by mingxuan 2020-10-27 +#define RD_SECT_FAT(dev, buf, sect_nr) rw_sector_fat(DEV_READ, \ + dev, \ + (sect_nr) * SECTOR_SIZE, \ + SECTOR_SIZE, /* read one sector */ \ + proc2pid(p_proc_current),/*TASK_A*/ \ + buf); + +//modified by mingxuan 2020-10-27 +#define WR_SECT_FAT(dev, buf, sect_nr) rw_sector_fat(DEV_WRITE, \ + dev, \ + (sect_nr) * SECTOR_SIZE, \ + SECTOR_SIZE, /* write one sector */ \ + proc2pid(p_proc_current), \ + buf); + +//added by xw, 18/8/27 +#define RD_SECT_SCHED(dev,sect_nr,fsbuf) rw_sector_sched(DEV_READ, \ + dev, \ + (sect_nr) * SECTOR_SIZE, \ + SECTOR_SIZE, /* read one sector */ \ + proc2pid(p_proc_current),/*TASK_A*/ \ + fsbuf); + +#define WR_SECT_SCHED(dev,sect_nr,fsbuf) rw_sector_sched(DEV_WRITE, \ + dev, \ + (sect_nr) * SECTOR_SIZE, \ + SECTOR_SIZE, /* write one sector */ \ + proc2pid(p_proc_current), \ + fsbuf); + +//modified by mingxuan 2020-10-27 +#define RD_SECT_SCHED_FAT(dev, buf, sect_nr) rw_sector_sched_fat(DEV_READ, \ + dev, \ + (sect_nr) * SECTOR_SIZE, \ + SECTOR_SIZE, /* read one sector */ \ + proc2pid(p_proc_current),/*TASK_A*/ \ + buf); + +//modified by mingxuan 2020-10-27 +#define WR_SECT_SCHED_FAT(dev, buf, sect_nr) rw_sector_sched_fat(DEV_WRITE, \ + dev, \ + (sect_nr) * SECTOR_SIZE, \ + SECTOR_SIZE, /* write one sector */ \ + proc2pid(p_proc_current), \ + buf); +//~xw +#endif /* FS_MISC_H */ diff --git a/include/global.h b/include/global.h new file mode 100644 index 0000000..096d287 --- /dev/null +++ b/include/global.h @@ -0,0 +1,53 @@ + +/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + global.h +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + Forrest Yu, 2005 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + +/* equal to 1 if kernel is initializing, equal to 0 if done. + * added by xw, 18/5/31 + */ +extern int kernel_initial; + +extern int ticks; + +extern int disp_pos; +extern u8 gdt_ptr[6]; // 0~15:Limit 16~47:Base +extern DESCRIPTOR gdt[GDT_SIZE]; +extern u8 idt_ptr[6]; // 0~15:Limit 16~47:Base +extern GATE idt[IDT_SIZE]; + +extern u32 k_reenter; +extern int u_proc_sum; //内核中用户进程/线程数量 add by visual 2016.5.25 + +extern TSS tss; +extern PROCESS* p_proc_current; +extern PROCESS* p_proc_next; //the next process that will run. added by xw, 18/4/26 + +extern PROCESS cpu_table[]; //added by xw, 18/6/1 +extern PROCESS proc_table[]; +extern char task_stack[]; +extern TASK task_table[]; +extern irq_handler irq_table[]; + +/* tty */ +//added by mingxuan 2019-5-19 +#include "tty.h" +#include "console.h" + +extern TTY tty_table[]; +extern CONSOLE console_table[]; +extern int current_console; + +// u32 PageTblNum; //页表数量 add by visual 2016.4.5 +extern u32 cr3_ready; //当前进程的页目录 add by visual 2016.4.5 + +struct memfree{ + u32 addr; + u32 size; +}; + +#include "fs_const.h" +#include "hd.h" +extern struct hd_info hd_info[1]; //added by mingxuan 2020-10-27 \ No newline at end of file diff --git a/include/hd.h b/include/hd.h new file mode 100644 index 0000000..641f46d --- /dev/null +++ b/include/hd.h @@ -0,0 +1,308 @@ +/*************************************************************************//** + ***************************************************************************** + * @file include/sys/hd.h + * @brief + * @author Forrest Y. Yu + * @date 2008 + ***************************************************************************** + *****************************************************************************/ + +#ifndef _ORANGES_HD_H_ +#define _ORANGES_HD_H_ + +/** + * @struct part_ent + * @brief Partition Entry struct. + * + * Master Boot Record (MBR): + * Located at offset 0x1BE in the 1st sector of a disk. MBR contains + * four 16-byte partition entries. Should end with 55h & AAh. + * + * partitions in MBR: + * A PC hard disk can contain either as many as four primary partitions, + * or 1-3 primaries and a single extended partition. Each of these + * partitions are described by a 16-byte entry in the Partition Table + * which is located in the Master Boot Record. + * + * extented partition: + * It is essentially a link list with many tricks. See + * http://en.wikipedia.org/wiki/Extended_boot_record for details. + */ +struct part_ent { + u8 boot_ind; /** + * boot indicator + * Bit 7 is the active partition flag, + * bits 6-0 are zero (when not zero this + * byte is also the drive number of the + * drive to boot so the active partition + * is always found on drive 80H, the first + * hard disk). + */ + + u8 start_head; /** + * Starting Head + */ + + u8 start_sector; /** + * Starting Sector. + * Only bits 0-5 are used. Bits 6-7 are + * the upper two bits for the Starting + * Cylinder field. + */ + + u8 start_cyl; /** + * Starting Cylinder. + * This field contains the lower 8 bits + * of the cylinder value. Starting cylinder + * is thus a 10-bit number, with a maximum + * value of 1023. + */ + + u8 sys_id; /** + * System ID + * e.g. + * 01: FAT12 + * 81: MINIX + * 83: Linux + */ + + u8 end_head; /** + * Ending Head + */ + + u8 end_sector; /** + * Ending Sector. + * Only bits 0-5 are used. Bits 6-7 are + * the upper two bits for the Ending + * Cylinder field. + */ + + u8 end_cyl; /** + * Ending Cylinder. + * This field contains the lower 8 bits + * of the cylinder value. Ending cylinder + * is thus a 10-bit number, with a maximum + * value of 1023. + */ + + u32 start_sect; /** + * starting sector counting from + * 0 / Relative Sector. / start in LBA + */ + + u32 nr_sects; /** + * nr of sectors in partition + */ + +} ; + +// added by mingxuan 2020-10-27 +struct fs_flags +{ + u8 orange_flag; + //u8 reserved1; + //u8 reserved2; + u16 reserved; + u32 fat32_flag1; + u32 fat32_flag2; +}; + + +/********************************************/ +/* I/O Ports used by hard disk controllers. */ +/********************************************/ +/* slave disk not supported yet, all master registers below */ + +/* Command Block Registers */ +/* MACRO PORT DESCRIPTION INPUT/OUTPUT */ +/* ----- ---- ----------- ------------ */ +#define REG_DATA 0x1F0 /* Data I/O */ +#define REG_FEATURES 0x1F1 /* Features O */ +#define REG_ERROR REG_FEATURES /* Error I */ + /* The contents of this register are valid only when the error bit + (ERR) in the Status Register is set, except at drive power-up or at the + completion of the drive's internal diagnostics, when the register + contains a status code. + When the error bit (ERR) is set, Error Register bits are interpreted as such: + | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | + +-----+-----+-----+-----+-----+-----+-----+-----+ + | BRK | UNC | | IDNF| | ABRT|TKONF| AMNF| + +-----+-----+-----+-----+-----+-----+-----+-----+ + | | | | | | | | + | | | | | | | `--- 0. Data address mark not found after correct ID field found + | | | | | | `--------- 1. Track 0 not found during execution of Recalibrate command + | | | | | `--------------- 2. Command aborted due to drive status error or invalid command + | | | | `--------------------- 3. Not used + | | | `--------------------------- 4. Requested sector's ID field not found + | | `--------------------------------- 5. Not used + | `--------------------------------------- 6. Uncorrectable data error encountered + `--------------------------------------------- 7. Bad block mark detected in the requested sector's ID field + */ +#define REG_NSECTOR 0x1F2 /* Sector Count I/O */ +#define REG_LBA_LOW 0x1F3 /* Sector Number / LBA Bits 0-7 I/O */ +#define REG_LBA_MID 0x1F4 /* Cylinder Low / LBA Bits 8-15 I/O */ +#define REG_LBA_HIGH 0x1F5 /* Cylinder High / LBA Bits 16-23 I/O */ +#define REG_DEVICE 0x1F6 /* Drive | Head | LBA bits 24-27 I/O */ + /* | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | + +-----+-----+-----+-----+-----+-----+-----+-----+ + | 1 | L | 1 | DRV | HS3 | HS2 | HS1 | HS0 | + +-----+-----+-----+-----+-----+-----+-----+-----+ + | | \_____________________/ + | | | + | | `------------ If L=0, Head Select. + | | These four bits select the head number. + | | HS0 is the least significant. + | | If L=1, HS0 through HS3 contain bit 24-27 of the LBA. + | `--------------------------- Drive. When DRV=0, drive 0 (master) is selected. + | When DRV=1, drive 1 (slave) is selected. + `--------------------------------------- LBA mode. This bit selects the mode of operation. + When L=0, addressing is by 'CHS' mode. + When L=1, addressing is by 'LBA' mode. + */ +#define REG_STATUS 0x1F7 /* Status I */ + /* Any pending interrupt is cleared whenever this register is read. + | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | + +-----+-----+-----+-----+-----+-----+-----+-----+ + | BSY | DRDY|DF/SE| # | DRQ | | | ERR | + +-----+-----+-----+-----+-----+-----+-----+-----+ + | | | | | | | | + | | | | | | | `--- 0. Error.(an error occurred) + | | | | | | `--------- 1. Obsolete. + | | | | | `--------------- 2. Obsolete. + | | | | `--------------------- 3. Data Request. (ready to transfer data) + | | | `--------------------------- 4. Command dependent. (formerly DSC bit) + | | `--------------------------------- 5. Device Fault / Stream Error. + | `--------------------------------------- 6. Drive Ready. + `--------------------------------------------- 7. Busy. If BSY=1, no other bits in the register are valid. + */ +#define STATUS_BSY 0x80 +#define STATUS_DRDY 0x40 +#define STATUS_DFSE 0x20 +#define STATUS_DSC 0x10 +#define STATUS_DRQ 0x08 +#define STATUS_CORR 0x04 +#define STATUS_IDX 0x02 +#define STATUS_ERR 0x01 + +#define REG_CMD REG_STATUS /* Command O */ + /* + +--------+---------------------------------+-----------------+ + | Command| Command Description | Parameters Used | + | Code | | PC SC SN CY DH | + +--------+---------------------------------+-----------------+ + | ECh @ | Identify Drive | D | + | 91h | Initialize Drive Parameters | V V | + | 20h | Read Sectors With Retry | V V V V | + | E8h @ | Write Buffer | D | + +--------+---------------------------------+-----------------+ + + KEY FOR SYMBOLS IN THE TABLE: + ===========================================-----========================================================================= + PC Register 1F1: Write Precompensation @ These commands are optional and may not be supported by some drives. + SC Register 1F2: Sector Count D Only DRIVE parameter is valid, HEAD parameter is ignored. + SN Register 1F3: Sector Number D+ Both drives execute this command regardless of the DRIVE parameter. + CY Register 1F4+1F5: Cylinder low + high V Indicates that the register contains a valid paramterer. + DH Register 1F6: Drive / Head + */ + +/* Control Block Registers */ +/* MACRO PORT DESCRIPTION INPUT/OUTPUT */ +/* ----- ---- ----------- ------------ */ +#define REG_DEV_CTRL 0x3F6 /* Device Control O */ + /* | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | + +-----+-----+-----+-----+-----+-----+-----+-----+ + | HOB | - | - | - | - |SRST |-IEN | 0 | + +-----+-----+-----+-----+-----+-----+-----+-----+ + | | | + | | `--------- Interrupt Enable. + | | - IEN=0, and the drive is selected, + | | drive interrupts to the host will be enabled. + | | - IEN=1, or the drive is not selected, + | | drive interrupts to the host will be disabled. + | `--------------- Software Reset. + | - The drive is held reset when RST=1. + | Setting RST=0 re-enables the drive. + | - The host must set RST=1 and wait for at least + | 5 microsecondsbefore setting RST=0, to ensure + | that the drive recognizes the reset. + `--------------------------------------------- HOB (High Order Byte) + - defined by 48-bit Address feature set. + */ +#define REG_ALT_STATUS REG_DEV_CTRL /* Alternate Status I */ + /* This register contains the same information as the Status Register. + The only difference is that reading this register does not imply interrupt acknowledge or clear a pending interrupt. + */ + +#define REG_DRV_ADDR 0x3F7 /* Drive Address I */ + + +struct hd_cmd { + u8 features; + u8 count; + u8 lba_low; + u8 lba_mid; + u8 lba_high; + u8 device; + u8 command; +}; + +// added by mingxuan 2020-10-27 +# define NO_FS_TYPE 0x0 //added by mingxuan 2020-10-30 +# define ORANGE_TYPE 0x1 +# define FAT32_TYPE 0x2 +# define TTY_FS_TYPE 0x3 //added by mingxuan 2020-10-30 + +struct part_info { + u32 base; /* # of start sector (NOT byte offset, but SECTOR) */ + u32 size; /* how many sectors in this partition */ + u32 fs_type; //added by mingxuan 2020-10-27 +}; + +/* main drive struct, one entry per drive */ +struct hd_info +{ + int open_cnt; + struct part_info primary[NR_PRIM_PER_DRIVE]; // NR_PRIM_PER_DRIVE = 5 + struct part_info logical[NR_SUB_PER_DRIVE]; // NR_SUB_PER_DRIVE = 16 *4 =64 +}; + + +/***************/ +/* DEFINITIONS */ +/***************/ +#define HD_TIMEOUT 10000 /* in millisec */ +#define PARTITION_TABLE_OFFSET 0x1BE +#define ATA_IDENTIFY 0xEC +#define ATA_READ 0x20 +#define ATA_WRITE 0x30 +/* for DEVICE register. */ +#define MAKE_DEVICE_REG(lba,drv,lba_highest) (((lba) << 6) | \ + ((drv) << 4) | \ + (lba_highest & 0xF) | 0xA0) + +// added by xw, 18/8/26 +typedef struct rdwt_info +{ + MESSAGE *msg; + void *kbuf; + PROCESS *proc; + struct rdwt_info *next; +} RWInfo; + +typedef struct +{ + RWInfo *front; + RWInfo *rear; +} HDQueue; + +void init_hd(); +void hd_open(int device); +void hd_close(int device); +void hd_service(); + +void hd_rdwt(MESSAGE *p); +void hd_rdwt_sched(MESSAGE *p); +void hd_ioctl(MESSAGE *p); +//~xw + +#endif /* _ORANGES_HD_H_ */ diff --git a/include/keyboard.h b/include/keyboard.h new file mode 100644 index 0000000..ee252bd --- /dev/null +++ b/include/keyboard.h @@ -0,0 +1,147 @@ +/*************************************************************************//** + ***************************************************************************** + * @file keyboard.h + * @brief + * @author Forrest Y. Yu + * @date 2005 + ***************************************************************************** + *****************************************************************************/ + +#ifndef _ORANGES_KEYBOARD_H_ +#define _ORANGES_KEYBOARD_H_ + + +/************************************************************************/ +/* Macros Declaration */ +/************************************************************************/ +#define KB_IN_BYTES 320 /* size of keyboard input buffer */ /* FIXME */ +#define MOUSE_IN_BYTES 3 +#define MAP_COLS 3 /* Number of columns in keymap */ +#define NR_SCAN_CODES 0x80 /* Number of scan codes (rows in keymap) */ + +#define FLAG_BREAK 0x0080 /* Break Code */ +#define FLAG_EXT 0x0100 /* Normal function keys */ +#define FLAG_SHIFT_L 0x0200 /* Shift key */ +#define FLAG_SHIFT_R 0x0400 /* Shift key */ +#define FLAG_CTRL_L 0x0800 /* Control key */ +#define FLAG_CTRL_R 0x1000 /* Control key */ +#define FLAG_ALT_L 0x2000 /* Alternate key */ +#define FLAG_ALT_R 0x4000 /* Alternate key */ +#define FLAG_PAD 0x8000 /* keys in num pad */ + +#define MASK_RAW 0x01FF /* raw key value = code_passed_to_tty & MASK_RAW + * the value can be found either in the keymap + * column 0 or in the list below + */ + +/* Special keys */ +#define ESC (0x01 + FLAG_EXT) /* Esc */ +#define TAB (0x02 + FLAG_EXT) /* Tab */ +#define ENTER (0x03 + FLAG_EXT) /* Enter */ +#define BACKSPACE (0x04 + FLAG_EXT) /* BackSpace */ + +#define GUI_L (0x05 + FLAG_EXT) /* L GUI */ +#define GUI_R (0x06 + FLAG_EXT) /* R GUI */ +#define APPS (0x07 + FLAG_EXT) /* APPS */ + +/* Shift, Ctrl, Alt */ +#define SHIFT_L (0x08 + FLAG_EXT) /* L Shift */ +#define SHIFT_R (0x09 + FLAG_EXT) /* R Shift */ +#define CTRL_L (0x0A + FLAG_EXT) /* L Ctrl */ +#define CTRL_R (0x0B + FLAG_EXT) /* R Ctrl */ +#define ALT_L (0x0C + FLAG_EXT) /* L Alt */ +#define ALT_R (0x0D + FLAG_EXT) /* R Alt */ + +/* Lock keys */ +#define CAPS_LOCK (0x0E + FLAG_EXT) /* Caps Lock */ +#define NUM_LOCK (0x0F + FLAG_EXT) /* Number Lock */ +#define SCROLL_LOCK (0x10 + FLAG_EXT) /* Scroll Lock */ + +/* Function keys */ +#define F1 (0x11 + FLAG_EXT) /* F1 */ +#define F2 (0x12 + FLAG_EXT) /* F2 */ +#define F3 (0x13 + FLAG_EXT) /* F3 */ +#define F4 (0x14 + FLAG_EXT) /* F4 */ +#define F5 (0x15 + FLAG_EXT) /* F5 */ +#define F6 (0x16 + FLAG_EXT) /* F6 */ +#define F7 (0x17 + FLAG_EXT) /* F7 */ +#define F8 (0x18 + FLAG_EXT) /* F8 */ +#define F9 (0x19 + FLAG_EXT) /* F9 */ +#define F10 (0x1A + FLAG_EXT) /* F10 */ +#define F11 (0x1B + FLAG_EXT) /* F11 */ +#define F12 (0x1C + FLAG_EXT) /* F12 */ + +/* Control Pad */ +#define PRINTSCREEN (0x1D + FLAG_EXT) /* Print Screen */ +#define PAUSEBREAK (0x1E + FLAG_EXT) /* Pause/Break */ +#define INSERT (0x1F + FLAG_EXT) /* Insert */ +#define DELETE (0x20 + FLAG_EXT) /* Delete */ +#define HOME (0x21 + FLAG_EXT) /* Home */ +#define END (0x22 + FLAG_EXT) /* End */ +#define PAGEUP (0x23 + FLAG_EXT) /* Page Up */ +#define PAGEDOWN (0x24 + FLAG_EXT) /* Page Down */ +#define UP (0x25 + FLAG_EXT) /* Up */ +#define DOWN (0x26 + FLAG_EXT) /* Down */ +#define LEFT (0x27 + FLAG_EXT) /* Left */ +#define RIGHT (0x28 + FLAG_EXT) /* Right */ + +/* ACPI keys */ +#define POWER (0x29 + FLAG_EXT) /* Power */ +#define SLEEP (0x2A + FLAG_EXT) /* Sleep */ +#define WAKE (0x2B + FLAG_EXT) /* Wake Up */ + +/* Num Pad */ +#define PAD_SLASH (0x2C + FLAG_EXT) /* / */ +#define PAD_STAR (0x2D + FLAG_EXT) /* * */ +#define PAD_MINUS (0x2E + FLAG_EXT) /* - */ +#define PAD_PLUS (0x2F + FLAG_EXT) /* + */ +#define PAD_ENTER (0x30 + FLAG_EXT) /* Enter */ +#define PAD_DOT (0x31 + FLAG_EXT) /* . */ +#define PAD_0 (0x32 + FLAG_EXT) /* 0 */ +#define PAD_1 (0x33 + FLAG_EXT) /* 1 */ +#define PAD_2 (0x34 + FLAG_EXT) /* 2 */ +#define PAD_3 (0x35 + FLAG_EXT) /* 3 */ +#define PAD_4 (0x36 + FLAG_EXT) /* 4 */ +#define PAD_5 (0x37 + FLAG_EXT) /* 5 */ +#define PAD_6 (0x38 + FLAG_EXT) /* 6 */ +#define PAD_7 (0x39 + FLAG_EXT) /* 7 */ +#define PAD_8 (0x3A + FLAG_EXT) /* 8 */ +#define PAD_9 (0x3B + FLAG_EXT) /* 9 */ +#define PAD_UP PAD_8 /* Up */ +#define PAD_DOWN PAD_2 /* Down */ +#define PAD_LEFT PAD_4 /* Left */ +#define PAD_RIGHT PAD_6 /* Right */ +#define PAD_HOME PAD_7 /* Home */ +#define PAD_END PAD_1 /* End */ +#define PAD_PAGEUP PAD_9 /* Page Up */ +#define PAD_PAGEDOWN PAD_3 /* Page Down */ +#define PAD_INS PAD_0 /* Ins */ +#define PAD_MID PAD_5 /* Middle key */ +#define PAD_DEL PAD_DOT /* Del */ + + +/************************************************************************/ +/* Stucture Definition */ +/************************************************************************/ +/* Keyboard structure, 1 per console. */ +/** + * @todo change u8 into char + * + */ + +typedef struct kb_inbuf { + u8* p_head; /**< points to the next free slot */ + u8* p_tail; /**< points to the byte to be handled */ + int count; /**< how many bytes to be handled in the buffer */ + u8 buf[KB_IN_BYTES]; +} KB_INPUT; + +#define MOUSE_UPDOWN_BOUND 15 +typedef struct mouse_inbuf{ + int count; + u8 buf[MOUSE_IN_BYTES]; +}MOUSE_INPUT; + +#endif /* _ORANGES_KEYBOARD_H_ */ + + diff --git a/include/keymap.h b/include/keymap.h new file mode 100644 index 0000000..8d9b1cf --- /dev/null +++ b/include/keymap.h @@ -0,0 +1,230 @@ +#ifndef _ORANGES_KEYMAP_H_ +#define _ORANGES_KEYMAP_H_ + + +/* Keymap for US MF-2 keyboard. */ + +static u32 keymap[NR_SCAN_CODES * MAP_COLS] = { + +/* scan-code !Shift Shift E0 XX */ +/* ==================================================================== */ +/* 0x00 - none */ 0, 0, 0, +/* 0x01 - ESC */ ESC, ESC, 0, +/* 0x02 - '1' */ '1', '!', 0, +/* 0x03 - '2' */ '2', '@', 0, +/* 0x04 - '3' */ '3', '#', 0, +/* 0x05 - '4' */ '4', '$', 0, +/* 0x06 - '5' */ '5', '%', 0, +/* 0x07 - '6' */ '6', '^', 0, +/* 0x08 - '7' */ '7', '&', 0, +/* 0x09 - '8' */ '8', '*', 0, +/* 0x0A - '9' */ '9', '(', 0, +/* 0x0B - '0' */ '0', ')', 0, +/* 0x0C - '-' */ '-', '_', 0, +/* 0x0D - '=' */ '=', '+', 0, +/* 0x0E - BS */ BACKSPACE, BACKSPACE, 0, +/* 0x0F - TAB */ TAB, TAB, 0, +/* 0x10 - 'q' */ 'q', 'Q', 0, +/* 0x11 - 'w' */ 'w', 'W', 0, +/* 0x12 - 'e' */ 'e', 'E', 0, +/* 0x13 - 'r' */ 'r', 'R', 0, +/* 0x14 - 't' */ 't', 'T', 0, +/* 0x15 - 'y' */ 'y', 'Y', 0, +/* 0x16 - 'u' */ 'u', 'U', 0, +/* 0x17 - 'i' */ 'i', 'I', 0, +/* 0x18 - 'o' */ 'o', 'O', 0, +/* 0x19 - 'p' */ 'p', 'P', 0, +/* 0x1A - '[' */ '[', '{', 0, +/* 0x1B - ']' */ ']', '}', 0, +/* 0x1C - CR/LF */ ENTER, ENTER, PAD_ENTER, +/* 0x1D - l. Ctrl */ CTRL_L, CTRL_L, CTRL_R, +/* 0x1E - 'a' */ 'a', 'A', 0, +/* 0x1F - 's' */ 's', 'S', 0, +/* 0x20 - 'd' */ 'd', 'D', 0, +/* 0x21 - 'f' */ 'f', 'F', 0, +/* 0x22 - 'g' */ 'g', 'G', 0, +/* 0x23 - 'h' */ 'h', 'H', 0, +/* 0x24 - 'j' */ 'j', 'J', 0, +/* 0x25 - 'k' */ 'k', 'K', 0, +/* 0x26 - 'l' */ 'l', 'L', 0, +/* 0x27 - ';' */ ';', ':', 0, +/* 0x28 - '\'' */ '\'', '"', 0, +/* 0x29 - '`' */ '`', '~', 0, +/* 0x2A - l. SHIFT */ SHIFT_L, SHIFT_L, 0, +/* 0x2B - '\' */ '\\', '|', 0, +/* 0x2C - 'z' */ 'z', 'Z', 0, +/* 0x2D - 'x' */ 'x', 'X', 0, +/* 0x2E - 'c' */ 'c', 'C', 0, +/* 0x2F - 'v' */ 'v', 'V', 0, +/* 0x30 - 'b' */ 'b', 'B', 0, +/* 0x31 - 'n' */ 'n', 'N', 0, +/* 0x32 - 'm' */ 'm', 'M', 0, +/* 0x33 - ',' */ ',', '<', 0, +/* 0x34 - '.' */ '.', '>', 0, +/* 0x35 - '/' */ '/', '?', PAD_SLASH, +/* 0x36 - r. SHIFT */ SHIFT_R, SHIFT_R, 0, +/* 0x37 - '*' */ '*', '*', 0, +/* 0x38 - ALT */ ALT_L, ALT_L, ALT_R, +/* 0x39 - ' ' */ ' ', ' ', 0, +/* 0x3A - CapsLock */ CAPS_LOCK, CAPS_LOCK, 0, +/* 0x3B - F1 */ F1, F1, 0, +/* 0x3C - F2 */ F2, F2, 0, +/* 0x3D - F3 */ F3, F3, 0, +/* 0x3E - F4 */ F4, F4, 0, +/* 0x3F - F5 */ F5, F5, 0, +/* 0x40 - F6 */ F6, F6, 0, +/* 0x41 - F7 */ F7, F7, 0, +/* 0x42 - F8 */ F8, F8, 0, +/* 0x43 - F9 */ F9, F9, 0, +/* 0x44 - F10 */ F10, F10, 0, +/* 0x45 - NumLock */ NUM_LOCK, NUM_LOCK, 0, +/* 0x46 - ScrLock */ SCROLL_LOCK, SCROLL_LOCK, 0, +/* 0x47 - Home */ PAD_HOME, '7', HOME, +/* 0x48 - CurUp */ PAD_UP, '8', UP, +/* 0x49 - PgUp */ PAD_PAGEUP, '9', PAGEUP, +/* 0x4A - '-' */ PAD_MINUS, '-', 0, +/* 0x4B - Left */ PAD_LEFT, '4', LEFT, +/* 0x4C - MID */ PAD_MID, '5', 0, +/* 0x4D - Right */ PAD_RIGHT, '6', RIGHT, +/* 0x4E - '+' */ PAD_PLUS, '+', 0, +/* 0x4F - End */ PAD_END, '1', END, +/* 0x50 - Down */ PAD_DOWN, '2', DOWN, +/* 0x51 - PgDown */ PAD_PAGEDOWN, '3', PAGEDOWN, +/* 0x52 - Insert */ PAD_INS, '0', INSERT, +/* 0x53 - Delete */ PAD_DOT, '.', DELETE, +/* 0x54 - Enter */ 0, 0, 0, +/* 0x55 - ??? */ 0, 0, 0, +/* 0x56 - ??? */ 0, 0, 0, +/* 0x57 - F11 */ F11, F11, 0, +/* 0x58 - F12 */ F12, F12, 0, +/* 0x59 - ??? */ 0, 0, 0, +/* 0x5A - ??? */ 0, 0, 0, +/* 0x5B - ??? */ 0, 0, GUI_L, +/* 0x5C - ??? */ 0, 0, GUI_R, +/* 0x5D - ??? */ 0, 0, APPS, +/* 0x5E - ??? */ 0, 0, 0, +/* 0x5F - ??? */ 0, 0, 0, +/* 0x60 - ??? */ 0, 0, 0, +/* 0x61 - ??? */ 0, 0, 0, +/* 0x62 - ??? */ 0, 0, 0, +/* 0x63 - ??? */ 0, 0, 0, +/* 0x64 - ??? */ 0, 0, 0, +/* 0x65 - ??? */ 0, 0, 0, +/* 0x66 - ??? */ 0, 0, 0, +/* 0x67 - ??? */ 0, 0, 0, +/* 0x68 - ??? */ 0, 0, 0, +/* 0x69 - ??? */ 0, 0, 0, +/* 0x6A - ??? */ 0, 0, 0, +/* 0x6B - ??? */ 0, 0, 0, +/* 0x6C - ??? */ 0, 0, 0, +/* 0x6D - ??? */ 0, 0, 0, +/* 0x6E - ??? */ 0, 0, 0, +/* 0x6F - ??? */ 0, 0, 0, +/* 0x70 - ??? */ 0, 0, 0, +/* 0x71 - ??? */ 0, 0, 0, +/* 0x72 - ??? */ 0, 0, 0, +/* 0x73 - ??? */ 0, 0, 0, +/* 0x74 - ??? */ 0, 0, 0, +/* 0x75 - ??? */ 0, 0, 0, +/* 0x76 - ??? */ 0, 0, 0, +/* 0x77 - ??? */ 0, 0, 0, +/* 0x78 - ??? */ 0, 0, 0, +/* 0x78 - ??? */ 0, 0, 0, +/* 0x7A - ??? */ 0, 0, 0, +/* 0x7B - ??? */ 0, 0, 0, +/* 0x7C - ??? */ 0, 0, 0, +/* 0x7D - ??? */ 0, 0, 0, +/* 0x7E - ??? */ 0, 0, 0, +/* 0x7F - ??? */ 0, 0, 0 +}; + + +/* + 回车键: 把光标移到第一列 + 换行键: 把光标前进到下一行 +*/ + + +/*==================================================================== + Appendix: Scan code set 1 + *==================================================================== +KEY MAKE BREAK| KEY MAKE BREAK | KEY MAKE BREAK +---------------------|------------------------|----------------------- +A 1E 9E | 9 0A 8A | [ 1A 9A +B 30 B0 | ` 29 89 | INSERT E0,52 E0,D2 +C 2E AE | - 0C 8C | HOME E0,47 E0,C7 +D 20 A0 | = 0D 8D | PG UP E0,49 E0,C9 +E 12 92 | \ 2B AB | DELETE E0,53 E0,D3 +F 21 A1 | BKSP 0E 8E | END E0,4F E0,CF +G 22 A2 | SPACE 39 B9 | PG DN E0,51 E0,D1 +H 23 A3 | TAB 0F 8F | U ARROW E0,48 E0,C8 +I 17 97 | CAPS 3A BA | L ARROW E0,4B E0,CB +J 24 A4 | L SHFT 2A AA | D ARROW E0,50 E0,D0 +K 25 A5 | L CTRL 1D 9D | R ARROW E0,4D E0,CD +L 26 A6 | L GUI E0,5B E0,DB | NUM 45 C5 +M 32 B2 | L ALT 38 B8 | KP / E0,35 E0,B5 +N 31 B1 | R SHFT 36 B6 | KP * 37 B7 +O 18 98 | R CTRL E0,1D E0,9D | KP - 4A CA +P 19 99 | R GUI E0,5C E0,DC | KP + 4E CE +Q 10 19 | R ALT E0,38 E0,B8 | KP EN E0,1C E0,9C +R 13 93 | APPS E0,5D E0,DD | KP . 53 D3 +S 1F 9F | ENTER 1C 9C | KP 0 52 D2 +T 14 94 | ESC 01 81 | KP 1 4F CF +U 16 96 | F1 3B BB | KP 2 50 D0 +V 2F AF | F2 3C BC | KP 3 51 D1 +W 11 91 | F3 3D BD | KP 4 4B CB +X 2D AD | F4 3E BE | KP 5 4C CC +Y 15 95 | F5 3F BF | KP 6 4D CD +Z 2C AC | F6 40 C0 | KP 7 47 C7 +0 0B 8B | F7 41 C1 | KP 8 48 C8 +1 02 82 | F8 42 C2 | KP 9 49 C9 +2 03 83 | F9 43 C3 | ] 1B 9B +3 04 84 | F10 44 C4 | ; 27 A7 +4 05 85 | F11 57 D7 | ' 28 A8 +5 06 86 | F12 58 D8 | , 33 B3 + | | +6 07 87 | PRTSCRN E0,2A E0,B7 | . 34 B4 + | E0,37 E0,AA | + | | +7 08 88 | SCROLL 46 C6 | / 35 B5 + | | +8 09 89 | PAUSE E1,1D | + | 45,E1, -NONE-| + | 9D,C5 | +---------------------------------------------------------------------- +----------------- +ACPI Scan Codes: +------------------------------------------- +Key Make Code Break Code +------------------------------------------- +Power E0, 5E E0, DE +Sleep E0, 5F E0, DF +Wake E0, 63 E0, E3 +------------------------------- +Windows Multimedia Scan Codes: +------------------------------------------- +Key Make Code Break Code +------------------------------------------- +Next Track E0, 19 E0, 99 +Previous Track E0, 10 E0, 90 +Stop E0, 24 E0, A4 +Play/Pause E0, 22 E0, A2 +Mute E0, 20 E0, A0 +Volume Up E0, 30 E0, B0 +Volume Down E0, 2E E0, AE +Media Select E0, 6D E0, ED +E-Mail E0, 6C E0, EC +Calculator E0, 21 E0, A1 +My Computer E0, 6B E0, EB +WWW Search E0, 65 E0, E5 +WWW Home E0, 32 E0, B2 +WWW Back E0, 6A E0, EA +WWW Forward E0, 69 E0, E9 +WWW Stop E0, 68 E0, E8 +WWW Refresh E0, 67 E0, E7 +WWW Favorites E0, 66 E0, E6 +*=====================================================================================*/ + + + +#endif /* _ORANGES_KEYMAP_H_ */ \ No newline at end of file diff --git a/include/memman.h b/include/memman.h new file mode 100644 index 0000000..703b462 --- /dev/null +++ b/include/memman.h @@ -0,0 +1,31 @@ +#ifndef _ORANGES_MEMMAN_H_ +#define _ORANGES_MEMMAN_H_ + +#define MEMMAN_FREES 4090 //32KB +#define MEMMAN_ADDR 0x01ff0000 //存memman,31M960K +#define FMIBuff 0x007ff000 //loader中getFreeMemInfo返回值存放起始地址(7M1020K) +#define KWALL 0x00600000 +#define WALL 0x00800000 +#define UWALL 0x01000000 +#define MEMSTART 0x00400000 +#define MEMEND 0x02000000 +#define TEST 0x11223344 +struct FREEINFO{ + u32 addr,size; +}; + +struct MEMMAN{ + u32 frees,maxfrees,lostsize,losts; //frees为当前空闲内存块数 + struct FREEINFO free[MEMMAN_FREES]; //空闲内存 +}; + +void init(); +u32 do_malloc(u32 size); +u32 do_kmalloc(u32 size); +u32 do_malloc_4k(); +u32 do_kmalloc_4k(); +u32 do_free(u32 addr,u32 size); +u32 do_free_4k(u32 addr); + + +#endif \ No newline at end of file diff --git a/include/proc.h b/include/proc.h new file mode 100644 index 0000000..4b3dfc2 --- /dev/null +++ b/include/proc.h @@ -0,0 +1,176 @@ + +/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + proc.h +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + Forrest Yu, 2005 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + +/* Used to find saved registers in the new kernel stack, + * for there is no variable name you can use in C. + * The macro defined below must coordinate with the equivalent in sconst.inc, + * whose content is used in asm. + * Note that you shouldn't use saved registers in the old + * kernel stack, i.e. STACK_FRAME, any more. + * To access them, use a pointer plus a offset defined + * below instead. + * added by xw, 17/12/11 + */ +#define INIT_STACK_SIZE 1024 * 8 //new kernel stack is 8kB +#define P_STACKBASE 0 +#define GSREG 0 +#define FSREG 1 * 4 +#define ESREG 2 * 4 +#define DSREG 3 * 4 +#define EDIREG 4 * 4 +#define ESIREG 5 * 4 +#define EBPREG 6 * 4 +#define KERNELESPREG 7 * 4 +#define EBXREG 8 * 4 +#define EDXREG 9 * 4 +#define ECXREG 10 * 4 +#define EAXREG 11 * 4 +#define RETADR 12 * 4 +#define EIPREG 13 * 4 +#define CSREG 14 * 4 +#define EFLAGSREG 15 * 4 +#define ESPREG 16 * 4 +#define SSREG 17 * 4 +#define P_STACKTOP 18 * 4 + +/*总PCB表数和taskPCB表数*/ +//modified by xw, 18/8/27 +//the memory space we put kernel is 0x30400~0x6ffff, so we should limit kernel size +// #define NR_PCBS 32 //add by visual 2016.4.5 +// #define NR_K_PCBS 10 //add by visual 2016.4.5 +#define NR_PCBS 12 +//#define NR_TASKS 4 //TestA~TestC + hd_service //deleted by mingxuan 2019-5-19 +#define NR_TASKS 2 //task_tty + hd_service //modified by mingxuan 2019-5-19 +#define NR_K_PCBS 4 //no K_PCB is empty now + +//~xw + +#define NR_CPUS 1 //numbers of cpu. added by xw, 18/6/1 +#define NR_FILES 64 //numbers of files a process can own. added by xw, 18/6/14 + +//enum proc_stat {IDLE,READY,WAITING,RUNNING}; //add by visual smile 2016.4.5 +//enum proc_stat {IDLE,READY,SLEEPING}; //eliminate RUNNING state +enum proc_stat {IDLE,READY,SLEEPING,KILLED}; /* add KILLED state. when a process's state is KILLED, the process + * won't be scheduled anymore, but all of the resources owned by + * it is not freed yet. + * added by xw, 18/12/19 + */ + +#define NR_CHILD_MAX (NR_PCBS-NR_K_PCBS-1) //定义最多子进程/线程数量 //add by visual 2016.5.26 +#define TYPE_PROCESS 0//进程//add by visual 2016.5.26 +#define TYPE_THREAD 1//线程//add by visual 2016.5.26 + +typedef struct s_stackframe { /* proc_ptr points here ↑ Low */ + u32 gs; /* ┓ │ */ + u32 fs; /* ┃ │ */ + u32 es; /* ┃ │ */ + u32 ds; /* ┃ │ */ + u32 edi; /* ┃ │ */ + u32 esi; /* ┣ pushed by save() │ */ + u32 ebp; /* ┃ │ */ + u32 kernel_esp; /* <- 'popad' will ignore it │ */ + u32 ebx; /* ┃ ↑栈从高地址往低地址增长*/ + u32 edx; /* ┃ │ */ + u32 ecx; /* ┃ │ */ + u32 eax; /* ┛ │ */ + u32 retaddr; /* return address for assembly code save() │ */ + u32 eip; /* ┓ │ */ + u32 cs; /* ┃ │ */ + u32 eflags; /* ┣ these are pushed by CPU during interrupt │ */ + u32 esp; /* ┃ │ */ + u32 ss; /* ┛ ┷High */ +}STACK_FRAME; + +typedef struct s_tree_info{//进程树记录,包括父进程,子进程,子线程 //add by visual 2016.5.25 + int type; //当前是进程还是线程 + int real_ppid; //亲父进程,创建它的那个进程 + int ppid; //当前父进程 + int child_p_num; //子进程数量 + int child_process[NR_CHILD_MAX];//子进程列表 + int child_t_num; //子线程数量 + int child_thread[NR_CHILD_MAX];//子线程列表 + int text_hold; //是否拥有代码 + int data_hold; //是否拥有数据 +}TREE_INFO; + +typedef struct s_lin_memmap {//线性地址分布结构体 edit by visual 2016.5.25 + u32 text_lin_base; //代码段基址 + u32 text_lin_limit; //代码段界限 + + u32 data_lin_base; //数据段基址 + u32 data_lin_limit; //数据段界限 + + u32 vpage_lin_base; //保留内存基址 + u32 vpage_lin_limit; //保留内存界限 + + u32 heap_lin_base; //堆基址 + u32 heap_lin_limit; //堆界限 + + u32 stack_lin_base; //栈基址 + u32 stack_lin_limit; //栈界限(使用时注意栈的生长方向) + + u32 arg_lin_base; //参数内存基址 + u32 arg_lin_limit; //参数内存界限 + + u32 kernel_lin_base; //内核基址 + u32 kernel_lin_limit; //内核界限 + + u32 stack_child_limit; //分给子线程的栈的界限 //add by visual 2016.5.27 +}LIN_MEMMAP; + +typedef struct s_proc { + STACK_FRAME regs; /* process registers saved in stack frame */ + + u16 ldt_sel; /* gdt selector giving ldt base and limit */ + DESCRIPTOR ldts[LDT_SIZE]; /* local descriptors for code and data */ + + char* esp_save_int; //to save the position of esp in the kernel stack of the process + //added by xw, 17/12/11 + char* esp_save_syscall; //to save the position of esp in the kernel stack of the process + char* esp_save_context; //to save the position of esp in the kernel stack of the process +// int save_type; //the cause of process losting CPU //save_type is not needed any more, xw, 18/4/20 + //1st-bit for interruption, 2nd-bit for context, 3rd-bit for syscall + void* channel; /*if non-zero, sleeping on channel, which is a pointer of the target field + for example, as for syscall sleep(int n), the target field is 'ticks', + and the channel is a pointer of 'ticks'. + */ + + LIN_MEMMAP memmap; //线性内存分部信息 add by visual 2016.5.4 + TREE_INFO info; //记录进程树关系 add by visual 2016.5.25 + int ticks; /* remained ticks */ + int priority; + + u32 pid; /* process id passed in from MM */ + char p_name[16]; /* name of the process */ + + enum proc_stat stat; //add by visual 2016.4.5 + + u32 cr3; //add by visual 2016.4.5 + + //added by zcr + struct file_desc * filp[NR_FILES]; + //~zcr +}PROCESS_0; + +//new PROCESS struct with PCB and process's kernel stack +//added by xw, 17/12/11 +typedef union task_union { + PROCESS_0 task; + char stack[INIT_STACK_SIZE/sizeof(char)]; +}PROCESS; + +typedef struct s_task { + task_f initial_eip; + int stacksize; + char name[32]; +}TASK; + +#define STACK_SIZE_TASK 0x1000 //add by visual 2016.4.5 +//#define STACK_SIZE_TOTAL (STACK_SIZE_TASK*NR_PCBS) //edit by visual 2016.4.5 + +//added by zcr +#define proc2pid(x) (x - proc_table) diff --git a/include/protect.h b/include/protect.h new file mode 100644 index 0000000..2b796e0 --- /dev/null +++ b/include/protect.h @@ -0,0 +1,159 @@ + +/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + protect.h +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + Forrest Yu, 2005 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + +#ifndef _ORANGES_PROTECT_H_ +#define _ORANGES_PROTECT_H_ + + +/* 存储段描述符/系统段描述符 */ +typedef struct s_descriptor /* 共 8 个字节 */ +{ + u16 limit_low; /* Limit */ + u16 base_low; /* Base */ + u8 base_mid; /* Base */ + u8 attr1; /* P(1) DPL(2) DT(1) TYPE(4) */ + u8 limit_high_attr2; /* G(1) D(1) 0(1) AVL(1) LimitHigh(4) */ + u8 base_high; /* Base */ +}DESCRIPTOR; + +/* 门描述符 */ +typedef struct s_gate +{ + u16 offset_low; /* Offset Low */ + u16 selector; /* Selector */ + u8 dcount; /* 该字段只在调用门描述符中有效。 + 如果在利用调用门调用子程序时引起特权级的转换和堆栈的改变,需要将外层堆栈中的参数复制到内层堆栈。 + 该双字计数字段就是用于说明这种情况发生时,要复制的双字参数的数量。 */ + u8 attr; /* P(1) DPL(2) DT(1) TYPE(4) */ + u16 offset_high; /* Offset High */ +}GATE; + +typedef struct s_tss { + u32 backlink; + u32 esp0; /* stack pointer to use during interrupt */ + u32 ss0; /* " segment " " " " */ + u32 esp1; + u32 ss1; + u32 esp2; + u32 ss2; + u32 cr3; + u32 eip; + u32 flags; + u32 eax; + u32 ecx; + u32 edx; + u32 ebx; + u32 esp; + u32 ebp; + u32 esi; + u32 edi; + u32 es; + u32 cs; + u32 ss; + u32 ds; + u32 fs; + u32 gs; + u32 ldt; + u16 trap; + u16 iobase; /* I/O位图基址大于或等于TSS段界限,就表示没有I/O许可位图 */ + /*u8 iomap[2];*/ +}TSS; + +/* GDT */ +/* 描述符索引 */ +#define INDEX_DUMMY 0 // ┓ +#define INDEX_FLAT_C 1 // ┣ LOADER 里面已经确定了的. +#define INDEX_FLAT_RW 2 // ┃ +#define INDEX_VIDEO 3 // ┛ +#define INDEX_TSS 4 +#define INDEX_LDT_FIRST 5 +/* 选择子 */ +#define SELECTOR_DUMMY 0 // ┓ +#define SELECTOR_FLAT_C 0x08 // ┣ LOADER 里面已经确定了的. +#define SELECTOR_FLAT_RW 0x10 // ┃ +#define SELECTOR_VIDEO (0x18+3) // ┛<-- RPL=3 +#define SELECTOR_TSS 0x20 // TSS. 从外层跳到内存时 SS 和 ESP 的值从里面获得. +#define SELECTOR_LDT_FIRST 0x28 + +#define SELECTOR_KERNEL_CS SELECTOR_FLAT_C +#define SELECTOR_KERNEL_DS SELECTOR_FLAT_RW +#define SELECTOR_KERNEL_GS SELECTOR_VIDEO + +/* 每个任务有一个单独的 LDT, 每个 LDT 中的描述符个数: */ +#define LDT_SIZE 2 +//added by zcr +/* descriptor indices in LDT */ +#define INDEX_LDT_C 0 +#define INDEX_LDT_RW 1 +//~zcr + +/* 描述符类型值说明 */ +#define DA_32 0x4000 /* 32 位段 */ +#define DA_LIMIT_4K 0x8000 /* 段界限粒度为 4K 字节 */ +#define DA_DPL0 0x00 /* DPL = 0 */ +#define DA_DPL1 0x20 /* DPL = 1 */ +#define DA_DPL2 0x40 /* DPL = 2 */ +#define DA_DPL3 0x60 /* DPL = 3 */ +/* 存储段描述符类型值说明 */ +#define DA_DR 0x90 /* 存在的只读数据段类型值 */ +#define DA_DRW 0x92 /* 存在的可读写数据段属性值 */ +#define DA_DRWA 0x93 /* 存在的已访问可读写数据段类型值 */ +#define DA_C 0x98 /* 存在的只执行代码段属性值 */ +#define DA_CR 0x9A /* 存在的可执行可读代码段属性值 */ +#define DA_CCO 0x9C /* 存在的只执行一致代码段属性值 */ +#define DA_CCOR 0x9E /* 存在的可执行可读一致代码段属性值 */ +/* 系统段描述符类型值说明 */ +#define DA_LDT 0x82 /* 局部描述符表段类型值 */ +#define DA_TaskGate 0x85 /* 任务门类型值 */ +#define DA_386TSS 0x89 /* 可用 386 任务状态段类型值 */ +#define DA_386CGate 0x8C /* 386 调用门类型值 */ +#define DA_386IGate 0x8E /* 386 中断门类型值 */ +#define DA_386TGate 0x8F /* 386 陷阱门类型值 */ + +/* 选择子类型值说明 */ +/* 其中, SA_ : Selector Attribute */ +#define SA_RPL_MASK 0xFFFC +#define SA_RPL0 0 +#define SA_RPL1 1 +#define SA_RPL2 2 +#define SA_RPL3 3 + +#define SA_TI_MASK 0xFFFB +#define SA_TIG 0 +#define SA_TIL 4 + +/* 中断向量 */ +#define INT_VECTOR_DIVIDE 0x0 +#define INT_VECTOR_DEBUG 0x1 +#define INT_VECTOR_NMI 0x2 +#define INT_VECTOR_BREAKPOINT 0x3 +#define INT_VECTOR_OVERFLOW 0x4 +#define INT_VECTOR_BOUNDS 0x5 +#define INT_VECTOR_INVAL_OP 0x6 +#define INT_VECTOR_COPROC_NOT 0x7 +#define INT_VECTOR_DOUBLE_FAULT 0x8 +#define INT_VECTOR_COPROC_SEG 0x9 +#define INT_VECTOR_INVAL_TSS 0xA +#define INT_VECTOR_SEG_NOT 0xB +#define INT_VECTOR_STACK_FAULT 0xC +#define INT_VECTOR_PROTECTION 0xD +#define INT_VECTOR_PAGE_FAULT 0xE +#define INT_VECTOR_COPROC_ERR 0x10 + +/* 中断向量 */ +#define INT_VECTOR_IRQ0 0x20 +#define INT_VECTOR_IRQ8 0x28 + +/* 系统调用 */ +#define INT_VECTOR_SYS_CALL 0x90 + +/* 宏 */ +/* 线性地址 → 物理地址 */ +#define vir2phys(seg_base, vir) (u32)(((u32)seg_base) + (u32)(vir)) + + +#endif /* _ORANGES_PROTECT_H_ */ diff --git a/include/proto.h b/include/proto.h new file mode 100644 index 0000000..f4379c3 --- /dev/null +++ b/include/proto.h @@ -0,0 +1,141 @@ + +/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + proto.h +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + Forrest Yu, 2005 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + +/* klib.asm */ +void disp_str(char* info); +void disp_int(int input); +void disp_color_str(char* info, int color); +void write_char(char ch); //added by mingxuan 2019-5-19 + +//added by zcr +void disable_irq(int irq); +void enable_irq(int irq); +void init_8259A(); +//~zcr + +/* protect.c */ +void init_prot(); +u32 seg2phys(u16 seg); + +/* klib.c */ +void delay(int time); + +/* kernel.asm */ +u32 read_cr2(); //add by visual 2016.5.9 +void refresh_page_cache(); //add by visual 2016.5.12 +//void restart_int(); +//void save_context(); +void restart_initial(); //added by xw, 18/4/18 +void restart_restore(); //added by xw, 18/4/20 +void sched(); //added by xw, 18/4/18 +void halt(); //added by xw, 18/6/11 +u32 get_arg(void *uesp, int order); //added by xw, 18/6/18 + +/* ktest.c */ +void initial(); + +/* keyboard.c */ +//added by mingxuan 2019-5-19 +void init_kb(); +void keyboard_read(); + +/* tty.c */ +//added by mingxuan 2019-5-19 +void in_process(TTY* p_tty,u32 key); +void task_tty(); +void tty_write(TTY* tty, char* buf, int len); +int tty_read(TTY* tty, char* buf, int len); + +/* printf.c */ +//added by mingxuan 2019-5-19 +int printf(const char *fmt, ...); + +/* vsprintf.c */ +//added by mingxuan 2019-5-19 +int vsprintf(char *buf, const char *fmt, va_list args); + +/* i8259.c */ +void put_irq_handler(int irq, irq_handler handler); +void spurious_irq(int irq); + +/* clock.c */ +void clock_handler(int irq); + +/*************************************************************** +* 以下是系统调用相关函数的声明 +****************************************************************/ +/* syscall.asm */ +void sys_call(); /* int_handler */ +int get_ticks(); +int get_pid(); //add by visual 2016.4.6 +void* kmalloc(int size); //edit by visual 2016.5.9 +void* kmalloc_4k(); //edit by visual 2016.5.9 +void* malloc(int size); //edit by visual 2016.5.9 +void* malloc_4k(); //edit by visual 2016.5.9 +int free(void *arg); //edit by visual 2016.5.9 +int free_4k(void* AdddrLin); //edit by visual 2016.5.9 +int fork(); //add by visual 2016.4.8 +int pthread(void *arg); //add by visual 2016.4.11 +void udisp_int(int arg); //add by visual 2016.5.16 +void udisp_str(char* arg); //add by visual 2016.5.16 +u32 exec(char* path); //add by visual 2016.5.16 +void yield(); //added by xw, 18/4/19 +void sleep(int n); //added by xw, 18/4/19 +void print_E(); +void print_F(); + +/* syscallc.c */ //edit by visual 2016.4.6 +int sys_get_ticks(); /* sys_call */ +int sys_get_pid(); //add by visual 2016.4.6 +void* sys_kmalloc(int size); //edit by visual 2016.5.9 +void* sys_kmalloc_4k(); //edit by visual 2016.5.9 +void* sys_malloc(int size); //edit by visual 2016.5.9 +void* sys_malloc_4k(); //edit by visual 2016.5.9 +int sys_free(void *arg); //edit by visual 2016.5.9 +int sys_free_4k(void* AdddrLin); //edit by visual 2016.5.9 +int sys_pthread(void *arg); //add by visual 2016.4.11 +void sys_udisp_int(int arg); //add by visual 2016.5.16 +void sys_udisp_str(char* arg); //add by visual 2016.5.16 + +/* proc.c */ +PROCESS* alloc_PCB(); +void free_PCB(PROCESS *p); +void sys_yield(); +void sys_sleep(int n); +void sys_wakeup(void *channel); +int ldt_seg_linear(PROCESS *p, int idx); +void* va2la(int pid, void* va); + +/* testfunc.c */ +void sys_print_E(); +void sys_print_F(); + +/*exec.c*/ +u32 sys_exec(char* path); //add by visual 2016.5.23 +/*fork.c*/ +int sys_fork(); //add by visual 2016.5.25 + +/*************************************************************** +* 以上是系统调用相关函数的声明 +****************************************************************/ + +/*pagepte.c*/ +u32 init_page_pte(u32 pid); //edit by visual 2016.4.28 +void page_fault_handler(u32 vec_no, u32 err_code, u32 eip, u32 cs, u32 eflags);//add by visual 2016.4.19 +u32 get_pde_index(u32 AddrLin);//add by visual 2016.4.28 +u32 get_pte_index(u32 AddrLin); +u32 get_pde_phy_addr(u32 pid); +u32 get_pte_phy_addr(u32 pid,u32 AddrLin); +u32 get_page_phy_addr(u32 pid,u32 AddrLin);//线性地址 +u32 pte_exist(u32 PageTblAddrPhy,u32 AddrLin); +u32 phy_exist(u32 PageTblPhyAddr,u32 AddrLin); +void write_page_pde(u32 PageDirPhyAddr,u32 AddrLin,u32 TblPhyAddr,u32 Attribute); +void write_page_pte( u32 TblPhyAddr,u32 AddrLin,u32 PhyAddr,u32 Attribute); +u32 vmalloc(u32 size); +int lin_mapping_phy(u32 AddrLin,u32 phy_addr,u32 pid,u32 pde_Attribute,u32 pte_Attribute);//edit by visual 2016.5.19 +void clear_kernel_pagepte_low(); //add by visual 2016.5.12 + diff --git a/include/sconst.inc b/include/sconst.inc new file mode 100644 index 0000000..ffc3680 --- /dev/null +++ b/include/sconst.inc @@ -0,0 +1,52 @@ + +; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +; sconst.inc +; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +; Forrest Yu, 2005 +; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +P_STACKBASE equ 0 +GSREG equ P_STACKBASE +FSREG equ GSREG + 4 +ESREG equ FSREG + 4 +DSREG equ ESREG + 4 +EDIREG equ DSREG + 4 +ESIREG equ EDIREG + 4 +EBPREG equ ESIREG + 4 +KERNELESPREG equ EBPREG + 4 +EBXREG equ KERNELESPREG + 4 +EDXREG equ EBXREG + 4 +ECXREG equ EDXREG + 4 +EAXREG equ ECXREG + 4 +RETADR equ EAXREG + 4 +EIPREG equ RETADR + 4 +CSREG equ EIPREG + 4 +EFLAGSREG equ CSREG + 4 +ESPREG equ EFLAGSREG + 4 +SSREG equ ESPREG + 4 +P_STACKTOP equ SSREG + 4 +P_LDT_SEL equ P_STACKTOP +P_LDT equ P_LDT_SEL + 4 +;added by xw +;begin +INIT_STACK_SIZE equ 1024 * 8 +ESP_SAVE_INT equ P_LDT + 16 +ESP_SAVE_SYSCALL equ ESP_SAVE_INT + 4 +ESP_SAVE_CONTEXT equ ESP_SAVE_SYSCALL + 4 +;SAVE_TYPE equ ESP_SAVE_CONTEXT + 4 ;Deleted by xw, 18/4/19 +;end + +TSS3_S_SP0 equ 4 + +INT_M_CTL equ 0x20 ; I/O port for interrupt controller +INT_M_CTLMASK equ 0x21 ; setting bits in this port disables ints +INT_S_CTL equ 0xA0 ; I/O port for second interrupt controller +INT_S_CTLMASK equ 0xA1 ; setting bits in this port disables ints + +EOI equ 0x20 + +; 以下选择子值必须与 protect.h 中保持一致!!! +SELECTOR_FLAT_C equ 0x08 ; LOADER 里面已经确定了的. +SELECTOR_TSS equ 0x20 ; TSS. 从外层跳到内存时 SS 和 ESP 的值从里面获得. +SELECTOR_KERNEL_CS equ SELECTOR_FLAT_C +SELECTOR_VIDEO equ 0x1b ; added by xw, 18/6/20 diff --git a/include/spinlock.h b/include/spinlock.h new file mode 100644 index 0000000..2fb4839 --- /dev/null +++ b/include/spinlock.h @@ -0,0 +1,29 @@ +/********************************************************** +* spinlock.h //added by mingxuan 2018-12-26 +***********************************************************/ + +// Mutual exclusion lock. +#define uint unsigned +struct spinlock { + uint locked; // Is the lock held? + + // For debugging: + char *name; // Name of lock. + int cpu; // The number of the cpu holding the lock. + uint pcs[10]; // The call stack (an array of program counters) + // that locked the lock. +}; + +void initlock(struct spinlock *lock, char *name); +// Acquire the lock. +// Loops (spins) until the lock is acquired. +// (Because contention is handled by spinning, must not +// go to sleep holding any locks.) +void acquire(struct spinlock *lock); +// Release the lock. +void release(struct spinlock *lock); + + + + + diff --git a/include/stdarg.h b/include/stdarg.h new file mode 100644 index 0000000..f705cf3 --- /dev/null +++ b/include/stdarg.h @@ -0,0 +1,12 @@ +#ifndef MINIOS_STDARG_H +#define MINIOS_STDARG_H + +typedef __builtin_va_list va_list; + +#define va_start(ap, last) __builtin_va_start(ap, last) + +#define va_arg(ap, type) __builtin_va_arg(ap, type) + +#define va_end(ap) __builtin_va_end(ap) + +#endif /* MINIOS_STDARG_H */ \ No newline at end of file diff --git a/include/stdio.h b/include/stdio.h new file mode 100644 index 0000000..833d083 --- /dev/null +++ b/include/stdio.h @@ -0,0 +1,74 @@ +/******************************************** +*用户库函数声明 add by visual 2016.5.16 +*************************************************/ +#include "const.h" //added by mingxuan 2019-5-19 +#include "type.h" //added by mingxuan 2019-5-19 +#include "stdarg.h" + +#ifndef _STDIO_H_ //added by mingxuan 2019-5-19 +#define _STDIO_H_ //added by mingxuan 2019-5-19 + +/*syscall.asm*/ +int get_ticks(); +int get_pid(); +void* kmalloc(int size); +void* kmalloc_4k(); +void* malloc(int size); +void* malloc_4k(); +int free(void *arg); +int free_4k(void* AdddrLin); +int fork(); +int pthread(void *arg); +void udisp_int(int arg); +void udisp_str(char* arg); + +//added by xw +/* file system */ +#define MAX_FILENAME_LEN 12 +#define MAX_PATH 128 +#define O_CREAT 1 +#define O_RDWR 2 +#define SEEK_SET 1 +#define SEEK_CUR 2 +#define SEEK_END 3 + +int open(const char *pathname, int flags); //added by xw, 18/6/19 +int close(int fd); //added by xw, 18/6/19 +int read(int fd, void *buf, int count); //added by xw, 18/6/19 +int write(int fd, const void *buf, int count); //added by xw, 18/6/19 +int lseek(int fd, int offset, int whence); //added by xw, 18/6/19 +int unlink(const char *pathname); //added by xw, 18/6/19 +//~xw + +/*string.asm*/ +long strtol(const char *cp,char **endp,unsigned int base); + +/*printf.c*/ +//added by mingxuan 2019-5-19 +#define EOF -1 +// #define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) +// #define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) +// #define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) +// #define va_end(ap) ( ap = (va_list)0 ) +#define isspace(s) (s==' ') + +#define TOLOWER(x) ((x) | 0x20) +#define isxdigit(c) (('0' <= (c) && (c) <= '9') || ('a' <= (c) && (c) <= 'f') || ('A' <= (c) && (c) <= 'F')) +#define isdigit(c) ('0' <= (c) && (c) <= '9') + +void printfmt(void (*putch)(int, void*), void *putdat, const char *fmt, ...); +void vprintfmt(void (*putch)(int, void*), void *putdat, const char *fmt, va_list); +int vsnprintf(char *buf, int n, const char *fmt, va_list ap); +int snprintf(char *buf, int n, const char *fmt, ...); + +int vprintf(const char *fmt, va_list ap); +int printf(const char *fmt, ...); + +int vkprintf(const char *fmt, va_list ap); +int kprintf(const char *fmt, ...); + +// int scanf(char *str, ...); +char getchar(); //added by mingxuan 2019-5-23 +char* gets(char *str); //added by mingxuan 2019-5-23 + +#endif //added by mingxuan 2019-5-19 \ No newline at end of file diff --git a/include/string.h b/include/string.h new file mode 100644 index 0000000..6e3c91d --- /dev/null +++ b/include/string.h @@ -0,0 +1,17 @@ +#ifndef MINIOS_STRING_H +#define MINIOS_STRING_H + +#include "type.h" + +int strlen(const char *s); +int strnlen(const char *s, size_t size); +char * strcpy(char *dst, const char *src); +char * strncpy(char *dst, const char *src, size_t size); +char * strcat(char *dst, const char *src); +int strcmp(const char *s1, const char *s2); +int strncmp(const char *s1, const char *s2, size_t size); + +void * memset(void *v, int c, size_t n); +void * memcpy(void *dst, const void *src, size_t n); + +#endif /* MINIOS_STRING_H */ \ No newline at end of file diff --git a/include/tty.h b/include/tty.h new file mode 100644 index 0000000..fae4111 --- /dev/null +++ b/include/tty.h @@ -0,0 +1,59 @@ +/*************************************************************************//** + ***************************************************************************** + * @file tty.h + * @brief + * @author Forrest Y. Yu + * @date 2005 + ***************************************************************************** + *****************************************************************************/ + +/********************************************************** +* tty.h //added by mingxuan 2019-5-17 +***********************************************************/ + +#ifndef _ORANGES_TTY_H_ +#define _ORANGES_TTY_H_ + + +#define TTY_IN_BYTES 256 /* tty input queue size */ +#define TTY_OUT_BUF_LEN 2 /* tty output buffer size */ + +/* TTY state (3bit) + wait_enter wait_space display + 1/0 1/0 1/0 +*/ + +#define TTY_STATE_WAIT_ENTER 4 /*100*/ +#define TTY_STATE_WAIT_SPACE 2 /*010*/ +#define TTY_STATE_DISPLAY 1 /*001*/ + +struct s_tty; +struct s_console; + +/* TTY */ +typedef struct s_tty +{ + u32 ibuf[TTY_IN_BYTES]; /* TTY input buffer */ + u32* ibuf_head; /* the next free slot */ + u32* ibuf_tail; /* 缓冲区显示位置指针 */ + u32* ibuf_read; + int ibuf_cnt; /* how many */ + int ibuf_read_cnt; + int status; + + int mouse_left_button; + int mouse_mid_button; + int mouse_X; + int mouse_Y; + + struct s_console * console; +}TTY; + +#include "console.h" + +void select_console(int nr_console); +void init_screen(TTY* tty); +void out_char(CONSOLE* con, char ch); +int is_current_console(CONSOLE* con); + +#endif /* _ORANGES_TTY_H_ */ \ No newline at end of file diff --git a/include/type.h b/include/type.h new file mode 100644 index 0000000..266615c --- /dev/null +++ b/include/type.h @@ -0,0 +1,112 @@ + +/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + type.h +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + Forrest Yu, 2005 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + +#ifndef _ORANGES_TYPE_H_ +#define _ORANGES_TYPE_H_ + +#ifndef NULL +#define NULL ((void*) 0) +#endif + +typedef _Bool bool; +enum { false, true }; + +typedef long long i64; +typedef unsigned long long u64; +typedef int i32; +typedef unsigned int u32; +typedef short i16; +typedef unsigned short u16; +typedef char i8; +typedef unsigned char u8; + +typedef i32 intptr_t; +typedef u32 uintptr_t; + +// 通常描述一个对象的大小,会根据机器的型号变化类型 +typedef u32 size_t; +// signed size_t 通常描述系统调用返回值,会根据机器的型号变化类型 +typedef i32 ssize_t; +// 通常描述偏移量,会根据机器的型号变化类型 +typedef i32 off_t; +// 通常描述物理地址 +typedef u32 phyaddr_t; + +typedef void (*int_handler) (); +typedef void (*task_f) (); +typedef void (*irq_handler) (int irq); + +typedef char * va_list; //added by mingxuan 2019-5-19 + +typedef void* system_call; + +//mainly used in filesystem. added by xw, 18/8/27 +/** + * MESSAGE mechanism is borrowed from MINIX + */ +struct mess1 { + int m1i1; + int m1i2; + int m1i3; + int m1i4; +}; + +struct mess2 { + void* m2p1; + void* m2p2; + void* m2p3; + void* m2p4; +}; + +struct mess3 { + int m3i1; + int m3i2; + int m3i3; + int m3i4; + u64 m3l1; + u64 m3l2; + void* m3p1; + void* m3p2; +}; + +typedef struct { + int source; + int type; + union { + struct mess1 m1; + struct mess2 m2; + struct mess3 m3; + } u; +} MESSAGE; + +/** + * @enum msgtype + * @brief MESSAGE types + */ +enum msgtype { + /* + * when hard interrupt occurs, a msg (with type==HARD_INT) will + * be sent to some tasks + */ + HARD_INT = 1, + + /* SYS task */ + GET_TICKS, + + /// zcr added from ch9/e/include/const.h + /* FS */ + OPEN, CLOSE, READ, WRITE, LSEEK, STAT, UNLINK, + + /* message type for drivers */ + DEV_OPEN = 1001, + DEV_CLOSE, + DEV_READ, + DEV_WRITE, + DEV_IOCTL +}; + +#endif /* _ORANGES_TYPE_H_ */ diff --git a/include/vfs.h b/include/vfs.h new file mode 100644 index 0000000..a0ecc61 --- /dev/null +++ b/include/vfs.h @@ -0,0 +1,91 @@ +/********************************************************** +* vfs.h //added by mingxuan 2019-5-17 +***********************************************************/ + +//#define NR_DEV 10 +#define NR_FS 10 //modified by mingxuan 2020-10-18 +#define DEV_NAME_LEN 15 +//#define NR_fs 3 +#define NR_FS_OP 3 //modified by mingxuan 2020-10-18 +#define NR_SB_OP 2 //added by mingxuan 2020-10-30 + +//#define FILE_MAX_LEN 512*4 //最大长度为4个扇区 +#define FILE_MAX_LEN 512*16 //最大长度为16个扇区(8KB) + +/* //deleted by mingxuan 2020-10-18 +//设备表 +struct device{ + char * dev_name; //设备名 + struct file_op * op; //指向操作表的一项 + int dev_num; //设备号 +}; +*/ +// Replace struct device, added by mingxuan 2020-10-18 +struct vfs{ + char * fs_name; //设备名 + struct file_op * op; //指向操作表的一项 + //int dev_num; //设备号 //deleted by mingxuan 2020-10-29 + + struct super_block *sb; //added by mingxuan 2020-10-29 + struct sb_op *s_op; //added by mingxuan 2020-10-29 +}; + +int sys_open(void *uesp); +int sys_close(void *uesp); +int sys_read(void *uesp); +int sys_write(void *uesp); +int sys_lseek(void *uesp); +int sys_unlink(void *uesp); +int sys_create(void *uesp); +int sys_delete(void *uesp); +int sys_opendir(void *uesp); +int sys_createdir(void *uesp); +int sys_deletedir(void *uesp); + +int do_vopen(const char *path, int flags); +int do_vclose(int fd); +int do_vread(int fd, char *buf, int count); +int do_vwrite(int fd, const char *buf, int count); +int do_vunlink(const char *path); +int do_vlseek(int fd, int offset, int whence); +int do_vcreate(char *pathname); +int do_vdelete(char *path); +int do_vopendir(char *dirname); +int do_vcreatedir(char *dirname); +int do_vdeletedir(char *dirname); + +void init_vfs(); +void init_file_desc_table(); +void init_fileop_table(); + +int sys_CreateFile(void *uesp); +int sys_DeleteFile(void *uesp); +int sys_OpenFile(void *uesp); +int sys_CloseFile(void *uesp); +int sys_WriteFile(void *uesp); +int sys_ReadFile(void *uesp); +int sys_OpenDir(void *uesp); +int sys_CreateDir(void *uesp); +int sys_DeleteDir(void *uesp); +int sys_ListDir(void *uesp); + +//文件系统的操作函数 +struct file_op{ + int (*create) (const char*); + int (*open) (const char* ,int); + int (*close) (int); + int (*read) (int,void * ,int); + int (*write) (int ,const void* ,int); + int (*lseek) (int ,int ,int); + int (*unlink) (const char*); + int (*delete) (const char*); + int (*opendir) (const char *); + int (*createdir) (const char *); + int (*deletedir) (const char *); +}; + +//added by mingxuan 2020-10-29 +struct sb_op{ + void (*read_super_block) (int); + struct super_block* (*get_super_block) (int); +}; \ No newline at end of file diff --git a/include/x86.h b/include/x86.h new file mode 100644 index 0000000..9e47ad6 --- /dev/null +++ b/include/x86.h @@ -0,0 +1,223 @@ +#ifndef MINIOS_X86_H +#define MINIOS_X86_H + +#include + +static inline u8 +inb(int port) +{ + u8 data; + asm volatile("inb %w1,%0" : "=a" (data) : "d" (port)); + return data; +} + +static inline void +insb(int port, void *addr, int cnt) +{ + asm volatile("cld\n\trepne\n\tinsb" + : "=D" (addr), "=c" (cnt) + : "d" (port), "0" (addr), "1" (cnt) + : "memory", "cc"); +} + +static inline u16 +inw(int port) +{ + u16 data; + asm volatile("inw %w1,%0" : "=a" (data) : "d" (port)); + return data; +} + +static inline void +insw(int port, void *addr, int cnt) +{ + asm volatile("cld\n\trepne\n\tinsw" + : "=D" (addr), "=c" (cnt) + : "d" (port), "0" (addr), "1" (cnt) + : "memory", "cc"); +} + +static inline u32 +inl(int port) +{ + u32 data; + asm volatile("inl %w1,%0" : "=a" (data) : "d" (port)); + return data; +} + +static inline void +insl(int port, void *addr, int cnt) +{ + asm volatile("cld\n\trepne\n\tinsl" + : "=D" (addr), "=c" (cnt) + : "d" (port), "0" (addr), "1" (cnt) + : "memory", "cc"); +} + +static inline void +outb(int port, u8 data) +{ + asm volatile("outb %0,%w1" : : "a" (data), "d" (port)); +} + +static inline void +outsb(int port, const void *addr, int cnt) +{ + asm volatile("cld\n\trepne\n\toutsb" + : "=S" (addr), "=c" (cnt) + : "d" (port), "0" (addr), "1" (cnt) + : "cc"); +} + +static inline void +outw(int port, u16 data) +{ + asm volatile("outw %0,%w1" : : "a" (data), "d" (port)); +} + +static inline void +outsw(int port, const void *addr, int cnt) +{ + asm volatile("cld\n\trepne\n\toutsw" + : "=S" (addr), "=c" (cnt) + : "d" (port), "0" (addr), "1" (cnt) + : "cc"); +} + +static inline void +outsl(int port, const void *addr, int cnt) +{ + asm volatile("cld\n\trepne\n\toutsl" + : "=S" (addr), "=c" (cnt) + : "d" (port), "0" (addr), "1" (cnt) + : "cc"); +} + +static inline void +outl(int port, u32 data) +{ + asm volatile("outl %0,%w1" : : "a" (data), "d" (port)); +} + +static inline void +lcr0(u32 val) +{ + asm volatile("movl %0,%%cr0" : : "r" (val)); +} + +static inline u32 +rcr0(void) +{ + u32 val; + asm volatile("movl %%cr0,%0" : "=r" (val)); + return val; +} + +static inline u32 +rcr2(void) +{ + u32 val; + asm volatile("movl %%cr2,%0" : "=r" (val)); + return val; +} + +static inline void +lcr3(u32 val) +{ + asm volatile("movl %0,%%cr3" : : "r" (val)); +} + +static inline u32 +rcr3(void) +{ + u32 val; + asm volatile("movl %%cr3,%0" : "=r" (val)); + return val; +} + +static inline void +lcr4(u32 val) +{ + asm volatile("movl %0,%%cr4" : : "r" (val)); +} + +static inline u32 +rcr4(void) +{ + u32 cr4; + asm volatile("movl %%cr4,%0" : "=r" (cr4)); + return cr4; +} + +static inline void +tlbflush(void) +{ + u32 cr3; + asm volatile("movl %%cr3,%0" : "=r" (cr3)); + asm volatile("movl %0,%%cr3" : : "r" (cr3)); +} + +static inline u32 +read_eflags(void) +{ + u32 eflags; + asm volatile("pushfl\n\t popl %0" : "=r" (eflags)); + return eflags; +} + +static inline void +write_eflags(u32 eflags) +{ + asm volatile("pushl %0\n\t popfl" : : "r" (eflags)); +} + +static inline u32 +read_ebp(void) +{ + u32 ebp; + asm volatile("movl %%ebp,%0" : "=r" (ebp)); + return ebp; +} + +static inline u32 +read_esp(void) +{ + u32 esp; + asm volatile("movl %%esp,%0" : "=r" (esp)); + return esp; +} + +static inline u64 +read_tsc(void) +{ + u64 tsc; + asm volatile("rdtsc" : "=A" (tsc)); + return tsc; +} + +static inline u32 +xchg(volatile u32 *addr, u32 newval) +{ + u32 result; + + // The + in "+m" denotes a read-modify-write operand. + asm volatile("lock\n\t xchgl %0, %1" + : "+m" (*addr), "=a" (result) + : "1" (newval) + : "cc"); + return result; +} + +static inline void +disable_int() +{ + asm volatile("cli"); +} + +static inline void +enable_int() +{ + asm volatile("sti"); +} + +#endif /* MINIOS_X86_H */ \ No newline at end of file diff --git a/kernel/Makefrag b/kernel/Makefrag new file mode 100644 index 0000000..9efc253 --- /dev/null +++ b/kernel/Makefrag @@ -0,0 +1,74 @@ +OBJDIRS += kern + +KERN_ENTRY_ADDR := 0xC0030400 + +KERN_SRCFILES :=kernel/kernel.asm \ + kernel/hd.c \ + kernel/fat32.c \ + kernel/tty.c \ + kernel/fs.c \ + kernel/global.c \ + kernel/clock.c \ + kernel/elf.c \ + kernel/spinlock.c \ + kernel/keyboard.c \ + kernel/assist.c \ + kernel/fork.c \ + kernel/file.c \ + kernel/console.c \ + kernel/syscallc.c \ + kernel/vfs.c \ + kernel/proc.c \ + kernel/pagetbl.c \ + kernel/pthread.c \ + kernel/memman.c \ + kernel/main.c \ + kernel/exec.c \ + kernel/ktest.c \ + kernel/base.c \ + kernel/start.c \ + kernel/i8259.c \ + kernel/testfunc.c \ + kernel/protect.c \ + lib/klib.c \ + lib/kliba.asm \ + + +KERN_OBJFILES := $(patsubst %.c, $(OBJDIR)/%.o, $(KERN_SRCFILES)) +KERN_OBJFILES := $(patsubst %.asm, $(OBJDIR)/%.o, $(KERN_OBJFILES)) + +$(OBJDIR)/kernel/ktest.o: kernel/ktest.c $(OBJDIR)/.vars.CFLAGS + @echo + cc $< + @mkdir -p $(@D) + @$(CC) $(CFLAGS) -c -o $@ $< \ + -D INSTALL_FILENAME='"$(INSTALL_FILENAME)"' \ + -D $(INSTALL_TYPE) \ + +$(OBJDIR)/kernel/fs.o: kernel/fs.c $(OBJDIR)/.vars.CFLAGS + @echo + cc $< + @mkdir -p $(@D) + @$(CC) $(CFLAGS) -c -o $@ $< \ + -D INSTALL_FILENAME='"$(INSTALL_FILENAME)"' \ + -D INSTALL_NR_SECTORS=$(INSTALL_NR_SECTORS) \ + -D INSTALL_START_SECTOR=$(INSTALL_START_SECTOR) \ + -D $(INSTALL_TYPE) \ + +$(OBJDIR)/kernel/spinlock.o: kernel/spinlock.c $(OBJDIR)/.vars.CFLAGS + @echo + cc $< + @mkdir -p $(@D) + @$(CC) $(CFLAGS) -masm=intel -c -o $@ $< + +$(OBJDIR)/kernel/%.o: kernel/%.c $(OBJDIR)/.vars.CFLAGS + @echo + cc $< + @mkdir -p $(@D) + @$(CC) $(CFLAGS) -c -o $@ $< + +$(OBJDIR)/kernel/%.o: kernel/%.asm + @echo + as obj $< + @mkdir -p $(@D) + @$(AS) -I ./include -f elf -o $@ $< + +$(OBJDIR)/kernel/kernel.bin: $(KERN_OBJFILES) $(LIB_A) $(OBJDIR)/.vars.LDFLAGS + @echo + ld $@ + @$(LD) $(LDFLAGS) -s -Ttext $(KERN_ENTRY_ADDR) -o $@ $(KERN_OBJFILES) $(LIB_A) $(GCC_LIB) + @$(LD) $(LDFLAGS) -Ttext $(KERN_ENTRY_ADDR) -o $(OBJDIR)/kernel/kernel.dbg $(KERN_OBJFILES) $(LIB_A) $(GCC_LIB) \ No newline at end of file diff --git a/kernel/assist.c b/kernel/assist.c new file mode 100644 index 0000000..7df1062 --- /dev/null +++ b/kernel/assist.c @@ -0,0 +1,257 @@ +/********************************************************** +* assist.c //added by mingxuan 2019-5-17 +***********************************************************/ + +#include "fat32.h" +#include "type.h" +#include "const.h" +#include "protect.h" +#include "string.h" +#include "proc.h" +#include "tty.h" +#include "console.h" +#include "global.h" +#include "proto.h" +#include "fs_const.h" +#include "hd.h" +#include "fs.h" +#include "fs_misc.h" + +extern CHAR cur_path[256]; +extern u8* buf; + +void MakeFullPath(PCHAR parent,PCHAR name,PCHAR fullpath) +{ + int i=0,j=0,len=0; + len=strlen(parent); + for(i=0;i=0;i--) + { + if(cur_path[i]!='\\') + { + cur_path[i]=0; + }else{ + cur_path[i]=0; + break; + } + } + len2=strlen(cur_path); + if(len2<=2)//根目录 + { + cur_path[len2]='\\'; + } + }else if(strcmp(addpath,"\\")==0){ + for(i=len1;i>=0;i--) + { + if(cur_path[i]!=':') + { + cur_path[i]=0; + }else{ + break; + } + } + cur_path[i+1]='\\'; + }else{ + if(cur_path[len1-1]!='\\') + { + cur_path[len1]='\\'; + len1++; + } + len2=strlen(addpath); + for(i=0;i=0;i--) + { + if(path[i]=='\\') + { + break; + } + } + for(i=i+1;i=0;i--) + { + parent[i]=fullpath[i]; + } +} + +STATE IsFullPath(PCHAR path) +{ + int i=0; + for(i=0;i<2;i++)//只看是不是以盘符和:开头的 + { + if(cur_path[i]!=path[i]) + { + return FALSE; + } + } + return TRUE; +} + +void ToFullPath(PCHAR path,PCHAR fullpath) +{ + int i=0,j=0,len=0; + if(IsFullPath(path)) + { + strcpy(fullpath,path); + }else + { + len=strlen(cur_path); + for(i=0;itm_year + 1900; + month=timeinfo->tm_mon + 1; + day=timeinfo->tm_mday; + hour=timeinfo->tm_hour; + minute=timeinfo->tm_min; + second=timeinfo->tm_sec; +*/ + year = 2018; + month = 12; + day = 27; + hour = 14; + minute = 30; + second = 24; + result[1]=hour*2048+minute*32+second/2; + result[0]=(year-1980)*512+month*32+day; +} + + +void FormatFileNameAndExt(PCHAR filename,PCHAR name,PCHAR ext) +{ + UINT i=0,j=0,len=0; + + len=strlen(filename); + for(i=0;i='a'&&filename[i]<='z') + { + name[i]=filename[i]-32; + }else{ + name[i]=filename[i]; + } + } + } + for(j=i;j<8;j++) + { + name[j]=' '; + } + if(i='a'&&filename[i]<='z') + { + ext[j++]=filename[i]-32; + }else{ + ext[j++]=filename[i]; + } + } + for(;j<3;j++) + { + ext[j]=' '; + } + } +} + + +void FormatDirNameAndExt(PCHAR dirname,PCHAR name,PCHAR ext) +{ + UINT i=0,len=0; + len=strlen(dirname); + for(i=0;i='a'&&dirname[i]<='z') + { + name[i]=dirname[i]-32; + }else{ + name[i]=dirname[i]; + } + } + for(;i<8;i++) + { + name[i]=' '; + } + for(i=0;i<3;i++) + { + ext[i]=' '; + } +} \ No newline at end of file diff --git a/kernel/base.c b/kernel/base.c new file mode 100644 index 0000000..542bf37 --- /dev/null +++ b/kernel/base.c @@ -0,0 +1,783 @@ +/********************************************************** +* base.c //added by mingxuan 2019-5-17 +***********************************************************/ + +#include "fat32.h" +#include "type.h" +#include "const.h" +#include "protect.h" +#include "string.h" +#include "proc.h" +#include "tty.h" +#include "console.h" +#include "global.h" +#include "proto.h" +#include "fs_const.h" +#include "hd.h" +#include "fs.h" +#include "fs_misc.h" + +DWORD FAT_END=268435455;//文件簇号结束标记 +DWORD TotalSectors=0;//总扇区数,当载入磁盘时,才从DBR中读取。 +WORD Bytes_Per_Sector=0;//每个扇区的字节数,当载入磁盘时,才从DBR中读取。 +BYTE Sectors_Per_Cluster=0;//每个簇的扇区数,当载入磁盘时,才从DBR中读取。 +WORD Reserved_Sector=0;//保留扇区数,当载入磁盘时,才从DBR中读取。 +DWORD Sectors_Per_FAT=0;//每个FAT所占的扇区数,当载入磁盘时,才从DBR中读取。 +UINT Position_Of_RootDir=0;//根目录的位置。 +UINT Position_Of_FAT1=0;//FAT1的位置。 +UINT Position_Of_FAT2=0;//FAT2的位置。 + +extern CHAR cur_path[256]; + +BYTE FATBuf[1024]={0}; +DWORD globalSectorIndex=-1; + +void ReadSector(BYTE* buf,DWORD sectorIndex) +{ + int fat32_dev = get_fs_dev(PRIMARY_MASTER, FAT32_TYPE); //added by mingxuan 2020-10-27 + + //RD_SECT_SCHED_FAT(buf, sectorIndex); // deleted by mingxuan 2020-10-27 + RD_SECT_SCHED_FAT(fat32_dev, buf, sectorIndex); // modified by mingxuan 2020-10-27 +} + +void WriteSector(BYTE* buf,DWORD sectorIndex) +{ + if(sectorIndex==globalSectorIndex) + { + memcpy(FATBuf,buf,512);//写FAT表的缓冲区,保持数据同步 + } + int fat32_dev = get_fs_dev(PRIMARY_MASTER, FAT32_TYPE); //added by mingxuan 2020-10-27 + + //WR_SECT_SCHED_FAT(buf, sectorIndex); // deleted by mingxuan 2020-10-27 + WR_SECT_SCHED_FAT(fat32_dev, buf, sectorIndex); // modified by mingxuan 2020-10-27 +} + +void DeleteAllRecord(DWORD startCluster) +{ + PBYTE buf=NULL; + Record record; + DWORD off=0; + DWORD curClusterIndex=startCluster,nextClusterIndex=0,start=0; + DWORD curSectorIndex=0,preSectorIndex=0,off_in_sector=0,last=0; + + if(startCluster==2) + { + off=Position_Of_RootDir+(startCluster-2)*Sectors_Per_Cluster*Bytes_Per_Sector+sizeof(Record); + }else{ + off=Position_Of_RootDir+(startCluster-2)*Sectors_Per_Cluster*Bytes_Per_Sector+2*sizeof(Record); + } + buf = (PBYTE)K_PHY2LIN(sys_kmalloc(Bytes_Per_Sector*sizeof(BYTE))); + last=Reserved_Sector+2*Sectors_Per_FAT+(startCluster-2)*Sectors_Per_Cluster+Sectors_Per_Cluster; + do + { + curSectorIndex=off/Bytes_Per_Sector; + off_in_sector=off%Bytes_Per_Sector; + if(curSectorIndex!=preSectorIndex) + { + if(preSectorIndex!=0) + { + WriteSector(buf,preSectorIndex); + } + ReadSector(buf,curSectorIndex); + preSectorIndex=curSectorIndex; + } + memcpy(&record,buf+off_in_sector,sizeof(Record)); + if(record.filename[0]==0) + { + WriteSector(buf,curSectorIndex); + break; + } + if(record.filename[0]!=(BYTE)0xE5&&record.filename[0]!=0) + { + record.filename[0]=(BYTE)0xE5; + memcpy(buf+off_in_sector,&record,sizeof(Record)); + start=(DWORD)(record.highClusterNum<<16)+(DWORD)record.lowClusterNum; + if(record.proByte==(BYTE)0x10)//是子目录 + { + DeleteAllRecord(start);//删除此子目录 + }else{ + ClearFATs(start); + } + } + preSectorIndex=curSectorIndex; + if(curSectorIndex>=last) + { + GetNextCluster(curClusterIndex,&nextClusterIndex); + if(nextClusterIndex==FAT_END) + { + WriteSector(buf,curSectorIndex); + break; + }else{ + curClusterIndex=nextClusterIndex; + off=Position_Of_RootDir+(curClusterIndex-2)*Sectors_Per_Cluster*Bytes_Per_Sector; + } + }else{ + off+=sizeof(Record); + } + }while(1); + sys_free(buf); + ClearFATs(startCluster); +} + +STATE FindClusterForDir(PDWORD pcluster) +{ + PBYTE buf=NULL; + DWORD clusterIndex=2,nextClusterIndex=0; + DWORD curSectorIndex=0,last=0,offset=0; + + buf = (PBYTE)K_PHY2LIN(sys_kmalloc(Bytes_Per_Sector*sizeof(BYTE))); + if(buf==NULL) + { + return SYSERROR; + } + curSectorIndex=Reserved_Sector; + last=curSectorIndex+Sectors_Per_FAT; + offset=8; + for(;curSectorIndexsize%Bytes_Per_Sector==0) + { + totalSectors=pfile->size/Bytes_Per_Sector; + }else{ + totalSectors=pfile->size/Bytes_Per_Sector+1; + } + sectorNum=pfile->off/Bytes_Per_Sector+1; + if(off_in_sector!=NULL) + { + (*off_in_sector)=pfile->off%Bytes_Per_Sector; + } + curSectorIndex=Reserved_Sector+2*Sectors_Per_FAT+(pfile->start-2)*Sectors_Per_Cluster; + do + { + counter++; + if(counter==sectorNum) + { + if(totalSectors==1) + { + *sectorIndex=curSectorIndex; + *isLastSector=1; + }else{ + *sectorIndex=curSectorIndex; + } + break; + } + GetNextSector(pfile,curSectorIndex,&nextSectorIndex,isLastSector); + curSectorIndex=nextSectorIndex; + }while(1); +} + +void GetNextSector(PFile pfile,DWORD curSectorIndex,PDWORD nextSectorIndex,PUINT isLastSector) +{ + DWORD temp=0; + DWORD curClusterIndex=0,nextClusterIndex=0,last=0; + BYTE off_in_cluster=0; + + temp=curSectorIndex-Reserved_Sector-2*Sectors_Per_FAT; + curClusterIndex=temp/Sectors_Per_Cluster+2;//此扇区所在的簇 + off_in_cluster=(BYTE)(temp%Sectors_Per_Cluster);//此扇区是所在簇的的几个扇区,从零开始 + + GetNextCluster(curClusterIndex,&nextClusterIndex); + if(nextClusterIndex==FAT_END)//此扇区所在簇是该文件的最后一个簇 + { + if(pfile->flag==R) + { + temp=pfile->size%(Sectors_Per_Cluster*Bytes_Per_Sector); + if(temp==0)//此文件实际占用了整数个簇 + { + last=Sectors_Per_Cluster; + }else{ + if(temp%Bytes_Per_Sector==0) + { + last=temp/Bytes_Per_Sector; + }else{ + last=temp/Bytes_Per_Sector+1;//最后一簇实际占用的扇区数 + } + } + }else{ + last=Sectors_Per_Cluster; + } + if(off_in_clusterflag==R) + { + temp=pfile->size%(Sectors_Per_Cluster*Bytes_Per_Sector); + if(temp!=0)//此文件实际占用了整数个簇 + { + last=temp/Bytes_Per_Sector+1;//最后一簇实际占用的扇区数 + if(last==1)//最后一簇实际上只占用了一个扇区 + { + *isLastSector=1;//那么当前返回的扇区就是此文件的最后一个扇区 + }else{ + *isLastSector=0; + } + } + } + } + } +} + +STATE GetNextCluster(DWORD clusterIndex,PDWORD nextCluster) +{ + DWORD sectorIndex=0,offset=0,off_in_sector=0; + + offset=8+(clusterIndex-2)*sizeof(DWORD); + sectorIndex=Reserved_Sector+offset/Bytes_Per_Sector; + off_in_sector=offset%Bytes_Per_Sector; + if(sectorIndex!=globalSectorIndex) + { + ReadSector(FATBuf,sectorIndex); + globalSectorIndex=sectorIndex; + } + memcpy(nextCluster,FATBuf+off_in_sector,sizeof(DWORD)); + return OK; +} + +STATE ReadRecord(DWORD parentCluster,PCHAR name,PRecord record,PDWORD sectorIndex,PDWORD off_in_sector) +{ + CHAR temp[256]={0}; + DWORD curSectorIndex=0,curClusterIndex=parentCluster,off=0,size_of_Record; + BYTE *buf; + UINT last=0; + + size_of_Record=sizeof(Record); + buf = (PBYTE)K_PHY2LIN(sys_kmalloc(Bytes_Per_Sector*sizeof(BYTE))); + do + { + curSectorIndex=Reserved_Sector+2*Sectors_Per_FAT+(curClusterIndex-2)*Sectors_Per_Cluster; + last=curSectorIndex+8; + for(;curSectorIndexfilename[0]==0)//没有此目录项 + { + sys_free(buf); + return WRONGPATH; + } + memset(temp,0,sizeof(temp)); + // if(record->sysReserved==0x18)//是长文件名文件的短目录项 + // { + // do + // { + // memcpy(&lrecord,buf+off,size_of_Record); + // strcpy(temp+offinlongname,lrecord.name1); + // offinlongname+=10; + // strcpy(temp+offinlongname,lrecord.name2); + // offinlongname+=19; + // if(lrecord.proByte&0x40!=0) + // { + // break; + // } + // if(off>=Bytes_Per_Sector) + // { + // ReadSector(buf,curSectorIndex); + // off=0; + // } + // }while(1); + // }else{ + // GetNameFromRecord(*record,temp); + // } + GetNameFromRecord(*record,temp); + if(strcmp(temp,name)==0) + { + if(sectorIndex!=NULL) + { + *sectorIndex=curSectorIndex; + } + if(off_in_sector!=NULL) + { + *off_in_sector=off; + } + sys_free(buf); + return OK; + } + } + } + // GetNextCluster(curClusterIndex,&nextClusterIndex); + // if(nextClusterIndex==FAT_END) + // { + // sys_free(buf); + // return WRONGPATH; + // }else{ + // curClusterIndex=nextClusterIndex; + // } + }while(1); +} + +void ClearFATs(DWORD startClusterIndex) +{ + PBYTE buf=NULL; + DWORD curClusterIndex=startClusterIndex,nextClusterIndex=0; + DWORD curSectorIndex=0,preSectorIndex=0,temp=0,offset=0; + DWORD clear=0; + + if(startClusterIndex==0) + { + return; + } + buf = (PBYTE)K_PHY2LIN(sys_kmalloc(Bytes_Per_Sector*sizeof(BYTE))); + do + { + temp=(curClusterIndex-2)*sizeof(DWORD)+8; + curSectorIndex=Reserved_Sector+temp/Bytes_Per_Sector; + offset=temp%Bytes_Per_Sector; + if(curSectorIndex!=preSectorIndex) + { + if(preSectorIndex!=0)//不是第一个扇区 + { + WriteSector(buf,preSectorIndex); + } + preSectorIndex=curSectorIndex; + ReadSector(buf,curSectorIndex); + } + memcpy(&nextClusterIndex,buf+offset,sizeof(DWORD)); + curClusterIndex=nextClusterIndex; + memcpy(buf+offset,&clear,sizeof(DWORD)); + preSectorIndex=curSectorIndex; + }while(curClusterIndex!=FAT_END); + WriteSector(buf,curSectorIndex); + sys_free(buf); +} + +STATE ClearRecord(DWORD parentCluster,PCHAR name,PDWORD startCluster) +{ + Record record; + DWORD startClusterIndex=0,sectorIndex=0,off_in_sector=0; + STATE state; + + state=ReadRecord(parentCluster,name,&record,§orIndex,&off_in_sector); + if(state!=OK) + { + return state; + } + record.filename[0]=(BYTE)0xE5; + startClusterIndex=((DWORD)record.highClusterNum<<16)+(DWORD)record.lowClusterNum; + WriteRecord(record,sectorIndex,off_in_sector); + *startCluster=startClusterIndex; + return OK; +} + +STATE PathToCluster(PCHAR path, PDWORD cluster) +{ + UINT i=0,j=0,len=0; + CHAR name[256]={0}; + CHAR fullpath[256]={0}; + Record record; + DWORD parentCluster=2; + STATE state; + + ToFullPath(path,fullpath); + len=strlen(fullpath); + for(i=0;i=len)//说明是根目录 + { + *cluster=2; + return OK; + } + j=0; + for(i=i+1;i=Bytes_Per_Sector) + { + ReadSector(buf,curSectorIndex); + offset=0; + } + }while(1); + } + else + { + if(record.proByte!=(BYTE)0x08)//不是卷标 + { + GetNameFromRecord(record,fullname); + if(strcmp(name,fullname)==0)//有重名的文件或目录 + { + sys_free(buf); + return NAMEEXIST; + } + } + } + } + } + }while(1); +} + +void GetNameFromRecord(Record record,PCHAR fullname) +{ + UINT i=0,j=0,point=0; + for(i=0;i<8;i++) + { + if(record.filename[i]==' ') + { + break; + } + if(record.filename[i]>='A'&&record.filename[i]<='Z') + { + fullname[j++]=record.filename[i]+32; + }else{ + fullname[j++]=record.filename[i]; + } + } + point=j; + fullname[j++]='.'; + for(i=0;i<3;i++) + { + if(record.extension[i]>='A'&&record.extension[i]<='Z') + { + fullname[j++]=record.extension[i]+32; + }else{ + fullname[j++]=record.extension[i]; + } + } + for(j=j-1;j>=0;j--)//去掉后面的空格 + { + if(fullname[j]==' '||(fullname[j]=='.'&&j==point)) + { + fullname[j]=0; + }else{ + break; + } + } +} + +void CreateRecord(PCHAR filename,BYTE type,DWORD startCluster,DWORD size,PRecord precord) +{ + WORD time[2]; + CHAR name[256]={0}; + CHAR ext[256]={0}; + if(type==(BYTE)0x08||type==(BYTE)0x10) + { + FormatDirNameAndExt(filename,name,ext); + precord->sysReserved=0x08; + }else{ + FormatFileNameAndExt(filename,name,ext); + precord->sysReserved=0x18; + } + TimeToBytes(time);//获取当前时间 + strcpy((char *)precord->filename,name); + strcpy((char *)precord->extension,ext); + precord->proByte=type; + precord->createMsecond=0; + precord->createTime=time[1]; + precord->createDate=time[0]; + precord->lastAccessDate=time[0]; + precord->highClusterNum=(WORD)(startCluster>>16); + precord->lastModifiedTime=time[1]; + precord->lastModifiedDate=time[0]; + precord->lowClusterNum=(WORD)(startCluster&0x0000ffff); + precord->filelength=size; +} + +STATE WriteRecord(Record record,DWORD sectorIndex,DWORD off_in_sector) +{ + BYTE *buf=NULL; + + buf = (PBYTE)K_PHY2LIN(sys_kmalloc(Bytes_Per_Sector*sizeof(BYTE))); + ReadSector(buf,sectorIndex); + memcpy(buf+off_in_sector,&record,sizeof(Record)); + WriteSector(buf,sectorIndex); + sys_free(buf); + return OK; +} + +STATE WriteFAT(DWORD totalclusters,PDWORD clusters) +{ + PBYTE buf=NULL; + DWORD i=0,curSectorIndex=0,preSectorIndex=0,offset=0,off_in_sector=0; + + buf = (PBYTE)K_PHY2LIN(sys_kmalloc(Bytes_Per_Sector*sizeof(BYTE))); + if(buf==NULL) + { + return SYSERROR; + } + for(i=0;istart=clusters[0]; + WriteFAT(n,clusters); + sys_free(clusters); + return OK; +} + +STATE AddCluster(DWORD startClusterIndex,DWORD num)//cluster表示该文件或目录所在目录的簇,num表示增加几个簇 +{ + PDWORD clusters=NULL; + DWORD curClusterIndex=startClusterIndex,nextClusterIndex=0; + // DWORD bytes_per_sector=Sectors_Per_FAT*Bytes_Per_Sector; + STATE state; + clusters = (PDWORD)K_PHY2LIN(sys_kmalloc((num+1)*sizeof(DWORD))); + if(clusters==NULL) + { + return SYSERROR; + } + state=FindClusterForFile(num,clusters+1); + if(state!=OK) + { + return state; + } + do + { + GetNextCluster(curClusterIndex,&nextClusterIndex); + if(nextClusterIndex==FAT_END) + { + clusters[0]=curClusterIndex; + break; + }else{ + curClusterIndex=nextClusterIndex; + } + }while(1); + WriteFAT(num+1,clusters); + sys_free(clusters); + return OK; +} + +STATE NeedMoreCluster(PFile pfile,DWORD size,PDWORD number) +{ + DWORD n=0,clusterNum,bytes_per_cluster=0; + + bytes_per_cluster=Sectors_Per_Cluster*Bytes_Per_Sector; + if(pfile->off == 0) { + return FALSE; + } + if(pfile->off%bytes_per_cluster==0)//clusterNum是实际占用的簇的个数 + { + clusterNum=pfile->off/bytes_per_cluster; + }else{ + clusterNum=pfile->off/bytes_per_cluster+1; + } + if(size>clusterNum*bytes_per_cluster-pfile->off)//空间不足需要追加更多的簇 + { + if((size-(clusterNum*bytes_per_cluster-pfile->off))%bytes_per_cluster==0) + { + n=(size-(clusterNum*bytes_per_cluster-pfile->off))/bytes_per_cluster; + }else{ + n=(size-(clusterNum*bytes_per_cluster-pfile->off))/bytes_per_cluster+1; + } + *number=n; + return TRUE;//表示还需要n簇 + } + return FALSE;//表示目前空间够用,不需要更多的簇 +} + +STATE FindClusterForFile(DWORD totalClusters,PDWORD clusters) +{ + PBYTE buf=NULL; + DWORD clusterIndex=1,nextClusterIndex=0; + UINT index=0,i=0,j=0; + DWORD curSectorIndex=0,off_in_sector=0; + + buf = (PBYTE)K_PHY2LIN(sys_kmalloc(Bytes_Per_Sector*sizeof(BYTE))); + if(buf==NULL) + { + return SYSERROR; + } + curSectorIndex=Reserved_Sector; + off_in_sector=8; + do + { + ReadSector(buf,curSectorIndex); + for(i=off_in_sector;i=totalClusters)//找够了 + { + sys_free(buf); + for(j=0;j=Position_Of_FAT2) + { + sys_free(buf); + return INSUFFICIENTSPACE; + }else{ + curSectorIndex++; + off_in_sector=0; + } + }while(1); +} + +void ClearClusters(DWORD cluster) +{ + BYTE buf[512]={0}; + UINT sectorIndex=0; + UINT first=0,last=0; + + first=Reserved_Sector+2*Sectors_Per_FAT+(cluster-2)*Sectors_Per_Cluster; + last=first+8; + + for(sectorIndex=first;sectorIndextask.ticks--; + sys_wakeup(&ticks); +} + +/*======================================================================* + milli_delay + *======================================================================*/ +void milli_delay(int milli_sec) +{ + int t = get_ticks(); + + while(((get_ticks() - t) * 1000 / HZ) < milli_sec) {} +} + diff --git a/kernel/console.c b/kernel/console.c new file mode 100644 index 0000000..380c45d --- /dev/null +++ b/kernel/console.c @@ -0,0 +1,327 @@ +#include "type.h" +#include "stdio.h" +#include "const.h" +#include "protect.h" +#include "string.h" +#include "fs.h" +#include "proc.h" +#include "tty.h" +#include "console.h" +#include "global.h" +#include "keyboard.h" +#include "proto.h" +#include "x86.h" + +/* local routines */ +static void set_cursor(unsigned int position); +static void set_video_start_addr(u32 addr); +static void flush(CONSOLE* con); +static void w_copy(unsigned int dst, const unsigned int src, int size); +static void clear_screen(int pos, int len); +void scroll_screen(CONSOLE* con, int dir); + +/***************************************************************************** + * init_screen + *****************************************************************************/ +/** + * Initialize the console of a certain tty. + * + * @param tty Whose console is to be initialized. + *****************************************************************************/ +void init_screen(TTY* tty) +{ + int nr_tty = tty - tty_table; + + tty->console = console_table + nr_tty; + + /* + * NOTE: + * variables related to `position' and `size' below are + * in WORDs, but not in BYTEs. + */ + int v_mem_size = V_MEM_SIZE >> 1; /* size of Video Memory */ + int size_per_con = (v_mem_size / NR_CONSOLES)/80*80; + tty->console->orig = nr_tty * size_per_con; + tty->console->con_size = size_per_con / SCR_WIDTH * SCR_WIDTH; + tty->console->cursor = tty->console->crtc_start = tty->console->orig; + tty->console->is_full = 0; + tty->console->current_line = 0; + + if(nr_tty==0){ + tty->console->cursor = disp_pos / 2; + } + const char prompt[] = "[TTY #?]\n"; + + const char * p = prompt; + for (; *p; p++){ + out_char(tty->console, *p == '?' ? nr_tty + '0' : *p); + } + + set_cursor(tty->console->cursor); +} + + +/***************************************************************************** + * out_char + *****************************************************************************/ +/** + * Print a char in a certain console. + * + * @param con The console to which the char is printed. + * @param ch The char to print. + *****************************************************************************/ + +void out_char(CONSOLE* con, char ch) +{ + disable_int(); + + int cursor_x = (con->cursor - con->orig) % SCR_WIDTH; + int cursor_y = (con->cursor - con->orig) / SCR_WIDTH; + + switch(ch) { + case '\n': + con->cursor = con->orig + SCR_WIDTH * (cursor_y + 1); + break; + case '\b': + if (con->cursor > con->orig) { + con->cursor--; + //*(pch - 2) = ' '; + //*(pch - 1) = DEFAULT_CHAR_COLOR; + disp_pos = con->cursor*2; + write_char(' '); + } + break; + default: + //*pch++ = ch; + //*pch++ = DEFAULT_CHAR_COLOR; + disp_pos = con->cursor*2; + write_char(ch); + con->cursor++; + + break; + } + + + + if (con->cursor - con->orig >= con->con_size) { + cursor_x = (con->cursor - con->orig) % SCR_WIDTH; + cursor_y = (con->cursor - con->orig) / SCR_WIDTH; + int cp_orig = con->orig + (cursor_y + 1) * SCR_WIDTH - SCR_SIZE; + w_copy(con->orig, cp_orig, SCR_SIZE - SCR_WIDTH); + con->crtc_start = con->orig; + con->cursor = con->orig + (SCR_SIZE - SCR_WIDTH) + cursor_x; + clear_screen(con->cursor, SCR_WIDTH); + if (!con->is_full) + con->is_full = 1; + } + + //assert(con->cursor - con->orig < con->con_size); + + while (con->cursor >= con->crtc_start + SCR_SIZE || + con->cursor < con->crtc_start) { + scroll_screen(con, SCR_UP); + + clear_screen(con->cursor, SCR_WIDTH); + } + + flush(con); + + enable_int(); +} + + +/***************************************************************************** + * clear_screen + *****************************************************************************/ +/** + * Write whitespaces to the screen. + * + * @param pos Write from here. + * @param len How many whitespaces will be written. + *****************************************************************************/ +static void clear_screen(int pos, int len) +{ + u8 * pch = (u8*)K_PHY2LIN(V_MEM_BASE + pos * 2); + while (--len >= 0) { + *pch++ = ' '; + *pch++ = DEFAULT_CHAR_COLOR; + } +} + + +/***************************************************************************** + * is_current_console + *****************************************************************************/ +/** + * Uses `nr_current_console' to determine if a console is the current one. + * + * @param con Ptr to console. + * + * @return TRUE if con is the current console. + *****************************************************************************/ +int is_current_console(CONSOLE* con) +{ + return (con == &console_table[current_console]); +} + + +/***************************************************************************** + * set_cursor + *****************************************************************************/ +/** + * Display the cursor by setting CRTC (6845 compatible) registers. + * + * @param position Position of the cursor based on the beginning of the video + * memory. Note that it counts in WORDs, not in BYTEs. + *****************************************************************************/ +static void set_cursor(unsigned int position) +{ + disable_int(); + outb(CRTC_ADDR_REG, CURSOR_H); + outb(CRTC_DATA_REG, (position >> 8) & 0xFF); + outb(CRTC_ADDR_REG, CURSOR_L); + outb(CRTC_DATA_REG, position & 0xFF); + enable_int(); +} + + +/***************************************************************************** + * set_video_start_addr + *****************************************************************************/ +/** + * Routine for hardware screen scrolling. + * + * @param addr Offset in the video memory. + *****************************************************************************/ +static void set_video_start_addr(u32 addr) +{ + disable_int(); + outb(CRTC_ADDR_REG, START_ADDR_H); + outb(CRTC_DATA_REG, (addr >> 8) & 0xFF); + outb(CRTC_ADDR_REG, START_ADDR_L); + outb(CRTC_DATA_REG, addr & 0xFF); + enable_int(); +} + + +/***************************************************************************** + * select_console + *****************************************************************************/ +/** + * Select a console as the current. + * + * @param nr_console Console nr, range in [0, NR_CONSOLES-1]. + *****************************************************************************/ +void select_console(int nr_console) +{ + if ((nr_console < 0) || (nr_console >= NR_CONSOLES)) return; + + flush(&console_table[current_console = nr_console]); +} + + +/***************************************************************************** + * scroll_screen + *****************************************************************************/ +/** + * Scroll the screen. + * + * Note that scrolling UP means the content of the screen will go upwards, so + * that the user can see lines below the bottom. Similarly scrolling DOWN means + * the content of the screen will go downwards so that the user can see lines + * above the top. + * + * When there is no line below the bottom of the screen, scrolling UP takes no + * effects; when there is no line above the top of the screen, scrolling DOWN + * takes no effects. + * + * @param con The console whose screen is to be scrolled. + * @param dir SCR_UP : scroll the screen upwards; + * SCR_DN : scroll the screen downwards + *****************************************************************************/ +void scroll_screen(CONSOLE* con, int dir) +{ + /* + * variables below are all in-console-offsets (based on con->orig) + */ + int oldest; /* addr of the oldest available line in the console */ + int newest; /* .... .. ... latest ......... .... .. ... ....... */ + int scr_top;/* position of the top of current screen */ + + newest = (con->cursor - con->orig) / SCR_WIDTH * SCR_WIDTH; + oldest = con->is_full ? (newest + SCR_WIDTH) % con->con_size : 0; + scr_top = con->crtc_start - con->orig; + + if (dir == SCR_DN) { + if (!con->is_full && scr_top > 0) { + con->crtc_start -= SCR_WIDTH; + } + else if (con->is_full && scr_top != oldest) { + if (con->cursor - con->orig >= con->con_size - SCR_SIZE) { + if (con->crtc_start != con->orig) + con->crtc_start -= SCR_WIDTH; + } + else if (con->crtc_start == con->orig) { + scr_top = con->con_size - SCR_SIZE; + con->crtc_start = con->orig + scr_top; + } + else { + con->crtc_start -= SCR_WIDTH; + } + } + } + else if (dir == SCR_UP) { + if (!con->is_full && newest >= scr_top + SCR_SIZE) { + con->crtc_start += SCR_WIDTH; + } + else if (con->is_full && scr_top + SCR_SIZE - SCR_WIDTH != newest) { + if (scr_top + SCR_SIZE == con->con_size) + con->crtc_start = con->orig; + else + con->crtc_start += SCR_WIDTH; + } + } + else { + //assert(dir == SCR_DN || dir == SCR_UP); + } + + flush(con); +} + + +/***************************************************************************** + * flush + *****************************************************************************/ +/** + * Set the cursor and starting address of a console by writing the + * CRT Controller Registers. + * + * @param con The console to be set. + *****************************************************************************/ +static void flush(CONSOLE* con) +{ + if (is_current_console(con)) { + set_cursor(con->cursor); + set_video_start_addr(con->crtc_start); + } +} + +/***************************************************************************** + * w_copy + *****************************************************************************/ +/** + * Copy data in WORDS. + * + * Note that the addresses of dst and src are not pointers, but integers, 'coz + * in most cases we pass integers into it as parameters. + * + * @param dst Addr of destination. + * @param src Addr of source. + * @param size How many words will be copied. + *****************************************************************************/ +static void w_copy(unsigned int dst, const unsigned int src, int size) +{ + memcpy((void*)(V_MEM_BASE + (dst << 1)), + (void*)(V_MEM_BASE + (src << 1)), + size << 1); +} \ No newline at end of file diff --git a/kernel/elf.c b/kernel/elf.c new file mode 100644 index 0000000..ca50fa2 --- /dev/null +++ b/kernel/elf.c @@ -0,0 +1,28 @@ +#include "type.h" +#include "const.h" +#include "protect.h" +#include "string.h" +#include "proc.h" +#include "global.h" +#include "proto.h" +#include "elf.h" +#include "fs.h" //added by mingxuan 2019-5-23 +#include "vfs.h" + +void read_Ehdr(u32 fd,Elf32_Ehdr *File_Ehdr,u32 offset) +{ + do_vlseek(fd,offset,SEEK_SET); //modified by mingxuan 2019-5-24 + do_vread(fd,(void*)File_Ehdr,sizeof(Elf32_Ehdr)); //modified by mingxuan 2019-5-24 +} + +void read_Phdr(u32 fd,Elf32_Phdr *File_Phdr,u32 offset) +{ + do_vlseek(fd,offset,SEEK_SET); //modified by mingxuan 2019-5-24 + do_vread(fd,(void*)File_Phdr,sizeof(Elf32_Phdr)); //modified by mingxuan 2019-5-24 +} + +void read_Shdr(u32 fd,Elf32_Shdr *File_Shdr,u32 offset) +{ + do_vlseek(fd,offset,SEEK_SET); //modified by mingxuan 2019-5-24 + do_vread(fd,(void*)File_Shdr,sizeof(Elf32_Shdr)); //modified by mingxuan 2019-5-23 +} \ No newline at end of file diff --git a/kernel/exec.c b/kernel/exec.c new file mode 100644 index 0000000..9d6af5a --- /dev/null +++ b/kernel/exec.c @@ -0,0 +1,248 @@ +/********************************************** +* exec.c add by visual 2016.5.23 +*************************************************/ +#include "type.h" +#include "const.h" +#include "protect.h" +#include "string.h" +#include "proc.h" +#include "global.h" +#include "proto.h" +#include "elf.h" +#include "fs.h" //added by mingxuan 2019-5-19 +#include "vfs.h" + + + +static u32 exec_elfcpy(u32 fd,Elf32_Phdr Echo_Phdr,u32 attribute); +static u32 exec_load(u32 fd,const Elf32_Ehdr* Echo_Ehdr,const Elf32_Phdr Echo_Phdr[]); +static int exec_pcb_init(char* path); + + + +/*======================================================================* +* sys_exec add by visual 2016.5.23 +*exec系统调用功能实现部分 +*======================================================================*/ +u32 sys_exec(char *path) +{ + Elf32_Ehdr *Echo_Ehdr = NULL; + Elf32_Phdr *Echo_Phdr = NULL; + u32 addr_lin; + u32 err_temp; + + char* p_reg; //point to a register in the new kernel stack, added by xw, 17/12/11 + + if( 0==path ) + { + disp_color_str("exec: path ERROR!",0x74); + return -1; + } + + /*******************打开文件************************/ + // u32 fd = open(path,"r"); //deleted by mingxuan 2019-5-19 + u32 fd = do_vopen(path, O_RDWR); //deleted by mingxuan 2019-5-19 + + if(fd==-1) + { + //printf("sys_exec open error!\n"); //deleted by mingxuan 2019-5-23 + return -1; + } + // u32 fd = fake_open(path,"r"); //modified by xw, 18/5/30 + + /*************获取elf信息**************/ + Echo_Ehdr = sys_kmalloc(sizeof(Elf32_Ehdr)); + read_Ehdr(fd, Echo_Ehdr, 0); + Echo_Phdr = sys_kmalloc(sizeof(Elf32_Phdr) * Echo_Ehdr->e_phnum); + for (int i = 0 ; i < Echo_Ehdr->e_phnum ; i++) + read_Phdr(fd, Echo_Phdr + i, Echo_Ehdr->e_phoff + i * sizeof(Elf32_Phdr)); + + /*************释放进程内存****************/ + //目前还没有实现 思路是:数据、代码根据text_info和data_info属性决定释放深度,其余内存段可以完全释放 + + /*************根据elf的program复制文件信息**************/ + if(-1==exec_load(fd,Echo_Ehdr,Echo_Phdr)) return -1;//使用了const指针传递 + + /*****************重新初始化该进程的进程表信息(包括LDT)、线性地址布局、进程树属性********************/ + exec_pcb_init(path); + + /***********************代码、数据、堆、栈***************************/ + //代码、数据已经处理,将eip重置即可 + p_proc_current->task.regs.eip = Echo_Ehdr->e_entry;//进程入口线性地址 + p_reg = (char*)(p_proc_current + 1); //added by xw, 17/12/11 + *((u32*)(p_reg + EIPREG - P_STACKTOP)) = p_proc_current->task.regs.eip; //added by xw, 17/12/11 + + //栈 + p_proc_current->task.regs.esp=(u32)p_proc_current->task.memmap.stack_lin_base; //栈地址最高处 + *((u32*)(p_reg + ESPREG - P_STACKTOP)) = p_proc_current->task.regs.esp; //added by xw, 17/12/11 + + for( addr_lin=p_proc_current->task.memmap.stack_lin_base ; addr_lin > p_proc_current->task.memmap.stack_lin_limit ; addr_lin-=num_4K ) + { + err_temp = lin_mapping_phy( addr_lin,//线性地址 //add by visual 2016.5.9 + MAX_UNSIGNED_INT,//物理地址 //edit by visual 2016.5.19 + p_proc_current->task.pid,//进程pid //edit by visual 2016.5.19 + PG_P | PG_USU | PG_RWW,//页目录的属性位 + PG_P | PG_USU | PG_RWW);//页表的属性位 + + if( err_temp!=0 ) + { + disp_color_str("kernel_main Error:lin_mapping_phy",0x74); + return -1; + } + } + //堆 用户还没有申请,所以没有分配,只在PCB表里标示了线性起始位置 + + real_close(fd); //added by mingxuan 2019-5-23 + if (Echo_Ehdr != NULL) + sys_free(Echo_Ehdr); + if (Echo_Phdr != NULL) + sys_free(Echo_Phdr); + + //disp_color_str("\n[exec success:",0x72);//灰底绿字 + //disp_color_str(path,0x72);//灰底绿字 + //disp_color_str("]",0x72);//灰底绿字 + return 0; +} + +/*======================================================================* +* exec_elfcpy add by visual 2016.5.23 +*复制elf中program到内存中 +*======================================================================*/ +static u32 exec_elfcpy(u32 fd,Elf32_Phdr Echo_Phdr,u32 attribute) // 这部分代码将来要移动到exec.c文件中,包括下面exec()中的一部分 +{ + u32 lin_addr = Echo_Phdr.p_vaddr; + u32 lin_limit = Echo_Phdr.p_vaddr + Echo_Phdr.p_memsz; + u32 file_offset = Echo_Phdr.p_offset; + u32 file_limit = Echo_Phdr.p_offset + Echo_Phdr.p_filesz; + char ch; + //u32 pde_addr_phy = get_pde_phy_addr(p_proc_current->task.pid); //页目录物理地址 //delete by visual 2016.5.19 + //u32 addr_phy = do_malloc(Echo_Phdr.p_memsz);//申请物理内存 //delete by visual 2016.5.19 + for( ; lin_addrtask.pid,PG_P | PG_USU | PG_RWW/*说明*/,attribute);//说明:PDE属性尽量为读写,因为它要映射1024个物理页,可能既有数据,又有代码 //edit by visual 2016.5.19 + if( file_offsete_phnum ) + { + disp_color_str("exec_load: elf ERROR!",0x74); + return -1; + } + + //我们还不能确定elf中一共能有几个program,但就目前我们查看过的elf文件中,只出现过两中program,一种.text(R-E)和一种.data(RW-) + for( ph_num=0; ph_nume_phnum ; ph_num++ ) + { + if( 0==Echo_Phdr[ph_num].p_memsz ) + {//最后一个program + break; + } + if( Echo_Phdr[ph_num].p_flags == 0x5 || Echo_Phdr[ph_num].p_flags == 0x4) //101,只读 + {//.text + exec_elfcpy(fd,Echo_Phdr[ph_num],PG_P | PG_USU | PG_RWR);//进程代码段 + p_proc_current->task.memmap.text_lin_base = Echo_Phdr[ph_num].p_vaddr; + p_proc_current->task.memmap.text_lin_limit = Echo_Phdr[ph_num].p_vaddr + Echo_Phdr[ph_num].p_memsz; + } + else if(Echo_Phdr[ph_num].p_flags == 0x6)//110,读写 + {//.data + exec_elfcpy(fd,Echo_Phdr[ph_num],PG_P | PG_USU | PG_RWW);//进程数据段 + p_proc_current->task.memmap.data_lin_base = Echo_Phdr[ph_num].p_vaddr; + p_proc_current->task.memmap.data_lin_limit = Echo_Phdr[ph_num].p_vaddr + Echo_Phdr[ph_num].p_memsz; + } + else + { + disp_color_str("exec_load: unKnown elf'program!",0x74); + return -1; + } + } + return 0; +} + + +/*======================================================================* +* exec_init add by visual 2016.5.23 +* 重新初始化寄存器和特权级、线性地址布局 +*======================================================================*/ +static int exec_pcb_init(char* path) +{ + char* p_regs; //point to registers in the new kernel stack, added by xw, 17/12/11 + + //名称 状态 特权级 寄存器 + strcpy(p_proc_current->task.p_name, path); //名称 + p_proc_current->task.stat = READY; //状态 + p_proc_current->task.ldts[0].attr1 = DA_C | PRIVILEGE_USER << 5;//特权级修改为用户级 + p_proc_current->task.ldts[1].attr1 = DA_DRW | PRIVILEGE_USER << 5;//特权级修改为用户级 + p_proc_current->task.regs.cs = ((8 * 0) & SA_RPL_MASK & SA_TI_MASK)| SA_TIL | RPL_USER; + p_proc_current->task.regs.ds = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK)| SA_TIL | RPL_USER; + p_proc_current->task.regs.es = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK)| SA_TIL | RPL_USER; + p_proc_current->task.regs.fs = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK)| SA_TIL | RPL_USER; + p_proc_current->task.regs.ss = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK)| SA_TIL | RPL_USER; + p_proc_current->task.regs.gs = (SELECTOR_KERNEL_GS & SA_RPL_MASK)| RPL_USER; + p_proc_current->task.regs.eflags = 0x202; /* IF=1,bit2 永远是1 */ + + /***************copy registers data****************************/ + //copy registers data to the bottom of the new kernel stack + //added by xw, 17/12/11 + p_regs = (char*)(p_proc_current + 1); + p_regs -= P_STACKTOP; + memcpy(p_regs, (char*)p_proc_current, 18 * 4); + + //进程表线性地址布局部分,text、data已经在前面初始化了 + p_proc_current->task.memmap.vpage_lin_base = VpageLinBase; //保留内存基址 + p_proc_current->task.memmap.vpage_lin_limit = VpageLinBase; //保留内存界限 + p_proc_current->task.memmap.heap_lin_base = HeapLinBase; //堆基址 + p_proc_current->task.memmap.heap_lin_limit = HeapLinBase; //堆界限 + p_proc_current->task.memmap.stack_child_limit = StackLinLimitMAX; //add by visual 2016.5.27 + p_proc_current->task.memmap.stack_lin_base = StackLinBase; //栈基址 + p_proc_current->task.memmap.stack_lin_limit = StackLinBase - 0x4000; //栈界限(使用时注意栈的生长方向) + p_proc_current->task.memmap.arg_lin_base = ArgLinBase; //参数内存基址 + p_proc_current->task.memmap.arg_lin_limit = ArgLinBase; //参数内存界限 + p_proc_current->task.memmap.kernel_lin_base = KernelLinBase; //内核基址 + p_proc_current->task.memmap.kernel_lin_limit = KernelLinBase + KernelSize; //内核大小初始化为8M + + //进程树属性,只要改两项,其余不用改 + //p_proc_current->task.info.type = TYPE_PROCESS; //当前是进程还是线程 + //p_proc_current->task.info.real_ppid = -1; //亲父进程,创建它的那个进程 + //p_proc_current->task.info.ppid = -1; //当前父进程 + //p_proc_current->task.info.child_p_num = 0; //子进程数量 + //p_proc_current->task.info.child_process[NR_CHILD_MAX];//子进程列表 + //p_proc_current->task.info.child_t_num = 0; //子线程数量 + //p_proc_current->task.info.child_thread[NR_CHILD_MAX];//子线程列表 + p_proc_current->task.info.text_hold = 1; //是否拥有代码 + p_proc_current->task.info.data_hold = 1; //是否拥有数据 + + return 0; +} + + diff --git a/kernel/fat32.c b/kernel/fat32.c new file mode 100644 index 0000000..19559b7 --- /dev/null +++ b/kernel/fat32.c @@ -0,0 +1,910 @@ +/********************************************************** +* fat32.c //added by mingxuan 2019-5-17 +***********************************************************/ + +#include "fat32.h" +#include "type.h" +#include "const.h" +#include "protect.h" +#include "string.h" +#include "proc.h" +#include "tty.h" +#include "console.h" +#include "global.h" +#include "proto.h" +#include "fs_const.h" +#include "hd.h" +#include "fs.h" +#include "fs_misc.h" +#include "string.h" +#include "stdio.h" + +extern DWORD FAT_END; +extern DWORD TotalSectors; +extern WORD Bytes_Per_Sector; +extern BYTE Sectors_Per_Cluster; +extern WORD Reserved_Sector; +extern DWORD Sectors_Per_FAT; +extern UINT Position_Of_RootDir; +extern UINT Position_Of_FAT1; +extern UINT Position_Of_FAT2; +extern struct file_desc f_desc_table[NR_FILE_DESC]; +CHAR VDiskPath[256]={0}; +CHAR cur_path[256]={0}; +u8* buf; +STATE state; +File f_desc_table_fat[NR_FILE_DESC]; + +static void load_disk(); +static void mkfs_fat(); + +STATE DeleteDir(const char *dirname) +{ + CHAR fullpath[256]={0}; + CHAR parent[256]={0}; + CHAR name[256]={0}; + DWORD parentCluster=0,startCluster=0; + UINT tag=0; + STATE state; + + ToFullPath((PCHAR)dirname,fullpath); + GetParentFromPath(fullpath,parent); + GetNameFromPath(fullpath,name); + + state=IsFile(fullpath,&tag); + if(state!=OK) + { + return state; + } + if(tag==F) + { + return WRONGPATH; + } + state=PathToCluster(parent,&parentCluster); + if(state!=OK) + { + return state; + } + state=ClearRecord(parentCluster,name,&startCluster); + if(state!=OK) + { + return state; + } + DeleteAllRecord(startCluster); + return OK; +} + +STATE CreateDir(const char *dirname) +{ + Record record; + CHAR fullname[256]={0}; + CHAR name[256]={0}; + CHAR parent[256]={0}; + DWORD parentCluster=0,startCluster=0,sectorIndex=0,off_in_sector=0; + STATE state; + + ToFullPath((PCHAR)dirname,fullname); + GetNameFromPath(fullname,name); + GetParentFromPath(fullname,parent); + state=PathToCluster(parent,&parentCluster); + if(state!=OK) + { + return state;//找不到路径 + } + state=FindSpaceInDir(parentCluster,name,§orIndex,&off_in_sector); + if(state!=OK) + { + return state;//虚拟磁盘空间不足 + } + state=FindClusterForDir(&startCluster); + if(state!=OK) + { + return state;//虚拟磁盘空间不足 + } + CreateRecord(name,0x10,startCluster,0,&record); + WriteRecord(record,sectorIndex,off_in_sector); + WriteFAT(1,&startCluster);//写FAT + CreateRecord(".",0x10,startCluster,0,&record);//准备目录项.的数据 + sectorIndex=Reserved_Sector+2*Sectors_Per_FAT+(startCluster-2)*Sectors_Per_Cluster; + WriteRecord(record,sectorIndex,0);//写.目录项 + CreateRecord("..",0x10,parentCluster,0,&record);//准备目录项..的数据 + WriteRecord(record,sectorIndex,sizeof(Record));//写..目录项 + //fflush(fp); + return OK; +} + +STATE OpenDir(const char* dirname) +{ + DWORD parentCluster=0; + CHAR fullpath[256]={0},parent[256]={0},name[256]={0}; + Record record; + STATE state; + + if(strcmp(dirname,".")==0) + { + return OK; + }else if(strcmp(dirname,"..")==0||strcmp(dirname,"\\")==0){ + ChangeCurrentPath((PCHAR)dirname); + return OK; + }else{ + if(IsFullPath((PCHAR)dirname)) + { + strcpy(fullpath,(PCHAR)dirname); + GetParentFromPath(fullpath,parent); + if(strlen(parent)==0)//说明dirname是根目录 + { + memset(cur_path,0,sizeof(cur_path)); + strcpy(cur_path,fullpath); + cur_path[strlen(cur_path)]='\\'; + return OK; + } + GetNameFromPath(fullpath,name); + }else{ + MakeFullPath(cur_path,(PCHAR)dirname,fullpath); + strcpy(parent,cur_path); + strcpy(name,(PCHAR)dirname); + } + state=PathToCluster(parent,&parentCluster); + if(state!=OK) + { + return state; + } + state=ReadRecord(parentCluster,name,&record,NULL,NULL); + if(state!=OK) + { + return state; + } + if(record.proByte==(BYTE)0x10) + { + strcpy(cur_path,fullpath); + return OK; + }else{ + return WRONGPATH; + } + } + return OK; +} + + +STATE ReadFile(int fd,void *buf, int length) +{ + int size = 0; + PBYTE sector=NULL; + DWORD curSectorIndex=0,nextSectorIndex=0,off_in_sector=0,free_in_sector=0,readsize=0; + UINT isLastSector=0,tag=0; + PFile pfile = p_proc_current->task.filp[fd] ->fd_node.fd_file; + + if(pfile->flag!=R && pfile->flag!=RW && pfile->flag!=(RW|C)) //modified by mingxuan 2019-5-18 + { + return ACCESSDENIED; + } + + disp_str("read:"); + if(pfile->off>=pfile->size) + { + return 0; + } + sector = (PBYTE)K_PHY2LIN(sys_kmalloc(Bytes_Per_Sector*sizeof(BYTE))); + if(sector==NULL) + { + return SYSERROR; + } + GetFileOffset(pfile,&curSectorIndex,&off_in_sector,&isLastSector); + do + { + if(isLastSector)//当前的扇区是该文件的最后一个扇区 + { + if(pfile->size%Bytes_Per_Sector==0) + { + free_in_sector=Bytes_Per_Sector-off_in_sector; + }else{ + free_in_sector=pfile->size%Bytes_Per_Sector-off_in_sector;//最后一个扇区的剩余量 + } + tag=1;//置跳出标志 + }else{ + free_in_sector=Bytes_Per_Sector-off_in_sector;//本扇区的剩余量 + } + if(free_in_sectoroff+=readsize; + if(tag==1)//最后一个扇区或缓冲区装满了 + { + break; + }else{//缓冲区还没装满并且还没到最后一个扇区 + GetNextSector(pfile,curSectorIndex,&nextSectorIndex,&isLastSector); + curSectorIndex=nextSectorIndex; + off_in_sector=0; + } + }while(1); + sys_free(sector); + //pfile->off = 0; + return size; +} + +STATE WriteFile(int fd, const void *buf, int length) +{ + + PBYTE sector=NULL; + DWORD clusterNum=0; + DWORD curSectorIndex=0,nextSectorIndex=0,off_in_sector=0,free_in_sector=0,off_in_buf=0; + UINT isLastSector=0; + STATE state; + PFile pfile = p_proc_current->task.filp[fd] ->fd_node.fd_file; + //PFile pfile = &f_desc_table_fat[0]; + + //if(pfile->flag!=W) //deleted by mingxuan 2019-5-18 + if(pfile->flag!=W && pfile->flag!=RW && pfile->flag!=(RW|C) ) //modified by mingxuan 2019-5-18 + { + return ACCESSDENIED; + } + + sector = (PBYTE)K_PHY2LIN(sys_kmalloc(Bytes_Per_Sector*sizeof(BYTE))); + if(sector==NULL) + { + return SYSERROR; + } + if(pfile->start==0)//此文件是个空文件原来没有分配簇 + { + state=AllotClustersForEmptyFile(pfile,length);//空间不足无法分配 + if(state!=OK) + { + sys_free(sector); + return state;//虚拟磁盘空间不足 + } + }else{ + if(NeedMoreCluster(pfile,length,&clusterNum)) + { + state=AddCluster(pfile->start,clusterNum);//空间不足 + if(state!=OK) + { + sys_free(sector); + return state;//虚拟磁盘空间不足 + } + } + } + GetFileOffset(pfile,&curSectorIndex,&off_in_sector,&isLastSector); + free_in_sector=Bytes_Per_Sector-off_in_sector; + while(free_in_sectoroff+=free_in_sector; + GetNextSector(pfile,curSectorIndex,&nextSectorIndex,&isLastSector); + curSectorIndex=nextSectorIndex; + free_in_sector=Bytes_Per_Sector; + off_in_sector=0; + } + ReadSector(sector,curSectorIndex); + memcpy(sector+off_in_sector,(void *)buf+off_in_buf,length-off_in_buf); + WriteSector(sector,curSectorIndex); + pfile->off+=length-off_in_buf; + sys_free(sector); + //fflush(fp); + return OK; +} + +STATE CloseFile(int fd) +{ + PFile pfile; + pfile = p_proc_current->task.filp[fd] ->fd_node.fd_file; + DWORD curSectorIndex=0,curClusterIndex=0,nextClusterIndex=0,parentCluster=0; + UINT isLastSector=0; + Record record; + DWORD sectorIndex=0,off_in_sector=0; + + //p_proc_current->task.filp_fat[fd] = 0; + f_desc_table_fat[fd].flag = 0; + p_proc_current->task.filp[fd]->flag = 0; + p_proc_current->task.filp[fd] = 0; + if(pfile->flag==R) + { + return OK; + }else{ + if(pfile->offsize) + { + GetFileOffset(pfile,&curSectorIndex,NULL,&isLastSector); + if(isLastSector==0) + { + curSectorIndex=(curClusterIndex-Reserved_Sector-2*Sectors_Per_FAT)/Sectors_Per_Cluster+2; + GetNextCluster(curClusterIndex,&nextClusterIndex); + if(nextClusterIndex!=FAT_END)//说明当前簇不是此文件的最后一簇 + { + WriteFAT(1,&curClusterIndex);//把当前簇设置为此文件的最后一簇 + ClearFATs(nextClusterIndex);//清除此文件的多余簇 + } + } + } + PathToCluster(pfile->parent,&parentCluster); + ReadRecord(parentCluster,pfile->name,&record,§orIndex,&off_in_sector); + record.highClusterNum=(WORD)(pfile->start>>16); + record.lowClusterNum=(WORD)(pfile->start&0x0000FFFF); + record.filelength=pfile->off; + WriteRecord(record,sectorIndex,off_in_sector); + } + return OK; +} + +STATE OpenFile(const char *filename,int mode) +{ + + CHAR fullpath[256]={0}; + CHAR parent[256]={0}; + CHAR name[256]={0}; + Record record; + DWORD parentCluster; + STATE state; + + DWORD sectorIndex=0,off_in_sector=0; //added by mingxuan 2019-5-19 + + ToFullPath((PCHAR)filename,fullpath); + GetParentFromPath(fullpath,parent); + GetNameFromPath(fullpath,name); + + state=PathToCluster(parent,&parentCluster); + disp_str("\nstate="); + disp_int(state); + if(state!=OK) + { + return -1; + } + + //added by mingxuan 2019-5-19 + state=FindSpaceInDir(parentCluster,name,§orIndex,&off_in_sector); //检测文件名是否存在 + if(mode & O_CREAT) //如果用户使用了O_CREAT + { + if(state == NAMEEXIST) //文件存在,使用O_CREAT是多余的,继续执行OpenFile即可 + { + disp_str("file exists, O_CREAT is no use!"); + } + else //文件不存在,需要使用O_CREAT,先创建文件,再执行OpenFile + { + CreateRecord(name,0x20,0,0,&record); + WriteRecord(record,sectorIndex,off_in_sector);//写目录项 + } + } + else //用户没有使用O_CREAT + { + if(state != NAMEEXIST) //文件不存在,需要使用O_CREAT,用户没有使用,则报错并返回-1,表示路径有误 + { + disp_str("no file, use O_CREAT!"); + return -1; + } + else{} //文件存在,使用O_CREAT是多余的,继续执行OpenFile即可 + } + //~mingxuan 2019-5-19 + + state=ReadRecord(parentCluster,name,&record,NULL,NULL); + disp_str("state="); + disp_int(state); + if(state!=OK) + { + disp_str("ReadRecord Fail!"); + return -1; + } + + int i; + int fd = -1; + for (i = 3; i < NR_FILES; i++) { + if (p_proc_current->task.filp[i] == 0) { + fd = i; + break; + } + } + + if ((fd < 0) || (fd >= NR_FILES)) { + // panic("filp[] is full (PID:%d)", proc2pid(p_proc_current)); + disp_str("filp[] is full (PID:"); + disp_int(proc2pid(p_proc_current)); + disp_str(")\n"); + return -1; + } + + //找一个未用的文件描述符 + for (i = 0; i < NR_FILE_DESC; i++) + if ((f_desc_table[i].flag == 0)) + break; + if (i >= NR_FILE_DESC) { + disp_str("f_desc_table[] is full (PID:"); + disp_int(proc2pid(p_proc_current)); + disp_str(")\n"); + return -1; + } + + p_proc_current->task.filp[fd] = &f_desc_table[i]; + f_desc_table[i].flag = 1; + + //找一个未用的FILE + for (i = 0; i < NR_FILE_DESC; i++) + if (f_desc_table_fat[i].flag == 0) + break; + if (i >= NR_FILE_DESC) { + disp_str("f_desc_table[] is full (PID:"); + disp_int(proc2pid(p_proc_current)); + disp_str(")\n"); + } + + //以下是给File结构体赋值 + memset(f_desc_table_fat[i].parent,0,sizeof(f_desc_table_fat[i].parent));//初始化parent字段 + memset(f_desc_table_fat[i].name,0,sizeof(f_desc_table_fat[i].name));//初始化name字段 + strcpy(f_desc_table_fat[i].parent,parent); + strcpy(f_desc_table_fat[i].name,name); + f_desc_table_fat[i].start=(record.highClusterNum<<16)+record.lowClusterNum; + f_desc_table_fat[i].off=0; + f_desc_table_fat[i].size=record.filelength; + f_desc_table_fat[i].flag=mode; + // disp_str("flag:"); + // deint(f_desc_table_fat[i].flag); + // disp_str("index:"); + // deint(i); + p_proc_current->task.filp[fd] ->fd_node.fd_file = &f_desc_table_fat[i]; + + return fd; +} + +STATE CreateFile(const char *filename) +{ + Record record; + CHAR fullpath[256]={0}; + CHAR parent[256]={0}; + CHAR name[256]={0}; + DWORD parentCluster=0,sectorIndex=0,off_in_sector=0; + STATE state; + + ToFullPath((PCHAR)filename,fullpath); + GetParentFromPath(fullpath,parent); + GetNameFromPath((PCHAR)filename,name); + state=PathToCluster(parent,&parentCluster); + if(state!=OK) + { + return state;//找不到路径 + } + + state=FindSpaceInDir(parentCluster,name,§orIndex,&off_in_sector); + if(state != OK) { + return state; + } + + CreateRecord(name,0x20,0,0,&record); + WriteRecord(record,sectorIndex,off_in_sector);//写目录项 + return OK; +} + +STATE DeleteFile(const char *filename) +{ + CHAR fullpath[256]={0}; + CHAR parent[256]={0}; + CHAR name[256]={0}; + DWORD parentCluster=0; + DWORD startCluster=0; + UINT tag=0; + STATE state; + + ToFullPath((PCHAR)filename,fullpath); + GetParentFromPath(fullpath,parent); + GetNameFromPath(fullpath,name); + + state=IsFile(fullpath,&tag); + if(state!=OK) + { + return state; + } + if(tag==D) + { + return WRONGPATH; + } + state=PathToCluster(parent,&parentCluster); + if(state!=OK) + { + return state; + } + state=ClearRecord(parentCluster,name,&startCluster); + if(state!=OK) + { + return state; + } + if(startCluster!=0) + { + ClearFATs(startCluster); + } + return OK; +} + +STATE IsFile(PCHAR path,PUINT tag) +{ + CHAR fullpath[256]={0}; + CHAR parent[256]={0}; + CHAR name[256]={0}; + DWORD parentCluster=0; + Record record; + STATE state; + + ToFullPath(path,fullpath); + GetParentFromPath(fullpath,parent); + GetNameFromPath(fullpath,name); + state=PathToCluster(parent,&parentCluster); + if(state!=OK) + { + return state;//找不到路径 + } + state=ReadRecord(parentCluster,name,&record,NULL,NULL); + if(state!=OK) + { + return state;//找不到路径 + } + if(record.proByte==0x10) + { + *tag=D; + }else{ + *tag=F; + } + return OK; +} + +void init_fs_fat() +{ + disp_str("Initializing fat32 file system... \n"); + + buf = (u8*)K_PHY2LIN(sys_kmalloc(FSBUF_SIZE)); + + int fat32_dev = get_fs_dev(PRIMARY_MASTER, FAT32_TYPE); //added by mingxuan 2020-10-27 + + //load_disk(FAT_DEV); // deleted by mingxuan 2020-10-27 + load_disk(fat32_dev); // modified by mingxuan 2020-10-27 + if (TotalSectors == 0) { + mkfs_fat(); + //load_disk(FAT_DEV); //deleted by mingxuan 2020-10-27 + load_disk(fat32_dev); //modified by mingxuan 2020-10-27 + } + int i; + for (i = 0; i < NR_FILE_DESC; ++i) { + f_desc_table_fat[i].flag = 0; + } +} + +static void load_disk(int dev) { + MESSAGE driver_msg; + PCHAR cur="V:\\"; + + driver_msg.type = DEV_READ; + driver_msg.DEVICE = MINOR(dev); + driver_msg.POSITION = SECTOR_SIZE * 1; + driver_msg.BUF = buf; + driver_msg.CNT = SECTOR_SIZE; + driver_msg.PROC_NR = proc2pid(p_proc_current);///TASK_A + + hd_rdwt(&driver_msg); + + memcpy(&Bytes_Per_Sector,buf+11,2); + memcpy(&Sectors_Per_Cluster,buf+13,1); + memcpy(&Reserved_Sector,buf+14,2); + memcpy(&TotalSectors,buf+32,4); + memcpy(&Sectors_Per_FAT,buf+36,4); + Position_Of_RootDir=(Reserved_Sector+Sectors_Per_FAT*2)*Bytes_Per_Sector; + Position_Of_FAT1=Reserved_Sector*Bytes_Per_Sector; + Position_Of_FAT2=(Reserved_Sector+Sectors_Per_FAT)*Bytes_Per_Sector; + strcpy(cur_path,cur); +} + +static void mkfs_fat() { + MESSAGE driver_msg; + + int fat32_dev = get_fs_dev(PRIMARY_MASTER, FAT32_TYPE); //added by mingxuan 2020-10-27 + + /* get the geometry of ROOTDEV */ + struct part_info geo; + driver_msg.type = DEV_IOCTL; + //driver_msg.DEVICE = MINOR(FAT_DEV); //deleted by mingxuan 2020-10-27 + driver_msg.DEVICE = MINOR(fat32_dev); //modified by mingxuan 2020-10-27 + + driver_msg.REQUEST = DIOCTL_GET_GEO; + driver_msg.BUF = &geo; + driver_msg.PROC_NR = proc2pid(p_proc_current); + hd_ioctl(&driver_msg); + + disp_str("dev size: "); + disp_int(geo.size); + disp_str(" sectors\n"); + + TotalSectors = geo.size; + + DWORD jump=0x009058eb;//跳转指令:占3个字节 + DWORD oem[2]={0x4f44534d,0x302e3553};//厂商标志,OS版本号:占8个字节 + //以下是BPB的内容 + WORD bytes_per_sector=512;//每扇区字节数:占2个字节 + WORD sectors_per_cluster=8;//每簇扇区数:占1个字节 + WORD reserved_sector=32;//保留扇区数:占2个字节 + WORD number_of_FAT=2;//FAT数:占1个字节 + BYTE mediaDescriptor=0xF8; + DWORD sectors_per_FAT=(TotalSectors*512-8192)/525312+1;//每FAT所占扇区数,用此公式可以算出来:占4个字节 + DWORD root_cluster_number=2;//根目录簇号:占4个字节 + //以下是扩展BPB内容 + CHAR volumeLabel[11]={'N','O',' ','N','A','M','E',' ',' ',' ',' '};//卷标:占11个字节 + CHAR systemID[8]={'F','A','T','3','2',' ',' ',' '};//系统ID,FAT32系统中一般取为“FAT32”:占8个字节 + //以下是有效结束标志 + DWORD end=0xaa55; + + DWORD media_descriptor[2]={0x0ffffff8,0xffffffff};//FAT介质描述符 + DWORD cluster_tag=0x0fffffff;//文件簇的结束单元标记 + + Record vLabel;//卷标的记录项。 + // DWORD clearSize=sectors_per_cluster*bytes_per_sector-sizeof(Record); + char volumelabel[3] = "MZY"; + + memcpy(buf,&jump,3);//写入跳转指令:占3个字节(其实没有用) + memcpy(buf+3,oem,8);//厂商标志,OS版本号:占8个字节 + //以下是写 BPB + memcpy(buf+11,&bytes_per_sector,2);//每扇区字节数:占2个字节 + memcpy(buf+13,§ors_per_cluster,1);//写入每簇扇区数:占1个字节 + memcpy(buf+14,&reserved_sector,2);//写入保留扇区数:占2个字节 + memcpy(buf+16,&number_of_FAT,1);//写入FAT数:占1个字节 + memcpy(buf+21,&mediaDescriptor,1);//写入媒体描述符 + memcpy(buf+32,&TotalSectors,4);//写入总扇区数 + memcpy(buf+36,§ors_per_FAT,4);//写入每FAT所占扇区数:占4个字节 + memcpy(buf+44,&root_cluster_number,4);//写入根目录簇号:占4个字节 + //以下是写 扩展BPB + memcpy(buf+71,volumeLabel,11);//写卷标:占11个字节 + memcpy(buf+82,systemID,8);//系统ID,FAT32系统中一般取为“FAT32”:占8个字节 + //由于引导代码对于本虚拟系统没有用,故省略 + memcpy(buf+510,&end,2); + //WR_SECT_FAT(buf, 1); //deleted by mingxuan 2020-10-27 + WR_SECT_FAT(fat32_dev, buf, 1); //modified by mingxuan 2020-10-27 + + //初始化FAT + memset(buf,0,SECTOR_SIZE);//写介质描述单元 + memcpy(buf,media_descriptor,8); + memcpy(buf+8,&cluster_tag,4);//写根目录的簇号 + //WR_SECT_FAT(buf, reserved_sector); // deleted by mingxuan 2020-10-27 + WR_SECT_FAT(fat32_dev, buf, reserved_sector); // modified by mingxuan 2020-10-27 + + //初始化根目录 + CreateRecord(volumelabel,0x08,0,0,&vLabel);//准备卷标的目录项的数据 + memset(buf,0,SECTOR_SIZE);//将准备好的记录项数据写入虚拟硬盘 + memcpy(buf,&vLabel,sizeof(Record)); + //WR_SECT_FAT(buf, reserved_sector+2*sectors_per_FAT); // deleted by mingxuan 2020-10-27 + WR_SECT_FAT(fat32_dev, buf, reserved_sector+2*sectors_per_FAT); // modified by mingxuan 2020-10-27 +} + +int rw_sector_fat(int io_type, int dev, u64 pos, int bytes, int proc_nr, void* buf) +{ + MESSAGE driver_msg; + + driver_msg.type = io_type; + driver_msg.DEVICE = MINOR(dev); + driver_msg.POSITION = pos; + driver_msg.CNT = bytes; /// hu is: 512 + driver_msg.PROC_NR = proc_nr; + driver_msg.BUF = buf; + + hd_rdwt(&driver_msg); + return 0; +} + +int rw_sector_sched_fat(int io_type, int dev, int pos, int bytes, int proc_nr, void* buf) +{ + MESSAGE driver_msg; + + driver_msg.type = io_type; + driver_msg.DEVICE = MINOR(dev); + driver_msg.POSITION = pos; + driver_msg.CNT = bytes; /// hu is: 512 + driver_msg.PROC_NR = proc_nr; + driver_msg.BUF = buf; + + hd_rdwt_sched(&driver_msg); + return 0; +} + +int sys_CreateFile(void *uesp) +{ + state=CreateFile((PCHAR)(void *)get_arg(uesp, 1)); + if(state==OK) + { + kprintf(" create file success"); + } + else { + DisErrorInfo(state); + } + + return state; +} + +int sys_DeleteFile(void *uesp) +{ + state=DeleteFile((PCHAR)(void *)get_arg(uesp, 1)); + if(state==OK) + { + kprintf(" delete file success"); + } + else { + DisErrorInfo(state); + } + return state; +} + +int sys_OpenFile(void *uesp) +{ +/* // state=OpenFile(get_arg(uesp, 1), + // get_arg(uesp, 2)); + // if(state==OK) + // { + // kprintf("open file success"); + // } + // else { + // DisErrorInfo(state); + // } + // return state; + state=OpenFile(get_arg(uesp, 1), + get_arg(uesp, 2)); + kprintf(" open file success"); + return state; */ + return 0; +} + +int sys_CloseFile(void *uesp) +{ + state=CloseFile(get_arg(uesp, 1)); + if(state==OK) + { + kprintf(" close file success"); + } + else { + DisErrorInfo(state); + } + return state; +} + +int sys_WriteFile(void *uesp) +{ + state=WriteFile(get_arg(uesp, 1), + (BYTE *)(void *)get_arg(uesp, 2), + get_arg(uesp, 3)); + if(state==OK) + { + kprintf(" write file success"); + } + else { + DisErrorInfo(state); + } + return state; +} + +int sys_ReadFile(void *uesp) +{ + // state=ReadFile(get_arg(uesp, 1), + // get_arg(uesp, 2), + // get_arg(uesp, 3), + // get_arg(uesp, 4)); + // if(state==OK) + // { + // //debug("read file success"); + // debug(" read file success"); + // } + // else { + // DisErrorInfo(state); + // } + // return state; + return 0; +} + +int sys_OpenDir(void *uesp) +{ + // state=OpenDir(get_arg(uesp, 1)); + // if(state==OK) + // { + // //debug("open dir success"); + // debug(" open dir success"); + // } + // else { + // DisErrorInfo(state); + // } + // return state; + return 0; +} + +int sys_CreateDir(void *uesp) +{ + // state=CreateDir(get_arg(uesp, 1)); + // if(state==OK) + // { + // //debug("create dir success"); + // debug(" create dir success"); + // } + // else { + // DisErrorInfo(state); + // } + // return state; + return 0; +} + +int sys_DeleteDir(void *uesp) +{ + // state=DeleteDir(get_arg(uesp, 1)); + // if(state==OK) + // { + // debug("delete dir success"); + // debug(" delete dir success"); + // } + // else { + // DisErrorInfo(state); + // } + // return state; + return 0; +} + +int sys_ListDir(void *uesp) { + + // DArray *array=NULL; + // char *s = get_arg(uesp, 1); + // CHAR temp[256]={0}; + // UINT tag=0; + + // array = InitDArray(10, 10); + // memset(temp, 0, sizeof(temp)); + // if (strlen(s) != 0) + // { + // strcpy(temp,s); + // if(IsFile(temp,&tag)) + // { + // if(tag==1) + // { + // printf("不是目录的路径\n\n"); + // } + // } + // } + // else { + // GetCurrentPath(temp); + // } + // state=ListAll(temp, array); + // if(state==OK) + // { + // DirCheckup(array); + // }else { + // DisErrorInfo(state); + // disp_str("\n"); + // } + // DestroyDArray(array); + return 0; +} + +void DisErrorInfo(STATE state) +{ + if(state==SYSERROR) + { + disp_str(" system error\n"); + } + else if(state==VDISKERROR) + { + disp_str(" disk error\n"); + } + else if(state==INSUFFICIENTSPACE) + { + disp_str(" no much space\n"); + } + else if(state==WRONGPATH) + { + disp_str(" path error\n"); + } + else if(state==NAMEEXIST) + { + disp_str(" name exists\n"); + } + else if(state==ACCESSDENIED) + { + disp_str(" deny access\n"); + } + else + { + disp_str(" unknown error\n"); + } +} + diff --git a/kernel/file.c b/kernel/file.c new file mode 100644 index 0000000..edf6c56 --- /dev/null +++ b/kernel/file.c @@ -0,0 +1,51 @@ +/******************************************** +* file.c //add by visual 2016.5.17 +*目前是虚拟的文件读写 +***********************************************/ + +#include "type.h" +#include "const.h" +#include "protect.h" +#include "string.h" +#include "proc.h" +#include "global.h" +#include "proto.h" + + +// #define BaseOfEchoFilePhyAddr (K_PHY2LIN(0x7e00)) //目前就这一个文件 +// static u32 position=0; + +// /***************************************************** +// * open //add by visual 2016.5.17 +// *目前没有什么用的open +// ******************************************************/ +// // u32 open(char* path,char* mode) +// u32 fake_open(char* path,char* mode) //modified by xw, 18/5/30 +// { +// position = 0; +// return 0; +// } + + +// /****************************************************** +// * read //add by visual 2016.5.17 +// ********************************************************/ +// // u32 read(u32 fd,void* buffer,u32 size) +// u32 fake_read(u32 fd,void* buffer,u32 size) //modified by xw, 18/5/30 +// { +// u32 addr_lin = BaseOfEchoFilePhyAddr + position; +// position += size; +// memcpy(buffer,(void*)addr_lin,size); +// return 0; +// } + +// /****************************************************** +// * seek //add by visual 2016.5.17 +// *******************************************************/ +// // u32 seek(u32 pos) +// u32 fake_seek(u32 pos) //modified by xw, 18/5/30 +// { +// position = pos; +// return 0; +// } + diff --git a/kernel/fork.c b/kernel/fork.c new file mode 100644 index 0000000..f7165b2 --- /dev/null +++ b/kernel/fork.c @@ -0,0 +1,238 @@ +/***************************************************** +* fork.c //add by visual 2016.5.25 +*系统调用fork()功能实现部分sys_fork() +********************************************************/ +#include "type.h" +#include "const.h" +#include "protect.h" +#include "string.h" +#include "proc.h" +#include "global.h" +#include "proto.h" + +static int fork_mem_cpy(u32 ppid,u32 pid); +static int fork_pcb_cpy(PROCESS* p_child); +static int fork_update_info(PROCESS* p_child); + + +/********************************************************** +* sys_fork //add by visual 2016.5.25 +*系统调用sys_fork的具体实现部分 +*************************************************************/ +int sys_fork() +{ + PROCESS* p_child; + char* p_reg; //point to a register in the new kernel stack, added by xw, 17/12/11 + + /*****************申请空白PCB表**********************/ + p_child = alloc_PCB(); + if( 0==p_child ) + { + disp_color_str("PCB NULL,fork faild!",0x74); + return -1; + } + else + { + /****************初始化子进程高端地址页表(内核部分)***********************///这个页表可以复制父进程的! + init_page_pte(p_child->task.pid); //这里面已经填写了该进程的cr3寄存器变量 + + /************复制父进程的PCB部分内容(保留了自己的标识信息)**************/ + fork_pcb_cpy(p_child); + + /**************复制线性内存,包括堆、栈、代码数据等等***********************/ + fork_mem_cpy(p_proc_current->task.pid,p_child->task.pid); + + /**************更新进程树标识info信息************************/ + fork_update_info(p_child); + + /************修改子进程的名字***************/ + strcpy(p_child->task.p_name,"fork"); // 所有的子进程都叫fork + + /*************子进程返回值在其eax寄存器***************/ + p_child->task.regs.eax = 0;//return child with 0 + p_reg = (char*)(p_child + 1); //added by xw, 17/12/11 + *((u32*)(p_reg + EAXREG - P_STACKTOP)) = p_child->task.regs.eax; //added by xw, 17/12/11 + + /****************用户进程数+1****************************/ + u_proc_sum += 1; + + disp_color_str("[fork success:",0x72); + disp_color_str(p_proc_current->task.p_name,0x72); + disp_color_str("]",0x72); + + //anything child need is prepared now, set its state to ready. added by xw, 17/12/11 + p_child->task.stat = READY; + } + return p_child->task.pid; +} + + +/********************************************************** +* fork_mem_cpy //add by visual 2016.5.24 +*复制父进程的一系列内存数据 +*************************************************************/ +static int fork_mem_cpy(u32 ppid,u32 pid) +{ + u32 addr_lin; + //复制代码,代码是共享的,直接将物理地址挂载在子进程的页表上 + for(addr_lin = p_proc_current->task.memmap.text_lin_base ; addr_lin < p_proc_current->task.memmap.text_lin_limit ; addr_lin+=num_4K ) + { + lin_mapping_phy(addr_lin,//线性地址 + get_page_phy_addr(ppid,addr_lin),//物理地址,为MAX_UNSIGNED_INT时,由该函数自动分配物理内存 + pid,//要挂载的进程的pid,子进程的pid + PG_P | PG_USU | PG_RWW,//页目录属性,一般都为可读写 + PG_P | PG_USU | PG_RWR);//页表属性,代码是只读的 + } + //复制数据,数据不共享,子进程需要申请物理地址,并复制过来 + for(addr_lin = p_proc_current->task.memmap.data_lin_base ; addr_lin < p_proc_current->task.memmap.data_lin_limit ; addr_lin+=num_4K ) + { + lin_mapping_phy(SharePageBase,0,ppid,PG_P | PG_USU | PG_RWW,0);//使用前必须清除这个物理页映射 + lin_mapping_phy(SharePageBase,MAX_UNSIGNED_INT,ppid,PG_P | PG_USU | PG_RWW,PG_P | PG_USU | PG_RWW);//利用父进程的共享页申请物理页 + memcpy((void*)SharePageBase,(void*)(addr_lin&0xFFFFF000),num_4K);//将数据复制到物理页上,注意这个地方是强制一页一页复制的 + lin_mapping_phy(addr_lin,//线性地址 + get_page_phy_addr(ppid,SharePageBase),//物理地址,获取共享页的物理地址,填进子进程页表 + pid,//要挂载的进程的pid,子进程的pid + PG_P | PG_USU | PG_RWW,//页目录属性,一般都为可读写 + PG_P | PG_USU | PG_RWW);//页表属性,数据是可读写的 + } + //复制保留内存,保留内存不共享,子进程需要申请物理地址,并复制过来 + for(addr_lin = p_proc_current->task.memmap.vpage_lin_base ; addr_lin < p_proc_current->task.memmap.vpage_lin_limit ; addr_lin+=num_4K ) + { + lin_mapping_phy(SharePageBase,0,ppid,PG_P | PG_USU | PG_RWW,0);//使用前必须清除这个物理页映射 + lin_mapping_phy(SharePageBase,MAX_UNSIGNED_INT,ppid,PG_P | PG_USU | PG_RWW,PG_P | PG_USU | PG_RWW);//利用父进程的共享页申请物理页 + memcpy((void*)SharePageBase,(void*)(addr_lin&0xFFFFF000),num_4K);//将数据复制到物理页上,注意这个地方是强制一页一页复制的 + lin_mapping_phy(addr_lin,//线性地址 + get_page_phy_addr(ppid,SharePageBase),//物理地址,获取共享页的物理地址,填进子进程页表 + pid,//要挂载的进程的pid,子进程的pid + PG_P | PG_USU | PG_RWW,//页目录属性,一般都为可读写 + PG_P | PG_USU | PG_RWW);//页表属性,保留内存是可读写的 + } + + //复制堆,堆不共享,子进程需要申请物理地址,并复制过来 + for(addr_lin = p_proc_current->task.memmap.heap_lin_base ; addr_lin < p_proc_current->task.memmap.heap_lin_limit ; addr_lin+=num_4K ) + { + lin_mapping_phy(SharePageBase,0,ppid,PG_P | PG_USU | PG_RWW,0);//使用前必须清除这个物理页映射 + lin_mapping_phy(SharePageBase,MAX_UNSIGNED_INT,ppid,PG_P | PG_USU | PG_RWW,PG_P | PG_USU | PG_RWW);//利用父进程的共享页申请物理页 + memcpy((void*)SharePageBase,(void*)(addr_lin&0xFFFFF000),num_4K);//将数据复制到物理页上,注意这个地方是强制一页一页复制的 + lin_mapping_phy(addr_lin,//线性地址 + get_page_phy_addr(ppid,SharePageBase),//物理地址,获取共享页的物理地址,填进子进程页表 + pid,//要挂载的进程的pid,子进程的pid + PG_P | PG_USU | PG_RWW,//页目录属性,一般都为可读写 + PG_P | PG_USU | PG_RWW);//页表属性,堆是可读写的 + } + + //复制栈,栈不共享,子进程需要申请物理地址,并复制过来(注意栈的复制方向) + for(addr_lin = p_proc_current->task.memmap.stack_lin_base ; addr_lin > p_proc_current->task.memmap.stack_lin_limit ; addr_lin-=num_4K ) + { + lin_mapping_phy(SharePageBase,0,ppid,PG_P | PG_USU | PG_RWW,0);//使用前必须清除这个物理页映射 + lin_mapping_phy(SharePageBase,MAX_UNSIGNED_INT,ppid,PG_P | PG_USU | PG_RWW,PG_P | PG_USU | PG_RWW);//利用父进程的共享页申请物理页 + memcpy((void*)SharePageBase,(void*)(addr_lin&0xFFFFF000),num_4K);//将数据复制到物理页上,注意这个地方是强制一页一页复制的 + lin_mapping_phy(addr_lin,//线性地址 + get_page_phy_addr(ppid,SharePageBase),//物理地址,获取共享页的物理地址,填进子进程页表 + pid,//要挂载的进程的pid,子进程的pid + PG_P | PG_USU | PG_RWW,//页目录属性,一般都为可读写 + PG_P | PG_USU | PG_RWW);//页表属性,栈是可读写的 + } + + //复制参数区,参数区不共享,子进程需要申请物理地址,并复制过来 + for(addr_lin = p_proc_current->task.memmap.arg_lin_base ; addr_lin < p_proc_current->task.memmap.arg_lin_limit ; addr_lin+=num_4K ) + { + lin_mapping_phy(SharePageBase,0,ppid,PG_P | PG_USU | PG_RWW,0);//使用前必须清除这个物理页映射 + lin_mapping_phy(SharePageBase,MAX_UNSIGNED_INT,ppid,PG_P | PG_USU | PG_RWW,PG_P | PG_USU | PG_RWW);//利用父进程的共享页申请物理页 + memcpy((void*)SharePageBase,(void*)(addr_lin&0xFFFFF000),num_4K);//将数据复制到物理页上,注意这个地方是强制一页一页复制的 + lin_mapping_phy(addr_lin,//线性地址 + get_page_phy_addr(ppid,SharePageBase),//物理地址,获取共享页的物理地址,填进子进程页表 + pid,//要挂载的进程的pid,子进程的pid + PG_P | PG_USU | PG_RWW,//页目录属性,一般都为可读写 + PG_P | PG_USU | PG_RWW);//页表属性,参数区是可读写的 + } + return 0; +} + +/********************************************************** +* fork_pcb_cpy //add by visual 2016.5.26 +*复制父进程PCB表,但是又马上恢复了子进程的标识信息 +*************************************************************/ +static int fork_pcb_cpy(PROCESS* p_child) +{ + int pid; + u32 eflags,selector_ldt,cr3_child; + char* p_reg; //point to a register in the new kernel stack, added by xw, 17/12/11 +// char* esp_save_int, esp_save_context; //It's not what you want! damn it. + char *esp_save_int, *esp_save_context; //use to save corresponding field in child's PCB. + + //暂存标识信息 + pid = p_child->task.pid; + + //eflags = p_child->task.regs.eflags; + p_reg = (char*)(p_child + 1); //added by xw, 17/12/11 + eflags = *((u32*)(p_reg + EFLAGSREG - P_STACKTOP)); //added by xw, 17/12/11 + + selector_ldt = p_child->task.ldt_sel; + cr3_child = p_child->task.cr3; + + //复制PCB内容 + //modified by xw, 17/12/11 + //modified begin + //*p_child = *p_proc_current; + + //esp_save_int and esp_save_context must be saved, because the child and the parent + //use different kernel stack! And these two are importent to the child's initial running. + //Added by xw, 18/4/21 + esp_save_int = p_child->task.esp_save_int; + esp_save_context = p_child->task.esp_save_context; + p_child->task = p_proc_current->task; + //note that syscalls can be interrupted now! the state of child can only be setted + //READY when anything else is well prepared. if an interruption happens right here, + //an error will still occur. + p_child->task.stat = IDLE; + p_child->task.esp_save_int = esp_save_int; //esp_save_int of child must be restored!! + p_child->task.esp_save_context = esp_save_context; //same above +// p_child->task.esp_save_context = (char*)(p_child + 1) - P_STACKTOP - 4 * 6; + memcpy(((char*)(p_child + 1) - P_STACKTOP), ((char*)(p_proc_current + 1) - P_STACKTOP), 18 * 4); + //modified end + + //恢复标识信息 + p_child->task.pid = pid; + + //p_child->task.regs.eflags = eflags; + p_reg = (char*)(p_child + 1); //added by xw, 17/12/11 + *((u32*)(p_reg + EFLAGSREG - P_STACKTOP)) = eflags; //added by xw, 17/12/11 + + p_child->task.ldt_sel = selector_ldt; + p_child->task.cr3 = cr3_child; + return 0; +} + + + +/********************************************************** +* fork_update_info //add by visual 2016.5.26 +*更新父进程和子进程的进程树标识info +*************************************************************/ +static int fork_update_info(PROCESS* p_child) +{ + /************更新父进程的info***************/ + //p_proc_current->task.info.type; //当前是进程还是线程 + //p_proc_current->task.info.real_ppid; //亲父进程,创建它的那个进程 + //p_proc_current->task.info.ppid; //当前父进程 + p_proc_current->task.info.child_p_num += 1; //子进程数量 + p_proc_current->task.info.child_process[p_proc_current->task.info.child_p_num-1] = p_child->task.pid;//子进程列表 + //p_proc_current->task.info.child_t_num; //子线程数量 + //p_proc_current->task.info.child_thread[NR_CHILD_MAX];//子线程列表 + //p_proc_current->task.text_hold; //是否拥有代码 + //p_proc_current->task.data_hold; //是否拥有数据 + + /************更新子进程的info***************/ + p_child->task.info.type = p_proc_current->task.info.type; //当前进程属性跟父进程一样 + p_child->task.info.real_ppid = p_proc_current->task.pid; //亲父进程,创建它的那个进程 + p_child->task.info.ppid = p_proc_current->task.pid; //当前父进程 + p_child->task.info.child_p_num = 0; //子进程数量 + //p_child->task.info.child_process[NR_CHILD_MAX] = pid;//子进程列表 + p_child->task.info.child_t_num = 0; //子线程数量 + //p_child->task.info.child_thread[NR_CHILD_MAX];//子线程列表 + p_child->task.info.text_hold = 0; //是否拥有代码,子进程不拥有代码 + p_child->task.info.data_hold = 1; //是否拥有数据,子进程拥有数据 + + return 0; +} \ No newline at end of file diff --git a/kernel/fs.c b/kernel/fs.c new file mode 100644 index 0000000..183f7f0 --- /dev/null +++ b/kernel/fs.c @@ -0,0 +1,1560 @@ +/// zcr copy from chapter9/d fs/main.c and modified it. + +#include "type.h" +#include "const.h" +#include "protect.h" +#include "string.h" +#include "proc.h" +#include "global.h" +#include "proto.h" +#include "fs_const.h" +#include "hd.h" +#include "fs.h" +#include "fs_misc.h" +#include "stdio.h" +#include "assert.h" + +//added by xw, 18/8/28 +/* data */ +static struct inode* root_inode; + +//static struct file_desc f_desc_table[NR_FILE_DESC]; //deleted by mingxuan 2020-10-30 +extern struct file_desc f_desc_table[NR_FILE_DESC]; //modified by mingxuan 2020-10-30 + +static struct inode inode_table[NR_INODE]; + +//static struct super_block super_block[NR_SUPER_BLOCK]; //deleted by mingxuan 2020-10-30 +extern struct super_block super_block[NR_SUPER_BLOCK]; //modified by mingxuan 2020-10-30 + +/* functions */ +static void mkfs(); +//static void read_super_block(int dev); + void read_super_block(int dev); // modified by mingxuan 2020-10-30 +//static struct super_block* get_super_block(int dev); + struct super_block* get_super_block(int dev); // modified by mingxuan 2020-10-30 + +static int do_open(MESSAGE *fs_msg); +static int do_close(int fd); +static int do_lseek(MESSAGE *fs_msg); +static int do_rdwt(MESSAGE *fs_msg); +static int do_unlink(MESSAGE *fs_msg); + +int real_open(const char *pathname, int flags); //modified by mingxuan 2019-5-17 +int real_close(int fd); //modified by mingxuan 2019-5-17 +int real_read(int fd, void *buf, int count); //注意:buf的类型被修改成char //modified by mingxuan 2019-5-17 +int real_write(int fd, const void *buf, int count); //注意:buf的类型被修改成char //modified by mingxuan 2019-5-17 +int real_unlink(const char *pathname); //modified by mingxuan 2019-5-17 +int real_lseek(int fd, int offset, int whence); //modified by mingxuan 2019-5-17 + +static int rw_sector(int io_type, int dev, u64 pos, int bytes, int proc_nr, void* buf); +static int rw_sector_sched(int io_type, int dev, int pos, int bytes, int proc_nr, void* buf); + +static int strip_path(char * filename, const char * pathname, struct inode** ppinode); +static int search_file(char *path); +static struct inode* create_file(char *path, int flags); +static struct inode* get_inode(int dev, int num); +static struct inode* get_inode_sched(int dev, int num); +static struct inode* new_inode(int dev, int inode_nr, int start_sect); +static void put_inode(struct inode *pinode); +static void sync_inode(struct inode *p); +static void new_dir_entry(struct inode *dir_inode,int inode_nr,char *filename); +static int alloc_imap_bit(int dev); +static int alloc_smap_bit(int dev, int nr_sects_to_alloc); + +static int memcmp(const void *s1, const void *s2, int n); +//~xw +int get_fs_dev(int drive, int fs_type); // added by mingxuan 2020-10-27 + + +// added by mingxuan 2020-10-27 +int get_fs_dev(int drive, int fs_type) +{ + + int i=0; + for(i=0; i < NR_PRIM_PER_DRIVE; i++) + { + if(hd_info[drive].primary[i].fs_type == fs_type) + return ((DEV_HD << MAJOR_SHIFT) | i); + } + + //added by mingxuan 2020-10-29 + for(i=0; i < NR_SUB_PER_DRIVE; i++) + { + if(hd_info[drive].logical[i].fs_type == fs_type) + return ((DEV_HD << MAJOR_SHIFT) | (i + MINOR_hd1a)); // logic的下标i加上hd1a才是该逻辑分区的次设备号 + } + return 0; +} + +/// zcr added +void init_fs() +{ + + kprintf("Initializing file system... "); + + int i; + for (i = 0; i < NR_INODE; i++) + memset(&inode_table[i], 0, sizeof(struct inode)); + struct super_block * sb = super_block; //deleted by mingxuan 2020-10-30 + + int orange_dev = get_fs_dev(PRIMARY_MASTER, ORANGE_TYPE); //added by mingxuan 2020-10-27 + + /* load super block of ROOT */ + read_super_block(orange_dev); // modified by mingxuan 2020-10-27 + sb = get_super_block(orange_dev); // modified by mingxuan 2020-10-27 + + kprintf("Superblock Address:0x%x \n", sb); + + if(sb->magic != MAGIC_V1) { //deleted by mingxuan 2019-5-20 + mkfs(); + kprintf("Make file system Done.\n"); + read_super_block(orange_dev); // modified by mingxuan 2020-10-27 + } + + root_inode = get_inode(orange_dev, ROOT_INODE); // modified by mingxuan 2020-10-27 +} + +/***************************************************************************** + * mkfs + *****************************************************************************/ +/** + * Make a available Orange'S FS in the disk. It will + * - Write a super block to sector 1. + * - Create three special files: dev_tty0, dev_tty1, dev_tty2 + * - Create the inode map + * - Create the sector map + * - Create the inodes of the files + * - Create `/', the root directory + *****************************************************************************/ +static void mkfs() +{ + MESSAGE driver_msg; + int i, j; + + int bits_per_sect = SECTOR_SIZE * 8; /* 8 bits per byte */ + + //local array, to substitute global fsbuf. added by xw, 18/12/27 + char fsbuf[SECTOR_SIZE]; + + int orange_dev = get_fs_dev(PRIMARY_MASTER, ORANGE_TYPE); //added by mingxuan 2020-10-27 + + /* get the geometry of ROOTDEV */ + struct part_info geo; + driver_msg.type = DEV_IOCTL; + //driver_msg.DEVICE = MINOR(ROOT_DEV); // deleted by mingxuan 2020-10-27 + driver_msg.DEVICE = MINOR(orange_dev); // modified by mingxuan 2020-10-27 + driver_msg.REQUEST = DIOCTL_GET_GEO; + driver_msg.BUF = &geo; + driver_msg.PROC_NR = proc2pid(p_proc_current); + // assert(dd_map[MAJOR(ROOT_DEV)].driver_nr != INVALID_DRIVER); + // send_recv(BOTH, dd_map[MAJOR(ROOT_DEV)].driver_nr, &driver_msg); + hd_ioctl(&driver_msg); + + kprintf("dev size: 0x%x sectors\n", geo.size); + + /************************/ + /* super block */ + /************************/ + struct super_block sb; + sb.magic = MAGIC_V1; + sb.nr_inodes = bits_per_sect; + sb.nr_inode_sects = sb.nr_inodes * INODE_SIZE / SECTOR_SIZE; + sb.nr_sects = geo.size; /* partition size in sector */ + sb.nr_imap_sects = 1; + sb.nr_smap_sects = sb.nr_sects / bits_per_sect + 1; + sb.n_1st_sect = 1 + 1 + /* boot sector & super block */ + sb.nr_imap_sects + sb.nr_smap_sects + sb.nr_inode_sects; + sb.root_inode = ROOT_INODE; + sb.inode_size = INODE_SIZE; + + struct inode x; + sb.inode_isize_off= (int)&x.i_size - (int)&x; + sb.inode_start_off= (int)&x.i_start_sect - (int)&x; + sb.dir_ent_size = DIR_ENTRY_SIZE; + + struct dir_entry de; + sb.dir_ent_inode_off = (int)&de.inode_nr - (int)&de; + sb.dir_ent_fname_off = (int)&de.name - (int)&de; + + sb.sb_dev = orange_dev; //added by mingxuan 2020-10-30 + sb.fs_type = ORANGE_TYPE; //added by mingxuan 2020-10-30 + + memset(fsbuf, 0x90, SECTOR_SIZE); + memcpy(fsbuf, &sb, SUPER_BLOCK_SIZE); + + /* write the super block */ + WR_SECT(orange_dev, 1, fsbuf); // modified by mingxuan 2020-10-27 + + kprintf("devbase:0x%x00", (geo.base + 0) * 2); + kprintf(" sb:0x%x00", (geo.base + 1) * 2); + kprintf(" imap:0x%x00", (geo.base + 2) * 2); + kprintf(" smap:0x%x00\n", (geo.base + 2 + sb.nr_imap_sects) * 2); + kprintf(" inodes:0x%x00", (geo.base + 2 + sb.nr_imap_sects + sb.nr_smap_sects) * 2); + kprintf(" 1st_sector:0x%x00\n", (geo.base + sb.n_1st_sect) * 2); + + /************************/ + /* inode map */ + /************************/ + memset(fsbuf, 0, SECTOR_SIZE); + for (i = 0; i < (NR_CONSOLES + 3); i++) //modified by mingxuan 2019-5-22 + fsbuf[0] |= 1 << i; + + + WR_SECT(orange_dev, 2, fsbuf); //modified by mingxuan 2020-10-27 + + /************************/ + /* secter map */ + /************************/ + memset(fsbuf, 0, SECTOR_SIZE); + int nr_sects = NR_DEFAULT_FILE_SECTS + 1; + for (i = 0; i < nr_sects / 8; i++) + fsbuf[i] = 0xFF; + + for (j = 0; j < nr_sects % 8; j++) + fsbuf[i] |= (1 << j); + + WR_SECT(orange_dev, 2 + sb.nr_imap_sects, fsbuf); //modified by mingxuan 2020-10-27 + + /* zeromemory the rest sector-map */ + memset(fsbuf, 0, SECTOR_SIZE); + for (i = 1; i < sb.nr_smap_sects; i++) + WR_SECT(orange_dev, 2 + sb.nr_imap_sects + i, fsbuf); //modified by mingxuan 2020-10-27 + + /* app.tar */ + //added by mingxuan 2019-5-19 + int bit_offset = INSTALL_START_SECTOR - + sb.n_1st_sect + 1; /* sect M <-> bit (M - sb.n_1stsect + 1) */ + int bit_off_in_sect = bit_offset % (SECTOR_SIZE * 8); + int bit_left = INSTALL_NR_SECTORS; + int cur_sect = bit_offset / (SECTOR_SIZE * 8); + RD_SECT(orange_dev, 2 + sb.nr_imap_sects + cur_sect, fsbuf);//modified by mingxuan 2020-10-27 + while (bit_left) { + int byte_off = bit_off_in_sect / 8; + /* this line is ineffecient in a loop, but I don't care */ + fsbuf[byte_off] |= 1 << (bit_off_in_sect % 8); + bit_left--; + bit_off_in_sect++; + if (bit_off_in_sect == (SECTOR_SIZE * 8)) { + WR_SECT(orange_dev, 2 + sb.nr_imap_sects + cur_sect, fsbuf); //modified by mingxuan 2020-10-27 + cur_sect++; + RD_SECT(orange_dev, 2 + sb.nr_imap_sects + cur_sect, fsbuf); //modified by mingxuan 2020-10-27 + bit_off_in_sect = 0; + } + } + WR_SECT(orange_dev, 2 + sb.nr_imap_sects + cur_sect, fsbuf); //modified by mingxuan 2020-10-27 + + /************************/ + /* inodes */ + /************************/ + /* inode of `/' */ + memset(fsbuf, 0, SECTOR_SIZE); + struct inode * pi = (struct inode*)fsbuf; + pi->i_mode = I_DIRECTORY; + + //modified by mingxuan 2019-5-21 + pi->i_size = DIR_ENTRY_SIZE * 5; /* 5 files: (预定义5个文件) + * `.', + * `dev_tty0', `dev_tty1', `dev_tty2','app.tar' + */ + + pi->i_start_sect = sb.n_1st_sect; + pi->i_nr_sects = NR_DEFAULT_FILE_SECTS; + + /* inode of `/dev_tty0~2' */ + for (i = 0; i < NR_CONSOLES; i++) { + pi = (struct inode*)(fsbuf + (INODE_SIZE * (i + 1))); + pi->i_mode = I_CHAR_SPECIAL; + pi->i_size = 0; + pi->i_start_sect = MAKE_DEV(DEV_CHAR_TTY, i); + pi->i_nr_sects = 0; + } + + /* inode of /app.tar */ + //added by mingxuan 2019-5-19 + pi = (struct inode*)(fsbuf + (INODE_SIZE * (NR_CONSOLES + 1))); + pi->i_mode = I_REGULAR; + pi->i_size = INSTALL_NR_SECTORS * SECTOR_SIZE; + pi->i_start_sect = INSTALL_START_SECTOR; + pi->i_nr_sects = INSTALL_NR_SECTORS; + + WR_SECT(orange_dev, 2 + sb.nr_imap_sects + sb.nr_smap_sects, fsbuf); //modified by mingxuan 2020-10-27 + + /************************/ + /* `/' */ + /************************/ + memset(fsbuf, 0, SECTOR_SIZE); + struct dir_entry * pde = (struct dir_entry *)fsbuf; + + pde->inode_nr = 1; + strcpy(pde->name, "."); + + /* dir entries of `/dev_tty0~2' */ + for (i = 0; i < NR_CONSOLES; i++) { + pde++; + pde->inode_nr = i + 2; /* dev_tty0's inode_nr is 2 */ + switch(i) { + case 0: + strcpy(pde->name, "dev_tty0"); + break; + case 1: + strcpy(pde->name, "dev_tty1"); + break; + case 2: + strcpy(pde->name, "dev_tty2"); + break; + } + } + + (++pde)->inode_nr = NR_CONSOLES + 2; //added by mingxuan 2019-5-19 + strcpy(pde->name, INSTALL_FILENAME); //added by mingxuan 2019-5-19 + + //WR_SECT(ROOT_DEV, sb.n_1st_sect, fsbuf); //modified by xw, 18/12/27 //deleted by mingxuan 2020-10-27 + WR_SECT(orange_dev, sb.n_1st_sect, fsbuf); //modified by mingxuan 2020-10-27 +} + +/***************************************************************************** + * rw_sector + *****************************************************************************/ +/** + * R/W a sector via messaging with the corresponding driver. + * + * @param io_type DEV_READ or DEV_WRITE + * @param dev device nr + * @param pos Byte offset from/to where to r/w. + * @param bytes r/w count in bytes. + * @param proc_nr To whom the buffer belongs. + * @param buf r/w buffer. + * + * @return Zero if success. + *****************************************************************************/ +/// zcr: change the "u64 pos" to "int pos" +static int rw_sector(int io_type, int dev, u64 pos, int bytes, int proc_nr, void* buf) +{ + MESSAGE driver_msg; + + driver_msg.type = io_type; + driver_msg.DEVICE = MINOR(dev); + driver_msg.POSITION = pos; + driver_msg.CNT = bytes; /// hu is: 512 + driver_msg.PROC_NR = proc_nr; + driver_msg.BUF = buf; + + hd_rdwt(&driver_msg); + return 0; +} + +//added by xw, 18/8/27 +static int rw_sector_sched(int io_type, int dev, int pos, int bytes, int proc_nr, void* buf) +{ + MESSAGE driver_msg; + + driver_msg.type = io_type; + driver_msg.DEVICE = MINOR(dev); + + driver_msg.POSITION = pos; + driver_msg.CNT = bytes; /// hu is: 512 + driver_msg.PROC_NR = proc_nr; + driver_msg.BUF = buf; + + hd_rdwt_sched(&driver_msg); + return 0; +} +//~xw + +// added by zcr from chapter9/e/lib/open.c and modified it. + +/***************************************************************************** + * open + *****************************************************************************/ +/** + * open/create a file. + * + * @param pathname The full path of the file to be opened/created. + * @param flags O_CREAT, O_RDWR, etc. + * + * @return File descriptor if successful, otherwise -1. + *****************************************************************************/ +//open is a syscall interface now. added by xw, 18/6/18 +// int open(const char *pathname, int flags) +// static int real_open(const char *pathname, int flags) //deleted by mingxuan 2019-5-17 +int real_open(const char *pathname, int flags) //modified by mingxuan 2019-5-17 +{ + //added by xw, 18/8/27 + MESSAGE fs_msg; + + fs_msg.type = OPEN; + fs_msg.PATHNAME = (void*)pathname; + fs_msg.FLAGS = flags; + fs_msg.NAME_LEN = strlen(pathname); + fs_msg.source = proc2pid(p_proc_current); + + int fd = do_open(&fs_msg); + + return fd; +} + +/***************************************************************************** + * do_open + *****************************************************************************/ +/** + * Open a file and return the file descriptor. + * + * @return File descriptor if successful, otherwise a negative error code. + *****************************************************************************/ +/// zcr modified. +static int do_open(MESSAGE *fs_msg) +{ + /*caller_nr is the process number of the */ + int fd = -1; /* return value */ + + char pathname[MAX_PATH]; + + /* get parameters from the message */ + int flags = fs_msg->FLAGS; /* access mode */ + int name_len = fs_msg->NAME_LEN; /* length of filename */ + int src = fs_msg->source; /* caller proc nr. */ + + memcpy((void*)va2la(src, pathname), (void*)va2la(src, fs_msg->PATHNAME), name_len); + pathname[name_len] = 0; + + /* find a free slot in PROCESS::filp[] */ + int i; + for (i = 0; i < NR_FILES; i++) { //modified by mingxuan 2019-5-20 + if (p_proc_current->task.filp[i] == 0) { + fd = i; + break; + } + } + + assert(0 <= fd && fd < NR_FILES); + + /* find a free slot in f_desc_table[] */ + for (i = 0; i < NR_FILE_DESC; i++) + //modified by mingxuan 2019-5-17 + if (f_desc_table[i].flag == 0) + break; + + assert(i < NR_FILE_DESC); + + int inode_nr = search_file(pathname); + struct inode * pin = 0; + if (flags & O_CREAT) { + if (inode_nr) { + return -1; + } + else { + pin = create_file(pathname, flags); + } + } + else { + char filename[MAX_PATH]; + struct inode * dir_inode; + if (strip_path(filename, pathname, &dir_inode) != 0) + return -1; + pin = get_inode(dir_inode->i_dev, inode_nr); //modified by mingxuan 2019-5-20 + } + + if (pin) { + /* connects proc with file_descriptor */ + p_proc_current->task.filp[fd] = &f_desc_table[i]; + + f_desc_table[i].flag = 1; //added by mingxuan 2019-5-17 + + /* connects file_descriptor with inode */ + f_desc_table[i].fd_node.fd_inode = pin; //modified by mingxuan 2019-5-17 + + f_desc_table[i].fd_mode = flags; + f_desc_table[i].fd_pos = 0; + + int imode = pin->i_mode & I_TYPE_MASK; + + if (imode == I_CHAR_SPECIAL) { + // MESSAGE driver_msg; + + // int dev = pin->i_start_sect; + //? + } + else if (imode == I_DIRECTORY) { + if(pin->i_num != ROOT_INODE) { + panic("pin->i_num != ROOT_INODE"); + } + } + else { + if(pin->i_mode != I_REGULAR) { + panic("Panic: pin->i_mode != I_REGULAR"); + } + } + } + else { + return -1; + } + + return fd; +} + +/***************************************************************************** + * create_file + *****************************************************************************/ +/** + * Create a file and return it's inode ptr. + * + * @param[in] path The full path of the new file + * @param[in] flags Attribiutes of the new file + * + * @return Ptr to i-node of the new file if successful, otherwise 0. + * + * @see open() + * @see do_open() + *****************************************************************************/ +static struct inode * create_file(char * path, int flags) +{ + + char filename[MAX_PATH]; + struct inode * dir_inode; + + if (strip_path(filename, path, &dir_inode) != 0) + return 0; + + int inode_nr = alloc_imap_bit(dir_inode->i_dev); + + int free_sect_nr = alloc_smap_bit(dir_inode->i_dev, NR_DEFAULT_FILE_SECTS); + + struct inode *newino = new_inode(dir_inode->i_dev, inode_nr, free_sect_nr); + + new_dir_entry(dir_inode, newino->i_num, filename); + + return newino; +} + +/// zcr copied from ch9/e/fs/misc.c + +/***************************************************************************** + * memcmp + *****************************************************************************/ +/** + * Compare memory areas. + * + * @param s1 The 1st area. + * @param s2 The 2nd area. + * @param n The first n bytes will be compared. + * + * @return an integer less than, equal to, or greater than zero if the first + * n bytes of s1 is found, respectively, to be less than, to match, + * or be greater than the first n bytes of s2. + *****************************************************************************/ +static int memcmp(const void * s1, const void *s2, int n) +{ + if ((s1 == 0) || (s2 == 0)) { /* for robustness */ + return (s1 - s2); + } + + const char * p1 = (const char *)s1; + const char * p2 = (const char *)s2; + int i; + for (i = 0; i < n; i++,p1++,p2++) { + if (*p1 != *p2) { + return (*p1 - *p2); + } + } + return 0; +} + + +/***************************************************************************** + * search_file + *****************************************************************************/ +/** + * Search the file and return the inode_nr. + * + * @param[in] path The full path of the file to search. + * @return Ptr to the i-node of the file if successful, otherwise zero. + * + * @see open() + * @see do_open() + *****************************************************************************/ +static int search_file(char * path) +{ + int i, j; + + char filename[MAX_PATH]; + memset(filename, 0, MAX_FILENAME_LEN); + struct inode * dir_inode; + if (strip_path(filename, path, &dir_inode) != 0) + return 0; + + if (filename[0] == 0) /* path: "/" */ + return dir_inode->i_num; + + /** + * Search the dir for the file. + */ + int dir_blk0_nr = dir_inode->i_start_sect; + int nr_dir_blks = (dir_inode->i_size + SECTOR_SIZE - 1) / SECTOR_SIZE; + int nr_dir_entries = + dir_inode->i_size / DIR_ENTRY_SIZE; /** + * including unused slots + * (the file has been deleted + * but the slot is still there) + */ + int m = 0; + struct dir_entry * pde; + char fsbuf[SECTOR_SIZE]; //local array, to substitute global fsbuf. added by xw, 18/12/27 + for (i = 0; i < nr_dir_blks; i++) { + //RD_SECT_SCHED(dir_inode->i_dev, dir_blk0_nr + i, fsbuf); //modified by xw, 18/12/27 + RD_SECT(dir_inode->i_dev, dir_blk0_nr + i, fsbuf); //modified by mingxuan 2019-5-20 + pde = (struct dir_entry *)fsbuf; + for (j = 0; j < SECTOR_SIZE / DIR_ENTRY_SIZE; j++,pde++) { + if (memcmp(filename, pde->name, MAX_FILENAME_LEN) == 0) + return pde->inode_nr; + if (++m > nr_dir_entries) + break; + } + if (m > nr_dir_entries) /* all entries have been iterated */ + break; + } + + /* file not found */ + return 0; +} + +/***************************************************************************** + * strip_path + *****************************************************************************/ +/** + * Get the basename from the fullpath. + * + * In Orange'S FS v1.0, all files are stored in the root directory. + * There is no sub-folder thing. + * + * This routine should be called at the very beginning of file operations + * such as open(), read() and write(). It accepts the full path and returns + * two things: the basename and a ptr of the root dir's i-node. + * + * e.g. After stip_path(filename, "/blah", ppinode) finishes, we get: + * - filename: "blah" + * - *ppinode: root_inode + * - ret val: 0 (successful) + * + * Currently an acceptable pathname should begin with at most one `/' + * preceding a filename. + * + * Filenames may contain any character except '/' and '\\0'. + * + * @param[out] filename The string for the result. + * @param[in] pathname The full pathname. + * @param[out] ppinode The ptr of the dir's inode will be stored here. + * + * @return Zero if success, otherwise the pathname is not valid. + *****************************************************************************/ +static int strip_path(char * filename, const char * pathname, struct inode** ppinode) +{ + const char * s = pathname; + char * t = filename; + + if (s == 0) + return -1; + + if (*s == '/') + s++; + + while (*s) { /* check each character */ + if (*s == '/') + return -1; + *t++ = *s++; + /* if filename is too long, just truncate it */ + if (t - filename >= MAX_FILENAME_LEN) + break; + } + *t = 0; + + *ppinode = root_inode; + + return 0; +} + +/***************************************************************************** + * read_super_block + *****************************************************************************/ +/** + * Read super block from the given device then write it into a free + * super_block[] slot. + * + * @param dev From which device the super block comes. + *****************************************************************************/ +//static void read_super_block(int dev) +void read_super_block(int dev) //modified by mingxuan 2020-10-30 +{ + int i; + MESSAGE driver_msg; + char fsbuf[SECTOR_SIZE]; //local array, to substitute global fsbuf. added by xw, 18/12/27 + + driver_msg.type = DEV_READ; + driver_msg.DEVICE = MINOR(dev); + driver_msg.POSITION = SECTOR_SIZE * 1; + driver_msg.BUF = fsbuf; + driver_msg.CNT = SECTOR_SIZE; + driver_msg.PROC_NR = proc2pid(p_proc_current);///TASK_A + + hd_rdwt(&driver_msg); + + for (i = 0; i < NR_SUPER_BLOCK; i++) + if (super_block[i].fs_type == ORANGE_TYPE) + break; + if (i == NR_SUPER_BLOCK) + panic("Cannot find orange's superblock!"); + + struct super_block * psb = (struct super_block *)fsbuf; + + super_block[i] = *psb; + + super_block[i].sb_dev = dev; + super_block[i].fs_type = ORANGE_TYPE; //added by mingxuan 2020-10-30 +} + + +/***************************************************************************** + * get_super_block + *****************************************************************************/ +/** + * Get the super block from super_block[]. + * + * @param dev Device nr. + * + * @return Super block ptr. + *****************************************************************************/ +struct super_block * get_super_block(int dev) //modified by mingxuan 2020-10-30 +{ + struct super_block * sb = super_block; + for (; sb < &super_block[NR_SUPER_BLOCK]; sb++){ + if (sb->sb_dev == dev) + return sb; + } + return 0; +} + + + +/***************************************************************************** + * get_inode + *****************************************************************************/ +/** + * Get the inode ptr of given inode nr. A cache -- inode_table[] -- is + * maintained to make things faster. If the inode requested is already there, + * just return it. Otherwise the inode will be read from the disk. + * + * @param dev Device nr. + * @param num I-node nr. + * + * @return The inode ptr requested. + *****************************************************************************/ +static struct inode * get_inode(int dev, int num) +{ + if (num == 0) + return 0; + + struct inode * p; + struct inode * q = 0; + for (p = &inode_table[0]; p < &inode_table[NR_INODE]; p++) { + if (p->i_cnt) { /* not a free slot */ + if ((p->i_dev == dev) && (p->i_num == num)) { + /* this is the inode we want */ + p->i_cnt++; + return p; + } + } + else { /* a free slot */ + if (!q) /* q hasn't been assigned yet */ + q = p; /* q <- the 1st free slot */ + } + } + + if (!q) + panic("the inode table is full"); + + q->i_dev = dev; + q->i_num = num; + q->i_cnt = 1; + + struct super_block * sb = get_super_block(dev); + int blk_nr = 1 + 1 + sb->nr_imap_sects + sb->nr_smap_sects + ((num - 1) / (SECTOR_SIZE / INODE_SIZE)); + char fsbuf[SECTOR_SIZE]; //local array, to substitute global fsbuf. added by xw, 18/12/27 + RD_SECT(dev, blk_nr, fsbuf); //added by xw, 18/12/27 + struct inode * pinode = + (struct inode*)((u8*)fsbuf + + ((num - 1 ) % (SECTOR_SIZE / INODE_SIZE)) + * INODE_SIZE); + q->i_mode = pinode->i_mode; + q->i_size = pinode->i_size; + q->i_start_sect = pinode->i_start_sect; + q->i_nr_sects = pinode->i_nr_sects; + return q; +} + +//added by xw, 18/8/27 +static struct inode * get_inode_sched(int dev, int num) +{ + if (num == 0) + return 0; + + struct inode * p; + struct inode * q = 0; + for (p = &inode_table[0]; p < &inode_table[NR_INODE]; p++) { + if (p->i_cnt) { /* not a free slot */ + if ((p->i_dev == dev) && (p->i_num == num)) { + /* this is the inode we want */ + p->i_cnt++; + return p; + } + } + else { /* a free slot */ + if (!q) /* q hasn't been assigned yet */ + q = p; /* q <- the 1st free slot */ + } + } + + if (!q) + panic("the inode table is full"); + + q->i_dev = dev; + q->i_num = num; + q->i_cnt = 1; + + struct super_block * sb = get_super_block(dev); + int blk_nr = 1 + 1 + sb->nr_imap_sects + sb->nr_smap_sects + ((num - 1) / (SECTOR_SIZE / INODE_SIZE)); + char fsbuf[SECTOR_SIZE]; //local array, to substitute global fsbuf. added by xw, 18/12/27 + RD_SECT_SCHED(dev, blk_nr, fsbuf); //added by xw, 18/12/27 + struct inode * pinode = + (struct inode*)((u8*)fsbuf + + ((num - 1 ) % (SECTOR_SIZE / INODE_SIZE)) + * INODE_SIZE); + q->i_mode = pinode->i_mode; + q->i_size = pinode->i_size; + q->i_start_sect = pinode->i_start_sect; + q->i_nr_sects = pinode->i_nr_sects; + return q; +} + +/***************************************************************************** + * put_inode + *****************************************************************************/ +/** + * Decrease the reference nr of a slot in inode_table[]. When the nr reaches + * zero, it means the inode is not used any more and can be overwritten by + * a new inode. + * + * @param pinode I-node ptr. + *****************************************************************************/ +static void put_inode(struct inode * pinode) +{ + // assert(pinode->i_cnt > 0); + pinode->i_cnt--; +} + +/***************************************************************************** + * sync_inode + *****************************************************************************/ +/** + * Write the inode back to the disk. Commonly invoked as soon as the + * inode is changed. + * + * @param p I-node ptr. + *****************************************************************************/ +static void sync_inode(struct inode * p) +{ + struct inode * pinode; + struct super_block * sb = get_super_block(p->i_dev); + int blk_nr = 1 + 1 + sb->nr_imap_sects + sb->nr_smap_sects + ((p->i_num - 1) / (SECTOR_SIZE / INODE_SIZE)); + char fsbuf[SECTOR_SIZE]; //local array, to substitute global fsbuf. added by xw, 18/12/27 + RD_SECT(p->i_dev, blk_nr, fsbuf); //modified by mingxuan 2019-5-20 + pinode = (struct inode*)((u8*)fsbuf + + (((p->i_num - 1) % (SECTOR_SIZE / INODE_SIZE)) + * INODE_SIZE)); + pinode->i_mode = p->i_mode; + pinode->i_size = p->i_size; + pinode->i_start_sect = p->i_start_sect; + pinode->i_nr_sects = p->i_nr_sects; + WR_SECT(p->i_dev, blk_nr, fsbuf); //modified by mingxuan 2019-5-20 +} + +/// added by zcr (from ch9/e/fs/open.c) +/***************************************************************************** + * new_inode + *****************************************************************************/ +/** + * Generate a new i-node and write it to disk. + * + * @param dev Home device of the i-node. + * @param inode_nr I-node nr. + * @param start_sect Start sector of the file pointed by the new i-node. + * + * @return Ptr of the new i-node. + *****************************************************************************/ +static struct inode * new_inode(int dev, int inode_nr, int start_sect) +{ + //struct inode * new_inode = get_inode_sched(dev, inode_nr); //modified by xw, 18/8/28 + struct inode * new_inode = get_inode(dev, inode_nr); //modified by mingxuan 2019-5-20 + + new_inode->i_mode = I_REGULAR; + new_inode->i_size = 0; + new_inode->i_start_sect = start_sect; + new_inode->i_nr_sects = NR_DEFAULT_FILE_SECTS; + + new_inode->i_dev = dev; + new_inode->i_cnt = 1; + new_inode->i_num = inode_nr; + + /* write to the inode array */ + sync_inode(new_inode); + + return new_inode; +} + +/***************************************************************************** + * new_dir_entry + *****************************************************************************/ +/** + * Write a new entry into the directory. + * + * @param dir_inode I-node of the directory. + * @param inode_nr I-node nr of the new file. + * @param filename Filename of the new file. + *****************************************************************************/ +static void new_dir_entry(struct inode *dir_inode,int inode_nr,char *filename) +{ + /* write the dir_entry */ + int dir_blk0_nr = dir_inode->i_start_sect; + int nr_dir_blks = (dir_inode->i_size + SECTOR_SIZE) / SECTOR_SIZE; + int nr_dir_entries = + dir_inode->i_size / DIR_ENTRY_SIZE; /** + * including unused slots + * (the file has been + * deleted but the slot + * is still there) + */ + int m = 0; + struct dir_entry * pde; + struct dir_entry * new_de = 0; + + int i, j; + char fsbuf[SECTOR_SIZE]; //local array, to substitute global fsbuf. added by xw, 18/12/27 + for (i = 0; i < nr_dir_blks; i++) { + RD_SECT(dir_inode->i_dev, dir_blk0_nr + i, fsbuf); //modified by mingxuan 2019-5-20 + + pde = (struct dir_entry *)fsbuf; + for (j = 0; j < SECTOR_SIZE / DIR_ENTRY_SIZE; j++,pde++) { + if (++m > nr_dir_entries) + break; + + if (pde->inode_nr == 0) { /* it's a free slot */ + new_de = pde; + break; + } + } + if (m > nr_dir_entries ||/* all entries have been iterated or */ + new_de) /* free slot is found */ + break; + } + if (!new_de) { /* reached the end of the dir */ + new_de = pde; + dir_inode->i_size += DIR_ENTRY_SIZE; + } + new_de->inode_nr = inode_nr; + strcpy(new_de->name, filename); + + /* write dir block -- ROOT dir block */ + WR_SECT(dir_inode->i_dev, dir_blk0_nr + i, fsbuf); //added by mingxuan 2019-5-20 + + /* update dir inode */ + sync_inode(dir_inode); +} + + +/***************************************************************************** + * alloc_imap_bit + *****************************************************************************/ +/** + * Allocate a bit in inode-map. + * + * @param dev In which device the inode-map is located. + * + * @return I-node nr. + *****************************************************************************/ +static int alloc_imap_bit(int dev) +{ + int inode_nr = 0; + int i, j, k; + + int imap_blk0_nr = 1 + 1; /* 1 boot sector & 1 super block */ + struct super_block * sb = get_super_block(dev); + + char fsbuf[SECTOR_SIZE]; //local array, to substitute global fsbuf. added by xw, 18/12/27 + + for (i = 0; i < sb->nr_imap_sects; i++) { + RD_SECT(dev, imap_blk0_nr + i, fsbuf); //modified by mingxuan 2019-5-20 + + for (j = 0; j < SECTOR_SIZE; j++) { + if (fsbuf[j] == '\xFF') //modified by xw, 18/12/28 + continue; + + /* skip `1' bits */ + for (k = 0; ((fsbuf[j] >> k) & 1) != 0; k++) {} + + /* i: sector index; j: byte index; k: bit index */ + inode_nr = (i * SECTOR_SIZE + j) * 8 + k; + fsbuf[j] |= (1 << k); + + /* write the bit to imap */ + WR_SECT(dev, imap_blk0_nr + i, fsbuf); //added by mingxuan 2019-5-20 + break; + } + + return inode_nr; + } + + /* no free bit in imap */ + panic("inode-map is probably full.\n"); + + return 0; +} + +/***************************************************************************** + * alloc_smap_bit + *****************************************************************************/ +/** + * Allocate a bit in sector-map. + * + * @param dev In which device the sector-map is located. + * @param nr_sects_to_alloc How many sectors are allocated. + * + * @return The 1st sector nr allocated. + *****************************************************************************/ +static int alloc_smap_bit(int dev, int nr_sects_to_alloc) +{ + /* int nr_sects_to_alloc = NR_DEFAULT_FILE_SECTS; */ + + int i; /* sector index */ + int j; /* byte index */ + int k; /* bit index */ + + struct super_block * sb = get_super_block(dev); + + int smap_blk0_nr = 1 + 1 + sb->nr_imap_sects; + int free_sect_nr = 0; + char fsbuf[SECTOR_SIZE]; //local array, to substitute global fsbuf. added by xw, 18/12/27 + + for (i = 0; i < sb->nr_smap_sects; i++) { /* smap_blk0_nr + i : + current sect nr. */ + //RD_SECT_SCHED(dev, smap_blk0_nr + i, fsbuf); //modified by xw, 18/12/27 + RD_SECT(dev, smap_blk0_nr + i, fsbuf); //modified by mingxuan 2019-5-20 + + /* byte offset in current sect */ + for (j = 0; j < SECTOR_SIZE && nr_sects_to_alloc > 0; j++) { + k = 0; + if (!free_sect_nr) { + /* loop until a free bit is found */ + //if (fsbuf[j] == 0xFF) continue; + if (fsbuf[j] == '\xFF') continue; //modified by xw, 18/12/28 + for (; ((fsbuf[j] >> k) & 1) != 0; k++) {} + free_sect_nr = (i * SECTOR_SIZE + j) * 8 + + k - 1 + sb->n_1st_sect; + } + + for (; k < 8; k++) { /* repeat till enough bits are set */ + // assert(((fsbuf[j] >> k) & 1) == 0); + fsbuf[j] |= (1 << k); + if (--nr_sects_to_alloc == 0) + break; + } + } + + if (free_sect_nr) /* free bit found, write the bits to smap */ + //WR_SECT_SCHED(dev, smap_blk0_nr + i, fsbuf); //modified by xw, 18/12/27 + WR_SECT(dev, smap_blk0_nr + i, fsbuf); //added by mingxuan 2019-5-20 + + if (nr_sects_to_alloc == 0) + break; + } + + // assert(nr_sects_to_alloc == 0); + + return free_sect_nr; +} + +/// zcr added and modified from ch9/e/fs.open.c and close.c +/***************************************************************************** + * close + *****************************************************************************/ +/** + * Close a file descriptor. + * + * @param fd File descriptor. + * + * @return Zero if successful, otherwise -1. + *****************************************************************************/ +//close is a syscall interface now. added by xw, 18/6/18 +// int close(int fd) +//static int real_close(int fd) //deleted by mingxuan 2019-5-17 +int real_close(int fd) //modified by mingxuan 2019-5-17 +{ + return do_close(fd); // terrible(always returns 0) +} + +/***************************************************************************** + * do_close + *****************************************************************************/ +/** + * Handle the message CLOSE. + * + * @return Zero if success. + *****************************************************************************/ +static int do_close(int fd) +{ + put_inode(p_proc_current->task.filp[fd]->fd_node.fd_inode); //modified by mingxuan 2019-5-17 + p_proc_current->task.filp[fd]->fd_node.fd_inode = 0; //modified by mingxuan 2019-5-17 + p_proc_current->task.filp[fd]->flag = 0; //added by mingxuan 2019-5-17 + p_proc_current->task.filp[fd] = 0; + + return 0; +} + +/// zcr copied from ch9/f/fs/read_write.c and modified it. + +/***************************************************************************** + * read + *****************************************************************************/ +/** + * Read from a file descriptor. + * + * @param fd File descriptor. + * @param buf Buffer to accept the bytes read. + * @param count How many bytes to read. + * + * @return On success, the number of bytes read are returned. + * On error, -1 is returned. + *****************************************************************************/ +int real_read(int fd, void *buf, int count) //注意:buf的类型被修改成char //modified by mingxuan 2019-5-17 +{ + //added by xw, 18/8/27 + MESSAGE fs_msg; + + fs_msg.type = READ; + fs_msg.FD = fd; + fs_msg.BUF = buf; + fs_msg.CNT = count; + fs_msg.source = proc2pid(p_proc_current); //added by xw, 18/12/22 + + // send_recv(BOTH, TASK_FS, &msg); + do_rdwt(&fs_msg); + + return fs_msg.CNT; +} + +/***************************************************************************** + * write + *****************************************************************************/ +/** + * Write to a file descriptor. + * + * @param fd File descriptor. + * @param buf Buffer including the bytes to write. + * @param count How many bytes to write. + * + * @return On success, the number of bytes written are returned. + * On error, -1 is returned. + *****************************************************************************/ +int real_write(int fd, const void *buf, int count) //注意:buf的类型被修改成char //modified by mingxuan 2019-5-17 +{ + //added by xw, 18/8/27 + MESSAGE fs_msg; + + fs_msg.type = WRITE; + fs_msg.FD = fd; + fs_msg.BUF = (void*)buf; + fs_msg.CNT = count; + fs_msg.source = proc2pid(p_proc_current); //added by xw, 18/12/22 + + // send_recv(BOTH, TASK_FS, &msg); + /// zcr added + do_rdwt(&fs_msg); + + return fs_msg.CNT; +} + + +/***************************************************************************** + * do_rdwt + *****************************************************************************/ +/** + * Read/Write file and return byte count read/written. + * + * Sector map is not needed to update, since the sectors for the file have been + * allocated and the bits are set when the file was created. + * + * @return How many bytes have been read/written. + *****************************************************************************/ +static int do_rdwt(MESSAGE *fs_msg) +{ + int fd = fs_msg->FD; /**< file descriptor. */ + void * buf = fs_msg->BUF;/**< r/w buffer */ + int len = fs_msg->CNT; /**< r/w bytes */ + + int src = fs_msg->source; /* caller proc nr. */ + + if (!(p_proc_current->task.filp[fd]->fd_mode & O_RDWR)) + return -1; + + int pos = p_proc_current->task.filp[fd]->fd_pos; + + //struct inode * pin = p_proc_current->task.filp[fd]->fd_inode; //deleted by mingxuan 2019-5-17 + struct inode * pin = p_proc_current->task.filp[fd]->fd_node.fd_inode; //modified by mingxuan 2019-5-17 + + int imode = pin->i_mode & I_TYPE_MASK; + + if (imode == I_CHAR_SPECIAL) { + int t = fs_msg->type == READ ? DEV_READ : DEV_WRITE; + fs_msg->type = t; + + //added by mingxuan 2019-5-19 + int dev = pin->i_start_sect; + int nr_tty = MINOR(dev); + if(MAJOR(dev) != 4) { + panic("Error: MAJOR(dev) == 4\n"); + } + + if(fs_msg->type == DEV_READ){ + fs_msg->CNT =tty_read(&tty_table[nr_tty],buf,len); + }else if(fs_msg->type==DEV_WRITE){ + tty_write(&tty_table[nr_tty],buf,len); + } + + return fs_msg->CNT; + } + else { + int pos_end; + if (fs_msg->type == READ) + pos_end = min(pos + len, pin->i_size); + else /* WRITE */ + pos_end = min(pos + len, pin->i_nr_sects * SECTOR_SIZE); + + int off = pos % SECTOR_SIZE; + int rw_sect_min = pin->i_start_sect + (pos>>SECTOR_SIZE_SHIFT); + int rw_sect_max = pin->i_start_sect + (pos_end>>SECTOR_SIZE_SHIFT); + + //modified by xw, 18/12/27 + int chunk = min(rw_sect_max - rw_sect_min + 1, + SECTOR_SIZE >> SECTOR_SIZE_SHIFT); + + int bytes_rw = 0; + int bytes_left = len; + int i; + + char fsbuf[SECTOR_SIZE]; //local array, to substitute global fsbuf. added by xw, 18/12/27 + + for (i = rw_sect_min; i <= rw_sect_max; i += chunk) { + /* read/write this amount of bytes every time */ + int bytes = min(bytes_left, chunk * SECTOR_SIZE - off); + rw_sector(DEV_READ, //modified by mingxuan 2019-5-21 + pin->i_dev, + i * SECTOR_SIZE, + chunk * SECTOR_SIZE, + proc2pid(p_proc_current), /// TASK_FS + fsbuf); + + if (fs_msg->type == READ) { + memcpy((void*)va2la(src, buf + bytes_rw), + (void*)va2la(proc2pid(p_proc_current), fsbuf + off), + bytes); + } + else { /* WRITE */ + memcpy((void*)va2la(proc2pid(p_proc_current), fsbuf + off), + (void*)va2la(src, buf + bytes_rw), + bytes); + + rw_sector(DEV_WRITE, //modified by mingxuan 2019-5-21 + pin->i_dev, + i * SECTOR_SIZE, + chunk * SECTOR_SIZE, + proc2pid(p_proc_current), + fsbuf); + } + off = 0; + bytes_rw += bytes; + p_proc_current->task.filp[fd]->fd_pos += bytes; + bytes_left -= bytes; + } + + if (p_proc_current->task.filp[fd]->fd_pos > pin->i_size) { + /* update inode::size */ + pin->i_size = p_proc_current->task.filp[fd]->fd_pos; + sync_inode(pin); + } + + return bytes_rw; + } +} + +/// zcr copied from ch9/h/lib/unlink.c and modified it + +/***************************************************************************** + * unlink + *****************************************************************************/ +/** + * Delete a file. + * + * @param pathname The full path of the file to delete. + * + * @return Zero if successful, otherwise -1. + *****************************************************************************/ +int real_unlink(const char * pathname) //modified by mingxuan 2019-5-17 +{ + //added by xw, 18/8/27 + MESSAGE fs_msg; + + fs_msg.type = UNLINK; + fs_msg.PATHNAME = (void*)pathname; + fs_msg.NAME_LEN = strlen(pathname); + fs_msg.source = proc2pid(p_proc_current); //added by xw, 18/12/22 + + // send_recv(BOTH, TASK_FS, &msg); + + // return fs_msg.RETVAL; + /// zcr added + return do_unlink(&fs_msg); +} + +/// zcr copied from the ch9/h/fs/link.c and modified it +/***************************************************************************** + * do_unlink + *****************************************************************************/ +/** + * Remove a file. + * + * @note We clear the i-node in inode_array[] although it is not really needed. + * We don't clear the data bytes so the file is recoverable. + * + * @return On success, zero is returned. On error, -1 is returned. + *****************************************************************************/ +static int do_unlink(MESSAGE *fs_msg) +{ + char pathname[MAX_PATH]; + + /* get parameters from the message */ + int name_len = fs_msg->NAME_LEN; /* length of filename */ + int src = fs_msg->source; /* caller proc nr. */ + // assert(name_len < MAX_PATH); + memcpy((void*)va2la(proc2pid(p_proc_current), pathname), + (void*)va2la(src, fs_msg->PATHNAME), + name_len); + pathname[name_len] = 0; + + if (strcmp(pathname , "/") == 0) { + kprintf("FS:do_unlink():: cannot unlink the root\n"); + return -1; + } + + int inode_nr = search_file(pathname); + if (inode_nr == INVALID_INODE) { /* file not found */ + kprintf("FS::do_unlink():: search_file() returns invalid inode: %s\n", pathname); + return -1; + } + + char filename[MAX_PATH]; + struct inode * dir_inode; + if (strip_path(filename, pathname, &dir_inode) != 0) + return -1; + + struct inode * pin = get_inode_sched(dir_inode->i_dev, inode_nr); //modified by xw, 18/8/28 + + if (pin->i_mode != I_REGULAR) { /* can only remove regular files */ + kprintf("cannot remove file %s, because it is not a regular file.\n", pathname); + return -1; + } + + if (pin->i_cnt > 1) { /* the file was opened */ + kprintf("cannot remove file %s, because pin->i_cnt is %d\n", pathname, pin->i_cnt); + return -1; + } + + struct super_block * sb = get_super_block(pin->i_dev); + + /*************************/ + /* free the bit in i-map */ + /*************************/ + int byte_idx = inode_nr / 8; + int bit_idx = inode_nr % 8; + // assert(byte_idx < SECTOR_SIZE); /* we have only one i-map sector */ + /* read sector 2 (skip bootsect and superblk): */ + char fsbuf[SECTOR_SIZE]; //local array, to substitute global fsbuf. added by xw, 18/12/27 + RD_SECT_SCHED(pin->i_dev, 2, fsbuf); //modified by xw, 18/12/27 + // assert(fsbuf[byte_idx % SECTOR_SIZE] & (1 << bit_idx)); + fsbuf[byte_idx % SECTOR_SIZE] &= ~(1 << bit_idx); + WR_SECT_SCHED(pin->i_dev, 2, fsbuf); //modified by xw, 18/12/27 + + /**************************/ + /* free the bits in s-map */ + /**************************/ + /* + * bit_idx: bit idx in the entire i-map + * ... ____|____ + * \ .-- byte_cnt: how many bytes between + * \ | the first and last byte + * +-+-+-+-+-+-+-+-+ V +-+-+-+-+-+-+-+-+ + * ... | | | | | |*|*|*|...|*|*|*|*| | | | | + * +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ + * 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 + * ...__/ + * byte_idx: byte idx in the entire i-map + */ + bit_idx = pin->i_start_sect - sb->n_1st_sect + 1; + byte_idx = bit_idx / 8; + int bits_left = pin->i_nr_sects; + int byte_cnt = (bits_left - (8 - (bit_idx % 8))) / 8; + + /* current sector nr. */ + int s = 2 /* 2: bootsect + superblk */ + + sb->nr_imap_sects + byte_idx / SECTOR_SIZE; + + RD_SECT_SCHED(pin->i_dev, s, fsbuf); //modified by xw, 18/12/27 + + int i; + /* clear the first byte */ + for (i = bit_idx % 8; (i < 8) && bits_left; i++,bits_left--) { + // assert((fsbuf[byte_idx % SECTOR_SIZE] >> i & 1) == 1); + fsbuf[byte_idx % SECTOR_SIZE] &= ~(1 << i); + } + + /* clear bytes from the second byte to the second to last */ + int k; + i = (byte_idx % SECTOR_SIZE) + 1; /* the second byte */ + for (k = 0; k < byte_cnt; k++,i++,bits_left-=8) { + if (i == SECTOR_SIZE) { + i = 0; + WR_SECT_SCHED(pin->i_dev, s, fsbuf); //modified by xw, 18/12/27 + RD_SECT_SCHED(pin->i_dev, ++s, fsbuf); //modified by xw, 18/12/27 + } + // assert(fsbuf[i] == 0xFF); + fsbuf[i] = 0; + } + + /* clear the last byte */ + if (i == SECTOR_SIZE) { + i = 0; + WR_SECT_SCHED(pin->i_dev, s, fsbuf); //modified by xw, 18/12/27 + RD_SECT_SCHED(pin->i_dev, ++s, fsbuf); //modified by xw, 18/12/27 + } + // assert((fsbuf[i] & mask) == mask); + fsbuf[i] &= (~0) << bits_left; + WR_SECT_SCHED(pin->i_dev, s, fsbuf); //modified by xw, 18/12/27 + + /***************************/ + /* clear the i-node itself */ + /***************************/ + pin->i_mode = 0; + pin->i_size = 0; + pin->i_start_sect = 0; + pin->i_nr_sects = 0; + sync_inode(pin); + /* release slot in inode_table[] */ + put_inode(pin); + + /************************************************/ + /* set the inode-nr to 0 in the directory entry */ + /************************************************/ + int dir_blk0_nr = dir_inode->i_start_sect; + int nr_dir_blks = (dir_inode->i_size + SECTOR_SIZE) / SECTOR_SIZE; + int nr_dir_entries = + dir_inode->i_size / DIR_ENTRY_SIZE; /* including unused slots + * (the file has been + * deleted but the slot + * is still there) + */ + int m = 0; + struct dir_entry * pde = 0; + int flg = 0; + int dir_size = 0; + + for (i = 0; i < nr_dir_blks; i++) { + RD_SECT_SCHED(dir_inode->i_dev, dir_blk0_nr + i, fsbuf); //modified by xw, 18/12/27 + + pde = (struct dir_entry *)fsbuf; + int j; + for (j = 0; j < SECTOR_SIZE / DIR_ENTRY_SIZE; j++,pde++) { + if (++m > nr_dir_entries) + break; + + if (pde->inode_nr == inode_nr) { + /* pde->inode_nr = 0; */ + memset(pde, 0, DIR_ENTRY_SIZE); + WR_SECT_SCHED(dir_inode->i_dev, dir_blk0_nr + i, fsbuf); //modified by xw, 18/12/27 + flg = 1; + break; + } + + if (pde->inode_nr != INVALID_INODE) + dir_size += DIR_ENTRY_SIZE; + } + + if (m > nr_dir_entries || /* all entries have been iterated OR */ + flg) /* file is found */ + break; + } + // assert(flg); + if (m == nr_dir_entries) { /* the file is the last one in the dir */ + dir_inode->i_size = dir_size; + sync_inode(dir_inode); + } + + return 0; +} + +int real_lseek(int fd, int offset, int whence) //modified by mingxuan 2019-5-17 +{ + //added by xw, 18/8/27 + MESSAGE fs_msg; + + fs_msg.FD = fd; + fs_msg.OFFSET = offset; + fs_msg.WHENCE = whence; + + return do_lseek(&fs_msg); +} +/// zcr copied from ch9/j/fs/open.c +/***************************************************************************** + * do_lseek + *****************************************************************************/ +/** + * Handle the message LSEEK. + * + * @return The new offset in bytes from the beginning of the file if successful, + * otherwise a negative number. + *****************************************************************************/ +static int do_lseek(MESSAGE *fs_msg) +{ + int fd = fs_msg->FD; + int off = fs_msg->OFFSET; + int whence = fs_msg->WHENCE; + + int pos = p_proc_current->task.filp[fd]->fd_pos; + //int f_size = p_proc_current->task.filp[fd]->fd_inode->i_size; //deleted by mingxuan 2019-5-17 + int f_size = p_proc_current->task.filp[fd]->fd_node.fd_inode->i_size; //modified by mingxuan 2019-5-17 + + switch (whence) { + case SEEK_SET: + pos = off; + break; + case SEEK_CUR: + pos += off; + break; + case SEEK_END: + pos = f_size + off; + break; + default: + return -1; + break; + } + if ((pos > f_size) || (pos < 0)) { + return -1; + } + p_proc_current->task.filp[fd]->fd_pos = pos; + return pos; +} \ No newline at end of file diff --git a/kernel/global.c b/kernel/global.c new file mode 100644 index 0000000..d5b43ae --- /dev/null +++ b/kernel/global.c @@ -0,0 +1,85 @@ + +/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + global.c +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + Forrest Yu, 2005 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ +/* + * To make things more direct. In the headers below, + * the variable will be defined here. + * added by xw, 18/6/17 + */ +// #define GLOBAL_VARIABLES_HERE +#include "const.h" +#include "type.h" +#include "protect.h" +#include "proc.h" +#include "global.h" +#include "proto.h" +#include "fs_const.h" +#include "hd.h" +#include "fs.h" +#include "fat32.h" //added by mingxuan 2019-5-17 +#include "vfs.h" //added by mingxuan 2019-5-17 + +int kernel_initial; +int ticks; +int disp_pos; +u8 gdt_ptr[6]; +DESCRIPTOR gdt[GDT_SIZE]; +u8 idt_ptr[6]; +GATE idt[IDT_SIZE]; +u32 k_reenter; +int u_proc_sum; +TSS tss; +PROCESS *p_proc_current; +PROCESS *p_proc_next; + +u32 cr3_ready; + +/* save the execution environment of each cpu, which doesn't belong to any process. + * added by xw, 18/6/1 + */ +PROCESS cpu_table[NR_CPUS]; + +PROCESS proc_table[NR_PCBS]; // edit by visual 2016.4.5 + +TASK task_table[NR_TASKS] = { + {hd_service, STACK_SIZE_TASK, "hd_service"}, + {task_tty, STACK_SIZE_TASK, "task_tty"}}; // added by xw, 18/8/27 + +irq_handler irq_table[NR_IRQ]; + +system_call sys_call_table[NR_SYS_CALL] = { + sys_get_ticks, // 1st + sys_get_pid, // add by visual 2016.4.6 + sys_kmalloc, // add by visual 2016.4.6 + sys_kmalloc_4k, // add by visual 2016.4.7 + sys_malloc, // add by visual 2016.4.7 //5th + sys_malloc_4k, // add by visual 2016.4.7 + sys_free, // add by visual 2016.4.7 + sys_free_4k, // add by visual 2016.4.7 + sys_fork, // add by visual 2016.4.8 + sys_pthread, // add by visual 2016.4.11 //10th + sys_udisp_int, // add by visual 2016.5.16 + sys_udisp_str, // add by visual 2016.5.16 + sys_exec, // add by visual 2016.5.16 + sys_yield, // added by xw + sys_sleep, // added by xw //15th + sys_print_E, // added by xw + sys_print_F, // added by xw + sys_open, // added by xw, 18/6/18 + sys_close, // added by xw, 18/6/18 + sys_read, // added by xw, 18/6/18 //20th + sys_write, // added by xw, 18/6/18 + sys_lseek, // added by xw, 18/6/18 + sys_unlink, // added by xw, 18/6/19 //23th + sys_create, // added by mingxuan 2019-5-17 + sys_delete, // added by mingxuan 2019-5-17 + sys_opendir, // added by mingxuan 2019-5-17 + sys_createdir, // added by mingxuan 2019-5-17 + sys_deletedir // added by mingxuan 2019-5-17 +}; + +TTY tty_table[NR_CONSOLES]; // added by mingxuan 2019-5-19 +CONSOLE console_table[NR_CONSOLES]; // added by mingxuan 2019-5-19 \ No newline at end of file diff --git a/kernel/hd.c b/kernel/hd.c new file mode 100644 index 0000000..f641a15 --- /dev/null +++ b/kernel/hd.c @@ -0,0 +1,672 @@ +/// zcr copy whole file from Orange's and the file was modified. + +/*************************************************************************//** + ***************************************************************************** + * @file hd.c + * @brief Hard disk (winchester) driver. + * The `device nr' in this file means minor device nr. + * @author Forrest Y. Yu + * @date 2005~2008 + ***************************************************************************** + *****************************************************************************/ + +#include "type.h" +#include "const.h" +#include "protect.h" +#include "string.h" +#include "proc.h" +#include "global.h" +#include "proto.h" +#include "fs_const.h" +#include "hd.h" +#include "fs.h" +#include "fs_misc.h" +#include "x86.h" +#include "stdio.h" +#include "assert.h" + +struct part_ent PARTITION_ENTRY; + +//added by xw, 18/8/28 +static HDQueue hdque; +static volatile int hd_int_waiting_flag; +static u8 hd_status; +static u8 hdbuf[SECTOR_SIZE * 2]; +//static struct hd_info hd_info[1]; +struct hd_info hd_info[1]; //modified by mingxuan 2020-10-27 + +static void init_hd_queue(HDQueue *hdq); +static void in_hd_queue(HDQueue *hdq, RWInfo *p); +static int out_hd_queue(HDQueue *hdq, RWInfo **p); +static void hd_rdwt_real(RWInfo *p); + +static void get_part_table(int drive, int sect_nr, struct part_ent *entry); +static void partition(int device, int style); +static void print_hdinfo(struct hd_info *hdi); +static void hd_identify(int drive); +static void print_identify_info(u16 *hdinfo); +static void hd_cmd_out(struct hd_cmd *cmd); + +static void inform_int(); +static void interrupt_wait(); +static void hd_handler(int irq); +static int waitfor(int mask, int val, int timeout); +//~xw + +#define DRV_OF_DEV(dev) (dev <= MAX_PRIM ? \ + dev / NR_PRIM_PER_DRIVE : \ + (dev - MINOR_hd1a) / NR_SUB_PER_DRIVE) + +/***************************************************************************** + * init_hd + *****************************************************************************/ +/** + * Check hard drive, set IRQ handler, enable IRQ and initialize data + * structures. + *****************************************************************************/ +void init_hd() +{ + int i; + + put_irq_handler(AT_WINI_IRQ, hd_handler); + enable_irq(CASCADE_IRQ); + enable_irq(AT_WINI_IRQ); + + for (i = 0; i < (sizeof(hd_info) / sizeof(hd_info[0])); i++) + memset(&hd_info[i], 0, sizeof(hd_info[0])); + hd_info[0].open_cnt = 0; + + init_hd_queue(&hdque); +} + +/***************************************************************************** + * hd_open + *****************************************************************************/ +/** + * This routine handles DEV_OPEN message. It identify the drive + * of the given device and read the partition table of the drive if it + * has not been read. + * + * @param device The device to be opened. + *****************************************************************************/ +// void hd_open(int device) //no need for int device, mingxuan +void hd_open(int drive) //modified by mingxuan 2020-10-27 +{ + kprintf("Read hd information... "); + + /* Get the number of drives from the BIOS data area */ + // u8 * pNrDrives = (u8*)(0x475); + hd_identify(drive); + + if (hd_info[drive].open_cnt++ == 0) { + partition(drive * (NR_PART_PER_DRIVE + 1), P_PRIMARY); + print_hdinfo(&hd_info[drive]); + } +} + +/***************************************************************************** + * hd_close + *****************************************************************************/ +/** + * This routine handles DEV_CLOSE message. + * + * @param device The device to be opened. + *****************************************************************************/ +void hd_close(int device) +{ + int drive = DRV_OF_DEV(device); + + hd_info[drive].open_cnt--; +} + + +/***************************************************************************** + * hd_rdwt + *****************************************************************************/ +/** + * This routine handles DEV_READ and DEV_WRITE message. + * + * @param p Message ptr. + *****************************************************************************/ +void hd_rdwt(MESSAGE * p) +{ + int drive = DRV_OF_DEV(p->DEVICE); + + u64 pos = p->POSITION; + + //We only allow to R/W from a SECTOR boundary: + + u32 sect_nr = (u32)(pos >> SECTOR_SIZE_SHIFT); // pos / SECTOR_SIZE + int logidx = (p->DEVICE - MINOR_hd1a) % NR_SUB_PER_DRIVE; + sect_nr += p->DEVICE < MAX_PRIM ? + hd_info[drive].primary[p->DEVICE].base : + hd_info[drive].logical[logidx].base; + + struct hd_cmd cmd; + cmd.features = 0; + cmd.count = (p->CNT + SECTOR_SIZE - 1) / SECTOR_SIZE; + cmd.lba_low = sect_nr & 0xFF; + cmd.lba_mid = (sect_nr >> 8) & 0xFF; + cmd.lba_high = (sect_nr >> 16) & 0xFF; + cmd.device = MAKE_DEVICE_REG(1, drive, (sect_nr >> 24) & 0xF); + cmd.command = (p->type == DEV_READ) ? ATA_READ : ATA_WRITE; + hd_cmd_out(&cmd); + + int bytes_left = p->CNT; + void * la = (void*)va2la(p->PROC_NR, p->BUF); + + while (bytes_left) { + int bytes = min(SECTOR_SIZE, bytes_left); + if (p->type == DEV_READ) { + interrupt_wait(); + insw(REG_DATA, hdbuf, SECTOR_SIZE); + memcpy(la, hdbuf, bytes); + } + else { + if (!waitfor(STATUS_DRQ, STATUS_DRQ, HD_TIMEOUT)) + ("hd writing error."); + + memcpy(hdbuf, la, bytes); + outsw(REG_DATA, hdbuf, SECTOR_SIZE); + interrupt_wait(); + } + bytes_left -= SECTOR_SIZE; + la += SECTOR_SIZE; + } +} + +//added by xw, 18/8/26 +void hd_service() +{ + RWInfo *rwinfo; + + while(1) + { + //the hd queue is not empty when out_hd_queue return 1. + while(out_hd_queue(&hdque, &rwinfo)) + { + hd_rdwt_real(rwinfo); + rwinfo->proc->task.stat = READY; + } + yield(); + } + +} + +static void hd_rdwt_real(RWInfo *p) +{ + int drive = DRV_OF_DEV(p->msg->DEVICE); + + u64 pos = p->msg->POSITION; + + //We only allow to R/W from a SECTOR boundary: + + u32 sect_nr = (u32)(pos >> SECTOR_SIZE_SHIFT); // pos / SECTOR_SIZE + int logidx = (p->msg->DEVICE - MINOR_hd1a) % NR_SUB_PER_DRIVE; + sect_nr += p->msg->DEVICE < MAX_PRIM ? + hd_info[drive].primary[p->msg->DEVICE].base : + hd_info[drive].logical[logidx].base; + + struct hd_cmd cmd; + cmd.features = 0; + cmd.count = (p->msg->CNT + SECTOR_SIZE - 1) / SECTOR_SIZE; + cmd.lba_low = sect_nr & 0xFF; + cmd.lba_mid = (sect_nr >> 8) & 0xFF; + cmd.lba_high = (sect_nr >> 16) & 0xFF; + cmd.device = MAKE_DEVICE_REG(1, drive, (sect_nr >> 24) & 0xF); + cmd.command = (p->msg->type == DEV_READ) ? ATA_READ : ATA_WRITE; + hd_cmd_out(&cmd); + + int bytes_left = p->msg->CNT; + void *la = p->kbuf; //attention here! + + while (bytes_left) { + int bytes = min(SECTOR_SIZE, bytes_left); + if (p->msg->type == DEV_READ) { + interrupt_wait(); + insw(REG_DATA, hdbuf, SECTOR_SIZE); + memcpy(la, hdbuf, bytes); + } + else { + if (!waitfor(STATUS_DRQ, STATUS_DRQ, HD_TIMEOUT)) + panic("hd writing error."); + + memcpy(hdbuf, la, bytes); + outsw(REG_DATA, hdbuf, SECTOR_SIZE); + interrupt_wait(); + } + bytes_left -= SECTOR_SIZE; + la += SECTOR_SIZE; + } +} + +void hd_rdwt_sched(MESSAGE *p) +{ + RWInfo rwinfo; + struct memfree hdque_buf; + int size = p->CNT; + void *buffer; + + buffer = (void*)K_PHY2LIN(sys_kmalloc(size)); + rwinfo.msg = p; + rwinfo.kbuf = buffer; + rwinfo.proc = p_proc_current; + + if (p->type == DEV_READ) { + in_hd_queue(&hdque, &rwinfo); + p_proc_current->task.channel = &hdque; + p_proc_current->task.stat = SLEEPING; + sched(); + memcpy(p->BUF, buffer, p->CNT); + } else { + memcpy(buffer, p->BUF, p->CNT); + in_hd_queue(&hdque, &rwinfo); + p_proc_current->task.channel = &hdque; + p_proc_current->task.stat = SLEEPING; + sched(); + } + + hdque_buf.addr = K_LIN2PHY((u32)buffer); + hdque_buf.size = size; + sys_free(&hdque_buf); +} + +void init_hd_queue(HDQueue *hdq) +{ + hdq->front = hdq->rear = NULL; +} + +static void in_hd_queue(HDQueue *hdq, RWInfo *p) +{ + p->next = NULL; + if(hdq->rear == NULL) { //put in the first node + hdq->front = hdq->rear = p; + } else { + hdq->rear->next = p; + hdq->rear = p; + } +} + +static int out_hd_queue(HDQueue *hdq, RWInfo **p) +{ + if (hdq->rear == NULL) + return 0; //empty + + *p = hdq->front; + if (hdq->front == hdq->rear) { //put out the last node + hdq->front = hdq->rear = NULL; + } else { + hdq->front = hdq->front->next; + } + return 1; //not empty +} +//~xw + +/***************************************************************************** + * hd_ioctl + *****************************************************************************/ +/** + * This routine handles the DEV_IOCTL message. + * + * @param p Ptr to the MESSAGE. + *****************************************************************************/ +void hd_ioctl(MESSAGE * p) +{ + int device = p->DEVICE; + int drive = DRV_OF_DEV(device); + + struct hd_info * hdi = &hd_info[drive]; + + if (p->REQUEST == DIOCTL_GET_GEO) { + void * dst = va2la(p->PROC_NR, p->BUF); + void * src = va2la(proc2pid(p_proc_current), + device < MAX_PRIM ? + &hdi->primary[device] : + &hdi->logical[(device - MINOR_hd1a) % + NR_SUB_PER_DRIVE]); + + memcpy(dst, src, sizeof(struct part_info)); + } + else { + // assert(0); + } +} + +/***************************************************************************** + * get_part_table + *****************************************************************************/ +/** + * Get a partition table of a drive. + * + * @param drive Drive nr (0 for the 1st disk, 1 for the 2nd, ...)n + * @param sect_nr The sector at which the partition table is located. + * @param entry Ptr to part_ent struct. + *****************************************************************************/ +static void get_part_table(int drive, int sect_nr, struct part_ent * entry) +{ + struct hd_cmd cmd; + cmd.features = 0; + cmd.count = 1; + cmd.lba_low = sect_nr & 0xFF; + cmd.lba_mid = (sect_nr >> 8) & 0xFF; + cmd.lba_high = (sect_nr >> 16) & 0xFF; + cmd.device = MAKE_DEVICE_REG(1, /* LBA mode*/ + drive, + (sect_nr >> 24) & 0xF); + cmd.command = ATA_READ; + hd_cmd_out(&cmd); + interrupt_wait(); + + insw(REG_DATA, hdbuf, SECTOR_SIZE); + memcpy(entry, + hdbuf + PARTITION_TABLE_OFFSET, + sizeof(struct part_ent) * NR_PART_PER_DRIVE); +} + +// added by mingxuan 2020-10-27 +static void get_fs_flags(int drive, int sect_nr, struct fs_flags * fs_flags_buf) +{ + struct hd_cmd cmd; + cmd.features = 0; + cmd.count = 1; + cmd.lba_low = sect_nr & 0xFF; + cmd.lba_mid = (sect_nr >> 8) & 0xFF; + cmd.lba_high = (sect_nr >> 16) & 0xFF; + cmd.device = MAKE_DEVICE_REG(1, /* LBA mode*/ + drive, + (sect_nr >> 24) & 0xF); + cmd.command = ATA_READ; + hd_cmd_out(&cmd); + interrupt_wait(); + + insw(REG_DATA, hdbuf, SECTOR_SIZE); + memcpy(fs_flags_buf, + hdbuf, + sizeof(struct fs_flags)); +} + +/***************************************************************************** + * partition + *****************************************************************************/ +/** + * This routine is called when a device is opened. It reads the + * partition table(s) and fills the hd_info struct. + * + * @param device Device nr. + * @param style P_PRIMARY or P_EXTENDED. + *****************************************************************************/ +static void partition(int device, int style) +{ + int i; + int drive = DRV_OF_DEV(device); + struct hd_info * hdi = &hd_info[drive]; + + struct part_ent part_tbl[NR_SUB_PER_DRIVE]; + + if (style == P_PRIMARY) { + get_part_table(drive, drive, part_tbl); + + int nr_prim_parts = 0; + for (i = 0; i < NR_PART_PER_DRIVE; i++) { /* 0~3 */ + if (part_tbl[i].sys_id == NO_PART) + continue; + + nr_prim_parts++; + int dev_nr = i + 1; /* 1~4 */ + hdi->primary[dev_nr].base = part_tbl[i].start_sect; + hdi->primary[dev_nr].size = part_tbl[i].nr_sects; + + // added by mingxuan 2020-10-27 + struct fs_flags fs_flags_buf; + get_fs_flags(drive, hdi->primary[dev_nr].base+1, &fs_flags_buf); //hdi->primary[dev_nr].base + 1 beacause of orange and fat32 is in 2nd sector, mingxuan + if(fs_flags_buf.orange_flag == 0x11) // Orange's Magic + hdi->primary[dev_nr].fs_type = ORANGE_TYPE; + else if(fs_flags_buf.fat32_flag1 == 0x534f4453 && fs_flags_buf.fat32_flag2 == 0x302e35) // FAT32 flags + hdi->primary[dev_nr].fs_type = FAT32_TYPE; + // added end, mingxuan 2020-10-27 + + if (part_tbl[i].sys_id == EXT_PART) /* extended */ + partition(device + dev_nr, P_EXTENDED); + } + } + else if (style == P_EXTENDED) { + int j = device % NR_PRIM_PER_DRIVE; /* 1~4 */ + int ext_start_sect = hdi->primary[j].base; + int s = ext_start_sect; + int nr_1st_sub = (j - 1) * NR_SUB_PER_PART; /* 0/16/32/48 */ + + for (i = 0; i < NR_SUB_PER_PART; i++) { + int dev_nr = nr_1st_sub + i;/* 0~15/16~31/32~47/48~63 */ + + get_part_table(drive, s, part_tbl); + + hdi->logical[dev_nr].base = s + part_tbl[0].start_sect; + hdi->logical[dev_nr].size = part_tbl[0].nr_sects; + + // added by mingxuan 2020-10-29 + struct fs_flags fs_flags_buf; + get_fs_flags(drive, hdi->logical[dev_nr].base+1, &fs_flags_buf); //hdi->primary[dev_nr].base + 1 beacause of orange and fat32 is in 2nd sector, mingxuan + if(fs_flags_buf.orange_flag == 0x11) // Orange's Magic + hdi->logical[dev_nr].fs_type = ORANGE_TYPE; + else if(fs_flags_buf.fat32_flag1 == 0x534f4453 && fs_flags_buf.fat32_flag2 == 0x302e35) // FAT32 flags + hdi->logical[dev_nr].fs_type = FAT32_TYPE; + // added end, mingxuan 2020-10-29 + + s = ext_start_sect + part_tbl[1].start_sect; + + /* no more logical partitions + in this extended partition */ + if (part_tbl[1].sys_id == NO_PART) + break; + } + } + else { + // assert(0); + } +} + +/***************************************************************************** + * print_hdinfo + *****************************************************************************/ +/** + * Print disk info. + * + * @param hdi Ptr to struct hd_info. + *****************************************************************************/ +static void print_hdinfo(struct hd_info * hdi) +{ + int i; + for (i = 0; i < NR_PART_PER_DRIVE + 1; i++) { + if(i == 0) { + kprintf(" "); + } + else { + kprintf(" "); + } + kprintf("PART_%d: base %d, size: %d (in sector)\n", + i, hdi->primary[i].base, hdi->primary[i].size); + } + for (i = 0; i < NR_SUB_PER_DRIVE; i++) { + if (hdi->logical[i].size == 0) + continue; + kprintf(" %d: base %d, size %d (in sector)\n", + i, hdi->logical[i].base, hdi->logical[i].size); + } +} + +/***************************************************************************** + * hd_identify + *****************************************************************************/ +/** + * Get the disk information. + * + * @param drive Drive Nr. + *****************************************************************************/ +static void hd_identify(int drive) +{ + struct hd_cmd cmd; + cmd.device = MAKE_DEVICE_REG(0, drive, 0); + cmd.command = ATA_IDENTIFY; + hd_cmd_out(&cmd); + interrupt_wait(); + insw(REG_DATA, hdbuf, SECTOR_SIZE); + + print_identify_info((u16*)hdbuf); + + u16* hdinfo = (u16*)hdbuf; + + hd_info[drive].primary[0].base = 0; + /* Total Nr of User Addressable Sectors */ + hd_info[drive].primary[0].size = ((int)hdinfo[61] << 16) + hdinfo[60]; +} + +/***************************************************************************** + * print_identify_info + *****************************************************************************/ +/** + * Print the hdinfo retrieved via ATA_IDENTIFY command. + * + * @param hdinfo The buffer read from the disk i/o port. + *****************************************************************************/ +static void print_identify_info(u16* hdinfo) +{ + int i, k; + char s[64]; + + struct iden_info_ascii { + int idx; + int len; + char * desc; + } iinfo[] = {{10, 20, "HD SN"}, /* Serial number in ASCII */ + {27, 40, "HD Model"} /* Model number in ASCII */ }; + + for (k = 0; k < sizeof(iinfo)/sizeof(iinfo[0]); k++) { + char * p = (char*)&hdinfo[iinfo[k].idx]; + for (i = 0; i < iinfo[k].len/2; i++) { + s[i*2+1] = *p++; + s[i*2] = *p++; + } + s[i*2] = 0; + kprintf("%s: %s\n", iinfo[k].desc, s); + } + + int capabilities = hdinfo[49]; + kprintf("LBA supported:%s ", capabilities & 0x0200 ? "YES" : "NO"); + + int cmd_set_supported = hdinfo[83]; + kprintf("LBA48 supported:%s ", cmd_set_supported & 0x0400 ? "YES" : "NO"); + + int sectors = ((int)hdinfo[61] << 16) + hdinfo[60]; + kprintf("HD size:%dMB\n", sectors * 512 / 1000000); +} + +/***************************************************************************** + * hd_cmd_out + *****************************************************************************/ +/** + * Output a command to HD controller. + * + * @param cmd The command struct ptr. + *****************************************************************************/ +static void hd_cmd_out(struct hd_cmd* cmd) +{ + /** + * For all commands, the host must first check if BSY=1, + * and should proceed no further unless and until BSY=0 + */ + if (!waitfor(STATUS_BSY, 0, HD_TIMEOUT)) + panic("hd error."); + + /* Activate the Interrupt Enable (nIEN) bit */ + outb(REG_DEV_CTRL, 0); + /* Load required parameters in the Command Block Registers */ + outb(REG_FEATURES, cmd->features); + outb(REG_NSECTOR, cmd->count); + outb(REG_LBA_LOW, cmd->lba_low); + outb(REG_LBA_MID, cmd->lba_mid); + outb(REG_LBA_HIGH, cmd->lba_high); + outb(REG_DEVICE, cmd->device); + /* Write the command code to the Command Register */ + outb(REG_CMD, cmd->command); +} + +/***************************************************************************** + * interrupt_wait + *****************************************************************************/ +/** + * Wait until a disk interrupt occurs. + * + *****************************************************************************/ +static void interrupt_wait() +{ + while(hd_int_waiting_flag) { + + } + hd_int_waiting_flag = 1; +} + + +/***************************************************************************** + * waitfor + *****************************************************************************/ +/** + * Wait for a certain status. + * + * @param mask Status mask. + * @param val Required status. + * @param timeout Timeout in milliseconds. + * + * @return One if sucess, zero if timeout. + *****************************************************************************/ +static int waitfor(int mask, int val, int timeout) +{ + int t = sys_get_ticks(); + + while(((sys_get_ticks() - t) * 1000 / HZ) < timeout){ + if ((inb(REG_STATUS) & mask) == val) + return 1; + } + + return 0; +} + +/***************************************************************************** + * hd_handler + *****************************************************************************/ +/** + * Interrupt handler. + * + * @param irq IRQ nr of the disk interrupt. + *****************************************************************************/ +static void hd_handler(int irq) +{ + /* + * Interrupts are cleared when the host + * - reads the Status Register, + * - issues a reset, or + * - writes to the Command Register. + */ + hd_status = inb(REG_STATUS); + inform_int(); + + /* There is two stages - in kernel intializing or in process running. + * Some operation shouldn't be valid in kernel intializing stage. + * added by xw, 18/6/1 + */ + if(kernel_initial == 1){ + return; + } + + //some operation only for process + + return; +} + +/***************************************************************************** + * inform_int + *****************************************************************************/ +static void inform_int() +{ + hd_int_waiting_flag = 0; + return; +} diff --git a/kernel/i8259.c b/kernel/i8259.c new file mode 100644 index 0000000..c2d0ecb --- /dev/null +++ b/kernel/i8259.c @@ -0,0 +1,75 @@ + +/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + i8259.c +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + Forrest Yu, 2005 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + + +#include "type.h" +#include "const.h" +#include "protect.h" +#include "proc.h" +#include "global.h" +#include "proto.h" +#include "x86.h" +#include "stdio.h" + + +/*======================================================================* + init_8259A + *======================================================================*/ +void init_8259A() +{ + outb(INT_M_CTL, 0x11); // Master 8259, ICW1. + outb(INT_S_CTL, 0x11); // Slave 8259, ICW1. + outb(INT_M_CTLMASK, INT_VECTOR_IRQ0); // Master 8259, ICW2. 设置 '主8259' 的中断入口地址为 0x20. + outb(INT_S_CTLMASK, INT_VECTOR_IRQ8); // Slave 8259, ICW2. 设置 '从8259' 的中断入口地址为 0x28 + outb(INT_M_CTLMASK, 0x4); // Master 8259, ICW3. IR2 对应 '从8259'. + outb(INT_S_CTLMASK, 0x2); // Slave 8259, ICW3. 对应 '主8259' 的 IR2. + outb(INT_M_CTLMASK, 0x1); // Master 8259, ICW4. + outb(INT_S_CTLMASK, 0x1); // Slave 8259, ICW4. + + outb(INT_M_CTLMASK, 0xFF); // Master 8259, OCW1. + outb(INT_S_CTLMASK, 0xFF); // Slave 8259, OCW1. + + int i; + for (i = 0; i < NR_IRQ; i++) { + irq_table[i] = spurious_irq; + } +} + +void disable_irq(int irq) +{ + u8 mask = 1 << (irq % 8); + if (irq < 8) + outb(INT_M_CTLMASK, inb(INT_M_CTLMASK) | mask); + else + outb(INT_S_CTLMASK, inb(INT_S_CTLMASK) | mask); +} + +void enable_irq(int irq) +{ + u8 mask = 1 << (irq % 8); + if (irq < 8) + outb(INT_M_CTLMASK, inb(INT_M_CTLMASK) & ~mask); + else + outb(INT_S_CTLMASK, inb(INT_S_CTLMASK) & ~mask); +} + +/*======================================================================* + spurious_irq + *======================================================================*/ +void spurious_irq(int irq) +{ + kprintf("spurious_irq: %d\n", irq); +} + +/*======================================================================* + put_irq_handler + *======================================================================*/ +void put_irq_handler(int irq, irq_handler handler) +{ + disable_irq(irq); + irq_table[irq] = handler; +} diff --git a/kernel/kernel.asm b/kernel/kernel.asm new file mode 100644 index 0000000..5e079f7 --- /dev/null +++ b/kernel/kernel.asm @@ -0,0 +1,608 @@ + +; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +; kernel.asm +; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +; Forrest Yu, 2005 +; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + +%include "sconst.inc" + +; 导入函数 +extern cstart +extern kernel_main +extern exception_handler +extern spurious_irq +extern clock_handler +extern disp_str +extern delay +extern irq_table +extern page_fault_handler +extern divide_error_handler ;added by xw, 18/12/22 +extern disp_int +extern schedule +extern switch_pde + +; 导入全局变量 +extern gdt_ptr +extern idt_ptr +extern p_proc_current +extern tss +extern disp_pos +extern k_reenter +extern sys_call_table +extern cr3_ready ;add by visual 2016.4.5 +extern p_proc_current +extern p_proc_next ;added by xw, 18/4/26 +extern kernel_initial ;added by xw, 18/6/10 + +bits 32 + +[SECTION .bss] +StackSpace resb 2 * 1024 +StackTop: ; used only as irq-stack in minios. added by xw + +; added by xw, 18/6/15 +KernelStackSpace resb 2 * 1024 +KernelStackTop: ; used as stack of kernel itself +; ~xw + +[section .text] ; 代码在此 + +global _start ; 导出 _start + +;global restart +global restart_initial ;Added by xw, 18/4/21 +global restart_restore ;Added by xw, 18/4/21 +global sched ;Added by xw, 18/4/21 +global sys_call +global read_cr2 ;//add by visual 2016.5.9 +global refresh_page_cache ; // add by visual 2016.5.12 +global halt ;added by xw, 18/6/11 +global get_arg ;added by xw, 18/6/18 + +global divide_error +global single_step_exception +global nmi +global breakpoint_exception +global overflow +global bounds_check +global inval_opcode +global copr_not_available +global double_fault +global copr_seg_overrun +global inval_tss +global segment_not_present +global stack_exception +global general_protection +global page_fault +global copr_error +global hwint00 +global hwint01 +global hwint02 +global hwint03 +global hwint04 +global hwint05 +global hwint06 +global hwint07 +global hwint08 +global hwint09 +global hwint10 +global hwint11 +global hwint12 +global hwint13 +global hwint14 +global hwint15 + + +_start: + ; 此时内存看上去是这样的(更详细的内存情况在 LOADER.ASM 中有说明): + ; ┃ ┃ + ; ┃ ... ┃ + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃■■■■■■Page Tables■■■■■■┃ + ; ┃■■■■■(大小由LOADER决定)■■■■┃ PageTblBase + ; 00101000h ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃■■■■Page Directory Table■■■■┃ PageDirBase = 1M + ; 00100000h ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃□□□□ Hardware Reserved □□□□┃ B8000h ← gs + ; 9FC00h ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃■■■■■■■LOADER.BIN■■■■■■┃ somewhere in LOADER ← esp + ; 90000h ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃■■■■■■■KERNEL.BIN■■■■■■┃ + ; 80000h ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃■■■■■■■■KERNEL■■■■■■■┃ 30400h ← KERNEL 入口 (KernelEntryPointPhyAddr) + ; 30000h ┣━━━━━━━━━━━━━━━━━━┫ + ; ┋ ... ┋ + ; ┋ ┋ + ; 0h ┗━━━━━━━━━━━━━━━━━━┛ ← cs, ds, es, fs, ss + ; + ; + ; GDT 以及相应的描述符是这样的: + ; + ; Descriptors Selectors + ; ┏━━━━━━━━━━━━━━━━━━┓ + ; ┃ Dummy Descriptor ┃ + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃ DESC_FLAT_C (0~4G) ┃ 8h = cs + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃ DESC_FLAT_RW (0~4G) ┃ 10h = ds, es, fs, ss + ; ┣━━━━━━━━━━━━━━━━━━┫ + ; ┃ DESC_VIDEO ┃ 1Bh = gs + ; ┗━━━━━━━━━━━━━━━━━━┛ + ; + ; 注意! 在使用 C 代码的时候一定要保证 ds, es, ss 这几个段寄存器的值是一样的 + ; 因为编译器有可能编译出使用它们的代码, 而编译器默认它们是一样的. 比如串拷贝操作会用到 ds 和 es. + ; + ; + + ; 把 esp 从 LOADER 挪到 KERNEL + ; modified by xw, 18/6/15 + ;mov esp, StackTop ; 堆栈在 bss 段中 + mov esp, KernelStackTop ; 堆栈在 bss 段中 + + mov dword [disp_pos], 0 + + sgdt [gdt_ptr] ; cstart() 中将会用到 gdt_ptr + call cstart ; 在此函数中改变了gdt_ptr,让它指向新的GDT + lgdt [gdt_ptr] ; 使用新的GDT + + lidt [idt_ptr] + + jmp SELECTOR_KERNEL_CS:csinit +csinit: ; “这个跳转指令强制使用刚刚初始化的结构”——<> P90. + + xor eax, eax + mov ax, SELECTOR_TSS + ltr ax + + jmp kernel_main + + +; 中断和异常 -- 硬件中断 +; --------------------------------- +%macro hwint_master 1 + ;call save + call save_int ;save registers and some other things. modified by xw, 17/12/11 + inc dword [k_reenter] ;If k_reenter isn't equal to 0, there is no switching to the irq-stack, + ;which is performed in save_int. Added by xw, 18/4/21 + + in al, INT_M_CTLMASK ; `. + or al, (1 << %1) ; | 屏蔽当前中断 + out INT_M_CTLMASK, al ; / + + mov al, EOI ; `. 置EOI位 + out INT_M_CTL, al ; / + + sti ; CPU在响应中断的过程中会自动关中断,这句之后就允许响应新的中断 + push %1 ; `. + call [irq_table + 4 * %1] ; | 中断处理程序 + pop ecx ; / ; ┛ + + cli + dec dword [k_reenter] + + in al, INT_M_CTLMASK ; `. + and al, ~(1 << %1) ; | 恢复接受当前中断 + out INT_M_CTLMASK, al ; / + ret +%endmacro + + +ALIGN 16 +hwint00: ; Interrupt routine for irq 0 (the clock). + hwint_master 0 + +ALIGN 16 +hwint01: ; Interrupt routine for irq 1 (keyboard) + hwint_master 1 + +ALIGN 16 +hwint02: ; Interrupt routine for irq 2 (cascade!) + hwint_master 2 + +ALIGN 16 +hwint03: ; Interrupt routine for irq 3 (second serial) + hwint_master 3 + +ALIGN 16 +hwint04: ; Interrupt routine for irq 4 (first serial) + hwint_master 4 + +ALIGN 16 +hwint05: ; Interrupt routine for irq 5 (XT winchester) + hwint_master 5 + +ALIGN 16 +hwint06: ; Interrupt routine for irq 6 (floppy) + hwint_master 6 + +ALIGN 16 +hwint07: ; Interrupt routine for irq 7 (printer) + hwint_master 7 + +; --------------------------------- +%macro hwint_slave 1 + ;added by xw, 18/5/29 + call save_int ;save registers and some other things. + inc dword [k_reenter] ;If k_reenter isn't equal to 0, there is no switching to the irq-stack, + ;which is performed in save_int. Added by xw, 18/4/21 + + in al, INT_S_CTLMASK ; `. + or al, (1 << (%1 - 8)) ; | 屏蔽当前中断 + out INT_S_CTLMASK, al ; / + mov al, EOI ; `. + out INT_M_CTL, al ; / 置EOI位(master) + nop ; `.一定注意:slave和master都要置EOI + out INT_S_CTL, al ; / 置EOI位(slave) + + sti ; CPU在响应中断的过程中会自动关中断,这句之后就允许响应新的中断 + push %1 ; `. + call [irq_table + 4 * %1] ; | 中断处理程序 + pop ecx ; / + + cli + dec dword [k_reenter] + in al, INT_S_CTLMASK ; `. + and al, ~(1 << (%1 - 8)) ; | 恢复接受当前中断 + out INT_S_CTLMASK, al ; / + ret + ;~xw +%endmacro +; --------------------------------- + +ALIGN 16 +hwint08: ; Interrupt routine for irq 8 (realtime clock). + hwint_slave 8 + +ALIGN 16 +hwint09: ; Interrupt routine for irq 9 (irq 2 redirected) + hwint_slave 9 + +ALIGN 16 +hwint10: ; Interrupt routine for irq 10 + hwint_slave 10 + +ALIGN 16 +hwint11: ; Interrupt routine for irq 11 + hwint_slave 11 + +ALIGN 16 +hwint12: ; Interrupt routine for irq 12 + hwint_slave 12 + +ALIGN 16 +hwint13: ; Interrupt routine for irq 13 (FPU exception) + hwint_slave 13 + +ALIGN 16 +hwint14: ; Interrupt routine for irq 14 (AT winchester) + hwint_slave 14 + +ALIGN 16 +hwint15: ; Interrupt routine for irq 15 + hwint_slave 15 + +; ==================================================================================== +; exception +; ==================================================================================== +;restructured exception-handling procedure +;added by xw, 18/12/18 +%macro exception_no_errcode 2 + push 0xFFFFFFFF ;no err code + call save_exception ;save registers and some other things. + mov esi, esp ;esp points to pushed address of restart_exception at present + add esi, 4 * 17 ;we use esi to help to fetch arguments of exception handler from the stack. + ;17 is calculated by: 4+8+retaddr+errcode+eip+cs+eflag=17 + mov eax, [esi] ;saved eflags + push eax + mov eax, [esi - 4] ;saved cs + push eax + mov eax, [esi - 4 * 2] ;saved eip + push eax + mov eax, [esi - 4 * 3] ;saved err code + push eax + push %1 ;vector_no + sti + call %2 + cli + add esp, 4 * 5 ;clear arguments of exception handler in stack + ret ;returned to 'restart_exception' procedure +%endmacro + +%macro exception_errcode 2 + call save_exception ;save registers and some other things. + mov esi, esp ;esp points to pushed address of restart_exception at present + add esi, 4 * 17 ;we use esi to help to fetch arguments of exception handler from the stack. + ;17 is calculated by: 4+8+retaddr+errcode+eip+cs+eflag=17 + mov eax, [esi] ;saved eflags + push eax + mov eax, [esi - 4] ;saved cs + push eax + mov eax, [esi - 4 * 2] ;saved eip + push eax + mov eax, [esi - 4 * 3] ;saved err code + push eax + push %1 ;vector_no + sti + call %2 + cli + add esp, 4 * 5 ;clear arguments of exception handler in stack + ret ;returned to 'restart_exception' procedure +%endmacro + +divide_error: ; vector_no = 0 +; exception_no_errcode 0, exception_handler + exception_no_errcode 0, divide_error_handler ;added by xw, 18/12/22 + +single_step_exception: ; vector_no = 1 + exception_no_errcode 1, exception_handler + +nmi: ; vector_no = 2 + exception_no_errcode 2, exception_handler + +breakpoint_exception: ; vector_no = 3 + exception_no_errcode 3, exception_handler + +overflow: ; vector_no = 4 + exception_no_errcode 4, exception_handler + +bounds_check: ; vector_no = 5 + exception_no_errcode 5, exception_handler + +inval_opcode: ; vector_no = 6 + exception_no_errcode 6, exception_handler + +copr_not_available: ; vector_no = 7 + exception_no_errcode 7, exception_handler + +double_fault: ; vector_no = 8 + exception_errcode 8, exception_handler + +copr_seg_overrun: ; vector_no = 9 + exception_no_errcode 9, exception_handler + +inval_tss: ; vector_no = 10 + exception_errcode 10, exception_handler + +segment_not_present: ; vector_no = 11 + exception_errcode 11, exception_handler + +stack_exception: ; vector_no = 12 + exception_errcode 12, exception_handler + +general_protection: ; vector_no = 13 + exception_errcode 13, exception_handler + +page_fault: ; vector_no = 14 + exception_errcode 14, page_fault_handler + +copr_error: ; vector_no = 16 + exception_no_errcode 16, exception_handler + +;environment saving when an exception occurs +;added by xw, 18/12/18 +save_exception: + pushad ; `. + push ds ; | + push es ; | 保存原寄存器值 + push fs ; | + push gs ; / + mov dx, ss + mov ds, dx + mov es, dx + mov fs, dx ;value of fs and gs in user process is different to that in kernel + mov dx, SELECTOR_VIDEO - 2 ;added by xw, 18/6/20 + mov gs, dx + + mov esi, esp + push restart_exception + jmp [esi + RETADR - P_STACKBASE] ;the err code is in higher address than retaddr in stack, so there is + ;no need to modify the position jumped to, that is, + ;"jmp [esi + RETADR - 4 - P_STACKBASE]" is actually wrong. + +;added by xw, 18/12/18 +restart_exception: + call sched + pop gs + pop fs + pop es + pop ds + popad + add esp, 4 * 2 ;clear retaddr and error code in stack + iretd + +; ==================================================================================== +; save +; ==================================================================================== +save_int: + pushad ; `. + push ds ; | + push es ; | 保存原寄存器值 + push fs ; | + push gs ; / + + cmp dword [k_reenter], 0 ;Added by xw, 18/4/19 + jnz instack + + mov ebx, [p_proc_current] ;xw + mov dword [ebx + ESP_SAVE_INT], esp ;xw save esp position in the kernel-stack of the process +; or dword [ebx + SAVE_TYPE], 1 ;set 1st-bit of save_type, added by xw, 17/12/04 + mov dx, ss + mov ds, dx + mov es, dx + mov fs, dx ;value of fs and gs in user process is different to that in kernel + mov dx, SELECTOR_VIDEO - 2 ;added by xw, 18/6/20 + mov gs, dx + + mov esi, esp + mov esp, StackTop ;switches to the irq-stack from current process's kernel stack + push restart_int ;added by xw, 18/4/19 + jmp [esi + RETADR - P_STACKBASE] +instack: ;already in the irq-stack + push restart_restore ;modified by xw, 18/4/19 +; jmp [esp + RETADR - P_STACKBASE] + jmp [esp + 4 + RETADR - P_STACKBASE] ;modified by xw, 18/6/4 + + +save_syscall: ;can't modify EAX, for it contains syscall number + ;can't modify EBX, for it contains the syscall argument + pushad ; `. + push ds ; | + push es ; | 保存原寄存器值 + push fs ; | + push gs ; / + mov edx, [p_proc_current] ;xw + mov dword [edx + ESP_SAVE_SYSCALL], esp ;xw save esp position in the kernel-stack of the process + mov dx, ss + mov ds, dx + mov es, dx + mov fs, dx ;value of fs and gs in user process is different to that in kernel + mov dx, SELECTOR_VIDEO - 2 ;added by xw, 18/6/20 + mov gs, dx + + mov esi, esp + push restart_syscall ;modified by xw, 17/12/04 + jmp [esi + RETADR - P_STACKBASE] +;modified end + +; ==================================================================================== +; sched(process switch) +; ==================================================================================== +sched: +;could be called by C function, you must save ebp, ebx, edi, esi, +;for C function assumes that they stay unchanged. added by xw, 18/4/19 + +;save_context + pushfd + pushad ;modified by xw, 18/6/4 + cli + mov ebx, [p_proc_current] + mov dword [ebx + ESP_SAVE_CONTEXT], esp ;save esp position in the kernel-stack of the process +;schedule + call schedule ;schedule is a C function, save eax, ecx, edx if you want them to stay unchanged. +;prepare to run new process + mov ebx, [p_proc_next] ;added by xw, 18/4/26 + mov dword [p_proc_current], ebx + call renew_env ;renew process executing environment + mov ebx, [p_proc_current] + mov esp, [ebx + ESP_SAVE_CONTEXT] ;switch to a new kernel stack + popad + popfd + ret +; ==================================================================================== +; renew_env +; ==================================================================================== +;renew process executing environment. Added by xw, 18/4/19 +renew_env: + call switch_pde ;to change the global variable cr3_ready + mov eax,[cr3_ready] ;to switch the page directory table + mov cr3,eax + + mov eax, [p_proc_current] + lldt [eax + P_LDT_SEL] ;load LDT + lea ebx, [eax + INIT_STACK_SIZE] + mov dword [tss + TSS3_S_SP0], ebx ;renew esp0 + ret + +; ==================================================================================== +; sys_call +; ==================================================================================== +sys_call: +;get syscall number from eax +;syscall that's called gets its argument from pushed ebx +;so we can't modify eax and ebx in save_syscall + call save_syscall ;save registers and some other things. modified by xw, 17/12/11 + sti + push ebx ;push the argument the syscall need + call [sys_call_table + eax * 4] ;将参数压入堆栈后再调用函数 add by visual 2016.4.6 + add esp, 4 ;clear the argument in the stack, modified by xw, 17/12/11 + cli + mov edx, [p_proc_current] + mov esi, [edx + ESP_SAVE_SYSCALL] + mov [esi + EAXREG - P_STACKBASE], eax ;the return value of C function is in EAX + ret + +; ==================================================================================== +; restart +; ==================================================================================== + +restart_int: + mov eax, [p_proc_current] + mov esp, [eax + ESP_SAVE_INT] ;switch back to the kernel stack from the irq-stack + cmp dword [kernel_initial], 0 ;added by xw, 18/6/10 + jnz restart_restore + call sched ;save current process's context, invoke schedule(), and then + ;switch to the chosen process's kernel stack and restore it's context + ;added by xw, 18/4/19 +; call renew_env + jmp restart_restore + +restart_syscall: + mov eax, [p_proc_current] + mov esp, [eax + ESP_SAVE_SYSCALL] ;xw restore esp position + call sched ;added by xw, 18/4/26 + jmp restart_restore + +;xw restart_reenter: +restart_restore: +; dec dword [k_reenter] + pop gs + pop fs + pop es + pop ds + popad + add esp, 4 + iretd + +;to launch the first process in the os. added by xw, 18/4/19 +restart_initial: +; mov eax, [p_proc_current] +; lldt [eax + P_LDT_SEL] +; lea ebx, [eax + INIT_STACK_SIZE] +; mov dword [tss + TSS3_S_SP0], ebx + call renew_env ;renew process executing environment + mov eax, [p_proc_current] + mov esp, [eax + ESP_SAVE_INT] ;restore esp position + jmp restart_restore + +; ==================================================================================== +; read_cr2 //add by visual 2016.5.9 +; ==================================================================================== +read_cr2: + mov eax,cr2 + ret + +; ==================================================================================== +; refresh_page_cache //add by visual 2016.5.12 +; ==================================================================================== +refresh_page_cache: + mov eax,cr3 + mov cr3,eax + ret + +; ==================================================================================== +; halt //added by xw, 18/6/11 +; ==================================================================================== +halt: + hlt + +; ==================================================================================== +; u32 get_arg(void *uesp, int order) //added by xw, 18/6/18 +; ==================================================================================== +; used to get the specified argument of the syscall from user space stack +; @uesp: user space stack pointer +; @order: which argument you want to get +; @uesp+0: the number of args, @uesp+8: the first arg, @uesp+12: the second arg... +get_arg: + push ebp + mov ebp, esp + push esi + push edi + mov esi, dword [ebp + 8] ;void *uesp + mov edi, dword [ebp + 12] ;int order + mov eax, dword [esi + edi * 4 + 4] + pop edi + pop esi + pop ebp + ret \ No newline at end of file diff --git a/kernel/keyboard.c b/kernel/keyboard.c new file mode 100644 index 0000000..c96571a --- /dev/null +++ b/kernel/keyboard.c @@ -0,0 +1,443 @@ +#include "type.h" +#include "const.h" +#include "protect.h" +#include "string.h" +#include "proc.h" +#include "tty.h" +#include "console.h" +#include "global.h" +#include "proto.h" +#include "keyboard.h" +#include "keymap.h" +#include "x86.h" + + +static KB_INPUT kb_in; +static MOUSE_INPUT mouse_in; +static int mouse_init; + +static int code_with_E0; +static int shift_l; /* l shift state */ +static int shift_r; /* r shift state */ +static int alt_l; /* l alt state */ +static int alt_r; /* r left state */ +static int ctrl_l; /* l ctrl state */ +static int ctrl_r; /* l ctrl state */ +static int caps_lock; /* Caps Lock */ +static int num_lock; /* Num Lock */ +static int scroll_lock; /* Scroll Lock */ +static int column; + +static u8 get_byte_from_kb_buf(); +static void set_leds(); +static void set_mouse_leds(); +static void kb_wait(); +// static void kb_ack(); + + +void kb_handler(int irq){ + u8 scan_code = inb(0x60); + if(kb_in.count < KB_IN_BYTES){ + *(kb_in.p_head) = scan_code; + kb_in.p_head++; + if(kb_in.p_head==kb_in.buf+KB_IN_BYTES){ + kb_in.p_head = kb_in.buf; + } + kb_in.count++; + } + +}; + +#define TTY_FIRST (tty_table) +#define TTY_END (tty_table+NR_CONSOLES) + +void mouse_handler(int irq){ + u8 scan_code = inb(0x60); + if(!mouse_init){ + mouse_init = 1; + return; + } + mouse_in.buf[mouse_in.count]=scan_code; + mouse_in.count++; + if(mouse_in.count==3){ + TTY* p_tty; + for (p_tty = TTY_FIRST; p_tty < TTY_END; p_tty++) { + if(p_tty->console==&console_table[current_console]){ + p_tty->mouse_left_button = mouse_in.buf[0]&0x01; + + u8 mid_button = mouse_in.buf[0]&0b100; + if(mid_button==0b100){ + p_tty->mouse_mid_button = 1; + }else{ + p_tty->mouse_mid_button = 0; + } + + if(p_tty->mouse_left_button){ + u8 dir_Y = mouse_in.buf[0]&0x20; + u8 dir_X = mouse_in.buf[0]&0x10; + if(dir_Y==0x20){//down + p_tty->mouse_Y -= 1; + }else{//up + p_tty->mouse_Y += 1; + } + + if(dir_X==0x10){//left + p_tty->mouse_X -= 1; + }else{//right + p_tty->mouse_X += 1; + } + } + } + } + + mouse_in.count=0; + } + + +} + +void init_mouse(){ + mouse_in.count = 0; + + put_irq_handler(MOUSE_IRQ,mouse_handler); + enable_irq(MOUSE_IRQ); + +} + +void init_kb(){ + kb_in.count = 0; + kb_in.p_head = kb_in.p_tail = kb_in.buf; + + shift_l = shift_r = 0; + alt_l = alt_r = 0; + ctrl_l = ctrl_r = 0; + + caps_lock = 0; + num_lock = 1; + scroll_lock = 0; + + column = 0; + + set_leds(); + put_irq_handler(KEYBOARD_IRQ, kb_handler); + enable_irq(KEYBOARD_IRQ); + + init_mouse(); + set_mouse_leds(); + +} + +void keyboard_read(TTY* p_tty) +{ + u8 scan_code; + + /** + * 1 : make + * 0 : break + */ + int make; + + /** + * We use a integer to record a key press. + * For instance, if the key HOME is pressed, key will be evaluated to + * `HOME' defined in keyboard.h. + */ + u32 key = 0; + + + /** + * This var points to a row in keymap[]. I don't use two-dimension + * array because I don't like it. + */ + u32* keyrow; + + while (kb_in.count > 0) { + code_with_E0 = 0; + scan_code = get_byte_from_kb_buf(); + + /* parse the scan code below */ + if (scan_code == 0xE1) { + int i; + u8 pausebreak_scan_code[] = {0xE1, 0x1D, 0x45, 0xE1, 0x9D, 0xC5}; + int is_pausebreak = 1; + for (i = 1; i < 6; i++) { + if (get_byte_from_kb_buf() != pausebreak_scan_code[i]) { + is_pausebreak = 0; + break; + } + } + if (is_pausebreak) { + key = PAUSEBREAK; + } + } + else if (scan_code == 0xE0) { + code_with_E0 = 1; + scan_code = get_byte_from_kb_buf(); + + /* PrintScreen is pressed */ + if (scan_code == 0x2A) { + code_with_E0 = 0; + if ((scan_code = get_byte_from_kb_buf()) == 0xE0) { + code_with_E0 = 1; + if ((scan_code = get_byte_from_kb_buf()) == 0x37) { + key = PRINTSCREEN; + make = 1; + } + } + } + /* PrintScreen is released */ + else if (scan_code == 0xB7) { + code_with_E0 = 0; + if ((scan_code = get_byte_from_kb_buf()) == 0xE0) { + code_with_E0 = 1; + if ((scan_code = get_byte_from_kb_buf()) == 0xAA) { + key = PRINTSCREEN; + make = 0; + } + } + } + } + + if ((key != PAUSEBREAK) && (key != PRINTSCREEN)) { + int caps; + + /* make or break */ + make = (scan_code & FLAG_BREAK ? 0 : 1); + + keyrow = &keymap[(scan_code & 0x7F) * MAP_COLS]; + + column = 0; + + caps = shift_l || shift_r; + if (caps_lock && + keyrow[0] >= 'a' && keyrow[0] <= 'z') + caps = !caps; + + if (caps) + column = 1; + + if (code_with_E0) + column = 2; + + key = keyrow[column]; + + switch(key) { + case SHIFT_L: + shift_l = make; + break; + case SHIFT_R: + shift_r = make; + break; + case CTRL_L: + ctrl_l = make; + break; + case CTRL_R: + ctrl_r = make; + break; + case ALT_L: + alt_l = make; + break; + case ALT_R: + alt_l = make; + break; + case CAPS_LOCK: + if (make) { + caps_lock = !caps_lock; + set_leds(); + } + break; + case NUM_LOCK: + if (make) { + num_lock = !num_lock; + set_leds(); + } + break; + case SCROLL_LOCK: + if (make) { + scroll_lock = !scroll_lock; + set_leds(); + } + break; + default: + break; + } + } + + if(make){ /* Break Code is ignored */ + int pad = 0; + + /* deal with the numpad first */ + if ((key >= PAD_SLASH) && (key <= PAD_9)) { + pad = 1; + switch(key) { /* '/', '*', '-', '+', + * and 'Enter' in num pad + */ + case PAD_SLASH: + key = '/'; + break; + case PAD_STAR: + key = '*'; + break; + case PAD_MINUS: + key = '-'; + break; + case PAD_PLUS: + key = '+'; + break; + case PAD_ENTER: + key = ENTER; + break; + default: + /* the value of these keys + * depends on the Numlock + */ + if (num_lock) { /* '0' ~ '9' and '.' in num pad */ + if (key >= PAD_0 && key <= PAD_9) + key = key - PAD_0 + '0'; + else if (key == PAD_DOT) + key = '.'; + } + else{ + switch(key) { + case PAD_HOME: + key = HOME; + break; + case PAD_END: + key = END; + break; + case PAD_PAGEUP: + key = PAGEUP; + break; + case PAD_PAGEDOWN: + key = PAGEDOWN; + break; + case PAD_INS: + key = INSERT; + break; + case PAD_UP: + key = UP; + break; + case PAD_DOWN: + key = DOWN; + break; + case PAD_LEFT: + key = LEFT; + break; + case PAD_RIGHT: + key = RIGHT; + break; + case PAD_DOT: + key = DELETE; + break; + default: + break; + } + } + break; + } + } + key |= shift_l ? FLAG_SHIFT_L : 0; + key |= shift_r ? FLAG_SHIFT_R : 0; + key |= ctrl_l ? FLAG_CTRL_L : 0; + key |= ctrl_r ? FLAG_CTRL_R : 0; + key |= alt_l ? FLAG_ALT_L : 0; + key |= alt_r ? FLAG_ALT_R : 0; + key |= pad ? FLAG_PAD : 0; + + in_process(p_tty,key); + } + } +} + + + +/***************************************************************************** + * get_byte_from_kb_buf + *****************************************************************************/ +/** + * Read a byte from the keyboard buffer. + * + * @return The byte read. + *****************************************************************************/ +static u8 get_byte_from_kb_buf() +{ + u8 scan_code; + + while (kb_in.count <= 0) {} /* wait for a byte to arrive */ + + disable_int(); /* for synchronization */ + scan_code = *(kb_in.p_tail); + kb_in.p_tail++; + if (kb_in.p_tail == kb_in.buf + KB_IN_BYTES) { + kb_in.p_tail = kb_in.buf; + } + kb_in.count--; + enable_int(); /* for synchronization */ + + return scan_code; +} + + +/***************************************************************************** + * kb_wait + *****************************************************************************/ +/** + * Wait until the input buffer of 8042 is empty. + * + *****************************************************************************/ +static void kb_wait() /* 等待 8042 的输入缓冲区空 */ +{ + u8 kb_stat; + + do { + kb_stat = inb(KB_CMD); + + } while (kb_stat & 0x02); +} + + +/***************************************************************************** + * kb_ack + *****************************************************************************/ +/** + * Read from the keyboard controller until a KB_ACK is received. + * + *****************************************************************************/ +// static void kb_ack() +// { +// u8 kb_read; + +// do { +// kb_read = inb(KB_DATA); +// } while (kb_read != KB_ACK); +// } + + +/***************************************************************************** + * set_leds + *****************************************************************************/ +/** + * Set the leds according to: caps_lock, num_lock & scroll_lock. + * + *****************************************************************************/ +static void set_leds() +{ + kb_wait(); + outb(KB_CMD, KEYCMD_WRITE_MODE); + kb_wait(); + outb(KB_DATA, KBC_MODE); +} + +static void set_mouse_leds(){ + kb_wait(); + outb(KB_CMD,KBCMD_EN_MOUSE_INTFACE); + kb_wait(); + outb(KB_CMD, KEYCMD_SENDTO_MOUSE); + kb_wait(); + outb(KB_DATA, MOUSECMD_ENABLE); + kb_wait(); + outb(KB_CMD, KEYCMD_WRITE_MODE); + kb_wait(); + outb(KB_DATA, KBC_MODE); +} + diff --git a/kernel/ktest.c b/kernel/ktest.c new file mode 100644 index 0000000..37c9ad6 --- /dev/null +++ b/kernel/ktest.c @@ -0,0 +1,133 @@ +/* + * To test if new kernel features work normally, and if old features still + * work normally with new features added. + * added by xw, 18/4/27 + */ +#include "type.h" +#include "const.h" +#include "protect.h" +#include "string.h" +#include "proc.h" +#include "global.h" +#include "proto.h" +#include "fs.h" +#include "vfs.h" +#include "string.h" + +/** + * @struct posix_tar_header + * Borrowed from GNU `tar' + */ +//added by mingxuan 2019-5-18 +struct posix_tar_header +{ /* byte offset */ + char name[100]; /* 0 */ + char mode[8]; /* 100 */ + char uid[8]; /* 108 */ + char gid[8]; /* 116 */ + char size[12]; /* 124 */ + char mtime[12]; /* 136 */ + char chksum[8]; /* 148 */ + char typeflag; /* 156 */ + char linkname[100]; /* 157 */ + char magic[6]; /* 257 */ + char version[2]; /* 263 */ + char uname[32]; /* 265 */ + char gname[32]; /* 297 */ + char devmajor[8]; /* 329 */ + char devminor[8]; /* 337 */ + char prefix[155]; /* 345 */ + /* 500 */ +}; + +/***************************************************************************** + * untar + * added by mingxuan 2019-5-18 + *****************************************************************************/ +/** + * Extract the tar file and store them. + * + * @param filename The tar file. + *****************************************************************************/ +static void untar(const char * filename) +{ + printf("[extract %s \n", filename); + int fd = do_vopen(filename, O_RDWR);//modified by mingxuan 2019-5-20 + + char buf[512 * 16]; + int chunk = sizeof(buf); + int i = 0; + + while (1) { + do_vread(fd, buf, 512); //modified by mingxuan 2019-5-21 + if (buf[0] == 0) { + if (i == 0) + printf(" need not unpack the file.\n"); + break; + } + i++; + + struct posix_tar_header * phdr = (struct posix_tar_header *)buf; + + /* calculate the file size */ + char * p = phdr->size; + int f_len = 0; + while (*p) + f_len = (f_len * 8) + (*p++ - '0'); /* octal */ + + int bytes_left = f_len; + char full_name[30] = "orange/"; + strcat(full_name,phdr->name); + int fdout = do_vopen(full_name, O_CREAT | O_RDWR ); //modified by mingxuan 2019-5-20 + if (fdout == -1) { + printf(" failed to extract file: %s\n", phdr->name); + printf(" aborted]\n"); + do_vclose(fd); //modified by mingxuan 2019-5-20 + return; + } + printf(" %s \n", phdr->name); //deleted by mingxuan 2019-5-22 + while (bytes_left) { + int iobytes = min(chunk, bytes_left); + + do_vread(fd, buf, ((iobytes - 1) / 512 + 1) * 512); //modified by mingxuan 2019-5-21 + + do_vwrite(fdout, buf, iobytes); //modified by mingxuan 2019-5-21 + bytes_left -= iobytes; + } + do_vclose(fdout); //modified by mingxuan 2019-5-20 + } + + if (i) { + do_vlseek(fd, 0, SEEK_SET); //modified by mingxuan 2019-5-20 + buf[0] = 0; + do_vwrite(fd, buf, 1); //modified by mingxuan 2019-5-20 + } + + do_vclose(fd); //modified by mingxuan 2019-5-21 + + printf(" done, %d files extracted]\n", i); +} + + +void initial() +{ + + int stdin = do_vopen("dev_tty0",O_RDWR); + int stdout= do_vopen("dev_tty0",O_RDWR); + int stderr= do_vopen("dev_tty0",O_RDWR); + + //untar(INSTALL_FILENAME); + //modified by mingxuan 2019-5-21 + char full_name[30]="orange/";; + printf("untar:%s\n",full_name); + strcat(full_name,INSTALL_FILENAME); + untar(full_name); + + do_vclose(stdin); + do_vclose(stdout); + do_vclose(stderr); + + exec("orange/shell_0.bin"); + + while(1); +} \ No newline at end of file diff --git a/kernel/main.c b/kernel/main.c new file mode 100644 index 0000000..70f5d1a --- /dev/null +++ b/kernel/main.c @@ -0,0 +1,472 @@ + +/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + main.c +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + Forrest Yu, 2005 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + +#include "type.h" +#include "const.h" +#include "protect.h" +#include "string.h" +#include "proc.h" +#include "global.h" +#include "proto.h" +#include "fs_const.h" +#include "hd.h" +#include "fs.h" +#include "memman.h" +#include "vfs.h" +#include "fat32.h" +#include "x86.h" +#include "assert.h" +#include "stdio.h" + +static int initialize_processes(); //added by xw, 18/5/26 +static int initialize_cpus(); //added by xw, 18/6/2 + +/*======================================================================* + kernel_main + *======================================================================*/ +int kernel_main() +{ + int error; + + disp_pos = 0; + for (int i = 0; i < 25; i++) { + for (int j = 0; j < 80; j++) { + kprintf(" "); + } + } + disp_pos = 0; + + kprintf("-----Kernel Initialization Begins-----\n"); + kernel_initial = 1; //kernel is in initial state. added by xw, 18/5/31 + + init();//内存管理模块的初始化 add by liang + + //initialize PCBs, added by xw, 18/5/26 + error = initialize_processes(); + if(error != 0) + return error; + + //initialize CPUs, added by xw, 18/6/2 + error = initialize_cpus(); + if(error != 0) + return error; + + k_reenter = 0; //record nest level of only interruption! it's different from Orange's. + //usage modified by xw + ticks = 0; //initialize system-wide ticks + p_proc_current = cpu_table; + + /************************************************************************ + *device initialization + added by xw, 18/6/4 + *************************************************************************/ + /* initialize 8253 PIT */ + outb(TIMER_MODE, RATE_GENERATOR); + outb(TIMER0, (u8) (TIMER_FREQ/HZ) ); + outb(TIMER0, (u8) ((TIMER_FREQ/HZ) >> 8)); + + /* initialize clock-irq */ + put_irq_handler(CLOCK_IRQ, clock_handler); /* 设定时钟中断处理程序 */ + enable_irq(CLOCK_IRQ); /* 让8259A可以接收时钟中断 */ + + init_kb(); //added by mingxuan 2019-5-19 + + /* initialize hd-irq and hd rdwt queue */ + init_hd(); + + /* enable interrupt, we should read information of some devices by interrupt. + * Note that you must have initialized all devices ready before you enable + * interrupt. added by xw + */ + enable_int(); + + /*********************************************************************** + open hard disk and initialize file system + coded by zcr on 2017.6.10. added by xw, 18/5/31 + ************************************************************************/ + init_fileop_table(); //added by mingxuan 2019-5-17 + + //hd_open(MINOR(ROOT_DEV)); + hd_open(PRIMARY_MASTER); //modified by mingxuan 2020-10-27 + + init_vfs(); //added by mingxuan 2020-10-30 + init_fs(); + init_fs_fat(); //added by mingxuan 2019-5-17 + //init_vfs(); //added by mingxuan 2019-5-17 //deleted by mingxuan 2020-10-30 + + /************************************************************************* + *第一个进程开始启动执行 + **************************************************************************/ + /* we don't want interrupt happens before processes run. + * added by xw, 18/5/31 + */ + disable_int(); + + kprintf("-----Processes Begin-----\n"); + + /* linear address 0~8M will no longer be mapped to physical address 0~8M. + * note that disp_xx can't work after this function is invoked until processes runs. + * add by visual 2016.5.13; moved by xw, 18/5/30 + */ + clear_kernel_pagepte_low(); + + p_proc_current = proc_table; + kernel_initial = 0; //kernel initialization is done. added by xw, 18/5/31 + restart_initial(); //modified by xw, 18/4/19 + while(1){} +} + +/************************************************************************* +return 0 if there is no error, or return -1. +added by xw, 18/6/2 +***************************************************************************/ +static int initialize_cpus() +{ + //just use the fields of struct PCB in cpu_table, we needn't initialize + //something at present. + + return 0; +} + +/************************************************************************* +进程初始化部分 +return 0 if there is no error, or return -1. +moved from kernel_main() by xw, 18/5/26 +***************************************************************************/ +static int initialize_processes() +{ + TASK* p_task = task_table; + PROCESS* p_proc = proc_table; + u16 selector_ldt = SELECTOR_LDT_FIRST; + char* p_regs; //point to registers in the new kernel stack, added by xw, 17/12/11 + task_f eip_context; //a funtion pointer, added by xw, 18/4/18 + /************************************************************************* + *进程初始化部分 edit by visual 2016.5.4 + ***************************************************************************/ + int pid; + u32 AddrLin,err_temp;//edit by visual 2016.5.9 + + /* set common fields in PCB. added by xw, 18/5/25 */ + p_proc = proc_table; + for( pid=0 ; pid对前NR_TASKS个PCB初始化,且状态为READY(生成的进程) + /*************基本信息*********************************/ + strcpy(p_proc->task.p_name, p_task->name); //名称 + p_proc->task.pid = pid; //pid + p_proc->task.stat = READY; //状态 + + /**************LDT*********************************/ + p_proc->task.ldt_sel = selector_ldt; + memcpy(&p_proc->task.ldts[0], &gdt[SELECTOR_KERNEL_CS >> 3],sizeof(DESCRIPTOR)); + p_proc->task.ldts[0].attr1 = DA_C | PRIVILEGE_TASK << 5; + memcpy(&p_proc->task.ldts[1], &gdt[SELECTOR_KERNEL_DS >> 3],sizeof(DESCRIPTOR)); + p_proc->task.ldts[1].attr1 = DA_DRW | PRIVILEGE_TASK << 5; + + /**************寄存器初值**********************************/ + p_proc->task.regs.cs = ((8 * 0) & SA_RPL_MASK & SA_TI_MASK)| SA_TIL | RPL_TASK; + p_proc->task.regs.ds = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK)| SA_TIL | RPL_TASK; + p_proc->task.regs.es = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK)| SA_TIL | RPL_TASK; + p_proc->task.regs.fs = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK)| SA_TIL | RPL_TASK; + p_proc->task.regs.ss = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK)| SA_TIL | RPL_TASK; + p_proc->task.regs.gs = (SELECTOR_KERNEL_GS & SA_RPL_MASK)| RPL_TASK; + p_proc->task.regs.eflags = 0x1202; /* IF=1, IOPL=1 */ + //p_proc->task.cr3 在页表初始化中处理 + + + /**************线性地址布局初始化**********************************/// add by visual 2016.5.4 + /**************task的代码数据大小及位置暂时是不会用到的,所以没有初始化************************************/ + p_proc->task.memmap.heap_lin_base = HeapLinBase; + p_proc->task.memmap.heap_lin_limit = HeapLinBase; //堆的界限将会一直动态变化 + + p_proc->task.memmap.stack_child_limit = StackLinLimitMAX; //add by visual 2016.5.27 + p_proc->task.memmap.stack_lin_base = StackLinBase; + p_proc->task.memmap.stack_lin_limit = StackLinBase - 0x4000; //栈的界限将会一直动态变化,目前赋值为16k,这个值会根据esp的位置进行调整,目前初始化为16K大小 + + p_proc->task.memmap.kernel_lin_base = KernelLinBase; + p_proc->task.memmap.kernel_lin_limit = KernelLinBase + KernelSize; //内核大小初始化为8M //add by visual 2016.5.10 + + /***************初始化PID进程页表*****************************/ + if( 0 != init_page_pte(pid) ) + { + disp_color_str("kernel_main Error:init_page_pte",0x74); + return -1; + } + // pde_addr_phy_temp = get_pde_phy_addr(pid);//获取该进程页目录物理地址 //delete by visual 2016.5.19 + + /****************代码数据*****************************/ + p_proc->task.regs.eip= (u32)p_task->initial_eip;//进程入口线性地址 edit by visual 2016.5.4 + + /****************栈(此时堆、栈已经区分,以后实验会重新规划堆的位置)*****************************/ + p_proc->task.regs.esp=(u32)StackLinBase; //栈地址最高处 + for( AddrLin=StackLinBase ; AddrLin>p_proc->task.memmap.stack_lin_limit ; AddrLin-=num_4K ) + {//栈 + //addr_phy_temp = (u32)do_kmalloc_4k();//为栈申请一个物理页,Task的栈是在内核里面 //delete by visual 2016.5.19 + //if( addr_phy_temp<0 || (addr_phy_temp&0x3FF)!=0 ) + //{ + // disp_color_str("kernel_main Error:addr_phy_temp",0x74); + // return -1; + //} + err_temp = lin_mapping_phy( AddrLin,//线性地址 //add by visual 2016.5.9 + MAX_UNSIGNED_INT,//物理地址 //edit by visual 2016.5.19 + pid,//进程pid //edit by visual 2016.5.19 + PG_P | PG_USU | PG_RWW,//页目录的属性位 + PG_P | PG_USU | PG_RWW);//页表的属性位 + if( err_temp!=0 ) + { + disp_color_str("kernel_main Error:lin_mapping_phy",0x74); + return -1; + } + + } + + /***************copy registers data to kernel stack****************************/ + //copy registers data to the bottom of the new kernel stack + //added by xw, 17/12/11 + p_regs = (char*)(p_proc + 1); + p_regs -= P_STACKTOP; + memcpy(p_regs, (char*)p_proc, 18 * 4); + + /***************some field about process switch****************************/ + p_proc->task.esp_save_int = p_regs; //initialize esp_save_int, added by xw, 17/12/11 + //p_proc->task.save_type = 1; + p_proc->task.esp_save_context = p_regs - 10 * 4; //when the process is chosen to run for the first time, + //sched() will fetch value from esp_save_context + eip_context = restart_restore; + *(u32*)(p_regs - 4) = (u32)eip_context; //initialize EIP in the context, so the process can + //start run. added by xw, 18/4/18 + *(u32*)(p_regs - 8) = 0x1202; //initialize EFLAGS in the context, IF=1, IOPL=1. xw, 18/4/20 + + /***************变量调整****************************/ + p_proc++; + p_task++; + selector_ldt += 1 << 3; + } + for( ; pid对中NR_TASKS~NR_K_PCBS的PCB表初始化,状态为IDLE,没有初始化esp(并没有生成,所以没有代码入口,只是留位置) + /*************基本信息*********************************/ + strcpy(p_proc->task.p_name, "Task"); //名称 + p_proc->task.pid = pid; //pid + p_proc->task.stat = IDLE; //状态 + + /**************LDT*********************************/ + p_proc->task.ldt_sel = selector_ldt; + memcpy(&p_proc->task.ldts[0], &gdt[SELECTOR_KERNEL_CS >> 3],sizeof(DESCRIPTOR)); + p_proc->task.ldts[0].attr1 = DA_C | PRIVILEGE_TASK << 5; + memcpy(&p_proc->task.ldts[1], &gdt[SELECTOR_KERNEL_DS >> 3],sizeof(DESCRIPTOR)); + p_proc->task.ldts[1].attr1 = DA_DRW | PRIVILEGE_TASK << 5; + + /**************寄存器初值**********************************/ + p_proc->task.regs.cs = ((8 * 0) & SA_RPL_MASK & SA_TI_MASK)| SA_TIL | RPL_TASK; + p_proc->task.regs.ds = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK)| SA_TIL | RPL_TASK; + p_proc->task.regs.es = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK)| SA_TIL | RPL_TASK; + p_proc->task.regs.fs = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK)| SA_TIL | RPL_TASK; + p_proc->task.regs.ss = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK)| SA_TIL | RPL_TASK; + p_proc->task.regs.gs = (SELECTOR_KERNEL_GS & SA_RPL_MASK)| RPL_TASK; + p_proc->task.regs.eflags = 0x1202; /* IF=1, IOPL=1 */ + + /****************页表、代码数据、堆栈*****************************/ + //无 + + /***************copy registers data to kernel stack****************************/ + //copy registers data to the bottom of the new kernel stack + //added by xw, 17/12/11 + p_regs = (char*)(p_proc + 1); + p_regs -= P_STACKTOP; + memcpy(p_regs, (char*)p_proc, 18 * 4); + + /***************some field about process switch****************************/ + p_proc->task.esp_save_int = p_regs; //initialize esp_save_int, added by xw, 17/12/11 + //p_proc->task.save_type = 1; + p_proc->task.esp_save_context = p_regs - 10 * 4; //when the process is chosen to run for the first time, + //sched() will fetch value from esp_save_context + eip_context = restart_restore; + *(u32*)(p_regs - 4) = (u32)eip_context; //initialize EIP in the context, so the process can + //start run. added by xw, 18/4/18 + *(u32*)(p_regs - 8) = 0x1202; //initialize EFLAGS in the context, IF=1, IOPL=1. xw, 18/4/20 + + /***************变量调整****************************/ + p_proc++; + selector_ldt += 1 << 3; + } + for( ; pidtask.p_name,"initial"); //名称 + p_proc->task.pid = pid; //pid + p_proc->task.stat = READY; //状态 + + /**************LDT*********************************/ + p_proc->task.ldt_sel = selector_ldt; + memcpy(&p_proc->task.ldts[0], &gdt[SELECTOR_KERNEL_CS >> 3],sizeof(DESCRIPTOR)); + p_proc->task.ldts[0].attr1 = DA_C | PRIVILEGE_TASK << 5; + memcpy(&p_proc->task.ldts[1], &gdt[SELECTOR_KERNEL_DS >> 3],sizeof(DESCRIPTOR)); + p_proc->task.ldts[1].attr1 = DA_DRW | PRIVILEGE_TASK << 5; + + /**************寄存器初值**********************************/ + p_proc->task.regs.cs = ((8 * 0) & SA_RPL_MASK & SA_TI_MASK)| SA_TIL | RPL_TASK; + p_proc->task.regs.ds = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK)| SA_TIL | RPL_TASK; + p_proc->task.regs.es = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK)| SA_TIL | RPL_TASK; + p_proc->task.regs.fs = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK)| SA_TIL | RPL_TASK; + p_proc->task.regs.ss = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK)| SA_TIL | RPL_TASK; + p_proc->task.regs.gs = (SELECTOR_KERNEL_GS & SA_RPL_MASK)| RPL_TASK; + p_proc->task.regs.eflags = 0x1202; /* IF=1, IOPL=1 */ + //p_proc->task.cr3 在页表初始化中处理 + + + /**************线性地址布局初始化**********************************/ //edit by visual 2016.5.25 + p_proc->task.memmap.text_lin_base = 0; //initial这些段的数据并不清楚,在变身init的时候才在中赋新值 + p_proc->task.memmap.text_lin_limit = 0; //initial这些段的数据并不清楚,在变身init的时候才在exec中赋新值 + p_proc->task.memmap.data_lin_base = 0; //initial这些段的数据并不清楚,在变身init的时候才在exec中赋新值 + p_proc->task.memmap.data_lin_limit= 0; //initial这些段的数据并不清楚,在变身init的时候才在exec中赋新值 + p_proc->task.memmap.vpage_lin_base = VpageLinBase; //保留内存基址 + p_proc->task.memmap.vpage_lin_limit = VpageLinBase; //保留内存界限 + p_proc->task.memmap.heap_lin_base = HeapLinBase; //堆基址 + p_proc->task.memmap.heap_lin_limit = HeapLinBase; //堆界限 + p_proc->task.memmap.stack_lin_base = StackLinBase; //栈基址 + p_proc->task.memmap.stack_lin_limit = StackLinBase - 0x4000; //栈界限(使用时注意栈的生长方向) + p_proc->task.memmap.arg_lin_base = ArgLinBase; //参数内存基址 + p_proc->task.memmap.arg_lin_limit = ArgLinBase; //参数内存界限 + p_proc->task.memmap.kernel_lin_base = KernelLinBase; //内核基址 + p_proc->task.memmap.kernel_lin_limit = KernelLinBase + KernelSize; //内核大小初始化为8M + + /*************************进程树信息初始化***************************************/ + p_proc->task.info.type = TYPE_PROCESS; //当前是进程还是线程 + p_proc->task.info.real_ppid = -1; //亲父进程,创建它的那个进程 + p_proc->task.info.ppid = -1; //当前父进程 + p_proc->task.info.child_p_num = 0; //子进程数量 + //p_proc->task.info.child_process[NR_CHILD_MAX];//子进程列表 + p_proc->task.info.child_t_num = 0; //子线程数量 + //p_proc->task.info.child_thread[NR_CHILD_MAX];//子线程列表 + p_proc->task.info.text_hold = 1; //是否拥有代码 + p_proc->task.info.data_hold = 1; //是否拥有数据 + + + /***************初始化PID进程页表*****************************/ + if( 0 != init_page_pte(pid) ) + { + disp_color_str("kernel_main Error:init_page_pte",0x74); + return -1; + } + //pde_addr_phy_temp = get_pde_phy_addr(pid);//获取该进程页目录物理地址 //edit by visual 2016.5.19 + + /****************代码数据*****************************/ + p_proc->task.regs.eip= (u32)initial;//进程入口线性地址 edit by visual 2016.5.17 + + /****************栈(此时堆、栈已经区分,以后实验会重新规划堆的位置)*****************************/ + p_proc->task.regs.esp=(u32)StackLinBase; //栈地址最高处 + for( AddrLin=StackLinBase ; AddrLin>p_proc->task.memmap.stack_lin_limit ; AddrLin-=num_4K ) + {//栈 + //addr_phy_temp = (u32)do_kmalloc_4k();//为栈申请一个物理页,Task的栈是在内核里面 //delete by visual 2016.5.19 + //if( addr_phy_temp<0 || (addr_phy_temp&0x3FF)!=0 ) + //{ + // disp_color_str("kernel_main Error:addr_phy_temp",0x74); + // return -1; + //} + err_temp = lin_mapping_phy( AddrLin,//线性地址 + MAX_UNSIGNED_INT,//物理地址 //edit by visual 2016.5.19 + pid,//进程pid //edit by visual 2016.5.19 + PG_P | PG_USU | PG_RWW,//页目录的属性位 + PG_P | PG_USU | PG_RWW);//页表的属性位 + if( err_temp!=0 ) + { + disp_color_str("kernel_main Error:lin_mapping_phy",0x74); + return -1; + } + + } + + /***************copy registers data to kernel stack****************************/ + //copy registers data to the bottom of the new kernel stack + //added by xw, 17/12/11 + p_regs = (char*)(p_proc + 1); + p_regs -= P_STACKTOP; + memcpy(p_regs, (char*)p_proc, 18 * 4); + + /***************some field about process switch****************************/ + p_proc->task.esp_save_int = p_regs; //initialize esp_save_int, added by xw, 17/12/11 + //p_proc->task.save_type = 1; + p_proc->task.esp_save_context = p_regs - 10 * 4; //when the process is chosen to run for the first time, + //sched() will fetch value from esp_save_context + eip_context = restart_restore; + *(u32*)(p_regs - 4) = (u32)eip_context; //initialize EIP in the context, so the process can + //start run. added by xw, 18/4/18 + *(u32*)(p_regs - 8) = 0x1202; //initialize EFLAGS in the context, IF=1, IOPL=1. xw, 18/4/20 + + /***************变量调整****************************/ + p_proc++; + selector_ldt += 1 << 3; + } + for( ; pid对后NR_K_PCBS~NR_PCBS的PCB表部分初始化,(名称,pid,stat,LDT选择子),状态为IDLE. + /*************基本信息*********************************/ + strcpy(p_proc->task.p_name, "USER"); //名称 + p_proc->task.pid = pid; //pid + p_proc->task.stat = IDLE; //状态 + + /**************LDT*********************************/ + p_proc->task.ldt_sel = selector_ldt; + memcpy(&p_proc->task.ldts[0], &gdt[SELECTOR_KERNEL_CS >> 3],sizeof(DESCRIPTOR)); + p_proc->task.ldts[0].attr1 = DA_C | PRIVILEGE_USER << 5; + memcpy(&p_proc->task.ldts[1], &gdt[SELECTOR_KERNEL_DS >> 3],sizeof(DESCRIPTOR)); + p_proc->task.ldts[1].attr1 = DA_DRW | PRIVILEGE_USER << 5; + + /**************寄存器初值**********************************/ + p_proc->task.regs.cs = ((8 * 0) & SA_RPL_MASK & SA_TI_MASK)| SA_TIL | RPL_USER; + p_proc->task.regs.ds = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK)| SA_TIL | RPL_USER; + p_proc->task.regs.es = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK)| SA_TIL | RPL_USER; + p_proc->task.regs.fs = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK)| SA_TIL | RPL_USER; + p_proc->task.regs.ss = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK)| SA_TIL | RPL_USER; + p_proc->task.regs.gs = (SELECTOR_KERNEL_GS & SA_RPL_MASK)| RPL_USER; + p_proc->task.regs.eflags = 0x0202; /* IF=1, 倒数第二位恒为1 */ + + /****************页表、代码数据、堆栈*****************************/ + //无 + + /***************copy registers data to kernel stack****************************/ + //copy registers data to the bottom of the new kernel stack + //added by xw, 17/12/11 + p_regs = (char*)(p_proc + 1); + p_regs -= P_STACKTOP; + memcpy(p_regs, (char*)p_proc, 18 * 4); + + /***************some field about process switch****************************/ + p_proc->task.esp_save_int = p_regs; //initialize esp_save_int, added by xw, 17/12/11 + //p_proc->task.save_type = 1; + p_proc->task.esp_save_context = p_regs - 10 * 4; //when the process is chosen to run for the first time, + //sched() will fetch value from esp_save_context + eip_context = restart_restore; + *(u32*)(p_regs - 4) = (u32)eip_context; //initialize EIP in the context, so the process can + //start run. added by xw, 18/4/18 + *(u32*)(p_regs - 8) = 0x1202; //initialize EFLAGS in the context, IF=1, IOPL=1. xw, 18/4/20 + + /***************变量调整****************************/ + p_proc++; + selector_ldt += 1 << 3; + } + + proc_table[0].task.ticks = proc_table[0].task.priority = 1; + proc_table[1].task.ticks = proc_table[1].task.priority = 1; + proc_table[2].task.ticks = proc_table[2].task.priority = 1; + proc_table[3].task.ticks = proc_table[3].task.priority = 1; //added by xw, 18/8/27 + proc_table[NR_K_PCBS].task.ticks = proc_table[NR_K_PCBS].task.priority = 1; + + /* When the first process begin running, a clock-interruption will happen immediately. + * If the first process's initial ticks is 1, it won't be the first process to execute its + * user code. Thus, it's will look weird, for proc_table[0] don't output first. + * added by xw, 18/4/19 + */ + proc_table[0].task.ticks = 2; + + return 0; +} \ No newline at end of file diff --git a/kernel/memman.c b/kernel/memman.c new file mode 100644 index 0000000..b84c167 --- /dev/null +++ b/kernel/memman.c @@ -0,0 +1,405 @@ +#include "type.h" +#include "const.h" +#include "protect.h" +#include "string.h" +#include "proc.h" +#include "global.h" +#include "proto.h" +#include "memman.h" +#include "stdio.h" + +u32 MemInfo[256] = {0}; //存放FMIBuff后1k内容 +struct MEMMAN s_memman; +struct MEMMAN *memman = &s_memman;//(struct MEMMAN *) MEMMAN_ADDR; + + +void memman_init(struct MEMMAN *man); + u32 memman_alloc(struct MEMMAN *man,u32 size); + u32 memman_kalloc(struct MEMMAN *man,u32 size); + u32 memman_alloc_4k(struct MEMMAN *man); + u32 memman_kalloc_4k(struct MEMMAN *man); + u32 memman_free(struct MEMMAN *man, u32 addr, u32 size); + void disp_free(); +u32 memman_total(struct MEMMAN *man); + +void init() //初始化 +{ + u32 memstart = MEMSTART; //4M 开始初始化 + u32 i,j; + + memcpy(MemInfo,(u32 *)FMIBuff,1024); //复制内存 + + memman_init(memman); //初始化memman中frees,maxfrees,lostsize,losts + + for(i = 1; i <= MemInfo[0]; i++) + { + if(MemInfo[i] < memstart)continue; //4M 之后开始free + memman_free(memman,memstart,MemInfo[i] - memstart); //free每一段可用内存 + memstart = MemInfo[i] + 0x1000; //memtest_sub(start,end)中每4KB检测一次 + } + + for(i = 0; i < memman->frees; i++) + {//6M处分开,4~6M为kmalloc_4k使用,6~8M为kmalloc使用 + if((memman->free[i].addr <= KWALL)&&(memman->free[i].addr + memman->free[i].size > KWALL)){ + if(memman->free[i].addr == KWALL)break; + else{ + + for(j = memman->frees; j>i+1; j--) + { //i之后向后一位 + memman->free[j] = memman->free[j-1]; + } + memman->frees++; + if(memman->maxfrees < memman->frees){ //更新man->maxfrees + memman->maxfrees = memman->frees; + } + memman->free[i+1].addr = KWALL; + memman->free[i+1].size = memman->free[i].addr + memman->free[i].size - KWALL; + memman->free[i].size = KWALL - 0x1000 - memman->free[i].addr; + break; + } + } + } + for(i = 0; i < memman->frees; i++) + {//8M处分开,4~8M为kmalloc使用,8~32M为malloc使用 + if((memman->free[i].addr <= WALL)&&(memman->free[i].addr + memman->free[i].size > WALL)){ + if(memman->free[i].addr == WALL)break; + else{ + + for(j = memman->frees; j>i+1; j--) + { //i之后向后一位 + memman->free[j] = memman->free[j-1]; + } + memman->frees++; + if(memman->maxfrees < memman->frees){ //更新man->maxfrees + memman->maxfrees = memman->frees; + } + memman->free[i+1].addr = WALL; + memman->free[i+1].size = memman->free[i].addr + memman->free[i].size - WALL; + memman->free[i].size = WALL - 0x1000 - memman->free[i].addr; + break; + } + } + } + + for(i = 0; i < memman->frees; i++) + {//16M处分开,8~16M为malloc使用,16~32M为malloc_4k使用 + if((memman->free[i].addr <= UWALL)&&(memman->free[i].addr + memman->free[i].size > UWALL)){ + if(memman->free[i].addr == UWALL)break; + else{ + + for(j = memman->frees; j>i+1; j--) + { //i之后向后一位 + memman->free[j] = memman->free[j-1]; + } + memman->frees++; + if(memman->maxfrees < memman->frees){ //更新man->maxfrees + memman->maxfrees = memman->frees; + } + memman->free[i+1].addr = UWALL; + memman->free[i+1].size = memman->free[i].addr + memman->free[i].size - UWALL; + memman->free[i].size = UWALL - 0x1000 - memman->free[i].addr; + break; + } + } + } + + + //modified by xw, 18/6/18 + //显示初始总容量 + kprintf("Memory Available:%d\n", memman_total(memman)); + //~xw + + return; + +} //于kernel_main()中调用,进行初始化 + +void memman_init(struct MEMMAN *man) +{ //memman基本信息初始化 + man->frees = 0; + man->maxfrees = 0; + man->lostsize = 0; + man->losts = 0; + return; +} + +u32 memman_alloc(struct MEMMAN *man,u32 size) +{ //分配 + u32 i,a; + for(i=0; ifrees; i++) + { + if((man->free[i].addr >= WALL)&&(man->free[i].addr + size < UWALL)){ //8M到16M + if(man->free[i].size >= size){ + a = man->free[i].addr; + man->free[i].addr += size; + man->free[i].size -= size; + if(man->free[i].size == 0){ + man->frees--; + for(; ifrees; i++) + { + man->free[i] = man->free[i+1]; + } + } + return a; + } + } + } + return -1; +} + +u32 memman_kalloc(struct MEMMAN *man,u32 size) +{ //分配 + u32 i,a; + for(i=0; ifrees; i++) + { + if((man->free[i].addr >= KWALL)&&(man->free[i].addr + size < WALL)){ //4M到8M + if(man->free[i].size >= size){ + a = man->free[i].addr; + man->free[i].addr += size; + man->free[i].size -= size; + if(man->free[i].size == 0){ + man->frees--; + for(; ifrees; i++) + { + man->free[i] = man->free[i+1]; + } + } + return a; + } + } + + } + return -1; +} + +u32 memman_alloc_4k(struct MEMMAN *man) +{ //分配 + u32 i,a; + u32 size = 0x1000; + for(i=0; ifrees; i++) + { + if((man->free[i].addr >= UWALL)&&(man->free[i].addr + size < MEMEND)){ //16M到32M + if(man->free[i].size >= size){ + a = man->free[i].addr; + man->free[i].addr += size; + man->free[i].size -= size; + if(man->free[i].size == 0){ + man->frees--; + for(; ifrees; i++) + { + man->free[i] = man->free[i+1]; + } + } + return a; + } + } + } + return -1; +} + +u32 memman_kalloc_4k(struct MEMMAN *man) +{ //分配 + u32 i,a; + u32 size = 0x1000; + for(i=0; ifrees; i++) + { + if((man->free[i].addr >= MEMSTART)&&(man->free[i].addr + size < KWALL)){ //4M到6M + if(man->free[i].size >= size){ + a = man->free[i].addr; + man->free[i].addr += size; + man->free[i].size -= size; + if(man->free[i].size == 0){ + man->frees--; + for(; ifrees; i++) + { + man->free[i] = man->free[i+1]; + } + } + return a; + } + } + } + return -1; +} + +u32 memman_free(struct MEMMAN *man, u32 addr, u32 size) +{ //释放 + int i,j; + + if(size == 0)return 0; //初始化时,防止有连续坏块 + + for(i=0; ifrees; i++) + { + if(man->free[i].addr > addr){ + break; + } + } //man->free[i-1].addr < addr free[i].addr + + if(i > 0){ //前面有可分配内存 + if(man->free[i-1].addr + man->free[i-1].size == addr){ //与前面相邻 + man->free[i-1].size += size; + if(i < man->frees){ //后面有可分配内存 + if(addr + size == man->free[i].addr){ //同时与后面相邻 + man->free[i-1].size += man->free[i].size; + man->frees--; + for(; ifrees; i++) + { + man->free[i] = man->free[i+1]; //结构体赋值,i之后向前一位 + } + } + } + return 0; + } //与前面不相邻 + } //前面无可分配内存 + + if(i < man->frees){ //后面有可分配内存 + if(addr + size == man->free[i].addr){ //与后面相邻 + man->free[i].addr = addr; + man->free[i].size += size; + return 0; + } + } + + if(man->frees < MEMMAN_FREES){ //数组未满 + for(j = man->frees; j>i; j--) + { //i之后向后一位 + man->free[j] = man->free[j-1]; + } + man->frees++; + if(man->maxfrees < man->frees){ //更新man->maxfrees + man->maxfrees = man->frees; + } + man->free[i].addr = addr; //插入 + man->free[i].size = size; + return 0; + } + + man->losts++; //free失败 + man->lostsize += size; + return -1; +} + +u32 memman_free_4k(struct MEMMAN *man, u32 addr) +{ + return memman_free(man, addr, 0x1000); +} + + + +u32 do_malloc(u32 size) +{ + return memman_alloc(memman,size); +} + +u32 do_kmalloc(u32 size) +{ + return memman_kalloc(memman,size); +} + +u32 do_malloc_4k() +{ + return memman_alloc_4k(memman); +} + +u32 do_kmalloc_4k() +{ + return memman_kalloc_4k(memman); +} + +u32 do_free(u32 addr,u32 size) +{ + return memman_free(memman,addr,size); +} + +u32 do_free_4k(u32 addr) +{ + return memman_free_4k(memman,addr); +} + +void disp_free() +{ //打印空闲内存块信息 + for(int i = 0; i < memman->frees; i++) + kprintf("0x%x#0x%x###", memman->free[i].addr, memman->free[i].size); +} + +u32 memman_total(struct MEMMAN *man) +{ //free总容量 + u32 i,t=0; + for(i=0; ifrees; i++){ + t += man->free[i].size; + } + return t; +} + +void memman_test() +{ //测试 + u32 *p1 = 0; + u32 *p2 = 0; + u32 *p3 = 0; + u32 *p4 = 0; + u32 *p = 0; + + p1 = (u32 *)do_malloc(4); + if(-1 != (u32)p1){ //打印p1,当前空闲内存信息,p1所指内存的内容 + disp_str("START"); + disp_int((u32)p1); + //disp_free(); + *p1 = TEST; + disp_int(*p1); + disp_str("END"); + } + + p2 = (u32 *)do_kmalloc(4); + if(-1 != (u32)p2){ + disp_str("START"); + disp_int((u32)p2); + //disp_free(); + *p2 = TEST; + disp_int(*p2); + disp_str("END"); + } + + do_free((u32)p1,4); + do_free((u32)p2,4); + + p3 = (u32 *)do_malloc_4k(); + if(-1 != (u32)p3){ + disp_str("START"); + disp_int((u32)p3); + //disp_free(); + *p3 = TEST; + disp_int(*p3); + p = p3 + 2044; + *p = 0x22334455; + disp_int(*p); + p += 2048; + *p = 0x33445566; + disp_int(*p); + disp_str("END"); + } + + p4 = (u32 *)do_kmalloc_4k(4); + if(-1 != (u32)p4){ + disp_str("START"); + disp_int((u32)p4); + //disp_free(); + *p4 = TEST; + disp_int(*p4); + p = p4 + 2044; + *p = 0x22334455; + disp_int(*p); + p += 2048; + *p = 0x33445566; + disp_int(*p); + disp_str("END"); + } + + do_free_4k((u32)p3); + do_free_4k((u32)p4); + + disp_str("START"); + disp_free(); + disp_str("END"); + + return; +} + diff --git a/kernel/pagetbl.c b/kernel/pagetbl.c new file mode 100644 index 0000000..62427ca --- /dev/null +++ b/kernel/pagetbl.c @@ -0,0 +1,375 @@ +/************************************************************* + *页式管理相关代码 add by visual 2016.4.19 + **************************************************************/ + +#include "type.h" +#include "const.h" +#include "protect.h" +#include "string.h" +#include "proc.h" +#include "global.h" +#include "proto.h" +#include "memman.h" + +// to determine if a page fault is reparable. added by xw, 18/6/11 +u32 cr2_save; +u32 cr2_count = 0; + +/*======================================================================* + switch_pde added by xw, 17/12/11 + *switch the page directory table after schedule() is called + *======================================================================*/ +void switch_pde() +{ + cr3_ready = p_proc_current->task.cr3; +} + +/*======================================================================* + init_page_pte add by visual 2016.4.19 +*该函数只初始化了进程的高端(内核端)地址页表 + *======================================================================*/ +u32 init_page_pte(u32 pid) +{ //页表初始化函数 + + u32 AddrLin, pde_addr_phy_temp, err_temp; + + pde_addr_phy_temp = do_kmalloc_4k(); //为页目录申请一页 + memset((void *)K_PHY2LIN(pde_addr_phy_temp), 0, num_4K); // add by visual 2016.5.26 + + if (pde_addr_phy_temp < 0 || (pde_addr_phy_temp & 0x3FF) != 0) // add by visual 2016.5.9 + { + disp_color_str("init_page_pte Error:pde_addr_phy_temp", 0x74); + return -1; + } + + proc_table[pid].task.cr3 = pde_addr_phy_temp; //初始化了进程表中cr3寄存器变量,属性位暂时不管 + /*********************页表初始化部分*********************************/ + u32 phy_addr = 0; + + for (AddrLin = KernelLinBase, phy_addr = 0; AddrLin < KernelLinBase + KernelSize; AddrLin += num_4K, phy_addr += num_4K) + { //只初始化内核部分,3G后的线性地址映射到物理地址开始处 + err_temp = lin_mapping_phy(AddrLin, //线性地址 //add by visual 2016.5.9 + phy_addr, //物理地址 + pid, //进程pid //edit by visual 2016.5.19 + PG_P | PG_USU | PG_RWW, //页目录的属性位(用户权限) //edit by visual 2016.5.26 + PG_P | PG_USS | PG_RWW); //页表的属性位(系统权限) //edit by visual 2016.5.17 + if (err_temp != 0) + { + disp_color_str("init_page_pte Error:lin_mapping_phy", 0x74); + return -1; + } + } + + return 0; +} + +/*======================================================================* + page_fault_handle edit by visual 2016.5.9 + *======================================================================*/ +void page_fault_handler(u32 vec_no, //异常编号,此时应该是14,代表缺页异常 + u32 err_code, //错误码 + u32 eip, //导致缺页的指令的线性地址 + u32 cs, //发生错误时的代码段寄存器内容 + u32 eflags) //时发生错误的标志寄存器内容 +{ //缺页中断处理函数 + u32 pde_addr_phy_temp; + u32 pte_addr_phy_temp; + u32 cr2; + + cr2 = read_cr2(); + + // if page fault happens in kernel, it's an error. + if (kernel_initial == 1) + { + disp_str("\n"); + disp_color_str("Page Fault\n", 0x74); + disp_color_str("eip=", 0x74); //灰底红字 + disp_int(eip); + disp_color_str("eflags=", 0x74); + disp_int(eflags); + disp_color_str("cs=", 0x74); + disp_int(cs); + disp_color_str("err_code=", 0x74); + disp_int(err_code); + disp_color_str("Cr2=", 0x74); //灰底红字 + disp_int(cr2); + halt(); + } + + //获取该进程页目录物理地址 + pde_addr_phy_temp = get_pde_phy_addr(p_proc_current->task.pid); + //获取该线性地址对应的页表的物理地址 + pte_addr_phy_temp = get_pte_phy_addr(p_proc_current->task.pid, cr2); + + if (cr2 == cr2_save) + { + cr2_count++; + if (cr2_count == 5) + { + disp_str("\n"); + disp_color_str("Page Fault\n", 0x74); + disp_color_str("eip=", 0x74); //灰底红字 + disp_int(eip); + disp_color_str("eflags=", 0x74); + disp_int(eflags); + disp_color_str("cs=", 0x74); + disp_int(cs); + disp_color_str("err_code=", 0x74); + disp_int(err_code); + disp_color_str("Cr2=", 0x74); //灰底红字 + disp_int(cr2); + disp_color_str("Cr3=", 0x74); + disp_int(p_proc_current->task.cr3); + //获取页目录中填写的内容 + disp_color_str("Pde=", 0x74); + disp_int(*((u32 *)K_PHY2LIN(pde_addr_phy_temp) + get_pde_index(cr2))); + //获取页表中填写的内容 + disp_color_str("Pte=", 0x74); + disp_int(*((u32 *)K_PHY2LIN(pte_addr_phy_temp) + get_pte_index(cr2))); + halt(); + } + } + else + { + cr2_save = cr2; + cr2_count = 0; + } + + if (0 == pte_exist(pde_addr_phy_temp, cr2)) + { //页表不存在 + // disp_color_str("[Pde Fault!]",0x74); //灰底红字 + (*((u32 *)K_PHY2LIN(pde_addr_phy_temp) + get_pde_index(cr2))) |= PG_P; + // disp_color_str("[Solved]",0x74); + } + else + { //只是缺少物理页 + // disp_color_str("[Pte Fault!]",0x74); //灰底红字 + (*((u32 *)K_PHY2LIN(pte_addr_phy_temp) + get_pte_index(cr2))) |= PG_P; + // disp_color_str("[Solved]",0x74); + } + refresh_page_cache(); +} + +/***************************地址转换过程*************************** + * + *第一步,CR3包含着页目录的起始地址,用32位线性地址的最高10位A31~A22作为页目录的页目录项的索引, + *将它乘以4,与CR3中的页目录的起始地址相加,形成相应页表的地址。 + * + *第二步,从指定的地址中取出32位页目录项,它的低12位为0,这32位是页表的起始地址。 + *用32位线性地址中的A21~A12位作为页表中的页面的索引,将它乘以4,与页表的起始地址相加,形成32位页面地址。 + * + *第三步,将A11~A0作为相对于页面地址的偏移量,与32位页面地址相加,形成32位物理地址。 + *************************************************************************/ + +/*======================================================================* + get_pde_index add by visual 2016.4.28 + *======================================================================*/ +inline u32 get_pde_index(u32 AddrLin) +{ //由 线性地址 得到 页目录项编号 + return (AddrLin >> 22); //高10位A31~A22 +} + +/*======================================================================* + get_pte_index add by visual 2016.4.28 + *======================================================================*/ + inline u32 get_pte_index(u32 AddrLin) +{ //由 线性地址 得到 页表项编号 + return (((AddrLin)&0x003FFFFF) >> 12); //中间10位A21~A12,0x3FFFFF = 0000 0000 0011 1111 1111 1111 1111 1111 +} + +/*======================================================================* + get_pde_phy_addr add by visual 2016.4.28 +*======================================================================*/ +inline u32 get_pde_phy_addr(u32 pid) +{ //获取页目录物理地址 + if (proc_table[pid].task.cr3 == 0) + { //还没有初始化页目录 + return -1; + } + else + { + return ((proc_table[pid].task.cr3) & 0xFFFFF000); + } +} + +/*======================================================================* + get_pte_phy_addr add by visual 2016.4.28 +*======================================================================*/ +inline u32 get_pte_phy_addr(u32 pid, //页目录物理地址 //edit by visual 2016.5.19 + u32 AddrLin) //线性地址 +{ //获取该线性地址所属页表的物理地址 + u32 PageDirPhyAddr = get_pde_phy_addr(pid); // add by visual 2016.5.19 + return (*((u32 *)K_PHY2LIN(PageDirPhyAddr) + get_pde_index(AddrLin))) & 0xFFFFF000; //先找到该进程页目录首地址,然后计算出该线性地址对应的页目录项,再访问,最后注意4k对齐 +} + +/*======================================================================* + get_page_phy_addr add by visual 2016.5.9 +*======================================================================*/ +inline u32 get_page_phy_addr(u32 pid, //页表物理地址 //edit by visual 2016.5.19 + u32 AddrLin) //线性地址 +{ //获取该线性地址对应的物理页物理地址 + u32 PageTblPhyAddr = get_pte_phy_addr(pid, AddrLin); // add by visual 2016.5.19 + return (*((u32 *)K_PHY2LIN(PageTblPhyAddr) + get_pte_index(AddrLin))) & 0xFFFFF000; +} + +/*======================================================================* + pte_exist add by visual 2016.4.28 +*======================================================================*/ +u32 pte_exist(u32 PageDirPhyAddr, //页目录物理地址 + u32 AddrLin) //线性地址 +{ //判断 有没有 页表 + if ((0x00000001 & (*((u32 *)K_PHY2LIN(PageDirPhyAddr) + get_pde_index(AddrLin)))) == 0) //先找到该进程页目录,然后计算出该线性地址对应的页目录项,访问并判断其是否存在 + { //标志位为0,不存在 + return 0; + } + else + { + return 1; + } +} + +/*======================================================================* + phy_exist add by visual 2016.4.28 +*======================================================================*/ +u32 phy_exist(u32 PageTblPhyAddr, //页表物理地址 + u32 AddrLin) //线性地址 +{ //判断 该线性地址 有没有 对应的 物理页 + if ((0x00000001 & (*((u32 *)K_PHY2LIN(PageTblPhyAddr) + get_pte_index(AddrLin)))) == 0) + { //标志位为0,不存在 + return 0; + } + else + { + return 1; + } +} + +/*======================================================================* + write_page_pde add by visual 2016.4.28 +*======================================================================*/ + void write_page_pde(u32 PageDirPhyAddr, //页目录物理地址 + u32 AddrLin, //线性地址 + u32 TblPhyAddr, //要填写的页表的物理地址(函数会进行4k对齐) + u32 Attribute) //属性 +{ //填写页目录 + (*((u32 *)K_PHY2LIN(PageDirPhyAddr) + get_pde_index(AddrLin))) = (TblPhyAddr & 0xFFFFF000) | Attribute; + //进程页目录起始地址+每一项的大小*所属的项 +} + +/*======================================================================* + write_page_pte add by visual 2016.4.28 + *======================================================================*/ +void write_page_pte(u32 TblPhyAddr, //页表物理地址 + u32 AddrLin, //线性地址 + u32 PhyAddr, //要填写的物理页物理地址(任意的物理地址,函数会进行4k对齐) + u32 Attribute) //属性 +{ //填写页目录,会添加属性 + (*((u32 *)K_PHY2LIN(TblPhyAddr) + get_pte_index(AddrLin))) = (PhyAddr & 0xFFFFF000) | Attribute; + //页表起始地址+一项的大小*所属的项 +} + +/*======================================================================* + * vmalloc add by visual 2016.5.4 + *从堆中分配size大小的内存,返回线性地址 + *======================================================================*/ +u32 vmalloc(u32 size) +{ + u32 temp; + if (p_proc_current->task.info.type == TYPE_PROCESS) + { //进程直接就是标识 + temp = p_proc_current->task.memmap.heap_lin_limit; + p_proc_current->task.memmap.heap_lin_limit += size; + } + else + { //线程需要取父进程的标识 + temp = *((u32 *)p_proc_current->task.memmap.heap_lin_limit); + (*((u32 *)p_proc_current->task.memmap.heap_lin_limit)) += size; + } + + return temp; +} + +/*======================================================================* + * lin_mapping_phy add by visual 2016.5.9 + *将线性地址映射到物理地址上去,函数内部会分配物理地址 + *======================================================================*/ +int lin_mapping_phy(u32 AddrLin, //线性地址 + u32 phy_addr, //物理地址,若为MAX_UNSIGNED_INT(0xFFFFFFFF),则表示需要由该函数判断是否分配物理地址,否则将phy_addr直接和AddrLin建立映射 + u32 pid, //进程pid //edit by visual 2016.5.19 + u32 pde_Attribute, //页目录中的属性位 + u32 pte_Attribute) //页表中的属性位 +{ + u32 pte_addr_phy; + u32 pde_addr_phy = get_pde_phy_addr(pid); // add by visual 2016.5.19 + + if (0 == pte_exist(pde_addr_phy, AddrLin)) + { //页表不存在,创建一个,并填进页目录中 + pte_addr_phy = (u32)do_kmalloc_4k(); //为页表申请一页 + memset((void *)K_PHY2LIN(pte_addr_phy), 0, num_4K); // add by visual 2016.5.26 + + if (pte_addr_phy < 0 || (pte_addr_phy & 0x3FF) != 0) // add by visual 2016.5.9 + { + disp_color_str("lin_mapping_phy Error:pte_addr_phy", 0x74); + return -1; + } + + write_page_pde(pde_addr_phy, //页目录物理地址 + AddrLin, //线性地址 + pte_addr_phy, //页表物理地址 + pde_Attribute); //属性 + } + else + { //页表存在,获取该页表物理地址 + pte_addr_phy = get_pte_phy_addr(pid, //进程pid //edit by visual 2016.5.19 + AddrLin); //线性地址 + } + + if (MAX_UNSIGNED_INT == phy_addr) // add by visual 2016.5.19 + { //由函数申请内存 + if (0 == phy_exist(pte_addr_phy, AddrLin)) + { //无物理页,申请物理页并修改phy_addr + if (AddrLin >= K_PHY2LIN(0)) + phy_addr = do_kmalloc_4k(); //从内核物理地址申请一页 + else + { + // disp_str("%"); + phy_addr = do_malloc_4k(); //从用户物理地址空间申请一页 + } + } + else + { + //有物理页,什么也不做,直接返回,必须返回 + return 0; + } + } + else + { //指定填写phy_addr + //不用修改phy_addr + } + + if (phy_addr < 0 || (phy_addr & 0x3FF) != 0) + { + disp_color_str("lin_mapping_phy:phy_addr ERROR", 0x74); + return -1; + } + + write_page_pte(pte_addr_phy, //页表物理地址 + AddrLin, //线性地址 + phy_addr, //物理页物理地址 + pte_Attribute); //属性 + refresh_page_cache(); + + return 0; +} + +/*======================================================================* + * clear_kernel_pagepte_low add by visual 2016.5.12 + *将内核低端页表清除 + *======================================================================*/ +void clear_kernel_pagepte_low() +{ + u32 page_num = *(u32 *)PageTblNumAddr; + memset((void *)(K_PHY2LIN(KernelPageTblAddr)), 0, 4 * page_num); //从内核页目录中清除内核页目录项前8项 + memset((void *)(K_PHY2LIN(KernelPageTblAddr + 0x1000)), 0, 4096 * page_num); //从内核页表中清除线性地址的低端映射关系 + refresh_page_cache(); +} diff --git a/kernel/proc.c b/kernel/proc.c new file mode 100644 index 0000000..998fbc1 --- /dev/null +++ b/kernel/proc.c @@ -0,0 +1,137 @@ + +/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + proc.c +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + Forrest Yu, 2005 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + +#include "type.h" +#include "const.h" +#include "protect.h" +#include "string.h" +#include "proc.h" +#include "global.h" +#include "proto.h" + +/*======================================================================* + schedule + *======================================================================*/ + void schedule() +{ + PROCESS* p; + int greatest_ticks = 0; + + //Added by xw, 18/4/21 + if (p_proc_current->task.stat == READY && p_proc_current->task.ticks > 0) { + p_proc_next = p_proc_current; //added by xw, 18/4/26 + return; + } + + while (!greatest_ticks) + { + for (p = proc_table; p < proc_table+NR_PCBS; p++) //edit by visual 2016.4.5 + { + if (p->task.stat == READY && p->task.ticks > greatest_ticks) //edit by visual 2016.4.5 + { + greatest_ticks = p->task.ticks; + // p_proc_current = p; + p_proc_next = p; //modified by xw, 18/4/26 + } + + } + + if (!greatest_ticks) + { + for (p = proc_table; p < proc_table+NR_PCBS; p++) //edit by visual 2016.4.5 + { + p->task.ticks = p->task.priority; + } + } + } +} + +/*======================================================================* + alloc_PCB add by visual 2016.4.8 + *======================================================================*/ + PROCESS* alloc_PCB() +{//分配PCB表 + PROCESS* p; + int i; + p=proc_table+NR_K_PCBS;//跳过前NR_K_PCBS个 + for(i=NR_K_PCBS;itask.stat==IDLE)break; + p++; + } + if(i>=NR_PCBS) return 0; //NULL + else return p; +} + +/*======================================================================* + free_PCB add by visual 2016.4.8 + *======================================================================*/ + void free_PCB(PROCESS *p) +{//释放PCB表 + p->task.stat=IDLE; +} + +/*======================================================================* + yield and sleep + *======================================================================*/ +//used for processes to give up the CPU +void sys_yield() +{ + p_proc_current->task.ticks = 0; /* modified by xw, 18/4/27 */ + sched(); //Modified by xw, 18/4/19 +} + +//used for processes to sleep for n ticks +void sys_sleep(int n) +{ + int ticks0; + + ticks0 = ticks; + p_proc_current->task.channel = &ticks; + + while(ticks - ticks0 < n){ + p_proc_current->task.stat = SLEEPING; +// save_context(); + sched(); //Modified by xw, 18/4/19 + } +} + +/*invoked by clock-interrupt handler to wakeup + *processes sleeping on ticks. + */ +void sys_wakeup(void *channel) +{ + PROCESS *p; + + for(p = proc_table; p < proc_table + NR_PCBS; p++){ + if(p->task.stat == SLEEPING && p->task.channel == channel){ + p->task.stat = READY; + } + } +} + +//added by zcr +int ldt_seg_linear(PROCESS *p, int idx) +{ + struct s_descriptor * d = &p->task.ldts[idx]; + return d->base_high << 24 | d->base_mid << 16 | d->base_low; +} + +void* va2la(int pid, void* va) +{ + if(kernel_initial == 1){ + return va; + } + + PROCESS* p = &proc_table[pid]; + u32 seg_base = ldt_seg_linear(p, INDEX_LDT_RW); + u32 la = seg_base + (u32)va; + + return (void*)la; +} +//~zcr + diff --git a/kernel/protect.c b/kernel/protect.c new file mode 100644 index 0000000..85cab81 --- /dev/null +++ b/kernel/protect.c @@ -0,0 +1,334 @@ + +/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + protect.c +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + Forrest Yu, 2005 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + +#include "type.h" +#include "const.h" +#include "protect.h" +#include "proc.h" +#include "global.h" +#include "proto.h" +#include "string.h" + + +/* 本文件内函数声明 */ +static void init_idt_desc(unsigned char vector, u8 desc_type, int_handler handler, unsigned char privilege); +static void init_descriptor(DESCRIPTOR * p_desc, u32 base, u32 limit, u16 attribute); + + +/* 中断处理函数 */ +void divide_error(); +void single_step_exception(); +void nmi(); +void breakpoint_exception(); +void overflow(); +void bounds_check(); +void inval_opcode(); +void copr_not_available(); +void double_fault(); +void copr_seg_overrun(); +void inval_tss(); +void segment_not_present(); +void stack_exception(); +void general_protection(); +void page_fault(); +void copr_error(); +void hwint00(); +void hwint01(); +void hwint02(); +void hwint03(); +void hwint04(); +void hwint05(); +void hwint06(); +void hwint07(); +void hwint08(); +void hwint09(); +void hwint10(); +void hwint11(); +void hwint12(); +void hwint13(); +void hwint14(); +void hwint15(); + + +/*======================================================================* + init_prot + *----------------------------------------------------------------------* + 初始化 IDT + *======================================================================*/ +void init_prot() +{ + init_8259A(); + + // 全部初始化成中断门(没有陷阱门) + init_idt_desc(INT_VECTOR_DIVIDE, DA_386IGate, + divide_error, PRIVILEGE_KRNL); + + init_idt_desc(INT_VECTOR_DEBUG, DA_386IGate, + single_step_exception, PRIVILEGE_KRNL); + + init_idt_desc(INT_VECTOR_NMI, DA_386IGate, + nmi, PRIVILEGE_KRNL); + + init_idt_desc(INT_VECTOR_BREAKPOINT, DA_386IGate, + breakpoint_exception, PRIVILEGE_USER); + + init_idt_desc(INT_VECTOR_OVERFLOW, DA_386IGate, + overflow, PRIVILEGE_USER); + + init_idt_desc(INT_VECTOR_BOUNDS, DA_386IGate, + bounds_check, PRIVILEGE_KRNL); + + init_idt_desc(INT_VECTOR_INVAL_OP, DA_386IGate, + inval_opcode, PRIVILEGE_KRNL); + + init_idt_desc(INT_VECTOR_COPROC_NOT, DA_386IGate, + copr_not_available, PRIVILEGE_KRNL); + + init_idt_desc(INT_VECTOR_DOUBLE_FAULT, DA_386IGate, + double_fault, PRIVILEGE_KRNL); + + init_idt_desc(INT_VECTOR_COPROC_SEG, DA_386IGate, + copr_seg_overrun, PRIVILEGE_KRNL); + + init_idt_desc(INT_VECTOR_INVAL_TSS, DA_386IGate, + inval_tss, PRIVILEGE_KRNL); + + init_idt_desc(INT_VECTOR_SEG_NOT, DA_386IGate, + segment_not_present, PRIVILEGE_KRNL); + + init_idt_desc(INT_VECTOR_STACK_FAULT, DA_386IGate, + stack_exception, PRIVILEGE_KRNL); + + init_idt_desc(INT_VECTOR_PROTECTION, DA_386IGate, + general_protection, PRIVILEGE_KRNL); + + init_idt_desc(INT_VECTOR_PAGE_FAULT, DA_386IGate, + page_fault, PRIVILEGE_KRNL); + + init_idt_desc(INT_VECTOR_COPROC_ERR, DA_386IGate, + copr_error, PRIVILEGE_KRNL); + + init_idt_desc(INT_VECTOR_IRQ0 + 0, DA_386IGate, + hwint00, PRIVILEGE_KRNL); + + init_idt_desc(INT_VECTOR_IRQ0 + 1, DA_386IGate, + hwint01, PRIVILEGE_KRNL); + + init_idt_desc(INT_VECTOR_IRQ0 + 2, DA_386IGate, + hwint02, PRIVILEGE_KRNL); + + init_idt_desc(INT_VECTOR_IRQ0 + 3, DA_386IGate, + hwint03, PRIVILEGE_KRNL); + + init_idt_desc(INT_VECTOR_IRQ0 + 4, DA_386IGate, + hwint04, PRIVILEGE_KRNL); + + init_idt_desc(INT_VECTOR_IRQ0 + 5, DA_386IGate, + hwint05, PRIVILEGE_KRNL); + + init_idt_desc(INT_VECTOR_IRQ0 + 6, DA_386IGate, + hwint06, PRIVILEGE_KRNL); + + init_idt_desc(INT_VECTOR_IRQ0 + 7, DA_386IGate, + hwint07, PRIVILEGE_KRNL); + + init_idt_desc(INT_VECTOR_IRQ8 + 0, DA_386IGate, + hwint08, PRIVILEGE_KRNL); + + init_idt_desc(INT_VECTOR_IRQ8 + 1, DA_386IGate, + hwint09, PRIVILEGE_KRNL); + + init_idt_desc(INT_VECTOR_IRQ8 + 2, DA_386IGate, + hwint10, PRIVILEGE_KRNL); + + init_idt_desc(INT_VECTOR_IRQ8 + 3, DA_386IGate, + hwint11, PRIVILEGE_KRNL); + + init_idt_desc(INT_VECTOR_IRQ8 + 4, DA_386IGate, + hwint12, PRIVILEGE_KRNL); + + init_idt_desc(INT_VECTOR_IRQ8 + 5, DA_386IGate, + hwint13, PRIVILEGE_KRNL); + + init_idt_desc(INT_VECTOR_IRQ8 + 6, DA_386IGate, + hwint14, PRIVILEGE_KRNL); + + init_idt_desc(INT_VECTOR_IRQ8 + 7, DA_386IGate, + hwint15, PRIVILEGE_KRNL); + + init_idt_desc(INT_VECTOR_SYS_CALL, DA_386IGate, + sys_call, PRIVILEGE_USER); + + /*修改显存描述符*/ //add by visual 2016.5.12 + init_descriptor(&gdt[INDEX_VIDEO], + K_PHY2LIN(0x0B8000), + 0x0ffff, + DA_DRW | DA_DPL3); + + /* 填充 GDT 中 TSS 这个描述符 */ + memset(&tss, 0, sizeof(tss)); + tss.ss0 = SELECTOR_KERNEL_DS; + init_descriptor(&gdt[INDEX_TSS], + vir2phys(seg2phys(SELECTOR_KERNEL_DS), &tss), + sizeof(tss) - 1, + DA_386TSS); + tss.iobase = sizeof(tss); /* 没有I/O许可位图 */ + + // 填充 GDT 中进程的 LDT 的描述符 + int i; + PROCESS* p_proc = proc_table; + u16 selector_ldt = INDEX_LDT_FIRST << 3; + for(i=0;i>3], + vir2phys(seg2phys(SELECTOR_KERNEL_DS),proc_table[i].task.ldts), + LDT_SIZE * sizeof(DESCRIPTOR) - 1, + DA_LDT); + p_proc++; + selector_ldt += 1 << 3; + } +} + + +/*======================================================================* + init_idt_desc + *----------------------------------------------------------------------* + 初始化 386 中断门 + *======================================================================*/ +void init_idt_desc(unsigned char vector, u8 desc_type, int_handler handler, unsigned char privilege) +{ + GATE * p_gate = &idt[vector]; + u32 base = (u32)handler; + p_gate->offset_low = base & 0xFFFF; + p_gate->selector = SELECTOR_KERNEL_CS; + p_gate->dcount = 0; + p_gate->attr = desc_type | (privilege << 5); + p_gate->offset_high = (base >> 16) & 0xFFFF; +} + + +/*======================================================================* + seg2phys + *----------------------------------------------------------------------* + 由段名求绝对地址 + *======================================================================*/ + u32 seg2phys(u16 seg) +{ + DESCRIPTOR* p_dest = &gdt[seg >> 3]; + + return (p_dest->base_high << 24) | (p_dest->base_mid << 16) | (p_dest->base_low); +} + +/*======================================================================* + init_descriptor + *----------------------------------------------------------------------* + 初始化段描述符 + *======================================================================*/ +static void init_descriptor(DESCRIPTOR * p_desc, u32 base, u32 limit, u16 attribute) +{ + p_desc->limit_low = limit & 0x0FFFF; // 段界限 1 (2 字节) + p_desc->base_low = base & 0x0FFFF; // 段基址 1 (2 字节) + p_desc->base_mid = (base >> 16) & 0x0FF; // 段基址 2 (1 字节) + p_desc->attr1 = attribute & 0xFF; // 属性 1 + p_desc->limit_high_attr2 = ((limit >> 16) & 0x0F) | + ((attribute >> 8) & 0xF0);// 段界限 2 + 属性 2 + p_desc->base_high = (base >> 24) & 0x0FF; // 段基址 3 (1 字节) +} + +/*======================================================================* + exception_handler + *----------------------------------------------------------------------* + 异常处理 + *======================================================================*/ + void exception_handler(int vec_no, int err_code, int eip, int cs, int eflags) +{ + int i; + int text_color = 0x74; /* 灰底红字 */ + char err_description[][64] = { "#DE Divide Error", + "#DB RESERVED", + "— NMI Interrupt", + "#BP Breakpoint", + "#OF Overflow", + "#BR BOUND Range Exceeded", + "#UD Invalid Opcode (Undefined Opcode)", + "#NM Device Not Available (No Math Coprocessor)", + "#DF Double Fault", + " Coprocessor Segment Overrun (reserved)", + "#TS Invalid TSS", + "#NP Segment Not Present", + "#SS Stack-Segment Fault", + "#GP General Protection", + "#PF Page Fault", + "— (Intel reserved. Do not use.)", + "#MF x87 FPU Floating-Point Error (Math Fault)", + "#AC Alignment Check", + "#MC Machine Check", + "#XF SIMD Floating-Point Exception" + }; + + /* 通过打印空格的方式清空屏幕的前五行,并把 disp_pos 清零 */ + disp_pos = 0; + for(i=0;i<80*5;i++){ + disp_str(" "); + } + disp_pos = 0; + + disp_color_str("Exception! --> ", text_color); + disp_color_str(err_description[vec_no], text_color); + disp_color_str("\n\n", text_color); + disp_color_str("EFLAGS:", text_color); + disp_int(eflags); + disp_color_str("CS:", text_color); + disp_int(cs); + disp_color_str("EIP:", text_color); + disp_int(eip); + + if(err_code != 0xFFFFFFFF){ + disp_color_str("Error code:", text_color); + disp_int(err_code); + } + + //added by xw, 18/12/19 + disp_str("\n"); + + //added by xw, 18/12/19 + p_proc_current->task.stat = KILLED; +} + +/*======================================================================* + divide error handler + *======================================================================*/ +//used for testing if a exception handler can be interrupted rightly, so it's +//not a real divide_error handler now. added by xw, 18/12/22 + void divide_error_handler() +{ + int vec_no, err_code, eip, cs, eflags; + int i, j; + + asm volatile ( "mov 8(%%ebp), %0\n\t" //get vec_no from stack + "mov 12(%%ebp), %1\n\t" //get err_code from stack + "mov 16(%%ebp), %2\n\t" //get eip from stack + "mov 20(%%ebp), %3\n\t" //get cs from stack + "mov 24(%%ebp), %4\n\t" //get eflags from stack + : "=r"(vec_no), "=r"(err_code), "=r"(eip), + "=r"(cs), "=r"(eflags) + ); + exception_handler(vec_no, err_code, eip, cs, eflags); + + while (1) + { + disp_str("Loop in divide error handler...\n"); + + i = 100; + while(--i){ + j = 1000; + while(--j){} + } + } +} + + + diff --git a/kernel/pthread.c b/kernel/pthread.c new file mode 100644 index 0000000..8cd43e2 --- /dev/null +++ b/kernel/pthread.c @@ -0,0 +1,213 @@ +/****************************************************************** +* pthread.c //add by visual 2016.5.26 +*系统调用pthread() +*******************************************************************/ +#include "type.h" +#include "const.h" +#include "protect.h" +#include "string.h" +#include "proc.h" +#include "global.h" +#include "proto.h" + +static int pthread_pcb_cpy(PROCESS *p_child,PROCESS *p_parent); +static int pthread_update_info(PROCESS *p_child,PROCESS *p_parent); +static int pthread_stack_init(PROCESS *p_child,PROCESS *p_parent); +static int pthread_heap_init(PROCESS *p_child,PROCESS *p_parent); + +/********************************************************** +* sys_pthread //add by visual 2016.5.25 +*系统调用sys_pthread的具体实现部分 +*************************************************************/ +int sys_pthread(void *entry) +{ + PROCESS* p_child; + + char* p_reg; //point to a register in the new kernel stack, added by xw, 17/12/11 + + /*if(p_proc_current->task.info.type == TYPE_THREAD ) + {//线程不能创建线程 + disp_color_str("[pthread failed:",0x74); + disp_color_str(p_proc_current->task.p_name,0x74); + disp_color_str("]",0x74); + return -1; + }*/ + /*****************申请空白PCB表**********************/ + p_child = alloc_PCB(); + if( 0==p_child ) + { + disp_color_str("PCB NULL,pthread faild!",0x74); + return -1; + } + else + { + PROCESS *p_parent; + if( p_proc_current->task.info.type == TYPE_THREAD ) + {//线程 + p_parent = &(proc_table[p_proc_current->task.info.ppid]);//父进程 + } + else + {//进程 + p_parent = p_proc_current;//父进程就是父线程 + } + /************复制父进程的PCB部分内容(保留了自己的标识信息,但cr3使用的是父进程的)**************/ + pthread_pcb_cpy(p_child,p_parent); + + /************在父进程的栈中分配子线程的栈(从进程栈的低地址分配8M,注意方向)**********************/ + pthread_stack_init(p_child,p_parent); + + /**************初始化子线程的堆(此时的这两个变量已经变成了指针)***********************/ + pthread_heap_init(p_child,p_parent); + + /********************设置线程的执行入口**********************************************/ + p_child->task.regs.eip = (u32)entry; + p_reg = (char*)(p_child + 1); //added by xw, 17/12/11 + *((u32*)(p_reg + EIPREG - P_STACKTOP)) = p_child->task.regs.eip; //added by xw, 17/12/11 + + /**************更新进程树标识info信息************************/ + pthread_update_info(p_child,p_parent); + + /************修改子进程的名字***************/ + strcpy(p_child->task.p_name,"pthread"); // 所有的子进程都叫pthread + + /*************子进程返回值在其eax寄存器***************/ + p_child->task.regs.eax = 0;//return child with 0 + p_reg = (char*)(p_child + 1); //added by xw, 17/12/11 + *((u32*)(p_reg + EAXREG - P_STACKTOP)) = p_child->task.regs.eax; //added by xw, 17/12/11 + + /****************用户进程数+1****************************/ + u_proc_sum += 1; + + disp_color_str("[pthread success:",0x72); + disp_color_str(p_proc_current->task.p_name,0x72); + disp_color_str("]",0x72); + + //anything child need is prepared now, set its state to ready. added by xw, 17/12/11 + p_child->task.stat = READY; + } + return p_child->task.pid; +} + + +/********************************************************** +* pthread_pcb_cpy //add by visual 2016.5.26 +*复制父进程PCB表,但是又马上恢复了子进程的标识信息 +*************************************************************/ +static int pthread_pcb_cpy(PROCESS *p_child,PROCESS *p_parent) +{ + int pid; + u32 eflags,selector_ldt; + char* p_reg; //point to a register in the new kernel stack, added by xw, 17/12/11 + char *esp_save_int, *esp_save_context; //use to save corresponding field in child's PCB, xw, 18/4/21 + + //暂存标识信息 + pid = p_child->task.pid; + + //eflags = p_child->task.regs.eflags; //deleted by xw, 17/12/11 + p_reg = (char*)(p_child + 1); //added by xw, 17/12/11 + eflags = *((u32*)(p_reg + EFLAGSREG - P_STACKTOP)); //added by xw, 17/12/11 + + selector_ldt = p_child->task.ldt_sel; + + //复制PCB内容 + //modified by xw, 17/12/11 + //modified begin + //*p_child = *p_parent; + + //esp_save_int and esp_save_context must be saved, because the child and the parent + //use different kernel stack! And these two are importent to the child's initial running. + //Added by xw, 18/4/21 + esp_save_int = p_child->task.esp_save_int; + esp_save_context = p_child->task.esp_save_context; + p_child->task = p_parent->task; + //note that syscalls can be interrupted now! the state of child can only be setted + //READY when anything else is well prepared. if an interruption happens right here, + //an error will still occur. + p_child->task.stat = IDLE; + p_child->task.esp_save_int = esp_save_int; //esp_save_int of child must be restored!! + p_child->task.esp_save_context = esp_save_context; //same above + memcpy(((char*)(p_child + 1) - P_STACKTOP), ((char*)(p_parent + 1) - P_STACKTOP), 18 * 4); + //modified end + + //恢复标识信息 + p_child->task.pid = pid; + + //p_child->task.regs.eflags = eflags; + p_reg = (char*)(p_child + 1); //added by xw, 17/12/11 + *((u32*)(p_reg + EFLAGSREG - P_STACKTOP)) = eflags; //added by xw, 17/12/11 + + p_child->task.ldt_sel = selector_ldt; + return 0; +} + + + +/********************************************************** +* pthread_update_info //add by visual 2016.5.26 +*更新父进程和子线程程的进程树标识info +*************************************************************/ +static int pthread_update_info(PROCESS* p_child,PROCESS *p_parent) +{ + /************更新父进程的info***************///注意 父进程 父进程 父进程 + if( p_parent!=p_proc_current ) + {//只有在线程创建线程的时候才会执行 ,p_parent事实上是父进程 + p_parent->task.info.child_t_num += 1; //子线程数量 + p_parent->task.info.child_thread[p_parent->task.info.child_t_num-1] = p_child->task.pid;//子线程列表 + } + /************更新父线程的info**************/ + //p_proc_current->task.info.type; //当前是进程还是线程 + //p_proc_current->task.info.real_ppid; //亲父进程,创建它的那个进程 + //p_proc_current->task.info.ppid; //当前父进程 + //p_proc_current->task.info.child_p_num += 1; //子进程数量 + //p_proc_current->task.info.child_process[p_proc_current->task.info.child_p_num-1] = p_child->task.pid;//子进程列表 + p_proc_current->task.info.child_t_num += 1; //子线程数量 + p_proc_current->task.info.child_thread[p_proc_current->task.info.child_t_num-1] = p_child->task.pid;//子线程列表 + //p_proc_current->task.text_hold; //是否拥有代码 + //p_proc_current->task.data_hold; //是否拥有数据 + + /************更新子线程的info***************/ + p_child->task.info.type = TYPE_THREAD ;//这是一个线程 + p_child->task.info.real_ppid = p_proc_current->task.pid; //亲父进程,创建它的那个线程,注意,这个是创建它的那个线程p_proc_current + p_child->task.info.ppid = p_parent->task.pid; //当前父进程 + p_child->task.info.child_p_num = 0; //子进程数量 + //p_child->task.info.child_process[NR_CHILD_MAX] = pid;//子进程列表 + p_child->task.info.child_t_num = 0; //子线程数量 + //p_child->task.info.child_thread[NR_CHILD_MAX];//子线程列表 + p_child->task.info.text_hold = 0; //是否拥有代码,子进程不拥有代码 + p_child->task.info.data_hold = 0; //是否拥有数据,子进程拥有数据 + + return 0; +} + +/********************************************************** +* pthread_stack_init //add by visual 2016.5.26 +*申请子线程的栈,并重置其esp +*************************************************************/ +static int pthread_stack_init(PROCESS* p_child,PROCESS *p_parent) +{ + int addr_lin; + char* p_reg; //point to a register in the new kernel stack, added by xw, 17/12/11 + + p_child->task.memmap.stack_lin_limit = p_parent->task.memmap.stack_child_limit;//子线程的栈界 + p_parent->task.memmap.stack_child_limit += 0x4000; //分配16K + p_child->task.memmap.stack_lin_base = p_parent->task.memmap.stack_child_limit - num_4B; //子线程的基址 + + for( addr_lin=p_child->task.memmap.stack_lin_base ; addr_lin>p_child->task.memmap.stack_lin_limit ; addr_lin-=num_4K)//申请物理地址 + lin_mapping_phy(addr_lin,MAX_UNSIGNED_INT,p_child->task.pid,PG_P | PG_USU | PG_RWW,PG_P | PG_USU | PG_RWW); + + p_child->task.regs.esp = p_child->task.memmap.stack_lin_base; //调整esp + p_reg = (char*)(p_child + 1); //added by xw, 17/12/11 + *((u32*)(p_reg + ESPREG - P_STACKTOP)) = p_child->task.regs.esp; //added by xw, 17/12/11 + + return 0; +} +/********************************************************** +* pthread_stack_init //add by visual 2016.5.26 +*子线程使用父进程的堆 +*************************************************************/ +static int pthread_heap_init(PROCESS* p_child,PROCESS *p_parent) +{ + p_child->task.memmap.heap_lin_base = (u32)&(p_parent->task.memmap.heap_lin_base); + p_child->task.memmap.heap_lin_limit = (u32)&(p_parent->task.memmap.heap_lin_limit); + return 0; +} diff --git a/kernel/spinlock.c b/kernel/spinlock.c new file mode 100644 index 0000000..08bf1f3 --- /dev/null +++ b/kernel/spinlock.c @@ -0,0 +1,56 @@ +/********************************************************** +* spinlock.c //added by mingxuan 2018-12-26 +***********************************************************/ +// Mutual exclusion spin locks. + +//#include "types.h" +//#include "defs.h" +//#include "x86.h" +//#include "mmu.h" +//#include "param.h" +//#include "proc.h" +#include "spinlock.h" + +//extern int use_console_lock; + +static inline uint +cmpxchg(uint oldval, uint newval, volatile uint* lock_addr) +{ + uint result; + asm volatile("lock; cmpxchg %0, %2" : + "+m" (*lock_addr), "=a" (result) : + "r"(newval), "1"(oldval) : + "cc"); + return result; +} + +void +initlock(struct spinlock *lock, char *name) +{ + lock->name = name; + lock->locked = 0; + lock->cpu = 0xffffffff; +} + +// Acquire the lock. +// Loops (spins) until the lock is acquired. +// (Because contention is handled by spinning, must not +// go to sleep holding any locks.) +void +acquire(struct spinlock *lock) +{ + + while(cmpxchg(0, 1, &lock->locked) == 1) + ; +} + +// Release the lock. +void +release(struct spinlock *lock) +{ + + lock->locked = 0; +} + + + diff --git a/kernel/start.c b/kernel/start.c new file mode 100644 index 0000000..9d8fe19 --- /dev/null +++ b/kernel/start.c @@ -0,0 +1,82 @@ + +/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + start.c +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + Forrest Yu, 2005 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + +#include "stdio.h" +#include "protect.h" +#include "proc.h" +#include "global.h" +#include "proto.h" + +#include "assert.h" +#include "string.h" + +/* + * 当发生不可挽回的错误时就打印错误信息并使CPU核休眠 + */ +void +_panic(const char *file, int line, const char *fmt,...) +{ + va_list ap; + + // 确保CPU核不受外界中断的影响 + asm volatile("cli"); + asm volatile("cld"); + + va_start(ap, fmt); + kprintf("kernel panic at %s:%d: ", file, line); + vkprintf(fmt, ap); + kprintf("\n"); + va_end(ap); + // 休眠CPU核,直接罢工 + while(1) + asm volatile("hlt"); +} + +/* + * 很像panic,但是不会休眠CPU核,就是正常打印信息 + */ +void +_warn(const char *file, int line, const char *fmt,...) +{ + va_list ap; + + va_start(ap, fmt); + kprintf("kernel warning at %s:%d: ", file, line); + vkprintf(fmt, ap); + kprintf("\n"); + va_end(ap); +} + + +/*======================================================================* + cstart + *======================================================================*/ +void cstart() +{ + kprintf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n-----\"cstart\" begins-----\n"); + + // 将 LOADER 中的 GDT 复制到新的 GDT 中 + memcpy( &gdt, // New GDT + (void*)(*((u32*)(&gdt_ptr[2]))), // Base of Old GDT + *((u16*)(&gdt_ptr[0])) + 1 // Limit of Old GDT + ); + // gdt_ptr[6] 共 6 个字节:0~15:Limit 16~47:Base。用作 sgdt 以及 lgdt 的参数。 + u16* p_gdt_limit = (u16*)(&gdt_ptr[0]); + u32* p_gdt_base = (u32*)(&gdt_ptr[2]); + *p_gdt_limit = GDT_SIZE * sizeof(DESCRIPTOR) - 1; + *p_gdt_base = (u32)&gdt; + + // idt_ptr[6] 共 6 个字节:0~15:Limit 16~47:Base。用作 sidt 以及 lidt 的参数。 + u16* p_idt_limit = (u16*)(&idt_ptr[0]); + u32* p_idt_base = (u32*)(&idt_ptr[2]); + *p_idt_limit = IDT_SIZE * sizeof(GATE) - 1; + *p_idt_base = (u32)&idt; + + init_prot(); + + kprintf("-----\"cstart\" finished-----\n"); +} \ No newline at end of file diff --git a/kernel/syscall.asm b/kernel/syscall.asm new file mode 100644 index 0000000..c1fe58c --- /dev/null +++ b/kernel/syscall.asm @@ -0,0 +1,361 @@ + +; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +; syscall.asm +; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +; Forrest Yu, 2005 +; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +%include "sconst.inc" + +_NR_get_ticks equ 0 ; 要跟 global.c 中 sys_call_table 的定义相对应! +_NR_get_pid equ 1 ; //add by visual 2016.4.6 +_NR_kmalloc equ 2 ; //add by visual 2016.4.6 +_NR_kmalloc_4k equ 3 ; //add by visual 2016.4.7 +_NR_malloc equ 4 ; //add by visual 2016.4.7 +_NR_malloc_4k equ 5 ; //add by visual 2016.4.7 +_NR_free equ 6 ; //add by visual 2016.4.7 +_NR_free_4k equ 7 ; //add by visual 2016.4.7 +_NR_fork equ 8 ; //add by visual 2016.4.8 +_NR_pthread equ 9 ; //add by visual 2016.4.11 +_NR_udisp_int equ 10 ; //add by visual 2016.5.16 +_NR_udisp_str equ 11 ; //add by visual 2016.5.16 +_NR_exec equ 12 ; //add by visual 2016.5.16 +_NR_yield equ 13 ; //added by xw, 17/12 +_NR_sleep equ 14 ; //added by xw, 17/12 +_NR_print_E equ 15 ; //added by xw, 18/4/27 +_NR_print_F equ 16 ; //added by xw, 18/4/27 + +_NR_open equ 17 ; //added by xw, 18/6/18 +_NR_close equ 18 ; //added by xw, 18/6/18 +_NR_read equ 19 ; //added by xw, 18/6/18 +_NR_write equ 20 ; //added by xw, 18/6/18 +_NR_lseek equ 21 ; //added by xw, 18/6/18 +_NR_unlink equ 22 ; //added by xw, 18/6/18 + +_NR_create equ 23 ; //added by mingxuan 2019-5-17 +_NR_delete equ 24 ; //added by mingxuan 2019-5-17 +_NR_opendir equ 25 ; //added by mingxuan 2019-5-17 +_NR_createdir equ 26 ; //added by mingxuan 2019-5-17 +_NR_deletedir equ 27 ; //added by mingxuan 2019-5-17 + +INT_VECTOR_SYS_CALL equ 0x90 + +; 导出符号 +global get_ticks +global get_pid ; //add by visual 2016.4.6 +global kmalloc ; //add by visual 2016.4.6 +global kmalloc_4k ; //add by visual 2016.4.7 +global malloc ; //add by visual 2016.4.7 +global malloc_4k ; //add by visual 2016.4.7 +global free ; //add by visual 2016.4.7 +global free_4k ; //add by visual 2016.4.7 +global fork ; //add by visual 2016.4.8 +global pthread ; //add by visual 2016.4.11 +global udisp_int ; //add by visual 2016.5.16 +global udisp_str ; //add by visual 2016.5.16 +global exec ; //add by visual 2016.5.16 +global yield ; //added by xw +global sleep ; //added by xw +global print_E ; //added by xw +global print_F ; //added by xw + +global open ; //added by xw, 18/6/18 +global close ; //added by xw, 18/6/18 +global read ; //added by xw, 18/6/18 +global write ; //added by xw, 18/6/18 +global lseek ; //added by xw, 18/6/18 +global unlink ; //added by xw, 18/6/19 + +global create ; //added by mingxuan 2019-5-17 +global delete ; //added by mingxuan 2019-5-17 +global opendir ; //added by mingxuan 2019-5-17 +global createdir ; //added by mingxuan 2019-5-17 +global deletedir ; //added by mingxuan 2019-5-17 + +bits 32 +[section .text] +; ==================================================================== +; get_ticks +; ==================================================================== +get_ticks: + mov eax, _NR_get_ticks + int INT_VECTOR_SYS_CALL + ret + +; ==================================================================== +; get_pid //add by visual 2016.4.6 +; ==================================================================== +get_pid: + mov eax, _NR_get_pid + int INT_VECTOR_SYS_CALL + ret + +; ; ==================================================================== +; ; kmalloc //add by visual 2016.4.6 +; ; ==================================================================== +; kmalloc: +; push ebx +; mov ebx,[esp+4] ; 将C函数调用时传来的参数放到ebx里!! +; mov eax, _NR_kmalloc +; int INT_VECTOR_SYS_CALL +; pop ebx +; ret + +; ; ==================================================================== +; ; kmalloc_4k //add by visual 2016.4.7 +; ; ==================================================================== +; kmalloc_4k: +; push ebx +; mov ebx,[esp+4] ; 将C函数调用时传来的参数放到ebx里!!111 +; mov eax, _NR_kmalloc_4k +; int INT_VECTOR_SYS_CALL +; pop ebx +; ret + +; ; ==================================================================== +; ; malloc //add by visual 2016.4.7 +; ; ==================================================================== +; malloc: +; push ebx +; mov ebx,[esp+4] ; 将C函数调用时传来的参数放到ebx里!!111 +; mov eax, _NR_malloc +; int INT_VECTOR_SYS_CALL +; pop ebx +; ret + +; ; ==================================================================== +; ; malloc_4k //add by visual 2016.4.7 +; ; ==================================================================== +; malloc_4k: +; push ebx +; mov ebx,[esp+4] ; 将C函数调用时传来的参数放到ebx里!!111 +; mov eax, _NR_malloc_4k +; int INT_VECTOR_SYS_CALL +; pop ebx +; ret + +; ; ==================================================================== +; ; free //add by visual 2016.4.7 +; ; ==================================================================== +; free: +; push ebx +; mov ebx,[esp+4] ; 将C函数调用时传来的参数放到ebx里!!111 +; mov eax, _NR_free +; int INT_VECTOR_SYS_CALL +; pop ebx +; ret + +; ; ==================================================================== +; ; free_4k //add by visual 2016.4.7 +; ; ==================================================================== +; free_4k: +; push ebx +; mov ebx,[esp+4] ; 将C函数调用时传来的参数放到ebx里!!111 +; mov eax, _NR_free_4k +; int INT_VECTOR_SYS_CALL +; pop ebx +; ret + +; ==================================================================== +; fork //add by visual 2016.4.8 +; ==================================================================== +fork: + mov eax, _NR_fork + int INT_VECTOR_SYS_CALL + ret + +; ==================================================================== +; pthread //add by visual 2016.4.11 +; ==================================================================== +pthread: + push ebx + mov ebx,[esp+8] + mov eax, _NR_pthread + int INT_VECTOR_SYS_CALL + pop ebx + ret + +; ==================================================================== +; udisp_int //add by visual 2016.5.16 +; ==================================================================== +udisp_int: + push ebx + mov ebx,[esp+8] + mov eax, _NR_udisp_int + int INT_VECTOR_SYS_CALL + pop ebx + ret + +; ==================================================================== +; udisp_str //add by visual 2016.5.16 +; ==================================================================== +udisp_str: + push ebx + mov ebx,[esp+8] + mov eax, _NR_udisp_str + int INT_VECTOR_SYS_CALL + pop ebx + ret + +; ==================================================================== +; exec //add by visual 2016.5.16 +; ==================================================================== +exec: + push ebx + mov ebx,[esp+8] + mov eax, _NR_exec + int INT_VECTOR_SYS_CALL + pop ebx + ret + +; ==================================================================== +; yield //added by xw +; ==================================================================== +yield: + mov eax, _NR_yield + int INT_VECTOR_SYS_CALL + ret + +; ==================================================================== +; sleep //added by xw +; ==================================================================== +sleep: + push ebx + mov ebx,[esp+8] + mov eax, _NR_sleep + int INT_VECTOR_SYS_CALL + pop ebx + ret + +; ; ==================================================================== +; ; print_E //added by xw +; ; ==================================================================== +; print_E: +; push ebx +; mov ebx,[esp+4] +; mov eax, _NR_print_E +; int INT_VECTOR_SYS_CALL +; pop ebx +; ret + +; ; ==================================================================== +; ; print_F //added by xw +; ; ==================================================================== +; print_F: +; push ebx +; mov ebx,[esp+4] +; mov eax, _NR_print_F +; int INT_VECTOR_SYS_CALL +; pop ebx +; ret + +; ==================================================================== +; open //added by xw, 18/6/18 +; ==================================================================== +; open has more than one parameter, to pass them, we save the esp to ebx, +; and ebx will be passed into kernel as usual. In kernel, we use the saved +; esp in user space to get the number of parameters and the values of them. +open: + push ebx + mov ebx, esp + mov eax, _NR_open + int INT_VECTOR_SYS_CALL + pop ebx + ret + +; ==================================================================== +; close //added by xw, 18/6/18 +; ==================================================================== +; close has only one parameter, but we can still use this method. +close: + push ebx + mov ebx, esp + mov eax, _NR_close + int INT_VECTOR_SYS_CALL + pop ebx + ret + +; ==================================================================== +; read //added by xw, 18/6/18 +; ==================================================================== +read: + push ebx + mov ebx, esp + mov eax, _NR_read + int INT_VECTOR_SYS_CALL + pop ebx + ret + +; ==================================================================== +; write //added by xw, 18/6/18 +; ==================================================================== +write: + push ebx + mov ebx, esp + mov eax, _NR_write + int INT_VECTOR_SYS_CALL + pop ebx + ret + +; ==================================================================== +; lseek //added by xw, 18/6/18 +; ==================================================================== +lseek: + push ebx + mov ebx, esp + mov eax, _NR_lseek + int INT_VECTOR_SYS_CALL + pop ebx + ret + +; ==================================================================== +; unlink //added by xw, 18/6/18 +; ==================================================================== +unlink: + push ebx + mov ebx, esp + mov eax, _NR_unlink + int INT_VECTOR_SYS_CALL + pop ebx + ret + +;和FAT32有关的系统调用 +create: + push ebx + mov ebx, esp + mov eax, _NR_create + int INT_VECTOR_SYS_CALL + pop ebx + ret + +delete: + push ebx + mov ebx, esp + mov eax, _NR_delete + int INT_VECTOR_SYS_CALL + pop ebx + ret + +opendir: + push ebx + mov ebx, esp + mov eax, _NR_opendir + int INT_VECTOR_SYS_CALL + pop ebx + ret + +createdir: + push ebx + mov ebx, esp + mov eax, _NR_createdir + int INT_VECTOR_SYS_CALL + pop ebx + ret + +deletedir: + push ebx + mov ebx, esp + mov eax, _NR_deletedir + int INT_VECTOR_SYS_CALL + pop ebx + ret \ No newline at end of file diff --git a/kernel/syscallc.c b/kernel/syscallc.c new file mode 100644 index 0000000..890f1a0 --- /dev/null +++ b/kernel/syscallc.c @@ -0,0 +1,136 @@ +/********************************************************* +*系统调用具体函数的实现 +* +* +* +**********************************************************/ + +#include "type.h" +#include "const.h" +#include "protect.h" +#include "string.h" +#include "proc.h" +#include "global.h" +#include "proto.h" +#include "memman.h" + +struct memfree *memarg = 0; + +/*======================================================================* + sys_get_ticks add by visual 2016.4.6 + *======================================================================*/ +int sys_get_ticks() +{ + return ticks; +} + + +/*======================================================================* + sys_get_pid add by visual 2016.4.6 + *======================================================================*/ +int sys_get_pid() +{ + return p_proc_current->task.pid; +} + +/*======================================================================* + sys_kmalloc add by visual 2016.4.6 + *======================================================================*/ +void* sys_kmalloc(int size) +{ //edit by visual 2015.5.9 + return (void*)(do_kmalloc(size)); +} + +/*======================================================================* + sys_kmalloc_4k add by visual 2016.4.7 + *======================================================================*/ +void* sys_kmalloc_4k() +{ + return (void*)(do_kmalloc_4k()); +} + +/*======================================================================* + sys_malloc edit by visual 2016.5.4 + *======================================================================*/ +void* sys_malloc(int size) +{ + int vir_addr,AddrLin; + vir_addr = vmalloc(size); + + for( AddrLin=vir_addr; AddrLintask.pid,//进程pid //edit by visual 2016.5.19 + PG_P | PG_USU | PG_RWW,//页目录的属性位 + PG_P | PG_USU | PG_RWW);//页表的属性位 + } + return (void*)vir_addr; +} + + +/*======================================================================* + sys_malloc_4k edit by visual 2016.5.4 + *======================================================================*/ +void* sys_malloc_4k() +{ + int vir_addr,AddrLin; + vir_addr = vmalloc(num_4K); + + for( AddrLin=vir_addr; AddrLintask.pid,//进程pid //edit by visual 2016.5.19 + PG_P | PG_USU | PG_RWW,//页目录的属性位 + PG_P | PG_USU | PG_RWW);//页表的属性位 + } + return (void*)vir_addr; +} + + +/*======================================================================* + sys_free add by visual 2016.4.7 + *======================================================================*/ +int sys_free(void *arg) +{ + memarg = (struct memfree *)arg; + return do_free(memarg->addr,memarg->size); +} + +/*======================================================================* + sys_free_4k edit by visual 2016.5.9 + *======================================================================*/ +int sys_free_4k(void* AddrLin) +{//线性地址可以不释放,但是页表映射关系必须清除! + int phy_addr; //add by visual 2016.5.9 + + phy_addr = get_page_phy_addr(p_proc_current->task.pid,(int)AddrLin);//获取物理页的物理地址 //edit by visual 2016.5.19 + lin_mapping_phy( (int)AddrLin,//线性地址 + phy_addr,//物理地址 + p_proc_current->task.pid,//进程pid //edit by visual 2016.5.19 + PG_P | PG_USU | PG_RWW,//页目录的属性位 + 0 | PG_USU | PG_RWW);//页表的属性位 + return do_free_4k(phy_addr); +} + + +/*======================================================================* + sys_udisp_int add by visual 2016.5.16 +用户用的打印函数 + *======================================================================*/ +void sys_udisp_int(int arg) +{ + disp_int(arg); + return ; +} + +/*======================================================================* + sys_udisp_str add by visual 2016.5.16 +用户用的打印函数 + *======================================================================*/ +void sys_udisp_str(char *arg) +{ + disp_str(arg); + return ; +} \ No newline at end of file diff --git a/kernel/testfunc.c b/kernel/testfunc.c new file mode 100644 index 0000000..bfe3c84 --- /dev/null +++ b/kernel/testfunc.c @@ -0,0 +1,53 @@ +/** + * testfunc.c + * This file contains functions that are used to test features we add. + * Added by xw, 18/6/17 + */ + +#include "type.h" +#include "const.h" +#include "protect.h" +#include "string.h" +#include "proc.h" +#include "global.h" +#include "proto.h" + +/* + * This syscall needs long time to finish, so we can use it + * to check if our os is kernel-preemptive. + * added by xw, 18/4/27 + */ +void sys_print_E() +{ + int i, j; + + disp_str("E( "); + + i = 100; + while(--i){ + j = 1000; + while(--j){} + } + + disp_str(") "); +} + +/* + * This syscall needs long time to finish, so we can use it + * to check if our os is kernel-preemptive. + * added by xw, 18/4/27 + */ +void sys_print_F() +{ + int i, j; + + disp_str("F( "); + + i = 100; + while(--i){ + j = 1000; + while(--j){} + } + + disp_str(") "); +} diff --git a/kernel/tty.c b/kernel/tty.c new file mode 100644 index 0000000..3f21ada --- /dev/null +++ b/kernel/tty.c @@ -0,0 +1,295 @@ +#include "type.h" +#include "const.h" +#include "protect.h" +#include "string.h" +#include "proc.h" +#include "tty.h" +#include "console.h" +#include "global.h" +#include "proto.h" +#include "keyboard.h" +#include "x86.h" + +int current_console; //当前显示在屏幕上的console +void tty_write(TTY *tty, char *buf, int len); +int tty_read(TTY *tty, char *buf, int len); + +static void init_tty(TTY *tty); +static void tty_mouse(TTY *tty); +static void tty_dev_read(TTY *tty); +static void tty_dev_write(TTY *tty); +static void put_key(TTY *tty, u32 key); + +void in_process(TTY *p_tty, u32 key) +{ + int real_line = p_tty->console->orig / SCR_WIDTH; + + if (!(key & FLAG_EXT)) + { + put_key(p_tty, key); + } + else + { + int raw_code = key & MASK_RAW; + switch (raw_code) + { + case ENTER: + put_key(p_tty, '\n'); + p_tty->status = p_tty->status & 3; //&3'b011 + break; + case BACKSPACE: + put_key(p_tty, '\b'); + break; + case UP: + if (p_tty->console->current_line < 43) + { + disable_int(); + p_tty->console->current_line++; + outb(CRTC_ADDR_REG, START_ADDR_H); + outb(CRTC_DATA_REG, ((80 * (p_tty->console->current_line + real_line)) >> 8) & 0xFF); + outb(CRTC_ADDR_REG, START_ADDR_L); + outb(CRTC_DATA_REG, (80 * (p_tty->console->current_line + real_line)) & 0xFF); + enable_int(); + } + break; + case DOWN: + if (p_tty->console->current_line > 0) + { + disable_int(); + p_tty->console->current_line--; + outb(CRTC_ADDR_REG, START_ADDR_H); + outb(CRTC_DATA_REG, ((80 * (p_tty->console->current_line + real_line)) >> 8) & 0xFF); + outb(CRTC_ADDR_REG, START_ADDR_L); + outb(CRTC_DATA_REG, (80 * (p_tty->console->current_line + real_line)) & 0xFF); + enable_int(); + } + break; + case F1: + case F2: + case F3: + case F4: + case F5: + case F6: + case F7: + case F8: + case F9: + case F10: + case F11: + case F12: + select_console(raw_code - F1); + break; + } + } +} + +#define TTY_FIRST (tty_table) +#define TTY_END (tty_table + NR_CONSOLES) + +void task_tty() +{ + TTY *p_tty; + for (p_tty = TTY_FIRST; p_tty < TTY_END; p_tty++) + { + init_tty(p_tty); + } + p_tty = tty_table; + + select_console(0); + + //设置第一个tty光标位置,第一个tty需要特殊处理 + disable_int(); + outb(CRTC_ADDR_REG, CURSOR_H); + outb(CRTC_DATA_REG, ((disp_pos / 2) >> 8) & 0xFF); + outb(CRTC_ADDR_REG, CURSOR_L); + outb(CRTC_DATA_REG, (disp_pos / 2) & 0xFF); + enable_int(); + + //轮询 + while (1) + { + for (p_tty = TTY_FIRST; p_tty < TTY_END; p_tty++) + { + do + { + tty_mouse(p_tty); /* tty判断鼠标操作 */ + tty_dev_read(p_tty); /* 从键盘输入缓冲区读到这个tty自己的缓冲区 */ + tty_dev_write(p_tty); /* 把tty缓存区的数据写到这个tty占有的显存 */ + + } while (p_tty->ibuf_cnt); + } + } +} + +static void init_tty(TTY *p_tty) +{ + p_tty->ibuf_read_cnt = p_tty->ibuf_cnt = 0; + p_tty->status = TTY_STATE_DISPLAY; + p_tty->ibuf_read = p_tty->ibuf_head = p_tty->ibuf_tail = p_tty->ibuf; + int det = p_tty - tty_table; + p_tty->console = console_table + det; + + p_tty->mouse_left_button = 0; + p_tty->mouse_mid_button = 0; + p_tty->mouse_X = 0; + p_tty->mouse_Y = 0; + init_screen(p_tty); +} + +static void tty_mouse(TTY *tty) +{ + if (is_current_console(tty->console)) + { + int real_line = tty->console->orig / SCR_WIDTH; + if (tty->mouse_left_button) + { + + if (tty->mouse_Y > MOUSE_UPDOWN_BOUND) + { //按住鼠标左键向上滚动 + if (tty->console->current_line < 43) + { + disable_int(); + tty->console->current_line++; + outb(CRTC_ADDR_REG, START_ADDR_H); + outb(CRTC_DATA_REG, ((80 * (tty->console->current_line + real_line)) >> 8) & 0xFF); + outb(CRTC_ADDR_REG, START_ADDR_L); + outb(CRTC_DATA_REG, (80 * (tty->console->current_line + real_line)) & 0xFF); + enable_int(); + tty->mouse_Y = 0; + } + } + else if (tty->mouse_Y < -MOUSE_UPDOWN_BOUND) + { //按住鼠标左键向下滚动 + if (tty->console->current_line > 0) + { + disable_int(); + tty->console->current_line--; + outb(CRTC_ADDR_REG, START_ADDR_H); + outb(CRTC_DATA_REG, ((80 * (tty->console->current_line + real_line)) >> 8) & 0xFF); + outb(CRTC_ADDR_REG, START_ADDR_L); + outb(CRTC_DATA_REG, (80 * (tty->console->current_line + real_line)) & 0xFF); + enable_int(); + tty->mouse_Y = 0; + } + } + } + + if (tty->mouse_mid_button) + { //点击中键复原 + disable_int(); + tty->console->current_line = 0; + outb(CRTC_ADDR_REG, START_ADDR_H); + outb(CRTC_DATA_REG, ((80 * (tty->console->current_line + real_line)) >> 8) & 0xFF); + outb(CRTC_ADDR_REG, START_ADDR_L); + outb(CRTC_DATA_REG, (80 * (tty->console->current_line + real_line)) & 0xFF); + enable_int(); + tty->mouse_Y = 0; + } + } +} + +static void tty_dev_read(TTY *tty) +{ + if (is_current_console(tty->console)) + { + keyboard_read(tty); + } +} + +static void tty_dev_write(TTY *tty) +{ + + if (tty->ibuf_cnt) + { + char ch = *(tty->ibuf_tail); + tty->ibuf_tail++; + if (tty->ibuf_tail == tty->ibuf + TTY_IN_BYTES) + { + tty->ibuf_tail = tty->ibuf; + } + tty->ibuf_cnt--; + + if (ch == '\b') + { + if (tty->ibuf_read_cnt == 1) + { + tty->ibuf_read_cnt--; + tty->ibuf_head--; + tty->ibuf_tail--; + return; + } + else + { + tty->ibuf_read_cnt -= 2; + if (tty->ibuf_head == tty->ibuf) + { + tty->ibuf_head = tty->ibuf_tail = &tty->ibuf[256 - 2]; + } + else + { + tty->ibuf_head--; + tty->ibuf_tail--; + tty->ibuf_head--; + tty->ibuf_tail--; + } + } + } + out_char(tty->console, ch); + } +} + +static void put_key(TTY *tty, u32 key) +{ + if (tty->ibuf_cnt < TTY_IN_BYTES) + { + *(tty->ibuf_head) = key; + tty->ibuf_head++; + if (tty->ibuf_head == tty->ibuf + TTY_IN_BYTES) + tty->ibuf_head = tty->ibuf; + tty->ibuf_cnt++; + tty->ibuf_read_cnt++; + } +} + +/***************************************************************************** + * tty_write + **************************************************************************** + + * 当fd=STD_OUT时,write()系统调用转发到此函数 + *****************************************************************************/ +void tty_write(TTY *tty, char *buf, int len) +{ + while (--len >= 0) + out_char(tty->console, *buf++); +} + +/***************************************************************************** + * tty_read + **************************************************************************** + + * 当fd=STD_IN时,read()系统调用转发到此函数 + *****************************************************************************/ +int tty_read(TTY *tty, char *buf, int len) +{ + int i = 0; + if (!tty->ibuf_read_cnt) + { + tty->status |= TTY_STATE_WAIT_ENTER; + } + + while ((tty->status & TTY_STATE_WAIT_ENTER)) + ; + + while (tty->ibuf_read_cnt && i < len) + { + buf[i] = *tty->ibuf_read; + tty->ibuf_read++; + if (tty->ibuf_read == tty->ibuf + TTY_IN_BYTES) + { + tty->ibuf_read = tty->ibuf; + } + tty->ibuf_read_cnt--; + i++; + } + + return i; +} \ No newline at end of file diff --git a/kernel/vfs.c b/kernel/vfs.c new file mode 100644 index 0000000..262de5f --- /dev/null +++ b/kernel/vfs.c @@ -0,0 +1,477 @@ +/********************************************************** +* vfs.c //added by mingxuan 2019-5-17 +***********************************************************/ + +#include "type.h" +#include "const.h" +#include "protect.h" +#include "string.h" +#include "proc.h" +#include "global.h" +#include "proto.h" +#include "fs_const.h" +#include "hd.h" +#include "fs.h" +#include "fs_misc.h" +#include "vfs.h" +#include "fat32.h" +#include "stdio.h" + +//static struct device device_table[NR_DEV]; //deleted by mingxuan 2020-10-18 +static struct vfs vfs_table[NR_FS]; //modified by mingxuan 2020-10-18 + +struct file_desc f_desc_table[NR_FILE_DESC]; +struct super_block super_block[NR_SUPER_BLOCK]; //added by mingxuan 2020-10-30 + +//static struct file_op f_op_table[NR_fs]; //文件系统操作表 +static struct file_op f_op_table[NR_FS_OP]; //modified by mingxuan 2020-10-18 +static struct sb_op sb_op_table[NR_SB_OP]; //added by mingxuan 2020-10-30 + +//static void init_dev_table();//deleted by mingxuan 2020-10-30 +static void init_vfs_table(); //modified by mingxuan 2020-10-30 +void init_file_desc_table(); //added by mingxuan 2020-10-30 +void init_fileop_table(); +void init_super_block_table(); //added by mingxuan 2020-10-30 +void init_sb_op_table(); + +static int get_index(char path[]); + +void init_vfs() +{ + + init_file_desc_table(); + init_fileop_table(); + + init_super_block_table(); + init_sb_op_table(); //added by mingxuan 2020-10-30 + + //init_dev_table(); //deleted by mingxuan 2020-10-30 + init_vfs_table(); //modified by mingxuan 2020-10-30 +} + +//added by mingxuan 2020-10-30 +void init_file_desc_table() +{ + int i; + for (i = 0; i < NR_FILE_DESC; i++) + memset(&f_desc_table[i], 0, sizeof(struct file_desc)); +} + +void init_fileop_table() +{ + // table[0] for tty + f_op_table[0].open = real_open; + f_op_table[0].close = real_close; + f_op_table[0].write = real_write; + f_op_table[0].lseek = real_lseek; + f_op_table[0].unlink = real_unlink; + f_op_table[0].read = real_read; + + // table[1] for orange + f_op_table[1].open = real_open; + f_op_table[1].close = real_close; + f_op_table[1].write = real_write; + f_op_table[1].lseek = real_lseek; + f_op_table[1].unlink = real_unlink; + f_op_table[1].read = real_read; + + // table[2] for fat32 + f_op_table[2].create = CreateFile; + f_op_table[2].delete = DeleteFile; + f_op_table[2].open = OpenFile; + f_op_table[2].close = CloseFile; + f_op_table[2].write = WriteFile; + f_op_table[2].read = ReadFile; + f_op_table[2].opendir = OpenDir; + f_op_table[2].createdir = CreateDir; + f_op_table[2].deletedir = DeleteDir; + +} + +//added by mingxuan 2020-10-30 +void init_super_block_table(){ + struct super_block * sb = super_block; //deleted by mingxuan 2020-10-30 + + //super_block[0] is tty0, super_block[1] is tty1, uper_block[2] is tty2 + for(; sb < &super_block[3]; sb++) { + sb->sb_dev = DEV_CHAR_TTY; + sb->fs_type = TTY_FS_TYPE; + } + + //super_block[3] is orange's superblock + sb->sb_dev = DEV_HD; + sb->fs_type = ORANGE_TYPE; + sb++; + + //super_block[4] is fat32's superblock + sb->sb_dev = DEV_HD; + sb->fs_type = FAT32_TYPE; + sb++; + + //another super_block are free + for (; sb < &super_block[NR_SUPER_BLOCK]; sb++) { + sb->sb_dev = NO_DEV; + sb->fs_type = NO_FS_TYPE; + } +} + +//added by mingxuan 2020-10-30 +void init_sb_op_table(){ + //orange + sb_op_table[0].read_super_block = read_super_block; + sb_op_table[0].get_super_block = get_super_block; + + //fat32 and tty + sb_op_table[1].read_super_block = NULL; + sb_op_table[1].get_super_block = NULL; +} + +//static void init_dev_table(){ +static void init_vfs_table(){ // modified by mingxuan 2020-10-30 + + // 我们假设每个tty就是一个文件系统 + // tty0 + // device_table[0].dev_name="dev_tty0"; + // device_table[0].op = &f_op_table[0]; + vfs_table[0].fs_name = "dev_tty0"; //modifed by mingxuan 2020-10-18 + vfs_table[0].op = &f_op_table[0]; + vfs_table[0].sb = &super_block[0]; //每个tty都有一个superblock //added by mingxuan 2020-10-30 + vfs_table[0].s_op = &sb_op_table[1]; //added by mingxuan 2020-10-30 + + // tty1 + //device_table[1].dev_name="dev_tty1"; + //device_table[1].op =&f_op_table[0]; + vfs_table[1].fs_name = "dev_tty1"; //modifed by mingxuan 2020-10-18 + vfs_table[1].op = &f_op_table[0]; + vfs_table[1].sb = &super_block[1]; //每个tty都有一个superblock //added by mingxuan 2020-10-30 + vfs_table[1].s_op = &sb_op_table[1]; //added by mingxuan 2020-10-30 + + // tty2 + //device_table[2].dev_name="dev_tty2"; + //device_table[2].op=&f_op_table[0]; + vfs_table[2].fs_name = "dev_tty2"; //modifed by mingxuan 2020-10-18 + vfs_table[2].op = &f_op_table[0]; + vfs_table[2].sb = &super_block[2]; //每个tty都有一个superblock //added by mingxuan 2020-10-30 + vfs_table[2].s_op = &sb_op_table[1]; //added by mingxuan 2020-10-30 + + // fat32 + //device_table[3].dev_name="fat0"; + //device_table[3].op=&f_op_table[2]; + vfs_table[3].fs_name = "fat0"; //modifed by mingxuan 2020-10-18 + vfs_table[3].op = &f_op_table[2]; + vfs_table[3].sb = &super_block[4]; //added by mingxuan 2020-10-30 + vfs_table[3].s_op = &sb_op_table[1]; //added by mingxuan 2020-10-30 + + // orange + //device_table[4].dev_name="orange"; + //device_table[4].op=&f_op_table[1]; + vfs_table[4].fs_name = "orange"; //modifed by mingxuan 2020-10-18 + vfs_table[4].op = &f_op_table[1]; + vfs_table[4].sb = &super_block[3]; //added by mingxuan 2020-10-30 + vfs_table[4].s_op = &sb_op_table[0]; //added by mingxuan 2020-10-30 + +} + +static int get_index(char path[]){ + + int pathlen = strlen(path); + //char dev_name[DEV_NAME_LEN]; + char fs_name[DEV_NAME_LEN]; //modified by mingxuan 2020-10-18 + int len = (pathlen < DEV_NAME_LEN) ? pathlen : DEV_NAME_LEN; + + int i,a=0; + for(i=0;iopen(pathname, flags); //modified by mingxuan 2020-10-18 + if(fd != -1) + { + p_proc_current -> task.filp[fd] -> dev_index = index; + } else { + kprintf(" error!\n"); + } + + return fd; +} + + +int do_vclose(int fd) { + int index = p_proc_current->task.filp[fd]->dev_index; + return vfs_table[index].op->close(fd); //modified by mingxuan 2020-10-18 +} + +int do_vread(int fd, char *buf, int count) { + int index = p_proc_current->task.filp[fd]->dev_index; + return vfs_table[index].op->read(fd, buf, count); //modified by mingxuan 2020-10-18 +} + +int do_vwrite(int fd, const char *buf, int count) { + //modified by mingxuan 2019-5-23 + char s[512]; + int index = p_proc_current->task.filp[fd]->dev_index; + const char *fsbuf = buf; + int f_len = count; + int bytes; + while(f_len) + { + int iobytes = min(512, f_len); + int i=0; + for(i=0; iwrite(fd,s,iobytes); + bytes = vfs_table[index].op->write(fd,s,iobytes); //modified by mingxuan 2020-10-18 + if(bytes != iobytes) + { + return bytes; + } + f_len -= bytes; + } + return count; +} + +int do_vunlink(const char *path) { + int pathlen = strlen(path); + char pathname[MAX_PATH]; + + strcpy(pathname,(char *)path); + pathname[pathlen] = 0; + + int index; + index = get_index(pathname); + if(index==-1){ + kprintf("pathname error!\n"); + return -1; + } + + //return device_table[index].op->unlink(pathname); + return vfs_table[index].op->unlink(pathname); //modified by mingxuan 2020-10-18 +} + +int do_vlseek(int fd, int offset, int whence) { + int index = p_proc_current->task.filp[fd]->dev_index; + + //return device_table[index].op->lseek(fd, offset, whence); + return vfs_table[index].op->lseek(fd, offset, whence); //modified by mingxuan 2020-10-18 + +} + +//int do_vcreate(char *pathname) { +int do_vcreate(char *filepath) { //modified by mingxuan 2019-5-17 + //added by mingxuan 2019-5-17 + int state; + const char *path = filepath; + + int pathlen = strlen(path); + char pathname[MAX_PATH]; + + strcpy(pathname,(char *)path); + pathname[pathlen] = 0; + + int index; + index = get_index(pathname); + if(index == -1){ + kprintf("pathname error! path: %s\n", path); + return -1; + } + state = vfs_table[index].op->create(pathname); //modified by mingxuan 2020-10-18 + if (state == 1) { + kprintf(" create file success!"); + } else { + DisErrorInfo(state); + } + return state; +} + +int do_vdelete(char *path) { + + int pathlen = strlen(path); + char pathname[MAX_PATH]; + + strcpy(pathname,path); + pathname[pathlen] = 0; + + int index; + index = get_index(pathname); + if(index==-1){ + kprintf("pathname error!\n"); + return -1; + } + //return device_table[index].op->delete(pathname); + return vfs_table[index].op->delete(pathname); //modified by mingxuan 2020-10-18 +} +int do_vopendir(char *path) { + int state; + + int pathlen = strlen(path); + char pathname[MAX_PATH]; + + strcpy(pathname,path); + pathname[pathlen] = 0; + + int index; + index = (int)(pathname[1]-'0'); + + for(int j=0;j<= pathlen-3;j++) + { + pathname[j] = pathname[j+3]; + } + state = f_op_table[index].opendir(pathname); + if (state == 1) { + kprintf(" open dir success!"); + } else { + DisErrorInfo(state); + } + return state; +} + +int do_vcreatedir(char *path) { + int state; + + int pathlen = strlen(path); + char pathname[MAX_PATH]; + + strcpy(pathname,path); + pathname[pathlen] = 0; + + int index; + index = (int)(pathname[1]-'0'); + + for(int j=0;j<= pathlen-3;j++) + { + pathname[j] = pathname[j+3]; + } + state = f_op_table[index].createdir(pathname); + if (state == 1) { + kprintf(" create dir success!"); + } else { + DisErrorInfo(state); + } + return state; +} + +int do_vdeletedir(char *path) { + int state; + int pathlen = strlen(path); + char pathname[MAX_PATH]; + + strcpy(pathname,path); + pathname[pathlen] = 0; + + int index; + index = (int)(pathname[1]-'0'); + + for(int j=0;j<= pathlen-3;j++) + { + pathname[j] = pathname[j+3]; + } + state = f_op_table[index].deletedir(pathname); + if (state == 1) { + kprintf(" delete dir success!"); + } else { + DisErrorInfo(state); + } + return state; +} \ No newline at end of file diff --git a/lib/Makefrag b/lib/Makefrag new file mode 100644 index 0000000..27b511d --- /dev/null +++ b/lib/Makefrag @@ -0,0 +1,26 @@ +OBJDIRS += lib + +LIB_OBJS := lib/string.o \ + kernel/syscall.o \ + lib/printf.o \ + lib/kprintf.o \ + lib/scanf.o \ + lib/printfmt.o \ + +LIB_OBJS := $(patsubst %, $(OBJDIR)/%, $(LIB_OBJS)) + +LIB_A = $(OBJDIR)/lib/ulib.a + +$(OBJDIR)/lib/%.o: lib/%.c $(OBJDIR)/.vars.CFLAGS + @echo + cc $< + @mkdir -p $(@D) + @$(CC) $(CFLAGS) -c -o $@ $< + +$(OBJDIR)/lib/%.o: lib/%.asm + @echo + as obj $< + @mkdir -p $(@D) + @$(AS) -I ./include -f elf -o $@ $< + +$(LIB_A): $(LIB_OBJS) + @echo + ar $@ + @$(AR) $(ARFLAGS) -o $@ $(LIB_OBJS) \ No newline at end of file diff --git a/lib/klib.c b/lib/klib.c new file mode 100644 index 0000000..7c443e3 --- /dev/null +++ b/lib/klib.c @@ -0,0 +1,75 @@ + +/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + klib.c +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + Forrest Yu, 2005 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + +#include "type.h" +#include "const.h" +#include "protect.h" +#include "string.h" +#include "proc.h" +#include "global.h" +#include "proto.h" + + +/*======================================================================* + itoa + *======================================================================*/ + char * itoa(char * str, int num)/* 数字前面的 0 不被显示出来, 比如 0000B800 被显示成 B800 */ +{ + char * p = str; + char ch; + int i; + int flag = FALSE; + + *p++ = '0'; + *p++ = 'x'; + + if(num == 0){ + *p++ = '0'; + } + else{ + for(i=28;i>=0;i-=4){ + ch = (num >> i) & 0xF; + if(flag || (ch > 0)){ + flag = TRUE; + ch += '0'; + if(ch > '9'){ + ch += 7; + } + *p++ = ch; + } + } + } + + *p = 0; + + return str; +} + + +/*======================================================================* + disp_int + *======================================================================*/ + void disp_int(int input) +{ + char output[16]; + itoa(output, input); + disp_str(output); +} + +/*======================================================================* + delay + *======================================================================*/ + void delay(int time) +{ + int i, j, k; + for(k=0;k +#include +#include +#include + +/* + * Print a number (base <= 16) in reverse order, + * using specified putch function and associated pointer putdat. + */ +static void +printnum(void (*putch)(int, void*), void *putdat, + unsigned long num, unsigned base, int width, int padc) +{ + // first recursively print all preceding (more significant) digits + if (num >= base) { + printnum(putch, putdat, num / base, base, width - 1, padc); + } else { + // print any needed pad characters before first digit + while (--width > 0) + putch(padc, putdat); + } + + // then print this (the least significant) digit + putch("0123456789abcdef"[num % base], putdat); +} + +// Get an unsigned int of various possible sizes from a varargs list, +// depending on the lflag parameter. +static unsigned long long +getuint(va_list *ap, int lflag) +{ + if (lflag >= 2) + return va_arg(*ap, unsigned long long); + else if (lflag) + return va_arg(*ap, unsigned long); + else + return va_arg(*ap, unsigned int); +} + +// Same as getuint but signed - can't use getuint +// because of sign extension +static long long +getint(va_list *ap, int lflag) +{ + if (lflag >= 2) + return va_arg(*ap, long long); + else if (lflag) + return va_arg(*ap, long); + else + return va_arg(*ap, int); +} + +// Main function to format and print a string. +void printfmt(void (*putch)(int, void*), void *putdat, const char *fmt, ...); + +void +vprintfmt(void (*putch)(int, void*), void *putdat, const char *fmt, va_list ap) +{ + register const char *p; + register int ch, err; + unsigned long long num; + int base, lflag, width, precision, altflag; + char padc; + + while (1) { + while ((ch = *(unsigned char *) fmt++) != '%') { + if (ch == '\0') + return; + putch(ch, putdat); + } + + // Process a %-escape sequence + padc = ' '; + width = -1; + precision = -1; + lflag = 0; + altflag = 0; + reswitch: + switch (ch = *(unsigned char *) fmt++) { + + // flag to pad on the right + case '-': + padc = '-'; + goto reswitch; + + // flag to pad with 0's instead of spaces + case '0': + padc = '0'; + goto reswitch; + + // width field + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + for (precision = 0; ; ++fmt) { + precision = precision * 10 + ch - '0'; + ch = *fmt; + if (ch < '0' || ch > '9') + break; + } + goto process_precision; + + case '*': + precision = va_arg(ap, int); + goto process_precision; + + case '.': + if (width < 0) + width = 0; + goto reswitch; + + case '#': + altflag = 1; + goto reswitch; + + process_precision: + if (width < 0) + width = precision, precision = -1; + goto reswitch; + + // long flag (doubled for long long) + case 'l': + lflag++; + goto reswitch; + + // character + case 'c': + putch(va_arg(ap, int), putdat); + break; + + // string + case 's': + if ((p = va_arg(ap, char *)) == NULL) + p = "(null)"; + if (width > 0 && padc != '-') + for (width -= strnlen(p, precision); width > 0; width--) + putch(padc, putdat); + for (; (ch = *p++) != '\0' && (precision < 0 || --precision >= 0); width--) + if (altflag && (ch < ' ' || ch > '~')) + putch('?', putdat); + else + putch(ch, putdat); + for (; width > 0; width--) + putch(' ', putdat); + break; + + // (signed) decimal + case 'd': + num = getint(&ap, lflag); + if ((long long) num < 0) { + putch('-', putdat); + num = -(long long) num; + } + base = 10; + goto number; + + // unsigned decimal + case 'u': + num = getuint(&ap, lflag); + base = 10; + goto number; + + // (unsigned) octal + case 'o': + // Replace this with your code. + putch('X', putdat); + putch('X', putdat); + putch('X', putdat); + break; + + // pointer + case 'p': + putch('0', putdat); + putch('x', putdat); + num = (unsigned long long) + (uintptr_t) va_arg(ap, void *); + base = 16; + goto number; + + // (unsigned) hexadecimal + case 'x': + num = getuint(&ap, lflag); + base = 16; + number: + printnum(putch, putdat, num, base, width, padc); + break; + + // escaped '%' character + case '%': + putch(ch, putdat); + break; + + // unrecognized escape sequence - just print it literally + default: + putch('%', putdat); + for (fmt--; fmt[-1] != '%'; fmt--) + /* do nothing */; + break; + } + } +} + +void +printfmt(void (*putch)(int, void*), void *putdat, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vprintfmt(putch, putdat, fmt, ap); + va_end(ap); +} + +struct sprintbuf { + char *buf; + char *ebuf; + int cnt; +}; + +static void +sprintputch(int ch, struct sprintbuf *b) +{ + b->cnt++; + if (b->buf < b->ebuf) + *b->buf++ = ch; +} + +int +vsnprintf(char *buf, int n, const char *fmt, va_list ap) +{ + struct sprintbuf b = {buf, buf+n-1, 0}; + + if (buf == NULL || n < 1) + return -1; + + // print the string to the buffer + vprintfmt((void*)sprintputch, &b, fmt, ap); + + // null terminate the buffer + *b.buf = '\0'; + + return b.cnt; +} + +int +snprintf(char *buf, int n, const char *fmt, ...) +{ + va_list ap; + int rc; + + va_start(ap, fmt); + rc = vsnprintf(buf, n, fmt, ap); + va_end(ap); + + return rc; +} \ No newline at end of file diff --git a/lib/scanf.c b/lib/scanf.c new file mode 100644 index 0000000..f07c1a8 --- /dev/null +++ b/lib/scanf.c @@ -0,0 +1,72 @@ +#include "stdio.h" + +char getchar() +{ + char ch; + return read(STD_IN,&ch,1)==1 ? ch : EOF; +} + +char* gets(char *str) +{ + char c; + char *cs; + cs= str; + while( (c=getchar()) != EOF ){ + if( (*cs=c)=='\n' ){ + *cs = '\0'; + break; + } + cs++; + } + return str; +} + +// int scanf(char *str, ...) +// { +// va_list vl; +// int i = 0, j = 0, ret = 0; +// char buff[100] = {0}; +// char *out_loc; +// gets(buff); +// va_start(vl, str); +// i = 0; +// while (str && str[i]) +// { +// if (str[i] == '%') +// { +// i++; +// switch (str[i]) +// { +// case 'c': +// { +// *(char *)va_arg(vl, char *) = buff[j]; +// j++; +// ret++; +// break; +// } +// case 'd': +// { +// *(int *)va_arg(vl, int *) = strtol(&buff[j], &out_loc, 10); +// j += out_loc - &buff[j]; +// ret++; +// break; +// } +// case 'x': +// { +// *(int *)va_arg(vl, int *) = strtol(&buff[j], &out_loc, 16); +// j += out_loc - &buff[j]; +// ret++; +// break; +// } +// } +// } +// else +// { +// buff[j] = str[i]; +// j++; +// } +// i++; +// } +// va_end(vl); +// return ret; +// } \ No newline at end of file diff --git a/lib/string.c b/lib/string.c new file mode 100644 index 0000000..d1e5417 --- /dev/null +++ b/lib/string.c @@ -0,0 +1,110 @@ +#include "string.h" + +int +strlen(const char *s) +{ + int n; + + for (n = 0; *s != '\0'; s++) + n++; + return n; +} + +int +strnlen(const char *s, size_t size) +{ + int n; + + for (n = 0; size > 0 && *s != '\0'; s++, size--) + n++; + return n; +} + +char * +strcpy(char *dst, const char *src) +{ + char *ret; + + ret = dst; + while ((*dst++ = *src++) != '\0') + /* do nothing */; + return ret; +} + +char * +strcat(char *dst, const char *src) +{ + int len = strlen(dst); + strcpy(dst + len, src); + return dst; +} + +char * +strncpy(char *dst, const char *src, size_t size) { + size_t i; + char *ret; + + ret = dst; + for (i = 0; i < size; i++) { + *dst++ = *src; + // If strlen(src) < size, null-pad 'dst' out to 'size' chars + if (*src != '\0') + src++; + } + return ret; +} + +int +strcmp(const char *p, const char *q) +{ + while (*p && *p == *q) + p++, q++; + return (int) ((unsigned char) *p - (unsigned char) *q); +} + +int +strncmp(const char *p, const char *q, size_t n) +{ + while (n > 0 && *p && *p == *q) + n--, p++, q++; + if (n == 0) + return 0; + else + return (int) ((unsigned char) *p - (unsigned char) *q); +} + +void * +memset(void *v, int c, size_t n) +{ + char *p; + int m; + + p = v; + m = n; + while (--m >= 0) + *p++ = c; + + return v; +} + +void * +memcpy(void *dst, const void *src, size_t n) +{ + const char *s; + char *d; + + s = src; + d = dst; + + if (s < d && s + n > d) { + s += n; + d += n; + while (n-- > 0) + *--d = *--s; + } else { + while (n-- > 0) + *d++ = *s++; + } + + return dst; +} \ No newline at end of file diff --git a/mergedep.pl b/mergedep.pl new file mode 100644 index 0000000..1730d53 --- /dev/null +++ b/mergedep.pl @@ -0,0 +1,86 @@ +#!/usr/bin/perl +# Copyright 2003 Bryan Ford +# Distributed under the GNU General Public License. +# +# Usage: mergedep [ ...] +# +# This script merges the contents of all specified +# on the command line into the single file , +# which may or may not previously exist. +# Dependencies in the will override +# any existing dependencies for the same targets in . +# The are deleted after is updated. +# +# The are typically generated by GCC with the -MD option, +# and the is typically included from a Makefile, +# as shown here for GNU 'make': +# +# .deps: $(wildcard *.d) +# perl mergedep $@ $^ +# -include .deps +# +# This script properly handles multiple dependencies per , +# including dependencies having no target, +# so it is compatible with GCC3's -MP option. +# + +sub readdeps { + my $filename = shift; + + open(DEPFILE, $filename) or return 0; + while () { + if (/([^:]*):([^\\:]*)([\\]?)$/) { + my $target = $1; + my $deplines = $2; + my $slash = $3; + while ($slash ne '') { + $_ = ; + defined($_) or die + "Unterminated dependency in $filename"; + /(^[ \t][^\\]*)([\\]?)$/ or die + "Bad continuation line in $filename"; + $deplines = "$deplines\\\n$1"; + $slash = $2; + } + #print "DEPENDENCY [[$target]]: [[$deplines]]\n"; + $dephash{$target} = $deplines; + } elsif (/^[#]?[ \t]*$/) { + # ignore blank lines and comments + } else { + die "Bad dependency line in $filename: $_"; + } + } + close DEPFILE; + return 1; +} + + +if ($#ARGV < 0) { + print "Usage: mergedep [ ..]\n"; + exit(1); +} + +%dephash = (); + +# Read the main dependency file +$maindeps = $ARGV[0]; +readdeps($maindeps); + +# Read and merge in the new dependency files +foreach $i (1 .. $#ARGV) { + readdeps($ARGV[$i]) or die "Can't open $ARGV[$i]"; +} + +# Update the main dependency file +open(DEPFILE, ">$maindeps.tmp") or die "Can't open output file $maindeps.tmp"; +foreach $target (keys %dephash) { + print DEPFILE "$target:$dephash{$target}"; +} +close DEPFILE; +rename("$maindeps.tmp", "$maindeps") or die "Can't overwrite $maindeps"; + +# Finally, delete the new dependency files +foreach $i (1 .. $#ARGV) { + unlink($ARGV[$i]) or print "Error removing $ARGV[$i]\n"; +} + diff --git a/user/Makefrag b/user/Makefrag new file mode 100644 index 0000000..9df3303 --- /dev/null +++ b/user/Makefrag @@ -0,0 +1,33 @@ +OBJDIRS += user + +USERLIB_SRCS := user/initstart.asm + +USERLIB_OBJS := $(patsubst %.c, $(OBJDIR)/%.o, $(USERLIB_SRCS)) +USERLIB_OBJS := $(patsubst %.asm, $(OBJDIR)/%.o, $(USERLIB_OBJS)) + +# 这里给我整不会了,文件名只能长度为16位,否则会寄,原因还挺难找 +USER_SRCS := user/ptTest.c \ + user/shell_0.c \ + +USER_BINS := $(patsubst %.c, $(OBJDIR)/%.bin, $(USER_SRCS)) +USER_BASENAMES := $(patsubst $(OBJDIR)/user/%, %, $(USER_BINS)) + +USER_TAR:= app.tar + +$(OBJDIR)/user/%.o: user/%.c $(OBJDIR)/.vars.CFLAGS + @echo + cc $< + @mkdir -p $(@D) + @$(CC) $(CFLAGS) -c -o $@ $< + +$(OBJDIR)/user/%.o: user/%.asm + @echo + as obj $< + @mkdir -p $(@D) + @$(AS) -I ./include -f elf -o $@ $< + +$(OBJDIR)/user/%.bin: $(OBJDIR)/user/%.o $(USERLIB_OBJS) $(LIB_A) $(OBJDIR)/.vars.LDFLAGS + @echo + ld $@ + @$(LD) $(LDFLAGS) -o $@ $< $(USERLIB_OBJS) $(LIB_A) $(GCC_LIB) + +$(OBJDIR)/user/$(USER_TAR): $(USER_BINS) + @echo + tar $@ + @tar -vcf $@ -C $(OBJDIR)/user $(USER_BASENAMES) \ No newline at end of file diff --git a/user/initstart.asm b/user/initstart.asm new file mode 100644 index 0000000..24ba2c4 --- /dev/null +++ b/user/initstart.asm @@ -0,0 +1,19 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; initstart.asm //add by visual 2016.5.16 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + +extern main + +bits 32 + +[section .text] + +global _start + +_start: + push eax + push ecx + call main + + hlt diff --git a/user/ptTest.c b/user/ptTest.c new file mode 100644 index 0000000..8c169b0 --- /dev/null +++ b/user/ptTest.c @@ -0,0 +1,41 @@ +#include "stdio.h" + +int global=0; + +char *str2,*str3; + + +void pthread_test1() +{ + int i; + //pthread(pthread_test2); + while(1) + { + printf("pth1"); + printf("%d",++global); + printf(" "); + i=10000000; + while(--i){} + } +} + +/*======================================================================* + Syscall Pthread Test +added by xw, 18/4/27 + *======================================================================*/ + +int main(int arg,char *argv[]) +{ + int i=0; + + pthread(pthread_test1); + while(1) + { + printf("init"); + printf("%d",++global); + printf(" "); + i=10000000; + while(--i){} + } + return 0; +} diff --git a/user/shell_0.c b/user/shell_0.c new file mode 100644 index 0000000..5691b43 --- /dev/null +++ b/user/shell_0.c @@ -0,0 +1,31 @@ +#include "type.h" +#include "const.h" +#include "protect.h" +#include "string.h" +#include "proc.h" +#include "global.h" +#include "proto.h" +#include "stdio.h" + +int main(int arg, char *argv[]) +{ + int stdin = open("dev_tty0", O_RDWR); + int stdout = open("dev_tty0", O_RDWR); + int stderr = open("dev_tty0", O_RDWR); + + char buf[1024]; + int pid; + int times = 0; + while (1) + { + printf("\nminiOS:/ $ "); + if (gets(buf) && strlen(buf) != 0) + { + if (exec(buf) != 0) + { + printf("exec failed: file not found!\n"); + continue; + } + } + } +} \ No newline at end of file diff --git a/实验七-v0.2.pdf b/实验七-v0.2.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f95ee7c886151e9054735a40d4af4a41d5fb9f1d GIT binary patch literal 187390 zcmeFZWmp|swl0h$KyW8mSXgitF2UU)xVyW%CIokP3lM_4ySuvwcXz*w^xoaO&*^jS zx!=9NzNgodS(MBgRjD!781J0#yNdL)pfC+REi)|X`o`!aEG#{M4q&Zk2Fu9_RB*L1 z1ONqe9ds?MjbVYGb&U<}0Sutya@^dohE@iz-C$q0|Ed4)Zm>WJQv-W|I^%0^0MqX! z^Y10g? zV6}U^*e^M(!GkOskWH-Xh5Y<@dtk_LxBr-Hr|VgNyEB@g-P+t$i{5+SRZA<#h)Aoe>+$jInia~1!)I`#F)gSohW&6d^GFKsvsZ(M{B*;T zws&4VaHQkZ?(8pe3!@xro(9>s{KHU_LJA-I*oQJ!i#z8@Qx% z{8{b&MZ>bQrE6+#Nk7^I!6W5YcKInd-2uYQsc}b*E4bw2#nqD2NF(#GhNb#Jgq9hc z_DU6|vo#{{xUe^FZXOMPrtZ38$7wf-SkV&t>6|d6M#2js zd*k0?-xN(AK%zGJroHWN3iV^_j)de()2($*n>-fJGAG*K_63(p9}JjTA0gZQ7}Icy zuBizYCZD9AB91}h#{Ft&( z)4C9ll55mBzNsc(hdzikd)*Z*==|Qm4CDa7^a^H<``7sI-5m&x+CIkgBP{O)&Wm`7 zo7U?xG$i#?+w>WbXrNS9ucqaMTo6xg9mr#?+)`8cX*~TV2(E;xaa!~PbbB9skor7e z!8!sVE~~c=nM4Y9N!F0nRT{4s_}94hbDonwrQt3pE2Gzv*_YScA|qH>Yil~^=i$V8 z>4;2Nex8|8$i`7%tPZ!TbjS;>i7?)*MunyIGva@LoZa^`TIp_F>z3pPn4Dlm2*lne z=PRY;H4P0))0dI+b1^U9$q{mF3B@;&R7a0|sBt=J^K=V7^$?Ex7kY3M9HO}MN<-SIrbON&!xt?*kxaw* z*bfvmcAc(7j`vSdF2z4%zBpi^GkRtQ>JFj;>ube!(ZdeB7UrwMtXsn#=Z9?5#-rQ(FQd4q{+4 z!9?QkDwhF8r5srI=<}l-R9=v&KYcI*Bh>^A3Z&(+?vDR#_%er!Pu%vi-{)X6rygvxC!da+v3{E z#b@(>%E*m(8uYAa!(5tYTJF>-Sq$S>-;U;?$ocA;fpAet5P{{heex!sNydZ$7+$oS)^GtZchw`ur+`BW=jK)%*%b@(GQ|5+zqv-oRX zr)Oed{Cn}B|4a4wBiqyeE*bwa-NXJ-kyK>#%nbD%V1e?EdJey9l(4m(CFm5;@^{Um zhXwNUTe|?%nOPZV=|CR|y=e3aALl8QNPr+UbLe8>r}gwzJlkH*^4~ z13`sRlNS&#*IvL-CNhp)`}A>kl#fReTf^F~cEv)HgMIVq`C6l-($Qo?j}G`TN!!Kj2|6F!|0_a6ucd5X=|6I#6@7j-Ouw;xtWVVU2#Yv_T+j9@`xpNI z?bp`;U|?teyZ>K({YSC?8`yXS53fkW&JY9(enSmdpp>D3sqSxg(t*0OfMSM~o#8ij zuyO!(vj@CF1`t5_CpQXPgA9P&DNGNb{|!7q;lTi)|BXICF8yaW1Aq#KE)F1P!vguN ztgJyM>3>5O(2hUDm;M#8{Bf&)M(Q8e{TIOG&qxIYs=NazGNl2GuMur$?*O1@`)fpn z$qrg|&>=THP;HCmqLO`yBqAhPFpncb&4=!4Pkbwf!XX|C;k`QSo357Maya6~#PI=I z{+8$zWk?WNhaoM9hL`}CZnFX{#Bb=Bl&>VTT)GdzG{6!)Abk|tmWgSc^TS8m)x&Qd z)ze=nE!4X)OJ{WI-|)?u0TYHq&nh#G;qHPy_n|XbzT0y=X;=)kYU1krX^K-?WS70C zq`_$!k{5pEH{Ot<7uuCfuZ^y-r=fY8ZqC3=e=>|C(lh+XtWr2gSJ8WDd~Hb9EOsoib;tK-@GskL>~%_V98>K%C}?->=Dsy;XS+Tzjj z)ve(WboknZ-gkl+o3iq;2xWEW+53T8m^51>S+tjd=B+-M8@%2)+yh#40xjDwx5z^z zR!A0AS26_Z$*QS0&ti0N!NkFuoRxyMA!9{ZP3dnuF$Ra-H1n^%%aR!mO^?F*E z&Npk&!bD?2lweMSU%AQUROwZ`m9cFhC&R&+(TqisA>BjB^-(s(X!4I=QqMJpDL}+% zDlmUrsupWHy-(eo1>YXCMBQO_q4>-5aW>Xr* z`RfYCAETih?viz+gzr<(ANIeE+2Wyfs1g(I+TN>mD<1O0F^cIIifwfSDBxFZQ<5dA z_R~>Lf06C43zrZcwZGgCR@i5CK{2GCqwl>YRpD3|l1VNCyuDXv=LJ_CE zwHLl2Jh&J)k3lKdZjX+pa9eV^Uw?R>Vi<F=QU#;7NVs;e8qQ|S$?E{n@Z;oslGq*1+Q9-pn1|h13ilKnXu}TQ^(S|*A zUA+k`^i2M^(SVw}A} zDnf7>A(9J*ju9WFKf9GpK^8jjC+|Sw7VzXd*F9jHmiR^{zU76rES5m!C&(u5mEnAd zpl~{WNp~jPOTUoO7OwB4o9xw9SLT}kHag3OUxI9OOiSjwpSXX{*HpV$`?;v^IU_zb zoxxN`yJu!DRy$~S7I_L`flFkz)b%OS_(40r2)Cx0cFy3Gm*80G<6GisB&Yj+Okw(l zsKVOE_mDVDKzOspMk{#@HeOB&=_8ETAtZ1?7JSrGqZl?s23ls34#O8eK28` zV8op}()MQyK_W@;rOE0WSBX_coqI|x$*yH2+rarR$J zgnt1Wm6J5!3U7)2K~lG4g2Gjp-*KV3{dtKyk=-pkyP^JjHpM75Pi+`!FQqq!TRF96 zTOgC0BF=YK=^6&rh#Gs?& zOf`scyKM6((0dGV!h;=>b2`8j9s1Cd+bc_wgc)6Ge%fFbhFpq^eZmt44o&M?9Y^hn z0FxX2C+(PxKEOz>LVR52N0|p#GVRXYv|Me_By=||;b z%T*SCTQr8#MoH6HB?`0xeq!>$(8O&E7Q)!Ka6|~;$Aai*cBpr534)UTTQ#CL#sK) zVgtv+~Bc}NRolY#6KNeiKzGgbl0Oz) zPbK}Pl-e+X;9Yrh(lzFozxSmW$sC?WWb|{&>8?WZxdz+V_i|RM!J!%4jq$Ql+w+Zq zZW{)N)Z+DVJp9@5H6>^rycCoSmw3bp!FWam>C^2u3@$qm;Kn##Oi*y8NqtcrR>r$4 z5<bW{)giNx>2^A4#sk6*#mhXI}F{ZACG%*;CkKNwHM zSaWg(&Y|@)?OfS2-hcxkfl2Dyd4)X$(mmb^1_UYWK|k`z$WseyLDdFFlK}z3flmgo zu(IRKlb-{Fg_4wtM<%F94{XQ9RdJ39Jl6#o1;HxWIX zn?)PP+$X9}_gr;5&KoMNs@G;smy0r?vN)ELdrbZNE7@K(LHPrL#g3@TU>USvNse#OutgoU2uz*CWTj~wnpU^w6_kEI_( zIUy&Dk+Byv#*{o?zbMMnduy7?Np+_s#65F94Q3IiRpwPHI6-l)bXo05-qg;zyc6a^ zQY(qHQdVp%d^XG&p)^`}2Y6S~L3;(px-4ITv9)|Y)^Ki#%bw6EqAj({Zlwj}tu?!` z`KZ1SyCQYM<~8wlv7Za`tO**eUgcnS{a08bLg!VV+X!pNMq}zx8nLG^hpkIqrYujrDh5glUB-{HLEAp>O1$IhB`C1}Kq2trJ;cX!<=4be z)j7J#{XS)!jxz*~sWI5RUcRM1a^5Ul!g|C(KDV4W?J(Q=JrjJtIuo}8j>ttwT6>Dil2vt|9L}J<-FDur%0w}xyo0^6(f1ed!PZ^ze*tQLgVo;y zB^FlJzoXjM@yP%ENQwTBxzzvhPzf|@VFWO|&I!NH`TiH9roRl682LUzx=lqqMp8>pL=n2a;)?nVQKdnUA=m#Bb0(n$K1VPj) zjJMb0RoM^D>X&@(y3Yj9cNb>|wY`^TnXHNibKU2=ajtOZr)RwzJ3Txq7*P@05zbsM z&l?*jz4wIQ@%DknLC!~0MbR~sCZ)bL$w8H4qf(CUDCViG>O@Fuu0+?jA7(b5HqRFQ zPmt+@Ou__dQOIQQ%DLI}LXL_#M}wXmYH`MR%cu}^$9z^#+U8mqVl4-hPp`c> z{VK_RRP``MB(JIPga|N3CZ+xkwF0+zN_~QwuDhVP+{6ugiwi^c2J-GbtVORHs-5to z(dBVwmVkl_{hV7+p};*_7XGi#U_8Gb-UN_PB1WD1bk9fEmn5Ao!^L6}s9^Fjf*W)@ z+onR|NG5p|ZTmXS`93jC#jR6xof|wgy179RrJNr8{P>Y+;!_5 zlD43PCT+>ys2OojF}bfx_^$*8m6UTde$>@3RLijm6woaG6p-|blrZMkg-;kijxgtP zcRhU=7WD|$hodY?SMJ+%xbpX7FTKZC?*I_NgyuVnCf`8ZbD>MOq z0TMPoG;KssmLacLP8UpHowL*E4?_%MH6f({=qIKHCy7caYccqZVcH?p6#E}cz5G$1 zAirEt_E@#hRGo@75VsDhW>DuSt^ql;ztWN!gEPSwPaNYRe?K-Q zE(kTvH%@Fucu+4PE}6BxKF-xLX^EZYbKSafNbvXJRm0g*QA12WW@rjutGNzMvWdU7 zwu}|DvQX2gxS8va8pg@xU%llJIY?)s@vtz{cP&pfN`-5!UJ)BfGaaH83Q>zA2{je{_4zv~-MVF@310(`_t+#!J!r;qXHxi*}6IO?s1t17)5@H8$X%EnY~^7sQA@X@<3}J zC%`TqJCE#o>)IQub5yyUFe{D`82?La-Th|iL{*SmhFWEzP;9|cv%x^QaptL_zEOH5 zt#QHBB7%L`pzKtmzI^hVz*p`PjXek7V(6`SH3J-Hhn5b-o8>d1c^O!i3k2;?`L;MY zGba!AOsRP)kCeb&uXy`TxOb__r}qa75G3+*hkB+$nB{75D8VWvo#P1)o{PB} z`34j#kZD;Z$DEb@QjZw?@f6<_Fv$CjV4_?dXC@>x2r2dZY~3O#YdlVu{6o?FwyWc6 z3PX8uBAl02Wv!t^5ly$i$~VM& z|Ef0rIblf8%*^ul+W0!Z_kUj-f0wTRH`9oJEr@?9+<(qS{W%}`-xHd{s{P@(tjNs= zR1T6Jk%e_=@89eSJ-xLbZ4a*mfPdY5`O-w>t{4-o77o6M{G)r1$lUd+Ah;8&qxGVc z%=~=vF{TF^t0%zQi{10i#%xu_K~~jYb0|V9Ng^`fW;8DXGDFoc>IRs{y8Pte^|ImM zi6x7;VO@i2M$DH3wO&JZ9X_|t93VroE{#%1HSPmsOJq*Z{>XsbthWB~7rx9`2>E-D zglg_I??O!64{YJB4eS}jECwC`nIFXG8cPD2)iX+t$<@l$&7ZWZG7h`Lt~+(+_z(~) zxT36d5>vkIq9RbSsV9lWu6;V#xGTL%Zu}g2RutMJ9*Na90n%u2G8wvsTuL8)ojWp^aKX{oD}<{X8+mJ?Y^8FK~09$m3csp_mL zg8MM$aCGJL#qr?4&(6F_Yc#c(6FkZ9b+!+^d?5akf&Z#=XZ7XjVF!tawmsRlg~6Dn zMF8QmnNQP!kIprA5Q->^+YN`^oG|z>_yJ_(0t7e$?<~X{=}embteL(fyW#ej?aw8a zq~i5IG)I8_#bS&!YC2I}I^|&XB<#7T@YoIfonw~gmMi)`l4I;L>P?G19#GYNrtbQx zQ&`6MQx0D6Ju7rD!V4s*0poRHQ^mHbS$gM0j&V3spQY9JB~=IV!SXXxuj9Veh9bdZ z?6d_f`RTnMHfg)iT@pQPVF4Sh2!h$*w0;H3u!7Q?mZo~pziUW)*#;l#0e2_GR_!UhzD%c4j5@UQZV-4 z$P^?~pexVc$A++?8q&2tHZS%u+iyqK$c34mnxgqomg#TEO^azoiPrS7k*W442{3U| ztbyf;y0EPDtx?RsB^ZG}jbB%m<~z94f@Rr4UlPX`=bMr)hEUyd9J?<>9Z++m%c?avy^~N`dthrT!?LaGewPo|zafoT6Z%>0`N3#(_^061>d;!@4KWpeR1} zfylU5G2*q3dXW0V*5%wvn0mJpu@h?uG~9fKMSO$H4*J$qW*qYI(})?FGnj;Zx0(aB zTkutZdP*4bGZRcvF{U}%aJyb}+^rTld4TD=z0Ddre%H6^4L#pAS4K#{1@0ql!!~7_ zlyZpmHJNkyL@blSXXLCQ2ps~n-da*!hc{n)@&0TgAfZ|F0e{}}3=0o+f}XUclKpui z=bl$Xg%UbbFBF499-CcUqR0M|QelIJ{6hduaGd4`H~KI-G|Kl4d_bpI5ekY8NuX;z z12^^%dEqyBh^K|n>ppO_n;%Xh8Vl{ydf}IQn+$aan*QNyI>FYY$3rcN^>X5Q&-aD= zF<&|TNN$;yD>_9`>*P!QjZT0$G$P7X3%zF6XK%#EH**fa;!z0vvVG5|!ro4W>5E#$ z<{0OK@z2V+EEfGtaCH4)2EfO+0MwvzlxThJ4g2;>h;?uxR<&kyHK~&I*E2tL?dBn-kw54AMr#L6}nhnoBus z2x&8`qOGQOkZiL#t9yL}{UEo!6uqZ}BA>2Oq=!r8fixK6zyeyP9g?x{xJ z59oB6WiO*XhX?9rxOgTzt2t(gmUt?g$RchQgYQH52|><=a@HMM zP@VwsW7m?|+`BgmkzYJZ=6)*(jIxR0X; z&Dk5AWJ3sQw9f0e^AdN9oE1$Etc)>fk@}wK`6%O5n%P=w_8$y3n~+-hgbwQaSTZR#HrGqyhj>dP-d@=bqe}|CQO3S3 zorCf9b?>XBA)Jl-`dKkwM!ibdx&Ux9IYen1hpo1BNr)o}>OuH11AdCCA_YrE-3T|;&6F_YPaU*80d2J)o^?s=r@aMw%&3{$i|E%QntSk(F zFYm9M-T#B~{+rSJKVS*}kB7d0xz9f<{coAmEA#i?N|!7@OO$j1kQeQPiyD8;dq5E> z#^K;dx1ngM=MS!UkBf27aiaj%|=AS zu>f|KNEAOmyu4 zARU6FuD!$Wbr#TuvZ;ZC2}l*kz)S~VWMur^nAw;B46JOw*I)O)u7kks|ERIBumD); z*a0l;YycLJJ!W=x04odgZyPMEOaNvUMgSWVs1N8GEFhaKEUch@ApRUQ$HcZxOkdS} z0EXYQQ-1}|fAYJ350-zn@i&{&x|SfB;y<20pS}L8dVr0N4i@-&z1IyIhSx1Ikdo0< zpU=wJ!Vm!3ly@++RC-+z(6tdYG&MGH06iIYkWmX=V|xJ8->m;lNJ2wT4|>)#ER1ZR z2f{)Jpr>P|`{Ta^`f?jG02qH$%zwT2|4c(O{+>qznf>q6f#&3}*#;f@+h5HY{DULk z=kCj=_+j8_{HV7B`iCIrW*p5-EV9``nx1xdzab70t-(mlt}5CcBy=9=rn4x%NDkV> zN`+10#c0Hv;|N~aC2x!4V&v&ImJZz59M2St1Vq#he%$Hgq@A|P+tBSpb$^qj&Zxaup2tqaMa}4q`vr(Y}TqQ+oQ3oUh8!-@_pa#@C^Cl!nBcVoAJfz zv8DLCq^h}|Kk0zs5tP`nN3_{2lY6zh`=T;K?@RjKLzEZZN@Hy8ep9unWaa2Q9S@=U zwy{C)5^?PiWCdst`b9M|BT-~?C)zT9mEL?aeI9@`gvrjpVW4XjC1R9OX_W}c@Puh3 z)#|4@d2&iC)hgimk4AO1=bX#^R5F#by7qzT3GpU#&$$j7bo?aw8ME2^Uj04%6Airo zGbWUPH=J))s}R0Gs|e8FE5e=e@M&>oL z{zyuH)D_Uz?Z479fHKZ+)htN%LeI=X2MWl)D`b~HnL4OXC*5%OpW`f6&~UVuKW$=J zms4}7HFeuIorDnem&eXY8|=+T7t^RUy{SNk3CW720F6a5X$10Ua(yS_qZ#lJ5pwfr zE_0w6OgmA3OGxsJ5FW+ zv5VkOt!@@@UXS*|0iU(tE>%m1JIORs3;mwGRP-+&zGlU3i26qEKNzeK{Ce8Bxjh`? zf%7D03Jv5IB>rZYGb)<%uA(bvGj^&9VGld$yyy^5$ZDlWYIe(-mYiBIu6rpydZOYy zxHztW($R64B}^%=w7AgPWND=e@~QQf-gIbk$hB;Q?eU5bzk-NypSQTrC-^h>!Z-gh zEv8_o&j>0`WOi9@RgjKZ9~As>WN8b8_Dpyv1=LZ30g7)7Z$xTvsH0lg&EME^unHT* z#p5e|a_SpM5Bp_t)l&eYR9-4LL0L!?TCi%ew^=k$5Gc5{&?l@0k7=0Gt%ea*&?iTl z$7$lq^@26y(N;;l(`r7v6C3qSTq4JOM4YThDKjB+fvzTxl1M&{x~CT0`MRdO{1FC% zA*dimefP732f0`rsWEOrAyGf&HL~uxKA=;WgtQqCH!A*GD6+kx3plR=F||V#H}gok zzzECLK_?VR%g7`D4qaaHP(mS%2}2!UtO&2N%z|~+Fh!T9NM8=Xcgli1(w3~L*O<^? zv4q;#CF_Jo_`wuhax4N~P|aoH3-ey=WVk(U+TkuVm&J!>c7Da95*M{k#J*4uZT@TtP_vOx$35nW3pxzses><|#~Frcg_^UoaK7eO z&r;?QGJdYv_1(#R(V0GE4MX=RO6qa@Jw91h+0A=aFnQv?~*Vz zhL)l&Rcn@d{6M^3FX`*#v>9_f>f1b-NUAN+v_Nzc&G??gb?mMqsb*O76)|@$&1Ey! z_)d~?JQnuyd5C0mgSEOMq`Ic^gpz!v@8mJl_1m4a!B*!_qhc*jvr)8SDEWY9D#{_g zo*Y1IaU6x+d7YfF@`Lp9QSI!NI`z=qEYI_9m{`@hG`fuhIM%y5SB+vV=m{s zH`GF;iQh_$wnimsz24^Xi?n}PO4TZyrA%C6)HEJ(SW`IXw1<*i8pl9#!(@NA{Hd5I zV3KtMKM~il9+qW44URRAA$NiLYo2g|ElNqizLd$n?Kpvrr%VKIC)lVwFXZ;V&T%Z7 z7hTKw_yC6sV;?o)y;xm{}RPR;< zzkDiA?Tm)bOTNINx!p*^o$A>-dQ+MR^VTKBhuAishzv4sLokWaB4b?3El(Fq$0DIjMpA+j5eOs z4q~~BzX-5^3`8+>&>%vAXZv;e1*#|7CLL)(ec$@LDcV$QcW)1`A&40_+|M(XcE#mc6?P}lywxYQwRr$P;3ki5BdKnI-?0&gYN$zu;gpKp%s;zqApke*# zE}PfK0$i_4eGS|v_G+YByP))FXu`Lv0}itcXb5t=h@ zby7_B!aYYg0znOjb$73%CBD1JVxts4XO9K=r~dbJ9!hC)qa-v-vUIXuol(1CLl>Uf zVd<`ywyEb`aV6qpRPjN`EmyzJ62gHqR%zaD zUgl$~D_YvK<2IGH%I1yl0&sUMp-XFrnK5fE7>9=vnP^()v!)MCCU#e_n{-WVj2Y*0 z(bx5{Z>_%V^P9tct9y^(r~pPMO=Kup{57GSn#O!+;+k4z>uFFYD*GdGdh-OiFJ@Ul zeYHI>C<5MP3H&Zzv`Fr|7J5A7rUela{L7DP93qv zHL^YsRxce5pG+lNdXPM1I@-=v>Sn#oJ??>lOxU=DXMes@yG0xON&dydxf+jyDTcAL z;l|(pL(9c==^5iVYn4FB6VqrG3tdJYgCS8)92IkY5<`h3Jh(+$%CXBXm4pn>_VvSZ z1pSVT2!Z>Ei{4&1T6sgqp^-|zHDmMEGjm4p_r5J9nlIF+ zc5N3`D$sl-lW1MwV-fuxGLjkiCvLu*LTd3sRI~^aJG8cmu8X0rFU?rJws9PI_}t4Y ztE*ngQK1BZEp4ecPc`~kzWXYW=PtY-*lH6@lChghaNyp>G~#3mCB~{nE18L5Tj=<~ zuW0fpMB5IcIj@%6+)7)tqh))}EYy%aI2p#W%G4@dB26dgNH}fvT_4maZ6?(vM{ivJ zdbGIF5m}x|F0y2QScJoI`U%HsGY&?3lKCDqUb79<2LgS}p^++?(OKHB< z4C_G2R6i{IaT&ZFFDq!W<3|4)?z|4jn%gScN>ZEnfh_1>pG_j5M z0`p#qKUF$CN&%Dwv>A3w`t!laZReOnOgQm3H#?ofvj_`<%Ba}m8 z&q4D7B1YG4rz*=%tDCn&YfqIo`0Uqh21{1AkdgF|(mq|4_llof?fR3SV7(K;BU6<;M&qI2VmRsSB1$iz{@Go3%q;CZqyusNS*Nh;m6VW|ng`?%( ziQ^x#+ycithf0nqMpD^pd)+TTj^^&lY8Y`^cr3E*Ei}Jy4|&`zhnLG1u=a~mT;O;L zqybQVV&o|JaKO>UhbK2QsNr6)809fBnBtGSJx(H;KaG5oE@G8)6+c@9j2cxuY;`kH z`|e5_@5aYUNa%dhD!j<(wW?d8pHF2-R;(;_9!g=0_G()ix}sVP@c*;`b$@Zp<8YbP z3_0H7<#nTEpJA9mqn+J>)r|JQS&0_&fzla`Qw#E;BVPuF^KHJ+xsfDsY-eq)aus}% zBEuTv z`)I+ki)Ab$ZyT%oOyfH%RRKAxRdAmwUU(XCV}K0^5*siu9UbMwS#o>!i-=RI z$gx|&Qc2Gjt{=iugUDkQQ`7L(9nx&-nlcvDJ-Z@h+$Y~PoQzisU5@$Kg#al} zEDlpM#MZC&QaL`M4o5kv^#Q0D7D`A=Yi|(RrF~^j5ldK8hJDo+2IVns*?(Ana!V3h ztkt9?0`1BA8ELr7|1%)IzjqaF2s;)DQAu# zpsD)+R{>pcP#_K)EMH;2nBp!n?7KZclTu%GcqnZ%ee#>UH<*4C1UWRDEuCC0=MVlTV`jH`!mC^FAIoIe!%k317GFw$XyX zvl|`S9{2@8IKDj%zK&Lt5`OZqw()>SknU4)8<6n?pQraY#OQ!5ZHlkt>FosaR9ImZr=s)~vFKRBciG ztolsCykjnlmR1Mq|AXI-cKxaLmo0rU<6Pur z8)w5VVDM?|Qj=T+o>=QQSH%fcTaT+*YTY%Kvv-+@-?dC~C6Ok6X)wW-;q)M|Id2gQ z`%z{|@lpBr@GruLq4iFWiD$+!X!B#+@K6}1^FtY-ERa_jDj;6s4RwN95M`Hv|OdMl@HXvSt(-D;1WyMZ2dpK>vwsEyylO(S}R7L(7Zs0=5M02$3cr zg^FRO$HF&=F%!k?0yfhc61?{*u`@vy)(c_btqIv@9Ppy zvoNF`*q_EB5Nx7-WG29>7Bj(@b25lx;W=Z719Kq7!JE({souKeKrBy*fDToWn0$7h z(BVEZHSrcQf$XaUH?gV<{rqVv{?a`QycM2p(E!W}N}&ioGv$o%Zd+>FqDQ8~l89Ou zPGAI+i7gs=_Y2dIAg2=4IOLg&IKMZBE)=PrAB@D9;~YtcTk^mj);&l_#XYU zgl{Cb-(m9j;j4pBq4q{6^t3xC$1&?6$OY~ow31YTvMLZZYBLcOsl2_d61v#<-rqWc z(9?5lQ2mC{{TuP_QRCv<-tYG;5)EZKqyjonb2{G=Q41x_QFXjCfoswHW=Q9y_AMq7 zzLmBk--NY=qysmaXVgWI4gboZg?9~|C}K%!O@Z=77dB6p{Rv$<&xXd25nQ@|L3GW+ zr?Nu&Gh4ufYfK01IhV+@JnYWISsr;)R7ZrY3);BsGnn7SJMBAEKREJ{H+~`H7MW$y z12ujf-40D*9qQz1zUZ{Mc0WGX!T-X$3qW6iEt6akU8DS(am4}4`ne9Wnwa1`TL_(( zmMJWi{F_S7mlW)~_|o9c1?Dm@1dUcGtH931%;F9lpN4T{oG;E;r)z80uyt+}z(yF9 z=a@)v!d>Xkksi=yH+NW&C8oxK(^FF>3Y+=4GF+ZBlg5y)R)twI2#Z`y9Zqjuyasj< z%AH)^XvC~V0+HX?(XV_ofDGq*4<;Bhj7#!z+Ua|)yXl_~=5V)Hwg}||aN59W*AG!m zB>d`@dGg~uy&+29SYMAxr9g{M@3vLWx@Q4=qi!Pk@pmZQFUjE(@&c2YYIMwSq8@QdvCZW!Ti>wE|a0)d73;; zphOpS4UvU^l-Ou9M)IU_7;z zJwT~-_kpx!8&An_FiB1Bsq?rLUlK)nvhAx^lVBdgbwou z>fFz?{yN_h4cu>>xxyx>)X*mT*>Y~bq{{Mqq~VDr>EF=sXA;V+a)yQLXgtqJN=8`i zx>8^U0Wh(BMo4&RBSVECFNfs=&J|$WKhmcHa<_fT#VEx>-IO()Covi-r}VxSvAoAx z56z*Fxv~ky8PY@vSSVyt0j30x#KdjIS`Br8e>#q^FF>{w%W;$+GCz~=v`VS?C|wd7 zZ0TwfCm0@WhKk$%F&)DueQAGXopn%cUPbic;N@@E)Oj$3&s#4E*%PiQ^Pwx3=S~ z0PApB!nONwN$5eq$NVeL1cASzpfIYCAUC55f4Zlk=lG+#SILF5h|kJ6ItP6^ zHTkmsFl+cTM7&b1KCszBFRns1iZ6dR-2GJDxHTt4axN@=97KS>j^2Z=?rD@(tBL0cj=h{zY}+J=Z5Mx1(E)?rK?Ty+-8RdTzN= zL;3c+LXL^J#>M-AigoxboT18p&n~^1FxjIM<+Gu zN{r^UK8YbZSd6h^B-g&Z6?PiOGCE=b;{L)f$+Y>kt*RVY?TSC$0(Nl5t|1>{06266 z3yF*|{t`zfx*LPIHa_fr=QBKv(-C8pkcMwF?`j=sYzcUW{qB=1G)KMYco18W*yf1S zolgPH8)H3;!A2eKwvixX1Iy4aqEg*onYL<>QP|q<9}Q&Riq`ivSY}N-?#H5=#UxTF z#o3k70!eo-@A#m}A|Z;mHLX}V`YYD}JQz-eDs2WL?!#Q2#6~raeFNb&IAO&CMh8x9 z@a&#+gQI5<_zAryPT;CzBsv%Vm_cp6X+75;+N>=P@OPQjgN8Jlx3i>Uf{t)lDvFD; zrcI7539wC=EBsNib?2tuh0~us8qeDW-$O`l{}NzH;DB*cYq#e^?myB=t6~4ZX|khf z4~Rq{>S(>aVe31(ke2TTlV`#SilLY}fXkl)1jcoAL@c5Hil*b?A!sG2dyCq>9~(OZ zuz|mKZG>Yxg>zZ~vQ06QzESpph6WGrG)8iFu^DWdG;~G{fmWU`5N#`OGDh8uKtjFn zYi41+#qxh@0VAXq)>`UaiHz9CCN5QD+&cwF8uJs(phDkVmPbBlOQeN4Z7<{4gn;WJ zg_J{vb6UkSB=WFVV@NMEI~NnafXUZ@e1Z|yKuKXDjz$r0rB?5hS zCzsrSeJepJCLvgQRQTz(aH7N^@S!c4>X9o}gJhY)G73>5C8seL0jBtY`c#!`4dOU+ zv3>v0S|{G(r>GkF^2ehDCkf}ai%JtDNPHwNVYj^~8zl19KqFh+s54lNlVq(#D=5DA zX~Y4rsR&rlfTt01jgTON54IsU6T>5}ensT<-1Y3L2*sSPbl6sRyYKqZqIXEhNFyYq z<-}?C-#^RK5aF32b4k1Go(a!A<0N=A0lYj{{KH93(X2XXWOBAytsPhjYg2P`R`Dxc(*nAog_VE}Wqe9)LTh8Ck#nu3OH~*c8NUxdO=fK<}Y3q<{s=*clkwz1|!7pZ5mZDeq`wV_|6dN&&xe zVL)YoIz8y$>nj2Nz4-St_;(!q`)ly;RPgUi`0q^k?@ajrzf7q4pG9{d(cW+65C~5x zSc`}WO6l7C1IztIz4p7`f5L75Q1AU!dG{x5{6lxg&I)?h&EIu*^sj=t|Gn-G1V{gs z>W&$doM}KNKw>7)0%ZB0HFtlAKwrTj=#5Pv;o*M(pRZ{2KPadE>v#72RVw9|v}pFR z9>eeC3B4s%1D;ll^oYg;r{f#1_9pKqSD%S|ebL39qx#+2i0A5f;|}ELFG*|J4);#^ z3e{dbkC%H$8R;KZj|rad@0WN+Yagy|nY4ClcQUjenyfDdHzqX$Z#K$XgHBhUuiWb0 z=hAq*U#?kNz1-ABoe8*?cpfdaAFr?PpPmC&BX(H4oo`MPX6dyFGFp~iP9rk7t)Cw+ z1_xWWXYVeZyq7#5U3j(^AfNW!?k=tzS9mfPBQkngo^P+8dY5-*F}+`&8BU1VZ0Mg{ zhpd6h8Ktw639dJ5b1xoEFQ>gvqrHuHOkVa}c+d~k(=V>g-#zD!CXc)`MT2vy_EOzN z$Hc^QBpXEirbLBtNXVq*-ulHjL@|jIJIX-|RpXKTFG9XCIMbkMH`&7K5csk^%7?yFmJ$)^4YMbi%mn@k`} z>1jt5vZkDR@;l!*Ei+|2dJCQ-=NObTQ5Ca;DiT+ zvWmUQZ%0Z2q@TYVKngJCBk@KbF9H}^BL!*{`m&*}pzgJicnXK@sML}Imn)fZqdm#{ z9nEW9lbRc)=f`*3U@fI?b%)O>Ug%GF(17VQnY(R}C$hJ?DZJd~sI-|50k2ZIjM!8Q zjNGUD3{so`JcW3dWVhO2SbP=AvvrWHH+j^p1(F7f6Je97X`y{8OK@&WF0|-?6PT)Q zNlQrf>zbydMWlctTgaF2vGe=~T{pqAHa0Neeo??}tZ!2&r1V8D*>5*NV)L!@FzL`y6 zaz^w(6ZP4vL&}YwNicL7{+`ulDzp^aC?G!@|M>S)13uX{ap>z=?u=1@hkmHH$F_T@ z_gsp}Eo~?*>kImoLa3p3C!}EPIh5eSHuHO~vzh7ROcmcI8d5=VMJ^l6U2`YOlJhaMtD;S#kil`$sw--F*HpKrKvMo@5c_gbM z>(qRCOu24JW`hNG=1;-`ic-4!9qArJcNX&jyPRm%Zhek65pV&jrUth;>nkhL#d8pV zGrb82fcDb7RL%SmSv(BD+xq8={8b-ZfUKd4CZ+YH@Vwux?m8nTaC@idipaSa5}Y!y8y!}d};-m|#d<|e`ZQah%<;t+83Vf%+WI?WNiRJB*1=<46HP&i_3XfC3C zP^Simu&e|p0QGPiDJj2%KX?M_aNf5Wr%K!b_PjfLR&`BEa%6g?rD_!XW3Cw}sS# ztRWvQK-;kd22jHomL>g=6D}6{AHD?!kh_E$u+~29ovif=1Gs$1xt(w+2rW(PW8{o) zl*%>6Cu`Nao!}Y61UB||VFF|FDf?OcCTtt+w@>>ZkKj_cV{$bxSAHwO_b zhGM4$86|h+FB)mqzcY&8W}idB1y2azN8`jDZ-XYa>MGXuR>Vf*a#oSD8k$7}&4%UO zBrWJ_qjy(2CC12>;lHIt%5kLuw+kDLM63=bE3{zD7TLs4tn_oMK1|YCX@g!}K2tU<8{l zH4mr-FO}XPY$nUJyaFwird|J%O3{dr2=?AvkrB#f;~xOEc1h9c-+alD{_Y<5(h$jK z%TGZG3O2L6bm)-CXJgi(S74lgm>9cR3u5btZX%U;*!LI8-u&LioX}ROlpL$DZ*hyn|B|TEL;Z(uuGIfti`EIU zbWC0kd%l!DfAo7j4GZ!l3j4b}{BK%`IvN*%htrhQ?wf@DvCHWqWbq*C36V92bo>=G zd1`F)1UX-ojLk0*&mttp0Gc(Ydhj7?@ldQz`n}Paqj3hAfj?MEy7HOLBs|Lpzcr=} zN4^D`pA_FG)zkU?={dSF2F-FtyNI4V#oS5xwy@)g!E!*c&HQ~YY8bw`DJa13j?Ry7 zt`6;c;`{dznh2UnIIg>o#%(_g60^R&zk_7WZ5)2zupGB3sTypS63#93|EPB_DEjuF zwSAv}`xUa8geD8z_muBT4GFjhV<}1W*8`43=T0~xnT1IcVn#}4KFo!{L{V0(C_xhNurVL8cokuvU=q3I zP-Z3~xn)s8(C_&lC=!`9dq3rqXo8tKqVI|2986@t4c1Whn@zEZWNv<^FOOwEQhjUH ztl)pk)^nj#T29SlnY7^oeUSdMzJA!4PUCT?;oJ#kzZt#HUu25vo_hFzba#Y+;V884 z9`4DvOYf6JeK-4wWZ#Y%tV4X;xNs8A+~m|*fsKAfGImLkaGt$#_mz`q_ z+&L2j^iKcg1aiX!E_8gbpWIo0{=@v8agw3S2z+;}vV)}i@7^x|54MAamFvHt-2XF% z`+q{V|6Q}>|AU%%|0{=|4OW9 zVgJU){9l>%aegp9iZo zq|?b|)9GK6=m;D3HyZ3l5=?nLu2!8BDC9Y!^SGQ5PwZUT?bk2Mr5DJqp2i#GJMGsW zMm=)e7n0r)gpvx$EIHpkOz^;v@A1D5d>tvmM2eKS9wLwt!xIYH4_0w^4!RKyyy?Pj zdV7L>OOhf$K_9M~b;TK4R~8 zGmP+ik%XZqWhs&l0O>B3K!LCVFNlPjuDj7OTY*o^i(aG|050s6y91B=#RbL=8V<}} z0nqd|Knm^(DSQ^H#k0TOxJZdUJs!QW;WD7ve~))m>xJ_w575a|txq6w3jeQ}v(PYN z*OT~|Vsl-pX|4tnYL?V3pxjzlq%axgVPbo}>|W0zrc6gl6sho!M_Yu51r=DqIP>}7 z1|eQJQe*I(Pm?mPbbB;c$p<&R8t&)`+~Z!bNUxJ@Bc=8zrT=2eBqp00)#C1vh;eB; zBaoweK1J>g>^0vEI=oIy_ttVYQS`gwWdtC}DJNcp^yO6{hsWlB0@O!XdYuJYb6q5B{z1;s5N$03j95zCy58{tw6 zc;UuIgIow_!d8d0e1NkSOvfBNoIBqGgvLk|O<;VX1QPJ}-AL5Jlks%G1#D=i%>mpr zZAIKo>&Bj^dLz@Ek_4Zla_Sc$QbZDT!gHDx` z(})CH@>G2olCd$Vh{lvWETKkuJkiv6JPyv=+ z@8)xN@?w1T6=r6ody>KUG0zo2Vhh!xF4hObzBN6AzZ9}yL%7b(FTwj2aVD~yM(&qxr?4BQ9nU?*YutkY*@BZrIPg2WSh6va~*WIEUo+*P)iRR1@y!`o!4 zM`D(-GF?L1ws~swlewC8WxfLDL!Z~md2sSgwEo#AI^bXucrpXe$=o_0%AUG;I=(?2 zkEJ|B&!vT9TE08R<){0FE?%tX_SrNij|O(nchM4<{>HJC=WCGJeHm{Nwfs9hmW#%~!@}xEuBe z+z@Bx6#T!(e`#nXlr!qQY}n%SznVtVEsdCN0GPX0!imag=AYr<7;Pl+23o~4U9pR- z6IWQ%n@Lg8*q`dl9kW>+u+UHqy%#nbDclQrYx_jTXzOc(KXd3&a2XS~RM^ z$_8UeEBl3#CqtQzQ^Tm|vL!sg#0<_cS*%964h)wNBPQUWNT+BQ=O z+t0x()7YIs$yIH4Zt!h z`;PnsWWl53Qn5@}x2?l&d^F>Q!+K~0{SM2IqzIQYC?zqivA8MgL1DBUJh$D+97M^Y zhIdymMSMq}x?DM2IR%2AqU=h7XO*c(>72EV?JzoqYV^N^4V0!;sQrNg%Cc#Be=cil z#>&7G;`Gl|g4*`RxNWjEaj;~MwEVeu5=0jJ<&N6!pqIZk-D{wc(|09?e3&f8AkBw}@LWAO)AH-KBd

{LyOwpO~G3X&S0n#)kyJK$`}|S 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

bFX+ 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 literal 0 HcmV?d00001