当前位置: 首页 > news >正文

网站快捷导航ie怎么做自己做企业网站详细流程免费

网站快捷导航ie怎么做,自己做企业网站详细流程免费,wordpress加载很慢,wordpress当中加入论坛大家好#xff0c;我是呼噜噜#xff0c;好久没有更新old linux了#xff0c;本文接着上一篇文章图解CPU的实模式与保护模式#xff0c;继续向着操作系统内核的世界前进#xff0c;一起来看看heads.s as86 与GNU as 首先我们得了解一个事实#xff0c;在Linux0.12内核源…大家好我是呼噜噜好久没有更新old linux了本文接着上一篇文章图解CPU的实模式与保护模式继续向着操作系统内核的世界前进一起来看看heads.s as86 与GNU as 首先我们得了解一个事实在Linux0.12内核源码中其实是使用了2套汇编器Assembler的一种是Intel8086汇编编译器as86和配套的链接器ld86并一种就是GNU as(gas)使用 GNU ld 链接器来链接产生的目标文件。 为什么使用了2套汇编器 我们知道Linux0.12中bootsect.s和setup.s是实模式下运行的16位代码程序而那个时候的GNU as 汇编编译器无法支持16位实模式代码程序编译所以Linus不得不使用as86和ld86其语法近似Intel语法 而从head.s开始的内核完全都是在保护模式下运行了操作系统system模块中其余所有汇编语言程序包括 C 语言产生的汇编程序都是使用GNU as 汇编编译器使用的是ATT语法。直到Linux内核2.4.x后bootsect.s和head.s程序才完全使用统一的GNU as 来编写 2种语法虽然是有所区别但其实都是类似的需要注意的最基本的区别是ATT语法中mov赋值的方向是从左到右 在Linux0.12内核源码解读(3)-Setup.S中最后我们说到CPU 进入了 32 位保护模式跳到了内存零地址处开始执行代码。先来回顾一下执行完setup.S时的内存分布情况: 作者小牛呼噜噜 此时从内存零地址处存放的system模块其首部是head.s代码即head.s代码从地址0处开始存放因此setup结束后执行的就是head.s文件 head.s主要是进入进行保护模式之后的初始化主要初始化些什么呢呼噜噜画了个流程图建议大家跟着下面流程图阅读以下全文 如果有人对本文中操作系统一系列初始化操作感到疑惑比如为什么要设置的话等之类的问题建议先看笔者前一篇文章图解CPU的实模式与保护模式 设置段寄存器和系统堆栈 _pg_dir: # 页目录将会存放在这里 startup_32:movl $0x10,%eax # 32位ax寄存器赋值0x10mov %ax,%dsmov %ax,%esmov %ax,%fsmov %ax,%gslss _stack_start,%esp #设置栈(系统栈)我们可以看到上面这段源代码中_pg_dir这个很重要和分页机制有关主要是标识内核分页机制完成后的内核起始地址(零地址)页目录将会存放在这里这个我们下文再讲。 movl $0x10,%eax将32位ax寄存器赋值0x10MOV类指令是最简单的数据传送指令这类指令把数据从源位置复制到目的位置需要声明要传送的数据元素的长度一般有以下几种 指令描述位数movb传送字节8位movw传送字16位movl传送双字32位movq传送四字64位 对于 GNU 汇编每个直接操作数要以$开始否则表示地址。每个寄存器名都要以%开头eax 表示是 32 位的 ax 寄存器。 如果面试官提问head.s中0x10这个地址具体是指向哪呢 这个是虽然简单但很有迷惑性的首先我们得知道当操作系统执行head.s的时候已经进入了保护模式此时段寄存器不再表示段的基地址而是表示段选择符(也叫段选择子) 段选择符描述b1-b0请求特权级RPLb20:全局描述符表 1:局部描述符表b15-b3描述符表项的索引, 指出选择第几项描述符(从0开始) 所以我们需要先0x10写成16位二进制形式(高位补零)0b0000 0000 0001 0000所以对应的段选择符请求特权级为 0RPL00、所指向的描述符存放在GDTT10、所指向的描述符索引为2DI0000 000000010也就是指向GDT全局段描述符表第3项(从0开始) 接着分别给 ds、es、fs、gs 这几个段寄存器赋值为0x10让这些寄存器都指向GDT的第3项 lss _stack_start,%esp主要作用是设置系统栈汇编指令lss会分别给一个段寄存器和一个16位通用寄存器赋值那么也就是说将操作数_stack_start的值传送给指定ss:esp其中ss就是堆栈寄存器存放堆栈段的段基址(实模式)保护模式下存放的就是段选择符只能存放16位的数据esp是指向栈顶的通用寄存器能够存放32位的数据 stack_start是一个标号它定义在kernel/sched.c文件中 #定义用户堆栈, PAGE_SIZE4096,所以user_stack长度为1024 long user_stack [PAGE_SIZE40962 ] ;struct {long * a;short b;} stack_start { user_stack [PAGE_SIZE2] , 0x10 };我们可以发现这是一个结构体将stack_start的值传给ss:esplss指令会把stack_start指向的内存地址的前四字节(32位)装入ESP寄存器后两字节(16位)装入SS段寄存器即ss0x10,esp user_stack [1024] 设置IDT call setup_idt #设置IDTsetup_idt:lea ignore_int,%edx #将 ignore_int 的有效地址偏移值值 赋值给 edx 寄存器movl $0x00080000,%eax # 将段选择符 0x0008 置入 eax 的高 16 位中movw %dx,%ax /* selector 0x0008 cs */ # 偏移值的低 16 位置入 eax 的低 16 位中。此时 eax 含有门描述符低 4 字节的值movw $0x8E00,%dx /* interrupt gate - dpl0, present */ #此时 edx 含有门描述符高 4 字节的值lea _idt,%edi # _idt 是中断描述符表的地址 取idt的偏移给edimov $256,%ecx #循环256次 rp_sidt:movl %eax,(%edi) # 将哑中断门描述符存入表中movl %edx,4(%edi) # eax 内容放到 edi4 所指内存位置处。addl $8,%edi # edi 指向表中下一项dec %ecx # 循环减1 jne rp_sidt jne 表示zf0跳转lidt idt_descr # 加载IDTR !!!retidt_descr:.word 256*8-1 # idt contains 256 entries 共 256 项是CPU寄存器中的值.long _idt .align 2 .word 0_idt: .fill 256,8,0 # idt is uninitialized这个是在内存中的 IDTInterrupt Descriptor Table即中断描述符表记录着0~255的中断号和调用函数之间的关系与中段向量表有些相似但要包含更多的信息。 不知道大家还记不记得在setup.S中临时将IDT临时设置为一个空表自此int n 不再是DOS中断了而是去IDT表中找到中断函数的地址再执行 上面这段代码实现了256 个中断描述符的设置各个中断描述符表项都指向一个ignore_int的函数地址其中ignore_int是一个只报错误的哑中断子程序内核在随后的初始化过程中会替换覆盖那些真正实用的中断描述符项 我们查看ignore_int会发现它就是去打印一串字符Unknown interrupt提示报错 int_msg:.asciz Unknown interrupt\n\r .align 2 ignore_int:pushl %eaxpushl %ecxpushl %edxpush %ds ## 注意ds,es,fs,gs 等虽然是 16 位的寄存器但入栈后仍然会以 32 位的形式入栈也即需要占用 4 个字节的堆栈空间push %espush %fsmovl $0x10,%eaxmov %ax,%dsmov %ax,%esmov %ax,%fspushl $int_msg # 把调用 printk 函数的参数指针地址入栈call _printkpopl %eaxpop %fspop %espop %dspopl %edxpopl %ecxpopl %eaxiret # 中断返回把中断调用时压入栈的 CPU 标志寄存器32 位值也弹出中断对操作系统来说非常重要可以跟硬件例如键盘鼠标显卡等产生交互没有中断操作系统就缺胳膊少腿当中断发生时CPU获取到中断向量后通过IDTR的值去查找IDT中断描述符表得到相应的中断描述符再根据中断描述符记录的信息来作权限判断运行级别转换最终调用相应的中断处理程序 设置GDT 我们来看下其相关源码 call setup_gdt #设置GDTsetup_gdt:lgdt gdt_descr # 加载全局描述符表寄存器(内容已设置好)retgdt_descr:.word 256*8-1 # so does gdt (not that thats any.long _gdt # magic number, but it works for me :^).align 3_gdt: .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其实这里和我们在Setup.S设置的GDT是一样的笔者这里再贴一下之前的代码比较一下发现是初始化出来的GDT是基本是一模一样的除了此时段限长不是原来的8MB而是现在的16MB gdt: ! 描述符表由多个8字节长的描述符项组成。这里给出了 3 个描述符项。.word 0,0,0,0 ! dummy 第1个为空描述符无用但必须存在.word 0x07FF ! 段界限为 8Mlimit2047 (2048*40968Mb) 第2个为空描述符.word 0x0000 ! 段基址为 0.word 0x9A00 ! code read/exec P1, DPL00, S1, 代码段只读可执行.word 0x00C0 ! granularity4096, 386 .word 0x07FF ! 段界限为 8M - limit2047 (2048*40968Mb) 第3个为空描述符.word 0x0000 ! 段基址为 0.word 0x9200 ! P1, DPL00, S1, 数据段可读可写.word 0x00C0 ! granularity4096, 386这里主要是为了防止GDT这块内存区域被其他程序覆盖使用head废除Setup.S设置的GDT并在内存中重新创建一个全新的全局描述符表 重复设置段寄存器与系统堆栈 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,%esp这里重复设置段寄存器与系统堆栈也是为了安全起见因为它们所指向的原描述符所指向的段的段限长为 8MB而刚刚在setup_gdt** 修改了 GDT段限长已经变为 16MB**所以当访问 8MB 以上的地址空间时有可能会产生段限长超限报警。为了防止这类可能发生的情况在这里重载刷新所有的段寄存器 检查A20是否打开 xorl %eax,%eax #清零,xorl只需要2个字节而是用movl实现清零需要5个字节 1: incl %eax # 检查A20是否开启movl %eax,0x000000 # 如果不是则永远循环cmpl %eax,0x100000je 1b # 1b表示向后(backward)跳转到标号 1 去引入A20是为解决80286的一个bug而引入的什么bug?请移步看前文Linux0.12内核源码解读(3)-Setup.S 在A20关闭的情况下系统仍然使用8086/8088的方式计算机处于20位的寻址模式,访问超过0xFFFFF2^201MB内存时会自动回卷比如0x100000会回卷到0x000000当在A20打开的情况下才会突破地址信号线20位的宽度变成32位可用实现最大寻址空间4GB 所以这部分代码是通过在内存0x000000处写入任意数据并和0x100000处比较是否一致来检查A20是否打开。如果一直相同的话说明内存回卷 A20没有打开然后就会一直比较下去即死循环。 检查x87协处理器是否存在 为了弥补 x86 系列在进行浮点计算时的不足Intel 于 1980 年推出了 x87 系列数学协处理器那时是一个外置的、可选的芯片。1989 年Intel 发布了 486 处理器。自从 486 开始以后的 CPU 一般都内置了协处理器。这样对于 486 以前的计算机而言操作系统检验 x87 协处理器是否存在就非常有必要了 /** 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.*注意! 在下面这段程序中486 应该将位 16 置位以检查在超级用户模式下的写保护,* 此后 verify_area() 调用就不需要了。486 的用户通常也会想将 NE(#5)置位以便* 对数学协处理器的出错使用 int 16*/movl %cr0,%eax # 校验数学芯片andl $0x80000011,%eax # Save PG,PE,ETorl $2,%eax # set MPmovl %eax,%cr0call check_x87jmp after_page_tablescheck_x87:fninit # 向协处理器发出初始化命令fstsw %ax # 取协处理器状态字到 ax 寄存器中cmpb $0,%al # 初始化后状态字应该为 0否则说明协处理器不存在je 1f /* no coprocessor: have to set bits */movl %cr0,%eax # 如果存在则向前跳转到标号 1 处否则改写 cr0xorl $6,%eax /* reset MP, set EM */movl %eax,%cr0ret .align 2 # align 是一汇编指示符。其含义是指存储边界对齐调整2表示把随后的代码或数据的偏移位置# 调整到地址值最后 2 比特位为零的位置2^2即按 4 字节方式对齐内存地址 1: .byte 0xDB,0xE4 /* fsetpm for 287, ignored by 387 */ret 这部分源码主要是用于检查数学协处理器芯片是否存在。方法是修改控制寄存器 CR0在假设存在协处理器的情况下执行一个协处理器指令如果出错的话则说明协处理器芯片不存在 需要设置 CR0 中的协处理器仿真位 EM位 2并复位协处理器存在标志 MP位 1这部分简单了解一下即可 构建分页管理机制 检查完数学协处理器芯片是否存在紧接着就执行jmp after_page_tables跳转after_page_tables这个标号处 after_page_tables:# 先将main函数参数L6标号和main函数入口地址压栈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应该永远不会回到这里但以防万一我们需要知道发生了什么 先将main函数参数L6标号和main函数入口地址压入栈中等待被使用我们这里先卖个关子讲完分页再讲解 jmp setup_paging 跳到分页设置想要理解这部分你得先了解什么是段页机制详情见图解CPU的实模式与保护模式 记住这张图的分页机制理解线性地址前10位中间10位后12位分别代表什么CR3指向哪边分页机制的原理我们接着阅读以下部分 内存页清零 setup_paging:movl $1024*5,%ecx xorl %eax,%eax # 清零xorl %edi,%edi # 清零并让页目录从 0x000 地址开始cld;rep;stosl # eax 内容存到 es:edi 所指内存位置处且 edi 增 4其中 ecx是计数器, 是重复(rep)前缀指令和loop指令的内定计数器表示控制循环次数cld相对应的指令是std二者均是用来操作方向标志位DFDirection Flag。cld使DF 复位即是让DF0std使DF置位即DF1.这两个指令用于串操作指令中。通过执行cld或std指令可以控制方向标志DF决定内存地址是增大DF0向高地址增加还是减小DF1向地地址减小rep 表示当 ecx0 时循环继续反之停止stosl指令相当于将eax中的值保存到es:edi指向的地址中若设置了EFLAGS中的方向位置位(即在STOSL指令前使用STD指令)则EDI自减4否则(使用CLD指令)EDI自增4 这一小段代码连起来就是按4字节的速度循环清空内存每次循环清空的内存范围** **1024*44096字节恰好是一个页也就是最终清空5页内存1 页目录 4 页页表 设置页目录表、页表 因为我们内核共有 4 个页表所以只需设置 4 项。 # 分别设置4个页表movl $pg07,_pg_dir /* set present bit/user r/w */movl $pg17,_pg_dir4 /* --------- --------- */movl $pg27,_pg_dir8 /* --------- --------- */movl $pg37,_pg_dir12 /* --------- --------- */可能就有人会问了为啥就只有 4 个页表不是可以设置1024项嘛 Linx0.12 当时规定最大寻址空间0xFFFFFF也就是16M而1个页目录表或者一个页表最多有1024 个项页的大小固定为4KB4页表数* 1024* 4KB 16MB所以只需前4个页表就能够支持16M寻址 咳咳还记得我们本文一开始讲的_pg_dir表示页目录表将会存放在这里(零地址处)紧挨着的其实还有4个页表 .org 0x1000 # .ORG伪指令用来表示起始的偏移地址紧接着ORG的数值就是偏移地址的起始值 pg0:.org 0x2000 pg1:.org 0x3000 pg2:.org 0x4000 pg3:.org 0x5000页目录项的结构与页表中项的结构一样4 个字节为 1 项。 我们简单举个例子 这里的$pg07其实就表示0x00001007是页目录表中的第 1 项我们按线性地址转换为对应的0b0000000000 0000000001 000000000111按照页目录和页表的结构我们知晓第 1 个页表所在的地址 0000000001 0x1000第 1 个页表的属性标志 0000000001110x07在二进制下根据这3个1分别表示页存在P1、用户可读写RW1、特权为用户态US1表示该页存在、用户可读写 原本页表0到页表3处的代码(也就是head.s17行到114行之间所有执行过的代码)全部清空此时页目录表和页表在内存的分布情况 | ... ——————— 0x5000 | 页表3 ——————— 0x4000 | 页表2 ——————— 0x3000 | 页表1 ——————— 0x2000,页的大小4K | 页表0 ——————— 0x1000 | 页目录表 ——————— 0x0000接着就是填充4个页表中所有项的内容,下面是从最后一个页表的最后一项开始按倒退顺序填充数据 movl $pg34092,%edi # edi最后一页表的最后一项movl $0xfff007,%eax /* 16Mb - 4096 7 (r/w user,p) */std #方向位置位edi 值递减(4 字节)。 1: stosl /* fill pages backwards - more efficient :-) */subl $0x1000,%eax # 每填写好一项物理地址值减 0x1000。jge 1b /*1b 表示向后跳转到标号1处如果小于 0 则说明全添写好了*/设置CR3和CR0 接着设置页目录表基址寄存器cr3指向页目录表。cr3中保存的是页目录表的物理内存地址然后设置启动使用分页处理cr0 的 PG 标志cr0中含有控制处理器操作模式和状态的系统控制标志 xorl %eax,%eax # 页目录表在 0x0000 处。movl %eax,%cr3 # 设置页目录基址寄存器CR3的值指向页目录表。页目录表在0x0000处movl %cr0,%eaxorl $0x80000000,%eax movl %eax,%cr0 /* 设置启动使用分页处理CR0的PG标志置位 */ret /* this also flushes prefetch-queue */需要注意的是当执行完这行代码movl %eax,%cr0后标志着操作系统正式开启分页此时段部件产生的地址就不再被看成物理地址被称为线性地址而是要送往页部件进行变换以得到真正的物理地址。 最后ret指令很重要它这里有2个作用 在改变分页处理标志后要求使用转移指令刷新预取指令队列这里用的是返回指令ret。将之前压入栈中的 main()程序入口地址弹出并跳转到 init/main.c 程序去运行。 乍眼一看ret指令怎么就和main函数联系到一起了我们马上详细来聊聊其中的缘由 跳转至main函数 跳转至main函数的准备工作其实在head.s的早就开始了但最后一步由ret指令执行的 after_page_tables:pushl $0 # These are the parameters to main :-)pushl $0pushl $0pushl $L6 # return address for main, if it decides to.pushl $_main... setup_paging:...ret在after_page_tables标号处先将main函数参数L6标号和main函数入口地址压入栈中等待被使用。这些参数比如3个0后续实际上也没有用到。 L6标号是main函数返回时的跳转地址。 汇编中的参数一般是通过寄存器传递的而C语言中的参数一般是通过栈来传递 直到setup_paging标号处的ret指令正好将之前压入栈中的 main()程序入口地址弹出这个时候CPU会把esp寄存器始终指向栈顶地址指向的内存地址处的值赋值给eip寄存器 而eip指令指针寄存器存储着下一条指令的地址通过CS:EIP联合指向即将执行的下一条指令。对于顺序执行的指令EIP从前一条指令边界移到下一条指令边界上对于控制转移指令例如JMP,JCC, CALLRET和IRET指令EIP会向前或先后跳跃数条指令。 一般情况下程序是不能直接读取或修改EIP寄存器的值但是可以隐式地通过控制转移指令JMPJCALL和RET中断和异常来间接控制EIP。要想读取到EIP寄存器的值唯一的手段是执行CALL指令然后从程序栈中读取返回指令指针。这里是通过修改程序栈中返回指令指针的值然后执行RET指令间接的加载EIP寄存器 最终CPU跳转到 init/main.c处去运行程序代码。 当执行完ret指令标志着head.s程序到此就真正结束了 后续就进入了我们倍感亲切的C程序世界我们下期再见~~ 参考资料 https://elixir.bootlin.com/linux/0.12/source/boot/head.s 英特尔® 64 位和 IA-32 架构开发人员手册卷 3A-英特尔® 《Linux内核完全注释5.0》 作者小牛呼噜噜 首发于公众号小牛呼噜噜系列文章还有 聊聊x86计算机启动发生的事Linux0.12内核源码解读(2)-Bootsect.SLinux0.12内核源码解读(3)-Setup.S图解CPU的实模式与保护模式Linux0.12内核源码解读(5)-head.sLinux0.12内核源码解读(6)-main.cLinux0.12内核源码解读(7)-陷阱门初始化图解计算机中断Linux0.12内核源码解读(9)-blk_dev_init和chr_dev_init什么是系统调用机制结合Linux0.12源码图解tty是什么聊聊linux0.12中tty与time的初始化linux0.12内核源码解读(12)-任务调度初始化sched_init
http://www.dnsts.com.cn/news/144046.html

相关文章:

  • 做网站用小公司还是大公司好怎么创建网页的快捷方式到桌面
  • 网帆-网站建设官方店适合个人开网店的平台
  • 国内做的比较好的旅游网站手机wap网站免费建站
  • seo网站排名厂商定制专业建设思路
  • 为什么网站突然打不开行业网站建设教程
  • 给朋友网站做宣传怎么写wordpress图片站优化
  • 信息手机网站模板做网站推广哪些
  • 网站开发背景知识微网站设计与开发是什么
  • 网站的功能规范厦门外贸网站建设
  • 检测网站是否被墙wordpress自定义页
  • 做响应网站的素材网站南昌百度推广公司
  • 国外有哪些做建筑材料的网站亚马逊外贸网站如何做
  • 文具网站建设策划书做财经类新闻的网站
  • 建基建设集团网站做门户网站难吗
  • 个人网站建设方案模板网络营销的策划方案
  • 网站建设公司优惠中男女做暧视频网站免费
  • 荷城网站制作公司最新黑帽seo培训
  • 免费php网站模板下载啥是网络推广
  • 微网站方案萧涵 wordpress
  • 东莞企业网站推广网站开发企业标准
  • 网站开发的职位要求新手学做网站 pdf 下载
  • 双语网站建设定制开发学历提升文案
  • 网站备案关闭嘉兴网站建设制作
  • 一 网站建设管理基本情况wordpress默认邮件在哪里设置
  • 做网站编辑累不累wordpress算前端
  • 做网站卖得出去吗wordpress 懒人图库
  • 大连英文网站建设杭州产品设计公司有哪些
  • 企业网站设计代码百度 营销推广怎么做
  • 南安市城乡住房建设局网站手机网站 微信链接
  • 云南省住房和城乡建设厅网站首页个人简历怎么写简短又吸引人