Foxdisk10-小字库显示汉字1

在以前的博客中曾经讨论过,设计的Foxdisk代码段和数据段总共只能128K。可是随便哪个汉字库轻轻松松都超过了100K,大的有近500K(我设计的时候只用了24×24以及16×16的字模),无论如何是不能包含在程序中的。唯一的办法,是将需要用的汉字字模提取出来,实现小字库的汉字显示。

当然,如果改变Foxdisk的设计方法,比如采用很久以前用过的一种技术:在硬盘的末端存一个裁剪的DOS或者linux,用OS来支持。现在遇到的存储空间不够啊、想实现的功能难以实现啊,都能解决。并且还能复用大量的DOS/Linux的软件,还是很不错的。不过,这就失去了我当时设计的初衷了,本来就是用来学习底层知识的一个小项目,用这种方法则屏蔽了大量的实现细节,所以我就放弃了。不过,在公司早期的一款产品中(卖了好几年,技术支持巨难,后来被我停掉了)是用了这种方法的,有机会也开个博客仔细探讨下。

文字显示主要包括文字的读取和显示两个步骤。西方文字显示完全可以集成在一个ROM内,但是中文字太多,通常都要用到字库。中文字库有两大类型:点阵式字库和矢量字库。

点阵式字库是通过将中文字看成一个个点组成的二维阵列来实现显示;矢量字库则通过对文字每个笔划的起始点和结束点的记录来完成文字显示。很明显,点阵字如果要放大文字将会出现明显的不平滑现象;而矢量字无论字的大小都可以保证字体圆滑。但是由于点阵字理解简单、便于编程等原因,在很多领域点阵字库成为了主流。

当前只针对点阵字库进行讨论研究,并针对研究的各字体库进行分析,阐述其在编程上的一些区别。在此基础上,提供了无字库方案的实现过程及代码。 点阵字的显示原理其实很简单。在规定大小的区域格内,如16×16、24×24等,将需要显示的字的对应格填充即可。字库内对应的格则以1表示,没有填充的以0表示。如图所示:

图1 英文字模图样

这是英文字模的图样,而中文字模的图样是这样的:

图2 中文字模的图样

图中的字模信息就存储在相应的字库中,而程序员就可以按照显示原理在显示器上以点位的方式显示字符。

每个英文字母都是由一个ASCII码组成的,而中文字是扩展ASCII码组成的。也就是说一个中文字是由左半边和右半边两部分的信息组成的。字库中的文字一般是按区位码的形式存放的,大体原理相同,但是在实际组织的时候有些细微的差别。[1]

本文所讨论的字库有HZK16(16×16汉字库)、HZK24S(24×24宋体)、HZK24H(24×24黑体)、HZK24F(24×24仿宋)、HZK24K(24×24楷体)、HZK24T(24×24汉字全角字符)。

对于24×24以上的汉字字库,比如48×48汉字字库,其存储方式与24×24 的相同,只是大小不同,简单修改就可适用,以下将作详细说明。

HZK16的中汉字存储可以用公式表示如下:

  • 区码 = 文字左半边信息(扩展ASCII码) – 0xA0
  • 位码 = 文字右半边信息 – 0xA0
  • 在字库中的地址 = [(区码-1) *94 + (位码-1)] * 32

其中1、2步减去0xA0的原因是中文字符用到的扩展ASCII码是从0xA0之后开始计算的,94的来源是字库中每个区最多存放94个中文字点阵。另外,因为HZK16是16×16字体,每个字由32个字节组成(16位x16)。[1]

24×24字体库中文字库与 16×16字体库的组织有些不同。HZK24T中已经存放了汉字的全角字符,其余的各种字体的汉字库16区之前是独立在此的。也就是说,对HZK24S、HZK24H、HZK24F和HZK24K,公式如下:

  • 区码 = 文字左半边信息(扩展ASCII码) – 0xA0
  • 位码 = 文字右半边信息 – 0xA0
  • 在字库中的地址 = [(区码-1- 15) *94 + (位码-1)] * 72

注意在第三步要减去15,前15区的字符存放在HZK24T中了。72表示24×24字库中汉字是按72字节存储的(24位x24)。

对于更大的字体,如32×32、48×48等,也是按照这样的方式存储。只是在第3步的计算中,32×32字体库中汉字占用 128字节,48×48字体库中汉字占用288字节。

汉字的点阵结构介绍完了,后面就面临着怎么提取字模以及显示的问题。实际上,在工具篇中已经有所涉及提取的问题了,下面两篇博客中详细描述。

光伏云项目杂记01—modbus串口丢包了

和深圳某家公司合作光伏云的项目,我们主要做数据传输模块,对方负责开发云端,光伏逆变器外采。针对不同的应用场景,做了三款产品。来来回回用了一段时间,反响还不错,和其他同类产品相比还比较稳定。图一是其中一款用得较多的数据传输模块。

图1 物联网透传设备-gprs dtu

前一段时间现场的工程师报了一个问题,处理过程比较有趣,就记录了下来。

数据传输模块(也即数据透传模块,简称DTU)安装在逆变器上,通过串口把逆变器的数据接收过来,DTU通过GPRS(2.5G)转发给服务器。DTU最重要的是不能断网,任何情况下导致断网都必须重新连接后台,保证数据的连续性。

工程师报告,使用组态软件连接DTU(串口连接),把数据发送到服务器。数据是按包的方式定时发送的,每过5分钟会有一个包丢失。有多个设备通过组态软件连接DTU,也就是说有很多包丢失了。

接到报告后,我开始着手搭建环境,模拟现场。工程师使用的是modbus slave,我找他要来相应的版本软件。DTU与一台小的Mini台式机相连,在我自己的阿里云上运行自己写的网络调试工具。

数据包一个个发出去,几个小时下来,没有出现丢包的现象。把modbus slave换成串口调试助手,也没有出现丢包现象。我把数据包调大,大概200字节一次发送,这次出现了。

联系后台软件工程师,用他的云平台配合modslave以及DTU配合测试。其中DTU与台式机通过USB转串的设备连接,后面也尝试直接用串口线连接。经过20小时的测试,现象不大一致。使用USB转串设备进行测试的,仍旧有拆包现象;使用原生串口的,没有发现拆包现象。不过,对比了服务器的后台记录,基本没有拆包,也有命令包直接就丢失了。

百思不得其解。要后台的工程师配合我,协调起来很麻烦。我回到阿里云,用自己的网络软件模拟后台。同时把modbus slave撤了,用自己以前的代码写了个串口调试的工具,继续测试。这回,不管是usb转串的还是原生串口连接,都没有出现丢包拆包。光伏云项目中,数据获取是有后端服务器软件发起的,由其发送命令包,通过DTU传送给设备,设备再通过DTU发给服务器。在测试中还发现一个现象,如果服务器发送命令包向设备要求数据,时间频率如果设为每5秒发一次,丢包现象还会出现;设置为30秒一次,则机会没有出现丢包过。

看起来有好几个问题混杂在一起,产生了这样奇怪的现象。首先,排除DTU本身固件的问题,DTU中采用了DMA机制,以单片机的处理速度,这点串口数据不应该会处理不了。最大的问题应该有两个:

1 modbus slave和串口调试助手在串口读写的编程中,有哪个地方没有处理好,导致了拆包的现象;

2 至于丢包现象,和服务器端的软件有关。工程师可能习惯了平常的internet,没有考虑到GPRS的网络稳定性远低于有线或者wifi网络。不能过于频繁的去塞数据。

分析完了,寻找证据。第二个问题好解决,请后端工程师改下通讯频率就行了,回头一起测试。 至于第一个问题,只能反汇编modbus slave了。很幸运,用IDA反汇编后,我读了半小时代码,找到了问题所在,如图2。

图2 用IDA反汇编modbus slave

问题出在SetCommTimeouts函数。定位到SetCommTimeouts函数的调用位置,其前三个参数分别为ReadIntervalTimeout=0xffffffff;ReadTotalTimeoutMultiplier=0x00; ReadTotalTimeoutConstant=0x00。也就是说,modbus slave读取一次缓冲区,读操作就完成了。

这样操作是有点问题的,对照我自己的代码,把三个参数分别改为:0x64,0x0A,0x3E8。这些修改是把exe当做文件编辑的,相当于在字节码上直接修改了。

Modbus slave的软件hack完了,重新搭建环境。4个小时后,测试结果显示,修改后的modbus slave没有出现拆包丢包现象。

Foxdisk09-工具篇

题外话,大概是2017年底,开始开发DTU,用来采集光伏逆变器的数据,通过GPRS发送给后端服务器。一个不大的物联网设备,针对客户的需求做了三种不同的形态。采用的是STM32F103C8T6+SIM800C的硬件架构,软件则用FreeRTOS,方便后续的扩展。2018年4月份,国家政策一变,原计划的50K的产品计划也逐渐泡汤。我写博客前,刚好看到文件夹中满满的资料,不禁有些惆怅。先定个小计划,Foxdisk的博客写完后,也针对这个项目写写历程吧,从前期找客户、定方案、打样到量产,甚至包括采购物料、包材、后期的款项等等,可以吐槽的地方太多了。

回到正题。发现之前提供的Foxdisk的下载链接没法用了,同时也发现小工具的代码没有放上去。什么时候有人需要,我再整整吧,这段时间有点忙,有点顾不上。

Foxdisk的编译过程中,有两个小工具需要使用,EHZ24.exe和ETRHZ.exe,名字有点怪,我随便取的。EHZ24.exe是用来提取汉字字模的工具,即将需要的汉字的点阵图提取出来,方便程序去打印到屏幕上。其主要功能如下:

  1. 可以针对多个源文件进行处理,提取C语言源代码中需要显示的汉字;
  2. 可指定提取的字模为楷体、黑体或者仿宋,字模为24×24点阵字;
  3. 提取出来的字模,自动生成一个.h的头文件,并在头文件中定义了字模的结构体。

源代码中HZK24K.h就是由这个工具自动生成的。 ETRHZ.exe提取的是16×16的点阵字,其功能与EHZ24.exe差不多,HZTABLE.H由其提取。理论上可以把这两个程序合成一个,只是因为这两个程序是我以前开发隔离卡产品时写的,当时就这么分开的,稍微改改就拿来用了,也没兴趣去合一了。如图,两个工具简陋的命令行帮助文档。

图1 汉字提取工具ETRHZ和EHZ24

这两个工具其实没什么可以说的,主要就是分析源文件,将程序中需要显示的汉字取出来,然后在相应的汉字库中,将对应的汉字字模提取,最后写入到生成的文件中。如图2,从生成的文件中,也能了解到其大致功能。

图2 ETRHZ提取的文件截图

这两个工具的源代码比较简单,有兴趣的可以看看,过段时间我上传到博客上。源代码不难看懂,主要是要了解汉字提取的原理。

为什么要提取汉字?那是因为汉字库比较大(一般200K左右,大的400多K,Foxdisk才100多K,没法放下),我们是在无操作系统的情况下工作,程序越小越好。具体的汉字显示原理,我在接下来的几篇中记录。

Foxdisk08-如何调试2

有段时间一直沉迷于裸机上跑程序,学习怎么让一个操作系统跑起来。使用过VirtualPC(现在被微软收购了),VMware,VirtualBOX,Bochs,最终还是觉得Bochs比较好用。它的最大好处是集合了完整的调试环境,对指令是软件解析的,速度虽然慢,但对程序员来说,一步步看代码是如何执行的,非常契合需求。

Bochs的文档非常的完整,使用起来也比较方便。我只针对如何调试Foxdisk,以及如何下断点进行记录,其他的可以查其自带的帮助文档。我使用的是Bochs2.2.6,帮助文档在Bochs-2.2.6\docs目录下,目前已经到了版本2.6.9,估计也差不多。

第一步是制作磁盘镜像。Bochs本身提供了镜像制作工具bximage,使用起来也比较简单。当然,也可以使用其他工具制作,比如windows下的winimage或者Linux下直接用dd命令。以制作一个4G虚拟硬盘文件为例,命令如下:

..\bximage.exe -hd -size=4096 -mode=growing -q myhd.img

Bochs的菜单栏中(我使用的是windows)提供了Disk Image Creation Tool的快捷菜单,其实就是bximage。

具体还可以参考帮助文档中Bochs User Manual-8.1 How to make a simple disk image。

第二步是编辑bochsrc.bxrc文件,指定需要启动的设备。可以启动虚拟软盘、虚拟光盘或者虚拟硬盘。找个例子看一下,修改为自己想启动的设备即可。比如虚拟硬盘,boot: c即可启动。需要提醒的是,在Foxdisk中,为了尽可能的模拟真实硬件,磁盘的参数要注意修改,一般spt=63,heads=16即可。很早以前的版本中,我的代码中没有主要到这些参数是可变的,虚拟机调试就出问题。现在的版本中,硬盘驱动的代码已经修补了这个问题,如果不去调试硬盘驱动,不必理会这些参数。双击bochsrc.bxrc即可启动,如图1,启动了一个DOS环境下的软件,当年和联想开发的一款安全计算机的界面。

图1 Bochs调试DOS程序

另外,还有一个问题,如何把需要使用的文件拷贝到虚拟硬盘中呢?我的方法比较简单,我做了一个虚拟软盘,使用winimage将需要的文件拷贝到虚拟软盘中。启动虚拟硬盘的时候,虚拟软盘作为一个挂接设备存在(注意修改bochsrc.bxrc,比如 floppya: 1_44=LBDEBUG.IMG, status=inserted  ),从软盘中将文件拷贝过去即可。

一切准备就绪后,就可调试了。最后一步,启动bochsdbg.exe,命令行如下:

…\Bochs-2.2.6\bochsdbg.exe -q -f bochsrc.bxrc。断在了bios最早的位置f000:fff0上,这时就可以进入调试了。如图2所示。

图2  bochsdbg界面

如何在bochs中对Foxdisk进行调试呢?最常用的是定位在mbr区,跟踪Foxdisk的运行。即利用bochs可以对内存下断点,定位想调试的位置,进行跟踪。典型的代码运行位置,一个是mbr的0x7c00,以及Foxdisk的Code_Seg(也即0x7000处)。以mbr定位为例:

<bochs:1> lb 0x7c00     //下断点

<bochs:2>c            //继续执行直到遇到断点

参考Foxdisk的代码,对照跟踪,很容易理解系统的运作原理。

总觉得bochs的调试工具和linux下gdb很类似,为便于查询,将常用的一些命令记录下来,详细的仍可参考其官方文档。

执行控制命令
c 继续执行,遇到断点将停止
step [count] 执行count条指令, 默认为1条
s [count] step的缩写
Ctrl-C 停止执行,返回命令行
Ctrl-D 执行完所有命令后,退出
q(quit) 退出调试器

设置断点

vb(vbreak) seg:off 在指定的虚拟地址(段+偏移)设置断点,在保护模式下也可以使用
lb(lbreak) addr 在一个线性地址设置断点
pb(pbreak) [*] addr 在一个物理地址设置断点
info break 显示所有断点状态
d(delete) n 删除一个断点

查看内存

x /nuf addr 查看一个线性地址的内存
xp /nuf addr 查看一个物理地址的内存
    n 显示多少个单位的内存
    u 内存单位大小,可以是
       b: 字节;  h: 字(2个字节);  w: 双字(4个字节);  g : 4字(8字节)
f 打印格式,可以是
    x: 16进制格式打印;  d: 10进制格式打印;  u: 无符号10进制格式打印;
o: 8进制格式打印;   t: 2进制格式打印

寄存器操作

Info registers  列举CPU整型寄存器内容
dump_cpu    查看所有与CPU相关的寄存器状态
where        打印当前call stack
set $reg=val   改变寄存器的内容,如set $edx=25

最奇怪的是,在安装Foxdisk的时候,颜色会不对,到现在我也没搞清楚怎么回事。

图3 Bochs安装Foxdisk-变色了

Foxdisk07-如何调试1

在开发过程中,我主要采用两种方式进行调试,一是使用Debug.exe,通过U盘或者光盘,启动DOS,用debug观察程序是否如想象中的写入硬盘指定区域。在早期的版本中,我也曾经去除了写硬盘以及替换MBR的功能,编译了可以在DOS下运行的Foxdisk,方便自己在DOS下对其进行调试。Debug是个很好的工具,当年开发隔离卡和双网隔离计算机的所有底层代码,均是依靠它来进行的,简单而强大。

另外一种调试方式则必须借助虚拟机了,我最常用的是使用bochs,它自带的调试功能,允许程序员看到所有的执行过程,当然也包括MBR的运行,以及如何把控制权转交给Foxdisk。因为Foxdisk是图形模式,进入图形模式之后,debug就没办法发挥作用了,后期所有的开发调试都是借助了bochs才得以进行下去。

当然,所有的调试环境使用的都是X86的汇编代码,intel指令集的参考书得常备手边。
Debug的命令不多,如图1所示,大概20几条,很容易记忆。
Foxdisk07-debuglist

图1 debug常用命令

以一个例子开始debug的使用,尝试用debug将MBR区(也即硬盘线性地址LBA0)的512字节读出,并存到文件mbr.bin中。在当年流行mbr病毒的时候,这是经常使用的方法。

如图2,使用命令’a’,编写一串汇编指令。
Foxdisk07-readmbr

图2 读取mbr

Debug中所有数据缺省都是16进制的,int 13实际上表示的是int 0x13(或者说int 13h)。使用int 0x13的02功能,读取mbr所在的位置(磁头0,磁柱0,扇区1)的数据,并拷贝到内存偏移0x200处(当前段)。

使用命令’d’查看读取到的数据。这是Foxdisk中Loader.asm编译后的二进制码,安装的时候写入到了mbr区域。
Foxdisk07-mbr2file

图3 写数据到文件

如图3,命令’w’将把BX:CX寄存器中指定字节数的内容写磁盘文件。因此,首先用命令’r’修改寄存器bx和cx。示例中需要保存的字节数为512个,因此,bx=0,cx=0x200。
‘w 200’的含义是将ds:0x200处的开始的数据,保存指定的字节数到文件中。

为了便于调试,Foxdisk中有些地方插入了标志字符串,比如Loader.asm开始的“LUOB”,这其实也是为了便于debug定位问题。

特将debug常用的命令记录在此,方便之后查看。
-? 显示 Debug 命令列表
-a [address] 指定键入汇编语言指令的位置。对 address 使用十六进制值,并键入不以
“h”字符结尾的每个值。如果不指定地址,a 将在它上次停止处开始汇编。
-d [range] 指定要显示其内容的内存区域的起始和结束地址,或起始地址和长度。有如
果不指定 range,Debug 程序将从以前 d 命令中所指定的地址范围的末尾
开始显示 128 个字节的内容。
-t [=address] 从指定地址起执行一条指令后停下来,显示所有寄存器内容及标志位的
值。如未指定地址则从当前的CS:IP开始执行。
-p [=address] 从指定地址起执行指令,直到遇到断点,如未指定地址则从当前的CS:
IP开始执行。
-g [=address] 从指定内存地址运行当前在内存中的程序。
-u [range] 对指定范围内的存储单元进行反汇编。
-r [register] ‘r’显示CPU内所有寄存器内容和标志位状态,‘r register name’则修改
某个寄存器内容。比如-r ax 修改寄存器ax的内容。
-q 退出debug。

138 total views, no views today

Foxdisk06-启动原理3

Loader.asm控制权转交给BootEntry后,开始启动Foxdisk的主体代码。BootEntry()函数中,只调用了Foxdisk()函数。BootEntry没有其他作用,只是用来定位的,方便Loader.asm去加载。

看代码前,首先熟悉一下将要用到的结构体,定义在Common.h中。
Foxdisk06-datastruct

图1 数据结构对应图

程序中允许安装四个不同的操作系统,每个系统的引导代码Foxdisk都会保存起来,理论上可以兼容所有类型的操作系统。如图1,准备了四个导航条与之对应。数据结构navBar定义了导航条的显示资料,包括字体、字符串、颜色等等;数据结构menuitem则定义了与导航条连接的菜单项的显示资料。数据结构_WallPaper是壁纸的数据结构,包含壁纸存放在硬盘中的位置及其是否有效。壁纸从1024×768的bmp中提取,没有压缩,直接存储在硬盘上,其大小为1024×768 bit。总共可以存7张不同的壁纸,在安装的时候指定。

数据结构OperationSystem对应每个导航条指定的系统,允许用户添加密码。数据结构FoxdiskPara定义了Foxdisk所用的必要信息,包括倒计时时间、默认进入的系统、foxdisk的存储位置等。

在以上数据结构的支持下,Foxdisk的启动才得以实现。在foxdisk()中,第一步启用磁盘访问的驱动,建立硬盘参数表,允许硬盘访问。第二步,获取FoxdiskPara等参数,这些参数在安装的时候已经存储在硬盘上了,接着调用bootMenu()。出现指定的壁纸及倒计时信息。如下图。
Foxdisk06-bootmenu

图2 启动界面

这是指定了壁纸的启动界面,如果没有指定,则启动缺省的foxdisk界面,如图3。
Foxdisk06-defbootmenu

图3 缺省启动界面

在启动界面上,如果按F2键,则进入Foxdisk的设置界面,类似图1。如果不按,倒计时结束将进入显示中正在进入的系统。当时为了设计右下角的显示信息,也费了一番功夫。最大的问题是,如何在不同的图片上,能够将倒计时的数据显示出来。固定颜色是不行的,很难保证图片颜色与倒计时颜色不同。最终用了些小技巧,用反色、倒影的方式进行显示,解决了这个问题。

读到现在,我也发现之前设计上的不足,很多地方过于繁复了,有些需要显示的信息,比如当前有哪些操作系统,在界面上却没有显示。

最后一步,调用SysRun()。也即在第二步中,如果按F2,将执行此处代码,进入Foxdisk的设置界面。
Foxdisk06-configmenu

图4 设置界面

设置界面的右下角提供了快捷选项。F1键唤出帮助界面;F2键为系统菜单,也即类似图1中显示的界面;F3键唤出参数设定,如图4显示;F4键还没有实现;F5键唤出关于菜单,打印关于Foxdisk的一些说明。

至此,所有启动相关的信息全部介绍完毕。Foxdisk在运行过程中,需要用到硬盘访问、分区设置、图形显示、汉字显示、时间中断等,最早曾经把扬声器也放进去了,觉得没有太大作用,后来把它去除了。

后续的博客中,将针对上述实现一一阐述,记下我在秦淮河畔曾经挥汗调试的日子。

172 total views, no views today

Foxdisk05-启动原理2

观察Foxdisk3的makefile,可以看到使用的是bcc -ms的编译开关。这是要求编译器采用Samll内存模式进行编译,其特点是栈和数据都在64K以内,Code在另外一个64K内。为什么采用Small方式?主要是我当时设计的时候,没有考虑到后面的各种新的需求,以为代码可以控制在64K内完成;另外,这种模式比较便于汇编和C的混合编写,不用去考虑复杂的数据段、代码段的定位。

在整个代码编写过程中,除了引导部分(也即Loader.asm),其他的代码尽量用C编写。但是有两部分的代码仍旧无法避免使用汇编,一是时钟中断,为了让几个任务并行工作,使用了汇编;二是32位数的乘除计算。我们在平常使用C/C++及其他高级语言的时候,大数的计算很平常。那是因为编译器已经提供了库函数调用,方便程序员使用,操作系统已经提供了这样的机制进行支持。Foxdisk某种程度上相当于简单的操作系统,这些机制完全没有提供。因此,在进行大数计算时,只能自己来写这些函数了。

回到我们的启动话题。Loader.asm包含了所有启动的秘密,其承载了将Foxdisk的演出场景安排好的任务,相当于整个程序的导演,也是整个程序最难读的地方。我试着以自己的语言,将其原理解释清楚。

Loader.asm在编译后,将链接成一段512字节的二进制码。当然,它不是单独存在的,是与其他代码混杂着存在于Foxdisk.exe中。这段512字节码将由Setup.c中的main()函数拷贝到MBR区,起到引导的作用。

Bios在释放控制权的时候,将把MBR区的代码加载到0x7C00处,并跳转执行。也即从Loader.asm中的load_start处开始执行。

整个Loader.asm实现的功能可以概括为三个:1) 将存储在硬盘上的Foxdisk代码拷贝到指定的内存;2)将Foxdisk运行的数据拷贝到另外一段64K的内存中;3) 设定栈,并远程跳转(retf)至Foxdisk的BootEntry处,开始运行Foxdisk。Loader.asm中的几个变量,nCodeSect:Foxdisk代码段占用的硬盘扇区数;nDataSect:Foxdisk数据段占用的扇区数;iBegin: Foxdisk程序镜像所存储的硬盘起始位置;这些变量均由Setup.c中的函数进行初始化,也即安装的时候确定。另外一个变量iDisk,其值为0x80或者0x81,表示BIOS所认的第一个硬盘还是第二个硬盘,也是由Setup.c中函数对其进行初始化的。如果Foxdisk镜像存储在第二个硬盘上,则必须设置为0x81。现在想来,其作用不大,而且容易给调试带来麻烦。

load_start到load_chs,尝试使用扩展中断0x13(ah=0x40以上的int 0x13)访问磁盘中的数据,加载Foxdisk的代码段至0x7000:0起始的64K内存,数据段加载至0x8000:0开始的64K内存。

如果磁盘不支持磁盘的扩展中断(这种情况基本不可能),则回到原始的硬盘访问中断,尝试将Foxdisk的代码段和数据段加载到指定的内存中。从load_chs到load_ok间做的就是这件事,其功能与上一段其实是相同的,大部分情况下也不会执行。

从标志load_ok开始,进行设置栈以及跳转的工作。Retf是条远跳转指令,它将栈中的4个字节当成CS:IP,跳转过去执行。以上工作均在实模式下进行,不用考虑复杂的权限问题。注意,在retf执行前,bx中已经包含了BootEntry的地址,执行前的push bx就是将地址压栈,准备远程跳转。

至此,我们可以暂时抛去晦涩难懂的汇编,进入了C的世界。

下一篇博客是启动原理的最后一篇,介绍Foxdisk的整体逻辑框架,从逻辑层面介绍我是如何编排实现Foxdisk的功能。

118 total views, no views today

随笔-放慢心态1

按照现在的工作状态,实在无法保证之前的技术博客按计划进行。在罪恶感的驱使下,我拿了自己无聊时写的随笔,用来充数。看看一个仍旧奋战在一线的程序员,如何在万恶的房价经济社会中,如何苦苦挣扎。

连续两周出差,身体严重透支。正好有一个小时的时间,在等一位朋友过来。用来整理技术文章是不够,正好把这一小段时间的感想记录下来。

人生过了三十岁,时间的齿轮好像突然加快,仿佛总是处在一个“时间不够”的状态。要学习新的东西、要照顾家庭、各种朋友应酬…忙来忙去,最后都忘记了自己为了什么在忙碌。

龙应台有本书《孩子你慢慢来》,我曾经在出差途中看完。有段话,这么写的,对比得很有意境:“淡水的街头,阳光斜照着窄巷里这间零乱的花铺。 回教徒和犹太人在彼此屠杀,衣索匹亚的老弱妇孺在一个接一个地饿死,纽约华尔街的证券市场挤满了表情紧张的人——我,坐在斜阳浅照的石阶上,愿意等上一辈子的时间,让这个孩子从从容容地把那个蝴蝶结扎好,用他五岁的手指。”

我很喜欢这段话。生活从来就不美好,世界残酷而真实,美好只存在于我们的内心,那些你所珍惜的事物,导引了我们生活的方向。

大学的第一年,元旦第一天,我一个人坐在三工教学楼的某个教室里面,努力准备马上要考试的科目。第一场雪就在那个时候下起来了,我看着窗外,就这么呆呆的看着雪坐了一个上午。

那时的自己,仿佛青春永远都会在,未来的日子总感觉非常遥远。除了不时会冒出来的考试,踢球、泡图书馆、看漫画,无聊时可以花两个礼拜画一张图,从容天真。

离开学校后,画风变了。如果每个人的生活都是一场电影,那么,在离开学校的这十几年,每一帧都充满了急躁,对未来的恐惧。 继续阅读“随笔-放慢心态1”

696 total views, no views today

Foxdisk04-启动原理1

我不记得是哪个小品了,赵本山用他的标准铁岭普通话说:“我不想知道它怎么来的,我只想知道它是怎么没的。”

对编程而言,“怎么来的”实际上非常重要,不能不了解。Foxdisk的启动过程,在“如何安装1”中已经有了说明,本篇会尽量的把相关的背景知识介绍清楚。

有几个关键词:BBS、Legacy BIOS、UEFI、MBR。

BBS

英特尔、Phoenix和康柏公司在1996年联合发布了BIOS引导规约(BIOS Boot Specification),简称BBS(图2)。尽管十几年已经过去了,但是这个规约中的大多数内容至今仍被使用着。本文中使用的很多术语和数据结构都来自这个规约。

Foxdisk04-BBS spec

 图1 BIOS Boot Spec

BBS将引导设备划分为以下三种类型: 继续阅读“Foxdisk04-启动原理1”

461 total views, no views today

Foxdisk03-如何安装2

整个Foxdisk3.01的代码,是将安装程序和主程序混在一起编译的。安装程序负责将代码拷贝到指定的硬盘区域,包括MBR代码、Foxdisk的启动代码、Foxdisk的资源、Foxdisk的code段和数据段。在“如何安装1”中叙述的foxdisk在硬盘中的映像,都是由它来实现的。

混编的好处是,安装程序能够很容易的定位主程序各种段(code、data、stack等等)的位置,方便安装。另外,很多函数可以共用。坏处是,安装程序也要占据不少的空间。我之前为了方便C和汇编混合,设定了编译模式为small,也即Code段必须在64K内,Stack和Data段加起来在64K之内。我在设计初期没有考虑好,代码写到后半段就感到很痛苦—许多想实现的功能因为代码的限制,没法去实现。

现在的编译器早就取消了这样的限制,以技术而言,BorlandC++3.1已经是石器时代的产品。以我现在常用的Vs2015,从来不用去考虑类似这样的问题,可以专注在程序本身的实现。计算机软件的发展非常快,一不小心我也变成了老古董了。

回到正题。与安装相关的文件有Setup.c、Setup.h、iSetup.c和iSetup.h,与主程序共用了磁盘访问、图像显示等函数。外设的访问,在后面专门写几个章节进行介绍,用汇编去访问外设,我估计现在除了开发驱动或者OS的程序员,很少会有人去关注它们了。

从Setup.c的main()函数开始跟踪,了解安装过程。

main函数做了两件事情: 卸载和软件安装。卸载的过程故意设计得复杂,通过判断命令行以及热键,确定用户需要完全卸载,然后再将相应的扇区覆盖。这几天我重读代码,觉得这里的设计很有问题,有可能导致卸载后无法进入操作系统了。

软件安装通过两个函数实现,firstSetup()和updateSetup()。前一个函数是首次安装,后一个是针对已经安装的软件进行参数更改或者代码升级。main()最后的汇编代码:

asm mov ax,3

asm int 0x10

其作用为回到文本模式。程序是在DOS环境下,并使用图形模式安装的,如果没有回到文本模式,DOS的命令行无法执行。

通篇代码中,有大量嵌入汇编的地方。这种写法不是很好,程序可读性比较差,我主要是习惯了汇编来实现一些小功能,不自觉的就用上了。后续出现内嵌汇编的地方,都会给出解释。

firSetup()中,将安装主程序的步骤分为了四个,代码中给出了很详细的说明,很容易读懂。此函数中所调用的图形函数和磁盘访问函数,其功能都比较单一,通过函数说明可以了解其作用。

我尽量不去调用C的库函数,以防止代码量的增加。因此,很多经典的函数,比如memset、memcpy等,只能重新自己实现。另外一个原因,Foxdisk的主程序中,是没有操作系统的,那些库函数也没法运行。我参考的代码来自于linux2.6,不愧是千锤百炼的代码,非常精简高效。

firSetup()中频繁使用的汇编代码:

asm xor ax,ax

asm int 0x16

这段代码的功能是等待按键,类似于C语言中的getchar()或者pause()。

updateSetup()的实现代码在iSetup.c中,其实现方式类似于firSetup(),就不一一解释了。

这两个函数都打开了时钟中断,实现一些需要定时的功能。篇幅所限,不再详细说明,在后续章节中针对时钟中断做一个详细说明。

426 total views, no views today

Foxdisk02-如何安装1

Foxdisk是基于BIOS中断或者直接访问硬件的,在设计的时候,第一个需要考虑的问题就是软件如何运行。这个问题很有趣,也是我在早期开发一个小的OS时试图搞清楚的最初的问题。

作为介于BIOS和操作系统间的一个小程序,我考虑了两种让Foxdisk运行的方法。其一是依靠硬件,将Foxdisk的引导代码放在PCI ROM或者以Option ROM的形式直接嵌入到BIOS中;另外一种是类似GRUB的方式,修改硬盘的MBR,实现Foxdisk的引导。我们首先从PC的启动过程谈起。

1)     PC启动的过程

Foxdisk02-PC_boot

图1 计算机启动过程

这是我理解的计算机的启动过程,显示了计算机从开机到进入操作系统的工作顺序。在上述的几个阶段,都可以抢得控制权,实现我们自己的代码。分别为:

阶段①:Call Rom,此阶段Option ROM可以抢得计算机的控制权。

阶段②:int19h,Option ROM软件可通过修改int 19h抢得控制权。另外,所有通过模拟可引导设备的Option ROM以使得BIOS能够引导其软件的方法均归于此阶段。

阶段③:LoadMBR,通过直接修改硬盘上的MBR区域抢得控制权。

阶段④:Load OS,在操作系统引导的时候抢得控制权。

阶段⑤:在操作系统层面安装软件。

阶段①~④均称为底层阶段,软件一般直接与计算机硬件打交道,或者通过BIOS中断访问硬件。阶段⑤称为上层阶段,软件借助于操作系统提供的各种API工作。

关于Option ROM的介绍,可以参考文档《BIOS Boot Specification》version 1.01。这是发布于1996年的文档,由intel、Phoenix和Compaq联合制定的规范。多年来,BIOS的启动过程也没有太大的变化,直到UEFI的出现。

具体的内容我不在博客中介绍,文档中介绍得很清楚。只要记住两点即可:第一,BIOS允许外设有自己的代码,用来实现一些特殊的功能,比如网卡的Option ROM、PCI设备的Option ROM;第二,只要依据一定的规范来写代码,BIOS会将控制权转移给Option ROM,这时整个计算机的控制权都在手中,理论上做什么都可以。

继续阅读“Foxdisk02-如何安装1”

561 total views, no views today

Foxdisk01-缘起

Foxdisk01-bootpic

“没有任何一个题目是彻底完成了的。总还会有些是哪个可做;在经过充分的研究和洞察以后,我们可以将任何解题方法加以改进;而且无论如何,我们总可以深化我们对答案的理解。”

–《怎样解题:数学思维的新方法》

用一段话来总结我自己的这个小项目,我以为上面的这段话很精准。从2006年1.00版开始,到2008年的冬天完成3.01版,直到现在,我也不觉得这个项目完成了。只是因为各种原因,没有动力再去更新它了,曾经写在计划中的4.0版,列出了许多我很有兴趣的功能,不大可能再去实现了。

继续阅读“Foxdisk01-缘起”

702 total views, no views today

Foxdisk00-源代码编译

为了便于之后博文的行文方便,我把代码放在了下载区:[download id=”9″]。

编译器使用的是Borland C++3.1,C编译器和汇编编译器都在其中。代码中包含三个汇编文件:Loaser.asm、iMath.asm、iTimeInt.asm,必须用Tasm编译。微软的masm语法要求稍微有些不同,因此foxdisk3.01的代码没有办法直接用微软早期的DOS下编译器编译。

编译步骤很简单:

1)      安装BorlandC++3.1;

2)      进入代码文件主目录,设置Path目录;(参考我的bcccmd.bat)

3)      etrhz.exe hztable.h Global.c Setup.c iSetup.c;

4)      ehz24.exe /k hzk24k.h _HZ24STR.c;

5)      运行make;

上述步骤执行后,会自动生成foxdisk.exe。在dos下安装的时候,需要调用资源文件ifox.bmp,包含在\Rleease文件夹中。

继续阅读“Foxdisk00-源代码编译”

144 total views, 2 views today

为自己定一个小目标

我的童年有很长的时间是在外婆家度过的,那个时候,外婆的爸爸妈妈还健在—我们的方言里称呼为“老外公、老外婆”。老外婆家离外婆家大概只有3公里左右,路两旁全是一拢拢农田和纵横交错的沟渠。进村子前,有几片不大的树林点缀在村子入口,松树、茶树、香樟树,还有一些我叫不出名字的树。春夏之交的时候,栀子花开了,空气中就飘着淡淡的栀子花甜香。

老外公和老外婆已经去世多年,我已经很难记得他们的样子;去拜见他们的场景记忆也慢慢开始模糊,唯有那飘在道路旁边的栀子花香味,始终不散。把这些回忆记录下来,是我很喜欢的事情。从小养成了的这种记录习惯,我的电脑里面有各种心情记录、管理记录,以及技术开发记录。这是比较私密的个人信息,作为技术宅男,我不是很喜欢把它们发布到QQ空间以外的地方。

改变这种想法从一位很好的朋友开始。

15年7月,参加了一次集体翻译的活动,第一次见到张佩。实际上对他慕名已久,公司产品开发中有时候需要驱动开发的知识,他的大作《竹林蹊径》是案边常用参考书。

张佩说话声音洪亮、语速很快、思考很快、写代码也很快,学习新技术奇快无比,我非常佩服。我是性格比较内向的人,不知道为什么,和他却一见如故,很快就像多年的好友一样无话不谈了。

那时我准备将我的foxdisk移植到uefi上,实现自己想要的一些功能。张佩很有兴趣,我就把自己的foxdisk 3.0的代码和文档给了他,请他指正。之后我有几次上海出差,每次都会抽一些时间去见他,他建议我把平常的一些技术学习的历程用博客写下来。 继续阅读“为自己定一个小目标”

152 total views, 1 views today