﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>IT博客网-嵌入式-文章分类-嵌入式技术资料</title><link>http://www.cnitblog.com/zouzheng/category/2077.html</link><description>要像阿甘一直向前奔跑！</description><language>zh-cn</language><lastBuildDate>Fri, 29 Feb 2008 10:00:36 GMT</lastBuildDate><pubDate>Fri, 29 Feb 2008 10:00:36 GMT</pubDate><ttl>60</ttl><item><title>Building arm-linux toolchain for ARM/XScale-iWMMXt(new ABI)</title><link>http://www.cnitblog.com/zouzheng/articles/40304.html</link><dc:creator>zz</dc:creator><author>zz</author><pubDate>Fri, 29 Feb 2008 09:41:00 GMT</pubDate><guid>http://www.cnitblog.com/zouzheng/articles/40304.html</guid><wfw:comment>http://www.cnitblog.com/zouzheng/comments/40304.html</wfw:comment><comments>http://www.cnitblog.com/zouzheng/articles/40304.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/zouzheng/comments/commentRss/40304.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/zouzheng/services/trackbacks/40304.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: &nbsp;[本文PDF文档点此下载]0 Preface两个月前，笔者在ChinaUnix BLOG上发表了一篇日志Building arm-linux toolchain for ARM/XSCALE，详细介绍了手工建立ARM-linux的交叉编译工具链的全过程，但这种方法越来越体现出局限性和落后性，主要表现在：(1)使用的软件包版本较老；(2)使用linux-threads而非新的...&nbsp;&nbsp;<a href='http://www.cnitblog.com/zouzheng/articles/40304.html'>阅读全文</a><img src ="http://www.cnitblog.com/zouzheng/aggbug/40304.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/zouzheng/" target="_blank">zz</a> 2008-02-29 17:41 <a href="http://www.cnitblog.com/zouzheng/articles/40304.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>HOWTO build arm-linux toolchain for ARM/XSCALE</title><link>http://www.cnitblog.com/zouzheng/articles/40216.html</link><dc:creator>zz</dc:creator><author>zz</author><pubDate>Wed, 27 Feb 2008 08:48:00 GMT</pubDate><guid>http://www.cnitblog.com/zouzheng/articles/40216.html</guid><wfw:comment>http://www.cnitblog.com/zouzheng/comments/40216.html</wfw:comment><comments>http://www.cnitblog.com/zouzheng/articles/40216.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/zouzheng/comments/commentRss/40216.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/zouzheng/services/trackbacks/40216.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: These instructions document how to build an arm-linux tool chainthat contains both little-endian and big-endian target libraries.1. Packages used:&nbsp;&nbsp;&nbsp; binutils-2.14.tar.gz&nbsp;&nbsp;&nb...&nbsp;&nbsp;<a href='http://www.cnitblog.com/zouzheng/articles/40216.html'>阅读全文</a><img src ="http://www.cnitblog.com/zouzheng/aggbug/40216.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/zouzheng/" target="_blank">zz</a> 2008-02-27 16:48 <a href="http://www.cnitblog.com/zouzheng/articles/40216.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>arm linux 启动流程</title><link>http://www.cnitblog.com/zouzheng/articles/40213.html</link><dc:creator>zz</dc:creator><author>zz</author><pubDate>Wed, 27 Feb 2008 08:28:00 GMT</pubDate><guid>http://www.cnitblog.com/zouzheng/articles/40213.html</guid><wfw:comment>http://www.cnitblog.com/zouzheng/comments/40213.html</wfw:comment><comments>http://www.cnitblog.com/zouzheng/articles/40213.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/zouzheng/comments/commentRss/40213.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/zouzheng/services/trackbacks/40213.html</trackback:ping><description><![CDATA[<p>arm linux 启动流程之 ppcboot&nbsp;&nbsp;&nbsp; <br><a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#65;&#117;&#116;&#104;&#111;&#114;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#68;&#97;&#110;&#115;&#101;&#110;&#45;&#45;&#45;&#45;&#45;&#120;&#122;&#100;&#50;&#55;&#51;&#52;&#64;&#49;&#54;&#51;&#46;&#99;&#111;&#109;"><u><font color=#0000ff>Author-------Dansen-----xzd2734@163.com</font></u></a></p>
<p>不是每一行代码都必须读懂，我只是大概地过一下流程<br>毕竟这些都是比较成熟的代码，没必要去改的<br>是针对我自己的板子的，硬件配置如下<br>cpu是s3c2410<br>board type 是 smdk2410<br>16M Nor Flash 地址是 0x0---0xFFFFFF<br>64M SDRAM&nbsp;&nbsp;&nbsp;&nbsp; 地址是 0x30000000---0x33FFFFFF<br>软件是华恒版的<br>ppcboot 2.0 和 linux 2.4.18<br>仔细分析了一下启动的流程，能更好地理解硬件和软件的配合<br>方便移植。<br>我们在flash的开始处烧写了ppcboot.bin,这是可执行的二进制文件<br>注意和ELF可执行性文件是有区别的。<br>cpu上电后可以从直接从flash地址0处取指令来执行<br>开始的代码在ppcboot-2.0.0\cpu\arm920t\start.s中<br>这里需要提一下编译链接时用到的一个很重要的链接文件<br>ppcboot-2.0.0\board\smdk2410\ppcboot.lds<br>这个文件给出了代码中各标号的基地址，和各个段的链接顺序<br>ENTRY(_start)<br>SECTIONS<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; . = 0x00000000;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; . = ALIGN(4);<br>&nbsp;.text&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; :<br>&nbsp;{<br>&nbsp;&nbsp; cpu/arm920t/start.o (.text)<br>&nbsp;&nbsp; *(.text)<br>&nbsp;}</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; . = ALIGN(4);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .rodata : { *(.rodata) }</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; . = ALIGN(4);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .data : { *(.data) }</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; . = ALIGN(4);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .got : { *(.got) }</p>
<p>&nbsp;armboot_end_data = .;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; . = ALIGN(4);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .bss : { *(.bss) }</p>
<p>&nbsp;armboot_end = .;<br>}<br>可以看到程序的入口是_start标号指示的，而cpu/arm920t/start.o<br>则被安排在程序最开始的地方，这个标号就是在start.s中<br>但是还有一点是需要特别注意的，开始我也是因为这个地方而没有很好地理解程序<br>虽然lds中有 . = 0x00000000 这一句，指示链接基地址，不过其实这句是不起作用的，<br>真正的链接基地址在ppcboot-2.0.0\config.mk中指定的<br>LDFLAGS += -Bstatic -T $(LDSCRIPT) -Ttext $(TEXT_BASE)<br>其中-Ttext $(TEXT_BASE)就是指定链接地址为TEXT_BASE的值<br>因而是可变的，TEXT_BASE在ppcboot-2.0.0\board\smdk2410\config.mk中定义<br>TEXT_BASE = 0x33F00000<br>ppcboot-2.0.0\config.mk是包括到Makefile中的,<br>在Makefile中有$(LD) $(LDFLAGS) $(OBJS) $(LIBS) $(LIBS) -Map ppcboot.map -o ppcboot<br>所以说起来真正的链接地址是0x33F00000,其实这样在把ppcboot拷到Ram中就可以实现无缝跳转了<br>.globl _start<br>_start: b&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; reset<br>跳到renset<br>reset: ldr&nbsp;&nbsp;&nbsp;&nbsp; r0, =pWTCON<br>&nbsp;mov&nbsp;&nbsp;&nbsp;&nbsp; r1, #0x0<br>&nbsp;str&nbsp;&nbsp;&nbsp;&nbsp; r1, [r0]<br>..........................<br>&nbsp;bl cpu_init_crit&nbsp; //bl跳转会回来<br>relocate: //下面开始要把ppcboot拷到Ram中<br>&nbsp;adr r0, _start&nbsp; /* r0 &lt;- current position of code */<br>&nbsp;ldr r2, _armboot_start<br>&nbsp;ldr r3, _armboot_end<br>&nbsp;sub r2, r3, r2&nbsp; /* r2 &lt;- size of armboot */<br>&nbsp;ldr r1, _TEXT_BASE&nbsp; /* r1 &lt;- destination address */<br>&nbsp;add r2, r0, r2&nbsp; /* r2 &lt;- source end address */<br>以上代码需要注意的一点是 adr 和 ldr 的区别<br>adr取得是当前pc相关的偏移地址,在这里程序还是在flash中运行<br>所以取得地址是以0x0为基址的<br>而ldr取的是_armboot_start所指的值<br>.globl _armboot_start<br>_armboot_start:<br>&nbsp;.word _start<br>看到它的值也是_start的地址,不过我们这里取的是绝对地址,是在链接是确定的以<br>TEXT_BASE为基址的.由于_start的偏移是0,所以r0是0,r2就是TEXT_BASE<br>copy_loop:<br>&nbsp;ldmia r0!, {r3-r10}<br>&nbsp;stmia r1!, {r3-r10}<br>&nbsp;cmp r0, r2<br>&nbsp;ble copy_loop<br>循环copy<br>&nbsp;ldr r0, _armboot_end&nbsp; /* set up the stack */<br>&nbsp;add r0, r0, #CONFIG_STACKSIZE<br>&nbsp;sub sp, r0, #12&nbsp; /* leave 3 words for abort-stack */</p>
<p>&nbsp;ldr pc, _start_armboot <br>_start_armboot: .word start_armboot</p>
<p>//通过这一句跳转到ppcboot-2.0.0\lib_arm\board.c中的start_armboot函数去执行了<br>start_armboot的绝对地址也是以TEXT_BASE为基址的,所以可以顺利的实现无缝跳转了.<br>接着下来就是一系列初始化的工作了<br>首先定义了一个全局的数据结构 gd_t gd_data;<br>DECLARE_GLOBAL_DATA_PTR 这个宏定义的是一个全局的gd_t类型的指针gd<br>gd = &amp;gd_data;<br>这样以后就可以用gd来访问gd_data这个数据结构了<br>&nbsp;for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {<br>&nbsp; if ((*init_fnc_ptr)() != 0) {<br>&nbsp;&nbsp; hang ();<br>&nbsp; }<br>&nbsp;}<br>init_fnc_ptr中是一系列初始化函数的指针<br>init_fnc_t *init_sequence[] = {<br>&nbsp;cpu_init,&nbsp; /* basic cpu dependent setup */<br>&nbsp;board_init,&nbsp; /* basic board dependent setup */<br>&nbsp;interrupt_init,&nbsp; /* set up exceptions */<br>&nbsp;env_init,&nbsp; /* initialize environment */<br>&nbsp;init_baudrate,&nbsp; /* initialze baudrate settings */<br>&nbsp;serial_init,&nbsp; /* serial communications setup */<br>&nbsp;display_banner,<br>&nbsp;dram_init,&nbsp; /* configure available RAM banks */<br>&nbsp;display_dram_config,</p>
<p>&nbsp;NULL,<br>};<br>基本上serial_init后我们就可以用printf函数来打印信息了.<br>&nbsp;for (;;) {<br>&nbsp; main_loop ();<br>&nbsp;}<br>进入了主循环,在M:\ppcboot-2.0.0\common\main.c中<br>&nbsp;{<br>&nbsp; char c = 'y';<br>&nbsp; unsigned long timedata;<br>&nbsp; printf("start linux now(y/n):");<br>&nbsp; timedata = 0;<br>&nbsp; for (;;) {<br>&nbsp;&nbsp; while (!tstc()) { /* while no incoming data */<br>&nbsp;&nbsp;&nbsp; if (timedata++ &gt; 3000 * 100 *3)<br>&nbsp;&nbsp;&nbsp;&nbsp; goto bootm; /* timed out */<br>&nbsp;&nbsp; }<br>&nbsp; c = getc();<br>&nbsp; }<br>tstc()是测试串口是否有数据输入,显然没有的话就会等待time out跳出<br>bootm:<br>&nbsp; if(c == 'y'||c == 'Y'){<br>&nbsp;&nbsp; strcpy(lastcommand , "bootm 30008000 30800000\r");<br>&nbsp;&nbsp; flag = 0;<br>&nbsp;&nbsp; rc = run_command (lastcommand, flag);<br>&nbsp;&nbsp; if (rc &lt;= 0) {<br>&nbsp;&nbsp;&nbsp; /* invalid command or not repeatable, forget it */<br>&nbsp;&nbsp;&nbsp; lastcommand[0] = 0;<br>&nbsp;&nbsp; }<br>&nbsp; }<br>&nbsp; else{<br>&nbsp;&nbsp; printf("\n\n");<br>&nbsp; }<br>&nbsp;} <br>这样如果串口没有输入或者输入时y Y 的话,就会去执行bootm 30008000 30800000\r这条命令<br>否则就会到ppcboot的命令行等待输入.<br>执行bootm 30008000 30800000\r这条命令会调用ppcboot-2.0.0\common\cmd_bootm.c中的<br>do_bootm函数,具体的命令怎样被分解,选择调用函数的机制我就不多说了,追着run_command去就是了<br>在do_bootm中调用了do_bootm_linux函数,这个函数在ppcboot-2.0.0\lib_arm\armlinux.c中<br>&nbsp;&nbsp;&nbsp; ret = memcpy((void *)0x30008000, (void *)0x40000, 0x100000);<br>&nbsp;&nbsp;&nbsp; if (ret != (void *)0x30008000)<br>&nbsp;&nbsp;&nbsp;&nbsp; printf("copy kernel failed\n");<br>&nbsp;&nbsp;&nbsp; else<br>&nbsp;&nbsp;&nbsp; printf("copy kernel done\n");&nbsp;&nbsp;&nbsp;&nbsp; </p>
<p>&nbsp;&nbsp;&nbsp; ret = memcpy((void *)0x30800000, (void *)0x140000, 0x440000);<br>&nbsp;&nbsp;&nbsp; if (ret != (void *)0x30800000)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("haha failed\n");<br>&nbsp;&nbsp;&nbsp; else<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("copy ramdisk done\n");</p>
<p>首先把kernel和ramdisk都拷到Ram相应的地方去.<br>&nbsp;&nbsp;&nbsp; setup_linux_param(0x30000000 + LINUX_PARAM_OFFSET); //也在armlinux.c中<br>&nbsp;&nbsp;&nbsp; #define LINUX_PARAM_OFFSET 0x100<br>建立要传给内核的参数,参数的地址都是固定的,所以内核也知道去这里取参数<br>参数格式比较复杂,我这里好像传得参数不多<br>void setup_linux_param(ulong param_base)<br>{<br>&nbsp;struct param_struct *params = (struct param_struct *)param_base;<br>...............<br>}<br>只是通过一个param_struct的结构体来传参数的,不过现在一般都用另一种tag标记的传参方法<br>一个主要的参数时char linux_cmd[] = "initrd=0x30800000,0x440000&nbsp; root=/dev/ram init=/linuxrc console=ttyS0";<br>&nbsp;if (linux_cmd == NULL) {<br>&nbsp; printf("Wrong magic: could not found linux command line\n");<br>&nbsp;} else {<br>&nbsp; memcpy(params-&gt;commandline, linux_cmd, strlen(linux_cmd) + 1);<br>&nbsp; printf("linux command line is: \"%s\"\n", linux_cmd);<br>&nbsp;}<br>是比较重要的.移植的时候常常需要修改<br>接着call_linux(0, 0xc1, 0x30008000); 看出来是准备调到linux去了<br>0xc1是machine type,这三个参数分别给了r0,r1,r2,这些都是调用内核的约定<br>void&nbsp; call_linux(long a0, long a1, long a2)<br>{<br>__asm__(<br>&nbsp;"mov r0, %0\n"<br>&nbsp;"mov r1, %1\n"<br>&nbsp;"mov r2, %2\n"<br>&nbsp;"mov ip, #0\n"<br>&nbsp;"mcr p15, 0, ip, c13, c0, 0\n" /* zero PID */<br>&nbsp;"mcr p15, 0, ip, c7, c7, 0\n" /* invalidate I,D caches */<br>&nbsp;"mcr p15, 0, ip, c7, c10, 4\n" /* drain write buffer */<br>&nbsp;"mcr p15, 0, ip, c8, c7, 0\n" /* invalidate I,D TLBs */<br>&nbsp;"mrc p15, 0, ip, c1, c0, 0\n" /* get control register */<br>&nbsp;"bic ip, ip, #0x0001\n"&nbsp; /* disable MMU */<br>&nbsp;"mcr p15, 0, ip, c1, c0, 0\n" /* write control register */<br>&nbsp;"mov pc, r2\n"<br>&nbsp;"nop\n"<br>&nbsp;"nop\n"<br>&nbsp;: /* no outpus */<br>&nbsp;: "r" (a0), "r" (a1), "r" (a2)<br>&nbsp;);<br>}<br>mov pc, r2 就是这句吧,调到了30008000去执行内核了</p>
<p>接下来就到内核了吧</p>
<br>
<p>arm linux 启动流程之 解压内核&nbsp;&nbsp;&nbsp;</p>
<p><a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#65;&#117;&#116;&#104;&#111;&#114;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#68;&#97;&#110;&#115;&#101;&#110;&#45;&#45;&#45;&#45;&#45;&#120;&#122;&#100;&#50;&#55;&#51;&#52;&#64;&#49;&#54;&#51;&#46;&#99;&#111;&#109;">Author-------Dansen-----xzd2734@163.com</a></p>
<p>从后往前看下编译生成zImage的过程，我们可以找到程序的入口还是那个很重要<br>链接文件，找到它,生成zImage所在的目录是kernel\arch\arm\boot\compressed\<br>Make过程为....ld -p -X -T vmlinux.lds head.o misc.o head-s3c2410.o piggy.o<br>libgcc.o -o vmlinux<br>然后是用二进制工具objcopy把vmlinux制作成可执行的二进制映像文件zImage<br>这样在我们就去kernel\arch\arm\boot\compressed\目录下去找到vmlinux.lds文件<br>如果没有编译就不会有这个文件，因为它也是在编译过程生成的，由同一目录下的<br>vmlinux.lds.in生成，打开这个文件<br>ENTRY(_start)<br>SECTIONS<br>{<br>&nbsp; . = LOAD_ADDR;<br>&nbsp; _load_addr = .;</p>
<p>&nbsp; . = TEXT_START;<br>&nbsp; _text = .;</p>
<p>&nbsp; .text : {<br>&nbsp;&nbsp;&nbsp; _start = .;<br>&nbsp;&nbsp;&nbsp; *(.start)<br>&nbsp;&nbsp;&nbsp; *(.text)<br>........<br>入口是_start,而且入口就直接定义在这个文件中了<br>入口直接接着.start段,所以程序开始是从.start段开始执行的<br>如果看看vmlinux.lds的生成过程就应该能找到LOAD_ADDR和TEXT_START的值<br>实际上这两个值是由其他两个变量赋给的 ZRELADDR 和 ZTEXTADDR<br>在kernel\arch\arm\boot\Makefile中我们可以找到这两个变量的值<br>ifeq ($(CONFIG_ARCH_S3C2410),y)<br>ZTEXTADDR&nbsp; = 0x30008000<br>ZRELADDR&nbsp; = 0x30008000<br>endif<br>所以<br>LOAD_ADDR = 0x30008000<br>TEXT_START = 0x30008000<br>看一下vmlinux.lds吧<br>ENTRY(_start)<br>SECTIONS<br>{<br>&nbsp; . = 0x30008000;<br>&nbsp; _load_addr = .;</p>
<p>&nbsp; . = 0;<br>&nbsp; _text = .;<br>显然LOAD_ADDR被赋值了0x30008000<br>看一下TEXT_START怎么成0了，我想这应该是一个偏移吧，偏移是0<br>所以它还是0x30008000<br>接着下来就从head.s来开始看代码吧<br>&nbsp; .section ".start", #alloc, #execinstr<br>/*<br>&nbsp;* sort out different calling conventions<br>&nbsp;*/<br>&nbsp; .align<br>start:<br>&nbsp; .type start,#function<br>&nbsp; .rept 8<br>&nbsp; mov r0, r0<br>&nbsp; .endr</p>
<p>&nbsp; b 1f<br>&nbsp; .word 0x016f2818&nbsp; @ Magic numbers to help the loader<br>&nbsp; .word start&nbsp;&nbsp; @ absolute load/run zImage address<br>&nbsp; .word _edata&nbsp;&nbsp; @ zImage end address<br>1:&nbsp; mov r7, r1&nbsp;&nbsp; @ save architecture ID<br>这里一定就是程序的入口了，一般汇编程序的含义就看看英文注释就是了<br>有一个要注意的地方，不是一个汇编文件就是属于一个段的，不是说先执行完了<br>head.s再去执行head-s3c2410.s,还是要注意链接的段,显然head.s<br>不一会就开始了另一个段.text<br>&nbsp; .text<br>&nbsp; adr r0, LC0<br>&nbsp; ldmia r0, {r1, r2, r3, r4, r5, r6, ip, sp}<br>&nbsp; subs r0, r0, r1&nbsp; @ calculate the delta offset<br>而我们的head-s3c2410.s呢<br>&nbsp;.section ".start", #alloc, #execinstr<br>__S3C2410_start:<br>&nbsp;bic r2, pc, #0x1f<br>&nbsp;add r3, r2, #0x4000&nbsp; @ 16 kb is quite enough...<br>还是属于.start段的，所以顺序执行下来时先执行head-s3c2410.s，然后再去执行<br>.text段。head-s3c2410.s主要是cpu的一些初始化工作。接着下来我们会需要把内核<br>接压缩，先说说为什么吧。还是注意到上面生成zImage的文件中有一个piggy.o，往上<br>追寻可以看到是piggy.o由那个真正的内核vmlinux生成的，这个vmlinux才是启动后一直在<br>运行的内核，原本很大，压缩以后可以方便地放在flash中，当然其实不压缩跳到它的<br>入口也就可以运行了。解压的内核是准备从LOAD_ADDR = 0x30008000开始的4M空间，会覆盖<br>我们的当前运行的代码，那样就先把内核解压到我们这个zImage+分配堆栈0x10000的最后<br>&nbsp; cmp r4, r2&nbsp; //r4 是LOAD_ADDR=0x30008000<br>&nbsp; bhs wont_overwrite //r2 是当前代码的最底部&nbsp;&nbsp;&nbsp; 这里当然不会跳转<br>&nbsp; add r0, r4, #4096*1024 @ 4MB largest kernel size<br>&nbsp; cmp r0, r5&nbsp; //r5 也是0x30008000 <br>&nbsp; bls wont_overwrite //不会跳转</p>
<p>&nbsp; mov r5, r2&nbsp; //r2是(user_stack+4096)在zImage的最后+0x10000<br>&nbsp; mov r0, r5&nbsp; <br>&nbsp; mov r3, r7&nbsp; //machine type<br>&nbsp; bl decompress_kernel <br>有了r5,r0,r7作为参数，就可以调用misc.c中的decompress_kernel函数进行解压缩了<br>这个函数调用的gunzip函数时gcc的库函数，所以在源码中找不到的<br>解压在r5开始的地方，函数返回的是r0解压得到的长度。这时候我们需要对代码经行调整<br>&nbsp; add r1, r5, r0&nbsp; @ end of decompressed kernel<br>&nbsp; adr r2, reloc_start<br>&nbsp; ldr r3, LC1&nbsp;&nbsp; //LC1: .word reloc_end - reloc_start<br>&nbsp; add r3, r2, r3<br>1:&nbsp; ldmia r2!, {r8 - r13}&nbsp; @ copy relocation code<br>&nbsp; stmia r1!, {r8 - r13}<br>&nbsp; ldmia r2!, {r8 - r13}<br>&nbsp; stmia r1!, {r8 - r13}<br>&nbsp; cmp r2, r3&nbsp; //这里就把从reloc_start到reloc_end这段我们需要的代码放到了<br>&nbsp; blo 1b&nbsp; //解压内核的最后，而在下面我们会将zImage都覆盖掉<br>&nbsp; bl cache_clean_flush<br>&nbsp; add pc, r5, r0 //调到调整后的reloc_start，在decompressed kernel后<br>reloc_start: add r8, r5, r0 //r5解压内核开始的地方 r0解压内核的长度<br>&nbsp; debug_reloc_start<br>&nbsp; mov r1, r4&nbsp; //r4=0x30008000<br>1:<br>&nbsp; .rept 4<br>&nbsp; ldmia r5!, {r0, r2, r3, r9 - r13} @ relocate kernel<br>&nbsp; stmia r1!, {r0, r2, r3, r9 - r13}<br>&nbsp; .endr</p>
<p>&nbsp; cmp r5, r8<br>&nbsp; blo 1b&nbsp; //这样就又把解压的真正内核移到了0x30008000处<br>call_kernel: bl cache_clean_flush<br>&nbsp; bl cache_off<br>&nbsp; mov r0, #0<br>&nbsp; mov r1, r7&nbsp;&nbsp; @ restore architecture number<br>&nbsp; mov pc, r4&nbsp;&nbsp; @ call kernel<br>上面就是跳到0x30008000这里去执行真正的内核了吧</p>
<br><br>
<p>arm linux 启动流程之 进入内核&nbsp;&nbsp; </p>
<p><a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#65;&#117;&#116;&#104;&#111;&#114;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#68;&#97;&#110;&#115;&#101;&#110;&#45;&#45;&#45;&#45;&#45;&#120;&#122;&#100;&#50;&#55;&#51;&#52;&#64;&#49;&#54;&#51;&#46;&#99;&#111;&#109;">Author-------Dansen-----xzd2734@163.com</a> </p>
<p>还是从编译链接生成vmlinux的过程来看吧，由一大堆.o文件链接而成，第一个就是<br>kernel\arch\arm\kernel\head-armv.o ,而且我们还看到了<br>lds链接文件kernel\arch\arm\vmlinux.lds,先把它分析一下<br>ENTRY(stext) //入口点是stext 应该就在head-armv.s中了<br>SECTIONS<br>{<br>&nbsp;. = 0xC0008000;&nbsp; //基址，是内核开始的虚拟地址<br>&nbsp;.init : {&nbsp;&nbsp; /* Init code and data&nbsp; */<br>&nbsp; _stext = .;<br>&nbsp; __init_begin = .;<br>&nbsp;&nbsp; *(.text.init)<br>&nbsp; __proc_info_begin = .;<br>&nbsp;&nbsp; *(.proc.info)<br>&nbsp; __proc_info_end = .;<br>&nbsp; __arch_info_begin = .;<br>&nbsp;&nbsp; *(.arch.info)<br>&nbsp; __arch_info_end = .;<br>&nbsp; __tagtable_begin = .;<br>&nbsp;&nbsp; *(.taglist)<br>&nbsp; __tagtable_end = .;<br>&nbsp;&nbsp; *(.data.init)<br>&nbsp; . = ALIGN(16);<br>&nbsp; __setup_start = .;<br>&nbsp;&nbsp; *(.setup.init)<br>&nbsp; __setup_end = .;<br>&nbsp; __initcall_start = .;<br>&nbsp;&nbsp; *(.initcall.init)<br>&nbsp; __initcall_end = .;<br>&nbsp; . = ALIGN(4096);<br>&nbsp; __init_end = .;<br>&nbsp;}<br>关于虚拟地址和物理地址的：使用MMU后，系统就会使用虚拟地址，通过MMU来指向<br>实际物理地址而在这里我们的0xC0008000实际物理地址就是0x30008000，<br>具体关于MMU的介绍参考《ARM体系结构与编程》。<br>到head-armv.s找到程序的入口<br>&nbsp; .section ".text.init",#alloc,#execinstr<br>&nbsp; .type stext, #function<br>ENTRY(stext)<br>&nbsp; mov r12, r0<br>&nbsp; mov r0, #F_BIT | I_BIT | MODE_SVC @ make sure svc mode<br>&nbsp; msr cpsr_c, r0&nbsp;&nbsp; @ and all irqs disabled<br>&nbsp; bl __lookup_processor_type<br>&nbsp; teq r10, #0&nbsp;&nbsp;&nbsp; @ invalid processor?<br>&nbsp; moveq r0, #'p'&nbsp;&nbsp; @ yes, error 'p'<br>&nbsp; beq __error<br>&nbsp; bl __lookup_architecture_type<br>&nbsp; teq r7, #0&nbsp;&nbsp;&nbsp; @ invalid architecture?<br>&nbsp; moveq r0, #'a'&nbsp;&nbsp; @ yes, error 'a'<br>&nbsp; beq __error<br>&nbsp; bl __create_page_tables<br>&nbsp; adr lr, __ret&nbsp;&nbsp; @ return address<br>&nbsp; add pc, r10, #12&nbsp;&nbsp; @ initialise processor<br>来看看上一句跳到哪里去了<br>去追寻r10的值，是在__lookup_processor_type子函数中赋的<br>__lookup_processor_type:<br>&nbsp; adr r5, 2f&nbsp;&nbsp; //r5 标号2的地址 基址是0x30008000<br>&nbsp; ldmia r5, {r7, r9, r10} //r7=__proc_info_end&nbsp; r9=__proc_info_begin<br>&nbsp; sub r5, r5, r10&nbsp; //r10 标号2的链接地址&nbsp;&nbsp; 基址是0xc0008000<br>&nbsp; add r7, r7, r5&nbsp;&nbsp; @ to our address space<br>&nbsp; add r10, r9, r5&nbsp; //r10 变换为基址是0x30008000的__proc_info_begin<br>2:&nbsp; .long __proc_info_end<br>&nbsp; .long __proc_info_begin<br>&nbsp; .long 2b<br>这样r10中存放的是__proc_info_begin的地址,因为现在我们还没有打开MMU<br>所以还是需要把基址变换到0x30008000,接着我们就去找__proc_info_begin吧<br>注意到在上面的vmlinux.lds中有这个标号,下来链接的是.proc.info段,<br>在kernel\arch\arm\mm\proc-arm920.s的最后找到了这个段<br>&nbsp;.section ".proc.info", #alloc, #execinstr</p>
<p>&nbsp;.type __arm920_proc_info,#object<br>__arm920_proc_info:<br>&nbsp;.long 0x41009200<br>&nbsp;.long 0xff00fff0<br>&nbsp;.long 0x00000c1e&nbsp;&nbsp; @ mmuflags<br>&nbsp;b __arm920_setup<br>ok,这样我们就知道add pc, r10, #12跳到哪里去了,因为这个地址刚好放了条跳转语句<br>注意了b语句用的都是相对地址,所以不需要变换地址,反正是跳到__arm920_setup,而且<br>上一条语句是adr lr, __ret,设定了__arm920_setup的返回地址是__ret,所以执行完<br>__arm920_setup后回到head-armv.s的__ret标号继续执行.<br>__ret:&nbsp; ldr lr, __switch_data<br>&nbsp; mcr p15, 0, r0, c1, c0 //注意这里了,在这里打开了MMU<br>&nbsp; mov r0, r0<br>&nbsp; mov r0, r0<br>&nbsp; mov r0, r0<br>&nbsp; mov pc, lr //跳到__mmap_switched,这里已经用了虚拟地址了吧<br>&nbsp;// 这条指令ldr lr, __switch_data加载的__mmap_switched地址就是虚拟地址啊<br>__switch_data: .long __mmap_switched<br>从__mmap_switched一路执行下来,就要调到C语言代码中去了<br>&nbsp; b SYMBOL_NAME(start_kernel) //在kernel\init\main.c中<br>这个程序不是特别复杂，细心看看还是能大概看懂，我也不能去一一注释<br>这里有一个流程图</p>
<p><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/dansen_xu/Drawing1.jpg"></p>
<p>到了C语言中就不是很难理解了<br>&nbsp;lock_kernel();<br>&nbsp;printk(linux_banner);<br>&nbsp;setup_arch(&amp;command_line);<br>&nbsp;printk("Kernel command line: %s\n", saved_command_line);<br>&nbsp;parse_options(command_line);<br>&nbsp;trap_init();<br>&nbsp;init_IRQ();<br>&nbsp;sched_init();<br>&nbsp;softirq_init();<br>&nbsp;time_init();<br>就是一大堆初始化工作,追着每个函数去看好了</p>
<p>start_kernel最后调用的一个函数<br>static void rest_init(void)<br>{<br>&nbsp;kernel_thread(init, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL);<br>&nbsp;unlock_kernel();<br>&nbsp;current-&gt;need_resched = 1;<br>&nbsp; cpu_idle();<br>}<br>用kernel_thread建立了一个init进程,执行的是main.c中的init函数<br>&nbsp;lock_kernel();<br>&nbsp;do_basic_setup();<br>在do_basic_setup中调用了do_initcalls函数<br>各种驱动都是在do_initcalls(void)中完成的<br>static void __init do_initcalls(void)<br>{<br>&nbsp;initcall_t *call;</p>
<p>&nbsp;call = &amp;__initcall_start;<br>&nbsp;do {<br>&nbsp; (*call)();<br>&nbsp; call++;<br>&nbsp;} while (call &lt; &amp;__initcall_end);</p>
<p>&nbsp;flush_scheduled_tasks();<br>}<br>__initcall_start也是在vmlinux.lds中赋值的,那就需要找到.initcall.ini这个段<br>在kernel\include\linux\init.h中可以找到<br>#define __init_call __attribute__ ((unused,__section__ (".initcall.init")))<br>typedef int (*initcall_t)(void);<br>#define __initcall(fn)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \<br>&nbsp;static initcall_t __initcall_##fn __init_call = fn<br>仔细研究下就发现这是把初始化函数的地址放到了.initcall.init段中<br>这样就可以不断调用驱动的初始化函数了<br>如果没有定义MODULE,那么#define module_init(x) __initcall(x);<br>所以如果要把驱动的编译进内核就很简单了吧<br>init的最后<br>&nbsp;if (execute_command)<br>&nbsp; execve(execute_command,argv_init,envp_init);<br>execute_command与ppcboot传的命令行参数是有关的哦,就是init=/linuxrc<br>这样就要去执行根目录下的linuxrc脚本,这个脚本会去执行busybox<br>而busybox又去执行/etc/init.d/rcS脚本,这个脚本又去执行/usr/etc/rc.local<br>完了</p>
<img src ="http://www.cnitblog.com/zouzheng/aggbug/40213.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/zouzheng/" target="_blank">zz</a> 2008-02-27 16:28 <a href="http://www.cnitblog.com/zouzheng/articles/40213.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>u-boot启动过程分析——基于lpc2210的移植代码</title><link>http://www.cnitblog.com/zouzheng/articles/40174.html</link><dc:creator>zz</dc:creator><author>zz</author><pubDate>Tue, 26 Feb 2008 06:51:00 GMT</pubDate><guid>http://www.cnitblog.com/zouzheng/articles/40174.html</guid><wfw:comment>http://www.cnitblog.com/zouzheng/comments/40174.html</wfw:comment><comments>http://www.cnitblog.com/zouzheng/articles/40174.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/zouzheng/comments/commentRss/40174.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/zouzheng/services/trackbacks/40174.html</trackback:ping><description><![CDATA[<p><em>作者：华清远见嵌入式培训中心（</em><em>转载请注明出处）</em>
<p>&nbsp;</p>
<div>u-boot是一种普遍用于嵌入式系统中的Bootloader。 </div>
<p>&nbsp;</p>
<div><strong><font color=#0938f7>Bootloader介绍</font></strong> </div>
<p>&nbsp;</p>
<div>Bootloader的定义：<u>Bootloader是在操作系统运行之前执行的一小段程序，通过这一小段程序，我们可以初始化硬件设备、建立内存空间的映射表，从而建立适当的系统软硬件环境，为最终调用操作系统内核做好准备</u>。意思就是说如果我们要想让一个操作系统在我们的板子上运转起来，我们就必须首先对我们的板子进行一些基本配置和初始化，然后才可以将操作系统引导进来运行。具体在Bootloader中完成了哪些操作我们会在后面分析到，这里我们先来回忆一下PC的体系结构：<u>PC机中的引导加载程序是由BIOS和位于硬盘MBR中的OS Boot Loader（比如LILO和GRUB等）一起组成的，BIOS在完成硬件检测和资源分配后，将硬盘MBR中的Boot Loader读到系统的RAM中，然后将控制权交给OS Boot Loader。Boot Loader的主要运行任务就是将内核映象从硬盘上读到RAM中，然后跳转到内核的入口点去运行，即开始启动操作系统</u>。在嵌入式系统中，通常并没有像BIOS那样的固件程序（注：有的嵌入式cpu也会内嵌一段短小的启动程序），因此整个系统的加载启动任务就完全由Boot Loader来完成。比如在一个基于ARM7TDMI core的嵌入式系统中，系统在上电或复位时通常都从地址0x00000000处开始执行，而在这个地址处安排的通常就是系统的Boot Loader程序。（<em>先想一下，通用PC和嵌入式系统为何会在此处存在如此的差异呢</em>？） </div>
<p>&nbsp;</p>
<div>Bootloader是基于特定硬件平台来实现的，因此几乎不可能为所有的嵌入式系统建立一个通用的Bootloader，不同的处理器架构都有不同的Bootloader，Bootloader不但依赖于cpu的体系结构，还依赖于嵌入式系统板级设备的配置。对于2块不同的板子而言，即使他们使用的是相同的处理器，要想让运行在一块板子上的Bootloader程序也能运行在另一块板子上，一般也需要修改Bootloader的源程序。 </div>
<p>&nbsp;</p>
<div><strong><font color=#0000ff>Bootloader的启动方式</font></strong> </div>
<p>&nbsp;</p>
<div>Bootloader的启动方式主要有网络启动方式、磁盘启动方式和Flash启动方式。 </div>
<p>&nbsp;</p>
<div><strong>1、网络启动方式</strong> </div>
<p align=center><a id=ImgSpan href="http://www.farsight.com.cn/FarsightBBS/UploadFile/2007-12/20071271619932958.gif" target=_blank><img alt=按此在新窗口浏览图片 src="http://www.farsight.com.cn/FarsightBBS/UploadFile/2007-12/20071271619932958.gif" onload="return imgzoom(this,550)" border=0></a></p>
<div><br></div>
<p align=center>图1&nbsp;&nbsp;Bootloader网络启动方式示意图
<p>&nbsp;</p>
<div>如图1所示，里面主机和目标板，他们中间通过网络来连接，首先目标板的DHCP/BIOS通过BOOTP服务来为Bootloader分配IP地址，配置网络参数，这样才能支持网络传输功能。我们使用的u-boot可以直接设置网络参数，因此这里就不用使用DHCP的方式动态分配IP了。接下来目标板的Bootloader通过TFTP服务将内核映像下载到目标板上，然后通过网络文件系统来建立主机与目标板之间的文件通信过程，之后的系统更新通常也是使用Boot Loader的这种工作模式。工作于这种模式下的Boot Loader通常都会向它的终端用户提供一个简单的命令行接口。 </div>
<p>&nbsp;</p>
<div><strong>2、磁盘启动方式</strong> </div>
<p>&nbsp;</p>
<div>这种方式主要是用在台式机和服务器上的，这些计算机都使用BIOS引导，并且使用磁盘作为存储介质，这里面两个重要的用来启动linux的有LILO和GRUB，这里就不再具体说明了。 </div>
<p>&nbsp;</p>
<div><strong>3、Flash启动方式</strong> </div>
<p>&nbsp;</p>
<div>这是我们最常用的方式。Flash有NOR Flash和NAND Flash两种。NOR Flash可以支持随机访问，所以代码可以直接在Flash上执行，Bootloader一般是存储在Flash芯片上的。另外Flash上还存储着参数、内核映像和文件系统。这种启动方式与网络启动方式之间的不同之处就在于，在网络启动方式中，内核映像和文件系统首先是放在主机上的，然后经过网络传输下载进目标板的，而这种启动方式中内核映像和文件系统则直接是放在Flash中的，这两点在我们u-boot的使用过程中都用到了。 </div>
<p>&nbsp;</p>
<div><strong><font color=#0000ff>U-boot的定义</font></strong> </div>
<p>&nbsp;</p>
<div>U-boot，全称Universal Boot Loader，是由DENX小组的开发的遵循GPL条款的开放源码项目，它的主要功能是完成硬件设备初始化、操作系统代码搬运，并提供一个控制台及一个指令集在操作系统运行前操控硬件设备。U-boot之所以这么通用，原因是他具有很多特点：开放源代码、支持多种嵌入式操作系统内核、支持多种处理器系列、较高的稳定性、高度灵活的功能设置、丰富的设备驱动源码以及较为丰富的开发调试文档与强大的网络技术支持。另外u-boot对操作系统和产品研发提供了灵活丰富的支持，主要表现在：可以引导压缩或非压缩系统内核，可以灵活设置/传递多个关键参数给操作系统，适合系统在不同开发阶段的调试要求与产品发布，支持多种文件系统，支持多种目标板环境参数存储介质，采用CRC32校验，可校验内核及镜像文件是否完好，提供多种控制台接口，使用户可以在不需要ICE的情况下通过串口/以太网/USB等接口下载数据并烧录到存储设备中去（这个功能在实际的产品中是很实用的，尤其是在软件现场升级的时候），以及提供丰富的设备驱动等。 </div>
<p>&nbsp;</p>
<div><strong><font color=#0000ff>u-boot源代码的目录结构</font></strong> </div>
<p>&nbsp;</p>
<div>1、board中存放于开发板相关的配置文件，每一个开发板都以子文件夹的形式出现。<br>2、Commom文件夹实现u-boot行下支持的命令，每一个命令对应一个文件。<br>3、cpu中存放特定cpu架构相关的目录，每一款cpu架构都对应了一个子目录。<br>4、Doc是文档目录，有u-boot非常完善的文档。<br>5、Drivers中是u-boot支持的各种设备的驱动程序。<br>6、Fs是支持的文件系统，其中最常用的是JFFS2文件系统。<br>7、Include文件夹是u-boot使用的头文件，还有各种硬件平台支持的汇编文件，系统配置文件和文件系统支持的文件。<br>8、Net是与网络协议相关的代码，bootp协议、TFTP协议、NFS文件系统得实现。<br>9、Tooles是生成U-boot的工具。 </div>
<p>&nbsp;</p>
<div>对u-boot的目录有了一些了解后，分析启动代码的过程就方便多了，其中比较重要的目录就是/board、/cpu、/drivers和/include目录，如果想实现u-boot在一个平台上的移植，就要对这些目录进行深入的分析。 </div>
<p>&nbsp;</p>
<div><strong><font color=#0000ff>u-boot的启动过程</font></strong> </div>
<p>&nbsp;</p>
<div>系统启动的入口点。既然我们现在要分析u-boot的启动过程，就必须先找到u-boot最先实现的是哪些代码，最先完成的是哪些任务。另一方面一个可执行的image必须有一个入口点，并且只能有一个全局入口点，所以要通知编译器这个入口在哪里。由此我们可以找到程序的入口点是在/board/lpc2210/u-boot.lds中指定的，其中ENTRY(_start)说明程序从_start开始运行，而他指向的是cpu/arm7tdmi/start.o文件。因为我们用的是ARM7TDMI的cpu架构，在复位后从地址0x00000000取它的第一条指令，所以我们将Flash映射到这个地址上，这样在系统加电后，cpu将首先执行u-boot程序。 </div>
<p>&nbsp;</p>
<div>u-boot的启动过程是多阶段实现的，分了两个阶段。依赖于cpu体系结构的代码（如设备初始化代码等）通常都放在stage1中，而且通常都是用汇编语言来实现，以达到短小精悍的目的。而stage2则通常是用C语言来实现的，这样可以实现复杂的功能，而且代码具有更好的可读性和可移植性。 </div>
<p>&nbsp;</p>
<div>下面我们先详细分析下stage1中的代码，如图2所示：</div>
<p align=center><a id=ImgSpan href="http://www.farsight.com.cn/FarsightBBS/UploadFile/2007-12/200712716213266446.gif" target=_blank><img alt=按此在新窗口浏览图片 src="http://www.farsight.com.cn/FarsightBBS/UploadFile/2007-12/200712716213266446.gif" onload="return imgzoom(this,550)" border=0></a></p>
<div align=center>图2&nbsp;&nbsp;Start.s程序流程 </div>
<p>&nbsp;</p>
<div align=center>代码真正开始是在_start，设置异常向量表，这样在cpu发生异常时就跳转到/cpu/arm7tdmi/interrupts中去执行相应得中断代码。在interrupts文件中大部分的异常代码都没有实现具体的功能，只是打印一些异常消息，其中关键的是reset中断代码，跳到reset入口地址。 </div>
<p>&nbsp;</p>
<div>reset复位入口之前有一些段的声明。在reset中，首先是将cpu设置为svc32模式下，并屏蔽所有irq和fiq。在u-boot中除了定时器使用了中断外，其他的基本上都不需要使用中断，比如串口通信和网络等通信等，在u-boot中只要完成一些简单的通信就可以了，所以在这里屏蔽掉了所有的中断响应。 </div>
<p>&nbsp;</p>
<div>初始化外部总线。这部分首先设置了I/O口功能，包括串口、网络接口等的设置，其他I/O口都设置为GPIO。然后设置BCFG0~BCFG3，即外部总线控制器。这里bank0对应Flash，设置为16位宽度，总线速度设为最慢，以实现稳定的操作；Bank1对应DRAM，设置和Flash相同；Bank2对应RTL8019。 </div>
<p>&nbsp;</p>
<div>接下来是cpu关键设置，包括系统重映射（告诉处理器在系统发生中断的时候到外部存储器中去读取中断向量表）和系统频率。 </div>
<p>&nbsp;</p>
<div>lowlevel_init，设定RAM的时序，并将中断控制器清零。这些部分和特定的平台有关，但大致的流程都是一样的。 </div>
<p>&nbsp;</p>
<div>下面就是代码的搬移阶段了。为了获得更快的执行速度，通常把stage2加载到RAM空间中来执行，因此必须为加载Boot Loader的stage2准备好一段可用的RAM空间范围。空间大小最好是memory page大小(通常是4KB)的倍数，一般而言，1M的RAM空间已经足够了。flash中存储的u-boot可执行文件中，代码段、数据段以及BSS段都是首尾相连存储的，所以在计算搬移大小的时候就是利用了用BSS段的首地址减去代码的首地址，这样算出来的就是实际使用的空间。程序用一个循环将代码搬移到0x81180000，即RAM底端1M空间用来存储代码。然后程序继续将中断向量表搬到RAM的顶端。由于stage2通常是C语言执行代码，所以还要建立堆栈去。在堆栈区之前还要将malloc分配的空间以及全局数据所需的空间空下来，他们的大小是由宏定义给出的，可以在相应位置修改。基本内存分布图：<br></div>
<p align=center><a id=ImgSpan href="http://www.farsight.com.cn/FarsightBBS/UploadFile/2007-12/20071271622022868.gif" target=_blank><img alt=按此在新窗口浏览图片 src="http://www.farsight.com.cn/FarsightBBS/UploadFile/2007-12/20071271622022868.gif" onload="return imgzoom(this,550)" border=0></a></p>
<div align=center><br><br>图3&nbsp;&nbsp;搬移后内存分布情况图 </div>
<p>&nbsp;</p>
<div>接下来是u-boot启动的第二个阶段，是用c代码写的，这部分是一些相对变化不大的部分，我们针对不同的板子改变它调用的一些初始化函数，并且通过设置一些宏定义来改变初始化的流程，所以这些代码在移植的过程中并不需要修改，也是错误相对较少出现的文件。在文件的开始先是定义了一个函数指针数组，通过这个数组，程序通过一个循环来按顺序进行常规的初始化，并在其后通过一些宏定义来初始化一些特定的设备。在最后程序进入一个循环，main_loop。这个循环接收用户输入的命令，以设置参数或者进行启动引导。 </div>
<p>&nbsp;</p>
<div>本篇文章将分析重点放在了前面的start.s上，是因为这部分无论在移植还是在调试过程中都是最容易出问题的地方，要解决问题就需要程序员对代码进行修改，所以在这里简单介绍了一下start.s的基本流程，希望能对大家有所帮助。</div>
<img src ="http://www.cnitblog.com/zouzheng/aggbug/40174.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/zouzheng/" target="_blank">zz</a> 2008-02-26 14:51 <a href="http://www.cnitblog.com/zouzheng/articles/40174.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>U-BOOT分析</title><link>http://www.cnitblog.com/zouzheng/articles/40173.html</link><dc:creator>zz</dc:creator><author>zz</author><pubDate>Tue, 26 Feb 2008 06:49:00 GMT</pubDate><guid>http://www.cnitblog.com/zouzheng/articles/40173.html</guid><wfw:comment>http://www.cnitblog.com/zouzheng/comments/40173.html</wfw:comment><comments>http://www.cnitblog.com/zouzheng/articles/40173.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/zouzheng/comments/commentRss/40173.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/zouzheng/services/trackbacks/40173.html</trackback:ping><description><![CDATA[我之前发在 linuxforum上面， 不过讨论的人比较少 ， 就贴在这里了。 <br>具体可见：
<p style="MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; LINE-HEIGHT: 150%">http://www.linuxforum.net/forum/showflat.php?Cat=&amp;Board=embedded&amp;Number=651003&amp;page=0&amp;view=collapsed&amp;sb=5&amp;o=0&amp;fpart=1#Post651082 <br><br>再读bootm源码，现象正常！ <br><br>呵呵 ， 原来 tftp download到是否是 hdr-&gt;ih_load(0x30008000) 确实是有说道的。 <br>原来对bootm源码有个地方理解错了， bootm 会根据 hdr-&gt;ih_comp 判断是否需要解压， 对于<br>mkimage -C none 的是不需要解压的。 <br><br>1&gt; mkimage -A arm -O linux -T kernel -C none -a 30008000 -e <br>30008040 -n linux-2.6.18.8 -d zImage uImage2.6.18.8-8040 <br>这种情况 ，只能把 uImage download到 30008000的位置上 ，否则 从 30008040<br>是启动不了的。 <br>2&gt; mkimage -A arm -O linux -T kernel -C none -a 30008000 -e <br>30008000 -n linux-2.6.18.8 -d zImage uImage2.6.18.8-8000 <br>这种情况download地址随便。 <br>如果 tftp 下载地址==0x30008000 ， 就从 0x30008040 启动就肯定OK 。 <br>详细的请看代码。 <br>研究了一下 u-boot-1.2.0 里面的 bootm的实现代码： do_bootm_linux() 函数 ，<br>原来由于我用mkimage的的时候的选项是 -C none ， 所以下面的判断中 hdr-&gt;ih_comp ＝ <br>IH_COMP_NONE <br>通过再读一遍代码， 也弄明白了上次问的问题， 就是 u－boot里面的解压和<br>内核自解压的区别： u-boot 里面的解压实际上是bootm 实现的 ， 把 mkimage -C bzip2<br>或者gzip 生成的 uImage进行解压 ； 而kernel的自解压是对zImage进行解压，<br>发生在bootm解压之后。 <br><br>呵呵 ， 原来 tftp download到是否是 hdr-&gt;ih_load(0x30008000) 确实是有说道的。 <br>原来对bootm源码有个地方理解错了， bootm 会根据 hdr-&gt;ih_comp 判断是否需要解压， 对于<br>mkimage -C none 的是不需要解压的。 <br><br>1&gt; mkimage -A arm -O linux -T kernel -C none -a 30008000 -e <br>30008040 -n linux-2.6.18.8 -d zImage uImage2.6.18.8-8040 <br>这种情况 ，只能把 uImage download到 30008000的位置上 ，否则 从 30008040<br>是启动不了的。 <br>2&gt; mkimage -A arm -O linux -T kernel -C none -a 30008000 -e <br>30008000 -n linux-2.6.18.8 -d zImage uImage2.6.18.8-8000 <br>这种情况download地址随便。 <br>如果 tftp 下载地址==0x30008000 ， 就从 0x30008040 启动就肯定OK 。 <br>详细的请看代码。 <br>研究了一下 u-boot-1.2.0 里面的 bootm的实现代码： do_bootm_linux() 函数 ，<br>原来由于我用mkimage的的时候的选项是 -C none ， 所以下面的判断中 hdr-&gt;ih_comp ＝ <br>IH_COMP_NONE <br>通过再读一遍代码， 也弄明白了上次问的问题， 就是 u－boot里面的解压和<br>内核自解压的区别： u-boot 里面的解压实际上是bootm 实现的 ， 把 mkimage -C bzip2<br>或者gzip 生成的 uImage进行解压 ； 而kernel的自解压是对zImage进行解压，<br>发生在bootm解压之后。 <br><br>switch (hdr-&gt;ih_comp) {<br>case IH_COMP_NONE:<br>if(ntohl(hdr-&gt;ih_load) == addr) { //如果你是tftp 到 0x30008000那么这里就命中了。 <br>printf (" XIP %s ... ", name); //tftp download 这里， 确实打印出来了。 <br>} else { //否则随便download到一个地址， 流程就进入这里了。 <br>#if defined(CONFIG_HW_WATCHDOG) || defined(CONFIG_WATCHDOG) //这个条件编译不成立<br>size_t l = len;<br>void *to = (void *)ntohl(hdr-&gt;ih_load); <br>void *from = (void *)data; <br>printf (" Loading %s ... ", name); <br>while (l &gt; 0) {<br>size_t tail = (l &gt; CHUNKSZ) ? CHUNKSZ : l;<br>WATCHDOG_RESET();<br>memmove (to, from, tail);<br>to += tail;<br>from += tail;<br>l -= tail;<br>}<br>#else/* !(CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG) */<br>------------------------实际执行的是这里---------------------------------<br>memmove ((void *) ntohl(hdr-&gt;ih_load), (uchar *)data, len); <br>//从这里就可以看出一旦 tftpdownload到一个任意地址， bootm 都会把<br>//它（去掉header后的kernel搬运到0x30008000 的位置上，因此entry <br>//point 肯定要是 0x30008000 ，但是对于tftp 恰好download到 <br>//0x30008000 的位置上的时候， 你会发现上面的代码中，就donothing <br>//了，因此就必须从 0x30008040 <br><br>#endif/* CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG */<br>}<br>break;<br>case IH_COMP_GZIP:<br>printf (" Uncompressing %s ... ", name);<br>if (gunzip ((void *)ntohl(hdr-&gt;ih_load), unc_len, // 把它解压到ih_load的位置上去<br>(uchar *)data, &amp;len) != 0) {<br>puts ("GUNZIP ERROR - must RESET board to recover\n");<br>SHOW_BOOT_PROGRESS (-6);<br>do_reset (cmdtp, flag, argc, argv);<br>}<br>break;<br>.....<br>省略了 gzip和 bzip2的处理 ， // 这里也说明 和 zImage的自解压是不一样的。 <br>gzip : mkimage -C gzip ; <br>bzip2 mkimage -C bzip2 . <br>他是只uImage 本身被压缩了 <br>default:<br>if (iflag)<br>enable_interrupts();<br>printf ("Unimplemented compression type %d\n", hdr-&gt;ih_comp);<br>SHOW_BOOT_PROGRESS (-7);<br>return 1;<br>}<br>//解压完毕，对于我得板子来说，是解压到 0x8000的位置上去了，这是个物理地址<br>puts ("OK\n");<br>SHOW_BOOT_PROGRESS (7);</p>
<img src ="http://www.cnitblog.com/zouzheng/aggbug/40173.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/zouzheng/" target="_blank">zz</a> 2008-02-26 14:49 <a href="http://www.cnitblog.com/zouzheng/articles/40173.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>u-boot 分析 - &lt;节选&gt; [嵌入式Linux系统开发技术详解-基于ARM]</title><link>http://www.cnitblog.com/zouzheng/articles/40172.html</link><dc:creator>zz</dc:creator><author>zz</author><pubDate>Tue, 26 Feb 2008 06:45:00 GMT</pubDate><guid>http://www.cnitblog.com/zouzheng/articles/40172.html</guid><wfw:comment>http://www.cnitblog.com/zouzheng/comments/40172.html</wfw:comment><comments>http://www.cnitblog.com/zouzheng/articles/40172.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/zouzheng/comments/commentRss/40172.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/zouzheng/services/trackbacks/40172.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 6.1 &nbsp;Bootloader对于计算机系统来说，从开机上电到操作系统启动需要一个引导过程。嵌入式Linux系统同样离不开引导程序，这个引导程序就叫作Bootloader。6.1.1&nbsp; Bootloader介绍Bootloader是在操作系统运行之前执行的一段小程序。通过这段小程序，我们可以初始化硬件设备、建立内存空间的映射表，从而建立适当的系统软硬件环境，为最终调...&nbsp;&nbsp;<a href='http://www.cnitblog.com/zouzheng/articles/40172.html'>阅读全文</a><img src ="http://www.cnitblog.com/zouzheng/aggbug/40172.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/zouzheng/" target="_blank">zz</a> 2008-02-26 14:45 <a href="http://www.cnitblog.com/zouzheng/articles/40172.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>QT技巧 </title><link>http://www.cnitblog.com/zouzheng/articles/39330.html</link><dc:creator>zz</dc:creator><author>zz</author><pubDate>Fri, 25 Jan 2008 13:03:00 GMT</pubDate><guid>http://www.cnitblog.com/zouzheng/articles/39330.html</guid><wfw:comment>http://www.cnitblog.com/zouzheng/comments/39330.html</wfw:comment><comments>http://www.cnitblog.com/zouzheng/articles/39330.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/zouzheng/comments/commentRss/39330.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/zouzheng/services/trackbacks/39330.html</trackback:ping><description><![CDATA[Qt&amp;Kdevelop技巧集(原创)<br>发表：2004-1-21 13:39:23 出处：你的博客网(yourblog.org)<br>1． 如何在Qt程序中加入OpenGL支持。<br>在QT程序中加入OpenGL支持很简单，只需要在Kdevelop连接的库中加入&#8220;-lGL -lGLU&#8221;即可，如果需要glut支持，还可以加入&#8220;-lglut&#8221;。具体操作是在kdevelop集成编译环境中按下&#8221;F7&#8221;，在弹出的对话框中选择 &#8220;Linker&#8221;一项，在输入栏输入你想添加的库即可，写法与gcc/g++一致。<br>一般在类QGLWidget中使用OpenGL,调用此类的头文件是qgl.h,具体写法请参考qt例程中的gear,texture,box等程序(在RedHat7.2中,它们在/usr/lib/qt-2.3.1/doc/examples下).<br><br>2． 检验linux/Unix环境是否支持OpenGL.<br>Qt中的QGLFormat类可以帮助我们轻易检验系统是否支持OpenGL，载入头文件（#include &lt;qgl.h&gt;）后，我们就可以使用QGLFormat的静态函数hasOpenGL来检验，具体写法如下例：<br>if (!QGLFormat::hasOpenGL()) //Test OpenGL Environment<br>{<br>qWarning( "This system has no OpenGL support. Exiting." );//弹出警告对话框<br>return -1;<br>}<br><br>3.获得屏幕的高和宽.<br>一般我们可以通过QT的Qapplication类来获得系统的一些信息,载入头文件(#include &lt;qapplication.h&gt;)我们就可以调用它,下例是使主程序充满整个屏幕的代码:<br>Gui_MainForm gui_mainform;<br>a.setMainWidget( &amp;gui_mainform );<br>gui_mainform.resize( QApplication::desktop()-&gt;width(), QApplication::desktop()-&gt;height() ); gui_mainform.show();<br><br>4.关于信号和槽.<br>信号和槽机制是QT库的重要特性,可以说不了解它就不了解Qt.此机制能在各类间建立方便快捷的通信联系,只要类中加载了Q_OBJECT宏并用 connect函数正确连接在一起即可,具体写法这里就不赘述了.但本人在使用过程中发现使用此机制容易破坏程序的结构性和封装性,速度也不是很让人满意,尤其是在跨多类调用时.鄙人的一孔之见是: 信号和槽机制不可不用,但不可多用.<br><br>5.QT程序中界面的设计.<br>尽管Kdevelop是一个优秀的集成编译环境,可遗憾的是它不是一个可视化的编译环境,好在有Qdesigner来帮助我们完成界面设计,该程序的使用很简单,使用过VB,VC和Delphi的程序员能很快其操作方式,操作完成后存盘会生成一个扩展名为&#8221;ui&#8221;的文件,你接下来的任务就是把它解析成 cpp和h文件,假设文件名为myform.ui,解析方法如下:<br>$uic myform.ui &#8211;I myform.h &#8211;o myform..cpp //这句生成cpp文件<br>$uic myform.ui &#8211;o myform.h //这句生成h文件.<br><br>6.由pro文件生成Makefile.<br>对于Linux/Unix程序员来说编写Makefile文件是一项令人烦恼的任务,而qt程序员就没有这样的烦恼,一句$qmake &#8211;o Makefile myprogram.pro就可以轻松愉快的完成任务,而pro文件的编写也很容易,其核心是h和cpp文件的简单列表.具体写法请参考一下qt自带的样例和教程吧(在RedHat7.2中,它在/usr/lib/qt-2.3.1/doc/examples下),相对Makefile文件简直没有什么难度.<br><br>7.主组件的选择.<br>一般我们在编程是使用继承Qwidget类的类作为主组件,这当然未可厚非.但在制作典型的多文档和单文档程序时我们有更好的选择— QmainWindow类,它可以方便的管理其中的菜单工具条主窗口和状态条等,在窗体几何属性发生变化时也能完美的实现内部组件缩放,这比用传统的几何布局类来管理要方便得多,而且不用写什么代码.关于它的具体细节请查阅QT的帮组文档,这里就不赘述了.<br><br>8.菜单项中加入Checked项.<br>在QT中,菜单项中加入Checked有点麻烦,具体写法如下:<br>1&gt; 定义int型成员变量,并在创建菜单项中写:<br>displayGeometryMode=new QPopupMenu(this); //这里创建弹出菜单组displayGeometryMode<br>m_menuIDWire=displayGeometryMode-&gt;insertItem("Wire",this,SLOT(slt_Change2WireMode()));.//创建弹出菜单子项<br>displayGeometryMode-&gt;setItemChecked(m_ menuIDWire,true);//设定此子项为选择状态<br><br>2&gt; 再在槽函数中写:<br>displayGeometryMode-&gt;setItemChecked(m_menuIDWire,false);//这里设定此子项为非选择状态<br><br>9.截获程序即将退出的信号.<br>有些时候我们需要在程序即将退出时进行一些处理,如保存文件等等.如何截获程序退出的信号呢?还是要用到Qapplication类的aboutToQuit()信号,程序中可以这样写:<br>connect(qApp,SIGNAL(aboutToQuit()),this,SLOT(Slot_SaveActions()));<br>在槽函数Slot_SaveActions()就可以进行相关处理了,注意,使用全局对象qApp需要加载头文件(#include &lt;qapplication.h&gt;).<br><br>10.弹出标准文件对话框.<br>在程序中弹出文件对话框是很容易处理的,举例如下:<br>QString filter="Txt files(*.txt)\n" //设置文件过滤,缺省显示文本文件<br>"All files(*)" ; //可选择显示所有文件<br>QString Filepathname=QFileDialog::getOpenFileName(" ",filter,this);//弹出对话框,这句需要加载头文件(#include &lt; qfiledialog.h &gt;)<br><br><br>11.将当前日期时间转化为标准Qstring.<br>QDateTime currentdatetime =QDateTime::currentDateTime();//需要加载头文件(#include &lt; qdatetime.h &gt;)<br>QString strDateTime=currentdatetime.toString();<br><br>12.设置定时器<br>所有Qobject的子类在设置定时器时都不必加载一个Qtimer对象,因为这样造成了资源浪费且需要书写多余的函数,很不方便.最好的办法是重载timerEvent函数,具体写法如下:<br>class Gui_DlgViewCtrlDatum : public QDialog<br>{<br>Q_OBJECT<br>public:<br>Gui_DlgViewCtrlDatum( QWidget* parent = 0, const char* name = 0, bool modal = FALSE, WFlags fl = 0 );<br>~Gui_DlgViewCtrlDatum();<br>protected:<br>void timerEvent( QTimerEvent * );<br>};<br>void Gui_DlgViewCtrlDatum::timerEvent( QTimerEvent *e )<br>{<br>//statements<br>}<br>再在Gui_DlgViewCtrlDatum的构造函数中设置时间间隔:<br>startTimer(50);//单位为毫秒<br><br>这样,每隔50毫秒,函数timerEvent便会被调用一次.<br><br>13.最方便的几何布局类QGridLayout<br>在QT的几何布局类中,笔者认为QgridLayout使用最为方便,举例如下:<br>QGridLayout* layout=new QGridLayout(this,10,10);//创建一个10*10的QgridLayout实例<br>layout-&gt;addMultiCellWidget(gui_dlgslab_glwnd,1,8,0,7);//将OpenGL窗口固定在QgridLayout中的(1,0)单元格到(8,7)单元格中<br>layout-&gt;addMultiCellWidget(Slider1,0,9,8,8);//将一个slider固定在单元格(0,8)到(9,8)中<br>layout-&gt;addWidget(UpLimitLbl,1,9);//将一个label(UpLimitLbl)固定在单元格(1,9)中<br>这样,无论窗体大小如何改变,它们的布局方式都不会发生改变,这比反复使用QvboxLayout和QhboxLayout要方便快捷许多.<br>注:使用几何布局类需要调用头文件(#include &lt;qlayout.h&gt;)<br><br>14.字符串类Qstring和字符串链表类QstringList.<br>Qstring是Qt中标准字符串类,下面列出它的一些常用函数:<br>toInt():将字符串转化成int类型.<br>ToFloat():将字符串转化成float类型.<br>ToDouble():将字符串转化成double类型.<br>Left(n):从左起取n个字符<br>Right(n):从右起取n个字符<br>SetNum(n):将实数n(包括int,float,double等)转化为Qsting型.<br><br>QstringList是大家比较少使用的类,它可以看成Qstring组成的链表(QT中标准链表类Qlist的函数对它都适用,它的单个节点是Qstring类型的),特别适合与处理文本,下面一段代码就可见其方便快捷:<br>Qstring strtmp=&#8221;abc|b|c|d&#8221;;<br>QstringList strlsttmp;<br>Strlsttmp =QStringList::split("|", strtmp);<br>For(unsigned int I=0;I&lt; Strlsttmp.count();I++)<br>{<br>cout&lt;&lt; Strlsttmp.at(I);<br>}<br>结果输出为:abc b c d,也就是说,通过一个函数split,一行文本就被符号&#8221;|&#8221;自动分割成了单个字符串.这在文本处理时特别省力.(请参考c语言大全第四版中用&#8221;strtok&#8221;函数分割文本的例程,将双方比较一下)<br><br>15. QGLWidget类如何加入鼠标支持.<br>QGLWidget类加入鼠标支持需要重载以下函数:<br>void mousePressEvent(QMouseEvent*);<br>void mouseMoveEvent(QMouseEvent*);<br>void mouseReleaseEvent(QMouseEvent*);<br>请具体看一个实例:<br>class Gui_WgtMain_GLWnd : public QGLWidget {<br>Q_OBJECT<br>public:<br>Gui_WgtMain_GLWnd(QWidget *parent=0, const char *name=0);<br>~Gui_WgtMain_GLWnd();<br>protected:<br>void initializeGL();<br>void paintGL();<br>void resizeGL( int w, int h );<br>void mousePressEvent(QMouseEvent*);<br>void mouseMoveEvent(QMouseEvent*);<br>void mouseReleaseEvent(QMouseEvent*);<br>private:<br>int m_nCnt;<br>};<br>void Gui_WgtMain_GLWnd::mousePressEvent(QMouseEvent* e)<br>{<br>//statements<br>}<br>void Gui_WgtMain_GLWnd:: mouseMoveEvent (QMouseEvent* e)<br>{<br>//statements<br>}<br>void Gui_WgtMain_GLWnd:: mouseReleaseEvent (QMouseEvent* e)<br>{<br>//statements<br>}<br>其中, e-&gt;x();e-&gt;y();可以获得鼠标的位置, e-&gt;button()可以取得鼠标按键的状态(左中右键以及ctrl,alt,shift等组合键),灵活使用他们就可以在用鼠标操作OpenGL画面了.<br><br>16.由ui文件生成.h和.cpp文件<br>生成.cpp文件<br>$uic myform.ui -i myform.h -o myform.cpp<br><br>生成.h文件<br>$uic myform.ui -o myform.h<br>
<img src ="http://www.cnitblog.com/zouzheng/aggbug/39330.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/zouzheng/" target="_blank">zz</a> 2008-01-25 21:03 <a href="http://www.cnitblog.com/zouzheng/articles/39330.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Arch Linux 中文化－字体设置</title><link>http://www.cnitblog.com/zouzheng/articles/39329.html</link><dc:creator>zz</dc:creator><author>zz</author><pubDate>Fri, 25 Jan 2008 13:01:00 GMT</pubDate><guid>http://www.cnitblog.com/zouzheng/articles/39329.html</guid><wfw:comment>http://www.cnitblog.com/zouzheng/comments/39329.html</wfw:comment><comments>http://www.cnitblog.com/zouzheng/articles/39329.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/zouzheng/comments/commentRss/39329.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/zouzheng/services/trackbacks/39329.html</trackback:ping><description><![CDATA[<strong>前言 ：<br><br></strong>Arch Linux 是一个定制性很强的发行版，其设计的哲学决定了Arch Linux 不太可能像SUSE、Fedora、Mandriva等发行版一样预设了一套完善的字体配置。Arch Linux需要用户进行一定的定制，用户在定制、配置过程中能学到更多的知识，这也是Arch Linux的设计哲学之一。 <br><br><strong>一 .字体相关库的简介</strong><br><br><strong>1 . LibXft </strong><br><br>libXft库是为了给X应用程序提供一个能访问FreeType字体光栅化引擎和X渲染扩展的、便于使用的接口，鉴于FreeType没有提供配置和定制 字体的功能，Xft也担负了这一任务。Xft提供了新的字体命名约定、复杂而精密的字体匹配和选择机制，并对相关功能进行充分的抽象，从而使得一般应用程 序既能够从使用X渲染扩展的文本输出获得益处，又能在不支持这一扩展的X服务器上正常工作。<br><br>libXft 是画字函数库, 它使用 Fontconfig match 到了所要的字型之后, 来决定该如何画这些字。libXft 会看情况而决定要不要使用 core protocol 或 XRender 来画字。libXft 主要作GTK 2 （GTK &lt; 2.8.0）程序，QT 程序的画字函数库。 <br><br><strong>2 .Cairo</strong><br><br>一个支持多种输出的向量图形库,也就是说，cairo是种画图的工具库，他可以向多种设备上画图.可以输出到pdf,ps，xlib，XCB，win32，svg。基于GTK （ = &gt; 2.8.0)的程序用cairo 画字而不再使用libXft 。<br><br><strong>3 .Fontconfig</strong><br><br>Fontconfig 包含两个基本的模块，即读取XML文件并建立内部配置的配置模块和接受请求的字体样板并返回最接近所需字体的匹配模块。<br><br><strong>4 .Freetype</strong><br><br>Freetype FreeType库是一个完全免费 ( 开源 ) 的、高质量的且可移植的字体<br>引擎，它提供统一的接口来访问多种字体格式文件，包括TrueType, OpenType, Type1, CID, CFF, Windows FON/FNT,X11 PCF 等。支持单色位图 (homochromous bitmap) 、反走样位图（ anti-alias bitmap ）的渲染。 Freetype 是高度模块化设计的程序库。Freetype 提供 libXft／cairo 如何画字的信息，包括处理 anti-aliasing 或 hinting. 因此 freetype 的改变会影响到 libXft/cairo 画出来的字，而 Fontconfig 的改变会影响到 libXft／cario 如何去选字来画。 <br><br><strong>5 .Pango </strong><br><br>Pango 是个开源的整合到GTK+2的渲染国际化文本文件的库，pango 的名字是由 希腊语 "Pan" (&#928;&#945;&#957;; lit. all) + 日语 "Go" (語; lit. language)组合成。也就是All language，Pango负责多国语言文本(如一个文本文件中由英文，日文，中文等不同的语系组成）的渲染。<br><br><br><font size=3><strong>二 .基本概念</strong></font><br><br><strong>1. 点阵字体 与 矢量字体</strong><br><br><strong>点阵字体</strong>也叫位图字体，其中每个字形都以一组二维像素 信息表示。由于位图的原故，点阵字体很难进行缩放，特定的点阵字体只能清晰地显示在相应的字号下。但对于 12-16px 这样小的汉字，点阵字体常常比其它类型的字体在屏幕上更好的显示效果。常见的点阵字体有 bdf，pcf，fnt，hbf 等格式。 <br>对于常见的计算机操作系统，字体的显示算法需要一些字体的信息来优化屏幕显示效果，英文称为 hinting。汉字由于笔画复杂，所以 hinting 的方式与西文截然不同。使用在汉字字体中嵌入预先制作的点阵位图既可以有效地避免 hinting 算法带来的计算开销，同时屏幕上显示的汉字边缘清晰，易于阅读。下面就是使用点阵显示和不使用点阵显示的两个例子： <br><br><strong>未使用点阵中文的显示效果 </strong><br><img style="CURSOR: pointer" onclick=javascript:window.open(this.src); alt="" src="http://img156.imageshack.us/img156/9267/nohint7qk.png" onload="return imgzoom(this,550);" border=0> <br><br><strong>使用点阵中文的显效果</strong> <br><img style="CURSOR: pointer" onclick=javascript:window.open(this.src); alt="" src="http://img100.imageshack.us/img100/7366/hinting3ik.png" onload="return imgzoom(this,550);" border=0><br><br><strong>矢量字体</strong>中每一个字形是通过数学曲线来描述的，它包含了字形边界上的关键点，连线的导数信息等，字体的渲染引擎通过读取这些数学矢量，然后 进行一定的数学运算来进行渲染。这类字体的好处是字体可以无限放大而不产生变形。矢量字体主要包括 Type1 和 TrueType 等几类。<br><br><strong>2 .内嵌点阵字的字体 </strong><br><br>由于CJK字体的结构复杂，在小号字体只有使用点阵才能有清晰的显示效果，但是点阵字体很难进行缩，而且线条单一，在大号字体显示时就显得苍白无力。把点阵字体内嵌到矢量字体就能发挥两者的优点，并且也弥补了两者的不足。小号字体时清晰明朗，大号字体时字型饱满，形态丰富。常见的内嵌点阵字的字体有：北京中易电子公司宋体SimSun 、方正宋体（FZSongti）、文鼎PL上海宋（Uming）、Firefly的文鼎ＰＬ新宋（AR PL New Sung）等。<br><br><strong>3 . Hinting 与 Autohinter</strong><br><br>Hinting 用来最佳化字型显示的方法。由于屏幕的像素有限，向量字型的缩放需要有更多的考虑, 例如当一条线位在两个像素格子中间时, 该取左边的格子还是右边的格子? 如果这方面的控制没有做好，就常常会出现字型的衬线没有对齐，或是小字歪七扭八的情况。 Hinting 是额外的信息, 它告诉 renderer 该如何处理这些细节的部份，使得向量字在小字的时候能够好看。也因此 Hinting 是非常费时费人力的工作，TrueType 字型很多，但是有良好 Hinting 的字型不多。拙劣的 Hinting 就会让字变得很难看。<br><br>为了稍微改善这个问题，freetype 有 autohint 的功能，可以自动为没有 hint 的字型做 hinting 的工作。另外由于 TrueType 的 hinting 是有专利的，不能完全自由地使用， autohint 就不受这个限制。autohint 自然无法做得像人力的 hint 一样好，不过至少比没有 hint 要好些。话虽如此，对于许多笔划复杂的文字 (如中文) 目前 freetype 的 autohint 还做得不甚完美，而因为建立完整的 hinting 的难度，即使是英文字，原本就很高，内建有 hinting 的中文字型就少之又少了。所以常常有人抱怨中文字在屏幕上很难看，就是没有理想 hinting, 或者是使用了 autohinter 所造成的一些反效果。<br><br><strong>4 . AA（Antialiasing）</strong><br><br>Antialiasing 是将字体在后台先以数倍的大小来绘画，然后再缩成想要的大小，未满一格的格子用灰阶补点。<br><br>Antialiasing 会给人一种朦胧的感觉，习惯了Windows XP 下清晰，锐利的字体显示时，Antialiasing 会让人不太适应，会让用户觉得Linux下的字体显示不如Windows XP。但是，其实 Anti-aliasing是一种很先进的显示技术 ，当长时间显示器上阅读的时候，Windows XP 下的锐利的字体显示风格，会让眼睛更加容易疲劳，使用Antialiasing，字体的显示更加柔和，更加适合长时间的在显示器上阅读文档，减少眼睛的疲劳。<br><br>微软的下一代OS Windows Vista 也将用ClearType 作为默认的显示效果，来取传统的黑白，锐利的英文字体显示和点阵CJK 显示。看！ClearType是不是和Linux下的 Anti-aliasing 效果很相似。:-D<br><br><img style="CURSOR: pointer" onclick=javascript:window.open(this.src); alt="" src="http://www.qcode.org/linux/UploadPic/2007/11/23/20071123132233800.jpg" onload="return imgzoom(this,550);" border=0><br><img style="CURSOR: pointer" onclick=javascript:window.open(this.src); alt="" src="http://img156.imageshack.us/img156/3976/aaat5rq.png" onload="return imgzoom(this,550);" border=0><br><br><strong>Autohint+antialiasing</strong><br><br><strong><font size=3>三 .字体简介</font></strong><br><br><strong>1 自由(free)的英文字体</strong><br>itstream vera fonts,DejaVu fonts[community repo]，MS True Type core fonts 等，DejaVu fonts是在Bitstream vera fonts release 1.10 的基础上进行不断扩充的字体。 MS True Type core fonts包含了Arial， Courier New， Times New Roman， Verdana等系列字体的总称。 MS True Type core fonts 的licence 是EULA，可以合法的免费使用。<br><br><strong>2 自由中文字体</strong><br><br>ttf-arphic-ukai<br>ttf-arphic-uming<br>ttf-fireflysung<br><br><strong>3 不合版权的字体</strong><br><br>LingSong ：Tahoma+Simsun的&#8220;杂交&#8221;字体，Tahoma版权属于微软，Simsun字体的版权属于 北京中易电子公司。 <br><br>Vera Sans YuanTi：Bitstream Vera+simsun点阵+方正准圆+方正粗圆的&#8220;杂交&#8221;字体。<br><br><strong><font size=3>四 .设置实例</font></strong><br><br><strong>1 开启/关闭 AA ，Autohint 与 Hinting</strong><br><br>AA 与 Autohint/Hinting配搭的效果示范图：<br><br><strong>Hinting-only</strong><br><img style="WIDTH: 438px; CURSOR: pointer" onclick=javascript:window.open(this.src); alt="" src="http://img156.imageshack.us/img156/2356/hintingonly0lr.png" onload="return imgzoom(this,550);" border=0><br><br><strong>Autohint-only</strong><br><img style="WIDTH: 438px; CURSOR: pointer" onclick=javascript:window.open(this.src); alt=��ͼƬ�ѱ���С�������鿴ԭ��СͼƬ�� src="http://img156.imageshack.us/img156/9067/autohitonly4ss.png" width=500 onload="return imgzoom(this,550);" border=0><br><br><strong>Hinting+Antialiasing</strong><br><img style="WIDTH: 438px; CURSOR: pointer" onclick=javascript:window.open(this.src); alt="" src="http://img100.imageshack.us/img100/3121/hintingantialias5as.png" onload="return imgzoom(this,550);" border=0><br><br><strong>Autohint+Antialiasing</strong><br><img style="WIDTH: 437px; CURSOR: pointer" onclick=javascript:window.open(this.src); alt="" src="http://img100.imageshack.us/img100/8963/autohintantialias8sa.png" onload="return imgzoom(this,550);" border=0><br><br><br>下面的配置表示：全部的字体使用autohint 和 antialiasing，渲染度为 hintfull ，通常这是一个很好的全局默认设置。也可以根据个人喜好使用渲染度为hintslight。<br>
<div style="MARGIN: 5px 20px 20px 0px">
<div class=smallfont style="MARGIN-BOTTOM: 2px">代码:</div>
<pre class=alt2 style="BORDER-RIGHT: #c6c6c6 1px solid; PADDING-RIGHT: 4px; BORDER-TOP: #c6c6c6 1px solid; PADDING-LEFT: 4px; PADDING-BOTTOM: 4px; MARGIN: 0px; OVERFLOW: auto; BORDER-LEFT: #c6c6c6 1px solid; WIDTH: 640px; PADDING-TOP: 4px; BORDER-BOTTOM: #c6c6c6 1px solid; HEIGHT: 194px">
<div dir=ltr style="TEXT-ALIGN: left">       &lt;match target="font"&gt;                &lt;edit name="autohint"&gt;                        &lt;bool&gt;true&lt;/bool&gt;                &lt;/edit&gt;                &lt;edit name="hintstyle"&gt;                        &lt;const&gt;hintfull&lt;/const&gt;                &lt;/edit&gt;                &lt;edit name="antialias"&gt;                        &lt;bool&gt;true&lt;/bool&gt;                &lt;/edit&gt;        &lt;/match&gt;</div>
</pre>
</div>
下面的配置就是代表的是：小于16号的MS core fonts,关闭Antialiasing 和 Autohint，(使用hinting) 。可以根据个人喜欢调节字号的大小。<br>
<div style="MARGIN: 5px 20px 20px 0px">
<div class=smallfont style="MARGIN-BOTTOM: 2px">代码:</div>
<pre class=alt2 style="BORDER-RIGHT: #c6c6c6 1px solid; PADDING-RIGHT: 4px; BORDER-TOP: #c6c6c6 1px solid; PADDING-LEFT: 4px; PADDING-BOTTOM: 4px; MARGIN: 0px; OVERFLOW: auto; BORDER-LEFT: #c6c6c6 1px solid; WIDTH: 640px; PADDING-TOP: 4px; BORDER-BOTTOM: #c6c6c6 1px solid; HEIGHT: 498px">
<div dir=ltr style="TEXT-ALIGN: left">        &lt;match target="font"&gt;                &lt;test name="family"&gt;                        &lt;string&gt;Andale Mono&lt;/string&gt;                        &lt;string&gt;Arial&lt;/string&gt;                        &lt;string&gt;Comic Sans MS&lt;/string&gt;                        &lt;string&gt;Georgia&lt;/string&gt;                        &lt;string&gt;Impact&lt;/string&gt;                        &lt;string&gt;Trebuchet MS&lt;/string&gt;                        &lt;string&gt;Verdana&lt;/string&gt;                        &lt;string&gt;Courier New&lt;/string&gt;                        &lt;string&gt;Times New Roman&lt;/string&gt;                        &lt;string&gt;Tahoma&lt;/string&gt;                        &lt;string&gt;Webdings&lt;/string&gt;                        &lt;string&gt;Albany AMT&lt;/string&gt;                        &lt;string&gt;Thorndale AMT&lt;/string&gt;                        &lt;string&gt;Cumberland AMT&lt;/string&gt;                        &lt;string&gt;Andale Sans&lt;/string&gt;                        &lt;string&gt;Andy MT&lt;/string&gt;                        &lt;string&gt;Bell MT&lt;/string&gt;                        &lt;string&gt;Monotype Sorts&lt;/string&gt;                &lt;/test&gt;&lt;test name="pixelsize" compare="less_eq"&gt;&lt;double&gt;16&lt;/double&gt;&lt;/test&gt;                &lt;edit name="autohint"&gt;                        &lt;bool&gt;false&lt;/bool&gt;                &lt;/edit&gt;                &lt;edit name="antialias"&gt;                        &lt;bool&gt;false&lt;/bool&gt;                &lt;/edit&gt;        &lt;/match&gt;</div>
</pre>
</div>
<br><strong>2 英文字和中文字等宽</strong><br><br>TrueType 字体分成两种，一种是可变字距，也就是每个字符宽度不一样，比如 "x" 与 "i"，这两个字符就不等宽，它是在每个字符中记录该字符的宽度，大多数的 TrueType 字体都是这种格式。 <br><br>另一种就是固定字距。也就是每个字符宽度都一样，这样显示或打印時，用会有对齐效果，比较美观，在英语环境中，不会有任何问题，但是CJK 的等宽字体，包含『半角英数字符』与『全角 CJK 字符』，字体引擎会把全角字宽度套用在半角字上，造成半宽字看起来间距太大。 <br><br>
<div style="MARGIN: 5px 20px 20px 0px">
<div class=smallfont style="MARGIN-BOTTOM: 2px">代码:</div>
<pre class=alt2 style="BORDER-RIGHT: #c6c6c6 1px solid; PADDING-RIGHT: 4px; BORDER-TOP: #c6c6c6 1px solid; PADDING-LEFT: 4px; PADDING-BOTTOM: 4px; MARGIN: 0px; OVERFLOW: auto; BORDER-LEFT: #c6c6c6 1px solid; WIDTH: 640px; PADDING-TOP: 4px; BORDER-BOTTOM: #c6c6c6 1px solid; HEIGHT: 226px">
<div dir=ltr style="TEXT-ALIGN: left">&lt;match target="font"&gt;                &lt;test target="pattern" name="lang" compare="contains"&gt;                        &lt;string&gt;zh&lt;/string&gt;                        &lt;string&gt;ja&lt;/string&gt;                        &lt;string&gt;ko&lt;/string&gt;                &lt;/test&gt;                &lt;edit name="spacing"&gt;                        &lt;const&gt;proportional&lt;/const&gt;                &lt;/edit&gt;                &lt;edit name="globaladvance"&gt;                &lt;bool&gt;false&lt;/bool&gt;                &lt;/edit&gt;        &lt;/match&gt;</div>
</pre>
</div>
这个配置表示：让字体引擎(FreeType)取消CJK字体的指定宽度。<br><br><strong>3 字体替换</strong><br><br>一些网页编写时候，对使用的字体预先设定，（如英文部分使用了simsun，使得英文显示很不美观）所以可以使用字体替换使得字体显示更美观。<br><br>把Simsun ， SimSun-18030 ， AR PL ShanHeiSun Uni等这个几个中文字体的英文部分，用 Tahoma,Arial 等替换。<br><br>
<div style="MARGIN: 5px 20px 20px 0px">
<div class=smallfont style="MARGIN-BOTTOM: 2px">代码:</div>
<pre class=alt2 style="BORDER-RIGHT: #c6c6c6 1px solid; PADDING-RIGHT: 4px; BORDER-TOP: #c6c6c6 1px solid; PADDING-LEFT: 4px; PADDING-BOTTOM: 4px; MARGIN: 0px; OVERFLOW: auto; BORDER-LEFT: #c6c6c6 1px solid; WIDTH: 640px; PADDING-TOP: 4px; BORDER-BOTTOM: #c6c6c6 1px solid; HEIGHT: 274px">
<div dir=ltr style="TEXT-ALIGN: left">&lt;match target="pattern"&gt;&lt;test name="family"&gt;&lt;string&gt;SimSun&lt;/string&gt;&lt;string&gt;SimSun-18030&lt;/string&gt;&lt;string&gt;AR PL ShanHeiSun Uni&lt;/string&gt;&lt;string&gt;AR PL New Sung&lt;/string&gt;&lt;string&gt;MingLiU&lt;/string&gt;&lt;/test&gt;&lt;edit binding="strong" mode="prepend" name="family"&gt;&lt;string&gt;Tahoma&lt;/string&gt;&lt;string&gt;Arial&lt;/string&gt;&lt;string&gt;Verdana&lt;/string&gt;&lt;string&gt;DejaVu Sans&lt;/string&gt;&lt;string&gt;Bitstream Vera Sans&lt;/string&gt;&lt;/edit&gt;&lt;/match&gt;</div>
</pre>
</div>
<br><br>把 AR PL ShanHeiSun Uni ， AR PL New Sung 字体中12～16号的中文字用 WenQuanYi Bitmap Song 点阵字替换， WenQuanYi Bitmap Song 比 AR PL ShanHeiSun Uni ， AR PL New Sung中的内嵌点阵更加完善，美观。<br><br>
<div style="MARGIN: 5px 20px 20px 0px">
<div class=smallfont style="MARGIN-BOTTOM: 2px">代码:</div>
<pre class=alt2 style="BORDER-RIGHT: #c6c6c6 1px solid; PADDING-RIGHT: 4px; BORDER-TOP: #c6c6c6 1px solid; PADDING-LEFT: 4px; PADDING-BOTTOM: 4px; MARGIN: 0px; OVERFLOW: auto; BORDER-LEFT: #c6c6c6 1px solid; WIDTH: 640px; PADDING-TOP: 4px; BORDER-BOTTOM: #c6c6c6 1px solid; HEIGHT: 274px">
<div dir=ltr style="TEXT-ALIGN: left">&lt;match target="pattern"&gt;&lt;test name="family" qual="any"&gt;&lt;string&gt;AR PL ShanHeiSun Uni&lt;/string&gt;&lt;string&gt;AR PL New Sung&lt;/string&gt;&lt;/test&gt;&lt;test compare="more_eq" name="pixelsize" &gt;&lt;double&gt;12&lt;/double&gt;&lt;/test&gt;&lt;test compare="less_eq" name="pixelsize" &gt;&lt;double&gt;16&lt;/double&gt;&lt;/test&gt;&lt;edit name="family" mode="prepend" binding="strong"&gt;&lt;string&gt;WenQuanYi Bitmap Song&lt;/string&gt;&lt;/edit&gt;&lt;/match&gt;</div>
</pre>
</div>
<br><br><strong>4 使用内嵌字体。</strong><br>
<div style="MARGIN: 5px 20px 20px 0px">
<div class=smallfont style="MARGIN-BOTTOM: 2px">代码:</div>
<pre class=alt2 style="BORDER-RIGHT: #c6c6c6 1px solid; PADDING-RIGHT: 4px; BORDER-TOP: #c6c6c6 1px solid; PADDING-LEFT: 4px; PADDING-BOTTOM: 4px; MARGIN: 0px; OVERFLOW: auto; BORDER-LEFT: #c6c6c6 1px solid; WIDTH: 640px; PADDING-TOP: 4px; BORDER-BOTTOM: #c6c6c6 1px solid; HEIGHT: 98px">
<div dir=ltr style="TEXT-ALIGN: left">&lt;match target="font"&gt;&lt;edit name="embeddedbitmap" mode="assign"&gt;&lt;bool&gt;true&lt;/bool&gt;&lt;/edit&gt;&lt;/match&gt;</div>
</pre>
</div>
内嵌点阵字当字体有内嵌字体时，优先使用内嵌字体。<br>注意：libXft 还没有embeddedbitmap选项的支持。可以到这里<a href="https://bugzilla.redhat.com/bugzilla/attachment.cgi?id=124032" target=_blank><font face=宋体 color=#000000>https://bugzilla.redhat.com/bugzilla....cgi?id=124032</font></a><br>得到libXft2.1.8.2的embeddedbitmap patch，通过ABS重新编译安装libXft 。<br><br><br><strong>5 必须使用hinting的字体</strong><br><br>有些 CJK 字体要the byte code interpreter （hinting）才能正确的显示，因为这些字体使用了hinting的一些技术制作的。常见的MingLiu 就是这类字体。<br>
<div style="MARGIN: 5px 20px 20px 0px">
<div class=smallfont style="MARGIN-BOTTOM: 2px">代码:</div>
<pre class=alt2 style="BORDER-RIGHT: #c6c6c6 1px solid; PADDING-RIGHT: 4px; BORDER-TOP: #c6c6c6 1px solid; PADDING-LEFT: 4px; PADDING-BOTTOM: 4px; MARGIN: 0px; OVERFLOW: auto; BORDER-LEFT: #c6c6c6 1px solid; WIDTH: 640px; PADDING-TOP: 4px; BORDER-BOTTOM: #c6c6c6 1px solid; HEIGHT: 210px">
<div dir=ltr style="TEXT-ALIGN: left">        &lt;match target="font"&gt;                &lt;test name="family"&gt;                        &lt;string&gt;MingLiU&lt;/string&gt;                        &lt;string&gt;PMingLiU&lt;/string&gt;                &lt;/test&gt;                &lt;edit name="autohint"&gt;                        &lt;bool&gt;false&lt;/bool&gt;                &lt;/edit&gt;                &lt;edit name="hinting"&gt;                        &lt;bool&gt;true&lt;/bool&gt;                &lt;/edit&gt;        &lt;/match&gt;</div>
</pre>
</div>
<br><br><strong>附：</strong><br><br><strong>Arch WiKi 的连接:</strong><br><br><a href="http://wiki.archlinux.org/index.php/字体设置" target=_blank><font face=宋体 color=#000000>http://wiki.archlinux.org/index.php/字体设置</font></a><br>
<img src ="http://www.cnitblog.com/zouzheng/aggbug/39329.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/zouzheng/" target="_blank">zz</a> 2008-01-25 21:01 <a href="http://www.cnitblog.com/zouzheng/articles/39329.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>嵌入式Linux 的safe mode 设计与实现</title><link>http://www.cnitblog.com/zouzheng/articles/39328.html</link><dc:creator>zz</dc:creator><author>zz</author><pubDate>Fri, 25 Jan 2008 12:53:00 GMT</pubDate><guid>http://www.cnitblog.com/zouzheng/articles/39328.html</guid><wfw:comment>http://www.cnitblog.com/zouzheng/comments/39328.html</wfw:comment><comments>http://www.cnitblog.com/zouzheng/articles/39328.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/zouzheng/comments/commentRss/39328.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/zouzheng/services/trackbacks/39328.html</trackback:ping><description><![CDATA[&nbsp;
<blockquote>目前的各种嵌入式产品已经丰富多彩，它们正改变着我们的生活方式。随着嵌入式产品功能的增加，如何让用户对已购买的产品的升级能安全地、顺利地完成，避免升级过程中出现的意外掉电所引起的产品故障，这样的问题要求嵌入产品设计开发者在设计时就将产品的 safe mode 安全模式考虑进去。这里我们将以一个嵌入式Linux 网络播放器为例，来说明 safe mode 安全模式的设计与实现。通过本文，我们可以了解到针对一个实际的嵌入式系统，设计中需要注意的技术要点和实现细节。</blockquote><!--start RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --><!--end RESERVED FOR FUTURE USE INCLUDE FILES-->
<p><a name=N1007D><span class=atitle>为什么需要 safe mode（安全模式）</span></a></p>
<p>当用户购买一个产品后，在后续的服务中，可能还会发生一些费用，让产品开发商增加成本，如免费电话咨询，产品的维修、寄送。所以说将产品的卖出并不意味着最终的赢利。这样的情况下，产品的设计就需要更加合理，更加优化，来满足用户各种可能的需求。特别是在发生异常故障的时候，如果能引导客户自行完成诊断、修复，那么将大大降低后续的服务成本。正因为如此，产品故障时，就很需要safe mode安全模式来帮助用户完成恢复的工作。</p>
<p>从节约产品的成本、产品所能提供的功能上来看，safe mode 是大有裨益的。</p>
<p>大家所熟知的 windows 系统，也提供了 safe mode 安全模式，它就可以帮助用户解决系统不稳定，硬件冲突等诸多故障，让用户在自己可以操作的能力范围内先行对系统进行诊断与修复。在很大程度上， windows 的 safe mode 给用户与 Microsoft 都带来了很大的便利。</p>
<p>嵌入式Linux产品与其他IT产品不同的地方，主要是使用flash来存贮运行时的系统。它没有大的内存，没有大的存储空间，但它却也是一个完整的系统。</p>
<p>在通常情况下，嵌入式Linux产品的flash上的内容是不会被破坏的，也即它们会有着较好的稳定性，不会因为用户的常规使用而导致flash上的 firmware被破坏。但随着产品的更新升级，用户也需要在自己家中完成对已购买商品的更新换代。而用户大多属于非技术熟悉者，在更新升级中就可能出现种种意想不到的情况。</p>
<p>比如在用户做firmware升级更新时，平时不会出现问题的firmware可能在这个过程中，就面临着巨大的风险，极有可能致使用户的系统无法启动，不能正常工作。这样的情况是我们不愿意看到的，而实际中却的的确确可能会发生。</p>
<p>考虑这样一个场景：当用户对产品进行firmware升级时，如果在烧写flash的过程中，意外掉电，那么用户手中的产品就将无法再次启动，因为 rootfs系统已经被破坏了。用户所能做的，也只能将产品送回产商进行维修。这样来回的过程不仅耗费用户的精力，同样也会增加产品开发商的成本。在产品升级换代很快的当前市场情况下，这样的情况可能会经常发生。</p>
<p>如何避免这样的情况的发生呢？如果我们可以提供一个机制，在进行升级前即往flash中写入一个标记，正常完成后，再写入另一个标记来表示整个过程的正常结束，否则的话，烧写时掉电不会写入第二个标记，只有第一个标记，那么就认为产品故障，这个时候，进入另一个新的提示界面，让用户自己选择从 USB或FTP来重新升级firmware。这样的话，整个过程用户就完全可以在界面的友好提示下自己完成，方便了用户与产品开发商。</p>
<p><a name=N1009C><span class=atitle>系统架构</span></a></p>
<p>本文以一个实际的产品为例，来说明safe mode的设计。</p>
<span style="DISPLAY: none">DeQLinux联盟</span><br><a name=fig1><strong>系统架构</strong></a><span style="DISPLAY: none">DeQLinux联盟</span><br><img height=352 alt=系统架构 src="http://www.xxlinux.com/linux/d/file/article/development/embed/2007-09-06/a9591e6a5dfef4c9af1f1a62df8ceb8e.jpg" width=364> <span style="DISPLAY: none">DeQLinux联盟</span><br>
<p>本系统为一个嵌入式Linux网络播放器，主要的功能为播放家庭网络中的多媒体文件，在家庭客厅等环境中有着大量的应用，它可以给用户提供更方便快捷的媒体文件的播放方式，并能充分利用家庭音响系统的巨大功能，而非PC环境下有限的外部设备，大大改善了媒体文件的播放体验。</p>
<span style="DISPLAY: none">DeQLinux联盟</span><br><a name=fig2><strong>本系统的架构如下图：</strong></a><span style="DISPLAY: none">DeQLinux联盟</span><br><img height=257 alt=本系统的架构如下图： src="http://www.xxlinux.com/linux/d/file/article/development/embed/2007-09-06/87170e46edfa1f09a7c5a275420c2975.jpg" width=481> <span style="DISPLAY: none">DeQLinux联盟</span><br>
<p>产品所使用的flash总大小为16M。</p>
<p>系统包括三大部分，即bootloader，config, kernel + rootfs：</p>
<span style="DISPLAY: none">DeQLinux联盟</span><br><img height=113 src="http://www.xxlinux.com/linux/d/file/article/development/embed/2007-09-06/e07bda036bc483e0d778e640b5d36316.jpg" width=532> <span style="DISPLAY: none">DeQLinux联盟</span><br>
<p>另外，/dev/mtdblock/0，在系统中对应整个flash block，即整个16M空间。</p>
<p>系统启动时，bootloader将kernel和根文件映象从flash上读取到RAM空间中，为内核设置启动参数，调用内核，进入application，进行媒体文件的播放。</p>
<p>这个通常意义上的嵌入式Linux系统，它是不带safe mode安全模式的。</p>
<p>这样的系统，在做系统更新升级时，主要是对kernel+rootfs部分进行升级，以此来增加系统的功能。</p>
<p>升级时，application主要是操作/dev/mtdblock/3设备文件：</p>
<p>第一步：下载新的firmware到ramfs中，也即ram disk中，比如/tmp目录下，采用的更新方式可以是USB或FTP；</p>
<p>第二步：read /tmp/firmware文件，并write到设备文件/dev/mtdblock/3上，即对已有的firmware进行了更新。</p>
<p>在升级的过程中，我们会提供友好的界面给用户，来提示下载进度与烧写flash的进度，让用户可以看到正在发生的状况。</p>
<p>最后烧写完成后，重新启动系统，即可进入到新的firmware中。</p>
<p>在通常的更新中，用户的产品配置config一般不去修改，保持用户已经做的配置选项，不能破坏。Config内容对应为/dev/mtdblock/2设备文件。</p>
<p>从USB/FTP 上更新时，所使用的firmware文件需要是一个更加完整的image文件，可以包括bootloader, default config, kernel+rootfs，并让application可以做到视image中的标记来决定是否需要更新bootloader、config等内容，这样会更加灵活。</p>
<p>在更新firmware时，如果掉电，那么kernel + rootfs部分将会出现不完整的情况，也就是说只写入了部分内容，而中途中断了，这样的话，一个不完整的系统将无法正常工作。在这样的情况下就需要safe mode安全模式了。</p>
<p><a name=N100FF><span class=atitle>safe mode架构设计</span></a></p>
<p>Safe mode的设计中，对原来的系统增加了两个部分的内容：</p>
<p>kernel + rootfs，即简单的UI界面与功能；</p>
<p>magic number，即烧写flash的标记。</p>
<span style="DISPLAY: none">DeQLinux联盟</span><br><img height=333 src="http://www.xxlinux.com/linux/d/file/article/development/embed/2007-09-06/98a10a6fd80a0f74e4fa150059c7227a.jpg" width=532> <span style="DISPLAY: none">DeQLinux联盟</span><br>
<p>safe mode实际上也是一个kernel + rootfs部分，只是它所具有的功能只包括一些简单的界面，主要是提供网络设置，从USB/FTP下载firmware，完成对flash的烧写。</p>
<p>为了区分，这里，将主功能部分的kernel + rootfs称为master。</p>
<p>我们将safe mode存放在master的后部，预留的flash大小为4M。</p>
<p>Magic number只占用一个字节的大小，是在这4M的最后的部分的一个字节，也即原始系统的15872K的最后一个字节位置处。</p>
<p>在开始烧写flash前，将magic number设置为0x55，表示烧写的开始。烧写正常结束后，将magic number设置为0xAA，表示烧写正常结束。</p>
<p>如果新产品中具备了safe mode模式，那么在以后再次更新升级时，开始烧写flash时，magic number的位置将会有0x55标记，如果烧写中途掉电，在重新启动后，将由bootloader来检查magic number的值，如果内容为0x55，那么bootloader将从safemode部分读出kernel和根文件映象，再为内核设置启动参数，调用内核，进入safe mode application。</p>
<p>如果bootloader读到magic number为0xAA，那么说明master firmware是正常的，就将直接进入master。</p>
<p>所以涉及到safe mode的地方也包括了对bootloader的修改，需要在系统上电阶段也检查safe mode的magic number，这个过程是必不可少的，只有在启动阶段就检查magic number，才能跳过损坏的master系统，进入安全模式，达到恢复系统的目的。</p>
<p><a name=N10135><span class=atitle>safe mode架构实现</span></a></p>
<p>在safe mode的实现中，需要保持原有master部分的稳定，所以对master系统的building system不做大的改动，也就是保持safe mode的building system与master的building system共存。原则上来说，要避免对master系统带来大的冲突。</p>
<p>Master building system主要涉及到的编译过程为：</p>
<p>make</p>
<p>make rootfs</p>
<p>这个时候将得到master.bin</p>
<p>safe mode building system和其类似，只是make rootfs部分有所区分：</p>
<p>make </p>
<p>make smrootfs</p>
<p>这个时候将得到safemode.bin</p>
<p>最后再将master与safe</p>
<p>mode部分做一个合并，得到一个整的rootfs</p>
<p>make dualrootfs</p>
<p>make dist</p>
<p>make</p>
<p>dualrootfs将调用一个外部的程序make_dual.c，所做的事情是要得到一个15872K的rootfs。这个rootfs包含的内容为master.bin + safemode.bin。</p>
<p>本系统中一般master.bin的大小约为10000K，再加上safemode.bin的4M，总大小并未达到15872K，那么中间多出的部分，我们需要将其补０填充好。需要补充的０的大小约为15872-4*1024-10000=1776K</p>
<span style="DISPLAY: none">DeQLinux联盟</span><br><img height=139 src="http://www.xxlinux.com/linux/d/file/article/development/embed/2007-09-06/feed70435aed3bc2973d76da1e2a6947.jpg" width=232> <span style="DISPLAY: none">DeQLinux联盟</span><br>
<p>make_dual.c就是完成上面的合并，补０的工作。它read master.bin，write rootfs，然后write　1776K个零到rootfs中，接下来read safemode.bin，再继续write 到rootfs中。</p>
<p>这样就得到了完整的、带master与safe mode的rootfs。</p>
<p><a name=N10180><span class=atitle>safe mode实现中遇到的问题及其解决</span></a></p>
<p><a name=N10187><span class=smalltitle>体积限制：</span></a></p>
<p>在safe mode的开发中，首先遇到的一个问题就是如何从已有的系统中简化出一个safe mode的application环境。</p>
<p>对master原有系统的裁剪来得到safe mode，将会比较容易，如果从头另写一套，将会花费较大精力，稳定性也无法得到确实的保障，所以最终采用的是精简master的系统来得到safe mode的大框架。</p>
<p>在实现safe mode时，要做的工作的原则是做到safe mode的rootfs尽量小，低于４M，并且保持与master外围特性的一致，这样可以避免重复开发，同时代码的共用可以减少维护的不便，提高整个系统的灵活度、稳定度。</p>
<p>就一个能运行的嵌入系统来说，最基本的内容应该包括Linux kernel，busybox工具包、图形驱动等内容。</p>
<p>在本系统中，为了支持FTP下载，需要有network的支持，也即需要包括wired/wireless的支持。</p>
<p>为了支持USB下载方式，就需要USB monitor管理进程的支持，这个主要是保持了与master系统的一致，而没有另外去写一个体积更小的USB管理模块。</p>
<p><a name=N1019F><span class=smalltitle>wireless模块：</span></a></p>
<p>本来在设计时，可以考虑不加入wireless的支持，但为了更加方便用户，保持用户的使用习惯，我们还是加入了对wireless的支持，这样也保持了与master系统的一致，但支持的代价是，safe mode的体积增大了大约250K。</p>
<p>在wireless module中，做了一个优化，master系统中wireless module在insmod时，是使用的rootfs中的/lib/module/wireless/XXX.o，这些未压缩的.o文件在rootfs系统中将占用较大空间，这样一来，对应的safe mode的内容将会超出４M的大小。为了解决这个问题，我们将这些wireless module压缩成wireless.tar.gz文件，放置到safemode.bin中，在Linux启动时，在/etc/rc脚本中将 wireless.tar.gz解压缩到ramfs中即／tmp/lib/module/wireless下，然后再从这里insmod安装 wireless模块。这样所做的努力，wireless module从原来的790K，缩减到了250K，而功能保持了一致。</p>
<p><a name=N101AB><span class=smalltitle>字体：</span></a></p>
<p>master 系统的字体使用的是freetype2，字体文件arialbd.ttf大约为280K，这也将占用大量的空间。由于safe mode在显示界面方面没有过高的要求，能让用户看到基本的图形界面就已经达到目的了，所以在safe mode中需要将freetype去掉。但由于master模式与safe mode都使用相同的图形引擎，这样就导致了，如果在safe mode中去掉freetype，那么就需要再次重新build基础的图形库，这样在master与safe mode的单独编译过程中就需要反复去make clean这些库。这会给每次的编译带来很大的不便，每次make clean等操作会占用大量的时间，耗时耗力。</p>
<p>基于这个考虑，我们决定master与safe mode在编译过程中都使用相同的图形库，即都编译生成freetype库。但在运行时，safe mode不去使用freetype。也就是说，freetype库会被编译进来，但字体文件不需要加到safe mode中，这样做的代价就是编译出来的safe mode的application比完全无freetype库的情况要大100K左右，但却保持了与master相同的库结构，而freetype字体就不再需要了，也就节约出了大约280K的空间。</p>
<p>最终优化的结果，safe mode的4M，包括Linux kernel, buzybox, safe mode application等压缩后的大小：</p>
<span style="DISPLAY: none">DeQLinux联盟</span><br><a name=fig6><strong>优化结果</strong></a><span style="DISPLAY: none">DeQLinux联盟</span><br><img height=220 alt=优化结果 src="http://www.xxlinux.com/linux/d/file/article/development/embed/2007-09-06/b08d1d37ea4230cf70104810874c2b0d.jpg" width=531> <span style="DISPLAY: none">DeQLinux联盟</span><br>
<p><a name=N101C9><span class=smalltitle>后续版本的兼容：</span></a></p>
<p>在safe mode的设计中，对后续多个版本升级的支持也是一个需要仔细考虑的地方。因为后续版本会存在很多的不确定性，如果发出的版本不能很好地兼容后续版本，那么将会给产品带来巨大的风险。</p>
<p>后续版本的可能情况，主要分两种：结构分区变化不大，结构分区变化巨大。</p>
<p>对后续版本中变化不大的情况，也即类似master + safe mode的情况，当再次更新时，只需要操作/dev/mtdblock/3对应master，/dev/mtdblock/4对应safe mode，即可。</p>
<p>但如果后续版本变化非常大，那么就需要特别注意了。</p>
<p>可以考虑这样一个情况：如果后续的版本，需求发生了大的变化，比如需要将原来master所在的分区再分成多个分区：</p>
<span style="DISPLAY: none">DeQLinux联盟</span><br><a name=fig7><strong>后续版本需求变化</strong></a><span style="DISPLAY: none">DeQLinux联盟</span><br><img height=352 alt=后续版本需求变化 src="http://www.xxlinux.com/linux/d/file/article/development/embed/2007-09-06/49a2706bf11ed3d5bc98de23dd8d5527.jpg" width=488> <span style="DISPLAY: none">DeQLinux联盟</span><br>
<p>那么从老版本升级到新版本时，这些分区的内容如何保证烧写后能正常工作呢？</p>
<p>解决的办法就是在老版本中，将后续的rootfs部分作为一个整体来操作，也就是说烧写时，是将master + part1 + part2+ safe mode作为一个整体来对待。在老版本看来，新版本中的这15872K的内容，不管它其中有多少个不同的分区，还是master + safe mode。在烧写时，还是按/dev/mtdblock/3对应master，/dev/mtdblock/4对应safe mode的方式来烧写，完成将15872K的内容完整烧写进flash即可。</p>
<p>为了做到这一点，在烧写中，我们将全部的15872K的内容分成两段，第一段为15872-4*1024=11776K，需要将其write到/dev/mtdblock/3中，第二段为4M，需要将其write到/dev/mtdblock/4中。这样全部的15872K的内容就完整地烧写完，而再次启动后的kernel会分辨出 master + part1 + part2 + safe mode，它们的总大小依然保持15872K不变。这整个过程中，都不用去理会新版本中到底包括哪些内容，哪些分区，只要保证是将15872K的内容全部完整地烧写进去就可以了。</p>
<p>整体rootfs的设计思想在这里帮了一个大忙，简化了升级更新时所需要考虑的复杂度，使设计变得更加灵活与易于维护。</p>
<p>这样才新发布的firmware里，如果分为多个分区，那么就保证再次升级时，将15872K的内容分成多段，写到类似/dev/mtdblock/3、4、5、6这样的设备文件里就可以了，只要保证这些区域是连续的、并且烧写的内容是全部的那15872K内容即可。</p>
<p><a name=N101FC><span class=smalltitle>Magic number：</span></a></p>
<p>值得注意的是，随着不同的版本的变化，magic number的位置还是应该保持在15872K的最后一个字节的位置。但这就出现一个问题，在不同的版本中，这个magic number的位置会是在不同的partition的最后一个字节。比如某个版本可能是在/dev/mtdblock/4的最后，但再后续的版本它会变成了/dev/mtdblock/7的最后面，这样就会存在很大的不确定性。所以在一个各个版本中，写magic number标记位时，需要一个统一的方法来做到这件事。最容易想到的办法当然就是magic number这个位置相对起始位置０是不变的。而前面提到过的/dev/mtdblock/0就刚好是代表了可以操作的整个flash分区。</p>
<p>有了/dev/mtdblock/0，这样我们就可以open 它，seek到magic number的位置，然后write下0x55或0xAA，这样就保持了写magic number的代码的一致性，不需要根据不同的分区，多次修改操作magic number的有关函数。</p>
<p><a name=N10208><span class=smalltitle>Booloader：</span></a></p>
<p>Bootloader的修改，也涉及到对magic number的读取，它的读取就相对简单一些，直接使用magic number在RAM中映射的绝对地址即可。</p>
<p>Bootloader检查完magic number后，需要将相对地址为0xBC0000的safe mode的kernel + rootfs读入到RAM，然后设置启动参数，调用内核，进入safe mode提示界面。</p>
<p><a name=N10214><span class=smalltitle>Linux kernel：</span></a></p>
<p>与老的、不带safe mode的image相比，新的image里的Linux kernel从总体的角度来说，并没有大的变化。在新做的master与safe mode的image中，它们各自需要包含一个Linux kernel，这两个kernel唯一的不同就是启动时所需要的rootfs在RAM中的映射位置不同。它们都有着相同的partition分区设置，编译选项等。</p>
<p>Safe mode必须包含自己的Linux kernel，因为它是运行在master损坏的情况下，master kernel已经不能启动了。</p>
<p><a name=N10220><span class=atitle>总结</span></a></p>
<p>上面的内容是在实际开发中对safe mode的设计与实现的一个描述。从这个描述中，可以看到safe mode在嵌入式Linux产品扮演着重要的角色，对它的设计涉及到很多方面，要考虑系统的尺寸，与现有buidling环境的的兼容性，对后续版本的升级的兼容性等诸多方面。</p>
<p>从某种意义上来说，safe mode的设计关系到产品的成败，一个好的safe mode的设计将会给产品带来巨大的灵活性与可扩展性，大大地方便了客户与产品开发商。 </p>
<span style="DISPLAY: none">DeQLinux联盟</span><br><span style="DISPLAY: none">DeQLinux联盟</span><br>
<p><a name=author><span class=atitle>关于作者</span></a></p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td colSpan=3><img height=5 src="http://www.xxlinux.com/linux/d/file/article/development/embed/2007-09-06/3eecc96320710c62a1e5a720edb45f8e.gif" width=571></td>
        </tr>
        <tr vAlign=top align=left>
            <td>
            <p>&#160;</p>
            </td>
            <td><img height=5 src="http://www.xxlinux.com/linux/d/file/article/development/embed/2007-09-06/3eecc96320710c62a1e5a720edb45f8e.gif" width=4></td>
            <td width="100%">
            <p>余涛，高级软件工程师，现从事 linux 嵌入式系统的开发工作，主要研究方向嵌入系统，UPNP 多媒体播放系统。您可以通过电子邮件 <a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#121;&#117;&#116;&#54;&#49;&#54;&#64;&#50;&#49;&#99;&#110;&#46;&#99;&#111;&#109;">yut616@21cn.com</a> 和他联系。希望能与更多的朋友交流关于 Linux 方面的知识。</p>
            </td>
        </tr>
    </tbody>
</table>
<img src ="http://www.cnitblog.com/zouzheng/aggbug/39328.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/zouzheng/" target="_blank">zz</a> 2008-01-25 20:53 <a href="http://www.cnitblog.com/zouzheng/articles/39328.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>汉字的动态编码与显示方案</title><link>http://www.cnitblog.com/zouzheng/articles/39327.html</link><dc:creator>zz</dc:creator><author>zz</author><pubDate>Fri, 25 Jan 2008 12:45:00 GMT</pubDate><guid>http://www.cnitblog.com/zouzheng/articles/39327.html</guid><wfw:comment>http://www.cnitblog.com/zouzheng/comments/39327.html</wfw:comment><comments>http://www.cnitblog.com/zouzheng/articles/39327.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/zouzheng/comments/commentRss/39327.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/zouzheng/services/trackbacks/39327.html</trackback:ping><description><![CDATA[&nbsp;<strong>摘要：</strong>综合几种常用单片机汉字显示方案，提出一种基于PC机预处理的汉字动态编码和动态字库的显示方法，较好地解决了存储空间、显示速度、软件开发维护几方面的相互矛盾；具有平台化的优点，同时，给出针对MCS51优化的汇编显示例程。
<p><span>&nbsp;&nbsp;&nbsp; <strong>关键词：</strong></span>机内码 动态编码 字库</p>
<p>因为汉字本身的特点，显示汉字始终是计算机在我国应用普及的一个障碍。最初，为了能在PC机上显示、处理汉字，国人发明了一种硬件设备"汉卡"，后来各种各样的采用纯软件技术的中文DOS逐渐成熟，其中、西文软件的运行速度和性能还是有明显的差距。最终在软件进入支持UNICODE、真正实现国际化的WIN95以后，硬件跨入"奔腾"时代，才实现了汉字与西文的统一显示，但是这一切是以硬件资源的飞速发展为前提的。以国际GB2312为例，一、二级汉字库共收录了6000多个汉字，每个字按16&#215;16点阵计算，字模需要占用32字节的存储空间，整个字库的规模在200k字节以上，高点阵（24点阵以上）和矢量字库以及Windows用的TrueType字体的字库规模都是几兆字节大小，这在早期的386时代是难以想象的。单片机因为使用灵活、结构简单、体积小、成本低而在工业和生活中得到广泛应用，也正是因此，它的硬件资源很有 限，寻址和计算机能力都远低于PC机，显示汉字更受限制。人们不满足单片机系统采用LED数码管的简单显示，根据单片机的特点，开发出了很多种汉字显示方法。</p>
<p><strong>1 几种常用单片机显示汉字方法</strong></p>
<p>(1)采用标准字[1]</p>
<p>这种方法仿器中文DOS的办法，将一个标准的汉字库装入ROM存储器，再根据汉字的机内码在字库中寻址，找到对应的字模，提取后送到显示器显示。因为采用了和PC机相同的编码(机内码)，软件的开发和维护非常简单，基本上与写PC机软件差不多。而对单片机系统自身的要求则相对高多了，16&#215;16点阵的字库需要256K字节，但是一般8位单片机的寻址能力只有64K字节，要进行存储器扩充，除增加很大一部分硬件成本外，还因为要进行存储器分页管理、地址切换，显示速度明显受影响，而且只能显示一种点阵字体。</p>
<p>（2）直接固化显示字模[2]</p>
<p>将要显示的语句中全部汉字的字模数据依次提取出来，顺序存放在存储器中，当显示时，直接取出字模数据送至显示器即可。这种方法占用空间少，程序实现简单，显示速度快；但是字模数据的提取和存储安排是一件委有繁琐的事件，要想大量显示汉字或进行程序修改几乎是不可能的，软件的可维护性很差。</p>
<p>(3)建立带索引的小字库[3]</p>
<p>将全部要显示的汉字统一建成一个小字库，字库分为2部分：索引素和字模表。索引表由若干定长记录组成，记录的内容为：汉字机内码、地址码、识别码。其中地址码是该汉字字模在字模表中的位置，识别码标志该汉字的点阵形式或字体等。字模表中按素引存放汉字字模。显示汉字时先根据待显汉字的机内码在索引表中寻找，找到对应索引记录后，读出地址码和识别码，再根据此从字模表中读出字模，送显即可。这种方法可根据实际使用对字库进行裁剪，硬件开销较小，但是要进行复杂的查询运算，字多了平均寻找时间就会变长，效率降低。</p>
<p><strong>2 汉字动态编码</strong></p>
<p>综上所述，我们发现：在方法1中，程序员工作量最少，但单片要机的软、硬件开销最大；方法2中，单片机的开销较少，但是编写和维护软件极为困难；方法3,介于二者之间。显然，存储空间、显示速度、软件开发维护件间存在着矛盾。受各种PC机模拟软件的启发，我们提出一种基于PC机预处理的汉字显示方法--汉字动态编码，在实际应用中较好地解决了这一问题。其基本原理如下：建立一种新的编码机制，这个汉字编码是动态的；一个编码不与某个汉字具体相联系，而仅代表某个汉字在字库中的位置（这个位置也是动态的）；用该码代替程序里字符串（C语言）或数据段（汇编语言）内汉字的机内码，单处机显示程序可根据这个新的编码直接在专门建立的动态小字库中找到字模，不用进行复杂的寻址、查找等运算，如图1所示。</p>
<p>实现汉字动态编码的过程就是先进行汉字识别，然后建立编码字典、提取字模、建立动态字库、改写机内码。首先扫描一遍程序文件，识别其中的汉字，将它们按出现先后顺序或机内码的大小排序，重复出现的剔除，建立了一个编码字典；根据汉字在编码字典的位置（序号），可以对汉字按区码、位码进行编码，也可以采用其它的方法编码，总之序号与它的动态编码存在一一对应关系；根据字典中每个汉字的机内码依次从PC机的汉字点阵字库中提取字模，顺序存储，建立一个小规模的动态字库，这样每个汉字的字模在字库中的位置就与其在编码字典中的序号、动态编码一一对应了。最后，再扫描一遍程序文件，按照编码字典将每个汉字的机内码改写为对应的动态编码。因为程序文件中的汉字随时会增减，编码随之而变，字库的大小也随时在变。所以称之为动态编码和动态字库。</p>
<p>考虑一般应用场合，1000个左右的汉字即可满足要求，按照汉字动态编码方法所需的字库仅为32K字节大小，只需要1片27256即可，几乎不用增加什么硬件。这样，字库的大小可由汉字的多少控制，程序的编写和维护可以沿用中文系统下的习惯，仅需要编写好的单片机程序用PC机进行一次预处理，程序员从繁杂的汉字处理工作中解放出来，有效地降低了软件和硬件开发成本。<br><img onclick=javascript:window.open(this.src); height=439 hspace=10 src="http://www2.minitos.com/article/UploadPic/2007-10/20071010141630290.gif" width=545 onload="return imgzoom(this,550);" vspace=10 border=0><br><strong>3 汉字动态编码的具体实现</strong></p>
<p>实现汉字动态编码的关键是建立编码字典和改写机内码。下面以是显示1行汉字"天上有个太阳，水中有个月亮"为例，说明动态编码的实现过程。</p>
<p>（1）汉字识别</p>
<p>汉字在PC机内的存储和处理是用机内码来实现的。每个汉字的机内码是唯一的，由2个字节组成，分区码和位码，为了和西文的ASCII码有区别，汉字机内码的区码和位码的取值都大于0A0H。我们要处理的源程序文件都是文本文件，存储的都是西文字符、控制符的ASCII码和中文字符的机内码，当扫描到文件中大于0A0H的字节内容时，即可判断该字节是汉字机内码的1个字节，而且肯定是成对出现，第1个字节是区别，第2个字节是位码，都大于0A0H，否则出错。</p>
<p>在C和汇编程序中表示字符的方式有所不同，但最终字符在文件中的存储格式是一样的。显示上面那行汉字，用C语言可以表示为：</p>
<p>char OneSent[]="天上有个太阳，水中有个月亮"；</p>
<p>printfhz（OneSent）;/*printfhz()显示函数*/</p>
<p>用十六进制编辑器（我们用的是UEdit32）察看文件中C语言字符串定义语句为：</p>
<p>63 68 61 72 20 20 4F 6E 65 53 65 6E 74 5B 5D 20 3D 20 22 CC EC C9 CF D3 D0 B8 F6 CC AB D1 F4 A3 AC CB AE D6 D0 D3 D0 B8 F6 D4 C2 C1 C1 22 20 3B 0D 0A</p>
<p>用汇编语言可以表示为：</p>
<p>ONESENT:DB '天上有个太阳，水中有个月亮'，00H</p>
<p>MOV DPTR,ONESENT</p>
<p>LCALL DISPLAY；DISPLAY是显示子程序</p>
<p>用十六进制编辑器察看上面用汇编语言定义字符串的那一条语句为：</p>
<p>4F 4E 45 53 45 4E 54 3A 44 42 20 27 CC EC C9 CF D3 D0 B8 F6 CC AB D1 F4 A3 AC CB AE D6 D0 D3 D0 B8 F6 D4 C2 C1 C1 27 2C 30 30 48 0D 0A</p>
<p>由此可以观察到情况确如前所述。</p>
<p>（2）建立编码字典</p>
<p>编码字典是在扫描的同时逐步建立起来的，每扫描到一个汉字（包括全角符号），即与字典中已有的字符进行比较，如没有重复，是新的字符就顺序存入字典，否则继续扫描，直至文件结属。由于每个字符都是从尾部添加的，它们的序号也是依次递增的，根据序号就可以进行动态编码了。由于显示的汉字一般都得在256个以上，即使进行动态编码，也需要用2字节编码来实现。以MCS51系列单片机和16&#215;16点阵汉字做一优化编码示例：8051的地址指针DPTR是16位指针，由高、低2字节指针DPH、DPL组合而成，如果将存储器按0FFH（256）字节分布，修改DPH即可直接寻址到任一页，修改DPL可寻址该页的任一字节。一个16&#215;16点阵汉字的字模是32字节大小，每页存储器正好能容纳8个汉字字模。可以优化设计动态编码的高字节指向字模的页地址（DPH），低字节指向字模在该页的首地址（DPL）。考虑地址空间的有效分配，将字库的地址放在0A000H以后（程序或数据存储器均可），动态编码的高字节要加上地址有效分配，将字库的地址放在0A000H以后（程序或数据存储器均可），动态编码的高字节要加上地址的页偏移量（大于等于0A0H）；考虑汉字与西文字符的区别，动态编码的低字节也需要加上一个大于或等于0A0H的偏移量。设某汉字在编码字典中的序号为Num，则该汉字的动态编码为：</p>
<p>动态编码高字节=页偏移量+Num/8</p>
<p>动态编码低字节=偏移量+(Num%8)&#215;32&nbsp;&nbsp;&nbsp; (1)</p>
<p>偏移量一般可设为0A0H。当单片机显示某个汉字时，只需将其动态编码的高字节送DPH，低字节减0A0H后送DPL，即可得到对应字模的地址指针。</p>
<p>（3）提取字模、建立动态字库</p>
<p>汉字机内码与点阵字库的详细关系可参考有关资料，它们存在如下联系：</p>
<p>字模首地址=（（机内码高字节-1）&#215;94+（机内码低字节-1））&#215;N&nbsp;&nbsp;&nbsp; （2）</p>
<p>注：N为一个汉字点阵字模的字节数。</p>
<p>按照编码字典内容，根据字模首地址，依次取出汉字字模，顺序写入一个二进制文件，即建成动态字库（其它方法略），用烧录器写入EPROM，就可以使用了。</p>
<p>（4）编码改写</p>
<p>机内码是PC机识别处理汉字用的，单片机只能处理我们建立起来的动态编码，还得把程序中汉字的仅机码根据编码字典改成对应的动态编码才行。由于在编写源程序的文本编辑器中看到的是经过系统处理过的字节，看不到汉字的机内码，也无法对其进行改写。根据"汉字识别"一节所述，不经过文本编辑器，直接将动态编码（十六进制数）定改磁盘文件对应位置即可，但是处理过后的汉字在文本编辑器里会显示出乱码。</p>
<p>（5）汉字显示</p>
<p>在明白了动态编码与动态字库中字模的关系后，可以完成按照PC机下汉字显示原理进行单片机下的程序设计，编写前面的函数printhz（）或子程序的DISPLAY，可参考相关资料[4]。<br><img onclick=javascript:window.open(this.src); height=131 hspace=10 src="http://www2.minitos.com/article/UploadPic/2007-10/20071010141631175.gif" width=545 onload="return imgzoom(this,550);" vspace=10 border=0><br><strong>4 MCS51汉字显示例程</strong></p>
<p>根据上述汉字动态编码方法，我们利用Borland C++编写了PC机预处理程序，将ASM51或C51源程序用PC机预处理后，建立了动态字库和改写了机内码，并且用ASM51写了一个针对MCS51进行优化的子程序DIS_CHAR。它显示一个西文或中文字符，实现过程如图2所示。</p>
<p>西文字符码的显示与流字显示基本相同，将西文字库（仅数字和字符部分）装入程序存储器中，根据ASCII码的值计算出字模首地址，将字符字模依次读出，再送显示即可。</p>
<p>此方案不但可用于单片机系统中，还可应用于任何无中文系统支持的嵌入式系统中。根据这个思路还可设计出不同字体、点阵混合的字库，支持包含2万多个字符的新国标编码，甚至矢量字体在单片机系统中的应用也成为可能。由于技术水平有限，此方案还存在一些不足之处，如改写编码后源程序中汉字显示为乱码，不知道改码处理是否正确，操作比较繁琐。如果能采用插件技术实现此方案，编辑器中能正常显示汉字，而输出已经是改码后的程序文件，则能很好地解决上述不足。在这里，我们抛码引玉，希望有兴趣的朋友一起合作，实现单片机中文显示的广义开发平台。</p>
<p>动态编码预处理的C语言源程序(在BC++3.1下调试通过)见网站补充版（http://www.dpj.com.cn）</p>
<img src ="http://www.cnitblog.com/zouzheng/aggbug/39327.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/zouzheng/" target="_blank">zz</a> 2008-01-25 20:45 <a href="http://www.cnitblog.com/zouzheng/articles/39327.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Fedora 7 字体美化</title><link>http://www.cnitblog.com/zouzheng/articles/39238.html</link><dc:creator>zz</dc:creator><author>zz</author><pubDate>Wed, 23 Jan 2008 08:09:00 GMT</pubDate><guid>http://www.cnitblog.com/zouzheng/articles/39238.html</guid><wfw:comment>http://www.cnitblog.com/zouzheng/comments/39238.html</wfw:comment><comments>http://www.cnitblog.com/zouzheng/articles/39238.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/zouzheng/comments/commentRss/39238.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/zouzheng/services/trackbacks/39238.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: edora7(Moonshine)发布也有一段时间了，这个版本包含了GNOME2.18和KDE3.5.6，美工有很大的提高；使用Xorg1.3版本，对nVidia显卡支持很好，支持显示器的热插拔和自动识别；yum性能也得到很大的提升，其它各方面的表现都很不错的，唯一的缺点就是中文字体显示不够完美。虽然比FC6有了极大的提高，但还是赶不上Ubuntu7.04。于是就有了自己动手美化中文显示的想法，下...&nbsp;&nbsp;<a href='http://www.cnitblog.com/zouzheng/articles/39238.html'>阅读全文</a><img src ="http://www.cnitblog.com/zouzheng/aggbug/39238.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/zouzheng/" target="_blank">zz</a> 2008-01-23 16:09 <a href="http://www.cnitblog.com/zouzheng/articles/39238.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>UCDOS汉字矢量字库(HZKSLxxJ)格式</title><link>http://www.cnitblog.com/zouzheng/articles/39235.html</link><dc:creator>zz</dc:creator><author>zz</author><pubDate>Wed, 23 Jan 2008 07:30:00 GMT</pubDate><guid>http://www.cnitblog.com/zouzheng/articles/39235.html</guid><wfw:comment>http://www.cnitblog.com/zouzheng/comments/39235.html</wfw:comment><comments>http://www.cnitblog.com/zouzheng/articles/39235.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/zouzheng/comments/commentRss/39235.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/zouzheng/services/trackbacks/39235.html</trackback:ping><description><![CDATA[<span id="article"><font id="zoom">在UCDOS矢量字库中，每个汉字都是以128&nbsp;X&nbsp;128点阵制成矢量数据。
每个汉字的矢量数据都由一指针指向,&nbsp;指针区在每个汉字字库文件的开头0xBB3E字节。每个汉字矢量数据指针占6个字节,&nbsp;其格式为:前4个字节为汉字
的矢量数据在文件中的偏移,&nbsp;后2个字节为汉字的矢量数据的长度。</font>
<p><font id="zoom">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;汉字指针在指针区的偏移由公式计算:pos=((qu-16)*94+wei-1)*6。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;注:qu--区号。wei--位号。</font></p>
<p><font id="zoom">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;汉字的矢量数据格式为:控制码+坐标值。共有十种控制码,以下是控制码的&nbsp;含义:</font></p>
<p><font id="zoom">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(1)若控制码第7,6位为11,清除码,结束当前笔划,将第一个坐标与当前坐<br>标连线;建立新笔划,(X,Y)各占7位,由控制码的第５位开始,即:11XXXXXX&nbsp;XYYYYYYY。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;注:一个字节的位:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;7&nbsp;6&nbsp;5&nbsp;4&nbsp;3&nbsp;2&nbsp;1&nbsp;0<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;X&nbsp;X&nbsp;X&nbsp;X&nbsp;X&nbsp;X&nbsp;X&nbsp;X</font></p>
<p><font id="zoom">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(2)若控制码小于等于0x40,之后控制码大小个字节为坐标值,每个坐标占一个字节,共有控制码大小个坐标,(X,Y)坐标各占4位,其自的最高位为符号位,&nbsp;即:FXXXFYYY。</font></p>
<p><font id="zoom">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(3)若控制码的高4位等于4,之后控制码的低4位大小个字节为坐标值,每个坐标占一个字节,共有控制码大小个坐标,(X,Y)坐标各占4位,X为正,Y为正,即:+XXXX+YYYY。</font></p>
<p><font id="zoom">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(4)若控制码的高4位等于5,之后控制码的低4位大小个字节为坐标值,每个坐标占一个字节,共有控制码大小个坐标,(X,Y)坐标各占4位,X为负,Y为正,即:-XXXX+YYYY。</font></p>
<p><font id="zoom">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(5)若控制码的高4位等于6,之后控制码的低4位大小个字节为坐标值,每个坐标占一个字节,共有控制码大小个坐标,(X,Y)坐标各占4位,X为负,Y为负,即:-XXXX-YYYY。</font></p>
<p><font id="zoom">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(6)若控制码的高4位等于7,之后控制码的低4位大小个字节为坐标值,每个坐标占一个字节,共有控制码大小个坐标,(X,Y)坐标各占4位,X为正,Y为负,即:+XXXX-YYYY。</font></p>
<p><font id="zoom">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(7)若控制码等于0x80,&nbsp;其后1字节为Y坐标值,&nbsp;最高位为符号位,&nbsp;X坐标不&nbsp;变,&nbsp;即:10000000&nbsp;FYYYYYYY。</font></p>
<p><font id="zoom">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(8)&nbsp;若控制码等于0x90,&nbsp;其后1字节为X坐标值,&nbsp;最高位为符号位,&nbsp;Y坐标不&nbsp;变,即:10000001&nbsp;FXXXXXXX。</font></p>
<p><font id="zoom">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(9)&nbsp;若控制码的高4位等于8,其后1字节为Y坐标值,控制码的低4位值为X坐标值,X坐标各占4位,最高位为符号位,Y坐标各占8位,最高位为符号位,&nbsp;即:1000FXXXFYYYYYYY。</font></p>
<p><font id="zoom">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(10)若控制码的高4位等于9,其后1字节为X坐标值,控制码的低4位值为Y坐标值,Y坐标各占4位,最高位为符号位,X坐标各占8位,最高位为符号位,&nbsp;即:1000FYYYFXXXXXXX。</font></p>
<p><font color="#ffffff">主题：Re: 请问ucdos矢量字库算法</font></p>
<p><font color="#408080">:&nbsp;&nbsp;&nbsp;&nbsp;我想直接读取ucdos下的矢量字库，请问有谁知道算法？</font><br><font color="#408080">:&nbsp;&nbsp;&nbsp;&nbsp;急！急！急?/font&gt;<br><font color="#408080">:&nbsp;?/font&gt;<br><br><br>/*&nbsp;矢量汉字的读取和显示&nbsp;*/<br>#include&nbsp;&lt;stdio.h&gt;<br>#include&nbsp;&lt;graphics.h&gt;<br>#define&nbsp;HZNUM&nbsp;(0xf7-0xaf)*(0xfe-0xa0)&nbsp;/*&nbsp;除前16区外的所有汉字&nbsp;*/<br>#define&nbsp;HZKSIZE&nbsp;128&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;汉字的大小&nbsp;*/<br>#define&nbsp;START_X&nbsp;0<br>#define&nbsp;START_Y&nbsp;0<br>#define&nbsp;VIEW_H&nbsp;256&nbsp;&nbsp;&nbsp;/*&nbsp;显示汉字的高度&nbsp;*/<br>#define&nbsp;VIEW_W&nbsp;256&nbsp;&nbsp;&nbsp;/*&nbsp;显示汉字的宽度&nbsp;*/<br>FILE&nbsp;*fp;<br>struct&nbsp;hz_struct&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;long&nbsp;shift;&nbsp;&nbsp;/*&nbsp;偏移量&nbsp;*/<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;int&nbsp;size;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;大小&nbsp;*/<br>&nbsp;}HZ_Index[HZNUM];&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;汉字索引&nbsp;*/<br>unsigned&nbsp;char&nbsp;buf[1024];<br>unsigned&nbsp;char&nbsp;dotbuf[1024];<br>main()<br>{<br>&nbsp;&nbsp;long&nbsp;ioffset;<br>&nbsp;&nbsp;int&nbsp;i,j;<br>&nbsp;&nbsp;char&nbsp;ch;<br>&nbsp;&nbsp;int&nbsp;gdriver=DETECT;<br>&nbsp;&nbsp;int&nbsp;gmode;<br>&nbsp;&nbsp;if((fp=fopen("hzksly1j","rb"))==NULL){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf("cano't&nbsp;open&nbsp;the&nbsp;HZlib!");<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;exit(1);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;fread(HZ_Index,sizeof(struct&nbsp;hz_struct),HZNUM,fp);<br>//if(registerbgidriver(EGAVGA_driver)&lt;0) exit(1);<br>  initgraph(&amp;gdriver,&amp;gmode,"");<br>  cleardevice();<br>  setcolor(LIGHTGRAY);<br>  for(i=0; i&lt;HZNUM;i++){<br>         Disp_HZ(HZ_Index[i].size,HZ_Index[i].shift);<br>         rectangle(START_X,START_Y,START_X+256,START_Y+256);<br>         ch=getch();<br>         cleardevice();<br>         if(ch=='q') break;<br>  }<br>  closegraph();<br>  fclose(fp);<br>}<br><br>/* 显示汉字 */<br>Disp_HZ(int length,long posi)<br>{<br>  int i,j,k;<br>  int x0,y0,x1,y1;<br>  int hzsize;<br>  union utype{<br>         unsigned short size;<br>         unsigned char str[2];<br>  }BH;<br>  if((fseek(fp,posi,0))!=0){<br>         printf("seek\"clib\"error!\n");<br>         exit(0);<br>  }<br>  memset(buf,0,1024);<br>  fread(buf,length,1,fp);<br>  hzsize=decode(buf,length);<br>  k=0;<br>  for(i=0;i&lt;hzsize;i++){<br>         BH.str[0]=dotbuf[k++];<br>         BH.str[1]=dotbuf[k++];<br>         if(BH.size==0)break;/* 每个汉字以0结束 */<br>         x0=START_X+(dotbuf[k++]*VIEW_W)/HZKSIZE;<br>         y0=START_Y+(dotbuf[k++]*VIEW_H)/HZKSIZE;<br>//       x0=START_X+dotbuf[k++];<br>//       y0=START_Y+dotbuf[k++];<br>         moveto(x0,y0);<br>         for(j=0;j&lt;BH.size-1;j++){<br>                 x1=START_X+(dotbuf[k++]*VIEW_W)/HZKSIZE;<br>                 y1=START_Y+(dotbuf[k++]*VIEW_W)/HZKSIZE;<br>//       x1=START_X+dotbuf[k++];<br>//       y1=START_Y+dotbuf[k++];<br>                 lineto(x1,y1);<br>                }<br>                lineto(x0,y0);<br>  }<br>}<br><br>/* 汉字字形还原<br> */<br>decode(p,length)<br>unsigned char *p;<br>int length;<br>{<br>  int k;<br>  i