[转贴]内核版之OS设计

hyl
(enthusiast)
10/06/02 10:28
[精华] 内核版之OS设计

本贴是索引页, 其回贴是早期系统的一些介绍.


了解最新进展 新闻和进展 Join us

参与问题的解决 Bug List

最新发布请看----项目主页 -------2003.4.8

导航

内核版OS设计讨论&下载

内核版OS设计--Kernel结构简析

内核设计--进程相关问题

内核版OS设计---LFB简介

内核设计--文件系统


论坛资源
写个小的OS项目,如何?----------杂谈而已
为什么懂了内核以后都要去搞自己的操作系统呢?

编辑者: hyl (10/21/04 10:08)

文章选项:

hyl
(enthusiast)
09/17/02 12:19
[精华] bootloader [re: hyl]



需要从一个简单的bootsect 开始写我的操作系统吗?

下面的网站给出了一些建议:
千万不要写bootloader. 至少不要把他当作写os的第一件事.
bootloader 不是 OS.
bootloader 仅仅是os的一个非常小的部分.
写一个好些的bootloader 的工作量和写一个小 OS的工作量相当.
写一个 bootloader 需要一些神秘的知识,像什么a20等等.
如果在 DOS 或者 Windows 9x下开发,可以使用 DOS 作为你的boottloader. , 你看 GRUB 怎么样.


但是,为什么不写一个自己的?

既然如此神秘, 既然有巨多的开源bootloader......, 拷贝一些代码修剪一下也挺好


你选择写还是不写? 不惜半年的深夜...写!? bootloader的一些研究


bootloader中0.0.5版有了如下特性:

1.Boot 装载 setup+os 到 0X10000

2.在实模式下跳到 setup, setup 把 os 移动到 0x100000 (1M)

3.然后切换到保护模式,用一句长跳转 jmp dword 8:0x100000
(nasm can mix 16bit and 32bit code)
4.使用了 c++

1).setup.s 预留了2048(0x800)个字节供以后使用

2).setup.s 预留空间, setup 搬移 os 的位置(紧邻setup之后),应该一致.
总结:
系统从 1M 的地方跑起来,总算像个操作系统的 Loader 了!!!!!

第一个版本0.0.1可以说是第一步了, 可能费时也最久.







http://osdev.neopages.net/

编辑者: hyl (10/01/02 09:05)

文章选项:

hyl
(enthusiast)
09/18/02 10:19
资源网站 [re: hyl]

操作系统资源中心
这个网站之好, 无法用言语表达............

OSD之家
好好好!

OS dev
也不错

execpc
快来看看吧......

openBLT
reactos
brainix team
v2os
Nachos
Thix
hello
aros
kerne link
os file
文件格式大全



编辑者: hyl (09/18/02 18:26)

文章选项:

hyl
(enthusiast)
09/18/02 12:30
建议 [re: hyl]



1.djgpp下的make 可能不认识长文件名. 总说no rule to make ....
也可能是版本低. 总之使用短一点的文件名称好.

2.把bootsect osimg 使用copy /b 连接成一个文件. 可以直接作为vmWare的虚拟软驱使用. 真是方便.^_^
注意:最好加上pad 使之大于1.44M.

3. NASM version 0.98 的bug
如果你向elf添加了自定义的section ,nasm就会把文件弄坏! (只可有 .text, .data, or .bss, 切记)

4. CygWin 和 MinGW32 把 BSS size 用错误的方式存入了 BSS section header.
这是bug, 就是因此, 这些工具不能和NASM 或者微软的编译器交互!.

5. CygWin and MinGW32 linkers crash when asked to make a binary kernel.
嗐! 我是不用这两个东西! 因此下面的东西我也不翻译了.
You can get around this to some extent by linking to PE format with identical memory alignment and file alignment:

ld --oformat pei-i386 --file-alignment 0x1000 --section-alignment 0x1000 ...
Now you have a 'binary' kernel with a 4096-byte PE header at the beginning, which the bootloader can skip over (thanks to Tim Robinson for this idea). Note that this may still fail if you have user-defined sections that come after the BSS.

6. 注意连接脚本的bug!:
这样写有错误:
.bss: {
*(.bss)
*(.common)
end = . ; _end = . ;
}
一些有bug的 ld 会把 'end' 放在common 和 bss中间. 所以应该这样写:
...
*(.common)
}
end = .; _end = . ;
(thanks to Jarek Pelczar for finding this bug)

7. DJGPP 的elf 工具好像有bug?!
如果你的内核仅仅有 .text, .data, and .bss sections, 还可以使用 objcopy 把 DJGPP COFF 转换到 ELF.

8. Bootloader 'foo' 不能装载我的 'bar' 核
根据 Multiboot 标准(GRUB就如此) , 一个保护模式的 kernel 不能依赖 GDT 的布局, 段描述符, 或bootloader建立的选择子. 你的核要做好防御性的措施啊! 不要对bootloader有所假设! 做到这一点大概需要使用汇编核相对地址.

9.只使用可用 RAM
段地址:偏移地址.......线性地址............... 使用情况
0000:0000-0000:03FF 000000-0003FF 中断向量表interrupt vector table
0040:0000-0040:00FF 000400-0004FF BIOS 数据区
0050:0000-0050:76FF 000500-007BFF 空闲常规内存(CONVENTIONAL MEMORY )
0000:7C00-0000:7DFF 007C00-007DFF 引导扇区
0000:7E00-9000:FBFF 007E00-09FBFF 空闲常规内存
9000:FC00-9000:FFFF 09FC00-09FFFF 扩展BIOS 数据区(EBDA)
A000:0000-F000:FFFF 0A0000-0FFFFF video memory 和BIOS ROMs
FFFF:000F-FFFF:FFFF 100000-10FFEF 高端内存区 (HMA)
10FFF0- 空闲扩展内存

引导扇区程序可能超出 007C00-007DFF, 决定于如何写. 可以在内核或第二阶段的装载完成后覆盖这段内存.

EBDA 这里说是1K. 有些计算机没有 EBDA, 有些大于 1K. 用BIOS 中断 INT 12h or INT 15h AX=E820h 可以找到常规内存的顶端, 不要使用超过这个限制的常规内存.

DOS 7+ 自动加载HIMEM.SYS , 然后把自己放到HMA. 最好不把你的内核装到HMA .最好使用 XMS 分配一段扩展内存. 这样可以防止把其他 XMS 的 'clients' , 像 SMARTDRV, 写掉.

Watch out for the case where HIMEM.SYS is loaded and SMARTDRV is also loaded in such a way that the free XMS memory block straddles a 4 meg line. If your kernel is loaded into this memory, and it uses paging, the kernel will need two pages tables to map the kernel memory. The kernel could be copied to 1 meg after it's been loaded. This will probably trash DOS, so do it just before entering pmode.

10.内核 code 和 data 没有连接到正确的地址
请使用连接脚本 (see 'More linker gotchas', below).

可以先连接到非binary 如COFF, ELF, PE, 然后 dump the symbols and disassemble the kernel, 最后转换到 binary:
ld -g -Tcoffkrnl.ld -o krnl.cof $(OBJS) lib/libc.a
objdump --line-numbers --source krnl.cof >krnl.lst
nm --line-numbers krnl.cof | sort >krnl.sym
objcopy -O binary krnl.cof krnl.bin
这样可以检查 krnl.lst and krnl.sym ,看看是不是正确的定位了内核的code 和data.

对于 x86, 内核的数据段对 连接和定位的错误很敏感,因为许多代码使用 EIP-相对寻址. 你的内核启动代码可以检测看看内核的数据段是不是被正确的连接,定位和加载:

DS_MAGIC equ 3544DA2Ah

[SECTION .text]
[BITS 32]
GLOBAL entry
entry:
call where_am_i ; where did the loader put us?
where_am_i:
pop esi ; ESI=physical adr (where we are)
sub esi,where_am_i ; subtract virtual adr (where we want to be)
; now ESI=virt-to-phys
cmp dword [esi + ds_magic],DS_MAGIC
je ds_ok
mov word [0B8000h],9F44h ; display blinking white-on-blue 'D'
jmp short $ ; freeze
ds_ok:
...

[SECTION .data]
ds_magic:
dd DS_MAGIC
...

More linker gotchas
ld -Ttext=NNN ... 会把代码段定位于地址 NNN, 但是 ld 仍然使用缺省的连接脚本定位 .data 和 .bss. 所以使用自己的连接脚本是一个好的选择.

下面列出的方法均不能在binary kernel中生成一个清零的BSS:
objcopy -O binary krnl.cof krnl.bin
ld --oformat binary -o krnl.bin $(OBJS)
如果你把一个文件附加到binary kernel,
copy /b krnl.bin + ramdisk.bin boot.bin
如果内核试图在地址 'end' 访问附加文件的话, 会发现得到不正确的结果. 因为附加文件覆盖了内核的BSS段,当内核的启动代码或者bootloader清除内核的BSS段时,附加的文件会被擦掉.

11. 从C程序编译的 binary kernel 之Entry point
C 编译的程序其入口不一定是c文件中的第一个函数,试试下面的方法:
copy con hello.c (cat >hello.c for Unix)
#include <stdio.h>
int main(void) { printf("hello"); return 0; }
^Z (^D for Unix)
gcc -c -O2 -g hello.c
objdump --disassemble hello.o
00000000 <.text>:
hello 0: 68 65 6c 6c 6f push $0x6f6c6c65
\0 5: 00 89 f6 55 89 e5 add %cl,0xe58955f6(%ecx)
00000008 <_main>:
在.text 段的第一个东东不是main(), 而是字符串 'hello'. 可以把 main() 单独放在一个文件中:
int main(void) { return real_main_in_another_file(); }
或者在文件开头放一个伪 main() 并且文件开始处没有变量或文字:
int real_main(int arg_c, char *arg_v[]);
int main(int arg_c, char *arg_v[])
{ return real_main(arg_c, arg_v); }
/* ... */
int real_main(int arg_c, char *arg_v[])
{ /* ... */ }
再者, 使用编译选项 -fwritable-strings:
gcc -fwritable-strings ...
一个相关的问题: COFF 重定位 (.o) 文件没有 a.out 头, 因此 entry point 不能指定. 可以假定 entry point 始 .text section的开始, 注意这里提到的方法,可以把C 程序的entry放在.text的开始.


12.一些东东需要 线性地址 (LINEAR addresses)
通常的基于段的地址转换不应用到LGDT指令的 "伪描述符" 中, IDT and LIDT 也是如此. 必须自己做转换:
... ; now in real mode
xor ebx,ebx
mov bx,ds
shl ebx,4
add [gdt_ptr + 2],ebx
lgdt [gdt_ptr]
...
gdt:
... ; your GDT here
gdt_end:

gdt_ptr:
dw gdt_end - gdt - 1
dd gdt ; this address is converted to linear

13. 显示输出不工作
也许你这样访问video memory :
*(unsigned char *)0xB8000 = 'A';
只有kernel data从0 开始时这句程序才工作. 如果你的 OS 没有这个条件,就要定义一个单独的保护模式段描述符,基址为 0, 然后使用 far pointer 功能访问 video memory. 假定 LINEAR_SEL 是0基址的段:
#include <sys/farptr.h> /* DJGPP only */
...
_farpokeb(LINEAR_SEL, 0xB8000, 'A');
或者, 使用virt_to_phys的转换:
*(unsigned char *)(0xB8000 - virt_to_phys) = 'A';
为了使near pointers 起到作用, kernel data 段必须没有限制 (i.e. limit = 4 Gig - 1 = 0xFFFFFFFF).

14.使用BIOS调用得到正确的内存大小
CMOS 不会报告大于 63.999 meg (65535/1024) 的扩展内存 , 也不会报告 扩展内存的'holes' , 也就是在 15 meg and 16 meg 中间的1m的holes,这个洞存在于特定的 16-bit SVGA 板, 特定的 OSes, 特定的 BIOS 设置.
直接探测会有问题:

地址回绕: 大于64 meg 的地址回到 0.
当探测到使用内存映射的硬件的地址时会引起机器'冻结' .
PC 可能使用一种奇异的方法打开 A20 门; 这种机制不支持直接的内存探测.
未使用volatile的C代码, 或者有bug的compiler 即使使用 volatile 也会产生错误的代码.
总线挂空时,即使没有内存也会使探测成功.
如果你非得使用直接探测,看看这个 直接内存探测

15. 麻烦的A20
没有一种单独的方法可以在所有的pc机上控制 A20 (HIMEM.SYS 支持 17 种不同的方法). 因此:
不要打开 A20, 如果不是必要的话.
如果需要把一些东西拷贝到扩展内存, 使用INT 15h AH=87h. bios 会自动控制 A20 .
可以使用 INT 15h AH=89h 进入 pmode.
如果使用自己的代码打开 A20, 请在打开后检测一下.
你的代码应该多试几种方法.
如果 HIMEM.SYS 已经加载, 使用他的 XMS 服务去控制 A20.
我就要使用自己的代码, 有一个可用就行了.^_^


16.只能从键盘得到一个中断?!
如果你不在你的ISR中清除或复位中断, 你将不能收到后续的interrupt. 对于所有的设备,你必须清除 8259 中断控制芯片. 对于 IRQs 0-7:
outportb(0x20, 0x20);
对于 IRQs 8-15:
outportb(0xA0, 0x20);
outportb(0x20, 0x20);
也 必须清掉产生这个中断的设备的某个控制位. 通常是读一个 I/O 寄存器.
Timer: (只需要在 8259 芯片清除一把即可)
Keyboard: 从I/O 口 0x60 读扫描码即可
Realtime clock: outportb(0x70, 0x0C); (void)inportb(0x71);
IDE disk: 从I/O 口0x1F7读状态字节就行.

17. 中断处理函数的重入问题
不要在中断处理中使用 printf() ! printf() 和许多其他的函数不能重入. 也不要使用浮点运算.


18. 混合 16位和 32位的代码
使用 aout, .obj (OMF) 或其他支持混合代码的文件格式:
nasm -f elf x.asm
x.asm:30: ELF format does not support non-32-bit relocations
16-bit objects 必须小于 64K (0x10000). 否则:
ld -s -oformat binary -Ttext=0x10000 -ox.bin x.o y.o
x.o(.text+0x13): relocation truncated to fit: 16 text
最后, linker 必须支持你使用的 object 文件的格式:
ld-elf -o test test.o
test.o: file not recognized: File format not recognized
如果你不能满足这种条件,那你必须把 16- 和 32-bit 代码放到不同的文件中.
不要忘记把 BSS清零!
无初值的全局和静态的局部变量存储于 uninitialized data segment (BSS). bootloader 和 kernel startup code 中的一个必须 zero the BSS.

19. 16-bit DPMI 的问题( with Turbo or Borland C for DOS)
Borland C for DOS (version 3.1 or newer) 和 Turbo C for DOS (version 3.0 or newer, not the free version 2.0) 使用16-bit 的DPMI. 这个和DJGPP冲突, DJGPP使用 32-bit的 DPMI. 如果你混合使用 Borland and DJGPP 的工具, 你会发现一些奇怪的信息:
在纯dos 环境下, 使用 DJGPP MAKE (32-bit DPMI) 调用 Turbo C 3.0 (16-bit DPMI) :

c:\tc\bin\tcc.exe -v -mt -w -O2 -d -Z -1 -D__STARTUP_ASM__=1 -c -oboot.obj boot.c
16-bit DPMI unsupported.
make.exe: *** [tboot.exe] Error 1
Using DJGPP MAKE to invoke Turbo C 3.0 from Windows DOS box
(note the absence of error message text):
c:\tc\bin\tcc.exe -v -mt -w -O2 -d -Z -1 -D__STARTUP_ASM__=1 -c -oboot.obj boot.c
make.exe: *** [tboot.exe] Error 234

在纯dos 下用Turbo C 3.0 的MAKE 调用 DJGPP :
gcc -c boot.c
Load error: no DPMI - Get csdpmi*b.zip

** error 110 ** deleting all
Using Turbo C 3.0 MAKE to invoke DJGPP from Windows DOS box:
gcc -c boot.c
Load error: can't switch mode

** error 106 ** deleting all
修正你的方法吧. 在紧要关头, 也可以用 Borland MAKER.EXE 调用 DJGPP 的工具. MAKER.EXE 运行于 real mode, 而不是 16-bit pmode.

20. 连接的问题: C to asm, or C++ to C, or C++ to asm
External C 和 asm 的函数在C++ 代码中调用时必须使用如下的方式声明:
extern "C" void mul64(long *, long, long); /* 32*32 -> 64 multiply */
者就会禁止 'name-mangling' (名称窜改), C++ 使用这种方式实现多态.
参见 http://www.parashift.com/c++-faq-lite/mixing-c-and-cpp.html

一些编译器 (mainly those for DOS and Windows) 在C程序中定义的名称前加了一个下划线. 汇编中如果调用c程序时必须考虑到这个习惯:

; extern unsigned long virt_to_phys;
GLOBAL _virt_to_phys
_virt_to_phys:
dd 0

; /* extern */ void blink(void);
GLOBAL _blink
_blink:
push ebx
mov ebx,0B8000h
sub ebx,[_virt_to_phys]
inc byte [ebx]
pop ebx
ret

21. 汇编的labels 使用和指令相同的名称
在 NASM中可以避免这个问题, 在label前加上 $ , 但是 label并没有这个$, 他只是告诉 NASM 这是一个标号, 不是保留字:
GLOBAL $cli
$cli:
cli
ret
(Thanks to Julian Hall for this tip.)

22.objcopy -O binary ... 产生的垃圾
确信把你不想要的 sections 从核中移走::
# -g 去掉debug sections (.stabs, .stabstr)
objcopy -g -O binary -R .note -R .comment krnl.elf krnl.bin
MinGW32 objcopy 据说有bug. 我真的不要他了!

23. 用RAWRITE安装bootsect 的问题
RAWRITE DOS 版本把3到一个track的东西一次写道磁盘 .如果用他来安装 bootsector 到一个 FAT12 的floppy, 他会覆盖第一个 FAT. (我不知道是不是 Windows 版本的 RawWrite 更好些, partcopy又 怎么样?)

24.Turbo C .EXE 文件太大了
编译 (tcc -v ...) 或 连接 (tlink /v ...) 时如果使用 debug 选项暗示着 TLINK /v /i ...
/i 选项放一个清零了的 BSS 到 .EXE 文件中. 通常情况下, 只有BSS 的大小在 .EXE 文件头 , BSS memory 在他装载的时候分配.

25.fixed or forbidden register ... was spilled'
新版本的 GNU 汇编程序对行内汇编的 clobber lists 的处理有所不同, 尽管下面的代码使用老版本的 GNU assembler 时工作的很好, 但是现在我们认为这是不正确的::
static inline void
memset(void *__dest, unsigned int __fill, unsigned int __size)
{
__asm__ __volatile__ ("cld
rep
stosb" :
/* no outputs */ :
"c" (__size),
"a" (__fill),
"D" (__dest) :
"ecx","eax","edi","memory");
}
因为 ECX, EAX, and EDI 同时存在于 clobber list 和input constraints. 所以要把他们从 clobber list移出:
...
"D" (__dest) :
"memory");
}
这样就可以无错的编译了.

26.不要把你的Linux program 命名为 'test'
test 是(bash)的一个内嵌命令. 如果你的程序叫test, 很不幸, 实际运行的程序是内嵌的那个. 咋一看, 什么都没有! 上帝,这不是你的错!.


编辑者: hyl (09/18/02 16:55)

文章选项:

hyl
(enthusiast)
09/18/02 12:53
开发杂谈 [re: hyl]



1. 为什么写 OS ?
研究: 更高,更强,更好
高可考性, 更高的性能,实时,分布,面向对象,更易移植,挑战商业,挑战自我,新构架.

学习
多任务,内存保护,标注c库,设备驱动,文件系统,应用

兴趣
就是喜欢!


2.设计决策
移植性
受限于intel? 使用x86-only 的特性? 4 个特权级(大多机器有只有2个), 基于段的地址转换和保护 (一般都有paging), TSSes 任务切换(vs. 栈交换).

内核体系
Linux? monolithic kernel? microkernel? exokernel? SASOS? .

多任务
不支持?协作?抢占调度?

线程
无? 内核支持还是进程协作? 如何在同宗线程间协调地址空间?

多处理器
不支持?紧耦合的SMP? 松散的分布式?

多用户和安全

开发工作站
linux?没有SourceInsight!
win? ...........

开发语言
C C++ asm ......

可执行文件格式
还有是否支持动态链接? (就如 ELF 和 PE , 他们简单)

高级语言库
GNU glibc, Cygnus Newlib, homebrew, .........

兼容?

抄袭代码
拿来学习的代码.......

编辑者: hyl (09/18/02 13:13)

文章选项:

hyl
(enthusiast)
09/19/02 13:30
boot loader 0.0.1 和最小系统 [re: hyl]

其代码见: bootloader的一些研究

0.0.1 可以说是最最小的东西了.
相当于一个hello wold!
当初是在win下写的, 但既不知vmWare是何方神圣又不知道partcopy. 只晓得linux下的dd好使, gcc好使. 也真是移到了linux下, 才看到屏幕上的几行简单的信息. 心里的感觉真是无比xx. 那是2002.1.28 深夜23:06.中国年的前夕.

为此, 已经不知多少无眠的夜晚捧着印出来的minix bootsect, linux0.0.1 bootsect 看来看去.... 至今这种印记还在代码中. 从floppy读os的代码我实在没有兴趣, 因此至今还是linux的老样子.

如果没有 make binary use C Compier , 没有nondot, 估计到如今写不可能有0.0.1的诞生.
0.0.1 中的Makefile中, 最最有用的一句:

# 将生成的操作系统文件从 elf 格式转换到 binary 格式
os.bin: os.elf
objcopy -R .comment -R .note -S -O binary os.elf os.bin


就是来自于nondot的那篇文章.

关于这个Makefile 可能还有一句值得一看:

....
OBJS = c.o
.......
os.elf: $(OBJS)
$(LD) $(OBJS) -o os.elf -e c -Ttext 0
# 因为我们的映象是binary格式.
# 所以入口点函数应该是第一个.o文件的第一个函数


有件事情我很抱歉, 0.0.1中没有使用head.s.
只有boot.s 和c.c.

boot.s 把c.c 生成的bin文件os.bin从磁盘上载入内存, 切换到保护模式, 跳到os.bin.
看看makefile中的

# 最终生成文件 bootimg
bootimg:os.bin Boot
cat Boot os.bin>bootimg
# Boot 是引导扇区内容,内核 os.bin 紧跟其后的扇区

应该能够想象整个的流程.

c.c 除了打印了一句信息外好像什么都没有做.
从c.c生成的os.bin是32位的代码, 需要保护模式才能运行. 其中的函数

/*
Text mode kprint
Show 'c' at
line : x (0...24 )
col : y (0...79 )
*/
void kputc(char c,char color,int x,int y)
{
/* p ponitor to Video Memory */
char *p = (char*)0xb8000;

/* calc line pos*/
p += 2*x*80+y*2;

/* show char whith color*/
*p = c;

*(p+1) = color;
}

要想正确工作, os的ds选择子选择的描述符所描述的段之基址必须从0 开始, 可读写, limit 大于video mem上限(0.0.1是8m, 见boot.s).


0.0.1是我的希望之光.

文章选项:

hyl
(enthusiast)
09/19/02 14:30
0.0.2---0.0.5 启动构架 [re: hyl]

0.0.1 太简单, 不知道他能够做什么工作.
0.0.2 中, 好像只是把c.c 搞来搞去,没有实质性的工作.

0.0.3 中多了两个文件, setup.s, head.s . 这个可以从Makefile中看出来.
关键在于, boot.s中不再切换到保护模式了, 这样的话,我可以在setup.s 中多做一些事情. 因为boot.s 只有512 字节,太少了!
可以看到, 0.0.3 重的setup.s 所作的事情也很简单. 它把os(head.s, c.c)搬到物理地址0. 然后使用临时的gdt切换到保护模式.最后跳到os的head.s.
head.s 重新加载了os自己的gdt, 因为setup.s 不属于操作系统的一部分!
可以看到setup.s 和head.s 的基本功能. 更重要的是基于这样一个设想, 在setup.s中, 未切换到保护模式前, 可以写更多的代码完成更多的功能.甚至我使用16bit 的c编译器, 配合bios, 那就方便的多了. head.s主要是os的一个入口, 单独放到一个文件中使入口管理更方便. 其中的idt,gdt也使os的全局变量更加集中.

考虑到把os放到地址0:0 不合适, 所以0.0.3.5中, os 被移到0x20000, 位于物理内存 128k处.

但是还是觉得不爽, 干脆在0.0.4中, 切换到保护模式后直接把os放到1M的地方. 离开常规内存这个是非之地.

从Makefile 中ld -T 的选项可以清楚的看到这种变化.
中间遇到很多问题, 一些记录了下来,一些却没有.


0.0.5 中我想试一试c++ 好不好玩.

文章选项:

hyl
(enthusiast)
09/20/02 16:12
轮询式驱动 [re: hyl]

新的简单内核中,比如说bootloader 0.0.5----, 还没有多任务支持, 没有中断机制的支持, 没有应该有的一切.

怎么在这种情况下做一些简单的i/o可能是开发到这个阶段比较关心的问题.
找一些资料, 比如kbd, hd, vga......

0.0.5对vga的支持仅仅是操作默认的framebuffer, 甚至连光标都移动不了.
国外有些好资料, 前面我也推荐了不少网站.

看看这些资料...... 拿来我们先用用!


感谢 execpc.osd 的兄弟们杰出无私的奉献.


edit by hyl

编辑者: xshell (09/26/02 09:55)

文章选项:

hyl
(enthusiast)
09/20/02 17:38
VGA [re: hyl]

关于在实模式下使用bios 输出文本的方法这里不再讨论, 如果那位兄弟还不清楚到google搜一把.

推荐
Ralf Brown著名的中断列表
freshground (old hand) 推荐
bios中断表



保护模式中不依赖bios才是重点.


原理: 显示到屏幕上的字母和符号统统存在于一段叫做 framebuffer 的显存中. 至于其出现于内存的物理地址, 要看VGA板的工作模式. VGA

的两种模式是: monochrome (单色?) emulation , 或者color emulation.

emulation---|--framebuffer linear address--|--framebuffer real-mode address--|--I/O address of CRTC
color-------|--B8000h----------------------|--B800h:0000 --------------------|--3D4h
monochrome--|--B0000h----------------------|--B000h:0000 --------------------|--3B4h


CRTC 是VGA的一个功能单元, 待会再讨论有关CRTC的东东. 一般来说, 应该是 color emulation, 记得大一的时候我们的实验室倒是有几台386

上有 monochrome 的古董.

备注1 的c代码可以检测VGA是处于那种工作模式.
如果能够看懂, 拿来用用应该不成问题.

不会弄代码的格式, 大家拷贝后自己整理吧.
这里给一个简单的.


/* video card mono/colour detection by Dark Fiber
* returns 0=mono, 1=colour
*/
int detect_video_type(void)
{
int rc;
char c=(*(USHORT*)0x410&0x30

/* C can be 0x00 or 0x20 for colour, 0x30 for mono
if(c==0x30)
rc=0; // mono
else
rc=1; // colour

return rc;
}






字符及属性


在framebuffer中的每个字符都占用两个字节: ASCII 码值在地址 N 的话, N+1 就是他的属性字节.
属性字节的各个位的含义如下.

b7 ----- 闪烁
b6:b4 -- 背景色(0-7)
b3:b0 -- 前景色(0-15)


color value -- color -- color value -- color
0 ------------ 黑色------ 8 ---------- 暗灰
1 ------------ 蓝色------ 9 ---------- 亮蓝
2 ------------ green ---- 10 --------- bright green
3 ------------ cyan ----- 11 --------- bright cyan
4 ------------ red ------ 12 --------- pink
5 ------------ magenta -- 13 --------- bright magenta
6 ------------ brown ---- 14 --------- yellow
7 ------------ white ---- 15 --------- bright white


假定使用color模式:

Turbo C 代码的例子(cpu 工作于 16-bit real mode), 把白色的 'H' 以蓝色背景放到屏幕左上角.

#include <dos.h> /* pokeb() */
pokeb(0xB800, 0, 'H');
pokeb(0xB800, 1, 0x1F);


NASM 汇编中这么写(16-bit real mode):

mov bx,0B800h
mov es,bx
mov byte [es:0],'H'
mov byte [es:1],1Fh


DJGPP 代码(32-bit pmode), 有所不同. 把黄的 'H' 以红色背景放到屏幕右上角.因为处于保护模式, 我们使用far指针.

#include <sys/farptr.h> /* _farpokeb() */
#include <go32.h> /* _dos_ds */
_farpokeb(_dos_ds, 0xB8000 + 79 * 2 + 0, '*');
_farpokeb(_dos_ds, 0xB8000 + 79 * 2 + 1, 0x4E);


非得用 near 指针?

#include <sys/nearptr.h>
#include <crt0.h>
#include <stdio.h>

unsigned char *fb;

if(!(_crt0_startup_flags & _CRT0_FLAG_NEARPTR))
{ if(!__djgpp_nearptr_enable())
{ printf("Could not enable nearptr access\n");
return -1; } } /* probably Windows NT DOS box */
fb = (unsigned char *)0xB8000 + __djgpp_conventional_base;
fb[79 * 2 + 0] = '*';
fb[79 * 2 + 1] = 0x4E;



Scrolling(滚屏)


BIOS 滚屏就算了吧?!

如果使用 movedata() , 也算简单. 与memcpy() 不同的地方在于movedata对于源和目的都使用 far指针.


Turbo C 代码: 在 80x25 的方式下上滚一行(color emulation):

#include <string.h> /* movedata() */
movedata(0xB800, 80 * 2,
0xB800, 0,
80 * (25 - 1) * 2);


DJGPP 代码 scroll 80x25 display up one line (color emulation):

#include <string.h> /* movedata() */
#include <go32.h> /* _dos_ds */
movedata(_dos_ds, 0xB8000L + 80 * 2,
_dos_ds, 0xB8000L,
80 * (25 - 1) * 2);


使用 movedata() 的的话,如果 src < dst, 比如下滚, 可能不正确. 使用near 指针最好了, memmove() 保证任何滚动都能正确的工

作.


hardware scrolling

硬件来做滚动就比较快了. 把VGA配置成使用不同地址的framebuffer 就可以实现快速滚屏. CRTC 寄存器 12 号13号 分别包含framebuffer

相对于B0000h, B8000h, or A0000h 之偏移(offset) 的MSB 与 LSB .


/* scroll up one line */
#include <dos.h> /* outportb() */
unsigned short crtc_adr = 0x3D4; /* 0x3B4 for monochrome */
unsigned short offset = 80;

/* the CRTC index is at crtc_adr + 0
select register 12 */
outportb(crtc_adr + 0, 12);
/* the selected CRTC register appears at crtc_adr + 1 */
outportb(crtc_adr + 1, offset >> 8);
outportb(crtc_adr + 0, 13);
outportb(crtc_adr + 1, offset & 0xFF);



硬件滚屏的缺陷在于不能够持续无限的滚动. 因为最终 framebuffer 会超过 video memory 的上(下)限.
可以用作 framebuffer 的那段内存可以分成几个虚拟控制台(virtual consoles (VCs)). 32K 的 video memory 可以被分成8 个80x25的VCs.

console 译作控制台我认为不妥, 这里的console无非就是虚拟的几个屏幕.上面的代码就可以选择把那个虚拟屏呈现给用户. (Linux 的 VCs

使用了不同的管理方法, 我不知道.)


Moving the cursor


CRTC 寄存器14号和 15 号, 包含光标位置的 MSB LSB . 光标的位置用相对B8000h 或 B0000h的偏移来表示.

#include <dos.h> /* outportb() */
unsigned short crtc_adr = 0x3D4; /* 0x3B4 for monochrome */
unsigned short offset;
unsigned short x = 20, y = 3;

offset = x + y * 80; /* 80 characters per line */
outportb(crtc_adr + 0, 14); /* MSB of offset to CRTC reg 14 */
outportb(crtc_adr + 1, offset >> 8);
outportb(crtc_adr + 0, 15); /* LSB of offset to CRTC reg 15 */
outportb(crtc_adr + 1, offset);



[i] 不要告诉我, 你不知道outportb 那里去找! [/i]


推荐网站
pc-hardware
VGADOC
一个vag包
execpc的控制台代码






备注1





/*****************************************************************************
Determines if VGA board is set for monochrome or color emulation.
Uses 3 different algorithms.

This code is public domain (no copyright).
You can do whatever you want with it.
*****************************************************************************/
#include <stdlib.h> /* atoi() */
#include <stdio.h> /* printf() */
//#include "../port.c" /* inportb(), peekw() */

/********************************* TURBO C **********************************/
#if defined(__TURBOC__)
#include <dos.h> /* inportb(), peek() */

#define peekw(S,O) peek(S,O)

/********************************* DJGPP ************************************/
#elif defined(__DJGPP__)
#include <crt0.h> /* _CRT0_FLAG_LOCK_MEMORY */
#include <dos.h> /* inportb() */

//#define NEARPTR 1

/* near pointers; not supported in Windows NT/2k/XP DOS box
Must call __djgpp_nearptr_enable() before using these functions */
#if defined(NEARPTR)
#include <sys/nearptr.h> /* __djgpp_conventional_base, __djgpp_nearptr_enable() */
#include <stdio.h> /* printf() */
#include <crt0.h> /* _CRT0_FLAG_NEARPTR, _crt0_startup_flags */

#define peekw(S,O) *(unsigned short *)(16uL * (S) + (O) + \
__djgpp_conventional_base)
/* far pointers */
#else
#include <sys/farptr.h> /* _farpeekw() */
#include <go32.h> /* _dos_ds */

#define peekw(S,O) _farpeekw(_dos_ds, 16uL * (S) + (O))
#endif

/******************************** WATCOM C **********************************/
#elif defined(__WATCOMC__)
#include <conio.h> /* inp() */

#if defined(__386__)
/* CauseWay DOS extender only */
#define peekw(S,O) *(unsigned short *)(16uL * (S) + (O))
#else
#include <dos.h> /* MK_FP() */

#define peekw(S,O) *(unsigned short far *)MK_FP(S,O)
#endif

#define inportb(P) inp(P)

#else
#error Not Turbo C, not DJGPP, not Watcom C. Sorry.
#endif


static unsigned short g_crtc_base_adr;
/*****************************************************************************
Pentium 486 Bochs
method color color (color) mono
------ ------- ----- ------- -------
1 pass pass pass UNTESTED
2 pass pass pass UNTESTED
3 pass pass pass UNTESTED
*****************************************************************************/
int main(int arg_c, char *arg_v[])
{
int method;

#if defined(__DJGPP__)&&defined(NEARPTR)
if(!(_crt0_startup_flags & _CRT0_FLAG_NEARPTR))
{
if(!__djgpp_nearptr_enable())
{
printf("Could not enable nearptr access "
"(Windows NT/2k/XP?)\nUn-define NEARPTR "
"in source code and re-compile\n");
return 1;
}
}
#endif
if(arg_c < 2)
{
printf("attempt to detect monochrome/color VGA emulation "
"using one of three methods\n"
"specify 1, 2, or 3 on the command line\n");
return 1;
}
method = atoi(arg_v[1]);
switch(method)
{
case 1:
/* this method cobbled from info in Finn Thoegersen's VGADOC4 */
#define VGA_MISC_READ 0x3CC

if((inportb(VGA_MISC_READ) & 0x01) == 0)
g_crtc_base_adr = 0x3B4; /* mono */
else
g_crtc_base_adr = 0x3D4; /* color */
break;
case 2:
/* I forgot where this came from:
"The word at low memory address 0040:0063 (or 0000:0463) contains the
I/O address of the CRTC which can be used to determine whether the video
system is colour or monochrome. A value of 3B4 hex indicates monochrome."
(I presume 3D4 hex means color; my Pentium system has that value at 0463.) */
g_crtc_base_adr = peekw(0x40, 0x63);
break;
case 3:
/* Dark Fiber's method, from the OS FAQ
[url=www.mega-tokyo.com/os]http://www.mega-tokyo.com/os[/url]

from MEMORY.LST of Ralf Brown's Interrupt List
0040:0010 is Installed Hardware word, b5:b4 indicate video hardware:
00 EGA,VGA,PGA, or other with on-board video BIOS
01 40x25 CGA color
10 80x25 CGA color
11 80x25 mono text

whoa, this won't work with DJGPP -- OK, I will make a slight change here
if((*(unsigned short *)0x410 & 30) == 0x30) */
if((peekw(0x40, 0x10) & 30) == 0x30)
g_crtc_base_adr = 0x3B4; /* mono */
else
g_crtc_base_adr = 0x3D4; /* color */
break;
default:
printf("didn't find 1, 2, or 3 on the command line, sorry\n");
return 1;
}
/* what've we got? */
if(g_crtc_base_adr < 0x3C0)
printf("MONOCHROME emulation detected\n");
else
printf("color emulation detected\n");
return 0;
}

编辑者: hyl (10/11/02 16:45)

文章选项:

hyl
(enthusiast)
09/23/02 21:38
附加档案
梦中的开发环境(0.0.5.1告别linux) [re: hyl]

0.0.5 以前的版本再linux下编译. 当时对boch vmWare不太了解.
调试及其痛苦. linux下的vim虽然语法加亮,虽然....我却不太会使用.

倒是非常喜欢SourceInsight.

知道windows下的nasm djgpp也是free,但是当时从没有再win下编译出一个bin核.

试图找出一个linux版本,有很好的中文支持, 有一个类似sourceinsight的工具, 我当然不希望用蹩足的英文注释我的代码....

但是始终没有一个中文环境, 使自己满意.

大四时玩turbo 4.1, 只会安装而已,
现在在linux下体验os, 感叹啊.......

还是不断的努力做到在win下开发.......
windows下我很容易找到16bit的C编译器,可以增强loader.

2002.9.16这天, 又找出nasm, vmWare, djgpp.....
没想到, 这么顺利的在vmWare下调出了熟悉的画面.

真是让人兴奋.

上穿的版本中如果make会找不到一个文件 pad .
这个pad什么都不是,仅仅为了在vmWare下调试时把bootimg扩展到1.4M以上, 可以随便找个什么东西代替!


新的特性
见0.0.5.1的readme.txt
5.移植到djgpp 使用windows 下的gcc nasm 编译成功,并且使用vmWare调试.
至此,使用下面的工具: sourceinsight, vmvare, djgpp, nasm. (这是我梦中的环境^_^)
下一步考 虑使用tc 或者其他的编译器制作setup中的16位程序. (使用C 加强setup.s 的功能)

编辑者: hyl (09/23/02 21:55)

文章选项:

hyl
(enthusiast)
09/24/02 13:19
keyboard [re: hyl]

thanks Adam Chapweske
信号和协议, 扫描码, 命令集, 初始化,兼容性问题, pc的键盘控制器.
参考


历史

常用键盘包括:
USB 键盘 - 最近为所有的新机器所支持(Macintosh and IBM/compatible).
IBM/兼容 键盘 - 也称 "AT keyboards" 或者 "PS/2 keyboards", 现代pc都支持. 本文的主题.
ADB 键盘 - Apple Desktop Bus of older Macintosh systems.
原来的IBM 以及兼容机使用一种称作 "XT "的键盘. 现在不多见了,我们不介绍.后来IBM引入 AT 系统, AT之后是IBM PS/2. AT 键盘和 PS/2

键盘类似,是我们常用的键盘. PS/2 设备使用更小的连接器,支持更多一点的特性. 同时兼容AT, 新的特性几乎不使用.

IBM 的几种键盘:

IBM PC/XT 键盘 (1981):
83 个键
5-pin DIN 连接器
简单单向串口协议
使用扫描码 set 1
没有 host-to-keyboard 的命令

IBM AT 键盘 (1984) - 不兼容XT
84 到 101键
5-pin DIN 连接器
双向串口协议
使用扫描码 set 2
8个 host-to-keyboard 命令

IBM PS/2 键盘(1987) - 兼容AT
84 到 101 键
6-pin mini-DIN 连接器
双向串口协议
可选扫描码 set 3
17 个 host-to-keyboard 命令


今天买的键盘都兼容 PS/2 和 AT .所以今天的 "AT" 和 "PS/2" 有意义的只有他们的接口大小. 尽量不使用扩展特性.


现代兼容 AT-PS/2 键盘
任意键数目 (通常是101 或 104)
5-pin or 6-pin 连接器
双向串口协议
扫描码 set 2 肯定支持
应答所有的命令,但是可能并无此功能.



简单描述:

键盘包含一个由 keys组成的矩阵. 所有的键都为一个板上处理器监控,称作键盘编码器, (一般是i8048? 见下表).虽然这种芯片挺多,但是其职
能基本如下:
监控是哪个或那几个键被按下/释放,把相应的数据送到主板. 这个处理器处理所有的 debouncing(?what!) ,把数据缓存到他的16-byte 的缓冲区中. 在IBM兼容机上,主板也有一个板上芯片,称作键盘控制器.一般是8042. 他负责解码从键盘来的信息,通知系统软件各种事件.在host 和主板的通讯中 都使用IBM 协议.


现代键盘的encoders:
Holtek: HT82K28A, HT82K628A, HT82K68A, HT82K68E?EMC: EM83050, EM83050H, EM83052H, EM83053H,?Intel: 8048, 8049
Motorola: 6868, 68HC11, 6805
Zilog: Z8602, Z8614, Z8615, Z86C15, Z86E23


电器接口和协议:
AT 和 PS/2 键盘使用和 PS/2 鼠标一样的协议. 这里是协议的细节.
扫描码:
键盘处理器(encoder)大部分时间在"扫描", 监视着键矩阵. 一旦发现有键被按下,释放,或被按住不放,encoder就会向计算机发送一个数据包,称为扫描码. 有两种不同的扫描码, "make codes" 和 "break codes". make code 是键被按下,按住不放是产生的. break code 是键被释放时产生的. 每个键都有自己唯一的make code 和 break code. make code 和 break codes 的集合称为扫描码集. 共有三种标准的扫描码集.所有现代的键盘默认使用扫描码集 set 2.

详细情况见:
Scan Code Set 1 - 原来的 XT 扫描码集, 也有现代键盘支持此种东东.
Scan Code Set 2 - 现代键盘的缺省扫描码集.
Scan Code Set 3 - 可选的 PS/2 扫描码集,很少使用.

Make Codes, Break Codes, and Typematic Repeat:
a make code 和一个 ASCII 码之间并无固定的转换关系.尽管set 2的 make codes 大部分只有1个字节宽度, 但是有"extended keys" 的make codes 是2或4个字节宽,这些扩展键的make codes 的第一个字节总是 E0h.?每个键也有自己的唯一 break code(1). 但总是和make code有联系, 给程序设计带来不少方便. set 2 的大部分break codes 有2个字节宽,第一个字节是 F0h 第二个是那个键的make code. 扩展键的 Break codes 通常3个字节,前两个字节是E0h, F0h, 最后一个字节是那个键的 make code的最后一个字节.
按住一个键不放时,那个key 变成 typematic, 意味着键盘会不断的向主板发送那个键,直到释放他,或者另外一个键按下.这个过程有两个参数: typematic 延迟, typematic 速率. typematic 延迟从 0.25 秒到 1.00 秒, typematic 速率从 2.0 cps (characters per second) 到 30.0 cps. 可以使用"Set Typematic Rate/Delay" (0xF3) 命令改变这个值.如果多个键被按住不放,只有最后一个键成为typematic. 实际上,"Pause/Break" 键没有 break code(set 1,2).


i8042 :键盘控制器

以上的讨论都是针对硬件,其实,如果写一个底层的键盘相关的软件for PC,是不该直接和键盘通信的. 主板上一般都有键盘控制器,它在键盘和外设总线间是一个接口. 这个控制器处理 signal-level的东东和协议的细节 ,同时提供转换,解释,处理扫描码,执行命

分享到:
评论
isiqi
  • 浏览: 4162265 次
  • 性别: Icon_minigender_1
  • 来自: 济南
社区版块
存档分类
最新评论

你可能感兴趣的:(设计模式,C++,c,OS,dos)