Repository: VOID001/neu-os Branch: master Commit: ba080fb5231c Files: 69 Total size: 129.2 KB Directory structure: gitextract_l9h0t38n/ ├── .gitignore ├── Makefile ├── Makefile.header ├── bochsrc ├── boot/ │ ├── Makefile │ ├── binary.s │ ├── bochsrc │ ├── bootsect.s │ ├── head.s │ ├── ldS.ld │ └── setup.s ├── docs/ │ └── EXPERIMENT.md ├── include/ │ ├── asm/ │ │ ├── io.h │ │ ├── segment.h │ │ └── system.h │ ├── errno.h │ ├── linux/ │ │ ├── fs.h │ │ ├── head.h │ │ ├── kernel.h │ │ ├── lib.h │ │ ├── mm.h │ │ ├── sched.h │ │ ├── sys.h │ │ └── tty.h │ ├── serial_debug.h │ ├── signal.h │ ├── stdarg.h │ ├── stddef.h │ ├── sys/ │ │ ├── types.h │ │ └── wait.h │ └── unistd.h ├── init/ │ ├── Makefile │ └── main.c ├── kernel/ │ ├── Makefile │ ├── asm.s │ ├── blk_drv/ │ │ ├── Makefile │ │ ├── blk.h │ │ └── request_scan_algo.c │ ├── chr_drv/ │ │ ├── Makefile │ │ ├── do_keyboard.c │ │ ├── keyboard.s │ │ ├── tty_io.c │ │ ├── tty_io.c.orig │ │ ├── tty_io.c.rej │ │ ├── tty_queue.c │ │ ├── tty_read.c │ │ └── vga_console.c │ ├── exit.c │ ├── fork.c │ ├── libc_restore.s │ ├── panic.c │ ├── printk.c │ ├── sched.c │ ├── serial_debug.c │ ├── signal.c │ ├── signal_demo.c │ ├── sys.c │ ├── system_call.s │ ├── traps.c │ ├── vsprintf.c │ └── vsprintf.c.old ├── ldS.ld ├── lib/ │ ├── Makefile │ ├── getline.c │ └── printf.c └── mm/ ├── Makefile ├── memory.c ├── mm_test.c └── page.s ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.o *.a *.sym boot/bootsect env.sh boot/bootimg boot/setup boot/binary .workdoc/ *.ini tags bootimg system .ycm_extra_conf.py ================================================ FILE: Makefile ================================================ include Makefile.header #AS=i686-elf-as #LD=i686-elf-ld #OBJCOPY=i686-elf-objcopy #QEMU=qemu-system-i386 #BOCHS=bochs #STRIP=i686-elf-strip .PHONY=clean run all boot/head.o boot/bootsect boot/setup kernel/kernel.o all: bootimg OBJS = boot/head.o init/main.o kernel/kernel.o mm/mm.o lib/lib.o DRIVERS = kernel/chr_drv/chr_drv.a system: $(OBJS) $(DRIVERS) @$(LD) -T ldS.ld $(OBJS) $(DRIVERS) -o system.sym @$(STRIP) system.sym -o system.o @$(OBJCOPY) -O binary -R .note -R .comment system.o system kernel/chr_drv/chr_drv.a: @make -C kernel/chr_drv/ kernel/kernel.o: @make -C kernel boot/head.o: @make head.o -C boot boot/bootsect: @make bootsect -C boot boot/setup: @make setup -C boot init/main.o: @make main.o -C init lib/lib.o: @make lib.o -C lib mm/mm.o: @make -C mm # Squash the bootimg together bootimg: boot/setup boot/bootsect system @echo -e "\e[1;33mStart building NEU-OS image..." @dd if=boot/bootsect of=bootimg bs=512 count=1 2>/dev/null @dd if=boot/setup of=bootimg bs=512 count=4 seek=1 2>/dev/null @dd if=system of=bootimg bs=512 seek=5 2>/dev/null @echo -e "\e[1;0;32mBuild bootimg done" run: bootimg $(QEMU) -boot a -fda bootimg -serial stdio run_bochs: bootimg $(BOCHS) -q run_debug: $(QEMU) -boot a -fda bootimg -S -s disassemble: system.sym objdump -S system.sym | less clean: @rm -f bootsect *.o setup *.sym bootimg a.out binary head system @make clean -C boot @make clean -C kernel @make clean -C mm @make clean -C init @make clean -C lib ================================================ FILE: Makefile.header ================================================ AS=as --32 CC=gcc LD=ld -m elf_i386 OBJCOPY=objcopy QEMU=qemu-system-i386 BOCHS=bochs STRIP=strip _ALLWARN=-Wall -Wextra -Waddress -Wconversion CFLAGS=-g -m32 -fno-builtin -fno-stack-protector -fomit-frame-pointer -fstrength-reduce $(_ALLWARN) ================================================ FILE: bochsrc ================================================ floppya: 1_44="./bootimg", status=inserted # Use Floppy Disk A for booting boot: floppy display_library: x, options="gui_debug" # Uncomment this line to enable GUI debugger #gdbstub: enabled=1, port=1234 ================================================ FILE: boot/Makefile ================================================ include ../Makefile.header LDFLAGS += -Ttext 0 .PHONY=clean run all all: bootsect setup head.o # Bootsector is RAW Binary # So is setup program bootsect: bootsect.s @$(AS) -n -g -o bootsect.o bootsect.s @$(LD) -T ldS.ld -o bootsect bootsect.o @cp -f bootsect bootsect.sym @$(OBJCOPY) -j .text -S -O binary bootsect setup: setup.s @$(AS) -n -g -o setup.o setup.s @$(LD) -T ldS.ld -o setup setup.o @cp -f setup setup.sym @$(OBJCOPY) -j .text -S -O binary setup head.o: head.s @$(AS) -n -g -o head.o head.s #@cp -f head head.sym #@$(OBJCOPY) -R .pdr -R .comment -R.note -S -O binary head #@$(LD) $(LDFLAGS) -o head head.o binary: binary.s @$(AS) -n -g -o binary.o binary.s @$(LD) $(LDFLAGS) -o binary binary.o @cp -f binary binary.sym @$(OBJCOPY) -R .pdr -R .comment -R.note -S -O binary binary #bootimg: setup bootsect head binary # @dd if=bootsect of=bootimg bs=512 count=1 # @dd if=setup of=bootimg bs=512 count=4 seek=1 # @dd if=binary of=bootimg bs=512 seek=5 # @echo "Build bootimg done" run: bootimg $(QEMU) -boot a -fda bootimg run_bochs: bootimg $(BOCHS) -q run_debug: $(QEMU) -boot a -fda bootimg -S -s clean: @rm -f bootsect *.o setup *.sym bootimg a.out binary head ================================================ FILE: boot/binary.s ================================================ # This code is used to test whether you make things right # switching to protected mode, do not modify it, it will work # Just as is .code32 .global _start, begtext, begbss, begdata _start: # Just have a try to move one char in the memory #mov 'O', %al #movb %al, 0xb8000 #movb $0b00000111, %al #movb %al, 0xb8001 #mov $0x0c63, %ax # Don't mess up the order #mov %ax, 0xb8000 put_red_string: xor %ecx, %ecx # Clear the counter loop: mov $red_str, %ebx add %cx, %bx movb (%ebx), %al movb $0x0c, %ah mov $0xb8A00, %ebx shl %ecx add %ecx, %ebx shr %ecx movw %ax, (%ebx) inc %ecx cmp $62, %ecx jne loop halt: jmp halt red_str: .ascii "You've entered protected mode! " # len = 0x1e ================================================ FILE: boot/bochsrc ================================================ floppya: 1_44="./bootimg", status=inserted # Use Floppy Disk A for booting boot: floppy display_library: x, options="gui_debug" # Uncomment this line to enable GUI debugger ================================================ FILE: boot/bootsect.s ================================================ ################################################################# # # # Lesson 3: 将操作系统装载到正确的地址,设置GDT后 # # 切换到保护模式 # # # # # ################################################################# # 以下代码将操作系统完整的装载到内存中 Layout 为: # 0x9000:0000 bootsect # 0x9000:0200 setup # 0x1000:0000 system # 这段代码没有需要学生进行修改的地方 # 有兴趣的同学可以仔细读一下 read_it 子程序 .code16 # 指定语法为 十六位汇编 .equ SYSSIZE, 0x3000 # System Size in Clicks(1 click = 16bits) # rewrite with AT&T syntax by falcon at 081012 # Modified by VOID001 at 2017 03 05 # loads pretty fast by getting whole sectors at a time whenever possible. #.global _start, begtext, begdata, begbss, endtext, enddata, endbss .global _start # 程序开始处 .global begtext, begdata, begbss, endtext, enddata, endbss .text begtext: .data begdata: .bss begbss: .text .equ SETUPLEN, 0x04 # Setup 程序占用的扇区数 .equ BOOTSEG, 0x07c0 # 当此扇区被BIOS识别为启动扇区装载到内存中时,装载到0x07c0段处 .equ INITSEG, 0x9000 # 我们的bootsector代码会被移动到这里 .equ SETUPSEG, 0x9020 # setup.s的代码会被移动到这里(Bootsector 之后的一个扇区) .equ SYSSEG, 0x1000 # system 程序的装载地址 # 此时我们处于实模式(REAL MODE)中,对内存的寻址方式为 # (段地址 << 4 + 偏移量) 可以寻址的线性空间为 20 位 .equ ROOT_DEV, 0x301 # 指定/dev/fda为系统镜像所在的设备 .equ ENDSEG, SYSSEG + SYSSIZE ljmp $BOOTSEG, $_start # 修改cs寄存器为BOOTSEG, 并跳转到_start处执行我们的代码 _start: mov $BOOTSEG, %ax # 这里我们将启动扇区从0x07c0:0000处移动到0x9000:0000 mov %ax, %ds # rep mov 的用法如下 # 源地址ds:si = 0x07c0:0000 # 目的地址es:di = 0x9000:0000 # 移动次数 %cx = 256 # 因为是movsw所以每次移动一个Word(2Byte) 256 * 2 = 512 Byte 即为启动扇区的大小 mov $INITSEG, %ax mov %ax, %es mov $256, %cx xor %si, %si xor %di, %di rep movsw # 进行移动 ljmp $INITSEG, $go # 长跳转同时切换CS:IP go: mov %cs,%ax # 对DS, ES, SS寄存器进行初始化 mov %ax, %ds mov %ax, %es # 设置好 ES 寄存器,为后续输出字符串准备 mov %ax, %ss mov $0xFF00, %sp # 设置好栈 load_setup: # 这里我们需要将软盘中的内容加载到内存中,并且跳转到相应地址执行代码 mov $0x0000, %dx # 选择磁盘号0,磁头号0进行读取 mov $0x0002, %cx # 从二号扇区,0轨道开始读(注意扇区是从1开始编号的) mov $INITSEG, %ax # ES:BX 指向装载目的地址 mov %ax, %es mov $0x0200, %bx mov $02, %ah # Service 2: Read Disk Sectors mov $4, %al # 读取的扇区数 int $0x13 # 调用BIOS中断读取 jnc demo_load_ok # 没有异常,加载成功 mov $0x0000, %dx mov $0x0000, %ax # Service 0: Reset the Disk int $0x13 jmp load_setup # 并一直重试,直到加载成功 demo_load_ok: # Here will jump to where the demo program is #mov $SETUPSEG, %ax #mov %ax, %cs # This is awful!! Do not change CS alone!! It will result in GDB cannot debug the code # And, of course the code will not work #mov %ax, %ds #ljmp $SETUPSEG, $0 # jump to where the demo program exists (Demo code, removed now) mov $0x00, %dl mov $0x0800, %ax int $0x13 mov $0x00, %ch mov %cx, %cs:sectors+0 mov $INITSEG, %ax mov %ax, %es # 下面的程序用来显示一行信息 print_msg: mov $0x03, %ah # 在输出我们的信息前读取光标的位置, 会将光标当前所在行,列存储在DX里(DH为行, DL为列) xor %bh, %bh int $0x10 mov $20, %cx # Set the output length mov $0x0007, %bx # page 0, color = 0x07 (from wikipedia https://en.wikipedia.org/wiki/INT_10H) mov $msg1, %bp mov $0x1301, %ax # write string, move cursor int $0x10 # 使用这个中断0x10的时候,输出的内容是从 ES:BP 中取得的,因而要设置好 ES 和 BP # 接下来将整个系统镜像装载到0x1000:0000开始的内存中 mov $SYSSEG, %ax mov %ax, %es # ES 作为参数 call read_it call kill_motor mov %cs:root_dev,%ax cmp $0, %ax jne root_defined # ROOT_DEV != 0, Defined root mov %cs:sectors+0, %bx # else check for the root dev mov $0x0208, %ax cmp $15, %bx je root_defined # Sector = 15, 1.2Mb Floopy Driver mov $0x021c, %ax cmp $18, %bx # Sector = 18 1.44Mb Floppy Driver je root_defined undef_root: # If no root found, loop forever jmp undef_root root_defined: mov %ax, %cs:root_dev+0 # Now everything loaded into memory, we jump to the setup-routine # which is now located at 0x9020:0000 ljmp $SETUPSEG, $0 # Here is the read_it routine and kill_motor routine # read_it 和 kill_motor 是两个子函数,用来快速读取软盘中的内容,以及关闭软驱 # 电机使用,下面是他们的代码 # 首先定义一些变量, 用于读取软盘信息使用 sread: .word 1 + SETUPLEN # 当前轨道读取的扇区数 head: .word 0 # 当前读头 track: .word 0 # 当前轨道 read_it: mov %es, %ax test $0x0fff, %ax die: jne die # If es is not at 64KB(0x1000) Boundary, then stop here xor %bx, %bx rp_read: mov %es, %ax cmp $ENDSEG, %ax jb ok1_read # If $ENDSEG > %ES, then continue reading, else just return ret ok1_read: mov %cs:sectors+0, %ax sub sread, %ax mov %ax, %cx # Calculate how much sectors left to read shl $9, %cx # cx = cx * 512B add %bx, %cx # current bytes read in now jnc ok2_read # If not bigger than 64K, continue to ok_2 je ok2_read xor %ax, %ax sub %bx, %ax shr $9, %ax ok2_read: call read_track mov %ax, %cx # cx = num of sectors read so far add sread, %ax cmp %cs:sectors+0, %ax jne ok3_read mov $1, %ax sub head, %ax jne ok4_read incw track ok4_read: mov %ax, head xor %ax, %ax ok3_read: mov %ax, sread shl $9, %cx add %cx, %bx # HERE!!! I MADE A FAULT HERE!!! jnc rp_read # If shorter than 64KB, then read the data again, else, adjust ES to next 64KB segment, then read again mov %es, %ax add $0x1000, %ax mov %ax, %es # Change the Segment to next 64KB xor %bx, %bx jmp rp_read # Comment for routine 0x13 service 2 # AH = 02 # AL = number of sectors to read (1-128 dec.) # CH = track/cylinder number (0-1023 dec., see below) # CL = sector number (1-17 dec.) # DH = head number (0-15 dec.) # DL = drive number (0=A:, 1=2nd floppy, 80h=drive 0, 81h=drive 1) # ES:BX = pointer to buffer read_track: # This routine do the actual read push %ax push %bx push %cx push %dx mov track, %dx # Set the track number $track, disk number 0 mov sread, %cx inc %cx mov %dl, %ch mov head, %dx mov %dl, %dh mov $0, %dl and $0x0100, %dx mov $2, %ah int $0x13 jc bad_rt pop %dx pop %cx pop %bx pop %ax ret ## Debug the load function #d_load_setup: # # 这里我们需要将软盘中的内容加载到内存中,并且跳转到相应地址执行代码 # mov $0x0000, %dx # 选择磁盘号0,磁头号0进行读取 # mov $0x0006, %cx # 从二号扇区,0轨道开始读(注意扇区是从1开始编号的) # mov $SYSSEG, %ax # ES:BX 指向装载目的地址 # mov %ax, %es # mov $0x0200, %bx # mov $02, %ah # Service 2: Read Disk Sectors # mov $4, %al # 读取的扇区数 # int $0x13 # 调用BIOS中断读取 #ddemo: jnc ddemo # 没有异常,加载成功 # mov $0x0000, %dx # mov $0x0000, %ax # Service 0: Reset the Disk # int $0x13 # jmp d_load_setup # 并一直重试,直到加载成功 ## END DEBUG bad_rt: mov $0, %ax mov $0, %dx int $0x13 pop %dx pop %cx pop %bx pop %ax jmp read_track kill_motor: push %dx mov $0x3f2, %dx mov $0, %al outsb pop %dx ret sectors: .word 0 msg1: .byte 13,10 .ascii "Hello VOID001!" .byte 13,10,13,10 .= 508 # 这里是对齐语法,等价于 .org 表示在该处补零,一直补到地址为 508 的地方 (即第一扇区的最后四字节) # 然后在这里的前两个字节填充根文件系统所在的设备号 ROOT_DEV,后两个字节填充 0xaa55 魔术值 # BIOS 会识别硬盘中第一扇区以 0xaa55 结尾的为启动扇区,于是 BIOS 会装载代码并且运行 root_dev: .word ROOT_DEV boot_flag: .word 0xAA55 .text endtext: .data enddata: .bss endbss: ================================================ FILE: boot/head.s ================================================ ####################################################### # Lesson 5: 设置最初的页表,进行系统初始化 # # 这里要注意,页表我们设置在了0x00000000 ,这会覆盖 # # head.s的代码 # ####################################################### .text .global idt, gdt, pg_dir, tmp_floppy_area pg_dir: # 这里说明pg_dir要写在这里,最后会覆盖掉startup code .global startup_32 startup_32: movl $0x10, %eax mov %ax, %ds mov %ax, %es mov %ax, %fs mov %ax, %gs lss stack_start, %esp # Setup the Stack to stack_start, Change SS and %esp together call setup_idt call setup_gdt mov $0x10, %eax mov %ax, %ds mov %ax, %es mov %ax, %fs mov %ax, %gs lss stack_start, %esp xorl %eax, %eax 1: incl %eax movl %eax, 0x000000 #Compare the address 0 with 1MB cmpl %eax, 0x100000 je 1b # If A20 not enabled, loop forever # 检查486协处理器是否存在 movl %cr0, %eax andl $0x80000011, %eax orl $2, %eax # Set MP Bit movl %eax, %cr0 call check_x87 jmp after_page_tables check_x87: fninit fstsw %ax cmpb $0, %al je 1f movl %cr0, %eax xorl $6, %eax movl %eax, %cr0 ret .align 2 1: .byte 0xDB, 0xE4 # 287 协处理器码 ret setup_idt: # We fill all the IDT entries with a default entry # that will display a message when any interrupt is triggered lea ignore_int, %edx movl $0x00080000, %eax movw %dx, %ax mov $0x8E00, %dx lea idt, %edi mov $256, %cx rp_sidt: # Fill the IDT Table will default stub entry mov %eax, (%edi) mov %edx, 4(%edi) addl $8, %edi dec %cx jne rp_sidt lidt idt_descr # Load IDT Register setup_gdt: lgdt gdt_descr ret # Make place for pg directory .org 0x1000 # Align to 4KB Boundary pg0: .org 0x2000 pg1: .org 0x3000 pg2: .org 0x4000 pg3: .org 0x5000 tmp_floppy_area: .fill 1024, 1, 0 # fill here with 1024 (1)Bytes of 0 after_page_tables: push $0 push $0 push $0 pushl $L6 pushl $main jmp setup_paging L6: jmp L6 int_msg: # Message to display when interrupt happen .asciz "Unknown interrupt\n" .align 2 # Align to 4Bytes # This routine is used to print a default message when any interrupt comes ignore_int: pushl %eax pushl %ecx pushl %edx push %ds push %es push %fs movl $0x10, %eax mov %ax, %ds mov %ax, %es mov %ax, %fs pushl $int_msg call printk // This is used to tell 8259A keyboard interrupts handling done // Temporary use, will remove later // mov $0x20, %al // out %al, $0x20 // // End // jmp 1f //1: jmp 1f //1: out %al, $0xA0 popl %eax pop %fs pop %es pop %ds popl %edx popl %ecx popl %eax iret .align 2 setup_paging: movl $1024 * 5, %ecx # We have 5 pages (one page pg_dir + 4 pages) xorl %eax, %eax xorl %edi, %edi cld;rep;stosl # Setup Page Directory(Only 4) movl $pg0+7, pg_dir # +7 Means set attribute present bit, r/w user movl $pg1+7, pg_dir+4 # -- -- --- movl $pg2+7, pg_dir+8 # -- -- --- movl $pg3+7, pg_dir+12 # -- -- --- # Then we fill the rest of the page table # We mapped the highest linear address to Phy Address 16MB movl $pg3 + 4092, %edi movl $0xfff007, %eax # 7 means present, r/w user attribute std 1: stosl subl $0x1000, %eax jge 1b # Set up the Page Dir register cr3 xorl %eax, %eax movl %eax, %cr3 # Then enable paging movl %cr0, %eax orl $0x80000000, %eax # Set the paging bit movl %eax, %cr0 # ENABLE PAGING NOW! ret .align 2 .word 0 idt_descr: .word 256*8 - 1 # Length in Bytes - 1 .long idt # Base .align 2 .word 0 gdt_descr: .word 256*8 - 1 .long gdt .align 8 idt: .fill 256, 8, 0 # Forget to set IDT at first QAQ gdt: # Empty Entry (FIRST ENTRY) .quad 0x0000000000000000 # BaseAddress = 0x00000000 # Limit = 0xfff # Granularity = 1 means 4KB Segment limit are 4KB unit # TYPE = 0xA Executable Read # DPL = 0x00 S = 1 P = 1 # Code Segment .quad 0x00c09a0000000fff # BaseAddress = 0x00000000 # Limit = 0xfff # Granularity = 1 means 4KB Segment limit are 4KB unit # TYPE = 0x2 Read/Write # DPL = 0x00 S = 1 P = 1 # Data Segment .quad 0x00c0920000000fff # Temporaray .quad 0x0000000000000000 .fill 252, 8, 0 ================================================ FILE: boot/ldS.ld ================================================ SECTIONS { .text 0x0000 : { *(.text) } /DISCARD/ : { } } ================================================ FILE: boot/setup.s ================================================ ########################################################### # # # Lesson 4 设置IDT 对8259A进行编程 # # # ########################################################### .code16 .text .equ SETUPSEG, 0x9020 .equ INITSEG, 0x9000 .equ SYSSEG, 0x1000 .equ LEN, 54 .global _start, begtext, begdata, begbss, endtext, enddata, endbss .text begtext: .data begdata: .bss begbss: .text show_text: mov $SETUPSEG, %ax mov %ax, %es mov $0x03, %ah xor %bh, %bh int $0x10 # these two line read the cursor position mov $0x000a, %bx # Set video parameter mov $0x1301, %ax mov $LEN, %cx mov $msg, %bp int $0x10 # 下面的代码调用BIOS中断将硬件的一些状态保存在0x9000:0000开始的内存处 # (注意这里会覆盖bootsect,不过无所谓,因为我们不再需要它了) ljmp $SETUPSEG, $_start _start: # 保存光标位置 # Comment for routine 10 service 3 # AH = 03 # BH = video page # on return: # CH = cursor starting scan line (low order 5 bits) # CL = cursor ending scan line (low order 5 bits) # DH = row # DL = column mov $INITSEG, %ax mov %ax, %ds mov $0x03, %ah xor %bh, %bh int $0x10 mov %dx, %ds:0 # 取扩展内存大小的值 # Comment for routine 0x15 service 0x88 # AH = 88h # on return: # CF = 80h for PC, PCjr # = 86h for XT and Model 30 # = other machines, set for error, clear for success # AX = number of contiguous 1k blocks of memory starting # at address 1024k (100000h) mov $0x88, %ah int $0x15 mov %ax, %ds:2 # 取显卡显示模式 # Comment for routine 10 service 0xf # AH = 0F # on return: # AH = number of screen columns # AL = mode currently set (see VIDEO MODES) # BH = current display page mov $0x0f, %ah int $0x10 mov %bx, %ds:4 mov %ax, %ds:6 # 检查显示方式(EGA/VGA)并取参数 # Comment for routine 10 service 0x12 # We use bl 0x10 # BL = 10 return video configuration information # on return: # BH = 0 if color mode in effect # = 1 if mono mode in effect # BL = 0 if 64k EGA memory # = 1 if 128k EGA memory # = 2 if 192k EGA memory # = 3 if 256k EGA memory # CH = feature bits # CL = switch settings mov $0x12, %ah mov $0x10, %bl int $0x10 mov %ax, %ds:8 mov %bx, %ds:10 mov %cx, %ds:12 # 复制硬盘参数表信息 # 比较奇怪的是硬盘参数表存在中断向量里 # 第一个硬盘参数表的首地址在0x41中断向量处,第二个参数的首地址表在0x46中断向量处,紧跟着第一个参数表, 每个参数表长度为0x10 Byte # 第一块硬盘参数表 mov $0x0000, %ax mov %ax, %ds lds %ds:4*0x41, %si mov $INITSEG, %ax mov %ax, %es mov $0x0080, %di mov $0x10, %cx rep movsb # 第二块硬盘参数表 mov $0x0000, %ax mov %ax, %ds lds %ds:4*0x46, %si mov $INITSEG, %ax mov %ax, %es mov $0x0090, %di mov $0x10, %cx rep movsb # 检查第二块硬盘是否存在,如果不存在的话就清空相应的参数表( # Comment for routine 0x13 service 0x15 # AH = 15h # DL = drive number (0=A:, 1=2nd floppy, 80h=drive 0, 81h=drive 1) # on return: # AH = 00 drive not present # = 01 diskette, no change detection present # = 02 diskette, change detection present # = 03 fixed disk present # CX:DX = number of fixed disk sectors; if 3 is returned in AH # CF = 0 if successful # = 1 if error mov $0x1500, %ax mov $0x81, %dl int $0x13 jc no_disk1 cmp $3, %ah je is_disk1 no_disk1: # 没有第二块硬盘,那么就对第二个硬盘表清零,使用stosb mov $INITSEG, %ax mov %ax, %es mov $0x0090, %di mov $0x10, %cx mov $0x00, %ax rep stosb is_disk1: # 下面该切换到保护模式了~(终于要离开这个不安全,文档匮乏,调试费力的16bit实模式了) # 进行切换保护模式的准备操作 cli # 关中断 # 我们先将system从0x1000:0000移动到0x0000:0000处 mov $0x0000, %ax cld # Direction = 0 move forward do_move: mov %ax, %es add $0x1000, %ax cmp $0x9000, %ax # Does we finish the move jz end_move mov %ax, %ds sub %di, %di sub %si, %si mov $0x8000, %cx # Move 0x8000 word = 0x10000 Byte (64KB) rep movsw jmp do_move # 下面我们加载 GDT, IDT 等 # 在这里补充加载GDT的代码,并在下面补充GDT表的结构 end_move: mov $SETUPSEG, %ax mov %ax, %ds lidt idt_48 lgdt gdt_48 # 开启A20地址线,使得可以访问1M以上的内存 inb $0x92, %al orb $0b00000010, %al outb %al, $0x92 # 这里我们会对8259A进行编程,很脏的活,不建议大家搞OwO(所以注释都是英文的辣) mov $0x11, %al # Init ICW1, 0x11 is init command out %al, $0x20 # 0x20 is 8259A-1 Port .word 0x00eb, 0x00eb # Time Delay jmp $+2, jmp $+2 out %al, $0xA0 # And init 8259A-2 .word 0x00eb, 0x00eb mov $0x20, %al # Send Hardware start intterupt number(0x20) out %al, $0x21 # From 0x20 - 0x27 .word 0x00eb, 0x00eb mov $0x28, %al out %al, $0xA1 # From 0x28 - 0x2F .word 0x00eb, 0x00eb mov $0x04, %al # 8259A-1 Set to Master out %al, $0x21 .word 0x00eb, 0x00eb mov $0x02, %al # 8259A-2 Set to Slave out %al, $0xA1 .word 0x00eb, 0x00eb mov $0x01, %al # 8086 Mode out %al, $0x21 .word 0x00eb, 0x00eb out %al, $0xA1 .word 0x00eb, 0x00eb mov $0xFF, %al out %al, $0x21 # Mask all the interrupts now .word 0x00eb, 0x00eb out %al, $0xA1 # 开启保护模式! mov %cr0, %eax bts $0, %eax # Turn on Protect Enable (PE) bit mov %eax, %cr0 # Jump to protected mode .equ sel_cs0, 0x0008 mov $0x10, %ax mov %ax, %ds mov %ax, %es mov %ax, %fs mov %ax, %gs ljmp $sel_cs0, $0 # 请填写GDTR信息 gdt_48: # This is the GDT Descriptor .word 0x800 # .word 512+gdt, 0x9 # This give the GDT Base address 0x90200 idt_48: .word 0 .word 0, 0 gdt: .word 0,0,0,0 .word 0x07FF .word 0x0000 .word 0x9A00 .word 0x00C0 .word 0x07FF .word 0x0000 .word 0x9200 .word 0x00C0 msg: .byte 13, 10 .ascii "You've successfully load the floppy data into RAM" .byte 13, 10, 13, 10 ================================================ FILE: docs/EXPERIMENT.md ================================================ ## 0 环境 SSR * 搭建环境 ( Virtualbox 跑通 / 自己搭建 ) * 熟悉环境(GCC, GNU Toolchain, Objdump, dd, Makefile, Bochs, QEMU) ## 1 启动 * 启动扇区 (QEMU 运行 Makefile 构建的 bootsector, 并且使用 Objdump 看反汇编) * [选] 软盘中断 / 硬盘中断 * 硬盘启动 [选] * 设置 GDT IDT 并查看(通过 Bochs 查看设置是否正确) ## 2 VGA Serial * 实现 printk (int, int-oct, int-hex, char, string) * [选] 实现滚屏,清屏,输出彩色字符 () * 提供好串口 ## 3 Page * Page Translation & It's Data Structure PTE PDE GDT * Bitmap / Buddy 分配 管理 * Page Fault 体验 ## 4 Interrupt * IDT, Trap Gate 等 实现时钟中断 * 实现 die 函数 ## 5 Process * 调度算法 Round Robin, FCFS, 多级队列 * fork 的实现(简单 fork 实现)(不带 COW 的 fork) * task_struct (进程的组织,链表,数组。) * execve 的实现 (a.out) [选做] ## 6 System Call & Signal * 系统调用的处理过程 * 实现一个系统调用 * 信号的机制 * 在 Linux 下自己写一个带信号处理的程序 ## 7 Drivers * 键盘 (译码,读取) * 终端(队列)(回显,缓冲队列,终端接口) * 终端的数据结构 * IDE 硬盘 / 软盘驱动 [选] * 声卡驱动 [选] ## 8 Filesystem (待定) ## 9 Virtual Memory (后续提高) ---- ### Draft 1. Roadmap [DONE] 2. 实验的文档中要有每一个步骤需要的原理/知识的资料提供 3. 实验环境文档要清晰 4. 实验的验证 5. 给小例子,让他完善。(每一个模块都是这样?) 6. 重点 - [6.1] 用户态内核态切换() - [6.2] 进程创建(fork) - [6.3] 中断的实现(键盘,时钟) - [6.4] 启动过程形象化 - [6.5] 存储 分页管理,页表 - [6.6] 系统调用 - 6.7 中断在系统中的重要性。 - 6.8 存储 虚拟存储(**暂无**) - 6.9 VGA / 键盘 7. 先小例子,让学生实现 8. 提供屏蔽部分底层实现的操作系统资源操作接口(如get_page free_page task_struct) ## 实验设计可以参考的书籍 * 《自己动手做操作系统》 Orange * Linux 内核设计的艺术 * Linux 0.11 内核完全解读 * 操作系统的设计与实现 陈文智 ================================================ FILE: include/asm/io.h ================================================ // 从Port读入数据到al, _v变量的值就是al的值 #define inb(port) ({\ unsigned char _v; \ __asm__ volatile("inb %%dx, %%al":"=a" (_v):"d" (port)); \ _v; \ }) #define outb(port, value) \ __asm__ ("outb %%al, %%dx"::"a" (value), "d" (port)); // 带有延时的outb #define outb_p(port, value) \ __asm__ volatile("outb %%al, %%dx\n\t" \ "jmp 1f\n\t" \ "1: jmp 1f\n\t" \ "1:"::"a" (value), "d" (port)) #define inb_p(port) ({ \ unsigned char _v; \ __asm__ volatile("inb %%dx, %%al\n\t" \ "jmp 1f\n\t" \ "1: jmp 1f\n\t" \ "1:": "=a" (_v): "d" (port)); \ _v; \ }) ================================================ FILE: include/asm/segment.h ================================================ static inline char get_fs_byte(const char *addr) { register char _v; __asm__ volatile("movb %%fs:%1, %0":"=r" (_v):"m"(*addr)); return _v; } static inline unsigned short get_fs_word(const char *addr) { unsigned short _v; __asm__ volatile("movw %%fs:%1, %0":"=r" (_v):"m"(*addr)); return _v; } static inline unsigned long get_fs_long(const char *addr) { unsigned long _v; __asm__ volatile("movl %%fs:%1, %0":"=r" (_v):"m"(*addr)); return _v; } static inline void put_fs_byte(char val, char *addr) { __asm__ volatile("movb %0, %%fs:%1":: "r" (val), "m" (*addr)); } static inline void put_fs_word(short val, short *addr) { __asm__ volatile("movw %0, %%fs:%1":: "r" (val), "m" (*addr)); } static inline void put_fs_long(unsigned long val, unsigned long *addr) { __asm__ volatile("movl %0, %%fs:%1":: "r" (val), "m" (*addr)); } ================================================ FILE: include/asm/system.h ================================================ /* * 定义了用来在C语言中直接调用的汇编宏函数 * */ #define sti() __asm__ volatile ("sti"::) #define cli() __asm__ volatile ("cli"::) #define nop() __asm__ volatile ("nop"::) #define iret() __asm__ volatile ("iret"::) // 下面这个宏是通用的设置门描述符的宏,参数分别为 // gate_addr: IDT 描述符的地址; type: 门类型 // dpl: 特权级别; funaddr: 要执行的程序的线性地址 // 设置的过程参考注释 #define _set_gate(gate_addr, type, dpl, funaddr) \ __asm__ volatile (\ "movw %%dx, %%ax\n\t"\ "movw %0, %%dx\n\t"\ "movl %%eax, %1\n\t"\ "movl %%edx, %2"\ :\ :"i" ((short)(0x8000 + ((dpl) << 13) + ((type) << 8))), \ "o" (*((char *) (gate_addr))),\ "o" (*(4 + (char *)(gate_addr))), \ "a" (0x00080000), \ "d" ((char*)(funaddr))) // 陷阱门, Type = 0xF #define set_trap_gate(n, funaddr) \ _set_gate(&idt[n], 0xF, 0, funaddr) // 中断门, Type = 0xE #define set_intr_gate(n, funaddr) \ _set_gate(&idt[n], 0xE, 0, funaddr) #define set_system_gate(n, funaddr) \ _set_gate(&idt[n], 0xF, 3, funaddr) #define _set_seg_desc(gate_addr,type,dpl,base,limit) {\ *(gate_addr) = ((base) & 0xff000000) | \ (((base) & 0x00ff0000)>>16) | \ ((limit) & 0xf0000) | \ ((dpl)<<13) | \ (0x00408000) | \ ((type)<<8); \ *((gate_addr)+1) = (((base) & 0x0000ffff)<<16) | \ ((limit) & 0x0ffff); } #define _set_tssldt_desc(n,addr,type) \ __asm__ volatile ("movw $104,%1\n\t" \ "movw %%ax,%2\n\t" \ "rorl $16,%%eax\n\t" \ "movb %%al,%3\n\t" \ "movb $" type ",%4\n\t" \ "movb $0x00,%5\n\t" \ "movb %%ah,%6\n\t" \ "rorl $16,%%eax" \ ::"a" (addr), "m" (*(n)), "m" (*(n+2)), "m" (*(n+4)), \ "m" (*(n+5)), "m" (*(n+6)), "m" (*(n+7)) \ ) #define set_tss_desc(n, addr) _set_tssldt_desc(((char *) (n)), ((int) (addr)), "0x89") #define set_ldt_desc(n, addr) _set_tssldt_desc(((char *) (n)), ((int) (addr)), "0x82") ================================================ FILE: include/errno.h ================================================ #ifndef _ERRNO_H #define _ERRNO_H // 错误号定义 extern int errno; #define ERROR 99 #define EPERM 1 #define ENOENT 2 #define ESRCH 3 #define EINTR 4 #define EIO 5 #define ENXIO 6 #define E2BIG 7 #define ENOEXEC 8 #define EBADF 9 #define ECHILD 10 #define EAGAIN 11 #define ENOMEM 12 #define EACCES 13 #define EFAULT 14 #define ENOTBLK 15 #define EBUSY 16 #define EEXIST 17 #define EXDEV 18 #define ENODEV 19 #define ENOTDIR 20 #define EISDIR 21 #define EINVAL 22 #define ENFILE 23 #define EMFILE 24 #define ENOTTY 25 #define ETXTBSY 26 #define EFBIG 27 #define ENOSPC 28 #define ESPIPE 29 #define EROFS 30 #define EMLINK 31 #define EPIPE 32 #define EDOM 33 #define ERANGE 34 #define EDEADLK 35 #define ENAMETOOLONG 36 #define ENOLCK 37 #define ENOSYS 38 #define ENOTEMPTY 39 #endif ================================================ FILE: include/linux/fs.h ================================================ #ifndef _FS_H #define _FS_H #include #define READ 0 #define WRITE 1 #define READA 2 #define WRITEA 3 struct buffer_head { char * b_data; /* pointer to data block 1024Byte */ unsigned long b_blocknr; /* block number */ unsigned short b_dev; /* device no */ unsigned char b_uptodate; unsigned char b_dirt; /* 0-clean 1-dirty */ unsigned char b_count; /* users using this block */ unsigned char b_lock; /* 0-unlock 1-lock */ struct task_struct * b_wait; struct buffer_head * b_prev; struct buffer_head * b_next; struct buffer_head * b_prev_free; struct buffer_head * b_next_free; }; #define MAJOR(a) (((unsigned)(a))>>8) #endif ================================================ FILE: include/linux/head.h ================================================ #ifndef _HEAD_H #define _HEAD_H typedef struct desc_struct { unsigned long a, b; } desc_table[256]; extern desc_table idt, gdt; extern unsigned long pg_dir[1024]; #endif ================================================ FILE: include/linux/kernel.h ================================================ #ifndef _KERNEL_H #define _KERNEL_H void printk(char *fmt, ...); // Simplest printk function to use void video_putchar_at(char ch, int x, int y, char attr); void video_putchar(char ch); void video_clear(); void video_init(); int video_getx(); int video_gety(); void roll_screen(); void memcpy(char *dest, char *src, int count, int size); void printnum(int num, int base, int sign); int get_cursor(); void update_cursor(int row, int col); void panic(const char *str); void verify_area(void *addr, unsigned int size); extern int video_x, video_y; #endif ================================================ FILE: include/linux/lib.h ================================================ char getchar(); int getline(char *str); int printf(char *fmt, ...); ================================================ FILE: include/linux/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SIZE 4096 /* extern */ unsigned long get_free_page(void); /* extern */ unsigned long put_page(unsigned long page, unsigned long address); /* extern */ void free_page(unsigned long addr); /* extern */ void calc_mem(void); void do_no_page(unsigned long error_code, unsigned long address); int copy_page_tables(unsigned long from, unsigned long to, unsigned long size); void mm_print_pageinfo(unsigned long addr); #endif ================================================ FILE: include/linux/sched.h ================================================ #ifndef _SCHED_H #define _SCHED_H // 最多有 64 个进程(任务)同时处于系统中 #define NR_TASKS 64 // 时钟频率 100 hz #define HZ 100 #define FIRST_TASK task[0] #define LAST_TASK task[NR_TASKS-1] #include #include // 目前signal.h 仅仅具有最基本的sigaction结构,之后会实现signal #include // 定义任务状态 #define TASK_RUNNING 0 #define TASK_INTERRUPTIBLE 1 #define TASK_UNINTERRUPTIBLE 2 #define TASK_ZOMBIE 3 #define TASK_STOPPED 4 #ifndef NULL #define NULL ((void *)(0)) #endif extern int copy_page_tables(unsigned long from, unsigned long to, unsigned long size); extern int free_page_tables(unsigned long from, unsigned long size); extern void schedule(void); typedef int (*fn_ptr)(); struct i387_struct { long cwd; // 控制字(Control) long swd; // 状态字(State) long twd; // 标记字(Tag) long fip; // 协处理器代码指针IP long fcs; // 协处理器代码段寄存器CS long foo; // 内存offset long fos; // 内存段 long st_space[20]; // 8个10字节的协处理器累加器 }; // 这里是和TSS结构对应的,要注意变量的顺序,具体参考Intel手册 // 还包含了 i387 协处理器的结构 struct tss_struct { long back_link; long esp0; long ss0; long esp1; long ss1; long esp2; long ss2; long cr3; long eip; long eflags; long eax, ecx, edx, ebx; // 顺序很重要 long esp; long ebp; long esi; long edi; long es; long cs; long ss; long ds; long fs; long gs; unsigned long ldt; unsigned long bitmap; struct i387_struct i387; }; // 进程描述符结构 struct task_struct { // --- 硬编码部分,下面的不应该修改 --- long state; long counter; // 时间片计数器 long priority; // 优先级 // 信号相关结构,分别为信号集,响应信号的行为,屏蔽的信号集 long signal; struct sigaction sigaction[32]; unsigned long blocked; // --- 硬编码部分结束 --- int exit_code; // 其父进程会读取该任务的 exit_code unsigned long start_code; unsigned long end_code; unsigned long end_data; unsigned long brk; // 总长度brk unsigned long start_stack; // 堆栈段地址 long pid; // pid 进程ID long father; // 父进程ID long pgrp; // 进程组ID long session; // 会话(session)ID long leader; // 会话(session)的首领 unsigned short uid; // 用户ID unsigned short euid; // 有效用户ID unsigned short suid; // SetUID unsigned short gid; // 组ID unsigned short egid; // 有效组ID unsigned short sgid; // SetGID long alarm; // 报警定时值 单位: jiffies long utime; // 用户态运行时间 long stime; // 内核态运行时间 long cutime; // 子进程用户态运行时间 long cstime; // 子进程内核态运行时间 long start_time; // 进程开始运行的时点 unsigned short used_math; // 是否使用了协处理器 int tty; // 下面是和文件系统相关的变量,暂时不使用,先注释掉 // unsigned short umask; // struct m_inode *pwd; // struct m_inode *executable; // unsigned long close_on_exec; // struct file * filp[NR_OPEN]; struct desc_struct ldt[3]; // LDT 第一项为空, 第二项是代码段,第三项是数据段(也是堆栈段) struct tss_struct tss; // 该进程的TSS结构 }; #define INIT_TASK \ /* state info */ {0,15,15, \ /* signals */ 0,{{},},0, \ /* exit_code, brk */0,0,0,0,0,0, \ /* pid */ 0,-1,0,0,0, \ /* uid */ 0,0,0,0,0,0, \ /* alarm, etc... */0,0,0,0,0,0,\ /* math */ 0, \ /* tty */ -1, \ /* LDT */ { \ {0, 0},\ {0x9f, 0xc0fa00}, \ {0x9f, 0xc0f200}, \ }, \ /* TSS */ {0, PAGE_SIZE+(long)&init_task, 0x10, 0, 0, 0, 0, (long)&pg_dir, \ 0, 0, 0, 0, 0, 0, 0, 0, \ 0, 0, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, \ _LDT(0), 0x80000000, \ {} \ } \ } extern struct task_struct *task[NR_TASKS]; extern struct task_struct *last_task_used_math; extern struct task_struct *current; extern long volatile jiffies; extern long start_time; #define CURRENT_TIME (start_time + jiffies / HZ) extern void add_timer(long *jiffies, void(*fn)(void)); extern void sleep_on(struct task_struct **p); extern void interruptible_sleep_on(struct task_struct **p); extern void wake_up(struct task_struct **p); extern void show_task_info(struct task_struct *task); #define FIRST_TSS_ENTRY 4 #define FIRST_LDT_ENTRY (FIRST_TSS_ENTRY+1) // 下面计算出TSS,LDT在GDT中的偏移量 // _TSS(n)表示第n个TSS,计算方式为,第一个 TSS 的入口为 4 << 3(因为每一个Entry占8Byte, 所以第4个的偏移量为4 << 3) #define _TSS(n) ((((unsigned long) n) << 4) + (FIRST_TSS_ENTRY << 3)) #define _LDT(n) ((((unsigned long) n) << 4) + (FIRST_LDT_ENTRY << 3)) #define ltr(n) __asm__ volatile("ltr %%ax"::"a" (_TSS(n))) #define lldt(n) __asm__ volatile("lldt %%ax"::"a" (_LDT(n))) // 取出当前的任务号,返回为n: 当前任务号(和进程号不一样) #define str(n) \ __asm__ volatile("str %%ax\n\t" \ "subl %2, %%eax\n\t" \ "shrl $4, %%eax\n\t" \ : "=a" (n) \ : "a" (0), "i" (FIRST_TSS_ENTRY << 3)) // #define PAGE_ALIGN(n) (((n) + 0xfff) & 0xfffff000) 这一行虽然在原有的linux0.11中,不过在整个repo中都没有使用到 #define switch_to(n) {\ struct {long a, b;} __tmp; \ __asm__ volatile ("cmpl %%ecx, current\n\t" \ "je 1f\n\t" \ "movw %%dx, %1\n\t" \ "xchgl %%ecx, current\n\t" \ "ljmp *%0\n\t" /* 长跳跃到新的TSS选择符,表示任务切换 */ \ "cmpl %%ecx, last_task_used_math\n\t" \ "jne 1f\n\t" \ "clts\n\t" \ "1:" \ ::"m" (*&__tmp.a), "m" (*&__tmp.b), \ "d" (_TSS(n)), "c" ((long) task[n])); \ } #define _set_base(addr, base) \ __asm__ volatile(/* "push %%edx\n\t" */ \ "movw %%dx, %0\n\t" \ "rorl $16, %%edx\n\t" \ "movb %%dl, %1\n\t" \ "movb %%dh, %2\n\t" \ /* "pop %%edx" */ \ ::"m" (*((addr) + 2)), \ "m" (*((addr) + 4)), \ "m" (*((addr) + 7)), \ "d" (base) \ /*:"dx"*/ \ ) #define _set_limit(addr, limit) \ __asm__ volatile( \ "movw %%dx, %0\n\t" \ "rorl $16, %%edx\n\t" \ "movb %1, %%dh\n\t" \ "andl $0xf0, %%dh\n\t" \ "orb %%dh, %%dl\n\t" \ "mov %%dl %1" \ :"m" (*(addr)), \ "m" ((*(addr) + 6)) \ "d" (limit) \ ) #define set_base(ldt, base) _set_base(((char *)&(ldt)), (base)) // limit >> 12 是因为当Descriptor中G位置位的时候,Limit单位是4KB #define set_limit(ldt, limit) _set_limit(((char *)ldt), (limit - 1) >> 12) static inline unsigned long _get_base(char *addr) { unsigned long __base; __asm__ volatile("movb %3, %%dh\n\t" "movb %2, %%dl\n\t" "shll $16, %%edx\n\t" "movw %1, %%dx\n\t" :"=&d" (__base) :"m" (*((addr) + 2)), "m" (*((addr) + 4)), "m" (*((addr) + 7))); return __base; } #define get_base(ldt) _get_base(((char *)&(ldt))) #define get_limit(segment) ({\ unsigned long __limit; \ __asm__ volatile("lsll %1, %0\n\tincl %0":"=r"(__limit):"r"(segment)); \ __limit; \ }) #endif ================================================ FILE: include/linux/sys.h ================================================ #ifndef _SYS_H #define _SYS_H #include extern int sys_fork(); extern int sys_pause(); extern int stub_syscall(); extern int serial_debugstr(char *str); extern int sys_kill(int pid, int sig); extern int sys_sigaction(int signum, struct sigaction *action, struct sigaction *old_action); extern int sys_sgetmask(void); extern int sys_ssetmask(int newmask); // Just for debug use extern int tty_read(int channel, char *buf, int nr); extern int _user_tty_write(int channel, char *buf, int nr); extern int sys_alarm(long seconds); extern int sys_sleep(long seconds); // 目前除了少数syscall之外其余的syscall均为stub状态 fn_ptr sys_call_table[] = { tty_read, // 0 stub_syscall, sys_fork, _user_tty_write, // 3 stub_syscall, stub_syscall, stub_syscall, stub_syscall, stub_syscall, stub_syscall, sys_sleep, // 10 stub_syscall, stub_syscall, stub_syscall, stub_syscall, stub_syscall, stub_syscall, stub_syscall, stub_syscall, stub_syscall, stub_syscall, // 20 stub_syscall, stub_syscall, stub_syscall, stub_syscall, stub_syscall, stub_syscall, stub_syscall, stub_syscall, sys_pause, stub_syscall, // 30 stub_syscall, stub_syscall, stub_syscall, stub_syscall, stub_syscall, stub_syscall, sys_kill, stub_syscall, stub_syscall, stub_syscall, // 40 stub_syscall, stub_syscall, stub_syscall, stub_syscall, stub_syscall, stub_syscall, stub_syscall, stub_syscall, stub_syscall, stub_syscall, // 50 stub_syscall, stub_syscall, stub_syscall, stub_syscall, stub_syscall, stub_syscall, stub_syscall, stub_syscall, stub_syscall, stub_syscall, // 60 stub_syscall, stub_syscall, stub_syscall, stub_syscall, stub_syscall, stub_syscall, sys_sigaction, sys_sgetmask, sys_ssetmask, stub_syscall, stub_syscall, serial_debugstr, }; #endif ================================================ FILE: include/linux/tty.h ================================================ #ifndef _TTY_H #define _TTY_H #include #define TTY_BUF_SIZE 1024 #define TTY_ECHO 0x0001 // 回显标志 void tty_init(); struct tty_queue { char buf[TTY_BUF_SIZE]; struct task_struct *wait_proc; // 等待该缓冲区的进程队列 unsigned long head; unsigned long tail; }; struct tty_struct { void (*write)(struct tty_struct *tty); int pgrp; unsigned int flags; struct tty_queue read_q; // read_q 存储的是键盘 Raw input struct tty_queue write_q; // write_q 存储的是即将输出到屏幕的字符 struct tty_queue buffer; // buffer 是用户的输入 buffer, 会被 tty_read 读走 }; extern struct tty_struct tty_table[]; int tty_isempty_q(const struct tty_queue *q); int tty_isfull_q(const struct tty_queue *q); char tty_pop_q(struct tty_queue *q); char tty_queue_head(const struct tty_queue *q); char tty_queue_tail(const struct tty_queue *q); int tty_push_q(struct tty_queue *q, char ch); int tty_push_q_front(struct tty_queue *q, char ch); void tty_queue_stat(const struct tty_queue *q); void tty_write(struct tty_struct *tty); int _user_tty_write(int channel, char *buf, int nr); void copy_to_buffer(struct tty_struct *tty); int tty_read(int channel, char *buf, int nr); #endif ================================================ FILE: include/serial_debug.h ================================================ #ifndef _SERIAL_DEBUG_H #define _SERIAL_DEBUG_H extern void s_putchar(char a); extern void s_puts(char *s); extern void s_printnum(int num, int base, int sign); extern void s_printk(char *fmt, ...); #endif ================================================ FILE: include/signal.h ================================================ #ifndef _SIGNAL_H #define _SIGNAL_H #include typedef int sig_atomic_t; typedef unsigned int sigset_t; #define _NSIG 32 // 32种信号,所以下面的信号集也是32bit啦 #define NSIG _NSIG // 定义信号宏 #define SIGHUP 1 #define SIGINT 2 #define SIGQUIT 3 #define SIGILL 4 #define SIGTRAP 5 #define SIGABRT 6 #define SIGIOT 6 #define SIGUNUSED 7 #define SIGFPE 8 #define SIGKILL 9 #define SIGUSR1 10 #define SIGSEGV 11 #define SIGUSR2 12 #define SIGPIPE 13 #define SIGALRM 14 #define SIGSTKFLT 16 #define SIGCHLD 17 #define SIGCONT 18 #define SIGSTOP 19 #define SIGTSTP 20 #define SIGTTIN 21 #define SIGTTOU 22 // 下面定义 sigaction 需要的结构 #define SA_NOCLDSTOP 1 #define SA_NOMASK (int)0x40000000 // 表示不阻止在某个信号的信号处理程序中再次收到该信号 #define SA_ONESHOT (int)0x80000000 // 只调用一次信号处理句柄 #define SIG_BLOCK 0 #define SIG_UNBLOCK 1 #define SIG_SETMASK 2 #define SIG_DFL ((void(*) (int))0) // 默认信号处理句柄 #define SIG_IGN ((void(*) (int))1) // 信号处理忽略句柄 void (*signal(int _sig, void(*func)(int)))(int); int raise(int sig); // 向自身发信号 int kill(pid_t pid, int sig); // 向某个进程发信号 typedef unsigned int sigset_t; // 32bit 信号集,一位表示一个信号,对于linux0.11已经够用了 struct sigaction { void (*sa_handler)(int); sigset_t sa_mask; int sa_flags; void (*sa_restorer)(void); }; // 对阻塞信号集的操作 // Not implemented in sigaction int sigaddset(sigset_t *mask, int signo); int sigdelset(sigset_t *mask, int signo); int sigemptyset(sigset_t *mask); int sigfillset(sigset_t *mask); int sigismember(sigset_t *mask, int signo); int sigpending(sigset_t *set); int sigprocmask(int how, sigset_t *set, sigset_t *oldset); // 改变对于某个信号sig的处理过程 // int sigaction(int sig, struct sigaction *act, struct sigaction *oldact); #endif ================================================ FILE: include/stdarg.h ================================================ #ifndef _STDARG_H #define _STDARG_H typedef char *va_list; /* Amount of space required in an argument list for an arg of type TYPE. TYPE may alternatively be an expression whose type is used. */ #define __va_rounded_size(TYPE) \ (((sizeof (TYPE) + sizeof (int) - 1) / sizeof (int)) * sizeof (int)) #ifndef __sparc__ #define va_start(AP, LASTARG) \ (AP = ((char *) &(LASTARG) + __va_rounded_size (LASTARG))) #else #define va_start(AP, LASTARG) \ (__builtin_saveregs (), \ AP = ((char *) &(LASTARG) + __va_rounded_size (LASTARG))) #endif void va_end (va_list); /* Defined in gnulib */ #define va_end(AP) #define va_arg(AP, TYPE) \ (AP += __va_rounded_size (TYPE), \ *((TYPE *) (AP - __va_rounded_size (TYPE)))) #endif /* _STDARG_H */ ================================================ FILE: include/stddef.h ================================================ #ifndef _STDDEF_H #define _STDDEF_H #ifndef _PTRDIFF_T #define _PTRDIFF_T typedef long ptrdiff_t; #endif #ifndef _SIZE_T #define _SIZE_T typedef unsigned long size_t; #endif #undef NULL #define NULL ((void *)0) #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) #endif ================================================ FILE: include/sys/types.h ================================================ #ifndef _SYS_TYPES_H #define _SYS_TYPES_H #ifndef _SIZE_T #define _SIZE_T typedef unsigned int size_t; #endif #ifndef _TIME_T #define _TIME_T typedef long time_t; #endif #ifndef _PTRDIFF_T #define _PTRDIFF_T typedef long ptrdiff_t; #endif #ifndef NULL #define NULL ((void *) 0) #endif typedef int pid_t; typedef unsigned short uid_t; typedef unsigned char gid_t; typedef unsigned short dev_t; typedef unsigned short ino_t; typedef unsigned short mode_t; typedef unsigned short umode_t; typedef unsigned char nlink_t; typedef int daddr_t; typedef long off_t; typedef unsigned char u_char; typedef unsigned short ushort; typedef struct { int quot,rem; } div_t; typedef struct { long quot,rem; } ldiv_t; struct ustat { daddr_t f_tfree; ino_t f_tinode; char f_fname[6]; char f_fpack[6]; }; #endif ================================================ FILE: include/sys/wait.h ================================================ #ifndef _SYS_WAIT_H #define _SYS_WAIT_H #include #define _LOW(v) ( (v) & 0377) #define _HIGH(v) ( ((v) >> 8) & 0377) /* options for waitpid, WUNTRACED not supported */ #define WNOHANG 1 #define WUNTRACED 2 #define WIFEXITED(s) (!((s)&0xFF) #define WIFSTOPPED(s) (((s)&0xFF)==0x7F) #define WEXITSTATUS(s) (((s)>>8)&0xFF) #define WTERMSIG(s) ((s)&0x7F) #define WSTOPSIG(s) (((s)>>8)&0xFF) #define WIFSIGNALED(s) (((unsigned int)(s)-1 & 0xFFFF) < 0xFF) pid_t wait(int *stat_loc); pid_t waitpid(pid_t pid, int *stat_loc, int options); #endif ================================================ FILE: include/unistd.h ================================================ #ifndef _UNISTD_H #define _UNISTD_H #ifdef __LIBRARY__ #define __NR_fork 2 #define __NR_sleep 10 #define __NR_pause 29 #define __NR_kill 37 #define __NR_sigaction 67 #define __NR_sgetmask 68 #define __NR_ssetmask 69 #define __NR_sys_debug 72 // Just for debug // TODO Change to write, read #define __NR_user_tty_read 0 #define __NR_user_tty_write 3 #define _syscall0(type, name) \ type name(void) \ { \ long __res; \ __asm__ volatile("int $0x80\n\t" \ : "=a" (__res) \ : "0" (__NR_##name)); \ if (__res >= 0) \ return (type) __res; \ /*errno = -__res;*/ \ return -1;\ } #define _syscall1(type, name, atype, a) \ type name(atype a) \ { \ long __res; \ __asm__ volatile("int $0x80\n\t" \ : "=a" (__res) \ : "0" (__NR_##name), "b" ((long) a)); \ if (__res >= 0) \ return (type) __res; \ /*errno = -__res;*/ \ return -1; \ } #define _syscall2(type, name, atype, a, btype, b) \ type name(atype a, btype b) \ { \ long __res; \ __asm__ volatile("int $0x80\n\t" \ : "=a" (__res) \ : "0" (__NR_##name), "b" ((long) a), "c" ((long) b)); \ if (__res >= 0) \ return (type) __res; \ /*errno = -__res;*/ \ return -1; \ } #define _syscall3(type, name, atype, a, btype, b, ctype, c) \ type name(atype a, btype b, ctype c) \ { \ long __res; \ __asm__ volatile("int $0x80\n\t" \ : "=a" (__res) \ : "0" (__NR_##name), "b" ((long) a), "c" ((long) b), "d" ((long) c)); \ if (__res >= 0) \ return (type) __res; \ /* errno = -__res;*/ \ return -1; \ } #endif #endif ================================================ FILE: init/Makefile ================================================ include ../Makefile.header CFLAGS += -I../include LDFLAGS += -r all: main.o .c.o: @$(CC) $(CFLAGS) \ -c -o $*.o $< clean: rm -rf *.o ================================================ FILE: init/main.c ================================================ /* * Default Routine to run after head.s * */ #define __LIBRARY__ #include #include #include #include #include #include #include // Use to debug serial #include extern void trap_init(void); extern void video_init(void); extern void sched_init(void); extern void mem_init(unsigned long start_mem, unsigned long end_mem); extern int user_tty_read(int channel, char *buf, int nr); void init(); // Here contains sycall routines static inline int fork(void) __attribute__((always_inline)); static inline int pause(void) __attribute__((always_inline)); static inline int sys_debug(char *str); static inline _syscall0(int,fork) static inline _syscall1(int, sys_debug, char *, str) static inline _syscall1(int, sleep, long, seconds) static inline int pause(void) { long __res; __asm__ volatile("int $0x80\n\t" :"=a" (__res) :"0" (__NR_pause)); if( __res >= 0) return (int) __res; return -1; } #define move_to_user_mode() \ __asm__ ("movl %%esp, %%eax\n\t" \ "pushl $0x17\n\t" \ "pushl %%eax\n\t" \ "pushfl\n\t" \ "pushl $0x0f\n\t" \ "pushl $1f\n\t" \ "iret\n" \ "1:\t movl $0x17, %%eax\n\t" \ "movw %%ax, %%ds\n\t" \ "movw %%ax, %%es\n\t" \ "movw %%ax, %%fs\n\t" \ "movw %%ax, %%gs" \ :::"ax"); int memtest_main(void); void signal_demo_main(void); void sched_abcd_demo(void); int main() { video_init(); trap_init(); sched_init(); mem_init(0x100000, 0x300000); tty_init(); sti(); printk("TTY Init done\n", 1); printk("root@neu-os# "); // 初始化物理页内存, 将 1MB - 16MB 地址空间的内存进行初始化 // s_printk("Test Serial mem_init = %x\n", mem_init); // mmtest_main(); move_to_user_mode(); sys_debug("User mode can print string use this syscall\n"); // now user process can execute! // but why cannot schedule! if(!fork()) { if(!fork()) { sched_abcd_demo(); } else { // signal_demo_main(); } while(1); } // printk("buf = %s\n", buf); } void sched_abcd_demo() { // Here init process (pid = 1) will // print AABB randomly char buf[100] = "TTY"; printf("Welcome to the OS, your are current at %x\n", sched_abcd_demo); printf("Execuse me, but who are you? "); getline(buf); printf("%s, emm good name! Hi %s. :)\n", buf, buf); printf("%s@neu-os$"); printf("This is a multi-thread demo, start in 3s ... 3"); sleep(1); printf(".2"); sleep(1); printf(".1"); sleep(1); printf(".0\n"); if(!fork()) { while(1) { printf("A"); } } if(!fork()) { while(1) { printf("B"); } } if(!fork()) { while(1) { printf("C"); } } if(!fork()) { while(1) { printf("D"); } } while(1); } ================================================ FILE: kernel/Makefile ================================================ include ../Makefile.header OBJS = printk.o panic.o traps.o asm.o sched.o system_call.o sys.o fork.o \ serial_debug.o signal.o signal_demo.o exit.o libc_restore.o vsprintf.o LDFLAGS += -r CFLAGS += -I../include .PHONY=clean run all all: kernel.o kernel.o: $(OBJS) $(LD) $(LDFLAGS) -o kernel.o $(OBJS) .c.o: @$(CC) $(CFLAGS) \ -c -o $*.o $< clean: - rm -f $(OBJS) kernel.o - make -C chr_drv clean ================================================ FILE: kernel/asm.s ================================================ .code32 # Code to implement low-level 80386 interrupt handler # We'll handle int0 - int16 here, some interrupts will # push addtional error code onto stack, so we need to implement two common # handler # 实现 Intel 80386 硬件相关中断,即中断号 0 - 16 的汇编处理程序 # 大部分函数都会去调用相应的 traps.c 中的 C 语言处理函数,汇编 # 只做最紧要的事情,主要流程在 C 函数里处理 # 下面仅对中断的具体含义进行简单的注释,想要了解详情的同学请查看 Intel IA32 手册 Vol 3A 第六章 .global divide_error, debug, nmi, int3, overflow, bounds, invalid_op .global double_fault, coprocessor_segment_overrun .global invalid_TSS, segment_not_present, stack_segment .global general_protection, coprocessor_error, irq13, reserved # 这两个是STUB的,之后会在system_call.s中实现 .global corprocessor_error, parallel_interrupt, device_not_available .global demo_timer_interrupt # 先处理无 error code 压栈的情况 # 相关中断有: 除零(Fault) 调试debug(Fault) nmi(Trap) 断点指令(Trap) divide_error: pushl $do_divide_error # 首先压栈要调用的函数 no_error_code: xchgl %eax, (%esp) # 将函数地址交换到 %eax 中, (同时%eax入栈) # 将当前寄存器压栈 pushl %ebx pushl %ecx pushl %edx pushl %edi pushl %esi pushl %ebp push %ds push %es push %fs pushl $0 # error code = 0 lea 44(%esp), %edx # 栈的地址由高地址向低地址生长,因而这里是去向栈底寻址 # 我们刚刚 push 了 11 个参数,现在将指针回退到中断返回地址 # 这一参数所在的栈指针位置, 并将地址放入 %edx pushl %edx movl $0x10, %edx # 初始化ds, es, fs加载为内核数据段选择符 mov %dx, %ds mov %dx, %es mov %dx, %fs call *%eax # 使用间接调用,调用 %eax 内存放的地址处的函数 addl $8, %esp # 丢弃最后入栈的两个参数, 让栈指针回到 %fs 入栈处 pop %fs pop %es pop %ds popl %ebp popl %esi popl %edi popl %edx popl %ecx popl %ebx popl %eax # 这里把原 eax 中内容恢复 iret # 中断返回,包括弹出相应的参数,以及返回原代码执行 # int1 debug 调试中断入口点 类型 Fault debug: pushl $do_int3 jmp no_error_code # int2 Non maskable interrupts 入口点 类型 Trap nmi: pushl $do_nmi jmp no_error_code # int3 断点指令引起中断的入口点 类型 Trap int3: pushl $do_int3 jmp no_error_code # int4 溢出错误入口点 类型 Trap overflow: pushl $do_overflow jmp no_error_code # int5 边界检查出错 类型 Fault bounds: pushl $do_bounds jmp no_error_code # int6 无效指令 类型 Fault invalid_op: pushl $do_invalid_op jmp no_error_code # int9 协处理器段超出 类型 Abort coprocessor_segment_overrun: pushl $do_coprocessor_segment_overrun jmp no_error_code # int15 其他 Intel 保留中断的入口点 reserved: pushl $do_reserved jmp no_error_code # int45 -- irq13 # 协处理器处理完一个操作的时候就会发送 IRQ13 信号,通知CPU操作完成,80387执行计算时 # CPU 会等待其完成,下面通过写协处理端口(0xF0)消除BUSY信号,并重新激活80387的扩展请求 # 引脚 PERREQ irq13: pushl %eax xorb %al, %al outb %al, $0xF0 movb $0x20, %al outb %al, $0x20 jmp 1f 1: jmp 1f 1: outb %al, $0xA0 # 向8259A 发送 EOI 信号 popl %eax jmp coprocessor_error # 下面的中断会在压入中断返回地址之后将出错号一同压栈,因此返回时需要弹出出错号 # Double Fault, 类型 Abort 有出错码 # 当CPU在调用一个异常处理程序的时候又检测到另一个异常,而且这两个异常无法被串行处理 # double_fault: pushl $do_double_fault error_code: xchgl %eax, 4(%esp) # 将出错号%eax交换,同时%eax入栈 xchgl %ebx, (%esp) # 将要调用的C函数的地址与%ebx交换,同时%ebx入栈 pushl %ecx pushl %edx pushl %esi pushl %edi pushl %ebp pushl %ds pushl %es pushl %fs pushl %eax lea 44(%esp), %eax pushl %eax movl $0x10, %eax mov %ax, %ds mov %ax, %es mov %ax, %fs call *%ebx # 间接调用, %ebx 中存放的就是要调用的C函数的地址 addl $8, %esp pop %fs pop %es pop %ds pop %ebp popl %edi popl %esi popl %edx popl %ecx popl %ebx popl %eax iret # int10 无效的任务状态段(TSS) 类型 Fault invalid_TSS: pushl $do_invalid_TSS jmp error_code # int11 段不存在 类型 Fault segment_not_present: pushl $do_segment_not_present jmp error_code # int12 堆栈段错误 类型 Fault stack_segment: pushl $do_stack_segment jmp error_code # 一般保护性错误 类型 Fault general_protection: pushl $do_general_protection jmp error_code # coprocessor error 先在这里实现一个stub的,之后会实现 coprocessor_error: pushl $do_stub jmp error_code parallel_interrupt: # 本版本没有实现,这里只发EOI pushl %eax movb $0x20, %al outb %al, $0x20 popl %eax iret device_not_available: pushl $do_stub jmp error_code # int7 设备不存在 将在 kernel/system_call.s 中实现 # int14 页错误 将在 mm/page.s 中实现 # int16 协处理器错误 将在 kernel/system_call.s 中实现 # int 0x20 时钟中断 将在 kernel/system_call.s 中实现 # int 0x80 系统调用 将在 kernel/system_call.s 中实现 ================================================ FILE: kernel/blk_drv/Makefile ================================================ ================================================ FILE: kernel/blk_drv/blk.h ================================================ /* * blk.h block driver commmon data structure and * macros, function prototypes */ #ifndef _BLK_H #define _BLK_H #define NR_BLK_DEV 7 #define NR_REQUEST 32 // This is the request queue structure struct request { int dev; int cmd; int errors; unsigned long sector; unsigned long nr_sectors; char * buffer; struct task_struct * waiting; struct buffer_head * bh; struct request * next; }; struct blk_dev_struct { void (*request_fn)(void); struct request * current_request; }; #define IN_ORDER(s1, s2) \ ((s1)->cmd < (s2)->cmd || (((s1)->cmd == (s2)->cmd) && \ ((s1)->dev < (s2)->dev) || ((s1)->dev == (s2)->dev && \ (s1)->sector < (s2)->sector))) #endif ================================================ FILE: kernel/blk_drv/request_scan_algo.c ================================================ /* * block driver Request queue implementation * using SCAN algorithm */ #include #include #include #include #include "blk.h" struct request req_tbl[NR_REQUEST]; struct task_struct * wait_for_request = NULL; struct blk_dev_struct blk_dev[NR_BLK_DEV] = { {NULL, NULL}, {NULL, NULL}, {NULL, NULL}, {NULL, NULL}, {NULL, NULL}, {NULL, NULL}, {NULL, NULL}, }; static void add_request(struct blk_dev_struct * dev, struct request * req) { struct request * tmp; req->next = NULL; cli(); // current disable fs/buffer functional if(req->bh) { req->bh->b_dirt = 0; } // If current req == NULL means this is the // first request if (!(tmp = dev->current_request)) { dev->current_request = req; sti(); (dev->request_fn)(); return ; } for(; tmp->next != NULL; tmp = tmp->next) { if ((IN_ORDER(tmp, req) || !IN_ORDER(tmp, tmp->next)) && IN_ORDER(req, tmp->next)) break; } req->next = tmp->next; tmp->next = req; sti(); } static inline void lock_buffer(struct buffer_head *bh) { cli(); while(bh->b_lock) { sleep_on(&bh->b_wait); } bh->b_lock = 1; sti(); } static inline void unlock_buffer(struct buffer_head *bh) { if (!bh->b_lock) { printk("unlock_buffer: buffer not locked\n"); } bh->b_lock = 0; wake_up(&bh->b_wait); } static void make_request(int major, int rw, struct buffer_head *bh) { struct request * req; int rw_ahead; short available = 0; if ((rw_ahead = (rw == READA || rw == WRITEA))) { if (bh->b_lock) { return ; } if (rw == READA) { rw = READ; } if (rw == WRITEA) { rw = WRITE; } } if (!(rw == READ || rw == WRITE)) { panic("Bad block dev command, must be R/W/RA/WA"); } lock_buffer(bh); if ((rw == WRITE && !bh->b_dirt) || (rw == READ && bh->b_uptodate)) { unlock_buffer(bh); return ; } // Do not let WRITE request fill the queue while(!available) { if (rw == READ) { req = req_tbl + NR_REQUEST; } else { req = req_tbl + ((NR_REQUEST * 2) / 3); } while(--req >= req_tbl) { if (req->dev < 0) // dev < 0 means a new request break; available = 1; } if (available) { break; } // no available found if (req < req_tbl) { if (rw_ahead) { unlock_buffer(bh); return; } sleep_on(&wait_for_request); } } // found an empty request place, insert into // req->dev = bh->b_dev; req->cmd = rw; req->errors = 0; req->buffer = bh->b_data; req->sector = bh->b_blocknr << 1; req->nr_sectors = 2; req->waiting = NULL; req->bh = bh; req->next = NULL; add_request(major + blk_dev, req); } // Low level read/write block void ll_rw_block(int rw, struct buffer_head *bh) { unsigned int major; if ((major = MAJOR(bh->b_dev)) >= NR_BLK_DEV || !(blk_dev[major].request_fn)) { printk("Trying to read nonexistent block_device\n"); return ; } make_request(major, rw, bh); } void blk_dev_init(void) { int i; for(i = 0; i < NR_REQUEST; i++) { req_tbl[i].dev = -1; req_tbl[i].next = NULL; } } ================================================ FILE: kernel/chr_drv/Makefile ================================================ include ../../Makefile.header OBJS = do_keyboard.o keyboard.o tty_io.o tty_queue.o vga_console.o \ tty_read.o LDFLAGS += -r CFLAGS += -I../../include .PHONY=clean run all all: chr_drv.a chr_drv.a: $(OBJS) @$(AR) rcs chr_drv.a $(OBJS) @sync .c.o: @$(CC) $(CFLAGS) \ -c -o $*.o $< clean: - rm -f $(OBJS) *.a ================================================ FILE: kernel/chr_drv/do_keyboard.c ================================================ #include #include #include #include #include #include #define RELEASE_CHAR(a) ((a) & 0x80) #define CAPS 0x3A #define LCTRL 0x1D #define LALT 0x38 #define LSHIFT 0x2A #define RCTRL #define RALT #define RSHIFT extern void keyboard_interrupt(); // 点代表非可见字符, 或者是"."本身 const char scancode_table[] = "..1234567890-=\b.qwertyuiop[]\n.asdfghjkl;\'`.\\zxcvbnm,./.*. .............7894561230......"; const char shift_scancode_table[] = "..!@#$%^&*()_+\b.QWERTYUIOP{}\n.ASDFGHJKL:\"~.|ZXCVBNM<>?.*. .............7894561230......"; // const char caps_scancode_table[] = "..1234567890-=\b.QWERTYUIOP[]\n.ASDFGHJKL;\'`.\\ZXCVBNM,./.*. .............7894561230......"; // This function is not supposed // to be here but just for convenience char toupper(char ch) { if (ch >= 'a' && ch <= 'z') { ch = (char)(ch - 'a' + 'A'); } return ch; } void do_keyboard_interrupt(short scancode) { // Define some flags static char caps = 0; static char lshift = 0; static char rshift = 0; static char lctrl = 0; static char rctrl = 0; static char lalt = 0; static char ralt = 0; static char cap_out = 0; char ch = ' '; if (RELEASE_CHAR(scancode)) { s_printk("[DEBUG] Keyboard Release 0x%x\n", scancode); // Check if Shift / Alt / Ctrl / Released if (scancode == (LCTRL | 0x80)) { lctrl = 0; } if (scancode == (LALT | 0x80)) { lalt = 0; } if (scancode == (LSHIFT | 0x80)) { lshift = 0; s_printk("LSHIFT[%d]\n", lshift); } return ; } if (!RELEASE_CHAR(scancode)) { if (scancode == LCTRL) { lctrl = 1; return ; } if (scancode == LALT) { lalt = 1; return ; } if (scancode == LSHIFT) { lshift = 1; return ; } if (scancode == CAPS) { caps = !caps; return ; } cap_out = caps?!(lshift || rshift):(lshift || rshift); if (lshift || rshift) { ch = shift_scancode_table[scancode]; if (cap_out) { ch = toupper(ch); } } else if (lctrl || rctrl) { // here we need ctrl escape } else if (lalt || ralt) { // here we do nothing :/ } else { ch = scancode_table[scancode]; if(cap_out) { ch = toupper(ch); } } s_printk("[DEBUG] Keyboard Press 0x%x[ %d ]\n", scancode, ch); // TODO: 使得 tty 和进程对应, 当前都是指向 tty_table[0] // push the parsed char into queue if(tty_push_q(&tty_table[0].read_q, ch)) { s_printk("[DEBUG] read queue full\n"); // here we need to sleep to wait queue // not full } copy_to_buffer(&tty_table[0]); #ifdef DEBUG tty_queue_stat(&tty_table[0].read_q); #endif } // Wakeup the buffer queue // 我们只有在收到 \n EOF 的时候才唤醒队列 // wake_up(&tty_table[0].buffer.wait_proc); return ; } ================================================ FILE: kernel/chr_drv/keyboard.s ================================================ # Very Simple Keyboard Program # The whole char_drv is rewritten for teaching purpose # Get keyboard interrupt then call the keyboard interrupt function .text .global keyboard_interrupt keyboard_interrupt: pushl %eax pushl %ebx pushl %ecx pushl %edx push %ds push %es movl $0x10, %eax mov %ax, %ds mov %ax, %es xor %al, %al # 读取扫描码 inb $0x60, %al push %ax # 发送 EOI 给8259 movb $0x20, %al outb %al, $0x20 call do_keyboard_interrupt pop %ax pop %es pop %ds popl %edx popl %ecx popl %ebx popl %eax iret ================================================ FILE: kernel/chr_drv/tty_io.c ================================================ #include #include #include #include #include #include #include #include #define DEBUG void con_init(); extern void con_write(struct tty_struct *tty); // 这里我们用 C99 的 dot initializer 初始化 struct tty_struct tty_table[] = { { .pgrp = 0, .write = NULL, .flags = 0 | TTY_ECHO, .read_q = { .head = 0, .tail = 0, }, .write_q = { .head = 0, .tail = 0, }, .buffer = { .head = 0, .tail = 0 } }, }; // struct tty_queue read_q; void tty_init(void) { tty_table[0].write = con_write; con_init(); } // copy_to_buffer handle the keyboard input // to tty echo char void copy_to_buffer(struct tty_struct *tty) { char ch; struct tty_queue *read_q= &tty->read_q; struct tty_queue *buffer= &tty->buffer; while(!tty_isempty_q(&tty->read_q)) { ch = tty_pop_q(read_q); // TODO Judge if it's a normal character switch(ch) { case '\b': // This is backspace char if (!tty_isempty_q(buffer)) { if(tty_queue_tail(buffer) == '\n') // \n 不能被清除掉 continue ; buffer->tail = (buffer->tail - 1) % TTY_BUF_SIZE; } else { continue; } break; case -1: // We do not process \n here, process it in tty_read case '\n': s_printk("Enter wake the tty read queue up!\n"); tty_push_q(buffer, ch); wake_up(&tty_table[0].buffer.wait_proc); break; // EOF default: if (!tty_isfull_q(buffer)) { tty_push_q(buffer, ch); } else { // here we need to sleep until the queue // is not full } break; } if (tty->flags | TTY_ECHO) { tty_push_q(&tty->write_q, ch); tty->write(tty); } } return ; } void tty_write(struct tty_struct* tty) { tty->write(tty); return ; } // 队列为空时,进程进入睡眠状态, 可被中断唤醒 // TODO: write sleep_if_empty void sleep_if_empty(struct tty_queue *q) { cli(); while(!current->signal && tty_isempty_q(q)) interruptible_sleep_on(&q->wait_proc); sti(); } static void sleep_if_full(struct tty_queue *q) { cli(); while(!current->signal && tty_isfull_q(q)) interruptible_sleep_on(&q->wait_proc); sti(); } // tty_read 函数从 tty->buffer 中读取内容 // 当没有读到足够的字符且 buffer 为空的时候 // sleep int tty_read(int channel, char *buf, int nr) { int len = 0; char ch; char *p = buf; char tmpbuf[TTY_BUF_SIZE]; int tmpbuf_len = 0; #ifdef DEBUG s_printk("[TTY] buf addr = 0x%x\n", buf); #endif // TODO make channel adjustable channel = 0; if (channel > 2 || channel < 0 || nr < 0) return -1; struct tty_struct *tty = tty_table + channel; // Sleep until the queue is not empty // Only we get an \n, we start to process // interruptible_sleep_on(&tty->buffer.wait_proc); // // outer loop for reading lines while (1) { // If the queue is empty and we haven't finish reading // Then we sleep, only enter can wake up the sleep // sleep_if_empty(&tty->buffer); // If enter triggered, we start to read then back while(1) { ch = tty_pop_q(&tty->buffer); #ifdef DEBUG if(tty_isempty_q(&tty->buffer)) { s_printk("Empty!"); } tty_queue_stat(&tty->buffer); #endif if (nr <= 0) { // get the char then put it back tmpbuf[tmpbuf_len++] = ch; if (ch == '\n' || ch == -1) { for (int i = tmpbuf_len - 1; i >= 0; i--) { tty_push_q_front(&tty->buffer, tmpbuf[i]); } break; // revert the queue and stop the cycle } // put the char in tmp queue and continue continue; } // TODO: Change -1 to EOF // We also keep the \n if (ch == '\n' || ch == -1) { // TODO: Figure out why put_fs_byte don't work put_fs_byte(ch, p++); // *p++ = ch; len++; nr--; break; } put_fs_byte(ch, p++); // *p++ = ch; len++; nr--; } #ifdef DEBUG s_printk("Buf = %s\n", buf); #endif if (nr <= 0) { break; } } return len; } int _user_tty_write(int channel, char *buf, int nr) { int i = 0; // TODO make channel adjustable channel = 0; struct tty_struct *tty = tty_table + channel; for (i = 0; i < nr; i++) { char c = get_fs_byte(buf + i); tty_push_q(&tty->write_q, c); } tty_write(tty); // TODO return len return nr; } ================================================ FILE: kernel/chr_drv/tty_io.c.orig ================================================ #include #include #include #include #include #include #include #include #define DEBUG void con_init(); extern void con_write(struct tty_struct *tty); // 这里我们用 C99 的 dot initializer 初始化 struct tty_struct tty_table[] = { { .pgrp = 0, .write = NULL, .flags = 0 | TTY_ECHO, .read_q = { .head = 0, .tail = 0, }, .write_q = { .head = 0, .tail = 0, }, .buffer = { .head = 0, .tail = 0 } }, }; // struct tty_queue read_q; void tty_init(void) { tty_table[0].write = con_write; con_init(); } // copy_to_buffer handle the keyboard input // to tty echo char void copy_to_buffer(struct tty_struct *tty) { char ch; struct tty_queue *read_q= &tty->read_q; struct tty_queue *buffer= &tty->buffer; while(!tty_isempty_q(&tty->read_q)) { ch = tty_pop_q(read_q); // Judge if it's a normal character // if (ch < 32) { // // This is control character // continue; // } switch(ch) { case '\b': // This is backspace char if (!tty_isempty_q(buffer)) { if(tty_queue_tail(buffer) == '\n') // \n 不能被清除掉 continue ; buffer->tail = (buffer->tail - 1) % TTY_BUF_SIZE; } else { continue; } break; case -1: case '\n': s_printk("Wakeup!\n"); wake_up(&tty_table[0].buffer.wait_proc); break; // EOF default: if (!tty_isfull_q(buffer)) { tty_push_q(buffer, ch); } else { // here we need to sleep until the queue // is not full } break; } if (tty->flags | TTY_ECHO) { tty_push_q(&tty->write_q, ch); tty->write(tty); } } return ; } void tty_write(struct tty_struct* tty) { tty->write(tty); return ; } // 队列为空时,进程进入睡眠状态, 可被中断唤醒 // static void sleep_if_empty(struct tty_queue *q) { // cli(); // while(!current->signal && tty_isempty_q(q)) // interruptible_sleep_on(&q->wait_proc); // sti(); // } // static void sleep_if_full(struct tty_queue *q) { // cli(); // while(!current->signal && tty_isfull_q(q)) // interruptible_sleep_on(&q->wait_proc); // sti(); // } // tty_read 函数从 tty->buffer 中读取内容 // 当没有读到足够的字符且 buffer 为空的时候 // sleep // nr = -1 means only stop at \n int tty_read(int channel, char *buf, int nr) { int len = 0; char ch; char *p = buf; // return from the tty_read when recv a \n or EOF // Sanity check #ifdef DEBUG s_printk("buf addr = 0x%x\n", buf); #endif if (channel > 2 || channel < 0 || nr < 0) return -1; struct tty_struct *tty = tty_table + channel; // Sleep until the queue is not empty // sleep_if_empty(&tty->buffer); // Only we get an \n, we start to process interruptible_sleep_on(&tty->buffer.wait_proc); while (nr > 0) { ch = tty_pop_q(&tty->buffer); // TODO: Change -1 to EOF if (ch == '\n' || ch == -1) { // \n Or EOF, we are done // Now we can just stop and pop all to buffer break; } /* if (ch == CTRL_INT) return -1; */ // TODO: Why put_fs_byte don't work here // put_fs_byte(ch, p); *p++ = ch; s_printk("%s\n", buf); len++; nr--; } // while (!done) { // sleep_if_empty(&tty->buffer); // for (i = tty->buffer.head; i != tty->buffer.tail; i = (i + 1) % TTY_BUF_SIZE) { // if (tty->buffer.buf[i] == '\n' || tty->buffer.buf[i] == -1) { // done = 1; // break; // } // } // } return len; } ================================================ FILE: kernel/chr_drv/tty_io.c.rej ================================================ --- kernel/chr_drv/tty_io.c +++ kernel/chr_drv/tty_io.c @@ -141,9 +141,7 @@ int tty_read(int channel, char *buf, int nr) { break; } /* if (ch == CTRL_INT) return -1; */ - put_fs_byte(ch, p); - *p++ = ch; - s_printk("%s\n", buf); + put_fs_byte(ch, p++); len++; nr--; } ================================================ FILE: kernel/chr_drv/tty_queue.c ================================================ #include #include #include #include // 定义对 tty_queue 的操作 // tty_queue 是一个循环队列,操作要注意这一点 int tty_isempty_q(const struct tty_queue *q) { if (q->head == q->tail) return 1; return 0; } int tty_isfull_q(const struct tty_queue *q) { if ((q->tail + 1) % TTY_BUF_SIZE == q->head) return 1; return 0; } // Here we should check the queue, if emtpy sleep char tty_pop_q(struct tty_queue *q) { sleep_if_empty(q); char ch; ch = q->buf[q->head]; q->head = (q->head + 1) % TTY_BUF_SIZE; return ch; } // 队列满状态下返回 -1 int tty_push_q(struct tty_queue *q, char ch) { if (tty_isfull_q(q)) return -1; q->buf[q->tail] = ch; q->tail = (q->tail + 1) % TTY_BUF_SIZE; return 0; } int tty_push_q_front(struct tty_queue *q, char ch) { if (tty_isfull_q(q)) return -1; q->head = (q->head - 1) % TTY_BUF_SIZE; q->buf[q->head] = ch; return 0; } char tty_queue_head(const struct tty_queue *q) { return q->buf[q->head]; } char tty_queue_tail(const struct tty_queue *q) { return q->buf[q->tail]; } void tty_queue_stat(const struct tty_queue *q) { unsigned int i; s_printk("[DEBUG] Queue head = %d, tail = %d\n", q->head, q->tail); s_printk("[DEBUG] Queue: [ "); for (i = q->head; i != q->tail; i = (i + 1) % TTY_BUF_SIZE) { s_printk("%d<-", q->buf[i]); if(i == (q->tail - 1) % TTY_BUF_SIZE) { s_printk("#"); } } s_printk(" ]\n"); } ================================================ FILE: kernel/chr_drv/tty_read.c ================================================ #define __LIBRARY__ #include #include // Define user_tty_read syscall for test _syscall3(int, user_tty_read, int, channel, char *, buf, int, nr) _syscall3(int, user_tty_write, int, channel, char *, buf, int, nr) ================================================ FILE: kernel/chr_drv/vga_console.c ================================================ /* * console low level implementation */ #include #include #include #include #include #include #include #include #include #define PAGE_SIZE 4096 #define VIDEO_MEM 0xB8000 #define VIDEO_X_SZ 80 #define VIDEO_Y_SZ 25 #define TAB_LEN 8 #define CALC_MEM(x, y) (2*((x) + 80*(y))) extern void keyboard_interrupt(char scancode); int video_x, video_y; long user_stack[PAGE_SIZE>>2]; struct video_info { unsigned int retval; // Return value unsigned int colormode; // Color bits unsigned int feature; // Feature settings }; extern int video_x; extern int video_y; char *video_buffer = (char *)VIDEO_MEM; void video_init() { // struct video_info *info = (struct video_info *)0x9000; video_x = 0; video_y = 0; video_clear(); update_cursor(video_y, video_x); } int video_getx() { return video_x; } int video_gety() { return video_y; } void update_cursor(int row, int col) { unsigned int pos = ((unsigned int)row * VIDEO_X_SZ) + (unsigned int)col; // LOW Cursor port to VGA Index Register outb(0x3D4, 0x0F); outb(0x3D5, (unsigned char)(pos & 0xFF)); // High Cursor port to VGA Index Register outb(0x3D4, 0x0E); outb(0x3D5, (unsigned char)((pos >> 8) & 0xFF)); return ; } int get_cursor() { int offset; outb(0x3D4,0xF); offset=inb(0x3D5)<<8; outb(0x3D4,0xE); offset+=inb(0x3D5); return offset; } void video_putchar(char ch) { if(ch == '\n') { video_x = 0; video_y++; } else if(ch == '\t') { while(video_x % TAB_LEN) video_x++; } else if(ch == '\b') { video_x--; if (video_x < 0) { video_x = VIDEO_X_SZ; video_y--; if (video_y < 0) video_y = 0; } // erase char video_putchar_at(' ', video_x, video_y, 0x0F); } else { video_putchar_at(ch, video_x, video_y, 0x0F); video_x++; } if(video_x >= VIDEO_X_SZ) { video_x = 0; video_y++; } if(video_y >= VIDEO_Y_SZ) { roll_screen(); video_x = 0; video_y = VIDEO_Y_SZ - 1; } update_cursor(video_y, video_x); return ; } void con_write(struct tty_struct *tty) { char ch; s_printk("Console write called\n"); while (!tty_isempty_q(&tty->write_q)) { // tty_queue_stat(&tty->write_q); ch = tty_pop_q(&tty->write_q); video_putchar(ch); } s_printk("Console write returned\n"); return ; } void video_clear() { int i; int j; video_x = 0; video_y = 0; for(i = 0; i < VIDEO_X_SZ; i++) { for(j = 0; j < VIDEO_Y_SZ; j++) { video_putchar_at(' ', i, j, 0x0F); // DO NOT USE 0x00 HERE, YOU WILL LOSE YOUR LOVELY BLINKING CURSOR( } } return ; } void video_putchar_at(char ch, int x, int y, char attr) { if(x >= 80) x = 80; if(y >= 25) y = 25; *(video_buffer + 2*(x+80*y)) = ch; // You should write it correct, think carefully *(video_buffer + 2*(x+80*y)+1) = attr; // Previous code : (video_buffer + 2*x + 80*y) (suck) return ; } void roll_screen() { int i; // Copy line A + 1 to line A for(i = 1; i < VIDEO_Y_SZ; i++) { memcpy(video_buffer + (i - 1) * 80 * 2, video_buffer + i * 80 * 2, VIDEO_X_SZ, 2*sizeof(char)); } // Clear the last line for(i = 0; i < VIDEO_X_SZ; i++) { video_putchar_at(' ', i, VIDEO_Y_SZ - 1, 0x0F); } return ; } void memcpy(char *dest, char *src, int count, int size) { int i; int j; for(i = 0; i < count; i++) { for(j = 0; j < size; j++) { *(dest + i*size + j) = *(src + i*size + j); } } return ; } void con_init(void) { register unsigned char a; set_trap_gate(0x21, &keyboard_interrupt); video_init(); outb_p(0x21, inb_p(0x21)&0xfd); a = inb_p(0x61); outb_p(0x61, a | 0x80); outb(0x61, a); } ================================================ FILE: kernel/exit.c ================================================ #include #include #include #include #include #include #include #include static inline int send_sig(long sig, struct task_struct* p, int priv); static void tell_father(int pid); // sys_kill 为系统调用 kill 的入口点 // TODO: 增加权限判断逻辑 sys.c int sys_kill(int pid, int sig) { struct task_struct **p = task + NR_TASKS; int err, retval = 0; s_printk("sys_kill entered\n"); // 目前没有进程组的概念,因而我们只处理 pid > 0 的情况 if (pid > 0) { while(--p > &FIRST_TASK) { if (*p && (*p)->pid == pid) { // 因为我们没有实现sys.c相关的逻辑,这里暂时给予执行kill的用户最高权限 if ((err = send_sig(sig, *p, 1))) { s_printk("send_sig error err = %d\n", err); retval = err; } } } } s_printk("sys_kill leaved, retval = %d\n", retval); return retval; } // send_sig 用来向进程发送信号 // TODO: 增加权限判断逻辑 sys.c static inline int send_sig(long sig, struct task_struct* p, int priv) { // s_printk("send_sig entered\n"); // First check params if (!p || sig < 1 || sig > 32) return -EINVAL; if(priv || current->euid == p->euid /*|| suser() */) // 目前我们没有对用户的权限检查 p->signal |= (1<<(sig - 1)); else return -EPERM; return 0; } // Use to release the task void release(struct task_struct *p) { int i; if(!p) { return; } for(i = 0; i < NR_TASKS; i++) { if(task[i] == p) { task[i] = NULL; free_page((unsigned long) p); schedule(); return; } } panic("Trying to release non-existent task"); } // Do exit 将进程置成 TASK_ZOMBIE 状态 // 并做收尾工作,不再返回到此内部 int do_exit(long code) { int i; // 释放当前进程代码段和数据(fs,也是用户堆栈)段的内存页 free_page_tables(get_base(current->ldt[1]), get_limit(0x0f)); free_page_tables(get_base(current->ldt[2]), get_limit(0x17)); for(i = 0; i < NR_TASKS; i++) { if(task[i] && task[i]->father == current->pid) { task[i]->father = 1; if(task[i]->state == TASK_ZOMBIE) (void) send_sig(SIGCHLD, task[1], 1); // TODO: Why sending SIGCHLD to init will make init terminate the child process? } } // TODO: 释放 TTY 资源 if(last_task_used_math == current) { last_task_used_math = NULL; } if(current->leader) { // TODO: 释放会话中全部进程 } current->state = TASK_ZOMBIE; current->exit_code = code; tell_father(current->father); schedule(); // Code will never come here // just use to suppress warning return -1; } // 左移八位是 wait, waitpid 所需, 用低位存 wait() 的状态信息 pid_t sys_exit(int error_code) { return do_exit((error_code & 0xff) << 8); } // 用来挂起当前进程,直到等待的 pid 退出或者收到要终止该进程的信号 // 如果进程是僵尸进程,则此系统调用立即返回,子进程使用的所有资源释放 pid_t sys_waitpid(pid_t pid, unsigned long stat_addr, int options) { int flag, code; struct task_struct **p; verify_area((void *)stat_addr, 4); repeat: flag = 0; for (p = &LAST_TASK; p > &FIRST_TASK; --p) { // 不能让自己挂起,这样会没有人能够唤醒 if (*p || *p == current) { continue; } if ((*p)->father != current->pid) { continue; } // 检查是否是要等待的 pid if (pid > 0) { if ((*p)->pid != pid) { continue; } } // pid = 0 表示等待进程组全部子进程 if (!pid) { if ((*p)->pgrp != current->pgrp) { continue; } } // pid < 0 则等待所有进程组号 = abs(pid) 的进程 if (pid != -1) { if((*p)->pgrp != -pid) { continue; } } switch ((*p)->state) { case TASK_STOPPED: // WUNTRACED 置位,立即返回 if(!(options & WUNTRACED)) { continue; } // 写入用户数据段 put_fs_long(0x7f, (unsigned long *)stat_addr); return (*p)->pid; case TASK_ZOMBIE: current->cutime += (*p)->utime; current->cstime += (*p)->stime; flag = (*p)->pid; code = (*p)->exit_code; release(*p); put_fs_long((unsigned long)code, (unsigned long *)stat_addr); return flag; default: flag = 1; continue; } } // 如果 flag 被置位,则说明我们找到了需要等待的进程, // 且进程不是退出或者僵死状态 if (flag) { if (options & WNOHANG) { return 0; } current->state = TASK_INTERRUPTIBLE; schedule(); if(!(current->signal & ~(1<<(SIGCHLD-1)))) { goto repeat; } return -EINTR; } // 没有找到适合的子进程 return -ECHILD; } // 通知父进程,向父进程发送 SIGCHLD 信号 static void tell_father(int pid) { int i; for (i = 0; i < NR_TASKS; i++) { if (!task[i]) { continue; } if(task[i]->pid != pid) { continue; } task[i]->signal |= (1<<(SIGCHLD-1)); return ; } s_printk("[DEBUG] NO FATHER!!!!\n"); release(current); } ================================================ FILE: kernel/fork.c ================================================ //#include 当前所有错误号为stub状态 #include #include #include //#include #include extern void write_verify(unsigned long address); long last_pid = 0; // 对齐内存区域,并且对区域进行验证,区域以4096Byte // 为单位, 进行write_verify(因为当时的CPU还不支持WP位) void verify_area(void *addr, unsigned int size) { unsigned long start; start = (unsigned long)addr; size += start & 0xfff; // size align start &= 0xfffff000; // start align at 4096Byte start += get_base(current->ldt[2]); while((int)size>0) { size -= 4096; write_verify(start); start += 4096; } } int copy_mem(int nr, struct task_struct *p) { unsigned long old_data_base, new_data_base, data_limit; unsigned long old_code_base, new_code_base, code_limit; code_limit = get_limit(0x0f); data_limit = get_limit(0x17); old_code_base = get_base(current->ldt[1]); old_data_base = get_base(current->ldt[2]); if(old_code_base != old_data_base) // This should never happen panic("Codeseg not fully overlapped with dataseg"); if(data_limit < code_limit) panic("bad data limit"); new_data_base = new_code_base = (unsigned int)nr * 0x4000000; // 64MB per nr p->start_code = new_code_base; set_base(p->ldt[1], new_code_base); set_base(p->ldt[2], new_data_base); if(copy_page_tables(old_data_base, new_data_base, data_limit)) { printk("free_page_tables: from copy_mem\n"); free_page_tables(new_data_base, data_limit); return -1; } return 0; } // 这里拷贝PCB,以及代码段数据段, 首先我们需要知道,C 语言中参数的获取顺序 // 在函数参数列表中的参数,顺序是这样的,参数列表最后面的参数,对应 // 在汇编中最先压栈的参数(就是离栈顶最远的参数) // 然后这里的参数涉及到很多,解释一下为什么会有这些参数 // 首先 CPU 进入中断调用,压栈SS, ESP, 标志寄存器 EFLAGS 和返回地址CS:EIP // 然后是system_call里压栈的寄存器 ds, es, fs, edx, ecx, ebx // 然后是调用sys_fork函数时,压入的函数返回地址 // 以及sys_fork里压栈的那些参数 gs, esi, edi, ebp, eax(nr) int copy_process(int nr, long ebp, long edi, long esi, long gs, long none, long ebx, long ecx, long edx, long fs, long es, long ds, long eip, long cs, long eflags, long esp, long ss) { struct task_struct *p; int i; /* Only for emit the warning! */ i = none; none = i; //i = eflags; //eflags = i; /* End */ p = (struct task_struct *) get_free_page(); if(!p) return -1; task[nr] = p; *p = *current; // 这里仅仅复制PCB(task_struct) // 初始化 PCB, TSS p->state = TASK_UNINTERRUPTIBLE; p->pid = last_pid; p->father = current->pid; p->counter = p->priority; p->signal = 0; p->alarm = 0; p->leader = 0; p->utime = p->stime = 0; p->cutime = p->cstime = 0; p->start_time = jiffies; p->tss.back_link = 0; p->tss.eflags = eflags; p->tss.esp0 = PAGE_SIZE + (long)p; p->tss.ss0 = 0x10; p->tss.eip = eip; p->tss.eax = 0; // 这是当fork返回的时候,子进程返回0的原因 p->tss.ecx = ecx; p->tss.ebx = ebx; p->tss.edx = edx; p->tss.esp = esp; p->tss.ebp = ebp; p->tss.esi = esi; p->tss.edi = edi; p->tss.es = es & 0xffff; p->tss.cs = cs & 0xffff; p->tss.ds = ds & 0xffff; p->tss.ss = ss & 0xffff; p->tss.fs = fs & 0xffff; p->tss.gs = gs & 0xffff; p->tss.ldt = _LDT(nr); p->tss.bitmap = 0x80000000; // 如果之前的进程使用了协处理器,这里复位TS标志 if(last_task_used_math == current) __asm__ ("clts; fnsave %0"::"m" (p->tss.i387)); // 复制进程页表 if(copy_mem(nr, p)) { task[nr] = NULL; free_page((unsigned long)p); return -1; } // 设置好TSS和LDT描述符,然后将任务置位 // 就绪状态,可以被OS调度 set_tss_desc(gdt + (nr<<1) + FIRST_TSS_ENTRY, &(p->tss)); set_ldt_desc(gdt + (nr<<1) + FIRST_LDT_ENTRY, &(p->ldt)); p->state = TASK_RUNNING; return last_pid; // 父进程返回儿子的pid } int find_empty_process(void) { int i; long tmp = last_pid; // 记录最初起始进程号,用于标记循环结束 while(1) { for(i = 0; i < NR_TASKS; i++) { if(task[i] && task[i]->pid == last_pid) break; } if(i == NR_TASKS) break; if((++last_pid) == tmp) break; // 判断last_pid 是否超出进程号数据表示范围,如果超出 // 则置为1 if(last_pid < 0) last_pid = 1; } for(i = 1; i < NR_TASKS; i++) if(!task[i]) return i; return -1; } ================================================ FILE: kernel/libc_restore.s ================================================ .global __sig_restore, __masksig_restore __sig_restore: # Use to restore to user routine addl $4, %esp popl %eax popl %ecx popl %edx popfl ret __masksig_restore: addl $4, %esp # We need a call here to set mask addl $4, %esp popl %eax popl %ecx popl %edx popfl ret ================================================ FILE: kernel/panic.c ================================================ /* * Use for trigger a kernel panic * When panic, it will print out a message and then * dead, use for alerting a major problem */ #define PANIC #include void panic(const char *str) { printk("Kernel Panic: %s\n", str); for(;;); } ================================================ FILE: kernel/printk.c ================================================ /* * Rewrite printk * now printk call for sprintf * then call tty_write * (user mode will call write(x, x, x) */ #include #include #include #include void vsprintf(char *dest, char *fmt, ...); void printk(char *fmt, ...) { int i = 0; char buf[TTY_BUF_SIZE] = ""; char *ptr = buf; va_list ap; for(i = 0; i < TTY_BUF_SIZE; i++) { buf[i] = 0; } s_printk("[DEBUG] printk called, fmt = %s", fmt); va_start(ap, fmt); vsprintf(buf, fmt, ap); va_end(ap); while(*ptr) { tty_push_q(&tty_table[0].write_q, *ptr); tty_queue_stat(&tty_table[0].write_q); ptr++; } tty_write(&tty_table[0]); } ================================================ FILE: kernel/sched.c ================================================ #include #include #include #include #include #include #include // #define DEBUG #define _S(nr) (1<<((nr)-1)) #define _BLOCKABLE (~(_S(SIGKILL) | _S(SIGSTOP))) extern int timer_interrupt(void); extern int system_call(void); union task_union { struct task_struct task; char stack[PAGE_SIZE]; }; static union task_union init_task = {INIT_TASK,}; long user_stack[PAGE_SIZE >> 2]; long startup_time; struct task_struct *current = &(init_task.task); struct task_struct *last_task_used_math = NULL; struct task_struct *task[NR_TASKS] = {&(init_task.task), }; struct { long *a; short b; } stack_start = {&user_stack[PAGE_SIZE >> 2], 0x10}; void sleep_on(struct task_struct **p) { struct task_struct *tmp; if(!p) return; if(current == &(init_task.task)) panic("task[0] trying to sleep"); tmp = *p; *p = current; current->state = TASK_UNINTERRUPTIBLE; schedule(); *p = tmp; if (tmp) tmp->state = TASK_RUNNING; } void schedule(void) { int i, next, c; struct task_struct **p; // 先处理信号 for (p = &LAST_TASK; p > &FIRST_TASK; --p) { if(*p) { // 如果 进程 p 有 alarm 而且目前时间已经超过 alarm // 对进程 p 发送 SIGALARM 信号 if((*p)->alarm && (*p)->alarm < jiffies) { (*p)->signal |= (1<<(SIGALRM-1)); #ifdef DEBUG s_printk("[DEBUG] process get SIGALRM signal: 0x%x mask: 0x%x\n blockable= 0x%x", \ (*p)->signal, (*p)->blocked, _BLOCKABLE); #endif (*p)->alarm = 0; } // 如果进程收到了信号,那么就对进程进行唤醒 if(((unsigned long)((*p)->signal) & (unsigned long)(((unsigned long)(_BLOCKABLE) & (~(*p)->blocked)))) \ && (unsigned long)((*p)->state) \ == TASK_INTERRUPTIBLE) { (*p)->state = TASK_RUNNING; } } } while(1) { // 初始化,i, p指向任务链表的末尾 c = -1; next = 0; i = NR_TASKS; p = &task[NR_TASKS]; while(--i) { // 跳过无效任务(空) if(!*(--p)) continue; if((*p)->state == TASK_RUNNING && (*p)->counter > c) { c = (*p)->counter; next = i; } } // 如果循环之后,系统中的任务只有counter > 0的或者没有任何可以 // 运行的任务,那么就退出循环并进行任务切换 if(c) break; for( p = &LAST_TASK; p > &FIRST_TASK; p--) { if(*p) { (*p)->counter = ((*p)->counter >> 1) + (*p)->priority; } } } // switch_to 接收参数为一个 Task 号 // show_task_info(task[next]); // printk("[%x] Scheduler select task %d\n", jiffies, next); // printk("[%x] Scheduler select task\n", next); #ifdef DEBUG s_printk("[DEBUG] [%x] Scheduler select task %d\n", jiffies, next); // THE 'next' VALUE IS NOT CORRECT! s_printk("[DEBUG] Scheduler select task %d\n", next); // THIS CAUSE CRASH #endif // TODO Fix the bug // These two lines of code will cause OS Crash // When switch to printk it won't // Why? switch_to(next) } void show_task_info(struct task_struct *task) { s_printk("Current task Info\n================\n"); s_printk("pid = %d\n", task->state); s_printk("counter = %d\n", task->counter); s_printk("start_code = %x\n", task->start_code); s_printk("end_code = %x\n", task->end_code); s_printk("brk = %x\n", current->ldt[0]); s_printk("gid = 0x%x\n", current->gid); s_printk("tss.ldt = 0x%x\n", current->tss.ldt); // s_printk("tss.eip = 0x%x\n", current->eip); } void wake_up(struct task_struct **p) { if(p && *p) { (*p)->state = TASK_RUNNING; *p = NULL; } } // 假设我们执行任务A的时候调用了这个函数 void interruptible_sleep_on(struct task_struct **p) { struct task_struct *tmp; if(!p) return; if(current == &(init_task.task)) // 我们不能让 init sleep panic("task[0] trying to sleep"); tmp = *p; *p = current; rep_label: current->state = TASK_INTERRUPTIBLE; // 这里会转到其他任务去执行 schedule(); // 回来的时候说明,调度程序调度到了这里,current = B // 我们要检查队列,如果 *p (这里应该是A) 和 当前运行任务不等 // 如果不是的话 if(*p && *p != current) { (*p)->state = TASK_RUNNING; goto rep_label; } *p = tmp; if(tmp) tmp->state = TASK_RUNNING; } int sys_pause(void) { current->state = TASK_INTERRUPTIBLE; schedule(); return 0; } int sys_alarm(long seconds) { int old = current->alarm; if (old) { old = (old - jiffies) / HZ; } current->alarm = (seconds > 0)?jiffies + HZ * seconds:0; return (old); } // 计时器函数 do_timer, 记录程序运行时间,如果处于 CPL = 2 的进程执行时间超过时间片 // 则进行调度,否则继续。 // 下面的这段代码是一个demo,用来验证时钟中断 // 已经设置好并且可用, 目前不支持其他定时器 // floppy操作依旧不支持 int counter = 0; long volatile jiffies = 0; void do_timer(long cpl) { if (!cpl) current->stime++; else current->utime++; if((--current->counter) > 0) return ; current->counter = 0; if(!cpl) return; schedule(); } // 这是一个临时函数,用于初始化8253计时器 // 并开启时钟中断 void sched_init() { int divisor = 1193180/HZ; int i; struct desc_struct *p; // 初始化任务0的TSS, LDT set_tss_desc(gdt + FIRST_TSS_ENTRY, &(init_task.task.tss)); set_ldt_desc(gdt + FIRST_LDT_ENTRY, &(init_task.task.ldt)); // 初始化其余各项,因为TSS LDT各占一个,所以 // +2 指向任务1的TSS, LDT(初始任务为0) // 把其余各项设置为0 p = gdt + 2 + FIRST_TSS_ENTRY; for(i = 1; i < NR_TASKS; i++) { task[i] = NULL; p->a = p->b = 0; p++; p->a = p->b = 0; p++; } // Clear Nested Task(NT) Flag __asm__("pushfl; andl $0xffffbfff, (%esp); popfl"); ltr(0); lldt(0); outb_p(0x43, 0x36); outb_p(0x40, divisor & 0xFF); outb_p(0x40, divisor >> 8); // timer interrupt gate setup: INT 0x20 set_intr_gate(0x20, &timer_interrupt); // Make 8259 accept timer interrupt outb(0x21, inb_p(0x21) & ~0x01); // 初始化 system_call set_system_gate(0x80, &system_call); } #undef DEBUG ================================================ FILE: kernel/serial_debug.c ================================================ #include #include #define PORT 0x3f8 int is_transmit_empty() { return inb(PORT + 5) & 0x20; } void s_putchar(char a) { while (is_transmit_empty() == 0); outb(PORT,a); } void s_puts(char *a) { while(*a) { s_putchar(*a++); } } int serial_debugstr(char *str) { s_puts(str); return 0; } void s_printnum(int num, int base, int sign) { char digits[] = "0123456789ABCDEF"; char buf[50] = ""; int cnt = 0; int i; if(sign && num < 0) { // Check for sign or unsign s_putchar('-'); num = -num; } if(num == 0) { s_putchar('0'); return ; } while(num) { buf[cnt++] = digits[num % base]; num = num / base; } for(i = cnt - 1; i >=0; i--) { s_putchar(buf[i]); } return ; } void s_printk(char *fmt, ...) { va_list ap; va_start(ap, fmt); char c, *s; while(*fmt) { c = *fmt++; if(c != '%') { s_putchar(c); continue; } c = *fmt++; if(c == '\0') break; switch(c) { case 'd': s_printnum(va_arg(ap, int), 10, 1); break; case 'u': s_printnum(va_arg(ap, int), 10, 0); break; case 'x': s_printnum(va_arg(ap, int), 16, 0); break; case 's': s = va_arg(ap, char*); while(*s) s_putchar(*s++); break; case 'c': s_putchar(va_arg(ap, char)); break; case '%': s_putchar('%'); } } return; } ================================================ FILE: kernel/signal.c ================================================ #include #include #include #include #include #include #define DEBUG // do_signal 函数调用之前内核栈的参数分布,见文档 void dump_sigaction(struct sigaction *action) { s_printk("[DEBUG] Sigaction dump\n"); s_printk("[DEBUG] addr = 0x%x, sa_mask = 0x%x, sa_handler = 0x%x, sa_restorer = 0x%x\n", action, action->sa_mask, action->sa_handler, action->sa_restorer); } void do_signal(long signr, long eax, long ebx, long ecx, long edx, long fs, long es, long ds, unsigned long eip, long cs, long eflags, unsigned long *esp, long ss) { #ifdef DEBUG s_printk("[DEBUG] Signal = %d\n", signr); s_printk("[DEBUG] Context signr = %d, eax = 0x%x, ebx = 0x%x, ecx = 0x%x\n \ edx = 0x%x, fs = 0x%x, es = 0x%x, ds = 0x%x\n \ eip = 0x%x, cs = 0x%x, eflags = 0x%x, esp = 0x%x, ss= 0x%x\n", signr, eax, ebx, ecx, edx, fs, es, ds, eip, cs, eflags ,esp, ss); s_printk("[DEBUG] current pid = %d\n", current->pid); dump_sigaction(¤t->sigaction[signr - 1]); #endif unsigned long sa_handler; unsigned long old_eip = eip; struct sigaction *sa = current->sigaction + signr - 1; unsigned int longs; unsigned long *tmp_esp; sa_handler = (unsigned long)sa->sa_handler; // IGNORE HANDLER if (sa_handler == 1) { return ; } // DEFAULT HANDLER if (!sa_handler) { return ; // if(signr == SIGCHLD) // return; // else // sys_debug("Default signal handler"); } // User registered signal handler, then process if ((sa->sa_flags & SA_ONESHOT)) { sa->sa_handler = NULL; } // Make EIP in the stack pointed to handler function *(&eip) = sa_handler; // Check wether we need to push sigmask longs = (sa->sa_flags & SA_NOMASK)?7:8; // Grow the stack *(&esp) -= longs; verify_area((void *)esp, longs * 4); tmp_esp = (unsigned long *)esp; put_fs_long((unsigned long)sa->sa_restorer, tmp_esp++); put_fs_long((unsigned long)signr, tmp_esp++); if (!(sa->sa_flags & SA_NOMASK)) put_fs_long((unsigned long)current->blocked, tmp_esp++); put_fs_long((unsigned long)eax, tmp_esp++); put_fs_long((unsigned long)ecx, tmp_esp++); put_fs_long((unsigned long)edx, tmp_esp++); put_fs_long((unsigned long)eflags, tmp_esp++); put_fs_long((unsigned long)old_eip, tmp_esp++); current->blocked |= sa->sa_mask; } int sys_sgetmask() { return (int)current->blocked; } int sys_ssetmask(int newmask) { int old; old = (int)current->blocked; // We cannot mask SIGKILL current->blocked = (unsigned long)(newmask & ~(1<<(SIGKILL-1))); return old; } // Save old first verify the area has enough space // and correct permission static inline void save_old(char *from, char *to) { unsigned int i; verify_area((void *)to, sizeof(struct sigaction)); for (i = 0; i < sizeof(struct sigaction); i++) { put_fs_byte(*from, to); from++; to++; } } static inline void get_new(char *from, char *to) { unsigned int i; for (i = 0; i < sizeof(struct sigaction); i++) { *to = get_fs_byte(from); from++; to++; } } // set signal handler in old styled POSIX way int sys_signal(int signum, long handler, long restorer) { struct sigaction tmp; // Validate the signal if (signum < 1 || signum > 32 || signum == SIGKILL) { return -1; } tmp.sa_handler = (void (*)(int))handler; tmp.sa_mask = 0; tmp.sa_flags = (int)(SA_ONESHOT | SA_NOMASK); tmp.sa_restorer = (void (*)(void))restorer; handler = (long) current->sigaction[signum-1].sa_handler; current->sigaction[signum - 1] = tmp; return handler; } int sys_sigaction(int signum, const struct sigaction* action, struct sigaction *old_action) { struct sigaction tmp; #ifdef DEBUG s_printk("[DEBUG] sys_sigaction entered, signum = %d\n", signum); #endif if (signum < 1 || signum > 32 || signum == SIGKILL) return -1; tmp = current->sigaction[signum - 1]; #ifdef DEBUG s_printk("[DEBUG] sigaction MARK %d\n", __LINE__); #endif get_new((char *)action, (char *)&(current->sigaction[signum - 1])); if (old_action) save_old((char *) &tmp, (char *)old_action); if (current->sigaction[signum - 1].sa_flags & SA_NOMASK) current->sigaction[signum - 1].sa_mask = 0; else current->sigaction[signum - 1].sa_mask |= (sigset_t)(1<<(signum - 1)); #ifdef DEBUG s_printk("[DEBUG] current pid = %d\n", current->pid); dump_sigaction(¤t->sigaction[signum-1]); #endif return 0; } #undef DEBUG ================================================ FILE: kernel/signal_demo.c ================================================ #define __LIBRARY__ #include #include #include #include #include // First we define kill system call _syscall2(int, kill, int, pid, int, sig) static inline _syscall1(int, sys_debug, char *, str) static inline _syscall3(int, sigaction, int, signum, struct sigaction *, action, struct sigaction *, old_action) extern void __sig_restore(); extern void __masksig_restore(); // A function use to demo signal usage // static void demo_handle(int sig) { sys_debug("Demo handler activate!\n"); sig++; } void signal_demo_main(void) { int ret = 0; struct sigaction sa_action; sa_action.sa_handler = demo_handle; // sa_action.sa_flags |= SA_NOMASK; // TODO: Add mask sa_action.sa_mask = 0; sa_action.sa_restorer = __sig_restore; ret = sigaction(SIGUSR1, &sa_action, NULL); if (ret) { panic("sigaction set failed"); } kill(1, SIGUSR1); return; } ================================================ FILE: kernel/sys.c ================================================ #include #include #include #include // #define DEBUG extern int sys_alarm(long seconds); int stub_syscall(void) { return 0; } // Here we implement syscall for sleep int sys_sleep(long seconds) { #ifdef DEBUG s_printk("[DEBUG] syscall entered, sleeping\n"); #endif sys_alarm(seconds); current->state = TASK_INTERRUPTIBLE; schedule(); // when sleep wakeup, we clear the alarm current->signal = current->signal & (~(1<<(SIGALRM-1))); return 0; } ================================================ FILE: kernel/system_call.s ================================================ # system_call.s 里面实现了系统调用的过程 # 实现了时钟中断和fork 必要的系统调用 # 还没有对信号进行处理 .global timer_interrupt, system_call, sys_fork # 我们来定义一下栈的布局 EAX = 0x00 EBX = 0x04 ECX = 0x08 EDX = 0x0C FS = 0x10 ES = 0x14 DS = 0x18 EIP = 0x1C CS = 0x20 EFLAGS = 0x24 OLDESP = 0x28 OLDSS = 0x2C nr_system_calls = 72+1 #sys_debug # offset in task_struct state = 0 counter = 4 priority = 8 signal = 12 sigaction = 16 blocked = (33*16) .align 2 bad_sys_call: movl $-1, %eax iret .align 2 reschedule: pushl $ret_from_syscall jmp schedule .align 2 system_call: cmp $nr_system_calls - 1, %eax ja bad_sys_call push %ds push %es push %fs push %edx push %ecx push %ebx movl $0x10, %edx mov %dx, %ds mov %dx, %es movl $0x17, %edx mov %dx, %fs call *sys_call_table(, %eax, 4) pushl %eax movl current,%eax # 这里对task_struct进行判断,如果current->state != TASK_RUNNING, 则需要进行重新调度 cmp $0, state(%eax) jne reschedule cmp $0, counter(%eax) # 如果时间片用光,也需要重新调度 je reschedule ret_from_syscall: # 这里是信号处理部分 movl current, %eax # task 0 不处理信号 cmpl task, %eax je 3f cmpw $0x0f, CS(%esp) # 如果二者不等,那么说明是通过中断跳转过来的,不处理信号 jne 3f # cmpw $0x17, OLDSS(%esp) # 如果二者不等,那么说明堆栈段非用户态堆栈,说明系统调用不是用户态发起的,也不处理 jne 3f movl signal(%eax), %ebx movl blocked(%eax), %ecx notl %ecx andl %ebx, %ecx # 对屏蔽信号和信号set进行运算,如果发现没有信号可以处理,就跳过 bsfl %ecx, %ecx je 3f btrl %ecx, %ebx # 现在ecx里存有第一个有效信号的位置,我们将此信号复位,用Bit Test Reset,将%ebx # 里相应位 reset 掉 movl %ebx, signal(%eax) incl %ecx # 将 offset 调整为从1开始的(1 - 32 信号表示) pushl %ecx # 将信号压栈 call do_signal popl %eax # 信号处理完毕 3: popl %eax popl %ebx popl %ecx popl %edx pop %fs pop %es pop %ds iret .align 2 timer_interrupt: push %ds push %es push %fs push %edx push %ecx push %ebx push %eax movl $0x10, %eax # 让DS指向内核数据段 mov %ax, %ds mov %ax, %es movl $0x17, %eax mov %ax, %fs incl jiffies movb $0x20, %al outb %al, $0x20 movl CS(%esp), %eax # 将 CS 取出,并 andl $3, %eax # 计算出 CPL pushl %eax call do_timer addl $4, %esp jmp ret_from_syscall .align 2 sys_fork: call find_empty_process testl %eax, %eax # 检查返回值是不是负值, 负值意味着没有获得到pid资源 js 1f push %gs pushl %esi pushl %edi pushl %ebp pushl %eax call copy_process addl $20, %esp 1: ret ================================================ FILE: kernel/traps.c ================================================ /* * Code to implement 80386 traps and interrupts * */ #include #include #include #include #include //void page_fault(void); // mm/page.s void page_fault(void); // 在kernel/asm.s中用到的函数原型 void divide_error(void); void debug(void); void nmi(void); void int3(void); void overflow(void); void bounds(void); void invalid_op(void); void device_not_available(void); void double_fault(void); void coprocessor_segment_overrun(void); void invalid_TSS(void); void segment_not_present(void); void stack_segment(void); void general_protection(void); void coprocessor_error(void); void irq13(void); void reserved(void); void parallel_interrupt(void); // 用来引发一个异常,因为目前我们没有实现 // 信号机制,且为单进程,所以die的处理就是打印完必要的信息 // 停机进入死循环 static void die(char *str, long esp_ptr, long nr) { long *esp = (long *)esp_ptr; printk("%s: %x\n", str, nr & 0xffff); printk("EIP: 0x%x:0x%x\n EFLAGS: 0x%x\n ESP 0x%x:0x%x\n", esp[1], esp[0], esp[2], esp[4], esp[3]); // Some Process Related code, now stub printk("base 0x%x, limit 0x%x\n", get_base(current->ldt[1]), get_limit(0x17)); printk("No Process now, System HALT! :(\n"); for(;;); return ; } void do_double_fault(long esp, long error_code) { die("double fault", esp, error_code); } void do_general_protection(long esp, long error_code) { die("general protection", esp, error_code); } void do_divide_error(long esp, long error_code) { die("divide error", esp, error_code); } void do_int3(long *esp, long error_code, long fs, long es, long ds, long ebp, long esi, long edi, long edx, long ecx, long ebx, long eax) { // Now we do not support Task Register int tr = 0; __asm__ volatile("str %%ax":"=a" (tr):"0"(0)); printk("eax\tebx\tecx\tedx\t\n%x\t%x\t%x\t%x\n",eax, ebx, ecx, edx); printk("esi\tedi\tebp\tesp\t\n%x\t%x\t%x\t%x\n",esi, edi, ebp, (long)esp); printk("ds\tes\tfs\ttr\n%x\t%x\t%x\t%x\n",ds, es, fs, tr); printk("EIP: %x CS:%x EFLAGS: %x", esp[0], esp[1], esp[2]); printk("errno = %d", error_code); return ; } void do_nmi(long esp, long error_code) { die("nmi", esp, error_code); } void do_debug(long esp, long error_code) { die("debug", esp, error_code); } void do_overflow(long esp, long error_code) { die("overflow", esp, error_code); } void do_bounds(long esp, long error_code) { die("bounds", esp, error_code); } void do_invalid_op(long esp, long error_code) { die("invalid operand", esp, error_code); } void do_device_not_available(long esp, long error_code) { die("device not available", esp, error_code); } void do_coprocessor_segment_overrun(long esp, long error_code) { die("coprocessor segment overrun", esp, error_code); } void do_invalid_TSS(long esp, long error_code) { die("invalid TSS", esp, error_code); } void do_segment_not_present(long esp, long error_code) { die("segment not present", esp, error_code); } void do_stack_segment(long esp, long error_code) { die("stack segment", esp, error_code); } void do_coprocessor_error(long esp, long error_code) { die("coprocessor error", esp, error_code); } void do_reserved(long esp, long error_code) { die("reserved(15, 17-47)error", esp, error_code); } void do_stub(long esp, long error_code) { printk("stub interrupt! %x, %x\n", esp, error_code); } void trap_init(void) { int i; set_trap_gate(0, ÷_error); set_trap_gate(1, &debug); set_trap_gate(2, &nmi); set_system_gate(3, &int3); set_system_gate(4, &overflow); set_system_gate(5, &bounds); set_trap_gate(6, &double_fault); set_trap_gate(7, &device_not_available); set_trap_gate(8, &double_fault); set_trap_gate(9, &coprocessor_segment_overrun); set_trap_gate(10, &invalid_TSS); set_trap_gate(11, &segment_not_present); set_trap_gate(12, &stack_segment); set_trap_gate(13, &general_protection); set_trap_gate(14, &page_fault); set_trap_gate(15, &reserved); set_trap_gate(16, &coprocessor_error); for(i = 17; i < 48; i++) { set_trap_gate(i, &reserved); } set_trap_gate(45, &irq13); // 开放外部中断 8259 outb_p(0x21, inb_p(0x21)&0xfb); outb(0xA1, inb_p(0xA1)&0xdf); set_trap_gate(39, ¶llel_interrupt); } ================================================ FILE: kernel/vsprintf.c ================================================ #include #include #include // Implement sprintf for print formatted string // Convert it to a string int _sprintnum(char *dest, int num, int base, int sign); void vsprintf(char *dest, char *fmt, va_list ap) { // va_start(ap, fmt); va_start 只能使用一次,获得 va_list 之后就不要 start 啦, 不然就会出现参数输出全都不对的错误 char c, *s; char *dest_ptr = dest; while(*fmt) { c = *fmt++; if(c != '%') { *(dest_ptr++) = c; continue; } c = *fmt++; if(c == '\0') break; switch(c) { case 'd': dest_ptr = dest_ptr + _sprintnum(dest_ptr, va_arg(ap, int), 10, 1); break; case 'u': dest_ptr = dest_ptr + _sprintnum(dest_ptr, va_arg(ap, int), 10, 0); break; case 'x': dest_ptr = dest_ptr + _sprintnum(dest_ptr, va_arg(ap, int), 16, 0); break; case 's': s = va_arg(ap, char*); while(*s) *dest_ptr++ = (*s++); break; case 'c': *dest_ptr++ = (va_arg(ap, char)); break; case '%': *dest_ptr++ = ('%'); } } return; } int _sprintnum(char *dest, int num, int base, int sign) { char digits[] = "0123456789ABCDEF"; char buf[50] = ""; int cnt = 0; int len = 0; int i; if(sign && num < 0) { // Check for sign or unsign *dest++ = '-'; num = -num; len++; } if(num == 0) { *dest++= '0'; len++; return len; } while(num) { buf[cnt++] = digits[num % base]; num = num / base; } for(i = cnt - 1; i >=0; i--) { *dest++ = (buf[i]); len++; } return len; } ================================================ FILE: kernel/vsprintf.c.old ================================================ /* Copyright (C) 2016 Gan Quan * Copyright (C) 2016 David Gao * * This file is part of AIM. * * AIM is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * AIM is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include typedef unsigned char uint8_t; typedef signed char int8_t; typedef unsigned short uint16_t; typedef signed short int16_t; typedef unsigned int uint32_t; typedef signed int int32_t; typedef unsigned long long uint64_t; typedef signed long long int64_t; typedef unsigned long size_t; typedef signed long ssize_t; int vsnprintf(char *str, size_t size, const char *fmt, va_list ap); #define NULL 0 int snprintf(char *str, size_t size, const char *fmt, ...) { int result; va_list ap; va_start(ap, fmt); result = vsnprintf(str, size, fmt, ap); va_end(ap); return result; } #define FLAG_UNSIGNED 0x001 /* unsigned integer */ #define FLAG_ZEROPAD 0x002 /* padding with zero */ #define FLAG_NEG 0x100 /* negative integer */ int vsnprintf(char *str, size_t size, const char *fmt, va_list ap) { #define is_digit(ch) (((ch) >= '0') && ((ch) <= '9')) #define digits(x) (((char)(x) <= 9) ? ('0' + (char)(x)) : ('a' + (char)(x) - 10)) int64_t val; uint64_t uval; ssize_t width = 0; int base; int flag = 0; ssize_t pos = 0; #define set_ch(ch) \ do { \ str[pos++] = ch; \ if (pos == size) { \ str[size - 1] = '\0'; \ return size; \ } \ } while (0) #define getint(ap) \ ((longflag == 0) ? va_arg(ap, int) : \ (longflag == 1) ? va_arg(ap, long) : va_arg(ap, long long)) #define getuint(ap) \ ((longflag == 0) ? va_arg(ap, unsigned int) : \ (longflag == 1) ? va_arg(ap, unsigned long) : \ va_arg(ap, unsigned long long)) int len; int longflag = 0; int buf_pos; char buf[25]; char *s; for ( ; *fmt != '\0'; ) { if (*fmt == '%') { ++fmt; fmt_loop: switch (*fmt) { case 'l': longflag++; ++fmt; goto fmt_loop; case 'd': base = 10; val = getint(ap); longflag = 0; if (val < 0) { flag |= FLAG_NEG; uval = -val; } else uval = val; goto print_uint; case 'o': base = 8; goto get_uint; case 'x': base = 16; goto get_uint; case 'p': /* %p equals %08x on 32 bit and %016x on * 64 bit */ base = 16; flag |= FLAG_ZEROPAD; longflag = 1; #ifdef __LP64__ /* 64 bit */ width = 16; #else /* 32 bit */ width = 8; #endif /* __LP64__ */ goto get_uint; case 'u': base = 10; get_uint: uval = getuint(ap); longflag = 0; print_uint: buf_pos = 0; while (uval > 0) { buf[buf_pos++] = digits(uval % base); uval /= base; } if (buf_pos == 0) buf[buf_pos++] = '0'; if (width == 0) { if (flag & FLAG_NEG) set_ch('-'); while ((--buf_pos) >= 0) set_ch(buf[buf_pos]); } else if ((width > 0) && (flag | FLAG_ZEROPAD)) { if (flag & FLAG_NEG) set_ch('-'); while ((--width) >= 0) { if (width >= buf_pos) set_ch('0'); else set_ch(buf[width]); } } else if (width > 0) { if (flag & FLAG_NEG) buf[buf_pos++] = '-'; while ((--width) >= 0) { if (width >= buf_pos) set_ch(' '); else set_ch(buf[width]); } } ++fmt; break; case 'c': set_ch((char)va_arg(ap, int)); ++fmt; break; case 's': s = va_arg(ap, char *); if (s == NULL) s = "(null)"; for (len = 0; *s != '\0'; ++s, ++len) set_ch(*s); for (; len < width; ++len) set_ch(' '); ++fmt; break; case '%': set_ch('%'); ++fmt; break; case '0': flag |= FLAG_ZEROPAD; ++fmt; goto fmt_loop; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': for ( ; is_digit(*fmt); ++fmt) { width *= 10; width += (int)(*fmt) - (int)'0'; } goto fmt_loop; default: set_ch('%'); goto print_default; } flag = 0; width = 0; continue; } print_default: set_ch(*fmt); ++fmt; } set_ch('\0'); --pos; /* exclude the null byte */ #undef FLAG_UNSIGNED #undef FLAG_ZEROPAD #undef FLAG_NEG #undef is_digit #undef set_ch return pos; } ================================================ FILE: ldS.ld ================================================ SECTIONS { ENTRY(startup_32) .text 0x0000 : { *(.text) } .rodata . : { *(.rodata) } .data . : { *(.data) } /DISCARD/ : { *(.eh_frame) } } ================================================ FILE: lib/Makefile ================================================ include ../Makefile.header OBJS = getline.o printf.o LDFLAGS += -r CFLAGS += -I../include .PHONY=clean run all all: lib.o lib.o: $(OBJS) $(LD) $(LDFLAGS) -o lib.o $(OBJS) .c.o: @$(CC) $(CFLAGS) \ -c -o $*.o $< clean: - rm -f $(OBJS) lib.o ================================================ FILE: lib/getline.c ================================================ #define __LIBRARY__ #define EOF -1 #include #include extern int user_tty_read(int channel, char *buf, int nr); char getchar() { char _buf[2048]; user_tty_read(0, _buf, 1); return _buf[0]; } int getline(char *str) { char _buf[2048]; char ch; char *p = str; int len = 0; while(ch != '\n' && ch != EOF) { user_tty_read(0, _buf, 1); ch = _buf[0]; *p++ = ch; len++; } *(p - 1) = '\0'; return len - 1; // remove the \n } ================================================ FILE: lib/printf.c ================================================ /* * User mode printf function */ #define __LIBRARY__ #include #include #include #include #include extern int user_tty_write(int channel, char *buf, int nr); void vsprintf(char *dest, char *fmt, va_list ap); static inline int sys_debug(char *str); static inline _syscall1(int, sys_debug, char *, str) int printf(char *fmt, ...) { int i = 0; char buf[TTY_BUF_SIZE] = ""; char *ptr = buf; va_list ap; for(i = 0; i < TTY_BUF_SIZE; i++) { buf[i] = 0; } va_start(ap, fmt); vsprintf(buf, fmt, ap); va_end(ap); i = 0; while(*ptr) { i++; ptr++; } return user_tty_write(0, buf, i); } ================================================ FILE: mm/Makefile ================================================ include ../Makefile.header LDFLAGS += -r CFLAGS += -I../include .PHONY=clean run all .c.o: @$(CC) $(CFLAGS) \ -c -o $*.o $< .s.o: @$(AS) -o $*.o $< .c.s: @$(CC) $(CFLAGS) \ -S -o $*.s $< OBJS = memory.o mm_test.o page.o all: mm.o mm.o: $(OBJS) @$(LD) $(LDFLAGS) -o mm.o $(OBJS) clean: @rm -f core *.o *.a tmp_make @for i in *.c;do rm -f `basename $$i .c`.s;done ================================================ FILE: mm/memory.c ================================================ /* * Routine use to handle physical page memory * memory.c 涉及到的函数均为物理页内存,以及页表管理函数 * 并且实现了 Demand Loading 以及 Copy On Write */ // 以下是一些在 head.s 里已经规划好的参数,在这里以宏的形式再次给出 #include #include #include #include #define LOW_MEM 0x100000ul // 0x00000000 - 0x00100000 为物理内存低 1MB 空间, 是系统代码所在 #define PAGING_MEMORY (15*1024*1024) // 剩余 15MB 空闲物理内存用于分页 #define PAGING_PAGES (PAGING_MEMORY >> 12) // 分页后的页数 #define MAP_NR(addr) (((addr) - LOW_MEM) >> 12) // 计算当前物理地址的对应页号 #define USED 100 // 占用状态, 为什么是100呢, 我不是很理解 // 该宏用于复制一页物理内存从 from 到 to #define copy_page(from, to) \ __asm__ volatile ("cld; rep; movsl;":"S" (from), "D" (to), "c" (1024)) // 用于使TLB失效, 刷新缓存 #define invalidate() \ __asm__ volatile("mov %%eax, %%cr3"::"a" (0)) static unsigned long HIGH_MEMORY = 0; void un_wp_page(unsigned long * table_entry); // 最基本的, linux0.11 使用内存字节位图来管理物理页的状态, 声明时全部填零 // 每一个字节表示这个物理页面被共享次数 static unsigned char mem_map[ PAGING_PAGES ] = {0,}; // 在当前状态因为没有进程管理, 如果内存超限直接panic static inline void oom() { panic("Out of Memory!! QWQ\n"); } // 此函数对物理页映像进行初始化(mem_map),将内核使用的部分内存映像初始设置为占用 // 将从start_mem -> end_mem 处的内存初始化为没有被使用 ( 0 - 1MB 空间不在考虑之内) // >>12 = /4KB(一页的大小) // void mem_init(unsigned long start_mem, unsigned long end_mem) { unsigned long i; HIGH_MEMORY = end_mem; // 物理内存最高处为 end_mem for(i = 0; i < PAGING_PAGES; i++) mem_map[i] = USED; // 除了start_mem ~ end_mem 的区域物理页都应该为被占用状态 i = (unsigned long)MAP_NR(start_mem); end_mem -= start_mem; end_mem >>= 12; // 计算多少页需要设置为free while(end_mem-->0) { mem_map[i++] = 0; } return ; } // 仅仅用来显示当前 memory 用量的一个小函数 void calc_mem(void) { int i, j, k, free = 0; long *pg_tbl; for(i = 0; i < PAGING_PAGES; i++) if(!mem_map[i]) free++; printk("%d pages free (of %d in total)\n", free, PAGING_PAGES); // 遍历除了页表页目录的其余页表项, 如果页面有效, 则统计有效页面数量 for(i = 2; i < 1024; i++) { if(pg_dir[i] & 1) { // 先检查 Dir 是否存在 pg_tbl = (long *)(0xfffff000 & pg_dir[i]); // 计算 pg_tbl 的地址 for(j = k = 0; j < 1024; j++) { if(pg_tbl[j] & 1) { // 检查 Entry 是否存在 k++; } } printk("PageDir[%d] uses %d pages\n", i, k); } } return ; } // 此函数用于获取第一个(按照顺序来说是最后一个)空闲的内存物理页, // 这个函数从mem_map的末尾开始遍历,直到遇到某个物理页映像为没有被占用状态,这时计算出该页的物理地址 // 初始化该页内容为0, 并返回页起始地址 // 如果不存在可以用的页则返回 0 unsigned long get_free_page(void) { register unsigned long __res asm("ax"); __asm__ volatile ("std; repne; scasb\n\t" "jne 1f\n\t" "movb $1, 1(%%edi)\n\t" "sall $12, %%ecx\n\t" "addl %2, %%ecx\n\t" "movl %%ecx, %%edx\n\t" "movl $1024, %%ecx\n\t" "leal 4092(%%edx), %%edi\n\t" "rep; stosl;\n\t" "movl %%edx, %%eax\n" "1: cld" : "=a" (__res) : "0" (0), "i" (LOW_MEM), "c" (PAGING_PAGES), "D" (mem_map + PAGING_PAGES - 1)); // 从尾端开始检查是否有可用的物理页 return __res; } // 释放一页物理页 // 就是将mem_map中相应的byte置0,以及做一些必要的error_check // 包括是否访问了内核内存,还有是否超出了物理内存(16MB)边界 void free_page(unsigned long addr) { if(addr < LOW_MEM) return ; // 决不允许操作物理内存低端 if(addr >= HIGH_MEMORY) return ; // 也不能超过可用内存高端 addr = MAP_NR(addr); // 计算出需要的页号 if(mem_map[addr]--) return; // 如果该页为被使用状态那么减少引用计数并返回 mem_map[addr] = 0; panic("Trying to free free page!"); // 不应该出现这种情况 因而引发panic } // 释放页表, 以及相应对应的物理页释放,并释放该页目录项占用的物理页 int free_page_tables(unsigned long from, unsigned long size) { unsigned long *pg_tbl; unsigned long *pg_dir, nr; // 检查是否为 4MB 内存边界(该函数仅仅处理连续的4MB内存块) if(from & 0x3fffff) panic("free_page_tables called with wrong alignment"); // 如果要释放低 4MB 内存,也会引发错误(也就是from == 0) if(!from) panic("try to free up swapper memory space"); size = (size + 0x3fffff) >> 22; // 计算占的目录项数 // 计算目录项地址 pg_dir = (unsigned long *)((from >> 20) & 0xffc); // 下面开始释放, 首先释放页表项, 后释放页目录项 // 注意释放的是连续的目录/页表项, 因而 pg_dir 自增即可 for(; size-->0; pg_dir++) { if(!(*pg_dir & 1)) // 说明该目录项没有被使用 continue; pg_tbl = (unsigned long *)(*pg_dir & 0xfffff000); // 从页目录项中取出页表项地址 for(nr = 0; nr < 1024; nr++) { // 开始释放页表项 if(*pg_tbl & 1) // 此页存在(P = 1) free_page(0xfffff000 & *pg_tbl); // 释放此页 *pg_tbl = 0; pg_tbl++; } // 然后释放掉这个页目录所占的页 free_page(0xfffff000 & *pg_dir); *pg_dir = 0; } invalidate(); return 0; } // 用来将一页内存放入页表(和页目录), 即将相应的页表,页目录项填充, // 如果要put的物理页在内存中还是unset状态(mem_map[xxx] = 0),那么先去获取一个可用的物理页 // 此函数假定了 pg_dir = 0 (hardcode) // 成功时返回该页的物理地址, 失败/OOM 时返回0 // 参数 page 为页面的物理地址 address 为线性地址 unsigned long put_page(unsigned long page, unsigned long address) { unsigned long *pg_tbl, tmp; if(page < LOW_MEM || page >= (unsigned long)HIGH_MEMORY) printk("Trying to put page %x at %x\n", page, address); // mem_map 中此页为unset状态 if(mem_map[MAP_NR(page)] != 1) printk("mem_map disagrees with %x at %x\n", page, address); // 计算该线性地址对应的页目录地址 pg_tbl = (unsigned long *) ((address >> 20) & 0xffc); // 如果页目录存在则直接取出 pg_tbl // printk("Params: pg_tbl = %x, entry = %x\n", pg_tbl, (address >> 12) & 0x3ff); if(*pg_tbl & 1) { // printk("Page table now available\n"); pg_tbl = (unsigned long *) (*pg_tbl & 0xfffff000); } // 否则申请物理页放置页目录 else { if(!(tmp = get_free_page())) { printk("NO FREE PAGE!"); return 0; } *pg_tbl = tmp | 7; // printk("Tmp = %x\n", tmp); // printk("Page Table = %x\n", *pg_tbl); pg_tbl = (unsigned long *) tmp; } // printk("Put Page Success\n"); pg_tbl[(address >> 12) & 0x3ff] = page | 7; //invalidate(); return page; } // 为线性地址 address 申请一个空物理页 // 失败则返回异常 void get_empty_page(unsigned long address) { unsigned long tmp; if(!(tmp = get_free_page()) || !put_page(tmp, address)) { free_page(tmp); oom(); } return ; } // 写页面验证, 即验证目前要操作的页面(线性地址)是否合法以及可以被写入 // 如果页面存在但是不可写入, 则会尝试解除页面写入保护 (un_wp_page()) void write_verify(unsigned long address) { unsigned long page; // 检查页目录项是否存在 if(!( (page = *((unsigned long *)((address >> 20) & 0xffc))) & 1)) { return ; } // 取页表首地址 page = page & 0xfffff000; page += ((address >> 10) & 0xffc); if((*(unsigned long *)page & 3) == 1) { // 页表P = 1, R/W = 0 un_wp_page((unsigned long *)page); } return ; } // 解除页面的写入保护 // 解除write protect,具体两个情况, 如果这个页面在 1MB 以上空间 // 并且存在,那么就直接在FLAG上添加 W FLAG 并且刷新TLB, 否则的话申请一个新的页面 // 并复制老页面的内容到新页面(Copy On Write) // void un_wp_page(unsigned long * table_entry) { s_printk("[DEBUG] un_wp_page(0x%x)\n", table_entry); unsigned long old_page, new_page; old_page = *table_entry & 0xfffff000; s_printk("[DEBUG] old_page = 0x%x\n", old_page); // 页面存在且位于1MB以上 if(old_page >= LOW_MEM && mem_map[MAP_NR(old_page)] == 1) { s_printk("[DEBUG] Above 1MB\n"); *table_entry |= 2; invalidate(); return ; } // 无法分配新的页面 if(!(new_page = (unsigned long)get_free_page())) // FUCK, I FORGET TO ADD "()" to the function, stuck for nearly two weeks oom(); // 页面被共享, 因为要进行 COW 之后该页面就是独立的了,所以引用计数 -1 if(old_page >= LOW_MEM) mem_map[MAP_NR(old_page)]--; *table_entry = new_page | 7; invalidate(); return ; } // 缺页异常会调用此函数 void do_wp_page(unsigned long error_code, unsigned long address) { s_printk("[DEBUG] Page Fault(Write) at [%x], errono %d\n", address, error_code); error_code = error_code; // 纯粹为了消除警告 // Checked the address caculate correct // IN 0x1000 OUT: 0x1004 un_wp_page((unsigned long *) (((address>>10)&0xffc) + (0xfffff000 & *((unsigned long *)((address >> 20) & 0xffc))))); mm_print_pageinfo(address); } // 缺页异常会调用此函数 // 缺页异常会调用的主要处理函数, (有进程管理相关代码, 暂时略), 如果当前进程executable是空的 // 或者地址已超出进程数据,则尝试取一个空页面并返回, 如果失败 // 则尝试共享页面,如果还失败,则尝试取一个新的页面(不在内存中的),再失败则报错OOM // 参数 error_code, 错误号; address 线性地址 void do_no_page(unsigned long error_code, unsigned long address) { //unsigned long tmp; unsigned long page; s_printk("[DEBUG] Page Fault at [%x], errono %d\n", address, error_code); address &= 0xfffff000; if(!(page = get_free_page())) oom(); if(put_page(page, address)) { mm_print_pageinfo(address); return ; } free_page(page); oom(); } // 下面的两个函数是多进程需要使用到的,copy_page_tables 和其姊妹函数 // free_page_tables // copy_page_table 用来拷贝线性地址连续一页的空间到另一个线性地址, // 做法为仅仅拷贝其页表项内容,不拷贝其物理内存内容 // 同时当from = 0时表示从内核拷贝,这时我们不需要拷贝 4MB ,仅仅拷贝 640 KB int copy_page_tables(unsigned long from, unsigned long to, unsigned long size) { s_printk("[DEBUG] copy_page_tables(0x%x, 0x%x, 0x%x)\n", from, to, size); unsigned long *from_page_table; unsigned long *to_page_table; unsigned long this_page; unsigned long *from_dir, *to_dir; unsigned long nr; // 检查内存边界,必须是4MB的整数倍,否则 panic if((from & 0x3fffff) || (to & 0x3fffff)) { panic("copy_page_tables called with wrong alignment"); } // 获取线性地址对应的页目录项 from_dir = 0 + (unsigned long *)((from >> 20) & 0xffc); to_dir = 0 + (unsigned long *)((to >> 20) & 0xffc); size = ((unsigned) (size + 0x3fffff)) >> 22; for(; size-->0; from_dir++, to_dir++) { if(1 & *to_dir) panic("copy_page_tables: already exists"); // from_dir 不存在,就跳过这页页表的复制 if(!(1 & *from_dir)) continue; from_page_table = (unsigned long *)(0xfffff000 & *from_dir); // I forget to fetch the from_page_table!! QAQ // s_printk("from_page_table: addr=0x%x\n", (unsigned long)(0xfffff000 & (unsigned long)*from_page_table)); if(!(to_page_table = (unsigned long *) get_free_page())) return -1; // s_printk("to_page_table = 0x%x\n", (unsigned long)(0xfffff000 & (unsigned long)*to_page_table)); *to_dir = ((unsigned long)to_page_table | 7); // 判断是不是复制内核页,如果是则只copy 640KB(0xA0=160个页面) nr = (from == 0)?0xA0:1024; for(; nr-->0; from_page_table++, to_page_table++) { this_page = *from_page_table; // s_printk("this_page_before: addr=0x%x\n", (unsigned long)(0xfffff000 & (unsigned long)this_page)); if(!(1 & this_page)) continue; // 这里将复制的页设置为只读 this_page &= (unsigned long)~2; // s_printk("this_page_after: addr=0x%x\n", (unsigned long)(0xfffff000 & (unsigned long)this_page)); *to_page_table = this_page; // 如果复制的页面地址为高于LOW_MEM,说明不是从内核空间复制 // 同时把原页面也设置为只读,这样二者共享了物理页面,如果向任何一个页面写入都会 // 引发页保护异常(Page Fault),触发写时复制(Copy On Write COW) if(this_page > LOW_MEM) { *from_page_table = this_page; mem_map[MAP_NR(this_page)]++; } } } invalidate(); return 0; } ================================================ FILE: mm/mm_test.c ================================================ /* * File to show mm functional and test mm * */ #include #include #include #define invalidate() \ __asm__ volatile("mov %%eax, %%cr3"::"a" (0)) unsigned long put_page(unsigned long page, unsigned long address); void testoom() { for(int i = 0; i < 20 * 1024 * 1024; i += 4096) do_no_page(0, (unsigned long)i); // This should not return! return ; } // This function is currently buggy // Cannot use // void test_share_page() { // // Copy a page then try to write to it // copy_page_tables(0x0, 0x4000000, 0xA0000); // mm_print_pageinfo(0x4000000); // // write to the page at 0x4000000 // char *w = 0x4000000; // *w = 'p'; // mm_print_pageinfo(0x4000000); // while (1); // } void test_put_page() { char *b = (char *)0x100000; calc_mem(); //put_page(0x200000, 0x100000); calc_mem(); *b = 'k'; while(1); return ; } // Helper function to convert linear address to PTE // return a pointer to a page table entry on success // return NULL(0) on failed unsigned long *linear_to_pte(unsigned long addr) { // get the Page Directory Entry first // This variable will be used to refer to pte later // (for saving memory XD) unsigned long *pde = (unsigned long *)((addr >> 20) & 0xffc); // Page dir not exist // Or the address is not inside the page table address range(<=4KB) if(!(*pde & 1) || (unsigned long)pde > 0x1000) { return 0; } // Now it is page table address :P pde = (unsigned long *)(*pde & 0xfffff000); // Page table address + page_table index = PTE // // Remember: x >> 12 & 0x3ff != x >> 10 & 0xffc (后者比前者二进制末尾多了两个0) // s_printk("pde = %d\n", (unsigned long)pde + 4); // s_printk("pte.offset = %d\n", (unsigned long)pde + (((addr >> 12) & 0x3ff) << 2)); return (unsigned long *)((unsigned long)(pde) + (((addr >> 12) & 0x3ff) << 2)); } void disable_linear(unsigned long addr) { // Your code here // Hint: Modify the correct page table entry // then you can make the page Not Present // Maybe you need a helper function for get // the page table entry for a specific address unsigned long *pte = linear_to_pte(addr); // Disable it *pte = 0; // Then RELOAD the TLB invalidate(); return ; } void mm_read_only(unsigned long addr) { unsigned long *pte = linear_to_pte(addr); printk("Before: 0x%x\n", *pte); *pte = *pte & 0xfffffffd; printk("After: 0x%x\n", *pte); invalidate(); return ; } void mm_print_pageinfo(unsigned long addr) { unsigned long *pte = linear_to_pte(addr); s_printk("[DEBUG] Linear addr: 0x%x, PTE addr = 0x%x. Flags[ ", addr, pte); if(*pte & 0x1) s_printk("P "); if(*pte & 0x2) s_printk("R/W "); else s_printk("RO "); if(*pte & 0x4) s_printk("U/S "); else s_printk("S "); s_printk("]\n"); s_printk("[DEBUG] Phyaddr = %x\n", (*pte & 0xfffff000)); } int mmtest_main(void) { // test_share_page(); // return 0; s_printk("0x%x\n", linear_to_pte(0x1000)); printk("Running Memory function tests\n"); printk("1. Make Linear Address 0xdad233 unavailable\n"); disable_linear(0xdad233); // Now don't modify the code above // make the linear address AT 0xdad233 accessible // and store 0x23333333 at 3MB physical memory // (Hint: add a table entry map to physical address 3MB) // printk("2. Put page(0x300000) at linear address 0xdad233\n"); put_page(0x300000, 0xdad233); unsigned long *x = (unsigned long *)0xdad233; *x = 0x23333333; printk("X = %x\n", *x); // Then make the linear address 0xdad233 Read Only printk("3. Make 0xdad233 READ ONLY\n"); mm_read_only(0xdad233); x = (unsigned long *)0xdad233; // DO not modify this code // Here will make WP bit enabled (temporarily) asm volatile("mov %%cr0, %%eax\n\t" "orl $0x00010000, %%eax\n\t" "mov %%eax, %%cr0\n\t" ::); // Please write a GCC Extended assembly here // to modify the content at linear address 0xdad233 // (This will trigger a page write protect fault), uncomment lines below to test //asm volatile("mov %%eax, (%%edi)" // ::"D" (x), "a" (0x233111)); printk("4. Print the page info of 0xdad233 in human readable mode\n"); mm_print_pageinfo(0xdad233); while(1); // Please implement } ================================================ FILE: mm/page.s ================================================ .code32 .global page_fault page_fault: xchgl %eax, (%esp) # 错误码入eax, 同时eax压栈 pushl %ecx pushl %edx push %ds push %es push %fs movl $0x10, %edx mov %dx, %ds mov %dx, %es mov %dx, %fs movl %cr2, %edx pushl %edx pushl %eax testl $1, %eax jne 1f call do_no_page jmp 2f 1: call do_wp_page 2: addl $8, %esp pop %fs pop %es pop %ds popl %edx popl %ecx popl %eax iret