本文始作于2012年1月23日,刊登于人人网,于2013年2月13日迁移至此
晚上从姥姥家回来看了会儿鸟哥的私房菜 - 如何规划 Linux 主机,讲了一些硬件的事。
主频 vs 倍频:
CPU的主频,即CPU内核工作的时钟频率(CPU Clock Speed)。通常所说的某某CPU是多少兆赫的,而这个多少兆赫就是“CPU的主频”。很多人认为CPU的主频就是其运行速度,其实不然。CPU的主频表示在CPU内数字脉冲信号震荡的速度,与CPU实际的运算能力并没有直接关系。主频和实际的运算速度存在一定的关系,但目前还没有一个确定的公式能够定量两者的数值关系,因为CPU的运算速度还要看CPU的流水线的各方面的性能指标(缓存、指令集,CPU的位数等等)。由于主频并不直接代表运算速度,所以在一定情况下,很可能会出现主频较高的CPU实际运算速度较低的现象。比如AMD公司的AthlonXP系列CPU大多都能以较低的主频,达到英特尔公司的Pentium 4系列CPU较高主频的CPU性能,所以AthlonXP系列CPU才以PR值的方式来命名。因此主频仅是CPU性能表现的一个方面,而不代表CPU的整体性能。
主频一般是指电脑CPU的标称频率,外频一般是指电脑电脑系统总线(外部设备)的频率。由于CPU的速度较高,而电脑外部设备的速度较慢,无法跟得上CPU的处理速度,严重影响电脑的整体性能,于是人们在CPU与电脑外部设备之间设计了一个可以暂时存放外部设备处理的数据的设备,即高速缓存,来解决这个问题,你可以把高速缓存看成是一座水库,有一条进水的小河道和一条放水的闸门,虽然流进这座水库的水虽然流速很慢很少,但由于水库里已经预先存放了大量的水,依然可以满足闸门大量放水推动水轮机发电的需要。在这个事例中,进水的速度就相当于外频,放水的速度就相当于主频,而放水速度与进水速度的比值就是倍频。于是得出,主频=外频×倍频。比如说老奔三的CPU主频是1GHz,它的外频是133MHz,那么它的倍频就是7.5。
我是这样理解的。小明会算10以内的加法,他算题的速度是恒定的,一分钟一道题,现在有一个老师考他,老师出题的速度是十分钟一道题,这样老师十分钟出题一道,这样他俩解决问题的速度就是十分钟一道题,显然这很慢。现在有三个老师一起出题,十分钟出题3道,小明这十分钟前三分钟在算题,后七分钟都没事干。这种情况下小明的晶体振荡频率为10Hz(假设一分钟为一秒),主频为3Hz,外频为1Hz,倍频为3。
今天写setup.s完成了所有实验要求的功能:
代码如下:

1 ! 2 ! setup.s (C) 1991 Linus Torvalds 3 ! 4 ! setup.s is responsible for getting the system data from the BIOS, 5 ! and putting them into the appropriate places in system memory. 6 ! both setup.s and system has been loaded by the bootblock. 7 ! 8 ! This code asks the bios for memory/disk/other parameters, and 9 ! puts them in a "safe" place: 0x90000-0x901FF, ie where the 10 ! boot-block used to be. It is then up to the protected mode 11 ! system to read them from there before the area is overwritten 12 ! for buffer-blocks. 13 ! 14 15 ! NOTE! These had better be the same as in bootsect.s! 16 17 INITSEG = 0x9000 ! we move boot here - out of the way 18 SYSSEG = 0x1000 ! system loaded at 0x10000 (65536). 19 SETUPSEG = 0x9020 ! this is the current segment 20 21 .globl begtext, begdata, begbss, endtext, enddata, endbss 22 .text 23 begtext: 24 .data 25 begdata: 26 .bss 27 begbss: 28 .text 29 30 entry start 31 start: 32 33 ! Show some message 34 35 mov ax,#SETUPSEG 36 mov es,ax 37 38 mov ah,#0x03 ! read cursor pos 39 xor bh,bh 40 int 0x10 41 42 mov cx,#19 43 mov bx,#0x0007 44 mov bp,#message 45 mov ax,#0x1301 46 int 0x10 47 48 mov ax,#0xe0d 49 int 0x10 50 mov al,#0x0a 51 int 0x10 52 mov ax,#0xe0d 53 int 0x10 54 mov al,#0x0a 55 int 0x10 56 57 ! ok, the read went well so we get current cursor position and save it for 58 ! posterity. 59 60 mov ax,#INITSEG ! this is done in bootsect already, but... 61 mov ds,ax 62 mov ah,#0x03 ! read cursor pos 63 xor bh,bh 64 int 0x10 ! save it in known place, con_init fetches 65 mov [0],dx ! it from 0x90000. 66 67 ! Get memory size (extended mem, kB) 68 69 mov ah,#0x88 70 int 0x15 71 mov [2],ax 72 73 ! Get video-card data: 74 75 mov ah,#0x0f 76 int 0x10 77 mov [4],bx ! bh = display page 78 mov [6],ax ! al = video mode, ah = window width 79 80 ! check for EGA/VGA and some config parameters 81 82 mov ah,#0x12 83 mov bl,#0x10 84 int 0x10 85 mov [8],ax 86 mov [10],bx 87 mov [12],cx 88 89 ! Get hd0 data 90 91 mov ax,#0x0000 92 mov ds,ax 93 lds si,[4*0x41] 94 mov ax,#INITSEG 95 mov es,ax 96 mov di,#0x0080 97 mov cx,#0x10 98 rep 99 movsb 100 101 ! Get hd1 data 102 103 mov ax,#0x0000 104 mov ds,ax 105 lds si,[4*0x46] 106 mov ax,#INITSEG 107 mov es,ax 108 mov di,#0x0090 109 mov cx,#0x10 110 rep 111 movsb 112 113 ! Print cursor position 114 115 mov ax,#SETUPSEG 116 mov es,ax 117 118 mov ah,#0x03 ! read cursor pos 119 xor bh,bh 120 int 0x10 121 122 mov cx,#12 123 mov bx,#0x0007 124 mov bp,#cursor 125 mov ax,#0x1301 126 int 0x10 127 128 print_cursor: 129 130 mov ax,#INITSEG ! this is done in bootsect already, but... 131 mov ds,ax 132 mov cx,#4 133 mov dx,[0] 134 135 print_cursor_digit: 136 137 rol dx,#4 138 mov ax,#0xe0f 139 and al,dl 140 add al,#0x30 141 cmp al,#0x3a 142 jl outp_cursor 143 add al,#0x07 144 145 outp_cursor: 146 147 int 0x10 148 loop print_cursor_digit 149 150 mov ax,#0xe0d 151 int 0x10 152 mov al,#0x0a 153 int 0x10 154 155 ! Print memory size 156 157 mov ah,#0x03 ! read cursor pos 158 xor bh,bh 159 int 0x10 160 161 mov cx,#13 162 mov bx,#0x0007 163 mov bp,#memory 164 mov ax,#0x1301 165 int 0x10 166 167 print_memory: 168 169 mov cx,#4 170 mov dx,[2] 171 172 print_memory_digit: 173 174 rol dx,#4 175 mov ax,#0xe0f 176 and al,dl 177 add al,#0x30 178 cmp al,#0x3a 179 jl outp_memory 180 add al,#0x07 181 182 outp_memory: 183 184 int 0x10 185 loop print_memory_digit 186 187 mov ah,#0x03 ! read cursor pos 188 xor bh,bh 189 int 0x10 190 191 mov cx,#2 192 mov bx,#0x0007 193 mov bp,#kb 194 mov ax,#0x1301 195 int 0x10 196 197 mov ax,#0xe0d 198 int 0x10 199 mov al,#0x0a 200 int 0x10 201 202 ! Print cyls 203 204 mov ah,#0x03 ! read cursor pos 205 xor bh,bh 206 int 0x10 207 208 mov cx,#6 209 mov bx,#0x0007 210 mov bp,#cyls 211 mov ax,#0x1301 212 int 0x10 213 214 print_cyls: 215 216 mov cx,#4 217 mov dx,[0x80] 218 219 print_cyls_digit: 220 221 rol dx,#4 222 mov ax,#0xe0f 223 and al,dl 224 add al,#0x30 225 cmp al,#0x3a 226 jl outp_cyls 227 add al,#0x07 228 229 outp_cyls: 230 231 int 0x10 232 loop print_cyls_digit 233 234 mov ax,#0xe0d 235 int 0x10 236 mov al,#0x0a 237 int 0x10 238 239 ! Print heads 240 241 mov ah,#0x03 ! read cursor pos 242 xor bh,bh 243 int 0x10 244 245 mov cx,#7 246 mov bx,#0x0007 247 mov bp,#heads 248 mov ax,#0x1301 249 int 0x10 250 251 print_heads: 252 253 mov cx,#4 254 mov dx,[0x82] 255 256 print_heads_digit: 257 258 rol dx,#4 259 mov ax,#0xe0f 260 and al,dl 261 add al,#0x30 262 cmp al,#0x3a 263 jl outp_heads 264 add al,#0x07 265 266 outp_heads: 267 268 int 0x10 269 loop print_heads_digit 270 271 mov ax,#0xe0d 272 int 0x10 273 mov al,#0x0a 274 int 0x10 275 276 ! Print sectors 277 278 mov ah,#0x03 ! read cursor pos 279 xor bh,bh 280 int 0x10 281 282 mov cx,#9 283 mov bx,#0x0007 284 mov bp,#sectors 285 mov ax,#0x1301 286 int 0x10 287 288 print_sectors: 289 290 mov cx,#4 291 mov dx,[0x8e] 292 293 print_sectors_digit: 294 295 rol dx,#4 296 mov ax,#0xe0f 297 and al,dl 298 add al,#0x30 299 cmp al,#0x3a 300 jl outp_sectors 301 add al,#0x07 302 303 outp_sectors: 304 305 int 0x10 306 loop print_sectors_digit 307 308 mov ax,#0xe0d 309 int 0x10 310 mov al,#0x0a 311 int 0x10 312 313 ! Check that there IS a hd1 :-) 314 315 mov ax,#0x01500 316 mov dl,#0x81 317 int 0x13 318 jc no_disk1 319 cmp ah,#3 320 je is_disk1 321 322 no_disk1: 323 324 ! Show some message 325 326 mov ax,#SETUPSEG 327 mov es,ax 328 329 mov ah,#0x03 ! read cursor pos 330 xor bh,bh 331 int 0x10 332 333 mov cx,#15 334 mov bx,#0x0007 335 mov bp,#no 336 mov ax,#0x1301 337 int 0x10 338 339 mov ax,#0xe0d 340 int 0x10 341 mov al,#0x0a 342 int 0x10 343 int 0x10 344 int 0x10 345 int 0x10 346 int 0x10 347 348 mov ax,#INITSEG 349 mov es,ax 350 mov di,#0x0090 351 mov cx,#0x10 352 mov ax,#0x00 353 rep 354 stosb 355 356 jmp exit 357 358 is_disk1: 359 360 ! Show some message 361 362 mov ax,#SETUPSEG 363 mov es,ax 364 365 mov ah,#0x03 ! read cursor pos 366 xor bh,bh 367 int 0x10 368 369 mov cx,#12 370 mov bx,#0x0007 371 mov bp,#exist 372 mov ax,#0x1301 373 int 0x10 374 375 mov ax,#0xe0d 376 int 0x10 377 mov al,#0x0a 378 int 0x10 379 int 0x10 380 int 0x10 381 int 0x10 382 int 0x10 383 384 exit: 385 386 ! now we want to move to protected mode ... 387 388 cli ! no interrupts allowed ! 389 390 ! first we move the system to it's rightful place 391 392 mov ax,#0x0000 393 cld ! 'direction'=0, movs moves forward 394 do_move: 395 mov es,ax ! destination segment 396 add ax,#0x1000 397 cmp ax,#0x9000 398 jz end_move 399 mov ds,ax ! source segment 400 sub di,di 401 sub si,si 402 mov cx,#0x8000 403 rep 404 movsw 405 jmp do_move 406 407 ! then we load the segment descriptors 408 409 end_move: 410 mov ax,#SETUPSEG ! right, forgot this at first. didn't work :-) 411 mov ds,ax 412 lidt idt_48 ! load idt with 0,0 413 lgdt gdt_48 ! load gdt with whatever appropriate 414 415 ! that was painless, now we enable A20 416 417 call empty_8042 418 mov al,#0xD1 ! command write 419 out #0x64,al 420 call empty_8042 421 mov al,#0xDF ! A20 on 422 out #0x60,al 423 call empty_8042 424 425 ! well, that went ok, I hope. Now we have to reprogram the interrupts :-( 426 ! we put them right after the intel-reserved hardware interrupts, at 427 ! int 0x20-0x2F. There they won't mess up anything. Sadly IBM really 428 ! messed this up with the original PC, and they haven't been able to 429 ! rectify it afterwards. Thus the bios puts interrupts at 0x08-0x0f, 430 ! which is used for the internal hardware interrupts as well. We just 431 ! have to reprogram the 8259's, and it isn't fun. 432 433 mov al,#0x11 ! initialization sequence 434 out #0x20,al ! send it to 8259A-1 435 .word 0x00eb,0x00eb ! jmp $+2, jmp $+2 436 out #0xA0,al ! and to 8259A-2 437 .word 0x00eb,0x00eb 438 mov al,#0x20 ! start of hardware int's (0x20) 439 out #0x21,al 440 .word 0x00eb,0x00eb 441 mov al,#0x28 ! start of hardware int's 2 (0x28) 442 out #0xA1,al 443 .word 0x00eb,0x00eb 444 mov al,#0x04 ! 8259-1 is master 445 out #0x21,al 446 .word 0x00eb,0x00eb 447 mov al,#0x02 ! 8259-2 is slave 448 out #0xA1,al 449 .word 0x00eb,0x00eb 450 mov al,#0x01 ! 8086 mode for both 451 out #0x21,al 452 .word 0x00eb,0x00eb 453 out #0xA1,al 454 .word 0x00eb,0x00eb 455 mov al,#0xFF ! mask off all interrupts for now 456 out #0x21,al 457 .word 0x00eb,0x00eb 458 out #0xA1,al 459 460 ! well, that certainly wasn't fun :-(. Hopefully it works, and we don't 461 ! need no steenking BIOS anyway (except for the initial loading :-). 462 ! The BIOS-routine wants lots of unnecessary data, and it's less 463 ! "interesting" anyway. This is how REAL programmers do it. 464 ! 465 ! Well, now's the time to actually move into protected mode. To make 466 ! things as simple as possible, we do no register set-up or anything, 467 ! we let the gnu-compiled 32-bit programs do that. We just jump to 468 ! absolute address 0x00000, in 32-bit protected mode. 469 mov ax,#0x0001 ! protected mode (PE) bit 470 lmsw ax ! This is it! 471 jmpi 0,8 ! jmp offset 0 of segment 8 (cs) 472 473 ! This routine checks that the keyboard command queue is empty 474 ! No timeout is used - if this hangs there is something wrong with 475 ! the machine, and we probably couldn't proceed anyway. 476 empty_8042: 477 .word 0x00eb,0x00eb 478 in al,#0x64 ! 8042 status port 479 test al,#2 ! is input buffer full? 480 jnz empty_8042 ! yes - loop 481 ret 482 483 gdt: 484 .word 0,0,0,0 ! dummy 485 486 .word 0x07FF ! 8Mb - limit=2047 (2048*4096=8Mb) 487 .word 0x0000 ! base address=0 488 .word 0x9A00 ! code read/exec 489 .word 0x00C0 ! granularity=4096, 386 490 491 .word 0x07FF ! 8Mb - limit=2047 (2048*4096=8Mb) 492 .word 0x0000 ! base address=0 493 .word 0x9200 ! data read/write 494 .word 0x00C0 ! granularity=4096, 386 495 496 cursor: ! 10byte 497 .ascii "Cursor POS: " 498 499 memory: ! 13byte 500 .ascii "Memory SIZE: " 501 502 kb: ! 2byte 503 .ascii "KB" 504 505 cyls: ! 6byte 506 .ascii "Cyls: " 507 508 heads: ! 7byte 509 .ascii "Heads: " 510 511 sectors: ! 9byte 512 .ascii "Secotrs: " 513 514 message: ! 19byte 515 .ascii "Now we are in SETUP" 516 517 exist: ! 12byte 518 .ascii "There is hd1" 519 520 no: ! 15byte 521 .ascii "There isn't hd1" 522 523 idt_48: 524 .word 0 ! idt limit=0 525 .word 0,0 ! idt base=0L 526 527 gdt_48: 528 .word 0x800 ! gdt limit=2048, 256 GDT entries 529 .word 512+gdt,0x9 ! gdt base = 0X9xxxx 530 531 .text 532 endtext: 533 .data 534 enddata: 535 .bss 536 endbss:
实际上输出信息的关键代码在cms上已经做好了,当初没看指导书的时候感觉这个地方会非常扎手,因为int中断输出都是按照字符数出的,没有按照十六进制数字输出的,就是内存里存的数没办法直接输出成实际数据,只能输出成字符或字符串,所以要想看到内存的真实值,就必须将字符串再翻译回对应的十六进制字符,这个循环在语言上就比较有难度,但是sunner替我们将这个函数已经写好了,这个要是搞汇编,估计是那种很常用的模板:
下面是完成显示16进制数的汇编语言程序的关键代码,其中用到的BIOS中断为INT 0x10,功能号0x0E(显示一个字符),即AH=0x0E,AL=要显示字符的ASCII码。
1 !以16进制方式打印栈顶的16位数 2 print_hex: 3 x,#4 ! 4个十六进制数字 4 mov dx,(bp) ! 将(bp)所指的值放入dx中,如果bp是指向栈顶的话 5 print_digit: 6 rol dx,#4 ! 循环以使低4比特用上 !! 取dx的高4比特移到低4比特处。 7 mov ax,#0xe0f ! ah = 请求的功能值,al = 半字节(4个比特)掩码。 8 and al,dl ! 取dl的低4比特值。 9 add al,#0x30 ! 给al数字加上十六进制0x30 10 cmp al,#0x3a 11 jl outp !是一个不大于十的数字 12 add al,#0x07 !是a~f,要多加7 13 outp: 14 int 0x10 15 loop print_digit 16 ret
这 里用到了一个loop指令,每次执行loop指令,cx减1,然后判断cx是否等于0。如果不为0则转移到loop指令后的标号处,实现循环;如果为0顺 序执行。另外还有一个非常相似的指令:rep指令,每次执行rep指令,cx减1,然后判断cx是否等于0,如果不为0则继续执行rep指令后的串操作指 令,直到cx为0,实现重复。
!打印回车换行 print_nl: mov ax,#0xe0d ! CR int 0x10 mov al,#0xa ! LF int 0x10 ret
只 要在适当的位置调用print_bx和print_nl(注意,一定要设置好栈,才能进行函数调用)就能将获得硬件参数打印到屏幕上,完成此次实验的任 务。但事情往往并不总是顺利的,前面的两个实验大多数实验者可能一次就编译调试通过了(这里要提醒大家:编写操作系统的代码一定要认真,因为要调试操作系 统并不是一件很方便的事)。但在这个实验中会出现运行结果不对的情况(为什么呢?因为我们给的代码并不是100%好用的)。所以接下来要复习一下汇编,并 阅读《Bochs使用手册》,学学在Bochs中如何调试操作系统代码。
我没有当作函数使用,为了避免调配栈的麻烦,我直接将函数改成代码然后几个十六进制输出就写块代码:
print_cursor: mov ax,#INITSEG ! this is done in bootsect already, but... mov ds,ax mov cx,#4 mov dx,[0] print_cursor_digit: rol dx,#4 mov ax,#0xe0f and al,dl add al,#0x30 cmp al,#0x3a jl outp_cursor add al,#0x07 outp_cursor: int 0x10 loop print_cursor_digit mov ax,#0xe0d int 0x10 mov al,#0x0a int 0x10
代码越来越多了,明天应该琢磨着学学用github了,实验一做完之后应该把BlackSun在github和bitbucket各备份一份。