Archive for January, 2008
熊猫的成长
Wednesday, January 30th, 2008我出生啦,伙计,你不要拿剪刀对着我,尤其是我下面好伐?



第二天,我还太小太嫩了,别人都说我看上去就像猫仔仔一样


第三天,要想我以后长得帅些,你们可要花大量时间和精力来照顾我啦

一周了,我比以前要有劲些了,你们都不许跟我抢地盘~



25天了,看上去是不是有点像我妈了?可我还是看不见东西,该死的眼睛就是张不开。嗯,明天我要多吃点再多吃点,使劲张开眼~


30天了,有人说我看上去就像一头猪



35-40天,我可以自由活动了,我要吃东西,还要玩乐高!

第85天,还是不能张开眼,急死人了急死人了


第90天,嘿嘿,我学会像人一样睡觉了,扑睡,侧睡,你们就是不给我盖被子,哼

嘿! 我长大啦,世界真奇妙~
“木乃伊”加入《特种部队》(更新:目前的完整演员阵容)
Wednesday, January 30th, 2008
既然由《木乃伊》(The Mummy)导演史蒂芬·索莫斯(Stephen Sommers)执导,那么在《特种部队》(G.I. Joe)中出现他的“老部下”也就情有可原了。据IESB报道,曾在《木乃伊1、2》中扮演“木乃伊Imhotep”的男演员阿诺德·沃斯鲁(Arnold Vosloo)将在《特种部队》中扮演反派“扎坦”(Zartan)。
这个角色在动画版《特种部队》里是几个主要坏蛋之一,是一个为Destro和眼镜蛇部队工作的雇佣兵。他善于伪装成别人的外貌和声音,精通20种语言和方言,是一个腹语和武术专家,擅长的兵器是弓箭。他还是飞车党“Dreadnoks”的首领,患有多重人格和妄想型精神分裂症。
《特种部队》(G.I. Joe)是派拉蒙继《变形金刚》后又一部根据孩之宝玩具改编的大制作电影。“G.I. Joe”是美国特训特种敢死队的代号,该队组建的目的是维护人类的自由与安全。他们的对手是妄图统治世界的残忍的眼镜蛇部队。影片计划下个月中旬在洛杉矶开拍,并于2009年8月7日上映。
更新:丹尼斯·奎德(Dennis Quaid)也加入了《特种部队》,他将扮演霍克将军(General Hawk)。
至此,加盟此片的演员已经有“男爵夫人”西耶娜·米勒(Sienna Miller)、“Ripcord”马龙·韦恩斯(Marlon Wayans)、“Rex”约瑟夫·高登·拉维特(Joseph Gordon-Levitt)、“公爵”钱宁·塔图姆(Channing Tatum)、“蛇眼”雷·帕克(Ray Park)、“红发女郎”雷切尔·妮科尔斯(Rachel Nichols)、“忍者白幽灵”李秉宪(Byung-hun Lee)、“Breaker”萨伊德·塔马乌伊(Said Taghmaoui)以及“Heavy Duty”阿德沃尔·阿吉纽依-艾格拜吉(Adewale Akinnuoye-Agbaje)。
作者:Nemo
来源:http://mymovie.blogbus.com/logs/14657580.html
这是我小时就很喜欢看的一部动画片,哈,终于有电影可看了,Ye!
[转]论调用约定
Wednesday, January 16th, 2008在C语言中,假设我们有这样的一个函数:
int function(int a,int b)
调用时只要用result = function(1,2)这样的方式就可以使用这个函数。但是,当高级语言被编译成计算机可以识别的机器码时,有一个问题就凸现出来:在CPU中,计算机没有办法知道一个函数调用需要多少个、什么样的参数,也没有硬件可以保存这些参数。也就是说,计算机不知道怎么给这个函数传递参数,传递参数的工作必须由函数调用者和函数本身来协调。为此,计算机提供了一种被称为栈的数据结构来支持参数传递。
栈是一种先进后出的数据结构,栈有一个存储区、一个栈顶指针。栈顶指针指向堆栈中第一个可用的数据项(被称为栈顶)。用户可以在栈顶上方向栈中加入数据,这个操作被称为压栈(Push),压栈以后,栈顶自动变成新加入数据项的位置,栈顶指针也随之修改。用户也可以从堆栈中取走栈顶,称为弹出栈(pop),弹出栈后,栈顶下的一个元素变成栈顶,栈顶指针随之修改。
函数调用时,调用者依次把参数压栈,然后调用函数,函数被调用以后,在堆栈中取得数据,并进行计算。函数计算结束以后,或者调用者、或者函数本身修改堆栈,使堆栈恢复原装。
在参数传递中,有两个很重要的问题必须得到明确说明:
- 当参数个数多于一个时,按照什么顺序把参数压入堆栈
- 函数调用后,由谁来把堆栈恢复原装
在高级语言中,通过函数调用约定来说明这两个问题。常见的调用约定有:
stdcall调用约定
stdcall很多时候被称为pascal调用约定,因为pascal是早期很常见的一种教学用计算机程序设计语言,其语法严谨,使用的函数调用约定就是stdcall。在Microsoft C++系列的C/C++编译器中,常常用PASCAL宏来声明这个调用约定,类似的宏还有WINAPI和CALLBACK。
stdcall调用约定声明的语法为(以前文的那个函数为例):
int __stdcall function(int a,int b)
stdcall的调用约定意味着:1)参数从右向左压入堆栈,2)函数自身修改堆栈 3)函数名自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸
以上述这个函数为例,参数b首先被压栈,然后是参数a,函数调用function(1,2)调用处翻译成汇编语言将变成:
push 2 第二个参数入栈 push 1 第一个参数入栈 call function 调用参数,注意此时自动把cs:eip入栈
而对于函数自身,则可以翻译为:
push ebp 保存ebp寄存器,该寄存器将用来保存堆栈的栈顶指针,可以在函数退出时恢复 mov ebp,esp 保存堆栈指针 mov eax,[ebp + 8H] 堆栈中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp +8指向a add eax,[ebp + 0CH] 堆栈中ebp + 12处保存了b mov esp,ebp 恢复esp pop ebp ret 8
而在编译时,这个函数的名字被翻译成_function@8
注意不同编译器会插入自己的汇编代码以提供编译的通用性,但是大体代码如此。其中在函数开始处保留esp到ebp中,在函数结束恢复是编译器常用的方法。
从函数调用看,2和1依次被push进堆栈,而在函数中又通过相对于ebp(即刚进函数时的堆栈指针)的偏移量存取参数。函数结束后,ret 8表示清理8个字节的堆栈,函数自己恢复了堆栈。
cdecl调用约定
cdecl调用约定又称为C调用约定,是C语言缺省的调用约定,它的定义语法是:
int function (int a ,int b) //不加修饰就是C调用约定 int __cdecl function(int a,int b)//明确指出C调用约定
在写本文时,出乎我的意料,发现cdecl调用约定的参数压栈顺序是和stdcall是一样的,参数首先由有向左压入堆栈。所不同的是,函数本身不清理堆栈,调用者负责清理堆栈。由于这种变化,C调用约定允许函数的参数的个数是不固定的,这也是C语言的一大特色。对于前面的function函数,使用cdecl后的汇编码变成:
调用处 push 1 push 2 call function add esp,8 注意:这里调用者在恢复堆栈 被调用函数_function处 push ebp 保存ebp寄存器,该寄存器将用来保存堆栈的栈顶指针,可以在函数退出时恢复 mov ebp,esp 保存堆栈指针 mov eax,[ebp + 8H] 堆栈中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp +8指向a add eax,[ebp + 0CH] 堆栈中ebp + 12处保存了b mov esp,ebp 恢复esp pop ebp ret 注意,这里没有修改堆栈
MSDN中说,该修饰自动在函数名前加前导的下划线,因此函数名在符号表中被记录为_function,但是我在编译时似乎没有看到这种变化。
由于参数按照从右向左顺序压栈,因此最开始的参数在最接近栈顶的位置,因此当采用不定个数参数时,第一个参数在栈中的位置肯定能知道,只要不定的参数个数能够根据第一个后者后续的明确的参数确定下来,就可以使用不定参数,例如对于CRT中的sprintf函数,定义为:
int sprintf(char* buffer,const char* format,…)
由于所有的不定参数都可以通过format确定,因此使用不定个数的参数是没有问题的。
fastcall
fastcall调用约定和stdcall类似,它意味着:
- 函数的第一个和第二个DWORD参数(或者尺寸更小的)通过ecx和edx传递,其他参数通过从右向左的顺序压栈
- 被调用函数清理堆栈
- 函数名修改规则同stdcall
其声明语法为:int fastcall function(int a,int b)
thiscall
thiscall是唯一一个不能明确指明的函数修饰,因为thiscall不是关键字。它是C++类成员函数缺省的调用约定。由于成员函数调用还有一个this指针,因此必须特殊处理,thiscall意味着:
- 参数从右向左入栈
- 如果参数个数确定,this指针通过ecx传递给被调用者;如果参数个数不确定,this指针在所有参数压栈后被压入堆栈。
- 对参数个数不定的,调用者清理堆栈,否则函数自己清理堆栈
为了说明这个调用约定,定义如下类和使用代码:
class A { public: int function1(int a,int b); int function2(int a,...); }; int A::function1 (int a,int b) { return a+b; } #includeint A::function2(int a,...) { va_list ap; va_start(ap,a); int i; int result = 0; for(i = 0 ; i < a ; i ++) { result += va_arg(ap,int); } return result; } void callee() { A a; a.function1 (1,2); a.function2(3,1,2,3); }
callee函数被翻译成汇编后就变成:
//函数function1调用 0401C1D push 2 00401C1F push 1 00401C21 lea ecx,[ebp-8] 00401C24 call function1 注意,这里this没有被入栈 //函数function2调用 00401C29 push 3 00401C2B push 2 00401C2D push 1 00401C2F push 3 00401C31 lea eax,[ebp-8] 这里引入this指针 00401C34 push eax 00401C35 call function2 00401C3A add esp,14h
可见,对于参数个数固定情况下,它类似于stdcall,不定时则类似cdecl
naked call
这是一个很少见的调用约定,一般程序设计者建议不要使用。编译器不会给这种函数增加初始化和清理代码,更特殊的是,你不能用return返回返回值,只能用插入汇编返回结果。这一般用于实模式驱动程序设计,假设定义一个求和的加法程序,可以定义为:
__declspec(naked) int add(int a,int b) { __asm mov eax,a __asm add eax,b __asm ret }
注意,这个函数没有显式的return返回值,返回通过修改eax寄存器实现,而且连退出函数的ret指令都必须显式插入。上面代码被翻译成汇编以后变成:
mov eax,[ebp+8] add eax,[ebp+12] ret 8
注意这个修饰是和__stdcall及cdecl结合使用的,前面是它和cdecl结合使用的代码,对于和stdcall结合的代码,则变成:
__declspec(naked) int __stdcall function(int a,int b) { __asm mov eax,a __asm add eax,b __asm ret 8 //注意后面的8 }
至于这种函数被调用,则和普通的cdecl及stdcall调用函数一致。
函数调用约定导致的常见问题
如果定义的约定和使用的约定不一致,则将导致堆栈被破坏,导致严重问题,下面是两种常见的问题:
- 函数原型声明和函数体定义不一致
- DLL导入函数时声明了不同的函数约定
以后者为例,假设我们在dll种声明了一种函数为:
__declspec(dllexport) int func(int a,int b);//注意,这里没有stdcall,使用的是cdecl
使用时代码为:
typedef int (*WINAPI DLLFUNC)func(int a,int b); hLib = LoadLibrary(...); DLLFUNC func = (DLLFUNC)GetProcAddress(...)//这里修改了调用约定 result = func(1,2);//导致错误
由于调用者没有理解WINAPI的含义错误的增加了这个修饰,上述代码必然导致堆栈被破坏,MFC在编译时插入的checkesp函数将告诉你,堆栈被破坏了。
[转]AT指令集及S寄存器
Wednesday, January 16th, 2008AT命令使计算机或终端与调制解调器通讯,所有命令行必须由ASCII字符“AT”开始并由 <Enter> 结束。除了A/指令和推出(缺省为+++)。这些将在后面讨论。字母”AT”用以提醒调制解调器注意,其后将有一条或多条命令出现, “AT”及其后的字母可以是大写或小写。
AT必须同为大写或小写。如”At”或”aT”是不允许的。
一串命令可以写在一行里。为了便于阅读可以加或不加空格。命令中或命令间的空格会被忽略,命令行的最多字符数为39(包括”AT”)。在输入一条命令期间,可以用退格键(backspace)改正除”AT”以外的错误。若命令行中任一处出现语法错误,本行其后的内容将被忽略,并返回ERROR。大数带有超出正常范围的参数的命令将不被接收并返回 ERROR.本章列出所有设置调制解调器的命令。包括控制ACTIVE调制解调器的贺氏标准AT命令集。贺氏V系列命令集和扩展命令集
AT命令集的描述
符号 * 表明该命令的设置可用AT&Wn命令存于两个用户方案中的一个
A/ 重执行命令
重执行前一AT命令行,主要用于连接时占线,无应答或号码错误。这一命令必须单独构成一命令行并由”/”字符结束,(<Enter> 不能用于结束命令)。
+++ 退出字符 缺省:+
切换调制解调器从在线状态到命令状态,而不会中断数据连接。可以通过改变S寄存器S2的值来改变这一字符。
AT=x 写入被选的S寄存器
这一命令将数值x写入当前被选的S寄存器,一个S寄存器可由ATSn命令选择,若 x 是一个数字,所有S 寄存器将返回 OK 响应。
AT? 读被选的S寄存器
这一命令读并且显示被选的S寄存器的内容。一个S寄存器可由ATSn命令选择。
ATA 应答
它必须是命令行中的最后一条指令。调制解调器在应答方式下继续执行连接程序。在与远端调制解调器交换载波后进入连接状态,如果在由寄存器S7规定的时间内(缺省值=50秒)没有检测到载波, 调制解调器将挂机。在连接过程中,通过DTE输入的任何一个字母都将中断这一命令。
ATBn* 选择ITU-T或Bell模式 缺省=0
ATB0 选择在1200和300bps速率下通讯的ITU-T V.22和V.21协议
ATB1 选择在1200和300bps速率下通讯的Bell 212A和103协议ATCn 载波控制缺省=1
包含这一命令只是为了保证兼容性,执行号只是返回一结果码而没有其它作用。
ATC1 正常传输载波切换ATDn 拨号
它必须是命令行中的最后一条指令, ATD命令使调制解调器摘机后, 根据输入的参数拨号,以建立连接。如果不带参数,调制解调器摘机后,不拨号进入发起方式。
使用标点可使命令更易读懂。圆括号,连字符和空格符会被忽略。拔号命令行中如果出现了非法字符,则该字符及其后的内容将被忽略。调制解调器允许的拨号命令长度为36个字符。
参数:0-9 A B C D * # L P T R ! @ W , ; ^ S=n
0-9 DTMF 符号0到9
A-D DTMF 符号A,B,C和D。在一些国家中不使用这些符号
* “星”号(仅用于音频拨号)
# “#”号(仅用于音频拨号)
J 为本次呼叫执行在可提供的最高速率下的MNP10链路协商(可选)
K 使本次呼叫MNP10链路协商期间电源电平可调(可选)
L 重拨上一次拨过的号码
P 脉冲拨号
T 双音频拨号
R 逆叫方式。允许调制解调器使用应答方式呼叫只能作为发起使用的调制解调 器, 必须作为命令行中的最后一个字符输入。
! 使调制解调器按照S29中规定的值挂机一段时间再摘机。
@ 使调制解调器等待5秒钟的无声回答
w 按照寄存器S7中规定的时间,在拨号前等待拨号音。
, 在拨号过程中,按照寄存器S8中规定的时间,暂停
; 拨号后返回命令状态
^ 打开呼叫音
() 被忽视,用于格式化号码串
- 被忽视,用于格式化号码串
<space> 被忽视,用于格式化号码串
S=n 用AT&Zn 命令存在地址n处的号码拨号ATE* 命令回应 缺省:1
ATE0 关闭命令回应
ATE1 打开 命令回应ATHn 摘挂机控制 缺省:0
ATH0 使调制解调器挂机
ATH1 当调制解调器处于挂机状态,使调制解调器摘机,返回响 OK,等待进一步的命令。ATIn 识别
I0 报告产品代码
I1 报告ROM中预先计算的校验和
I2 计算校验和并与ROM中的校验和比较,返回”OK”或”ERROR”结果码
I3 报告固件修正
I4 报告OEM定义的识别串
I5 报告国家代码参数
I6 报告固件修正
I7 报告调制解调器数据泵类型ATLn* 扬声器音量 缺省:2
ATL0 扬声器低音量
ATL1 扬声器低音量
ATL2 扬声器中音量
ATL3 扬声器高音量ATMn* 扬声器控制 缺省:1
ATM0 关闭扬声器
ATM1 扬声器在呼叫建立握手阶段打开至检测到来自于远端调制解调器的载波后关闭
ATM2 扬声器持续开
ATM3 扬声器在应答期间打开。当检测到来自于远端的调制解调器的载波和拨号时关闭ATNn* 调制握手 缺省:1
ATN0 要求调制解调器S37选择连接速率,若S37=0,则连接速率必须与发出的上一条AT命令的速率相匹配。如果所选择的速率可用不止一个通讯标准实现(如Bell212A或ITU-T V.22 速率在 1200bps)调制解调器同时参考ATB 命令选择。ATN1 允许时使用双方调制解调器都支持的任一速率握手,使能够自动检测。在这一方式下,ATB命令被忽视,调制解调器只用ITU-T方式连接。
ATOn 进入数据在现状态 缺省:0
ATO0 使调制解调器从命令在现状态直接返回数据在线状态,不经过自动均衡。
ATO1 使调制解调器从命令在现状态返回数据在状态,经过自动均衡。ATP* 设脉冲拨号为缺省
ATQn* 结果码显示 缺省:0
ATQ0 调制解调器向DTE发送结果码
ATQ1 禁止调制解调器向DTE发送结果码
ATSn 设S寄存器n为缺省寄存器
ATSn? 读S寄存器读S寄存器中的内容,所有的S寄存器都可以读
ATSn=x 写入S寄存器
将 x值写入指定的S寄存器n
ATT* 设音频拔号为缺省
ATVn* 结束码类型 (消息控制) 缺省:1
ATV0 发送短型 (数字型) 结果码
ATV1 发送长型 (字符型) 结果码ATWn* 协商进程报告 缺省:0
ATW0 不报告纠错呼叫进程
ATW1 报告纠错呼叫进程
ATW2 不报告纠错呼叫进程,CONNECT xxxx指示DCE速率。ATXn* 扩展结果码 缺省:4
ATX0 调制解调器忽视拨号音和忙音。当由盲拨建立连接时,发送CONNECT信息。ATX1 调制解调器忽视拨号音和忙音。当由盲拨建立连接时,CONNECT XXXX 反映的是比特速率
ATX2 调制解调器忽视忙音,但在拨号前等待拨号音,如果5秒钟内检测不到拨号音,则发送NO DIAL TONE 信息,连接建立后 发送 CONNECT xxxx反映比特速率。
ATX3 调制解调器忽视拨号音,若检测到忙音,发送BUSY信息,当由盲拨建立起连接时, CONNECT XXXX 反映的是比特速率。
ATX4 如果5秒钟内检测不到拨号音,发送NO DIAL TONE 讯息,检测到忙音, 发送BUSY信息。连接建立后发送CONNECT XXXX 反映比特速率。
ATYn* 控制长间隔拆接 缺省:0
ATY0 不允许长间隔拆接
ATY1 允许长间隔拆接ATZn 复位 缺省:0
重新调出由用户方案规定的动态配置
ATZ0 软复位并重新调出用户方案0
ATZ1 软复位并重新调出用户方案1AT&An* 握手异常终止(备选) 缺省:1
AT&A0 在握手时禁止用户进行异常终止。当拨号或应答时,握手不能异常终止,只有DTR 信号下降。AT&A1 用户可以在握手时进行异常终止.在接收到DTE的字符后,发起和应答可以在握手期间随时进行异常终止.
AT&Cn* RS232-C DCD 设置缺省:1
AT&C0 DCD为ON,不论来自远端的调制解调器的数据载波的状态为何。
AT&C1 DCD 跟随来自于远端调制解调器的数据载波的状态AT&Dn* RS232-C DTR 设置缺省:2
决定了调制解调器与来自串口的DTR信号相关的操作。由于跟踪DTR的下降引起的操作在下表列出:
1 调制解调器断开连接并发送结果码OK
2 若在数据状态下,则进入命令状态,并发送结果码OK
3 调制解调器断开连接并发送结果码OK, DTR 为 OFF时不能自动应答
4 调制解调器执行热启动(即与ATZ命令相同)
AT&Fn 重新调用工厂 设置缺省:0
&F0 重新调用作为V.42bis自动可靠方式的出厂缺省设置
&F1 重新调用作为MNP5自动可靠方式的出厂缺省设置
&F2 重新调用作为DIRECT方式的出厂缺省设置
&F3 重新调用作为MNP10方式自动可靠方式的出厂缺省设置(可选)
AT&Gn* 设置保护音 缺省:0
AT&G0 无保护音
AT&G1 无保护音
AT&G2 1800HZ保护音
AT&Jn* 电话插头选择 缺省:0
包含这一命令只是基于兼容性的考虑,没有任何功能
AT&J0 不操作任何功能
AT&J1 不操作任何功能
AT&Kn* DTE/调制解调器流 控制缺省:3
AT&K0 关闭流控制
AT&K3 使用RTS/CTS流控
AT&K4 使用XON/XOFF流控
AT&K5 使用透明XON/XOFF流控
AT&K6 使用RTS/CTS和XON/XOFF流控(作为传真方式下的缺省)
AT&Ln* 传输线类型 缺省:0
AT&L0 拨号线
AT&L1 二线专线 (备选)
AT&L2 四线专线 (备选)
AT&Mn* 通讯方式
与AT&Q0-3相同
AT&Pn* 拨号脉冲占空比 缺省:0
AT&P0 39%61%占空比@10PPS
AT&P1 33%67%占空比@10PPS
AT&P2 39%61%占空比@20PPS
AT&P3 33%67%占空比@20PPS
AT&Qn* 通讯方式 缺省:5
AT&Q0 选择直接异步操作
AT&Q1 选择同步模式一操作
AT&Q2 选择同步模式二操作
AT&Q3 选择同步模式三操作
AT&Q4 选择自动同步模式操作
AT&Q5 选择纠错模式操作
AT&Q6 选择标准模式下的异步操作
AT&Rn* RS232-C RTS/CTS 设置缺省:0
AT&R0 CTS跟踪RTS, 本地DTE发送的RTS由OFF变为ON经过由寄存器S26所规定的以10微秒为增量的延迟后,CTS变为ONAT&R1 调制解调器忽视RTS,除非使用了AT&K3命令,CTS保持为ON
AT&Sn* RS232-C DSR 设置缺省:0
AT&S0 DSR始终为ON
AT&S1 DSR根据EIA-232-C的规定操作
AT&Tn* 测试和诊断 缺省:4
测试只能在非纠错方式下(标准或直接模式)下的异步操作中进行,除参数7和8以外,要中止正在进行中的测试必须首先敲入退出符。若S18非零,则测试经由S18规定的时间后自动中止并显示OK。AT&T0 终止进行中的测试
AT&T1 启动本地模拟回环
AT&T3 在本地启动远端数字回环·,若连接未建通,返回ERROR
AT&T4 允许调制解调器响应来自远端的进行远程数字环回测试的请求
AT&T5 拒绝调制解调器响应来自远端的进行远程数字环回测试的求
AT&T6 启动远端数字环回测试,若连接未通,返回ERROR
T&T7 启动远端数字环回自测试,若连接未建通,返回ERROR
AT&T8 启动本地模拟环回自测试
AT&V 看当今配置及用户参数
AT&V0 查看当前配置、用户方案和存储的电话号码
AT&V1 显示最后一次数据连接的详细情况
AT&Wn 储存用户参数 缺省:0
AT&W0 作为用户0存贮
AT&W1 作为用户1存贮
AT&Xn* 选择同步时钟源 缺省:0
AT&X0 调制解调器提供传输时钟,内部时钟。 AT&X1 DTE提供传输时钟,外部时钟。
AT&X2 由调制解调器从接外载波信号中提供传输时钟,从属接收时钟
AT&Yn* 指示缺省用户参数 缺省:0
在硬复位后可选择将使用的用户方案。
AT&Y0 选择用户方案0
AT&Y1 选择用户方案1
AT&Zn=x 储存电话号码(n=0-3) 缺省:0
将一36位数字电话号码(x)存放在一指定电话号码表中(n), 作以后拨号用(参见命令ATDS=n)
AT\An 最大MNP块的大小缺省:2
AT\A0 设最大块为64个字符
AT\A1 设最大块为128个字符
AT\A2 设最大块为192个字符
AT\A3 设最大块为256个字符
AT\Bn 发送中断信号(n=1-9) 缺省:3
当在非MNP连接期间输入此命令,调制解调器向远端调制解器发送一中断信号,中断信号长度参数为n值的100倍(以毫秒 为单位),在MNP模式下,输入此命令,调制解调器向远端调制解调器发送一链路注意码PDU
AT\Gn 调制解调器到调制解调器的流控制 缺省:0
AT\G0 关闭流控(XON/XOFF)
AT\G1 打开流控(XON/XOFF)
AT\Jn DTE速率自动调整控制 缺省:0
AT\J0 关闭匹配线路速率的DTE速率调整功能
AT\J1 打开匹配线路速率的DTE速率调整功能
AT\Kn 中断控制 缺省:5
在数据传输期间收到来自DTE的中断信号时,调制解调器作出如下响应AT\K0,2,4 调制解调器进入连机命令状态,而不向远端发送中断信号
AT\K1 调制解调器清空终端的缓冲器并向远端调制解调器发送中断信号
AT\K3 调制解调器不清空终端的缓冲器,但向远端调制解调器发送中断信号
AT\K5 调制解调器随发送的数据发送中断信号. 调制解调器在连机命令状态时数据传输过程中,做如下操作
AT\K0,1 调制解调器清空终端的缓冲器,并向远端调制解调器发送中断信号
AT\K2,3 调制解调器不清空缓冲器,但向远端调制解调器发送中断信号
AT\K4,5 调制解调器随传输的数据按顺序发送中断信号 在非纠错模式下收到来自DTE的中断信号时,调制解调器做如下操作
AT\K0,1 调制解调器清除终端的缓冲器,并向本地DTE发送中断信号
AT\K2,3 调制解调器不清除缓冲器,但向本地DTE发送中断信号
AT\K4,5 调制解调器随接收的数据按顺序发送中断信号
AT\Ln MNP块传输控制 缺省:0
AT\L0 对于MNP链路连接使用流模式
AT\L1 对于MNP链路连接使用块模式
AT\Nn 操作模式控制 缺省:3
AT\N0 选择标准速度缓存模式(无纠错)
AT\N1 选择直接模式(等效于&M0,&Q0)
AT\N2 选择可靠模式,可靠连接失败会使调制解调器挂机
AT\N3 选择自动可靠模式
AT\N4 选择LAPM纠错模式,LAPM纠错连接失败会使调制解调器挂机
AT\N5 选择MNP纠错模式,MNP纠错连接失败会使调制解调器挂机
AT\Vn 单线连接信息 缺省:0
AT\V0 关闭单线连接信息。
AT\V1 打开单线连接信息。
AT%C* 压缩控制 缺省: 3
AT%C0 关闭数据压缩 AT%C1 打开MNP5数据压缩
AT%C2 打开V.42bis数据压缩
AT%C3 打开MNP5和V.42bis数据压缩
AT%En 开/关自动均衡 缺省:2
控制是使调制解调器自动监听线路质量并请求均衡(%E1)还是当线路质量不好时降速,线路质量好时升速。
AT%E0 关闭线路质量监听和自动均衡。
AT%E1 打开线路质量监听和自动均衡。
AT%E2 打开线路质量监听和速率自动调整上升或下降。
AT%E3 打开线路质量监听和采用快速挂机的自动均衡。
AT%L 报告接收灵敏度
返回接收信号的电平值,提供以下数值
001=-1dBm接收电平
002=-2dBm接收电平
: :
043=-43dBm接收电平
AT%On 选择应答或呼叫模式 缺省:1
AT%O0 选择应答式模
AT%O1 选择发起式模
AT%Rn 选择接收灵敏度 (适用於专线型号) 缺省:0
AT%R0 -43dBm
AT%R1 -33dBm
备选:适用於拔号线型号,JP2跳线:-33dBM 连接1-2 针;-43 连接2-3针
AT%Q 显示线路信号质量
返回眼图指标(EQM)值的高字节,该字节的表示范围为0到127,当这一数值为70DC±10(依赖于线路速率)或更大时,若已使用了AT%E1命令则调制解调器将自动均衡,标准连接时这一数在0到15之间。到60时则为较差连接。
AT#CIDn 呼叫者身份鉴定 缺省:0
AT#CID=0关闭呼叫者身份鉴定
AT#CID=1打开DTE格式化形式的呼叫者身份鉴定
AT#CID=2打开DTE非格式化形式的呼叫者身份鉴定
AT#CID? 从调制解调器中恢复当前呼叫者身份鉴定方式
AT#CID=? 返回调制解调器允许模式的列表,表中各部分间用逗号隔开AT-SDR=n 鉴别性振铃 缺省:0
AT-SDR=0 允许任何振铃、并报告”RING”
AT-SDR=1 允许一类型振铃
AT-SDR=2 允许二类型振铃
AT-SDR=3 允许一及二类型振铃
AT-SDR=4 允许三类型振铃
AT-SDR=5 允许一及三类型振铃
AT-SDR=6 允许二及三类型振铃
AT-SDR=7 允许一、二及三类型振铃
| 响2秒、停4秒 | |
| 响0.8秒、停0.4秒、响0.8秒、停4秒 | |
| 响0.4秒、停0.2秒、响0.4秒、停0.2秒、响0.8秒、停4秒 |
AT+MS* 选择线路调制方式
命令格式为(336型号):
AT+MS=<模式>,<自动模式>,<最小速率>,<最大速率>
缺省值为 AT+MS=11,1,300,33600 (336型号)命令格式为(560型号):
AT+MS=<模式>,<自动模式>,<最小速率>,<最大速率>,
<x_law>,<rb_signal>,<maxup_rate>
缺省值为 AT+MS=12,1,300,56000,33600 (560型号)AT+MS? 向包含所选选项的DTE发送一信息流
AT+MS=? 向包含所提供选项的DTE发送一信息流
| 调制方式选择 | ||
| V.21 | 300 | |
| V.22 | 1200 | |
| V.22bis | 2400或1200 | |
| V.23 | 1200 | |
| V.32 | 9600或4800 | |
| V.32bis | 14400,12000,9600,7200 或4800 | |
| V.34 | 33600,31200,28800,26400,24000,21600,19200, 16800,14400,12000, 9600,7200,4800或2400 |
|
| V.90 | 56000,54667,53333,52000,50667,49333,48000,46667,45333,42667, 41333,40000,38667,37333,36000,34667,33333,32000,30667,29333, 28000 (560型号适用) |
|
| K56flex | 56000,54000,52000,50000,48000,46000,44000,42000,40000,38000, 36000,34000,32000 (560型号适用) |
|
| Bell 103 | 300 | |
| Bell 212 | 1200 |
<x_law> 是一个可选的数字,用来确定码类型,选择是:
0 = u-Law 1 = A-Law注意:ATZ命令将复位<x_law>值为0 (u-Law)。
<rb_signaling> 是一个可选的数字,用于配置一个发送数据的调制解调器产生“丢失位”信号或不产生“丢 失位”信号;或配置一台接收数据的调制解调器检测“丢失位”信号或不检测“丢失位”信 号。选择是:
0 = 发送数据的调制解调器产生丢失位信号。接收数据的调制解调器检测丢失位信号。1= 发送数据的调制解调器不产生丢失位信号。接收数据的调制解调器不检测丢失位信号。
注意:ATZ命令将复位<rb_signaling>值为0。
Maxup_rate : 连接速率的最大值。
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1535176
[转]内存种类知多少
Monday, January 14th, 2008终于知道为什么SDRAM都被CPU当做Internal RAM来用。
凡是对电脑有所了解的朋友都知道内存这玩意,可是,可能有不少朋友对内存的认识仅仅局限在SDRAM和DDR SDRAM这两种类型,事实上,内存的种类是非常多的,从能否写入的角度来分,就可以分为RAM(随机存取存储器)和ROM(只读存储器)这两大类。每一类别里面有分别有许多种类的内存。以下就让我们看看内存到底有些什么种类吧!
一、RAM(Random Access Memory,随机存取存储器)
RAM的特点是:电脑开机时,操作系统和应用程序的所有正在运行的数据和程序都会放置其中,并且随时可以对存放在里面的数据进行修改和存取。它的工作需要由持续的电力提供,一旦系统断电,存放在里面的所有数据和程序都会自动清空掉,并且再也无法恢复。
根据组成元件的不同,RAM内存又分为以下十八种:
01.DRAM(Dynamic RAM,动态随机存取存储器):
这是最普通的RAM,一个电子管与一个电容器组成一个位存储单元,DRAM将每个内存位作为一个电荷保存在位存储单元中,用电容的充放电来做储存动作,但因电容本身有漏电问题,因此必须每几微秒就要刷新一次,否则数据会丢失。存取时间和放电时间一致,约为2~4ms。因为成本比较便宜,通常都用作计算机内的主存储器。
02.SRAM(Static RAM,静态随机存取存储器)
静态,指的是内存里面的数据可以长驻其中而不需要随时进行存取。每6颗电子管组成一个位存储单元,因为没有电容器,因此无须不断充电即可正常运作,因此它可以比一般的动态随机处理内存处理速度更快更稳定,往往用来做高速缓存。
03.VRAM(Video RAM,视频内存)
它的主要功能是将显卡的视频数据输出到数模转换器中,有效降低绘图显示芯片的工作负担。它采用双数据口设计,其中一个数据口是并行式的数据输出入口,另一个是串行式的数据输出口。多用于高级显卡中的高档内存。
04.FPM DRAM(Fast Page Mode DRAM,快速页切换模式动态随机存取存储器)
改良版的DRAM,大多数为72Pin或30Pin的模块。传统的DRAM在存取一个BIT的数据时,必须送出行地址和列地址各一次才能读写数据。而FRM DRAM在触发了行地址后,如果CPU需要的地址在同一行内,则可以连续输出列地址而不必再输出行地址了。由于一般的程序和数据在内存中排列的地址是连续的,这种情况下输出行地址后连续输出列地址就可以得到所需要的数据。FPM将记忆体内部隔成许多页数Pages,从512B到数KB不等,在读取一连续区域内的数据时,就可以通过快速页切换模式来直接读取各page内的资料,从而大大提高读取速度。在96年以前,在486时代和PENTIUM时代的初期,FPM DRAM被大量使用。
05.EDO DRAM(Extended Data Out DRAM,延伸数据输出动态随机存取存储器)
这是继FPM之后出现的一种存储器,一般为72Pin、168Pin的模块。它不需要像FPM DRAM那样在存取每一BIT 数据时必须输出行地址和列地址并使其稳定一段时间,然后才能读写有效的数据,而下一个BIT的地址必须等待这次读写操作完成才能输出。因此它可以大大缩短等待输出地址的时间,其存取速度一般比FPM模式快15%左右。它一般应用于中档以下的Pentium主板标准内存,后期的486系统开始支持EDO DRAM,到96年后期,EDO DRAM开始执行。。
06.BEDO DRAM(Burst Extended Data Out DRAM,爆发式延伸数据输出动态随机存取存储器)
这是改良型的EDO DRAM,是由美光公司提出的,它在芯片上增加了一个地址计数器来追踪下一个地址。它是突发式的读取方式,也就是当一个数据地址被送出后,剩下的三个数据每一个都只需要一个周期就能读取,因此一次可以存取多组数据,速度比EDO DRAM快。但支持BEDO DRAM内存的主板可谓少之又少,只有极少几款提供支持(如VIA APOLLO VP2),因此很快就被DRAM取代了。
07.MDRAM(Multi-Bank DRAM,多插槽动态随机存取存储器)
MoSys公司提出的一种内存规格,其内部分成数个类别不同的小储存库 (BANK),也即由数个属立的小单位矩阵所构成,每个储存库之间以高于外部的资料速度相互连接,一般应用于高速显示卡或加速卡中,也有少数主机板用于L2高速缓存中。
08.WRAM(Window RAM,窗口随机存取存储器)
韩国Samsung公司开发的内存模式,是VRAM内存的改良版,不同之处是它的控制线路有一、二十组的输入/输出控制器,并采用EDO的资料存取模式,因此速度相对较快,另外还提供了区块搬移功能(BitBlt),可应用于专业绘图工作上。
09.RDRAM(Rambus DRAM,高频动态随机存取存储器)
Rambus公司独立设计完成的一种内存模式,速度一般可以达到500~530MB/s,是DRAM的10倍以上。但使用该内存后内存控制器需要作相当大的改变,因此它们一般应用于专业的图形加速适配卡或者电视游戏机的视频内存中。
10.SDRAM(Synchronous DRAM,同步动态随机存取存储器)
这是一种与CPU实现外频Clock同步的内存模式,一般都采用168Pin的内存模组,工作电压为3.3V。 所谓clock同步是指内存能够与CPU同步存取资料,这样可以取消等待周期,减少数据传输的延迟,因此可提升计算机的性能和效率。
11.SGRAM(Synchronous Graphics RAM,同步绘图随机存取存储器)
SDRAM的改良版,它以区块Block,即每32bit为基本存取单位,个别地取回或修改存取的资料,减少内存整体读写的次数,另外还针对绘图需要而增加了绘图控制器,并提供区块搬移功能(BitBlt),效率明显高于SDRAM。
12.SB SRAM(Synchronous Burst SRAM,同步爆发式静态随机存取存储器)
一般的SRAM是非同步的,为了适应CPU越来越快的速度,需要使它的工作时脉变得与系统同步,这就是SB SRAM产生的原因。
13.PB SRAM(Pipeline Burst SRAM,管线爆发式静态随机存取存储器)
CPU外频速度的迅猛提升对与其相搭配的内存提出了更高的要求,管线爆发式SRAM取代同步爆发式SRAM成为必然的选择,因为它可以有效地延长存取时脉,从而有效提高访问速度。
14.DDR SDRAM(Double Data Rate二倍速率同步动态随机存取存储器)
作为SDRAM的换代产品,它具有两大特点:其一,速度比SDRAM有一倍的提高;其二,采用了DLL(Delay Locked Loop:延时锁定回路)提供一个数据滤波信号。这是目前内存市场上的主流模式。
15.SLDRAM (Synchronize Link,同步链环动态随机存取存储器)
这是一种扩展型SDRAM结构内存,在增加了更先进同步电路的同时,还改进了逻辑控制电路,不过由于技术显示,投入实用的难度不小。
16.CDRAM(CACHED DRAM,同步缓存动态随机存取存储器)
这是三菱电气公司首先研制的专利技术,它是在DRAM芯片的外部插针和内部DRAM之间插入一个SRAM作为二级CACHE使用。当前,几乎所有的CPU都装有一级CACHE来提高效率,随着CPU时钟频率的成倍提高,CACHE不被选中对系统性能产生的影响将会越来越大,而CACHE DRAM所提供的二级CACHE正好用以补充CPU一级CACHE之不足,因此能极大地提高CPU效率。
17.DDRII (Double Data Rate Synchronous DRAM,第二代同步双倍速率动态随机存取存储器)
DDRII 是DDR原有的SLDRAM联盟于1999年解散后将既有的研发成果与DDR整合之后的未来新标准。DDRII的详细规格目前尚未确定。
18.DRDRAM (Direct Rambus DRAM)
是下一代的主流内存标准之一,由Rambus 公司所设计发展出来,是将所有的接脚都连结到一个共同的Bus,这样不但可以减少控制器的体积,已可以增加资料传送的效率。
二、ROM(READ Only Memory,只读存储器)
ROM是线路最简单半导体电路,通过掩模工艺,一次性制造,在元件正常工作的情况下,其中的代码与数据将永久保存,并且不能够进行修改。一般应用于PC系统的程序码、主机板上的 BIOS (基本输入/输出系统Basic Input/Output System)等。它的读取速度比RAM慢很多。
根据组成元件的不同,ROM内存又分为以下五种:
1.MASK ROM(掩模型只读存储器)
制造商为了大量生产ROM内存,需要先制作一颗有原始数据的ROM或EPROM作为样本,然后再大量复制,这一样本就是MASK ROM,而烧录在MASK ROM中的资料永远无法做修改。它的成本比较低。
2.PROM(Programmable ROM,可编程只读存储器)
这是一种可以用刻录机将资料写入的ROM内存,但只能写入一次,所以也被称为“一次可编程只读存储器”(One Time Progarmming ROM,OTP-ROM)。PROM在出厂时,存储的内容全为1,用户可以根据需要将其中的某些单元写入数据0(部分的PROM在出厂时数据全为0,则用户可以将其中的部分单元写入1), 以实现对其“编程”的目的。
3.EPROM(Erasable Programmable,可擦可编程只读存储器)
这是一种具有可擦除功能,擦除后即可进行再编程的ROM内存,写入前必须先把里面的内容用紫外线照射它的IC卡上的透明视窗的方式来清除掉。这一类芯片比较容易识别,其封装中包含有“石英玻璃窗”,一个编程后的EPROM芯片的“石英玻璃窗”一般使用黑色不干胶纸盖住, 以防止遭到阳光直射。
4.EEPROM(Electrically Erasable Programmable,电可擦可编程只读存储器)
功能与使用方式与EPROM一样,不同之处是清除数据的方式,它是以约20V的电压来进行清除的。另外它还可以用电信号进行数据写入。这类ROM内存多应用于即插即用(PnP)接口中。
5.Flash Memory(快闪存储器)
这是一种可以直接在主机板上修改内容而不需要将IC拔下的内存,当电源关掉后储存在里面的资料并不会流失掉,在写入资料时必须先将原本的资料清除掉,然后才能再写入新的资料,缺点为写入资料的速度太慢。
[转]嵌入式系统 Boot Loader 技术内幕
Monday, January 14th, 2008级别: 初级
詹荣开 (zhanrk@sohu.com), Linux爱好者
2003 年 12 月 01 日
本文详细地介绍了基于嵌入式系统中的 OS 启动加载程序 ―― Boot Loader 的概念、软件设计的主要任务以及结构框架等内容。
在专用的嵌入式板子运行 GNU/Linux 系统已经变得越来越流行。一个嵌入式 Linux 系统从软件的角度看通常可以分为四个层次:
1. 引导加载程序。包括固化在固件(firmware)中的 boot 代码(可选),和 Boot Loader 两大部分。
2. Linux 内核。特定于嵌入式板子的定制内核以及内核的启动参数。
3. 文件系统。包括根文件系统和建立于 Flash 内存设备之上文件系统。通常用 ram disk 来作为 root fs。
4. 用户应用程序。特定于用户的应用程序。有时在用户应用程序和内核层之间可能还会包括一个嵌入式图形用户界面。常用的嵌入式 GUI 有:MicroWindows 和 MiniGUI 懂。
引导加载程序是系统加电后运行的第一段软件代码。回忆一下 PC 的体系结构我们可以知道,PC 机中的引导加载程序由 BIOS(其本质就是一段固件程序)和位于硬盘 MBR 中的 OS Boot Loader(比如,LILO 和 GRUB 等)一起组成。BIOS 在完成硬件检测和资源分配后,将硬盘 MBR 中的 Boot Loader 读到系统的 RAM 中,然后将控制权交给 OS Boot Loader。Boot Loader 的主要运行任务就是将内核映象从硬盘上读到 RAM 中,然后跳转到内核的入口点去运行,也即开始启动操作系统。
而在嵌入式系统中,通常并没有像 BIOS 那样的固件程序(注,有的嵌入式 CPU 也会内嵌一段短小的启动程序),因此整个系统的加载启动任务就完全由 Boot Loader 来完成。比如在一个基于 ARM7TDMI core 的嵌入式系统中,系统在上电或复位时通常都从地址 0×00000000 处开始执行,而在这个地址处安排的通常就是系统的 Boot Loader 程序。
本文将从 Boot Loader 的概念、Boot Loader 的主要任务、Boot Loader 的框架结构以及 Boot Loader 的安装等四个方面来讨论嵌入式系统的 Boot Loader。
|
简单地说,Boot Loader 就是在操作系统内核运行之前运行的一段小程序。通过这段小程序,我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境。
通常,Boot Loader 是严重地依赖于硬件而实现的,特别是在嵌入式世界。因此,在嵌入式世界里建立一个通用的 Boot Loader 几乎是不可能的。尽管如此,我们仍然可以对 Boot Loader 归纳出一些通用的概念来,以指导用户特定的 Boot Loader 设计与实现。
每种不同的 CPU 体系结构都有不同的 Boot Loader。有些 Boot Loader 也支持多种体系结构的 CPU,比如 U-Boot 就同时支持 ARM 体系结构和MIPS 体系结构。除了依赖于 CPU 的体系结构外,Boot Loader 实际上也依赖于具体的嵌入式板级设备的配置。这也就是说,对于两块不同的嵌入式板而言,即使它们是基于同一种 CPU 而构建的,要想让运行在一块板子上的 Boot Loader 程序也能运行在另一块板子上,通常也都需要修改 Boot Loader 的源程序。
2. Boot Loader 的安装媒介(Installation Medium)
系统加电或复位后,所有的 CPU 通常都从某个由 CPU 制造商预先安排的地址上取指令。比如,基于 ARM7TDMI core 的 CPU 在复位时通常都从地址 0×00000000 取它的第一条指令。而基于 CPU 构建的嵌入式系统通常都有某种类型的固态存储设备(比如:ROM、EEPROM 或 FLASH 等)被映射到这个预先安排的地址上。因此在系统加电后,CPU 将首先执行 Boot Loader 程序。
下图1就是一个同时装有 Boot Loader、内核的启动参数、内核映像和根文件系统映像的固态存储设备的典型空间分配结构图。
图1 固态存储设备的典型空间分配结构

3. 用来控制 Boot Loader 的设备或机制
主机和目标机之间一般通过串口建立连接,Boot Loader 软件在执行时通常会通过串口来进行 I/O,比如:输出打印信息到串口,从串口读取用户控制字符等。
4. Boot Loader 的启动过程是单阶段(Single Stage)还是多阶段(Multi-Stage)
通常多阶段的 Boot Loader 能提供更为复杂的功能,以及更好的可移植性。从固态存储设备上启动的 Boot Loader 大多都是 2 阶段的启动过程,也即启动过程可以分为 stage 1 和 stage 2 两部分。而至于在 stage 1 和 stage 2 具体完成哪些任务将在下面讨论。
5. Boot Loader 的操作模式 (Operation Mode)
大多数 Boot Loader 都包含两种不同的操作模式:”启动加载”模式和”下载”模式,这种区别仅对于开发人员才有意义。但从最终用户的角度看,Boot Loader 的作用就是用来加载操作系统,而并不存在所谓的启动加载模式与下载工作模式的区别。
启动加载(Boot loading)模式:这种模式也称为”自主”(Autonomous)模式。也即 Boot Loader 从目标机上的某个固态存储设备上将操作系统加载到 RAM 中运行,整个过程并没有用户的介入。这种模式是 Boot Loader 的正常工作模式,因此在嵌入式产品发布的时侯,Boot Loader 显然必须工作在这种模式下。
下载(Downloading)模式:在这种模式下,目标机上的 Boot Loader 将通过串口连接或网络连接等通信手段从主机(Host)下载文件,比如:下载内核映像和根文件系统映像等。从主机下载的文件通常首先被 Boot Loader 保存到目标机的 RAM 中,然后再被 Boot Loader 写到目标机上的FLASH 类固态存储设备中。Boot Loader 的这种模式通常在第一次安装内核与根文件系统时被使用;此外,以后的系统更新也会使用 Boot Loader 的这种工作模式。工作于这种模式下的 Boot Loader 通常都会向它的终端用户提供一个简单的命令行接口。
像 Blob 或 U-Boot 等这样功能强大的 Boot Loader 通常同时支持这两种工作模式,而且允许用户在这两种工作模式之间进行切换。比如,Blob 在启动时处于正常的启动加载模式,但是它会延时 10 秒等待终端用户按下任意键而将 blob 切换到下载模式。如果在 10 秒内没有用户按键,则 blob 继续启动 Linux 内核。
6. BootLoader 与主机之间进行文件传输所用的通信设备及协议
最常见的情况就是,目标机上的 Boot Loader 通过串口与主机之间进行文件传输,传输协议通常是 xmodem/ymodem/zmodem 协议中的一种。但是,串口传输的速度是有限的,因此通过以太网连接并借助 TFTP 协议来下载文件是个更好的选择。
此外,在论及这个话题时,主机方所用的软件也要考虑。比如,在通过以太网连接和 TFTP 协议来下载文件时,主机方必须有一个软件用来的提供 TFTP 服务。
在讨论了 BootLoader 的上述概念后,下面我们来具体看看 BootLoader 的应该完成哪些任务。
|
在继续本节的讨论之前,首先我们做一个假定,那就是:假定内核映像与根文件系统映像都被加载到 RAM 中运行。之所以提出这样一个假设前提是因为,在嵌入式系统中内核映像与根文件系统映像也可以直接在 ROM 或 Flash 这样的固态存储设备中直接运行。但这种做法无疑是以运行速度的牺牲为代价的。
从操作系统的角度看,Boot Loader 的总目标就是正确地调用内核来执行。
另外,由于 Boot Loader 的实现依赖于 CPU 的体系结构,因此大多数 Boot Loader 都分为 stage1 和 stage2 两大部分。依赖于 CPU 体系结构的代码,比如设备初始化代码等,通常都放在 stage1 中,而且通常都用汇编语言来实现,以达到短小精悍的目的。而 stage2 则通常用C语言来实现,这样可以实现给复杂的功能,而且代码会具有更好的可读性和可移植性。
Boot Loader 的 stage1 通常包括以下步骤(以执行的先后顺序):
- 硬件设备初始化。
- 为加载 Boot Loader 的 stage2 准备 RAM 空间。
- 拷贝 Boot Loader 的 stage2 到 RAM 空间中。
- 设置好堆栈。
- 跳转到 stage2 的 C 入口点。
Boot Loader 的 stage2 通常包括以下步骤(以执行的先后顺序):
3.1.1 基本的硬件初始化
这是 Boot Loader 一开始就执行的操作,其目的是为 stage2 的执行以及随后的 kernel 的执行准备好一些基本的硬件环境。它通常包括以下步骤(以执行的先后顺序):
1. 屏蔽所有的中断。为中断提供服务通常是 OS 设备驱动程序的责任,因此在 Boot Loader 的执行全过程中可以不必响应任何中断。中断屏蔽可以通过写 CPU 的中断屏蔽寄存器或状态寄存器(比如 ARM 的 CPSR 寄存器)来完成。
2. 设置 CPU 的速度和时钟频率。
3. RAM 初始化。包括正确地设置系统的内存控制器的功能寄存器以及各内存库控制寄存器等。
4. 初始化 LED。典型地,通过 GPIO 来驱动 LED,其目的是表明系统的状态是 OK 还是 Error。如果板子上没有 LED,那么也可以通过初始化 UART 向串口打印 Boot Loader 的 Logo 字符信息来完成这一点。
5. 关闭 CPU 内部指令/数据 cache。
3.1.2 为加载 stage2 准备 RAM 空间
为了获得更快的执行速度,通常把 stage2 加载到 RAM 空间中来执行,因此必须为加载 Boot Loader 的 stage2 准备好一段可用的 RAM 空间范围。
由于 stage2 通常是 C 语言执行代码,因此在考虑空间大小时,除了 stage2 可执行映象的大小外,还必须把堆栈空间也考虑进来。此外,空间大小最好是 memory page 大小(通常是 4KB)的倍数。一般而言,1M 的 RAM 空间已经足够了。具体的地址范围可以任意安排,比如 blob 就将它的 stage2 可执行映像安排到从系统 RAM 起始地址 0xc0200000 开始的 1M 空间内执行。但是,将 stage2 安排到整个 RAM 空间的最顶 1MB(也即(RamEnd-1MB) - RamEnd)是一种值得推荐的方法。
为了后面的叙述方便,这里把所安排的 RAM 空间范围的大小记为:stage2_size(字节),把起始地址和终止地址分别记为:stage2_start 和 stage2_end(这两个地址均以 4 字节边界对齐)。因此:
stage2_end=stage2_start+stage2_size |
另外,还必须确保所安排的地址范围的的确确是可读写的 RAM 空间,因此,必须对你所安排的地址范围进行测试。具体的测试方法可以采用类似于 blob 的方法,也即:以 memory page 为被测试单位,测试每个 memory page 开始的两个字是否是可读写的。为了后面叙述的方便,我们记这个检测算法为:test_mempage,其具体步骤如下:
1. 先保存 memory page 一开始两个字的内容。
2. 向这两个字中写入任意的数字。比如:向第一个字写入 0×55,第 2 个字写入 0xaa。
3. 然后,立即将这两个字的内容读回。显然,我们读到的内容应该分别是 0×55 和 0xaa。如果不是,则说明这个 memory page 所占据的地址范围不是一段有效的 RAM 空间。
4. 再向这两个字中写入任意的数字。比如:向第一个字写入 0xaa,第 2 个字中写入 0×55。
5. 然后,立即将这两个字的内容立即读回。显然,我们读到的内容应该分别是 0xaa 和 0×55。如果不是,则说明这个 memory page 所占据的地址范围不是一段有效的 RAM 空间。
6. 恢复这两个字的原始内容。测试完毕。
为了得到一段干净的 RAM 空间范围,我们也可以将所安排的 RAM 空间范围进行清零操作。
3.1.3 拷贝 stage2 到 RAM 中
拷贝时要确定两点:(1) stage2 的可执行映象在固态存储设备的存放起始地址和终止地址;(2) RAM 空间的起始地址。
3.1.4 设置堆栈指针 sp
堆栈指针的设置是为了执行 C 语言代码作好准备。通常我们可以把 sp 的值设置为(stage2_end-4),也即在 3.1.2 节所安排的那个 1MB 的 RAM 空间的最顶端(堆栈向下生长)。
此外,在设置堆栈指针 sp 之前,也可以关闭 led 灯,以提示用户我们准备跳转到 stage2。
经过上述这些执行步骤后,系统的物理内存布局应该如下图2所示。
3.1.5 跳转到 stage2 的 C 入口点
在上述一切都就绪后,就可以跳转到 Boot Loader 的 stage2 去执行了。比如,在 ARM 系统中,这可以通过修改 PC 寄存器为合适的地址来实现。
图2 bootloader 的 stage2 可执行映象刚被拷贝到 RAM 空间时的系统内存布局

3.2 Boot Loader 的 stage2
正如前面所说,stage2 的代码通常用 C 语言来实现,以便于实现更复杂的功能和取得更好的代码可读性和可移植性。但是与普通 C 语言应用程序不同的是,在编译和链接 boot loader 这样的程序时,我们不能使用 glibc 库中的任何支持函数。其原因是显而易见的。这就给我们带来一个问题,那就是从那里跳转进 main() 函数呢?直接把 main() 函数的起始地址作为整个 stage2 执行映像的入口点或许是最直接的想法。但是这样做有两个缺点:1)无法通过main() 函数传递函数参数;2)无法处理 main() 函数返回的情况。一种更为巧妙的方法是利用 trampoline(弹簧床)的概念。也即,用汇编语言写一段trampoline 小程序,并将这段 trampoline 小程序来作为 stage2 可执行映象的执行入口点。然后我们可以在 trampoline 汇编小程序中用 CPU 跳转指令跳入 main() 函数中去执行;而当 main() 函数返回时,CPU 执行路径显然再次回到我们的 trampoline 程序。简而言之,这种方法的思想就是:用这段 trampoline 小程序来作为 main() 函数的外部包裹(external wrapper)。
下面给出一个简单的 trampoline 程序示例(来自blob):
.text .globl _trampoline _trampoline: bl main /* if main ever returns we just call it again */ b _trampoline |
可以看出,当 main() 函数返回后,我们又用一条跳转指令重新执行 trampoline 程序――当然也就重新执行 main() 函数,这也就是 trampoline(弹簧床)一词的意思所在。
3.2.1初始化本阶段要使用到的硬件设备
这通常包括:(1)初始化至少一个串口,以便和终端用户进行 I/O 输出信息;(2)初始化计时器等。
在初始化这些设备之前,也可以重新把 LED 灯点亮,以表明我们已经进入 main() 函数执行。
设备初始化完成后,可以输出一些打印信息,程序名字字符串、版本号等。
3.2.2 检测系统的内存映射(memory map)
所谓内存映射就是指在整个 4GB 物理地址空间中有哪些地址范围被分配用来寻址系统的 RAM 单元。比如,在 SA-1100 CPU 中,从 0xC000,0000 开始的 512M 地址空间被用作系统的 RAM 地址空间,而在 Samsung S3C44B0X CPU 中,从 0×0c00,0000 到 0×1000,0000 之间的 64M 地址空间被用作系统的


















