汉字点阵字库原理
一、 汉字编码
1. 区位码
在国标GD2312—80中规定,所有的国标汉字及符号分配在一个94行、94列的方阵中,方阵的每一行称为一个“区”,编号为01区到94区,每一列称为一个“位”,编号为01位到94位,方阵中的每一个汉字和符号所在的区号和位号组合在一起形成的四个阿拉伯数字就是它们的“区位码”。区位码的前两位是它的区号,后两位是它的位号。用区位码就可以唯一地确定一个汉字或符号,反过来说,任何一个汉字或符号也都对应着一个唯一的区位码。汉字“母”字的区位码是3624,表明它在方阵的36区24位,问号“?”的区位码为0331,则它在03区3l位。
2. 机内码
汉字的机内码是指在计算机中表示一个汉字的编码。机内码与区位码稍有区别。如上所述,汉字区位码的区码和位码的取值均在1~94之间,如直接用区位码作为机内码,就会与基本ASCII码混淆。为了避免机内码与基本ASCII码的冲突,需要避开基本ASCII码中的控制码(00H~1FH),还需与基本ASCII码中的字符相区别。为了实现这两点,可以先在区码和位码分别加上20H,在此基础上再加80H(此处“H”表示前两位数字为十六进制数)。经过这些处理,用机内码表示一个汉字需要占两个字节,分别 称为高位字节和低位字节,这两位字节的机内码按如下规则表示:
高位字节 = 区码 + 20H + 80H(或区码 + A0H)
低位字节 = 位码 + 20H + 80H(或位码 + AOH)
由于汉字的区码与位码的取值范围的十六进制数均为01H~5EH(即十进制的01~94),所以汉字的高位字节与低位字节的取值范围则为A1H~FEH(即十进制的161~254)。
例如,汉字“啊”的区位码为1601,区码和位码分别用十六进制表示即为1001H,它的机内码的高位字节为B0H,低位字节为A1H,机内码就是B0A1H。
二、 点阵字库结构
1. 点阵字库存储
在汉字的点阵字库中,每个字节的每个位都代表一个汉字的一个点,每个汉字都是由一个矩形的点阵组成,0代表没有,1代表有点,将0和1分别用不同颜色画出,就形成了一个汉字,常用的点阵矩阵有12*12, 14*14, 16*16三种字库。
字库根据字节所表示点的不同有分为横向矩阵和纵向矩阵,目前多数的字库都是横向矩阵的存储方式(用得最多的应该是早期UCDOS字库),纵向矩阵一般是因为有某些液晶是采用纵向扫描显示法,为了提高显示速度,于是便把字库矩阵做成纵向,省得在显示时还要做矩阵转换。我们接下去所描述的都是指横向矩阵字库。
2. 16*16点阵字库
对于16*16的矩阵来说,它所需要的位数共是16*16=256个位,每个字节为8位,因此,每个汉字都需要用256/8=32个字节来表示。
即每两个字节代表一行的16个点,共需要16行,显示汉字时,只需一次性读取32个字节,并将每两个字节为一行打印出来,即可形成一个汉字。
3. 14*14与12*12点阵字库
对于14*14和12*12的字库,理论上计算,它们所需要的点阵分别为(14*14/8)=25, (12*12/8)=18个字节,但是,如果按这种方式来存储,那么取点阵和显示时,由于它们每一行都不是8的整位数,因此,就会涉到点阵的计算处理问题,会增加程序的复杂度,降低程序的效率。
为了解决这个问题,有些点阵字库会将14*14和12*12的字库按16*14和16*12来存储,即,每行还是按两个字节来存储,但是14*14的字库,每两个字节的最后两位是没有使用,12*12的字节,每两字节的最后4位是没有使用,这个根据不同的字库会有不同的处理方式,所以在使用字库时要注意这个问题,特别是14*14的字库。
三、 汉字点阵获取
1. 利用区位码获取汉字
汉字点阵字库是根据区位码的顺序进行存储的,因此,我们可以根据区位来获取一个字库的点阵,它的计算公式如下:
点阵起始位置 = ((区码- 1)*94 + (位码 – 1)) * 汉字点阵字节数
获取点阵起始位置后,我们就可以从这个位置开始,读取出一个汉字的点阵。
2. 利用汉字机内码获取汉字
前面我们己经讲过,汉字的区位码和机内码的关系如下:
机内码高位字节 = 区码 + 20H + 80H(或区码 + A0H)
机内码低位字节 = 位码 + 20H + 80H(或位码 + AOH)
反过来说,我们也可以根据机内码来获得区位码:
区码 = 机内码高位字节 - A0H
位码 = 机内码低位字节 - AOH
将这个公式与获取汉字点阵的公式进行合并计就可以得到汉字的点阵位置。
2009年10月26日星期一
汉字点阵字库原理
2009年10月25日星期日
gbk、gb2312、big5、unicode、utf-8 - Reedey的专栏 - CSDN博客
gbk、gb2312、big5、unicode、utf-8 收藏
标题 谈谈Unicode编码,简要解释UCS、UTF、BMP、BOM等名词 选择自 fmddlmyy 的 Blog
关键字 谈谈Unicode编码,简要解释UCS、UTF、BMP、BOM等名词
这是一篇程序员写给程序员的趣味读物。所谓趣味是指可以比较轻松地了解一些原来不清楚的概念,增进知识,类似于打RPG游戏的升级。整理这篇文章的动机是两个问题:
问题一:
使用Windows记事本的“另存为”,可以在GBK、Unicode、Unicode big endian和UTF-8这几种编码方式间相互转换。同样是txt文件,Windows是怎样识别编码方式的呢?
我很早前就发现Unicode、Unicode big endian和UTF-8编码的txt文件的开头会多出几个字节,分别是FF、FE(Unicode),FE、FF(Unicode big endian),EF、BB、BF(UTF-8)。但这些标记是基于什么标准呢?
问题二:
最近在网上看到一个ConvertUTF.c,实现了UTF-32、UTF-16和UTF-8这三种编码方式的相互转换。对于Unicode(UCS2)、GBK、UTF-8这些编码方式,我原来就了解。但这个程序让我有些糊涂,想不起来UTF-16和UCS2有什么关系。
查了查相关资料,总算将这些问题弄清楚了,顺带也了解了一些Unicode的细节。写成一篇文章,送给有过类似疑问的朋友。本文在写作时尽量做到通俗易懂,但要求读者知道什么是字节,什么是十六进制。
0、big endian和little endian
big endian和little endian是CPU处理多字节数的不同方式。例如“汉”字的Unicode编码是6C49。那么写到文件里时,究竟是将6C写在前面,还是将49写在前面?如果将6C写在前面,就是big endian。还是将49写在前面,就是little endian。
“endian”这个词出自《格列佛游记》。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开,由此曾发生过六次叛乱,其中一个皇帝送了命,另一个丢了王位。
我们一般将endian翻译成“字节序”,将big endian和little endian称作“大尾”和“小尾”。
1、字符编码、内码,顺带介绍汉字编码
字符必须编码后才能被计算机处理。计算机使用的缺省编码方式就是计算机的内码。早期的计算机使用7位的ASCII编码,为了处理汉字,程序员设计了用于简体中文的GB2312和用于繁体中文的big5。
GB2312(1980年)一共收录了7445个字符,包括6763个汉字和682个其它符号。汉字区的内码范围高字节从B0-F7,低字节从A1-FE,占用的码位是72*94=6768。其中有5个空位是D7FA-D7FE。
GB2312支持的汉字太少。1995年的汉字扩展规范GBK1.0收录了21886个符号,它分为汉字区和图形符号区。汉字区包括21003个字符。2000年的GB18030是取代GBK1.0的正式国家标准。该标准收录了27484个汉字,同时还收录了藏文、蒙文、维吾尔文等主要的少数民族文字。现在的PC平台必须支持GB18030,对嵌入式产品暂不作要求。所以手机、MP3一般只支持GB2312。
从ASCII、GB2312、GBK到GB18030,这些编码方法是向下兼容的,即同一个字符在这些方案中总是有相同的编码,后面的标准支持更多的字符。在这些编码中,英文和中文可以统一地处理。区分中文编码的方法是高字节的最高位不为0。按照程序员的称呼,GB2312、GBK到GB18030都属于双字节字符集 (DBCS)。
有的中文Windows的缺省内码还是GBK,可以通过GB18030升级包升级到GB18030。不过GB18030相对GBK增加的字符,普通人是很难用到的,通常我们还是用GBK指代中文Windows内码。
这里还有一些细节:
GB2312的原文还是区位码,从区位码到内码,需要在高字节和低字节上分别加上A0。
在DBCS中,GB内码的存储格式始终是big endian,即高位在前。
GB2312的两个字节的最高位都是1。但符合这个条件的码位只有128*128=16384个。所以GBK和GB18030的低字节最高位都可能不是1。不过这不影响DBCS字符流的解析:在读取DBCS字符流时,只要遇到高位为1的字节,就可以将下两个字节作为一个双字节编码,而不用管低字节的高位是什么。
2、Unicode、UCS和UTF
前面提到从ASCII、GB2312、GBK到GB18030的编码方法是向下兼容的。而Unicode只与ASCII兼容(更准确地说,是与ISO-8859-1兼容),与GB码不兼容。例如“汉”字的Unicode编码是6C49,而GB码是BABA。
Unicode也是一种字符编码方法,不过它是由国际组织设计,可以容纳全世界所有语言文字的编码方案。Unicode的学名是"Universal Multiple-Octet Coded Character Set",简称为UCS。UCS可以看作是"Unicode Character Set"的缩写。
根据维基百科全书(http://zh.wikipedia.org/wiki/)的记载:历史上存在两个试图独立设计Unicode的组织,即国际标准化组织(ISO)和一个软件制造商的协会(unicode.org)。ISO开发了ISO 10646项目,Unicode协会开发了Unicode项目。
在1991年前后,双方都认识到世界不需要两个不兼容的字符集。于是它们开始合并双方的工作成果,并为创立一个单一编码表而协同工作。从Unicode2.0开始,Unicode项目采用了与ISO 10646-1相同的字库和字码。
目前两个项目仍都存在,并独立地公布各自的标准。Unicode协会现在的最新版本是2005年的Unicode 4.1.0。ISO的最新标准是10646-3:2003。
UCS规定了怎么用多个字节表示各种文字。怎样传输这些编码,是由UTF(UCS Transformation Format)规范规定的,常见的UTF规范包括UTF-8、UTF-7、UTF-16。
IETF的RFC2781和RFC3629以RFC的一贯风格,清晰、明快又不失严谨地描述了UTF-16和UTF-8的编码方法。我总是记不得IETF是Internet Engineering Task Force的缩写。但IETF负责维护的RFC是Internet上一切规范的基础。
3、UCS-2、UCS-4、BMP
UCS有两种格式:UCS-2和UCS-4。顾名思义,UCS-2就是用两个字节编码,UCS-4就是用4个字节(实际上只用了31位,最高位必须为0)编码。下面让我们做一些简单的数学游戏:
UCS-2有2^16=65536个码位,UCS-4有2^31=2147483648个码位。
UCS-4根据最高位为0的最高字节分成2^7=128个group。每个group再根据次高字节分为256个plane。每个plane根据第3个字节分为256行 (rows),每行包含256个cells。当然同一行的cells只是最后一个字节不同,其余都相同。
group 0的plane 0被称作Basic Multilingual Plane, 即BMP。或者说UCS-4中,高两个字节为0的码位被称作BMP。
将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。在UCS-2的两个字节前加上两个零字节,就得到了UCS-4的BMP。而目前的UCS-4规范中还没有任何字符被分配在BMP之外。
4、UTF编码
UTF-8就是以8位为单元对UCS进行编码。从UCS-2到UTF-8的编码方式如下:
UCS-2编码(16进制) UTF-8 字节流(二进制)
0000 - 007F 0xxxxxxx
0080 - 07FF 110xxxxx 10xxxxxx
0800 - FFFF 1110xxxx 10xxxxxx 10xxxxxx
例如“汉”字的Unicode编码是6C49。6C49在0800-FFFF之间,所以肯定要用3字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将6C49写成二进制是:0110 110001 001001,用这个比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。
读者可以用记事本测试一下我们的编码是否正确。
UTF-16以16位为单元对UCS进行编码。对于小于0x10000的UCS码,UTF-16编码就等于UCS码对应的16位无符号整数。对于不小于0x10000的UCS码,定义了一个算法。不过由于实际使用的UCS2,或者UCS4的BMP必然小于0x10000,所以就目前而言,可以认为UTF-16和UCS-2基本相同。但UCS-2只是一个编码方案,UTF-16却要用于实际的传输,所以就不得不考虑字节序的问题。
5、UTF的字节序和BOM
UTF-8以字节为编码单元,没有字节序的问题。UTF-16以两个字节为编码单元,在解释一个UTF-16文本前,首先要弄清楚每个编码单元的字节序。例如收到一个“奎”的Unicode编码是594E,“乙”的Unicode编码是4E59。如果我们收到UTF-16字节流“594E”,那么这是“奎”还是“乙”?
Unicode规范中推荐的标记字节顺序的方法是BOM。BOM不是“Bill Of Material”的BOM表,而是Byte Order Mark。BOM是一个有点小聪明的想法:
在UCS编码中有一个叫做"ZERO WIDTH NO-BREAK SPACE"的字符,它的编码是FEFF。而FFFE在UCS中是不存在的字符,所以不应该出现在实际传输中。UCS规范建议我们在传输字节流前,先传输字符"ZERO WIDTH NO-BREAK SPACE"。
这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little-Endian的。因此字符"ZERO WIDTH NO-BREAK SPACE"又被称作BOM。
UTF-8不需要BOM来表明字节顺序,但可以用BOM来表明编码方式。字符"ZERO WIDTH NO-BREAK SPACE"的UTF-8编码是EF BB BF(读者可以用我们前面介绍的编码方法验证一下)。所以如果接收者收到以EF BB BF开头的字节流,就知道这是UTF-8编码了。
Windows就是使用BOM来标记文本文件的编码方式的。
6、进一步的参考资料
本文主要参考的资料是 "Short overview of ISO-IEC 10646 and Unicode" (http://www.nada.kth.se/i18n/ucs/unicode-iso10646-oview.html)。
我还找了两篇看上去不错的资料,不过因为我开始的疑问都找到了答案,所以就没有看:
"Understanding Unicode A general introduction to the Unicode Standard" (http://scripts.sil.org/cms/scripts/page.php?site_id=nrsiitem_id=IWS-Chapter04a)
"Character set encoding basics Understanding character set encodings and legacy encodings" (http://scripts.sil.org/cms/scripts/page.php?site_id=nrsiitem_id=IWS-Chapter03)
我写过UTF-8、UCS-2、GBK相互转换的软件包,包括使用Windows API和不使用Windows API的版本。以后有时间的话,我会整理一下放到我的个人主页上(http://fmddlmyy.home4u.china.com/)。
我是想清楚所有问题后才开始写这篇文章的,原以为一会儿就能写好。没想到考虑措辞和查证细节花费了很长时间,竟然从下午1:30写到9:00。希望有读者能从中受益。
***********************************************************
[zz]GB2312/GBK/GB18030/BIG5 的历史
关键字: 字符集
http://iask.sina.com.cn/b/7837434.html?from=related
GBK中的“K”是扩展的意思,而GB2312中的“2312”以及GB18030中的“18030”是国家标准的代号,BIG5是港澳台地区的编码。
下面详细介绍一下字库情况,你就可看出其区别:
(一)GB2312-80字库
从1975年开始,我国为了研究汉字的使用频度,进行了大规模的字频统计工作,内容包括工业、农业、军事、科技、政治、经济、文学、艺术、教育、体育、医药卫生、天文地理、自然、化学、文字改革、考古等多方面的出版物,在数以亿计的浩翰文献资料中,统计出实际使用的不同的汉字数为6335个,而其中有 3000多个汉字的累计使用频度达到了99.9%,而另外的3000多个累计频度不到0.1%,说明了常用汉字与次常用汉字的数量不足7000个,这就为国家制定汉字库标准提供了依据。1980年颁布了《信息交换用汉字编码字符集--基本集》的国标交换码,国家标准号为:GB2312-80,选入了 6763个汉字,分为两级,一级字库中有3755个,是常用汉字,二级字库中有3008个,是次常用汉字;还选入了682个字符,包含有数字、一般符号、拉丁字母、日本假名、希腊字母、俄文字母、拼音符号、注音字母等。
(二)大字符集字库(又叫GBK字库)
国际标准化组织为了将世界各民族的文字进行统一编码,制定了UCS标准。根据这一标准,中、日、韩三国共同制定了《CJK统一汉字编码字符集》,其国际标准号为:ISO/IEC10646,国家标准号为:GB13000-90,该汉字编码字符集就是通常人们所说的大字符集,它编入了20902个汉字,收集了大陆一二级字库中的简体字,台湾《通用汉字标准交换码》中的繁体字,58个香港特别用字和92个延边地区朝鲜族“吏读”字,甚至涵盖了日文与韩文中的通用汉字,满足了方方面面的需要。Windows95/98/NT/2000中都装入了大字符集汉字库,人们一般称它为GBK字库。有了GBK字库,还要有对应的汉字输入法,才能输入其中的全部汉字,如果某种汉字输入法仅编入了一二级字库,仍然只能输入6763个汉字。
(三)台湾 BIG5 字库
港澳台地区普遍使用台湾的《通用汉字标准交换码》,地区标准号为:CNS11643,选入了13 000多个繁体汉字,这就是人们讲的BIG5码,或叫大五码。钱码的“海外繁体版”编入了BIG5字库,能输入13 000多个繁体汉字。
四)新标准汉字库 (GB18030-2000)
2000年3月,国家信息产业部和质量技术监督局在北京联合发布了两项新标准,一项叫做《信息技术和信息交换用汉字编码字符集、基本集的扩充》,国家标准号为:GB18030-2000,收录了27533个汉字,还收录了藏、蒙、维等主要少数民族的文字,以期一举解决邮政、户政、金融、地理信息系统等生僻汉字与主要少数民族语言的输入,该标准于2000年12月31日强制执行;另一项是《信息技术和数字键盘汉字输入通用要求》,国家标准号是: GB/T18031-2000,为数字键盘输入提供了统一的标准。 新标准汉字库已经公布,迫切需要与之相应的输入方法。
(五)方正超大字符集
方正超大字符集字体包括了上面提到的全部汉字以及在第二平面中(42,711)选出的36,862个在中国大陆,香港特别行政区(以及部分台湾地区)使用的汉字。因此包括西文等常用字符在内,宋体-方正超大字符集共包括65,531个字符。这是目前包含字数最全的字库。要安装该字库需在安装WinXP时采用自定义安装,选择安装宋体-方正超大字符集,但是一般的输入法是无法打出这么多的字的,但可以用“插入”—“符号”的方法选择插入。
2009年3月30日星期一
用busybox1.13.1构建基于MIPS的嵌入式根文件系统
用busybox1.13.1构建基于MIPS的嵌入式根文件系统
一、主机环境
笔者所用环境为mipsel-unknown-linux-gnu-gcc 3.4.3 GLIBC版本为2.2.5,建议使用高版本进行测试,不过笔者没有测试过,是否会存在问题。
gcc version 3.4.1 (fedora core 5)
所需源文件: busybox-1.13.1.tar.bz2; 网上可轻易下载
二、用Busybox创建nfs文件系统
1、解压busybox-1.13.1.tar.bz2
[root@vm-dev rootfs]# ls
busybox-1.13.1.tar.bz2
[root@vm-dev rootfs]# tar -vxjf busybox-1.13.1.tar.bz2
[root@vm-dev rootfs]# cd busybox-1.13.1
[root@vm-dev busybox-1.13.1]# pwd
/work/busybox-1.13.1
[root@vm-dev busybox-1.13.1]# vi Makefile
[root@vm-dev busybox-1.13.1]#
修改Makefile中的ARCH和CROSS_COMPILE与本机的路径一致,注意,如果在/root/.bash_profile中指定了路径,则不需要再给出路径。
CROSS_COMPILE ?= mipsel-unknown-linux-gnu-
...
ARCH ?= mips
2、编译busybox。
先make menuconfig,修改以下:
[root@vm-dev busybox-1.13.1]# make menuconfig
Busybox Settings --->
Build Options --->
[*] Build BusyBox as a static binary (no shared libs)
//直接编译成静态库,省事点
(mipsel-unknown-linux-gnu-) Cross Compiler prefix
//这里和Makefile里保持一致,应该写一处就行了
Installation Options --->
[ ] Don't use /usr
//使用usr目录
Busybox Library Tuning --->
[*] Fancy shell prompts
//一定要选上,否则很多转意字符无法识别
Shells --->
Choose your default shell (ash) --->
//这里选择shell为ash,应该是默认选中的
--- ash
//把ash这档的选项全部选上
Miscellaneous Utilities --->
[ ] inotifyd
//不选
保存退出,直接make,make install。
可以看到如下目录
[root@vm-dev busybox-1.13.1]# ls _install/
bin linuxrc sbin usr
[root@vm-dev busybox-1.13.1]#
3、用shell脚本创建根文件系统的目录结构
用shell脚本创建根文件系统的目录结构,并在想要建立根文件系统的地方运行此脚本。我是用root用户登陆的,直接创建了设备节点。
[root@vm-dev root_stand]# cat build_fs.sh
#!/bin/sh
echo "makeing rootdir"
mkdir rootfs
cd rootfs
echo "makeing dir: bin dev etc lib proc sbin sys usr"
mkdir bin dev etc lib proc sbin sys usr #8 dirs
mkdir usr/bin usr/lib usr/sbin lib/modules
#Don't use mknod, unless you run this Script as
mknod -m 600 dev/console c 5 1
mknod -m 666 dev/null c 1 3
echo "making dir: mnt tmp var"
mkdir mnt tmp var
chmod 1777 tmp
mkdir mnt/etc mnt/jiffs2 mnt/yaffs mnt/data mnt/temp
mkdir var/lib var/lock var/log var/run var/tmp
chmod 1777 var/tmp
echo "making dir: home root boot"
mkdir home root boot
echo "done"
[root@vm-dev root_stand]#
执行这个sh:
[root@vm-dev root_stand]# sh build_fs.sh
makeing rootdir
makeing dir: bin dev etc lib proc sbin sys usr
making dir: mnt tmp var
making dir: home root boot
done
创建出一个主文件夹rootfs,里面有一批文件:
[root@vm-dev root_stand]# cd rootfs/
[root@vm-dev rootfs]# ls
bin boot dev etc home lib mnt proc root sbin sys tmp usr var
[root@vm-dev rootfs]#
4、把busybox源码目录下的etc的内容拷贝到这里的etc下
[root@vm-dev rootfs]# cd etc/
[root@vm-dev etc]# ls
[root@vm-dev etc]# cp -a /work/busybox-1.13.1/examples/bootfloppy/etc/* ./
[root@vm-dev etc]# ls
fstab init.d inittab profile
[root@vm-dev etc]#
5、修改拷贝过来的profile文件
[root@vm-dev etc]# vi profile
# /etc/profile: system-wide .profile file for the Bourne shells
echo "Processing /etc/profile"
# no-op
# Set search library path
echo " Set search library path"
export LD_LIBRARY_PATH=/lib:/usr/lib
# Set user path
echo " Set user path"
PATH=/bin:/sbin:/usr/bin:/usr/sbin
export PATH
# Set PS1
echo " Set PS1"
HOSTNAME=`/bin/hostname`
# 此处让shell提示符显示host名称的。是`,不是’,要注意
# 会在进入根系统后显示Jacky
export PS1="\\e[32m[$USER@$HOSTNAME \\w\\a]\\$\\e[00;37m "
# 此处\\e[32m是让后面的“[$USER@$HOSTNAME \\w\\a]”显示为绿色
# \\e[00是关闭效果
# \\e[05是闪烁
# 37m是让后面的显示为白色
# 多个命令可以;号隔开
echo "All done!"
echo
6、修改初始化文件inittab和fstab
Inittab
[root@vm-dev etc]# vi inittab
::sysinit:/etc/init.d/rcS
::respawn:-/bin/sh
::restart:/sbin/init
tty2::askfirst:-/bin/sh
::ctrlaltdel:/bin/umount -a -r
::shutdown:/bin/umount -a -r
::shutdown:/sbin/swapoff –a
Fstab
[root@vm-dev etc]# vim fstab
proc /proc proc defaults 0 0
none /tmp ramfs defaults 0 0
mdev /dev ramfs defaults 0 0
sysfs /sys sysfs defaults 0 0
7、修改初始化的脚本文件init.d/rcS
[root@vm-dev etc]# vi init.d/rcS
#! /bin/sh
echo "Processing etc/init.d/rc.S"
#hostname ${HOSTNAME}
hostname up-tech
echo " Mount all"
/bin/mount -a
echo " Start mdev...."
/bin/echo /sbin/mdev > proc/sys/kernel/hotplug
mdev -s
echo "****************************************************"
echo " rootfs by NFS, s3c2410"
echo " Created by lyj_uptech @ 2008.11.28"
echo " Good Luck"
echo " www.up-tech.com"
echo "****************************************************"
echo
8、创建一个空的mdev.conf文件,在挂载根文件系统时用到
[root@vm-dev etc]# touch mdev.conf
9、从本机拷贝passwd、shadow、group文件。
[root@vm-dev etc]# cp /etc/passwd .
[root@vm-dev etc]# cp /etc/shadow .
[root@vm-dev etc]# cp /etc/group .
修改passwd文件,把第一行和最后一行的bash修改成ash。
10、把busybox默认安装目录中的文件全部复制到rootfs中
会发现多了linuxrc -> bin/busybox文件,这是挂载文件系统需要执行的。
[root@vm-dev etc]# cd ..
[root@vm-dev etc]# cp -Rfv /work/busybox-1.13.1/_install/* ./
OK,以上用busybox创建了一个基本的文件系统。
PS:
如果编译busybox时选择动态库方式编译,则需要查看生成的busybox使用哪些动态库,然后把它们拷贝到rootfs/lib目录下。
[root@vm-dev lib]# arm-linux-readelf -d ../bin/busybox
参考文献:http://blog.chinaunix.net/u2/65122/showart.php?id=1671023
2009年3月26日星期四
双向链表
双向链表
维基百科,自由的百科全书
跳转到: 导航, 搜索
双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。
/* 线性表的双向链表存储结构 */
typedef struct DuLNode
{
ElemType data;
struct DuLNode *prior,*next;
}DuLNode,*DuLinkList;
/*带头结点的双向循环链表的基本操作(14个) */
void InitList(DuLinkList *L)
{ /* 产生空的双向循环链表L */
*L=(DuLinkList)malloc(sizeof(DuLNode));
if(*L)
(*L)->next=(*L)->prior=*L;
else
exit(OVERFLOW);
}
void DestroyList(DuLinkList *L)
{ /* 操作结果:销毁双向循环链表L */
DuLinkList q,p=(*L)->next; /* p指向第一个结点 */
while(p!=*L) /* p没到表头 */
{
q=p->next;
free(p);
p=q;
}
free(*L);
*L=NULL;
}
void ClearList(DuLinkList L) /* 不改变L */
{ /* 初始条件:L已存在。操作结果:将L重置为空表 */
DuLinkList q,p=L->next; /* p指向第一个结点 */
while(p!=L) /* p没到表头 */
{
q=p->next;
free(p);
p=q;
}
L->next=L->prior=L; /* 头结点的两个指针域均指向自身 */
}
Status ListEmpty(DuLinkList L)
{ /* 初始条件:线性表L已存在。操作结果:若L为空表,则返回TRUE,否则返回FALSE */
if(L->next==L&&L->prior==L)
return TRUE;
else
return FALSE;
}
int ListLength(DuLinkList L)
{ /* 初始条件:L已存在。操作结果:返回L中数据元素个数 */
int i=0;
DuLinkList p=L->next; /* p指向第一个结点 */
while(p!=L) /* p没到表头 */
{
i++;
p=p->next;
}
return i;
}
Status GetElem(DuLinkList L,int i,ElemType *e)
{ /* 当第i个元素存在时,其值赋给e并返回OK,否则返回ERROR */
int j=1; /* j为计数器 */
DuLinkList p=L->next; /* p指向第一个结点 */
while(p!=L&&jnext;
j++;
}
if(p==L||j>i) /* 第i个元素不存在 */
return ERROR;
*e=p->data; /* 取第i个元素 */
return OK;
}
int LocateElem(DuLinkList L,ElemType e,Status(*compare)(ElemType,ElemType))
{ /* 初始条件:L已存在,compare()是数据元素判定函数 */
/* 操作结果:返回L中第1个与e满足关系compare()的数据元素的位序。 */
/* 若这样的数据元素不存在,则返回值为0 */
int i=0;
DuLinkList p=L->next; /* p指向第1个元素 */
while(p!=L)
{
i++;
if(compare(p->data,e)) /* 找到这样的数据元素 */
return i;
p=p->next;
}
return 0;
}
Status PriorElem(DuLinkList L,ElemType cur_e,ElemType *pre_e)
{ /* 操作结果:若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前驱, */
/* 否则操作失败,pre_e无定义 */
DuLinkList p=L->next->next; /* p指向第2个元素 */
while(p!=L) /* p没到表头 */
{
if(p->data==cur_e)
{
*pre_e=p->prior->data;
return TRUE;
}
p=p->next;
}
return FALSE;
}
Status NextElem(DuLinkList L,ElemType cur_e,ElemType *next_e)
{ /* 操作结果:若cur_e是L的数据元素,且不是最后一个,则用next_e返回它的后继, */
/* 否则操作失败,next_e无定义 */
DuLinkList p=L->next->next; /* p指向第2个元素 */
while(p!=L) /* p没到表头 */
{
if(p->prior->data==cur_e)
{
*next_e=p->data;
return TRUE;
}
p=p->next;
}
return FALSE;
}
DuLinkList GetElemP(DuLinkList L,int i) /* 另加 */
{ /* 在双向链表L中返回第i个元素的地址。i为0,返回头结点的地址。若第i个元素不存在,*/
/* 返回NULL */
int j;
DuLinkList p=L; /* p指向头结点 */
if(i<0||i>ListLength(L)) /* i值不合法 */
return NULL;
for(j=1;j<=i;j++)
p=p->next;
return p;
}
Status ListInsert(DuLinkList L,int i,ElemType e)
{ /* 在带头结点的双链循环线性表L中第i个位置之前插入元素e,i的合法值为1≤i≤表长+1 */
/* 改进算法2.18,否则无法在第表长+1个结点之前插入元素 */
DuLinkList p,s;
if(i<1||i>ListLength(L)+1) /* i值不合法 */
return ERROR;
p=GetElemP(L,i-1); /* 在L中确定第i个元素前驱的位置指针p */
if(!p) /* p=NULL,即第i个元素的前驱不存在(设头结点为第1个元素的前驱) */
return ERROR;
s=(DuLinkList)malloc(sizeof(DuLNode));
if(!s)
return OVERFLOW;
s->data=e;
s->prior=p; /* 在第i-1个元素之后插入 */
s->next=p->next;
p->next->prior=s;
p->next=s;
return OK;
}
Status ListDelete(DuLinkList L,int i,ElemType *e)
{ /* 删除带头结点的双链循环线性表L的第i个元素,i的合法值为1≤i≤表长 */
DuLinkList p;
if(i<1) /* i值不合法 */
return ERROR;
p=GetElemP(L,i); /* 在L中确定第i个元素的位置指针p */
if(!p) /* p=NULL,即第i个元素不存在 */
return ERROR;
*e=p->data;
p->prior->next=p->next;
p->next->prior=p->prior;
free(p);
return OK;
}
void ListTraverse(DuLinkList L,void(*visit)(ElemType))
{ /* 由双链循环线性表L的头结点出发,正序对每个数据元素调用函数visit() */
DuLinkList p=L->next; /* p指向头结点 */
while(p!=L)
{
visit(p->data);
p=p->next;
}
printf("\n");
}
void ListTraverseBack(DuLinkList L,void(*visit)(ElemType))
{ /* 由双链循环线性表L的头结点出发,逆序对每个数据元素调用函数visit()。另加 */
DuLinkList p=L->prior; /* p指向尾结点 */
while(p!=L)
{
visit(p->data);
p=p->prior;
}
printf("\n");
}
链表的C语言实现之循环链表及双向链表
链表的C语言实现之循环链表及双向链表
2005-06-15 13:46作者:leton2008出处:BLOG责任编辑:方舟
一、循环链表
循环链表是与单链表一样,是一种链式的存储结构,所不同的是,循环链表的最后一个结点的指针是指向该循环链表的第一个结点或者表头结点,从而构成一个环形的链。
循环链表的运算与单链表的运算基本一致。所不同的有以下几点:
1、在建立一个循环链表时,必须使其最后一个结点的指针指向表头结点,而不是象单链表那样置为NULL。此种情况还使用于在最后一个结点后插入一个新的结点。
2、在判断是否到表尾时,是判断该结点链域的值是否是表头结点,当链域值等于表头指针时,说明已到表尾。而非象单链表那样判断链域值是否为NULL。
二、双向链表
双向链表其实是单链表的改进。
当我们对单链表进行操作时,有时你要对某个结点的直接前驱进行操作时,又必须从表头开始查找。这是由单链表结点的结构所限制的。因为单链表每个结点只有一个存储直接后继结点地址的链域,那么能不能定义一个既有存储直接后继结点地址的链域,又有存储直接前驱结点地址的链域的这样一个双链域结点结构呢?这就是双向链表。
在双向链表中,结点除含有数据域外,还有两个链域,一个存储直接后继结点地址,一般称之为右链域;一个存储直接前驱结点地址,一般称之为左链域。在c语言中双向链表结点类型可以定义为:
typedef struct node
{
int data; /*数据域*/
struct node *llink,*rlink; /*链域,*llink是左链域指针,*rlink是右链域指针*/
}JD;
当然,也可以把一个双向链表构建成一个双向循环链表。
双向链表与单向链表一样,也有三种基本运算:查找、插入和删除。
双向链表的基本运算:
1、查找
假若我们要在一个带表头的双向循环链表中查找数据域为一特定值的某个结点时,我们同样从表头结点往后依次比较各结点数据域的值,若正是该特定值,则返回指向结点的指针,否则继续往后查,直到表尾。
下例就是应用双向循环链表查找算法的一个程序。
#include <stdio.h>
#include <malloc.h>
#define N 10
typedef struct node
{
char name[20];
struct node *llink,*rlink;
}stud;
stud * creat(int n)
{
stud *p,*h,*s;
int i;
if((h=(stud *)malloc(sizeof(stud)))==NULL)
{
printf("不能分配内存空间!");
exit(0);
}
h->name[0]=’\0’;
h->llink=NULL;
h->rlink=NULL;
p=h;
for(i=0;i<n;i++)
{
if((s= (stud *) malloc(sizeof(stud)))==NULL)
{
printf("不能分配内存空间!");
exit(0);
}
p->rlink=s;
printf("请输入第%d个人的姓名",i+1);
scanf("%s",s->name);
s->llink=p;
s->rlink=NULL;
p=s;
}
h->llink=s;
p->rlink=h;
return(h);
}
stud * search(stud *h,char *x)
{
stud *p;
char *y;
p=h->rlink;
while(p!=h)
{
y=p->name;
if(strcmp(y,x)==0)
return(p);
else p=p->rlink;
}
printf("没有查找到该数据!");
}
void print(stud *h)
{
int n;
stud *p;
p=h->rlink;
printf("数据信息为:\n");
while(p!=h)
{
printf("%s ",&*(p->name));
p=p->rlink;
}
printf("\n");
}
main()
{
int number;
char studname[20];
stud *head,*searchpoint;
number=N;
clrscr();
head=creat(number);
print(head);
printf("请输入你要查找的人的姓名:");
scanf("%s",studname);
searchpoint=search(head,studname);
printf("你所要查找的人的姓名是:%s",*&searchpoint->name);
}
2009年3月6日星期五
两个变量交换的三种方法
两个变量交换的三种方法
在我们写程序的时候,经常会遇到两个变量A与B交换的操作,通常大家会借助第三个变量来实现:
如:C=A;A=B;B=C;
这种方法需要借助第三变量来实现;
第二种方法是利用加减法实现两个变量的交换,
如:A=A+B;B=A-B;A=A-B;
第三种方法是得用位异或运算来实现,也是效率最高的一种,在大量数据交换的时候,效率明显优于前两种方法,
如:A=A^B;B=A^B;A=A^B;
原理:利用一个数异或本身等于0和异或运算符合交换率。
希望对大家有帮助!
package test;
public class VarTest
{
public static void main(String[] args)
{
int a = 3;
int b = 2;
int c;
c = a;
a = b;
b = c;
System.out.println("a:" + a);
System.out.println("b:" + b);
System.out.println("**********");
a = a + b;
b = a - b;
a = a - b;
System.out.println("a:" + a);
System.out.println("b:" + b);
System.out.println("************");
a = a ^ b;
b = a ^ b;
a = a ^ b;
System.out.println("a:" + a);
System.out.println("b:" + b);
}
}
2009年3月3日星期二
QuickPwn2.2.5多国语言版教程(iPhone 2.2.1固件 破解)
QuickPwn2.2.5多国语言版教程(iPhone 2.2.1固件
破解)
QuickPwn2.2.5下载地址http://www.chinamac.com/download/44922.html
QuickPwn2.2.5(-2)支持2.2.1、2.2、2.1及2.0.x.固件的一代iPhone(越狱+解锁)、iPhone 3G(越狱+不能解锁)以及iPod Touch(一代越狱)。由于此次加入了中文支持,而默认语言是英文,所以先简单介绍一下语言的切换方法:
1.先打开QuickPwqn2.2.5,在弹出的蓝色提示框上点击OK按钮开始使用。
2.注意下最下面状态栏里的小地球了没有?
点击后在弹出的菜单中选择最下方的中文
WOW,中文出现了!
下面详细介绍一下破解过程:
注意:iPhone 3G请暂时不要升级至2.2.1。
注意事项:
1.2008新款MacBook请先行查看其是否支持DFU功能,如果不能,请转移到其它支持DFU的机器或系统下进行破解\越狱工作。
2.iPhone2.2以上的升级请使用iTunes8.0.2进行!
破解所需工具及其它版本破解教程
所需软件 | 下载地址 |
iTunes8.0.2 | 下载地址 |
QuickPwn2.2.5多国语言版 | 下载地址 |
一代:iPhone1,1_2.2.1_5H11_Restore.ipsw | 下载地址 |
二代(3G iPhone):iPhone1,2_2.2.1_5H11_Restore.ipsw | 下载地址 |
BootLoader 3.9和4.6 | 下载地址 |
Microsoft .NET Framework 2.0简体版 | 下载地址 |
其它固件 | 下载地址 |
1.启动软件后不再需要你要破解的设备,连接USB后QuickPwn会自动识别。
2.QuickPwn接下来会自动读取你的固件版本,并在浏览框下方用文字来提示。选择成功后,设备图片上会显示一个绿色的大对勾。
3.直接进入功能选择界面了,这里可以选择是否安装Cydia和Installer,可以选择是否替换开机和恢复画面,还可以选择是否进行解锁(只对1代iPhone起作用)。
如果你是第一次破解2G版的iPhone,则应该把解锁你的iPhone也选中,在稍后的窗口中添加BL36和Bl46两个文件。
如果你是3G版的iPhone则没有“解锁你的iPhone”这个选项。
4.开始破解前的准备画面,QuickPwn会提示你一些注意事项,比如会尝试关闭iTunes、不要在虚拟环境下进行破解等等,这时记得将iPhone与电脑连接好并点击右下角继续。
5.开始破解了,下面显示的是破解的五个步骤,进行到哪个步骤时,相应那一行的文字就会亮起,中间的三步是需要大家跟着操作的,这三步是软件在指导你进入DFU模式。具体过程是:等待QuickPwn帮你进入恢复模式;
当第二行文字亮起时我们需要马上按住Home键并持续5秒,
第三行文字亮起时马上按住电源键但不要放开Home,等待倒计时10秒结束;
第四行文字亮起时我们松开电源键并按住Home不放等待30秒(有时不用等到30秒,会提前结束),成功的话会亮起第五行字并出现进度条。在操作过程中电脑右下角的任务栏会不断弹出一些提示,不用管它。
如果进入DFU失败,会弹出提示让你再按要求操作一遍,直到成功为止。
进入DFU成功后,会看到QuickPwn向iPhone上传文件的进程,耐心等待即可。
6.破解成功后会看到下面的画面,这时等待iPhone重启,重启完毕后iPhone我们就完成了整个的破解工作。
如果进入DFU失败,会弹出提示让你再按要求操作一遍,直到成功为止。
进入DFU成功后,会看到QuickPwn向iPhone上传文件的进程,耐心等待即可。
6.破解成功后会看到下面的画面,这时等待iPhone重启,重启完毕后iPhone我们就完成了整个的破解工作。
http://www.chinamac.com/uploadfile/2009/0203/20090131163228415.jpg
2009年2月11日星期三
探究CRC32算法实现原理-why table-driven implemention
探究CRC32算法实现原理-why table-driven implemention
Author : Kevin Lynx
email : zmhn320@163.com
Preface
基于不重造轮子的原则,本文尽量不涉及网络上遍地都是的资料。
What's CRC ?
简而言之,CRC是一个数值。该数值被用于校验数据的正确性。CRC数值简单地说就是通过让你需要做
处理的数据除以一个常数而得到的余数。当你得到这个数值后你可以将这个数值附加到你的数据后,
当数据被传送到其他地方后,取出原始数据(可能在传送过程中被破坏)与附加的CRC数值,然后将这里
的原始数据除以之前那个常数(约定好的)然后得到新的CRC值。比较两个CRC值是否相等即可确认你的
数据是否在传送过程中出现错误。
那么,如何让你的数据除以一个常数?方法是对你的数据进行必要的编码处理,逐字节处理成数字。
那么这个常数是什么?你不必关注它是什么,也不需要关注它是如何获得的。当你真的要动手写一个
CRC的实现算法时,我可以告诉你,CRC的理论学家会告诉你。不同长度的常数对应着不同的CRC实现算法。
当这个常数为32位时,也就是这里所说的CRC32。
以上内容你不必全部理解,因为你需要查阅其他资料来获取CRC完整的理论介绍。
The mathematics behind CRC ?
很多教科书会把CRC与多项式关联起来。这里的多项式指的是系数为0或1的式子,例如:
a0 + a1*x + a2*x^2 + ... + an*x^n。其中a0, a1, ..., an要么为0要么为1。我们并不关注x取什么值。
(如果你要关注,你可以简单地认为x为2) 这里把a0, a1, ..., an的值取出来排列起来,就可以表示比特
流。例如 1 + x + x^3所表示的比特流就为:1101。部分资料会将这个顺序颠倒,这个很正常。
什么是生成多项式?
所谓的生成多项式,就是上面我所说的常数。注意,在这里,一个多项式就表示了一个比特流,也就是一堆
1、0,组合起来最终就是一个数值。例如CRC32算法中,这个生成多项式为:
c(x) = 1 + x + x^2 + x^4 + x^5 + x^7 + x^8 + x^10 + x^11 + x^12 + x^16 + x^22 + x^23 + x^26 + x^32。
其对应的数字就为:11101101101110001000001100100000(x^32在实际计算时隐含给出,因此这里没有包含它
的系数),也就是0xEDB88320(多项式对应的数字可能颠倒,颠倒后得到的是0x04C11DB7,其实也是正确的)。
由此可以看出,CRC值也可以看成我们的数据除以一个生成多项式而得到的余数。
如何做这个除法?
套用大部分教科书给出的计算方法,因为任何数据都可以被处理成纯数字,因此,在某种程度上说,我们可以
直接开始这个除法。尽管事实上这并不是标准的除法。例如,我们的数据为1101011011(方便起见我直接给二进制
表示了,从这里也可以看出,CRC是按bit进行计算的),给定的生成多项式(对应的值)为10011。通常的教科书
会告诉我们在进行这个除法前,会把我们的数据左移几位(生成多项式位数-1位),从而可以容纳将来计算得到
的CRC值(我上面所说的将CRC值附加到原始数据后)。但是为什么要这样做?我也不知道。(不知道的东西不能含糊
而过)那么,除法就为:
1100001010
_______________
10011 ) 11010110110000 附加了几个零的新数据
10011......... 这里的减法(希望你不至于忘掉小学算术)是一个异或操作
-----.........
10011........
10011........
-----........
00001....... 逐bit计算
00000.......
-----.......
00010......
00000......
-----......
00101.....
00000.....
-----.....
01011....
00000....
-----....
10110...
10011...
-----...
01010..
00000..
-----..
10100.
10011.
-----.
01110
00000
-----
1110 = 这个余数也就是所谓的CRC值,通常又被称为校验值。
希望进行到这里,你可以获取更多关于CRC的感性认识。而我们所要做的,也就是实现一个CRC的计算算法。
说白了,就是提供一个程序,给定一段数据,以及一个生成多项式(对于CRC32算法而言该值固定),然后
计算得出上面的1110余数。
The simplest algorithm.
最简单的实现算法,是一种模拟算法。我们模拟上面的除法过程,遵从网上一份比较全面的资料,我们设定
一个变量register。我们逐bit地将我们的数据放到register中。然后判断register最高位是否为1,如果是
则与生成多项式异或操作,否则继续处理。这个过程简单地模拟了上述除法过程:
- /// The simplest CRC implement algorithm.
- ///
- /**//*
- Load the register with zero bits.
- Augment the message by appending W zero bits to the end of it.
- While (more message bits)
- Begin
- Shift the register left by one bit, reading the next bit of the
- augmented message into register bit position 0.
- If (a 1 bit popped out of the register during step 3)
- Register = Register XOR Poly.
- End
- The register now contains the remainder.
- */
- #include
- #define POLY 0x13
- int main()
- {
- /**//// the data
- unsigned short data = 0x035b;
- /**//// load the register with zero bits
- unsigned short regi = 0x0000;
- /**//// augment the data by appending W(4) zero bits to the end of it.
- data <<= 4;
- /**//// we do it bit after bit
- for( int cur_bit = 15; cur_bit >= 0; -- cur_bit )
- {
- /**//// test the highest bit which will be poped later.
- /// in fact, the 5th bit from right is the hightest bit here
- if( ( ( regi >> 4 ) & 0x0001 ) == 0x1 )
- {
- regi = regi ^ POLY;
- }
- /**//// shift the register
- regi <<= 1;
- /**//// reading the next bit of the augmented data
- unsigned short tmp = ( data >> cur_bit ) & 0x0001;
- regi |= tmp;
- }
- /**//// and now, register contains the remainder which is also called CRC value.
- return 0;
- }
better algorithm ?
很多时候这种让人容易理解的算法都不会被实际用到。这种逐bit操作的算法实在很慢。你可能知道
一般的CRC32算法都是一种基于表(table-driven)的算法。但是你可能不知道这个表是如何来的。
一种改善这种bit after bit的方法就是将这个bit扩大,例如典型的做法就是换成byte。这里我要详细地叙述下
上面那种算法的过程:
我们每次会先检查register的最高位是否为1,如果为1,则将生成多项式(所谓的Poly)与register进行异或操作。
然后,将register左移一位,也就舍弃了最高位。然后将我们的数据拿一bit出来放到register的最低位。
也就是说,register中的某一位的值会决定后面几位的值。如果将register最高字节每一bit编码为:
t7 t6 t5 t4 t3 t2 t1 t0。那么,t7会决定t6-t0的值(如果为1),t6会决定t5-t0的值,依次类推。但是,无论谁
决定谁的值,当上面那个算法迭代一个字节后(8bits),t7-t0都会被丢弃(whatever you do)。唯一留下来的东西,
就是对这个字节以后字节的影响。
那么,如果我们可以直接获取这个影响,我们就可以byte after byte地处理,而不是bit after bit。如何获取这个
影响呢?这个影响又是什么呢?这个影响就对应着我们的table-driven CRC算法中的表元素!
但是,为什么我们逐bit进行计算的过程为什么可以简化为一步操作?事实上,我们没有简化这个操作。一种用于教学
的算法,是实时地计算这个影响值:
- ///
- /// The table-driven CRC implement algorithm part 1.
- ///
- /**//*
- While (augmented message is not exhausted)
- Begin
- Examine the top byte of the register
- Calculate the control byte from the top byte of the register
- Sum all the Polys at various offsets that are to be XORed into
- the register in accordance with the control byte
- Shift the register left by one byte, reading a new message byte
- into the rightmost byte of the register
- XOR the summed polys to the register
- End
- */
- #include
- #include
- #include
- #define POLY 0x04C11DB7L
- int main()
- {
- /**//// the data
- unsigned long data = 0x1011035b;
- /**//// load the register with the data
- unsigned long regi = 0;
- /**//// allocate memory to contain the AUGMENTED data (added some zeros)
- unsigned char p[8];
- /**//// copy data
- memset( p, 0, 8 );
- memcpy( p, &data, 4 );
- /**//// because data contains 4 bytes
- for( int i = 0; i <>
- {
- /**//// get the top byte of the register
- unsigned char top_byte = (unsigned char)( ( regi >> 24 ) & 0xff );
- /**//// sum all the polys at various offsets
- unsigned long sum_poly = top_byte <<>
- for( int j = 0; j <>
- {
- /**//// check the top bit
- if( ( sum_poly >> 31 ) != 0 )
- {
- /**//// TODO : understand why '<<' first
- sum_poly = ( sum_poly <<>
- }
- else
- {
- sum_poly <<= 1;
- }
- }
- /**//// shift the register left by on byte, reading a new
- regi = ( ( regi <<>
- /**//// xor the summed polys to the register
- regi ^= sum_poly;
- }
- /**//// and now, register contains the remainder which is also called CRC value.
- return 0;
- }
其中:
- /// sum all the polys at various offsets
- unsigned long sum_poly = top_byte <<>
- for( int j = 0; j <>
- {
- /**//// check the top bit
- if( ( sum_poly >> 31 ) != 0 )
- {
- /**//// TODO : understand why '<<' first
- sum_poly = ( sum_poly <<>
- }
- else
- {
- sum_poly <<= 1;
- }
- }
就是用于计算这个影响值的。事实上,table-driven CRC算法中的那个表就是通过这段代码生成的(排除其他一些细节)。
你可能并不是很理解,这里我建议你忽略各种细节(更多的细节见参考资料)。你所需要知道的是,我们将8次逐bit的操
作合并到了一次byte操作中。而这个byte操作,就是8次bit操作的合操作(上面提到的影响值)。这个byte操作其实就是
一个数值,也就是table-driven CRC算法中那个表的一个元素。不同序列的bit操作其实对应着不同的unsigned char
值,因此那个table有256个元素。
show me where the table is :
如上所说,上面的算法很容易地就可以引进一个表:
进一步简化:
上述算法一个典型特征是会在我们的数据后面添加若干0。这样做其他做了很多没用的计算。一种简化做法就是将这些
没用的计算合并到其他计算中。其实这都是一些位操作的技巧:
- **////
- /// The table-driven CRC implement algorithm part 2.
- ///
- /**//*
- While (augmented message is not exhausted)
- Begin
- Examine the top byte of the register
- Calculate the control byte from the top byte of the register
- Sum all the Polys at various offsets that are to be XORed into
- the register in accordance with the control byte
- Shift the register left by one byte, reading a new message byte
- into the rightmost byte of the register
- XOR the summed polys to the register
- End
- */
- #include
- #include
- #include
- #define POLY 0x04C11DB7L
- unsigned long get_sum_poly( unsigned char top_byte )
- {
- /**//// sum all the polys at various offsets
- unsigned long sum_poly = top_byte <<>
- for( int j = 0; j <>
- {
- /**//// check the top bit
- if( ( sum_poly >> 31 ) != 0 )
- {
- /**//// TODO : understand why '<<' first
- sum_poly = ( sum_poly <<>
- }
- else
- {
- sum_poly <<= 1;
- }
- }
- return sum_poly;
- }
- void create_table( unsigned long *table )
- {
- for( int i = 0; i <>
- {
- table[i] = get_sum_poly( (unsigned char) i );
- }
- }
- int main()
- {
- /**//// the data
- unsigned long data = 0x1011035b;
- /**//// load the register with the data
- unsigned long regi = 0;
- /**//// allocate memory to contain the AUGMENTED data (added some zeros)
- unsigned char p[8];
- /**//// copy data
- memset( p, 0, 8 );
- memcpy( p, &data, 4 );
- /**//// the table
- unsigned long table[256];
- /**//// create the table
- create_table( table );
- /**//// because data contains 4 bytes
- for( int i = 0; i <>
- {
- /**//// get the top byte of the register
- unsigned char top_byte = (unsigned char)( ( regi >> 24 ) & 0xff );
- /**//// shift the register left by on byte, reading a new
- regi = ( ( regi <<>
- /**//// xor the summed polys to the register
- regi ^= table[top_byte];
- }
- /**//// and now, register contains the remainder which is also called CRC value.
- return 0;
- }
讨厌的附加0
以上算法有个很大的特征就是要为我们的数据附加很多0。附加0后其实也附加了很多无用的操作。我们要将这些
讨厌的0去掉:
- int main()
- {
- /**//// the data
- unsigned long data = 0x1011035b;
- /**//// load the register with the data
- unsigned long regi = 0;
- /**//// allocate memory to contain the data
- unsigned char p[4];
- /**//// copy data
- memcpy( p, &data, 4 );
- /**//// the table
- unsigned long table[256];
- /**//// create the table
- create_table( table );
- /**//// because data contains 4 bytes
- for( int i = 0; i <>
- {
- regi = ( regi <<>> 24 ) ^ p[i] ];
- }
- /**//// and now, register contains the remainder which is also called CRC value.
- return 0;
- }
关键的一句regi = ( regi <<>> 24 ) ^ p[i] ]; 简化了很多没用的操作。
In practice :
似乎一切被我说的很简单。我想只是因为我没说清楚。我尽量让你注意到事情的重点。我们进行到这里,似乎
我们立马就可以写出自己的CRC32算法并用于实践。但是你很快就会发现,事情并不如你想像的那么简单。
在实际处理时,很多数据的bit会进行一种颠倒操作,例如1010会被颠倒为0101。出现这样的情况是因为某些硬件
在实现CRC算法时,采用了这种(丑陋的)习惯。有些软件实现CRC算法时,也延用了这个习惯。
另外,关于register的初始值问题,有些CRC算法会初始化为0xffffffff。以下给出一个会进行bit颠倒的算法,
该算法可以直接输出table-driven中的表:
- **////
- /// The table-driven CRC implement algorithm part 4.
- ///
- /// Donot need augment W/8 zero bytes.
- ///
- #include
- #include
- #include
- #define POLY 0x04C11DB7L
- #define BITMASK(X) (1L << (X))
- unsigned long refelect( unsigned long v, int b )
- {
- int i;
- unsigned long t = v;
- for( i = 0; i <>
- {
- if( t & 1L )
- v |= BITMASK( (b-1)-i );
- else
- v &= ~BITMASK( (b-1)-i );
- t >>= 1;
- }
- return v;
- }
- /**//// i'll try to write a correct algorithm
- unsigned long get_sum_poly( unsigned char byte )
- {
- byte = (unsigned long) refelect( byte, 8 );
- unsigned long sum_poly = byte <<>
- for( int i = 0; i <>
- {
- /**//// check the top bit
- if( ( sum_poly >> 31 ) != 0 )
- {
- /**//// TODO : understand why '<<' first
- sum_poly = ( sum_poly <<>
- }
- else
- {
- sum_poly <<= 1;
- }
- }
- sum_poly = refelect( sum_poly, 32 );
- return sum_poly;
- }
- void create_table( unsigned long *table )
- {
- for( int i = 0; i <= 255; ++ i )
- {
- table[i] = get_sum_poly( (unsigned char) i );
- }
- }
- void output_table( const unsigned long *table )
- {
- FILE *fp = fopen( "table.txt", "w" );
- for( int y = 0; y <>
- {
- fprintf( fp, "0x%08lXL,\t0x%08lXL,\t0x%08lXL,\t0x%08lXL, \n",
- table[ y * 4 + 0],
- table[ y * 4 + 1],
- table[ y * 4 + 2],
- table[ y * 4 + 3] );
- }
- fclose( fp );
- }
- int main()
- {
- /**//// the table
- unsigned long table[256];
- /**//// the data
- unsigned long data = 0x1011035b;
- /**//// load the register with the data
- unsigned long regi = 0;
- /**//// allocate memory to contain the data
- unsigned char p[4];
- /**//// copy data
- memcpy( p, &data, 4 );
- /**//// create the table
- create_table( table );
- /**//// output the table
- output_table( table );
- /**//// because data contains 4 bytes
- for( int i = 0; i <>
- {
- regi = ( regi <<>> 24 ) ^ p[i] ];
- }
- /**//// and now, register contains the remainder which is also called CRC value.
- return 0;
- }
Please FORGIVE me
我想我并没有将整个过程彻底地讲清楚。但是我希望你能明白大致的原理。关于table-driven中那个神奇的表的来历,
关于CRC32算法的推导过程等等之类。
本文代码下载: http://www.cppblog.com/Files/kevinlynx/CRC%20Implement.rar