编程高手箴言》读后 #2

毕业也有几年了,也看了和学了不少东西。有时也想写点什么,但总是觉得头绪很多,一直没有
动笔。最近翻了翻梁先生的《编程高手箴言》,突然想写点什么,权且用读书笔记的形式写点东西。
等号上面的摘字《箴言》,下面则是笔者自己的感想。希望大家指教,但是谩骂就不必了,谢谢。
注:这一部分涉及《箴言》第二章。

 

CPU是计算机的心脏,是控制程序的核心。只有真正了解了CPU的结构和运行机理,才能真正编出优秀的
程序。 ...以至今天如火如荼的64位。
================================================================================================
CPU确实是计算机的核心,但是是不是一定要了解了CPU的结构和运行机理了才能写出漂亮的程序呢?我觉得
不一定。软件是分层的,每一层都有可能出漂亮的软件。如果你希望在底层,尤其是和硬件关系密切的层,比如
操作系统硬件核心层写出漂亮的代码,当然需要深刻理解CPU的结构和运行机理。然而,如果你的工作层次较
高,那么你实际看到的已经不是硬件CPU了,而是你的操作系统,开发运行环境等一起提供的一个“虚拟”的机器了。
就如我们在用汇编开发程序的同志,一般不太会关注CPU内部的逻辑设计吧。
至于64位机,呵呵,前途究竟如何还是个未知数,毕竟AMD推出64机后,Intel并没有立即跟进,好像还没有到
“如火如荼”的地步吧。开个玩笑,如果AMD的64位机工作在32位时能发挥哪怕1.5倍同档次32位机的性能,我也就
会毫不犹豫的淘汰我这片AthlonXP了 :-) 当然,64位取代32位是必然的,只是个时间问题了,兄弟们努力啊,编
出牛牛的软件,充分去发挥硬件的优势和特点啊 ;-)

RISC就是在设计CPU的时候,只把最常用的指令用硬件来实现,其他的指令都通过微代码用软件的方法模拟实现。
CISC是一种指令对应一组执行单元的体系结构。不过,随着CISC工作频率的提高和技术的发展,RISC现在已经
黯然失色了。
================================================================================================
实际上,一些CISC CPU也是离不开微代码的,比如Intel就给它的P4 CPU出过微代码补丁程序,大家可以看看这个
链接http://www.pcpop.com/News/2002/8/7779.shtml。当然,现在RISC体系正面临CISC体系CPU的强大攻势,一些
传统RISC服务器供应商,比如IBM,SUN都为了留后手而推出了基于CISC的服务器。但是,市场的选择有时未必就是
技术最先进的东西。毕竟最大的芯片巨无霸Intel是CISC的支持者。而RISC和CISC体系之争从来没有个最终的结论。
让我们记住一些闪亮的RISC CPU家族吧:DEC/Compaq Alpha, MIPS, HP PA-RISC系列, SUN Sparc系列,
IBM/Moto PowerPC等等,即使有这么一天CISC CPU淡出江湖。

1989年,Intel推出80486芯片,速度上突破100MHz,超过了RISC CPU。
================================================================================================
按照Intel自己的文档,它1989推出的486DX是25MHz工作频率,一级缓存是16Kb。如果Intel所言无误,那么以这种
工作频率要超过同时的RISC,恐怕还是个问题。虽说作频率并不是CPU工作速度的唯一因素。引用中科院和科大做
CPU的一个成员胡伟武的话:“我的愿望就是超过Intel,孔子说朝闻道夕死可矣,我是早上超过洋鬼子,夕死可矣。
不是说卖得比Intel好,我要跑得比他们快,超过Alpha比较难,超过Intel有希望。我们要专注于结构的突破超
过Intel”。

80386提供了两种工作模式,其一为实模式,...,其二为保护模式。
================================================================================================
根据Intel的手册,其实还应该有个系统管理模式。我没有碰到过在这个模式下工作的程序,感兴趣的同志请自行参考
Intel的手册。

不同任务间的保护:通过把不同的任务放在不同的虚拟地址空间中去,来实现不同任务的隔离(即A程序不能访问B
程序的代码和数据)
================================================================================================
虚拟空间的概念对于刚刚碰386保护模式的同志可能有点难理解,其实是这样的:假设一个程序被你在多任务操作系统里
启动了两遍,那么你这个程序在两个实例里看到的虚拟空间其实是一样的,都是0~4G,然而底层的寻址机制能够让这两个
实例里同一个数据或者指令虽然具有相同的虚拟地址,然而却有不同的物理地址。我们知道,软件最终要落实到硬件,
如果最终的物理地址不一样,那么这两个程序就不会相互干扰了。当然了,如果你硬要让两个程序的某一个虚拟地址对应
同一个物理地址也可以,对于操作系统而言是小菜一碟,只要把转换表格小小修改一下就可以达到目的。
(具体的寻址方法见下面)

《箴言》一书对386 CPU的保护模式下的寻址给出了flat, segmented模式的寻址方式。
================================================================================================
386CPU在进入保护模式之前,实际上要在主存里面初始化好一大堆数据结构的。按照Intel手册自己的说法,
The contents of the protected-mode system data structures loaded into memory during software
initialization, depend largely on the type of memory management the protected-mode operatingsystem
or executive is going to support: flat, flat with paging, segmented, or segmented with
paging. 《箴言》一书给出的是一个简单的模型,可能早期的DOS程序员为了访问大内存,在没有OS的支持下选择
这种方式。而在现在实际的操作系统里,不会用这种方式,基本上都是flat,paging模式。有的同志也许要问,
《箴言》给出的访问模式不是挺好的嘛,简单易懂。那么为什么现在稍微好一点的操作系统不采用这种方式呢?
原因是为了支持“虚拟内存”,只有启用了CPU的paging模式后,才可以设计出高效的虚拟内存管理程序来。何谓
虚拟内存,简单的说就是一般应用程序设计者和某些底层操作系统设计者看到的存贮器。我们知道,32位的机器
可以寻址4G的地址范围(实际上较新的Intel CPU通过一定的方式可以访问超过4G的物理存贮器,然而它们可以直接
访问的虚拟存贮器还是4G),而我们绝大多数的同志并没有这么多的存贮器,怎么办?利用paging功能,在OS的支持
下,就可以给程序员提供一个虚拟的存贮器,其大小是4G(当然,现在的操作系统并没有把全部4G给普通程序,而是
保留了一些给OS内核,比如Linux好像就是给应用程序留下了3G的虚拟空间)。这种所谓的“虚拟内存”是如何实现的
呢,要回答这个问题就必须从CPU在启用了paging后如何得到物理地址的过程说起。下面我试图用尽量通俗的文字描述
这个过程,尽量少设计386保护模式的一些术语。至于官方的说法请参考Intel 386程序员手册,这个手册可以从Intel
免费下到。给个链接吧,免得有的同志再去搜了,http://www.intel.com/design/Pentium4/manuals/245470.htm
OK, let's go! 这里要描述的是一些主流操作系统使用的flat,paging模式。何谓flat,其实386CPU还是保留了早期CPU
的分段功能,而flat模式就是将段的基地址设为0,而将这个段的范围设为4G,这样,其实是没有用分段功能。何谓
paging呢,就是把虚拟内存分成许多小块,然后将这些块再映射到物理内存或者辅助存贮器,比如硬盘。这就解释了为
什么可以提供给普通程序员大的多的(比如3G)内存空间,因为我们可以把一些虚拟内存映射到磁盘上,在需要的时候
在将他们调到物理内存中去。
如果我们反汇编一些程序,就可以看到,从表面上看,好像访问内存的方法和过去没什么差别嘛。实际的情况是这种差别
是藏在幕后的。我们在汇编程序里使用的其实是“虚拟地址”,虚拟地址的形成还是和过去的段式管理一样,即段的开始
地址加上偏移量。由于在flat模式下,所有的段开始地址都是0,所以我们在汇编程序里指定的偏移就是最终的虚拟地址了。
比如下面这个语句,movl $0x4321,0x8049430,其含义是将整数0x4321送到地址是0x8049430的地方去。(抱歉,由于我手头
的机器上没有Win32平台的开发工具,只好从用gdb反汇编的程序中摘了一行)在flat模式下,0x8049430就是最终的虚拟地址了。
得到虚拟地址后,革命成功了第一步,下面就是要利用分页管理来得到最终的物理地址了。过程如下,首先把虚拟地址,也
就是0x8049430的高10bits取出来作为一个index去索引“第一个”表格,这个表格的起始地址放在系统CR3寄存器里。从“第一个”
表格里取到一个32位整数,而这个整数就对应了“第二个”表格的起始地址。然后将虚拟地址0x8049430的中间10个bits拿出来
索引这第二个表格,又可以得到一个32位的值,这个值其实就是一个物理地址了,用这个物理地址加上刚才0x8049430剩下
的12个bits的值就可以形成最终0x8049430对应的物理地址了。写完了上面这个过程,我自己也回头看了一下,确实写的不怎么
清楚,是吧?那好,我们就举个实际生活中一个不存在的例子来说明(废话,既然不存在还说了干吗:-)。
假如你经营一个很大的酒店,现在让你接待很多来自全国各地的旅客,聪明的你想到了一个安排他们的方法,每来一个人,
你就拿过他的身份证,(假设身份证的格式是 “省名-城市名-出生序号”)根据他所属的省份去查找“第一个”表格,让他到这个省份
对应的办公室去。这个旅客到了省份对应的办公室后,就有一个接待人员拿过他的身份证根据接下来的城市信息查“第二个”表格
又把他安排到这个城市的旅客所属的楼层去。这个旅客到达相应的楼层后,根据自己的出生序号再找到自己的房间。也许你要问,
干吗还要按照省份先进行一次安排操作啊,干吗不直接安排到相应的城市对应的办公室去啊?原因是为了节约几个办公室,有的省份因为
没有计划,所以聪明的你根本就没有安排这个省份下面城市对应的办公室,来了一个人,你根据他的省份立即就可以知道这个人
应不应该接待,否则如果每个城市一个办公室,那多浪费啊,特别如果你的客人只集中在几个特定的省份的时候。
而我们的程序在运行的时候恰恰就是“客人只集中在几个特定的省份”这种情况,所以采取了两级索引的方式,这样可以节约
用来维护分页机制所需要表格的尺寸。如果不这样,我们假设一个小块单元是4K bytes,那么映射4G空间我们就需要4M+4K bytes
来存放分页所需要的表格。(这个值是这样计算的,“第一个”表格是4K大小,有1024个表项,每个表项对应一个“第二个”表,这样就有
1024个第二个表,而每个第二个表尺寸也是4K,于是就需要4M的空间放“第二个”表)
呵呵,费了半天劲,也不知道说清楚了没有。对这个部分感兴趣的同志可以进一步参考Intel手册和Linux启动代码。
另外,保护模式的一个很重要的概念就是“保护”,系统里面的段啊,页啊都有很多属性和权限,低权限的代码不可以调需要更高
权限的指令。你想想,如果不是这样的话,用户程序只要随便改改那两个表格,岂不是什么地址都可以访问了,呵呵。一些病毒代码
挖空心思也就是想让病毒体在CPU的最高特权级下运行,CIH病毒就是一个很好的证明。
噢,对了,这个链接http://jiurl.nease.net/document/JiurlPlayWin2k/MmPaging1.htm给出了关于分页机制的比我讲的好的多得
文章,感兴趣的同志可以去看看。
如果有同志想写自己的小测试程序,推荐到sourceforge上下载bochs,一个x86模拟器,这样至少可以免去你reboot computer的烦恼。


对于386的中断处理,《箴言》给出了一页的描述。
=======================================================================================================
而在Intel的手册里是好几章在讲。这一块我几乎没有碰过,呵呵,平时做试验的时候都是直接关中断了,呵呵。同志们同样可以参考
Intel手册和Linux的中断处理部分。希望有同志贴出帖子来描述一下386下面中断的处理方法。

关于《箴言》所附的示例程序
============================================================================
这个程序我没有编译,一来没有bc3.1,二来没有纯DOS环境了。只是有几个问题不知道答案,希望明白的同志指教:
1) 普通的机器没有4G的存贮器,那么肯定有一些4G里面的地址根本没有对应的物理设备,不知道访问这些地址会不会导致系统异常。
2) 按照Intel的手册说明,进入保护模式需要一个长跳转,而示例程序里是个局部短跳转。不知道是不是真的像《箴言》里说得那样。
   另外《箴言》程序里的英文注释实在是看不懂,希望理解的同志指点。
3) 既然在实模式里一个段只有64K,那么我们又怎么在实模式里通过FS访问所有的内存呢?
呵呵,有机会一定要用bochs好好试一下。

今天又在网上看到了一片文章,大家不妨看看,再对照《箴言》,有什么感想?
http://www.lisoleg.net/lisoleg/memory/%D4%DADOS%CA%B5%C4%A3%CA%BD%CF%C2%D6%B1%BD%D3%B4%E6%C8%A14GB%C4%DA%B4%E6.txt

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
梁肇新开发技术总结,值得借鉴 这是我在网上能够找到的最清晰的版本,分享给大家 目录: 第1章 程序点滴 1 1.1 程序≠软件 1 1.1.1 商业软件门槛的形成 2 1.1.2 认清自己的发展 4 1.2 高手是怎样练成的 5 1.2.1 高手成长的六个阶段 5 1.2.2 初级程序员和高级程序员的区别 7 1.2.3 程序员是吃青春饭的吗 9 1.3 正确的入门方法 11 1.3.1 规范的格式是入门的基础 13 1.3.2 调试的重要性 17 1.4 开放性思维 18 1.4.1 动态库的重要性 19 1.4.2 程序设计流程 20 1.4.3 保证程序可预测性 21 第2章 认识CPU 23 2.1 8位微处理器回顾 23 2.2 16位微处理器 24 2.2.1 组成结构 24 2.2.2 8086寄存器组成 25 2.2.3 内存的寻址 26 2.2.4 中断处理 27 2.3 32位微处理器 29 2.3.1 寄存器组成 29 2.3.2 保护模式 32 2.3.3 80386的寻址方式 32 2.4 【实例】:在DOS实模式下读取4GB内存代码分析 36 2.4.1 程序的意义 37 2.4.2 程序代码 37 2.4.3 程序原理 41 2.4.4 程序中的一些解释 42 第3章 Windows运行机理 44 3.1 内核分析 44 3.1.1 运行机理 44 3.1.2 LE文件的格式 53 3.1.3 VxD的设计实现 59 3.1.4 【实例】:CPU降温程序代码分析 65 3.2 消息的运行方式 82 3.2.1 认识消息 82 3.2.2 Windows系统中消息的运作方式 84 3.2.3 消息处理过程实例 87 3.3 GDI的结构和组成 89 3.3.1 GDI的组成 89 3.3.2 GDI和DirectDraw的关系 91 3.4 线程的机制 93 3.4.1 线程的工作方式 93 3.4.2 线程与GDI的冲突:死机的主要原因 94 3.4.3 线程的内存泄漏的主要原因 96 3.4.4 进程管理 98 3.4.5 同步机制 100 3.5 PE结构分析 103 3.5.1 PE头标 103 3.5.2 表节 113 3.5.3 PE文件引入 119 3.5.4 PE文件引出 125 3.5.5 PE文件资源 129 第4章 编程语言的运行机理 133 4.1 汇编的原理 133 4.1.1 指令系统 133 4.1.2 汇编与Win API的接口方法 141 4.1.3 【实例】:自定义程序的入口点 145 4.2 高级语言的原理 151 4.2.1 C/C++的原理 151 4.2.2 解释语言的原理 165 4.2.3 【实例】:用C实现简单的BASIC语言环境 165 4.3 C、C++的学习方式 187 4.3.1 从BASIC到C 187 4.3.2 C、汇编、API的关系 187 4.3.3 接口的建立方法 190 4.4 挂钩技术 201 4.4.1 Windows上C的挂钩 201 4.4.2 C++的挂钩技术 213 第5章 代码的规范和风格 220 5.1 环境的设置 220 5.1.1 集成环境的设置 220 5.1.2 TAB值的设置 221 5.1.3 编译环境的设置 222 5.1.4 设置herosoft.dsm宏 224 5.2 变量定义的规范 227 5.2.1 变量的命名规则 227 5.2.2 变量定义的地方规定 228 5.2.3 变量的对齐规定 229 5.3 代码对齐方式、分块、换行的规范 230 5.4 快速的代码整理方法 232 5.5 注释的规范 233 5.6 头文件的规范 236 5.7 建议采用的一些规则 236 5.8 可灵活运用的一些规则 238 5.9 标准化代码示例 239 5.10 成对编码规则 243 5.10.1 成对编码的实现方法 243 5.10.2 成对编码中的几点问题 248 5.11 正确的成对编码的工程编程方法 251 5.11.1 编码前的工作 252 5.11.2 成对编码的工程方法 255 5.11.3 两个问题的解释 260 第6章 分析方法 266 6.1 分析概要 266 6.1.1 分析案例一:软件硬盘阵列 268 6.1.2 分析案例之二:游戏内存修改工具 274 6.2 接口的提炼 286 6.2.1 分离接口 286 6.2.2 参数分析 287 6.3 主干和分支 290 6.3.1 主干和分支分析举例 291 6.3.2 程序检?? 300 6.4 是否对象化 301 6.5 是否DLL化 307 6.5.1 DLL的建立和调用 307 6.5.2 DLL动态与静态加载的比较
讲述一个程序员成长的六个阶段: 1、能熟练地使用某种语言,这相当于练武中的套路和架式这些表面的东西。 2、精通基于某种平台的接口(如win的API),以及所对应语言的自身的库函数。这也就相当于可以进行真实的散打对练了,可以真实地在实践中做一些应用了。 3、此阶段能深入了解某个平台系统的底层,已经具有了初级的内功的能力,也就是“手中有剑,心中无剑” 4、此阶段能够直接在平台上进行比较深层次的开发。基本上能达到这个层次可以说是进入了高层次。这时进入了高级内功的修炼。比如能进行VxD或操作系统的内核的修改。这时已经不再有语言的束缚,此时语言只是一种工具,即使要用自己不会的语言进行开发,也只是简单地熟悉下,就手到擒来,完全不像是第一阶段的时候学习语言的那种情况。 5、此阶段已经不再局限于简单的技术上面的问题了,而是能从全局上把握和设计一个比较大的系统体系结构,从内核到外层界面。可以说是“手中无剑,心中有剑”。到了这个阶段就能对市面上的任何软件进行剖析,并能按自己的要求进行设计,就算是MS Word这样的大型软件,只要有充足的时间,也一定能够设计出来。 6、最高境界,达到“无招胜有招”。这时候,任何问题都纯粹变成了一个思路问题,不是用什么代码就能表示的。此时,对于练功的人来说,他已不用再去学什么少林拳,只是在旁边看别人对战,就能把这拳来用。这个就是大师级别的人物。这时,Win32或Linux在眼里是没有什么区别的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值