Linux中的段

发布时间:2008-07-17 15:50:36来源:红联作者:zgzzhanzhan

0:w(5(

摘自我的《Linux操作系统原理与应用》一书

Intel微处理器的段机制是从8086开始提出的,那时引入的段机制解决了从CPU内部16位地址到20位实地址的转换。为了保持这种兼容性,386仍然使用段机制,但比以前复杂得多。因此,Linux内核的设计并没有全部采用Intel所提供的段方案,仅仅有限度地使用了一下分段机制。这不仅简化了Linux内核的设计,而且为把Linux移植到其他平台创造了条件,因为很多RISC处理器并不支持段机制。但是,对段机制相关知识的了解是进入Linux内核的必经之路。

从2.2版开始,Linux让所有的进程(或叫任务)都使用相同的逻辑地址空间,因此就没有必要使用局部描述符表LDT。但内核中也用到LDT,那只是在VM86模式中运行Wine,也就是说在Linux上模拟运行Winodws软件或DOS软件的程序时才使用。

在80X86上任意给出的地址都是一个虚拟地址,即任意一个地址都是通过“选择符:偏移量”的方式给出的,这是段机制存访问模式的基本特点。所以在80X86上设计操作系统时无法回避使用段机制。一个虚拟地址最终会通过“段基地址+偏移量”的方式转化为一个线性地址。但是,由于绝大多数硬件平台都不支持段机制,只支持分页机制,所以为了让Linux具有更好的可移植性,我们需要去掉段机制而只使用分页机制。

但不幸的是,80X86规定段机制是不可禁止的,因此不可能绕过它直接给出线性地址空间的地址。万般无奈之下,Linux的设计人员干脆让段的基地址为0,而段的界限为4GB,这时任意给出一个偏移量,则等式为“0+偏移量=线性地址”,也就是说“偏移量=线性地址”。另外由于段机制规定“偏移量 < 4GB”,所以偏移量的范围为0H~FFFFFFFFH,这恰好是线性地址空间范围,也就是说虚拟地址直接映射到了线性地址,我们以后所提到的虚拟地址和线性地址指的也就是同一地址。看来,Linux在没有回避段机制的情况下巧妙地把段机制给绕过去了。

另外,由于80X86段机制还规定,必须为代码段和数据段创建不同的段,所以Linux必须为代码段和数据段分别创建一个基地址为0,段界限为4GB的段描述符。不仅如此,由于Linux内核运行在特权级0,而用户程序运行在特权级别3,根据80X86的段保护机制规定,特权级3的程序是无法访问特权级为0的段的,所以Linux必须为内核和用户程序分别创建其代码段和数据段。这就意味着Linux必须创建4个段描述符----特权级0的代码段和数据段,特权级3的代码段和数据段。

Linux在启动的过程中设置了段寄存器的值和全局描述符表GDT的内容,段的定义在include/asm-i386/segment.h中:

#define __KERNEL_CS 0x10 /*内核代码段,index=2,TI=0,RPL=0*/

#define __KERNEL_DS 0x18 /*内核数据段, index=3,TI=0,RPL=0*/

#define __USER_CS 0x23 /*用户代码段, index=4,TI=0,RPL=3*/

#define __USER_DS 0x2B /*用户数据段, index=5,TI=0,RPL=3*/

从定义看出,没有定义堆栈段,实际上,Linux内核不区分数据段和堆栈段,这也体现了Linux内核尽量减少段的使用。因为没有使用LDT,因此,TI=0,并把这4个段都放在GDT中, index就是某个段在GDT表中的下标。内核代码段和数据段具有最高特权,因此其RPL为0,而用户代码段和数据段具有最低特权,因此其RPL为3。可以看出,Linux内核再次简化了特权级的使用,使用了两个特权级而不是4个。

全局描述符表的定义在arch/i386/kernel/head.S中:

ENTRY(gdt_table)

.quad 0x0000000000000000 /* NULL descrīptor */

.quad 0x0000000000000000 /* not used */

.quad 0x00cf9a000000ffff /* 0x10 kernel 4GB code at 0x00000000 */

.quad 0x00cf92000000ffff /* 0x18 kernel 4GB data at 0x00000000 */

.quad 0x00cffa000000ffff /* 0x23 user 4GB code at 0x00000000 */

.quad 0x00cff2000000ffff /* 0x2b user 4GB data at 0x00000000 */

.quad 0x0000000000000000 /* not used */

.quad 0x0000000000000000 /* not used */

/*

* The APM segments have byte granularity and their bases

* and limits are set at run time.

*/

.quad 0x0040920000000000 /* 0x40 APM set up for bad BIOS's */

.quad 0x00409a0000000000 /* 0x48 APM CS code */

.quad 0x00009a0000000000 /* 0x50 APM CS 16 code (16 bit) */

.quad 0x0040920000000000 /* 0x58 APM DS data */

.fill NR_CPUS*4,8,0 /* space for TSS's and LDT's */

从代码可以看出,GDT放在数组变量gdt_table中。按Intel规定,GDT中的第一项为空,这是为了防止加电后段寄存器未经初始化就进入保护模式而使用GDT的。第二项也没用。从下标2到5共4项对应于前面的4种段描述符值。对照图2.10,从描述符的数值可以得出:

? 段的基地址全部为0x00000000

? 段的上限全部为0xffff

? 段的粒度G为1,即段长单位为4KB

? 段的D位为1,即对这四个段的访问都为32位指令

? 段的P位为1,即四个段都在内存。

由此可以得出,每个段的逻辑地址空间范围为0~4GB。读者可能对此不太理解,但只要对照图2.9就可以发现,这种设置既简单又巧妙。因为每个段的基地址为0,因此,逻辑地址到线性地址映射保持不变,也就是说,偏移量就是线性地址,我们以后所提到的逻辑地址(或虚拟地址)和线性地址指的也就是同一地址。看来,Linux巧妙地把段机制给绕过去了,而完全利用了分页机制。

从逻辑上说,Linux巧妙地绕过了逻辑地址到线性地址的映射,但实质上还得应付Intel所提供的段机制。只不过,Linux把段机制变得相当简单,它只把段分为两种:用户态(RPL=3)的段和内核态(RPL=0)的段,因此,描述符投影寄存器的内容很少发生变化,只在进程从用户态切换到内核态或者反之时才发生变化。另外,用户段和内核段的区别也仅仅在其RPL不同,因此内核根本无需访问描述符投影寄存器,当然也无需访问GDT,而仅从段寄存器的最低两位就可以获取RPL的信息。Linux这样设计所带来的好处是显而易见的,Intel的分段部件对Linux性能造成的影响可以忽略不计。

在上面描述的GDT表中,紧接着那四个段描述的两个描述符被保留,然后是四个高级电源管理(APM)特征描述符,对此不进行详细讨论。

按Intel的规定,每个进程有一个任务状态段(TSS)和局部描述符表LDT,但Linux也没有完全遵循Intel的设计思路。如前所述,Linux的进程没有使用LDT,而对TSS的使用也非常有限,每个CPU仅使用一个TSS。

通过上面的介绍可以看出,Intel的设计可谓周全细致,但Linux的设计者并没有完全陷入这种沼泽,而是选择了简洁而有效的途径,以完成所需功能并达到较好的性能为目的。

但是,如果这么定义段,则段保护的第一个作用就失去了,因为这些段使用完全相同的线性地址空间(0~4GB),它们互相覆盖。可以设想,如果不使用分页的话,线性地址空间直接被映射到物理空间,则你修改任何一个段的数据,都会同时修改其它段的数据,段机制所提供的通过“基地址:界限”方式本来将线性地址空间分割,以让段与段之间完全隔离,这种实现段保护的方式根本就不起作用了。那么,这是不是意味着用户可以随意修改内核数据?显然不是的,这是因为,一方面用户段和内核段具有不同的特权级别,另一方面,Linux之所以这么定义段,正是为了实现一个纯的分页,而分页机制会提供给我们所需要的保护。

linux中的段定义的,Linux中的段相关推荐

  1. python中函数的定义包括_python中函数的定义及调用

    python中函数的定义及使用方法 1.函数的概念:函数是将具有独立功能的代码块组织为一个整体,使其具有特殊功能的代码集; 2.函数的作用:使用函数可以加强代码的复用性,提高程序编写的效率; 3.函数 ...

  2. matlab定义字母常数,在matlab中,如何定义函数式子中的未知常数。

    新建一个.m文件,第一行输入下面的格式function [y,m] = abc(x)其中... 定义未知数:syms xx是变量 .syms是定义符号变量.补充:syms是... 这不是解方程,这是曲 ...

  3. masm汇编语言堆栈段定义了却提示无堆栈段

    看王爽<汇编语言>的时候,发现加了堆栈段仍然显示没有堆栈段 这样改了之后就可以了 stack segment stackdw 0,0,0,0,0,0,0,0 stack ends 参考文章 ...

  4. XOS 源码详解2: os_s_xxxx.s 汇编代码的段定义AREA,程序入口ENTRY,程序结尾END.

    代码段定义 EXPORT    __Vectors ;** ;* @brief    中断向量 ;*         AREA    Vectors, CODE ; 系统异常 __Vectors 数据 ...

  5. linux mmu的实现的讲解_Linux中的段

    Intel 微处理器的段机制是从8086 开始提出的, 那时引入的段机制解决了从CPU 内部 16 位地址到20 位实地址的转换.为了保持这种兼容性,386 仍然使用段机制,但比以前复杂. 因此,Li ...

  6. Linux中THIS_MODULE宏定义详解

    一直都在耿耿于怀,这个THIS_MODULE到底是个什么玩意,linux内核中无处不在的东西.今天上网搜了一下,算是基本明白了.网上牛人写的已经比较详细,另外目前暂时没有时间往更深层次分析,所以直接贴 ...

  7. linux代码段映射,bss,data,text,rodata,堆,栈,常量段与其各段在物理存储中关系

    本文想从linux出发[目前还想可不可以从51单片机出发],解答程序代码和各种数据结构是如何存储(映射)到存储区的. 目前未完成整理,还是初稿的想法.bss段: BSS段(bsssegment)通常是 ...

  8. linux machine start,Linux中的MACH定义之MACHINE_START / MACHINE_END

    (写在转载之前) 在linux中machine对应一个设备,对于开发者而言对应一个新的项目,项目开展的初始需要对应定义一个MACH,在arch/arm/mach-xxx/目录下,此文件中定义了GPIO ...

  9. Linux内核源码中使用宏定义的若干技巧

    在C中,宏定义的概念虽然简单,但是真要用好却并不那么容易,下面从Linux源码中抽取一些宏定义的使用方法,希望能从中得到点启发: 1. 类型检查 比如module_init的宏定义: 点击(此处)折叠 ...

  10. 如何在Linux kernel Makefile中添加宏定义

    如何在Linux kernel Makefile中添加宏定义: CFLAGS_object.o += -DMACRO_NAME 在编译object.o时定义宏MACRO_NAME,在kernel中添加 ...

最新文章

  1. Python标准库:itertools迭代器函数
  2. TListBox的项目个数
  3. C语言再学习 -- 结构和其他数据形式
  4. MyBatis 实践 -Mapper与DAO
  5. 一起来学SpringBoot | 第四篇:整合Thymeleaf模板
  6. google的几个搜索业务
  7. 基于linux的安全通讯过程
  8. 最长上升子序列(LIS)题目合集
  9. win10 ipv4无网络连接
  10. matlab画图三维立体,matlab的三维图形绘制
  11. 家用洗地机买什么牌子好一点?家用洗地机推荐
  12. mysql 删除一条数据sql语句_sql删除语句
  13. 如何带好一个团队?团队管理的要点有哪些?
  14. pygame-KidsCanCode系列jumpy-part18-背景滚动
  15. 推荐场景下融合多模态信息的内容召回模型
  16. OpenWrt软路由安装可道云
  17. 数学和英语不好,能当程序员吗?
  18. Wi-Fi 安全协议 - WPA
  19. 计算机和机械课程有关联吗,机械工程及自动化有哪些课程
  20. 儿童语言发育迟缓分类中C群d是指,语言发育迟缓的定义及常见表现

热门文章

  1. 电子签章系统如何无代码接入第三方应用
  2. 大咖分享|王建峰:数据指标管理体系建设实践分享
  3. 如何同步企业微信的标签?
  4. android 免root 免流,安卓无需Root一键免流软件合集,具体哪个能用自测
  5. 电脑版美食大战老鼠放置html,美食大战老鼠2
  6. 震惊!PC端QQ也能防撤回?
  7. Dell安装Ubuntu教程
  8. detours钩子库的简单使用
  9. 【Codecs系列】VP9码流结构和概述
  10. 【计算几何各种小模板总结贴】[不定期更新]