Chinaunix首页 | 论坛 | 博客
  • 博客访问: 206909
  • 博文数量: 38
  • 博客积分: 2060
  • 博客等级: 大尉
  • 技术积分: 388
  • 用 户 组: 普通用户
  • 注册时间: 2009-03-17 10:10
文章分类

全部博文(38)

文章存档

2011年(1)

2009年(37)

我的朋友

分类: LINUX

2009-10-15 19:03:54

(以下内容大部分转至网络)

 

段地址,偏移地址

    对于8086来说,由于其地址总线宽度为20位,但是其寄存器宽度为16位,那么一个16位的寄存器就无法存放20位的地址,所以引入分段,断地址16位,偏移地址同样可以16(通用寄存器宽度决定的)。于是一个地址的绝对地址=段地址*16+偏移地址,这样系统的寻址范围就达到了1M。在这1M的内存中,每64K字节分成一段,段地址就是这每一段的首字节的实际地址/16,偏移地址是以这个段地址为基准的,表明这个字节在这一段内存中的相对地址。所以一个地址的绝对地址=段地址*16+偏移地址。

 

地址总线和数据总线

地址总线宽度决定了机器能访问的最大的内存,80386以后的32位机器的地址总线宽度为32位,所能访问的地址空间为4G。数据总线宽度是指CPU一次能够读取的数据的位数。通常说的32位,64位的计算机,这个位数指的是CPU GPRsGeneral-Purpose Registers,通用寄存器)的数据宽度为64

 

64K*32位的主存储器

64K=2^6*2^10 得出16位地址总线.

32位就是数据总线的宽度,

那么地址和数据总线的和就是16+32=48.

 

实模式与保护模式(转至网友)

 

1x86实模式介绍

    x86体系的处理器刚开始时只有20根地址线,寻址寄存器是16位。我们知道16位的寄存器可以访问64K的地址空间,如果程序要想访问大于64K的内存,就需要把内存分段,每段64K,用段地址+偏移量的方式来访问,这样使20根地址线全用上,最大的寻址空间就可以到1M字节,这在当时已经是非常大的内存空间了。

 

2、实模式的问题与保护模式的出现

    事实上,实模式将整个物理内存看成分段的区域,程序代码和数据位于不同区域,系统程序和用户程序并没有区别对待,而且每一个指针都是指向实际的物理地址。这样一来,用户程序的一个指针如果指向了系统程序区域或其他用户程序区域,并修改了内容,那么对于这个被修改的系统程序或用户程序,其后果就很可能是灾难性的。再者,随着软件的发展,1M的寻址空间已经远远不能满足实际的需求了。最后,对处理器多任务支持需求也日益紧迫,所有这些都促使新技术的出现。

    为了克服实模式下的内存非法访问问题,并满足飞速发展的内存寻址和多任务需求,处理器厂商开发出保护模式。在保护模式中,除了内存寻址空间大大提高;提供了硬件对多任务的支持;物理内存地址也不能直接被程序访问,程序内部的地址(虚拟地址)要由操作系统转化为物理地址去访问,程序对此一无所知。至此,进程(程序的运行态)有了严格的边界,任何其他进程根本没有办法访问不属于自己的物理内存区域,甚至在自己的虚拟地址范围内也不是可以任意访问的,因为有一些虚拟区域已经被放进一些公共系统运行库。这些区域也不能随便修改,若修改就会有出现linux中的段错误,或 Windows中的非法内存访问对话框。

 

3386以上处理器的特点

    386处理器有三种工作方式:实模式、保护模式和虚拟8086模式。

    在保护方式下,全部32条地址线有效,可寻址高达4G字节的物理地址空间;扩充的存储器分段管理机制和可选的存储器分页管理机制,不仅为存储器共享和保护提供了硬件支持,而且为实现虚拟存储器提供了硬件支持;支持多任务,能够快速地进行任务切换和保护任务环境;4个特权级和完善的特权检查机制,既能实现资源共享又能保证代码和数据的安全和保密及任务的隔离;支持虚拟8086方式,便于执行8086程序。

 

4、保护模式下的地址转换

    通过采用段地址加偏移量的方式,80386支持的虚拟地址空间可达64T字节。但由于实际物理内存的大小可能会远小于虚拟地址空间,所以实际上虚拟地址中只有部分才可以真正映射到物理存储器。同时由于每一个任务有一个虚拟地址空间。为了避免多个并行任务的多个虚拟地址空间直接映射到同一个物理地址空间,还需要使用线性地址空间来隔离虚拟地址空间和物理地址空间。线性地址空间由一维的线性地址构成,线性地址空间和物理地址空间是对等。线性地址也是32位长,寻址空间为4G字节。

    在操作系统中,应用程序使用虚拟地址(也即逻辑地址)访问内存,操作系统将虚拟地址转换为线性地址,然后由处理器将线性地址转换为物理地址,但是在交由处理器转换前,操作系统必须设置处理器所需要的相关描述符表和描述符信息。其实在Linux系统中,这在系统启动时就设置好了,而且是设置后就不会再改动了。

 

AT&Tintel汇编语法格式比较(转至网友)

1AT&T 格式Linux 汇编语法格式

    AT&T 汇编格式中,寄存器名要加上 '%' 作为前缀;而在 Intel 汇编格式中,寄存器名不需要加前缀。例如:

    AT&T 格式:  pushl %eax

    Intel 格式: push eax

  

AT&T 汇编格式中,用 '$' 前缀表示一个立即操作数;而在 Intel 汇编格式中,立即数的表示不用带任何前缀。例如:

AT&T 格式:  pushl $1

Intel 格式: push 1

  

AT&T Intel 格式中的源操作数和目标操作数的位置正好相反。在 Intel 汇编格式中,目标操作数在源操作数的左边;而在 AT&T 汇编格式中,目标操作数在源操作数的右边。例如:

AT&T 格式:  addl $1, %eax

Intel 格式: add eax, 1

 

AT&T 汇编格式中,操作数的字长由操作符的最后一个字母决定,后缀'b''w''l'分别表示操作数为字节(byte8 比特)、字(word16 比特)和长字(long32比特);而在 Intel 汇编格式中,操作数的字长是用 "byte ptr" "word ptr" 等前缀来表示的。例如:

AT&T 格式:  movb val, %al

Intel 格式: mov al, byte ptr val

  

  在 AT&T 汇编格式中,绝对转移和调用指令(jump/call)的操作数前要加上'*'作为前缀,而在 Intel 格式中则不需要。

远程转移指令和远程子调用指令的操作码,在 AT&T 汇编格式中为 "ljump" "lcall",而在 Intel 汇编格式中则为 "jmp far" "call far",即:

AT&T 格式:     ljump $section, $offset         lcall $section, $offset

Intel 格式:     jmp far section:offset          call far section:offset

 

与之相应的远程返回指令则为:

AT&T 格式:  lret $stack_adjust

Intel 格式: ret far stack_adjust

  

  在 AT&T 汇编格式中,内存操作数的寻址方式是

  section:disp(base, index, scale)

  

  而在 Intel 汇编格式中,内存操作数的寻址方式为:

  section:[base + index*scale + disp]

  

  由于 Linux 工作在保护模式下,用的是 32 位线性地址,所以在计算地址时不用考虑段基址和偏移量,而是采用如下的地址计算方法:

  disp + base + index * scale

  

  下面是一些内存操作数的例子:

  AT&T 格式

   Intel 格式

  

  movl -4(%ebp), %eax

   mov eax, [ebp - 4]

  

  movl array(, %eax, 4), %eax

   mov eax, [eax*4 + array]

  

  movw array(%ebx, %eax, 4), %cx

   mov cx, [ebx + 4*eax + array]

  

  movb $4, %fs:(%eax)

   mov fs:eax, 4

  

  

  二、Hello World!

  

  既然所有程序设计语言的第一个例子都是在屏幕上打印一个字符串 "Hello World!",那我们也以这种方式来开始介绍 Linux 下的汇编语言程序设计。

  

  在 Linux 操作系统中,你有很多办法可以实现在屏幕上显示一个字符串,但最简洁的方式是使用 Linux 内核提供的系统调用。使用这种方法最大的好处是可以直接和操作系统的内核进行通讯,不需要链接诸如 libc 这样的函数库,也不需要使用 ELF 解释器,因而代码尺寸小且执行速度快。

  

  Linux 是一个运行在保护模式下的 32 位操作系统,采用 flat memory 模式,目前最常用到的是 ELF 格式的二进制代码。一个 ELF 格式的可执行程序通常划分为如下几个部分:.text.data .bss,其中 .text 是只读的代码区,.data 是可读可写的数据区,而 .bss 则是可读可写且没有初始化的数据区。代码区和数据区在 ELF 中统称为 section,根据实际需要你可以使用其它标准的 section,也可以添加自定义 section,但一个 ELF 可执行程序至少应该有一个 .text 部分。下面给出我们的第一个汇编程序,用的是 AT&T 汇编语言格式:

  

  例1. AT&T 格式

  

  #hello.s

  .data # 数据段声明

   msg : .string "Hello, world!\\n" # 要输出的字符串

   len = . - msg # 字串长度

  .text # 代码段声明

  .global _start # 指定入口函数

  _start: # 在屏幕上显示一个字符串

   movl $len, %edx # 参数三:字符串长度

   movl $msg, %ecx # 参数二:要显示的字符串

   movl $1, %ebx # 参数一:文件描述符(stdout)

   movl $4, %eax # 系统调用号(sys_write)

   int $0x80 # 调用内核功能

   # 退出程序

   movl $0,%ebx # 参数一:退出代码

   movl $1,%eax # 系统调用号(sys_exit)

   int $0x80 # 调用内核功能

  

  初次接触到 AT&T 格式的汇编代码时,很多程序员都认为太晦涩难懂了,没有关系,在 Linux 平台上你同样可以使用 Intel 格式来编写汇编程序:

  

  例2. Intel 格式

  ; hello.asm

  section .data ; 数据段声明

   msg db "Hello, world!", 0xA ; 要输出的字符串

   len equ $ - msg ; 字串长度

  section .text ; 代码段声明

  global _start ; 指定入口函数

  _start: ; 在屏幕上显示一个字符串

   mov edx, len ; 参数三:字符串长度

   mov ecx, msg ; 参数二:要显示的字符串

   mov ebx, 1 ; 参数一:文件描述符(stdout)

   mov eax, 4 ; 系统调用号(sys_write)

   int 0x80 ; 调用内核功能

   ; 退出程序

   mov ebx, 0 ; 参数一:退出代码

   mov eax, 1 ; 系统调用号(sys_exit)

   int 0x80 ; 调用内核功能

  

  上面两个汇编程序采用的语法虽然完全不同,但功能却都是调用 Linux 内核提供的 sys_write 来显示一个字符串,然后再调用 sys_exit 退出程序。在 Linux 内核源文件 include/asm-i386/unistd.h 中,可以找到所有系统调用的定义。

 

 
阅读(1743) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~