山东建设网站当前互联网发展现状
- 作者: 五速梦信息网
- 时间: 2026年03月21日 09:46
当前位置: 首页 > news >正文
山东建设网站,当前互联网发展现状,个人网站服务器租用,兴海县公司网站建设这一部分是在讲解head.s代码#xff0c;这个代码与bootsect.s和setup.s在同一目录下#xff0c;但是head.s程序在被编译生成目标文件后会与内核其他程序一起被链接成system模块#xff0c;位于system模块的最前面开始部分。system模块将被放置在磁盘上setup模块之后开始的扇…这一部分是在讲解head.s代码这个代码与bootsect.s和setup.s在同一目录下但是head.s程序在被编译生成目标文件后会与内核其他程序一起被链接成system模块位于system模块的最前面开始部分。system模块将被放置在磁盘上setup模块之后开始的扇区中即从磁盘上第6个扇区开始放置。其大小为120KB其中head部分有14KBhead除了做了一些调用main函数的准备工作之外还做了一件对内核程序在内存中的布局已经对内核程序的正常运行都有重大意义的事情就是用程序自身代码在程序自身所在的内存空间中创建了内核分页机制即在 0x00000000处创建了页目录表页表缓冲区GDTIDT并将head程序已经执行过的代码所占空间覆盖这意味着head程序自己将自己废弃main函数开始执行。我们看看这些是怎么实现的。 同时CPU的运行模式变了汇编语法也变了变成了比较麻烦的ATT语法对于ATT语法不是很熟悉的可以参考别的博客 开始head.s pg_dir: .globl startup_32 startup_32:movl \(0x10,%eaxmov %ax,%dsmov %ax,%esmov %ax,%fsmov %ax,%gslss stack_start,%espcall setup_idtcall setup_gdtmovl \)0x10,%eax # reload all the segment registersmov %ax,%ds # after changing gdt. CS was alreadymov %ax,%es # reloaded in setup_gdtmov %ax,%fsmov %ax,%gslss stack_start,%espxorl %eax,%eax 1: incl %eax # check that A20 really IS enabledmovl %eax,0x000000 # loop forever if it isntcmpl %eax,0x100000je 1bpg_dir标识内核分页机制完成后的内核起始位置也就是物理内存的起始位置head程序马上要在这里建立页目录表为分页机制做准备这一点非常重要是内核能够掌握用户进程的基础之一其占据的内存空间为 0x0000-0x4FFF在实模式下CS是代码段基址但是在保护模式下CS是代码段选择符之后在设置分页机制时页目录会存放在这里也会覆盖这里的代码。 jmpi 0,8这个代码使得CS与GDT的第二项关联并且使代码段基址指向 0x000000。所以这个指令是一样的 可以看到mov指令来初始化段寄存器ds/es/fs/gs指向可读可写但不能执行的数据段。这个是怎么指向数据段的呢我也是一段时间之后才反应过来他是类似于上面的指令我们需要明白的一点是什么呢现在是保护模式了所以段寄存器不是直接指向内存了我们需要通过GDT来访问内存了通过GDT访问内存这点非常重要非常重要 在CPU中跟段有关的CPU寄存器一共有6个csssdsesfsgs它们保存的是段选择符。而同时这六个寄存器每个都有一个对应的非编程寄存器它们对应的非编程寄存器中保存的是段描述符。 CS代码段寄存器 DS数据段寄存器 SS栈段寄存器 ES扩展段寄存器 FSGS386处理器之后被引入 这里初始化的目的是为了让着四个寄存器可以变到保护模式然后加载堆栈段描述符我们来看看stack_start到底是啥 long user_stack [ PAGE_SIZE2 ] ;struct {long * a;short b;} stack_start { user_stack [PAGE_SIZE2] , 0x10 };这个程序在/kernel/sched.c文件当中。lss作用在这里最终的效果是把0x10作为段选择子加载到ss中并将user_stack的地址放到esp中。可以测算出其起始位置为 0x1E25C。 lss指令相当于让 ss:esp 这个栈顶指针指向了 _stack_start 这个标号的位置。还记得原来的栈顶指针在哪里嘛0x9FF00现在要变了。在这个结构体中高位 8 字节是 0x10将会赋值给 ss 栈段寄存器低位 16 字节是 user_stack 这个数组的最后一个元素的地址值将其赋值给 esp 寄存器。为什么是最后一个元素值呢因为栈是从后往前生长的。 0x10我们这里也应该当做段选择子来看待其实就是第四个段选择符段基址为 0x000000段限长8MB内核特权级后面的压栈动作就在这里执行。 这里我们就能看出来了实模式和保护模式的寻址方式差异非常大如果没有我们之前在实模式下创建的GDT表格现在寻址都无法执行我们之前设置栈的寄存器是SP现在是ESP这是专为保护模式下进行操作作出的调整。 setup_idt和setup_gdt分别对应建立新的IDT表和GDT表 建立新的IDT /** setup_idt** sets up a idt with 256 entries pointing to* ignore_int, interrupt gates. It then loads* idt. Everything that wants to install itself* in the idt-table may do so themselves. Interrupts* are enabled elsewhere, when we can be relatively* sure everything is ok. This routine will be over-* written by the page tables./ setup_idt:lea ignore_int,%edxmovl $0x00080000,%eaxmovw %dx,%ax / selector 0x0008 cs /movw $0x8E00,%dx / interrupt gate - dpl0, present /lea idt,%edimov \(256,%ecx rp_sidt:movl %eax,(%edi)movl %edx,4(%edi)addl \)8,%edidec %ecxjne rp_sidtlidt idt_descrretignore_int是一个函数作用是打印Unknown interrupt这个字符串然后结束。想看看的给你瞅一眼 我们还是先解释清除这段代码写了什么lea这个指令可以将ignore_int的偏移地址给edx然后将0x00080000这个值给了eax %eax可存放一般数据可作为累加器使用 %ebx可存放一般数据可用来存放数据的指针偏移地址 %ecx可存放一般数据可用来做计数器常常将循环次数用它来存放 %edx可存放一般数据可用来存放乘法运算产生的部分积用来存放输入输出的端口地址指针 %esi可存放一般数据可用于串操作中存放源地址对一串数据访问 %edi可存放一般数据可用于串操作中存放目的地址对一串数据访问 %esp用于寻址一个称为堆栈的存储区通过它来访问堆栈数据 %ebp可存放一般数据用来存放访问堆栈段的一个数据区作为基地址 MOVBMOVWMOVL三种指令的区别是它们分别是在大小为 1,2和4个字节的数据上进行操作。 MOVW 把 16 位立即数放到寄存器的底16位高16位清0 MOVT 把 16 位立即数放到寄存器的高16位低 16位不影响 / This is the default interrupt handler :-) / int_msg:.asciz Unknown interrupt\n\r .align 2 ignore_int:pushl %eaxpushl %ecxpushl %edxpush %dspush %espush %fsmovl \(0x10,%eaxmov %ax,%dsmov %ax,%esmov %ax,%fspushl \)int_msgcall printkpopl %eaxpop %fspop %espop %dspopl %edxpopl %ecxpopl %eaxiretprintk是一个函数被定义在linuxsrc/kernel/printk.c的printk函数。 我们看一下中断描述符的组成 Offset对应中断服务程序的段内偏移地址 Selector所在段选择符 DPL描述符特权级 P段存在标志 TYPE段描述符类型 创建IDT表是重建保护模式下中断服务体系的开始程序先让所有的中断描述符默认指向ignore_int这个位置将来main函数里面还要让中断描述符对应具体的中断服务程序之后还要对IDT寄存器的值进行设置这种初始化操作可以防止无意中覆盖代码或数据引起的逻辑混乱以及对开发中的误操作给出及时的提示。IDT有256个表项实际只使用了几十个对于误用未使用的中断描述符这样的提示信息可以提醒开发人员注意错误。 实际上这段程序的作用就是设置中断描述符表中断嘛对应着中断号你发起中断给中断号执行中断程序此时所有的中断号都指向了一个打印程序hhhh这个打印程序就是默认的中断处理程序之后会被重新设置的中断覆盖。 建立新的GDT /** setup_gdt** This routines sets up a new gdt and loads it. Only two entries are currently built, the same* ones that were built in init.s. The routine* is VERY complicated at two whole lines, so this* rather long comment is certainly needed :-).* This routine will beoverwritten by the page tables.*/ setup_gdt:lgdt gdt_descrret这个是构造好了的 .align 2 .word 0 idt_descr:.word 256*8-1 # idt contains 256 entries.long idt .align 2 .word 0 gdt_descr:.word 2568-1 # so does gdt (not that thats any.long gdt # magic number, but it works for me :^).align 8 idt: .fill 256,8,0 # idt is uninitializedgdt: .quad 0x0000000000000000 / NULL descriptor /.quad 0x00c09a0000000fff / 16Mb /.quad 0x00c0920000000fff / 16Mb /.quad 0x0000000000000000 / TEMPORARY - dont use /.fill 252,8,0 / space for LDTs and TSSs etc /最后的gdt:就是gdt表的样子其实吧和我们之前设置的gdt表一模一样只是换个位置。第零段位空第一段指向代码段第二段指向数据段第三段为空剩余的252项留给任务状态段描述符TSS和局部描述符LDT 这里我们搞好了之后 movl $0x10,%eax # reload all the segment registersmov %ax,%ds # after changing gdt. CS was alreadymov %ax,%es # reloaded in setup_gdtmov %ax,%fsmov %ax,%gslss stack_start,%espxorl %eax,%eax然后又来了一遍加载每次更新GDT之后由于段描述符的变化我们必须重新加载一遍保证与最新的保持一致。 1: incl %eax # check that A20 really IS enabledmovl %eax,0x000000 # loop forever if it isntcmpl %eax,0x100000je 1b这部分开始检查A20是否真正的开启了防止出了差错否则就一直循环。 /** NOTE! 486 should set bit 16, to check for write-protect in supervisor mode. Then it would be unnecessary with the verify_area()-calls.* 486 users probably want to set the NE (#5) bit also, so as to use* int 16 for math errors./movl %cr0,%eax # check math chipandl $0x80000011,%eax # Save PG,PE,ET / orl \(0x10020,%eax here for 486 might be good */orl \)2,%eax # set MPmovl %eax,%cr0call check_x87jmp after_page_tables这段代码就是检查数字协处理器芯片是否存在。这个和硬件相关确认无误后跳转 after_page_tables:pushl \(0 # These are the parameters to main :-)pushl \)0pushl \(0pushl \)L6 # return address for main, if it decides to.pushl \(mainjmp setup_paging L6:jmp L6 # main should never return here, but# just in case, we know what happens.到这里我们开始压栈压栈完毕后然后跳转到setup_paging 开启分页 /** Setup_paging** This routine sets up paging by setting the page bit* in cr0. The page tables are set up, identity-mapping* the first 16MB. The pager assumes that no illegal* addresses are produced (ie 4Mb on a 4Mb machine).** NOTE! Although all physical memory should be identity* mapped by this routine, only the kernel page functions* use the 1Mb addresses directly. All normal functions* use just the lower 1Mb, or the local data space, which* will be mapped to some other place - mm keeps track of* that.** For those with more memory than 16 Mb - tough luck. Ive* not got it, why should you :-) The source is here. Change* it. (Seriously - it shouldnt be too difficult. Mostly* change some constants etc. I left it at 16Mb, as my machine* even cannot be extended past that (ok, but it was cheap :-)* Ive tried to show which constants to change by having* some kind of marker at them (search for 16Mb), but I* wont guarantee thats all :-( )*/ .align 2 setup_paging:movl \)10245,%ecx / 5 pages - pg_dir4 page tables /xorl %eax,%eaxxorl %edi,%edi / pg_dir is at 0x000 /cld;rep;stoslmovl $pg07,pg_dir / set present bit/user r/w /movl $pg17,pg_dir4 / ——— ——— /movl $pg27,pg_dir8 / ——— ——— /movl $pg37,pg_dir12 / ——— ——— /movl \(pg34092,%edimovl \)0xfff007,%eax / 16Mb - 4096 7 (r/w user,p) /std 1: stosl / fill pages backwards - more efficient :-) /subl $0x1000,%eaxjge 1bxorl %eax,%eax / pg_dir is at 0x0000 /movl %eax,%cr3 / cr3 - page directory start /movl %cr0,%eaxorl $0x80000000,%eaxmovl %eax,%cr0 / set paging (PG) bit /ret / this also flushes prefetch-queue /这些代码会让改Linux内核向现代操作系统更近了一步开启分页保护。分页的情况如下 /** I put the kernel page tables right after the page directory, using 4 of them to span 16 Mb of physical memory. People with* more than 16MB will have to expand this./ .org 0x1000 pg0:.org 0x2000 pg1:.org 0x3000 pg2:.org 0x4000 pg3:.org 0x5000我们回顾一下分页的一些知识开启分页需要将CR0寄存器中的PG位开启开启这个标志之前必须已经或者同步开启 PE标志位PG 0且PE 0处理器工作在实地址模式下。PG 0且PE 1处理器工作在没有开启分页机制的保护模式下。PG 1且PE 0在PE没有开启的情况下无法开启PG。PG 1且PE 1处理器工作在开启了分页机制的保护模式下。 我们没开启分页模式之前我们是怎么找到一个物理地址呢段寄存器和从全局描述符表中取出段基地址然后加上偏移地址得到真实的物理地址但是开启分页机制之后又会多一步转换分段机制得到线性地址之后还需要多一步分页机制的转换要是不开启分页机制的话这一步就直接是物理地址了。 比如我们的线性地址是 0000000011_0100000000_000000000000 经过了段机制的转换那从这个线性地址到实际物理地址的转换是怎么样的呢 高 10 位负责在页目录表中找到一个页目录项这个页目录项的值加上中间 10 位拼接后的地址去页表中去寻找一个页表项这个页表项的值再加上后 12 位偏移地址就是最终的物理地址。这一切的操作都由计算机的一个硬件角MMU也叫内存管理单元或者分页内存管理单元这个部件来讲虚拟地址转换为物理地址。整个过程我们是不需要关心的因为这个是硬件层的操作了操作系统只是在软件层。 我们的这种分配方式为二级页表一层页目录项PDE一层叫页表项PTE之后再开启分页机制的开关。 所以这段代码就是帮我们把页表和页目录表在内存中写好之后开启 cr0 寄存器的分页开关仅此而已。 按照当时的Linux的任务总可使用的内存不超过16MB也就是最大地址空间为 0xFFFFF 。而按照当前的页目录表和页表这种机制1 个页目录表最多包含 1024 个页目录项也就是 1024 个页表1 个页表最多包含 1024 个页表项也就是 1024 个页1 页为 4KB因为有 12 位偏移地址因此16M 的地址空间可以用 1 个页目录表 4 个页表搞定。 这一段代码还包括将页目录表放在内存地址的最开头记得pg_dir这个标签嘛现在就是将页目录表放在了这个位置也就是内存的一开头的位置然后紧接着他放四个页表在页目录表和页表中填写好数值来覆盖16MB的内存随后开启分页机制。 同时如 idt 和 gdt 一样我们也需要通过一个寄存器告诉 CPU 我们把这些页表放在了哪里就是这段代码。 xor eax,eax mov cr3,eax具体的页表设置好后映射内存的情况时怎样呢那就看页表的具体数据 setup_paging:…cld;rep;stoslmovl $pg07,pg_dir / set present bit/user r/w /movl $pg17,pg_dir4 / ——— ——— /movl $pg27,pg_dir8 / ——— ——— /movl $pg37,pg_dir12 / ——— ——— /movl \(pg34092,%edimovl \)0xfff007,%eax / 16Mb - 4096 7 (r/w user,p) /std 1: stosl / fill pages backwards - more efficient :-) */subl \(0x1000,%eaxjge 1b前五行表示页目录表的前 4 个页目录项分别指向 4 个页表。比如页目录项中的第一项 [eax] 被赋值为 pg07也就是 0x00001007根据页目录项的格式表示页表地址为 0x1000页属性为 0x07 表示改页存在、用户可读写。 后面几行表示填充 4 个页表的每一项一共 4*10244096 项依次映射到内存的前 16MB 空间。 现在只有四个页目录项也就是将前面的16M的线性地址空间与16M的物理地址空间对应上了。 具体的分段与分页的理论我们这里就不在详细的展开了如果需要详细的理论分析可以看我的另一篇文章详细解释了分段和分页理论。 为什么要重设GDT 为什么要废除原来的GDT而重新设计一套GDT呢别以为有啥高深的原因就是因为原来GDT所在的位置是设计代码时再setup.s里面设置的数据将来这个setup.s 所在的内存位置会在设计缓冲区时被覆盖掉如果不改变位置将来GDT的内容就会被覆盖掉其实吧就是管理内存懂了吧 目前的内存分布 内存位置内容0x00000 - 0x00FFF页目录表0x01000 - 0x01FFF页表00x02000 - 0x02FFF页表10x03000 - 0x03FFF页表20x04000 - 0x04FFF页表30x05000 - 0x05400软盘缓冲区0x05401 - 0x054B7空0x054B8 - 0x05CB7中断描述符表IDT0x05CB8 - 0x064B7全局描述符表GDT 我们上面做了那么多工作其实都是为了达到现在的内存状态啊 准备进入main 我们上面讲了分页代码这里有一个骚操作帮助进入main函数 after_page_tables:pushl \)0 # These are the parameters to main :-)pushl \(0pushl \)0pushl \(L6 # return address for main, if it decides to.pushl \)mainjmp setup_paging L6:jmp L6 # main should never return here, but# just in case, we know what happens.分页之后我们执行了这个代码这个代码就有意思了push指令是压榨五个push指令过去以后栈会变成这个样子 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XSud4WfK-1676125712644)(C:\Users\LoveSS\AppData\Roaming\Typora\typora-user-images\image-20230211212519126.png)] 然后注意setup_paging 最后一个指令是 ret也就是我们上一回讲的设置分页的代码的最后一个指令形象地说它叫返回指令但 CPU 可没有那么聪明它并不知道该返回到哪里执行只是很机械地把栈顶的元素值当做返回地址跳转去那里执行。 再具体说是把 esp 寄存器栈顶地址所指向的内存处的值赋值给 eip 寄存器而 cs:eip 就是 CPU 要执行的下一条指令的地址。而此时栈顶刚好是 main.c 里写的 main 函数的内存地址是我们刚刚特意压入栈的所以 CPU 就理所应当跳过来了。 call指令会自动将EIP的值压栈保护返回现场然后执行被调函数程序等到被调函数程序执行完毕后也就是被调函数ret指令时自动出栈给EIP并还原现场继续执行call的下一条指令对操作系统而言这个指令就有点怪异了如果call调用了操作系统的main函数那ret返回给谁呢难道还有一个更底层的程序接受操作系统的返回嘛操作系统已经是最底层的系统了那还怎么更底层呢所以这里使用了这么一个技巧直接将main压栈ret返回时直接就执行main函数了而其他的三个压栈的 0本意是作为 main 函数的参数但实际上似乎也没有用到所以也不必关心。 终于终于我们的底层搞完了进入main函数了这三章内容讲了我们是怎么从加电到加载完内核并跳到内核程序的我们梳理一下 加载内核的流程 目前的内存分布 内存位置内容0x00000 - 0x00FFF页目录表0x01000 - 0x01FFF页表00x02000 - 0x02FFF页表10x03000 - 0x03FFF页表20x04000 - 0x04FFF页表30x05000 - 0x05400软盘缓冲区0x05401 - 0x054B7空0x054B8 - 0x05CB7中断描述符表IDT0x05CB8 - 0x064B7全局描述符表GDT0x064B8 - 0x80000system内核0x80001 - 0x8FFFF空0x90000 - 0x901FCsetup在内存中保存的信息0x901FD - 0x901FF空0x90200 - 0x90A00setup程序0x90A00 - 0x9FF00栈栈指向0x9FF00并未占完0x9FF01 - 0xFDFFF空
- 上一篇: 山东建设厅官方网站二建报名进地铁建设公司网站
- 下一篇: 山东建设信息网站要制作自己的网站需要什么
相关文章
-
山东建设厅官方网站二建报名进地铁建设公司网站
山东建设厅官方网站二建报名进地铁建设公司网站
- 技术栈
- 2026年03月21日
-
山东济南网站推广国内用wordpress建设
山东济南网站推广国内用wordpress建设
- 技术栈
- 2026年03月21日
-
山东济南网站建设公司排名沧州网站建设的技术方案
山东济南网站建设公司排名沧州网站建设的技术方案
- 技术栈
- 2026年03月21日
-
山东建设信息网站要制作自己的网站需要什么
山东建设信息网站要制作自己的网站需要什么
- 技术栈
- 2026年03月21日
-
山东建设银行官网网站网站建设方案书怎么写
山东建设银行官网网站网站建设方案书怎么写
- 技术栈
- 2026年03月21日
-
山东丽天建设集团网站如何做一家门户网站
山东丽天建设集团网站如何做一家门户网站
- 技术栈
- 2026年03月21日






