WinZip解压缩之操作教学(精选2篇)
WinZip解压缩之操作教学 第1篇
1.在档案总管中连击滑鼠左键二下,开启abc123.zip之压缩档,
WinZip解压缩之操作教学
。
下载地址:WinZip
2.WinZip程式⒆绦校K得到如下面,cxExtract按oM行解嚎s工作。2.WinZip程式将自动执行,并得到如下画面,点选Extract按钮进行解压缩工作。
3.你可以在Extract to中直接入要存放解横n案的目,也可以闹虚g的目^中cx,O定好後按Extracto,即_始M行解嚎s工作,
3.你可以在Extract to中直接输入要存放解压后档案的目录,也可以从中间的目录区中点选,设定好后按Extract钮,即开始进行解压缩工作。
4.窗右下角的B簦由t糇Gr就是已完成工作。4.当视窗右下角的状态灯,由红灯变绿灯时就是已经完成工作。
5.如下已利的n案解嚎s在C:Temp中。5.如下已经顺利的将档案解压缩在C:Temp中。
WinZip解压缩之操作教学 第2篇
Linux内核是采用deflate压缩规范进行压缩的, 这是一种无专利限制的非常好的通用压缩算法, 它采用lz77+Huffman编码的组合。对Linux内核解压的流程和defalte压缩规范做了介绍, 并对相应的数据结构以及实现解压的外围算法和代码作必要的分析。
2 内核解压缩流程
如图1所示。
boot/compressed/head.S调用boot/compressed/misc.c中的decompress_kernel () 函数。
对于小内核[zImage], decompress_kernel () 调用setup_normal_output_buffer () 设置解压缩输出的目标起始位置output_data=0x100000;对于大内核[bzImage], decompressed_kernel () 解压的目标代码分两块存放, 所以调用setup_output_buffer_if_we_run_high () 设置两个目标地址low_buffer_start=0x2000和high_buffer_start=max (&end+0x3000, 0x100000+low_buffer_size) 。低地址部分涵盖0x2000~0x90000, 大小是low_buffer_size;高地址是取&end+0x3000和0x100000+low_buffer_size中的较大值, 这样既能保证离自解压代码结束地址end后留有0x3000的堆空间 (HEAP_SIZE) , 又保证高解压数据部分距离0x100000有不小于low_buffer_size的距离, 这样就能使合并已解压的内核的时候不会被low_buffer的数据覆盖掉。此外, 因为在现阶段内核尚未正式运行, 因此解压过程中的内存分配是使用compressed/misc.c中的malloc () 实现的, malloc () 维护着堆的当前指针free_mem_ptr, free_mem_ptr的最初位置就是&end, 结束位置free_mem_end_ptr是两种情况之一, 小内核是0x90000, 大内核是&end+0x3000。malloc (size) 分配size字节的内存, 成功则free_mem_ptr后移size字节, 如果发现已经到了堆末尾 (free_mem_ptr>=free_mem_end_ptr时) 就报错。堆内存的回收函数free () 被定义为空操作。因为每次解压完一个压缩数据快block后, inflate () 函数都会调用gzip_release () 还原free_mem_ptr的值为初始值 (&end) 的, 而每次解压一个block前会先调用gzip_mark () 备份这个初始指针。
decompress_kernel () 调用linux/lib/inflate.c中定义的gunzip () , gunzip () 再调用inflate () 进行解压。压缩数据block (块) 为单位进行解码, inflate () 循环调用inflate_block () , 每次解压一个块。inflate_block () 先读取块头的几位, 第一位是结束块标志 (最后一个块) , 这个值会返回给调用者inflate () , 以决定是否结束循环;紧接着的两位是块类型, 块被分为3种类型, dynamic、stored和fixed, 分别表示为10、00、01, inflate_block () 根据不同的值分别调用inflate_dynamic () 、inflate_stored () 、inflate_fixed () 进行解码。stored块是没经过压缩的, 它的数据会原封不动地拷贝到输出端, 另两种类型的块的解压过程都会经过两个阶段:调用huft_build () 构造huffman解码表和调用inflate_codes () 进行解码, 这也是下面代码分析中要着重讨论的。
由于内核是压缩后与boot/compressed/head.S和boot/compressed/misc.c的目标代码连接的, 因此boot/compressed/misc.c中的decompress_kernl () 不可能调用到linux/lib/inflate.c中的gunzip () 及其他函数, 那这个函数怎么调用的呢?
在misc.c开始部分有#include"../../../../lib/inflate.c", 从而在预编译阶段, 就把linux/lib/inflate.c中的函数代码都包含进misc.c中了, 最后都编译在misc.o中, 因此解压阶段调用的是自己的gunzip () 。
3 deflate压缩规范
Linux内核解压缩的核心是inflate () , inflate是针对用deflate压缩规范的压缩数据进行解压的算法。因此, 要了解Linux内核的解压过程, 有必要了解deflate规范。deflate采用lz77压缩算法加上Huffman-Shannon-Fano[HSF]编码的组合。经典的lz77滑动窗口数据压缩算法使用一个32KB的滑动窗口window, 对于待压缩数据的下一个输入串, 寻找在滑动窗口中重复匹配的最长字串的起始位置和长度, 使用长度length和距离distance表示, 普通字符literal的值 (包括不可打印字符) 为0~255, 256用来表示block结束标记, 257~286表示长度码 (配合使用附加位) , 0~30表示距离码 (配合附加位) , 在此基础上对字符/长度和距离使用HSF编码。HSF编码是Huffman编码 (即最优可变长度前缀码) 的变种, 它除了Huffman编码的特点外, 码字是按二进制数值递增的。
inflate算法构建类似树的多级解码表, 每个子表使用编码中的连续几位形成的二进制为索引。首先读入初始长度 (比如最小码长) 的位串, 作为一级表的索引, 根据编码搜寻到的表项或者是叶子节点 (可以直接得出编码代表的值) , 或者指向下一级子表, 并指示接着要读入的编码位数 (作为下一级表的索引) , 最终达到解码的目的, 使字符/长度位串 (0~286) 和Huffman码一一对应, 根据Huffman码作为索引查询Huffman多级解码表最终可以确定原始值。inflate算法就是在lz77编码基础上对literal/length和distance再用Huffman编码。因此解码的过程必须是简而言之这样的过程:读入基本长度的位串 (比如Huffman编码的最小长度) , 以其值为索引检索literal/length的Huffman表, 得到1个huft结构指针t, 比较t->e的值, 有如下几种情况:
(1) =15表示本块block (inflate压缩数据由一个个数据块block组成) 结束;
(2) =16表示是0~256的字符值 (包括不可打印字符) , 直接输出到Window缓冲区;
(3) >16表示当前表示中间表, 以t->e-16的值为索引查询t->v.t指向的下一级huffman表;
(4) <16表示这个编码是代表最终值 (叶子节点) 的, 以t->v.n码得到长度的基本值, 再读入t->e个附加位, 两个值相加得到最终的lz77编码中的长度值。
distance的得到过程与上同。两个值length和distance都解出来后, 把window[ptr-distance]开始的length个字节拷贝到window[ptr]处, 并更新ptr指针 (后移length个位置) 。
注:这里的ptr指缓冲区 (或称为滑动窗口) 的当前位置。每次达到最大值 (Window填满) 后输出被解压的数据到目的位置output_data, 然后Window指针清0[缓冲数据仍保留直到被覆盖]。另外这里的huffman码使用的倒序编码, 即最小值的位LSB (least signified bit) 在最左边, 最大值的位MSB (most signified bit) , 详见代码分析。
比如对于最小长度是7位的9位编码1010110 01来说, 实际编码是这样的10 0110101, 先读入7位0110101为索引访问第一级huffman表, 得huft结构指针t, t->e应该=16+2, 表示还要读入2位索引, 于是再读入10, 以它的值2为索引检索t->v.t指向的二级表 (二级表一般有很多张, 1个一级表项就可以指向一个二级表) , 得到新的t, 这时t->e因该是小于16的, 指示number of extra bits (附加位数) , t->v.n指示基本值。
4 数据完整性校验
压缩解压过程必然伴随着数据完整性的验证, gunzip中使用了32位循环冗余校验外加数据长度的检查。在压缩数据的过程中, 压缩程序会不断地计算crc值, 直到压缩过程结束, 然后在最后一个压缩数据块之后写入32位长度的crc校验值和32位的数据原始长度值。在解压时, 解压程序也会计算crc值, 当解完最后一个数据块后gunzup会读入最后一个压缩数据块之后的原始crc值和原始长度值, 把解压生成的crc值和解压的数据长度分别与之对比, 都一致的话就表明解压成功了。以下是32位CRC校验使用的多项式:
5 HSF编码
5.1 概述
deflate压缩规范使用的是Huffman编码的一个变种, 全称是Huffman-Shannon-Fano编码, 它除了具有Huffman编码的优点外, 还有个特点就是随着码长由小到大, 编码的二进制数值也是从小到大递增的, 同一码长下数值呈连续分布。这个特点使得只要确定了编码的码长, 就可以算出编码。
5.2 生成HSF编码的例程
这是来自RFC1951文档的一段例程, 演示了deflate中使用的Huffman编码的生成方法。学过数据结构的都知道, 一般的Huffman编码是变长前缀码, 是通过构造一棵Huffman树生成的。这里的HSF编码增加了两个附加规则:
所有给定长度的编码都是按字典序连续, 和它们所代表的字符顺序相同。短编码在长编码前面。
HSF的编码方法码长和码值是顺次增加的, 最小最短的码排在最前, 0作为第一个码长的编码, 同样长度的码数值连续递增, 等该长度的编码数达到预定值后, 先递增编码 (加1) , 然后移动一位 (方向取决于编码顺序) , 作为下个编码长度的首个编码。按照这种方法就能生成变长前缀码。
再来看上述代码, 代码5.1根据bl_count[]数组中预先确定的各码长编码数把各个码长的最小编码保存在next_code[bits]数组中 (bits代表码长) 。代码5.2根据next_code[]数组生成广义字符0~max_code的编码。从代码可知, 编码总是从0开始。
比如码长分别为3、4、5, 其编码数bl_count[3]=3, bl_count[4]=2, bl_count[5]=4。那么next_code[3]=000, next_code[4]=0110, next_code[5]=10100, 顺次下来的编码清单是这样的:
最后生成的编码依次是:000、001、010、0110、0111、10000、10001、10010、10011。
5.3 HSF解码表
5.3.1 HSF解码表结构
和传统Huffman编码算法使用的二叉树不同, HSF解码使用的是多级表结构, 每级表使用HSF编码中的连续几位作为索引, 找到表项, 这是一个huft结构, 如果已经读入了编码的所有码长, 那么这个表项指示的将是解码的值, 否则该表项指示下一级子表以及要读入的后续码长 (作为子表的索引) , 直至读完一个编码的所有码长。表结构如图2所示。
L是解压程序设置的解码表码长, 在构建解码表时如果超出实际码长的范围会被调整为最小或最大码长, 当L=最大码长时, 就只存在一级表了。
5.3.2 表项huft结构定义
多级解码表的每一个表项都是一个huft结构。
e是一个多用途字段:
(1) >16表示当前表项有子表, 接着读入 (e-16) 位作为索引查询v.t指向的下一级表。
(2) =16表示已达叶节点, 码值就是v.n, 直接输出到Window缓冲区。
(3) =15表示block结束。
(4) <15表示已达叶节点, 但是码值不是简单值。需要通过v.n得到基本值, 再读入e个附加位, 两个值相加得到最终的解码值。
b是本表项涵盖的码长, 也是解码过程中阶段性需要丢弃的位数 (解完一部分就丢掉) 。v是一个union类型, 当表项是非叶结点时, v.t指向下一级子表;否则, v.n指示了码值 (或者基本值) 。
5.3.3 解码表的内存分配与回收
解压阶段创建huffman解码表时使用malloc () 分配内存malloc () 维护一个堆指针free_mem_ptr, 指向下次分配内存开始的地址。堆位于内核上方的内存空间。
堆指针free_mem_ptr初始指向end, end是内核编译连接时连接程序生成的符号, 指向自解压内核的结束地址+1。
内存回收函数free () 是空操作, 实际的内存回收通过恢复堆指针实现的, 这发生于每次解码完一个块之后。
6 代码分析
(1) 解压缩主函数decompress_kernel ()
(2) 解压缩数据输出缓冲区的设置
1) 小内核的解压数据输出的目标地址指针output_data被设置为内存1MB处 (0x100000)
2) 大内核的输出缓冲区
如果是大内核, 解压数据有两个地方存放, 低部缓冲区low_buffer和高部缓冲区high_buffer。对于大内核, decompress_kernel () 调用setup_output_buffer_if_we_run_high () 函数设置moveparams结构参数, 其中包括了高、低两块缓冲区的起始地址和大小。
这个结构是head.S中调用decompress_kernel () 时压入堆栈中的, 函数返回后, 根据函数的返回值 (eax=high_loaded) , 不为0的话转去执行合并程序。合并程序根据这个堆栈中的缓冲区结构参数将两个缓冲区中的解压数据合并成完整的内核, 并放到内存地址从0x100000开始的地方。以下是setup_output_buffer_if_we_run_high () 函数分析:
3) 解压后设置两个解压缓冲区的大小
decompress_kernel () 调用gunzip () 解压内核成功后, 如果是大内核, 还要设置解压数据所在的两个缓冲区 (low_buffer和high_buffer) 的大小, 这是由下面的函数实现的:
(3) 读入压缩内核数据
1) 获取1字节get_byte ()
内核解压缩时是用get_byte () 读入字节数据。这是一个宏定义, 如下:
fill_inbuf () 的功能是初始化数据指针inbuf, 指向input_data, 大小是input_len。这里的input_data和input_len都是连接程序ID生成的符号, input_data指示压缩内核所在的内存地址, input_len是压缩内核的长度。首次运行get_byte () 时, inptr=insize=0, 所以调用fill_inbuf () 设置inbuf指针并返回压缩数据的第一个字节inbuf[0], 以后则返回后续字节inbuf[inptr++]。
以下是compressed/Makefile中连接脚本, 可以看一下input_data和input_len是如何产生的:
内核目标代码system首先被去掉elf文件头存为$$tmppiggy, 然后被压缩为$$tmppiggy.gz, 使用连接脚本$$tmppiggy.lnk连接为elf格式文件piggy.o, 该文件只包含一个数据段, 就是上述被压缩的内核$$tmppiggy.gz, 并引出符号input_data和input_len分别表示数据部分的地址和大小, ID程序把boot/compressed/head.o、boot/compressed/和自解压内核连接完成后。
注:inbuf[0]是压缩内核的第一个字节, inbuf[inptr++]指向后续字节 (inptr=1起) 。
2) 获取任意n位二进制 (n范围是1~32)
仅有读入1字节的宏get_byte () 还是不够的, 因为HSF编码是变长的, 有的码长不足1字节, 有的码长超过1字节, 因此编码是跨字节边界的, 需要有获取指定位长二进制的手段。
如何获取并操作跨字节边界的位串?通过定义NEEDBITS和DUMPBITS两个宏获取和丢弃指定数量的位, 实现对位操作的扩展。
b是解压程序中定义的32位变量, 作为读入位串的缓存, k指示其中的有效数据位数。NEEDBITS (n) 获取b中的n位, 它首先判断b中的数据位数k是否不足n, 当不足n时, 循环调用get_byte () 读取字节, 并调整k=k+8, 直至有效位数k>=n为止。此时b中的低n位 (0~n-1位) 就是解压程序所需的n位数据。DUMPBITS (n) 宏则通过右移位丢弃b中的低n位, 并调整k=k-n (表示有效位数减少n位) 。
通过以上的宏, 虽然每次还是读取8B, 但是一次可以对跨字节边界的n位进行操作了 (n=1~32) 。
(4) 输出解码数据
解压过程中, 使用32KB的滑动窗口作为输出的缓存。这是在boot/compressed/misc.c中定义的:
inflate.c中的解压函数在解码出一个字节数据后, 就拷贝到滑动窗口中 (slide[w++]=?) , 局部变量w保存窗口中的当前写入位置;而slide是定义为Window的宏。
当滑动窗口已满就要把其中的解压数据拷贝到输出缓冲区去, 这是通过如下代码实现的:
内核在输出解压数据的时候对滑动窗口中的解码数据进行crc校验值的计算。以下是输出代码清单:
在解压阶段, 程序维持着几个指针。首先是输入数据的指针inptr, 解码前通过inbuf[inptr++]读取数据并后移指针;其次是滑动窗口指针w, 解码后slide[w++]保存解码数据并后移指针w;最后是输出缓冲区指针, 小内核使用output_ptr, 通过out=&output_data[output_ptr];得到当前输出的起始地址, 输出后后移output_ptr指针, 大内核不使用output_ptr而直接使用并调整output_data指针。
(5) makecrc ()
该函数生成8位二进制1~256的32位crc码。crc_32_tab[i]中保存各字符的32位crc值。
参考文献
[1]Linux内核2.4.0源代码, 可以从www.kernel.org下载.
[2]RFC1951 (Request For Comment 1951) .