Gzip格式分析

GZIP文件格式简介

GZIP最早由Jean-loup Gailly和Mark Adler创建,用于UNIX系统的文件压缩。我们在Linux中经常会用到后缀为.gz的文件,它们就是GZIP格式的。现今已经成为Internet上使用非常普遍的一种数据压缩格式,或者说一种文件格式。HTTP协议上的GZIP编码是一种用来改进WEB应用程序性能的技术。大流量的WEB站点常常使用GZIP压缩技术来让用户感受更快的速度。

GZIP本身只是一种文件格式,其内部通常采用DEFLATE数据格式,而DEFLATE采用LZ77压缩算法来压缩数据。

GZIP文件由1到多个“块”组成,实际上通常只有1块。每个块包含头、数据和尾三部分。块的概貌如下:

+---+---+---+---+---+---+---+---+---+---+========//========+===========//==========+---+---+---+---+---+---+---+---+
|ID1|ID2| CM|FLG|     MTIME     |XFL| OS|   额外的头字段   |       压缩的数据      |     CRC32     |     ISIZE     |
+---+---+---+---+---+---+---+---+---+---+========//========+===========//==========+---+---+---+---+---+---+---+---+

1. 头部分

ID1与ID2:各1字节。固定值,ID1 = 31 (0x1F),ID2 = 139(0x8B),指示GZIP格式。 CM:1字节。压缩方法。目前只有一种:CM = 8,指示DEFLATE方法。 FLG:1字节。标志。

bit 0 FTEXT - 指示文本数据
bit 1 FHCRC - 指示存在CRC16头校验字段
bit 2 FEXTRA - 指示存在可选项字段
bit 3 FNAME - 指示存在原文件名字段
bit 4 FCOMMENT - 指示存在注释字段
bit 5-7 保留

MTIME:4字节。更改时间。UINX格式。 XFL:1字节。附加的标志。当CM = 8时,XFL = 2 - 最大压缩但最慢的算法;XFL = 4 - 最快但最小压缩的算法 OS:1字节。操作系统,确切地说应该是文件系统。有下列定义:

0 - FAT文件系统 (MS-DOS, OS/2, NT/Win32)
1 - Amiga
2 - VMS/OpenVMS
3 - Unix
4 - VM/CMS
5 - Atari TOS
6 - HPFS文件系统 (OS/2, NT)
7 - Macintosh
8 - Z-System
9 - CP/M
10 - TOPS-20
11 - NTFS文件系统 (NT)
12 - QDOS
13 - Acorn RISCOS
255 - 未知

额外的头字段:

(若 FLG.FEXTRA = 1)

+---+---+---+---+===============//================+
|SI1|SI2|  XLEN |      长度为XLEN字节的可选项     |
+---+---+---+---+===============//================+

(若 FLG.FNAME = 1)

+=======================//========================+
|               原文件名(以NULL结尾)              |
+=======================//========================+

(若 FLG.FCOMMENT = 1)

+=======================//========================+
|   注释文字(只能使用iso-8859-1字符,以NULL结尾)  |
+=======================//========================+

(若 FLG.FHCRC = 1)

+---+---+
| CRC16 |
+---+---+

存在额外的可选项时,SI1与SI2指示可选项ID,XLEN指示可选项字节数。如 SI1 = 0x41 ('A'),SI2 = 0x70 ('P'),表示可选项是Apollo文件格式的额外数据。

2. 数据部分

DEFLATE数据格式,包含一系列子数据块。子块概貌如下:

+......+......+......+=============//============+
|BFINAL|    BTYPE    |            数据           |
+......+......+......+=============//============+
BFINAL:1比特。0 - 还有后续子块;1 - 该子块是最后一块。 BTYPE:2比特。00 - 不压缩;01 - 静态Huffman编码压缩;10 - 动态Huffman编码压缩;11 - 保留。

各种情形的处理过程,请参考后面列出的RFC文档。

3. 尾部分

CRC32:4字节。原始(未压缩)数据的32位校验和。 ISIZE:4字节。原始(未压缩)数据的长度的低32位。

GZIP中字节排列顺序是LSB方式,即Little-Endian,与ZLIB中的相反。

下面是GZIP文件 gzip-1.3.3.tar.gz 格式的简要分析示意图:

GZIP与ZLIB有着很深的渊源。有关ZLIB, GZIP以及DEFLATE等更加详细的说明,可参考RFC 1950-1952。从这些文档里也能找到其它的参考文献。

GZIP已成为GNU Project的一个组成部分,其官方站点为www.gzip.org。在这里可以下载到GZIP源码。目前最新版本是1.2.4,以及beta版的1.3.3。

 

 
gzip文件格式解析及源代码分析  

2011-07-26 10:20:54|  分类: linux学习 |  标签:gzip  gzip-1.2.4  源代码  文件格式  分析   |字号 订阅

在windows下使用UltraEdit打开后缀的.gz的文件显示的数据格式如下:
2 bytes  GZIP标志字节:0x1f, 0x8b (\037 \213)  
1 byte   压缩方法: (0..7 reserved, 8 = deflate)
1 byte   标志位:
            bit 0 set: 文件可能是ASCII文本文件
            bit 1 set: 附加多个gzip文件部分
            bit 2 set: 存在有可选的附加 内容
            bit 3 set: 提供了原始的文件名称
            bit 4 set: 则提供有一个O-终结的文件内容
            bit 5 set: 文件被加密
            bit 6,7:   保留
4 bytes  文件更改时间(Unix时间)
1 byte   额外的标志,决定了压缩方法。 2:使用最大的压缩,最慢的算法
                                4:采用最快的算法
1 byte   这个标志指明了进行压缩时系统的类型。
                 0 - FAT filesystem (MS-DOS, OS/2, NT/Win32)
                 1 - Amiga
                 2 - VMS (or OpenVMS)
                 3 - Unix
                 4 - VM/CMS
                 5 - Atari TOS
                 6 - HPFS filesystem (OS/2, NT)
                 7 - Macintosh
                 8 - Z-System
                 9 - CP/M
                10 - TOPS-20
                11 - NTFS filesystem (NT)
                12 - QDOS
                13 - Acorn RISCOS
               255 - unknown
2 bytes  optional part number (second part=1) 可选的序号
2 bytes  optional extra field length        可选的附加内容的长度
? bytes  optional extra field           可选的附加内容
? bytes  optional original file name, zero terminated  
可选的原始文件名称,以'\0'结束   
? bytes  optional file comment, zero terminated  
可选文件内容(这部分不被解释,而是可读的供人使用的,以'\0'结束
12 bytes optional encryption header      
? bytes  compressed data
4 bytes  crc32                    这个是未压缩数据的循环冗余校验值。
4 bytes  uncompressed input size modulo 2^32    这是原始数据的长度以2的32次方为模的值。
 
设计了一种可以单向编码的格式,而不用反向查找,也不用预知未压缩数据及输出的
已压缩数据的大小。如果输入的数据不是一个文件,那么修改时间被设置为压缩的开
始时间。
The format was designed to allow single pass compression without any
backwards seek, and without a priori knowledge of the uncompressed
input size or the available size on the output media. If input does
not come from a regular disk file, the file modification time is set
to the time at which compression started.
 
时间戳主要是用在在网络上传输gzip文件的情况下。在这种情况下,它不需要保存所有
者的属性。在本地传输的时候,所有者的属性在压缩/解压缩时由gzip所保存。忽略值为
0的时间戳。 
The time stamp is useful mainly when one gzip file is transferred over
a network. In this case it would not help to keep ownership
attributes. In the local case, the ownership attributes are preserved
by gzip when compressing/decompressing the file. A time stamp of zero
is ignored.
 
标志位中,值为0的位是可选的,它可以使我们对输入的数据做一个预先的了解。在不
确定的时候,要将标志位清除。对有不同文件格式(文本文件和二进制文件)的系统来说,
解码时,可以使用标志位来选择不同的格式。
Bit 0 in the flags is only an optional indication, which can be set by
a small lookahead in the input data. In case of doubt, the flag is
cleared indicating binary data. For systems which have different
file formats for ascii text and binary data, the decompressor can
use the flag to choose the appropriate format.
 
如果有附加内容,则它必须包含一个或多个子字段,每个子字段有如下格式:
The extra field, if present, must consist of one or more subfields,
each with the following format:
 
  subfield id   : 2 bytes    子字段ID
  subfield size : 2 bytes  (little-endian format)子字段长度(小端字节序)
  subfield data        子字段内容
 
    子字段ID可以包含两个可记住的字母。请发送一些这样的ID给[email protected]
第二个字节为0的ID是被保留的。定义了如下的ID
The subfield id can consist of two letters with some mnemonic value.
Please send any such id to [email protected]. Ids with a zero second
byte are reserved for future use. The following ids are defined:
 
  Ap (0x41, 0x70) : Apollo file type information
 
    子字段长度是子字段内容的长度,不包含ID及子字段长度这四字节。但是
前面所说的 "可选的附加内容的长度 "则包含了ID及子字段长度的四字节。
The subfield size is the size of the subfield data and does not
include the id and the size itself. The field 'extra field length' is
the total size of the extra field, including subfield ids and sizes.
 
必须可以在压缩数据中找到数据结束的位置,而不论数据的实际长度是多少。如果压缩
数据不能够放到一个文件中(如磁盘的情况),每一部分都要由一个头字段开始,但是只
有最后一部分中有CRC32和原始数据的长度。 解压程序应该可以提示输入另外的,存在
于多个压缩文件中的数据。这是必要,但不是绝对的,因为当一部分数据毁坏时,还需
要得到其它部分的内容。
It must be possible to detect the end of the compressed data with any
compression format, regardless of the actual size of the compressed
data. If the compressed data cannot fit in one file (in particular for
diskettes), each part starts with a header as described above, but
only the last part has the crc32 and uncompressed size. A decompressor
may prompt for additional data for multipart compressed files. It is
desirable but not mandatory that multiple parts be extractable
independently so that partial data can be recovered if one of the
parts is damaged. This is possible only if no compression state is
kept from one part to the other. The compression-type dependent flags
can indicate this.
 
如果压缩文件的系统对文件名的大小写不敏感,则原始文件名会会强制转换成小写。
如果是从标准输入读入的数据,则没有原始文件名。
If the file being compressed is on a file system with case insensitive
names, the original name field must be forced to lower case. There is
no original file name if the data was compressed from standard input.
 
即使压缩后的文件会比原来的文件大,压缩还是会完成的。
Compression is always performed, even if the compressed file is
slightly larger than the original. The worst case expansion is
a few bytes for the gzip file header, plus 5 bytes every 32K block,
or an expansion ratio of 0.015% for large files. Note that the actual
number of used disk blocks almost never increases.
 
The encryption is that of zip 1.9. For the encryption check, the
last byte of the decoded encryption header must be zero. The time
stamp of an encrypted file might be set to zero to avoid giving a clue
about the construction of the random header.

gzip-1.2.4程序分析

一点说明:
    在gzip.c中:
DECLARE(uch, inbuf,  INBUFSIZ +INBUF_EXTRA);
DECLARE(uch, outbuf, OUTBUFSIZ+OUTBUF_EXTRA);
DECLARE(ush, d_buf,  DIST_BUFSIZE);
DECLARE(uch, window, 2L*WSIZE);
#ifndef MAXSEG_64K
       DECLARE(ush, tab_prefix, 1L< #else
    DECLARE(ush, tab_prefix0, 1L<<(BITS-1));
    DECLARE(ush, tab_prefix1, 1L<<(BITS-1));
#endif
    实际上定义了一些数组:inbuf,outbuf,d_buf,window,tab_prefix,tab_prefix0,tabfix1.
1/
==================================================================================
入口程序:gzip-1.2.4/gzip.c
函数:  int main (argc, argv)
         int argc;
         char **argv;
功能:  1)通过命令内容(gzip,gunzip,unzip等),设置操作类型(压缩或是解压缩)。
    2)通过参数,设置一些全局变量的值,对我们而言,有用的是:ascii(表示
为文本文件,可以根据本地的换行符来代替解压后的文件中的换行符)、decompress(表示进行解压操作)和level(转换操作的级别 进行更快
的转换还是进行更大压缩比的转换,当然,这只对压缩而言)。
    3)为输入、输出及窗口的缓冲分配内存。
    4)调用treat_file(argv[optind++]);对文件进行操作。
 
 
2/
==================================================================================
函数:  local void treat_file(iname)
         char *iname;
参数:  为文件名称;
功能:  1)得到输入的文件的状态:name,size,time,mode等。
    2)创建输出文件的名称。
    3)当进行解压操作时,调用    local int get_method(in) 来得到gz文件的压缩方法。
    4)如果命令行中的参数-l,则调用do_list()显示文件信息。
    5)调用local int create_outfile()创建输出文件。
    6) 调用(*work)(ifd, ofd)进行压缩、解压缩的操作。这时的work指针被get_method()
函数置为unzip()函数(解压时),或是为默认的zip()函数。在解压缩时,
这个过程是在循环中的,因为可能会包含多个文件。
 
 
3/
==================================================================================
函数:  local int get_method(in)
        int in;        /* input file descriptor */
参数:  文件名称
功能:  1)验证第一第二字节是否为0x1F,0x8B。
    2)验证第三字节是否为0x08(deflate)。
    3)设置函数指针work = unzip。(work的默认值是zip)
    4)得到做为flags的第四字节。
    5)如果设置了第1、5、6、7位,则给出错误提示。(编号0到7是从最低位开始)
    6)将第5到8字节中的时间值保存在全局变量time_stamp中。
    7)跳过第9字节(压缩时采用的算法 更快或是比例更高)和
第10字节(压缩时的操作系统)。
    8)如果设置了flags的第1位,则得到当前文件的编号
    9)如果设置了flags的第2位(存在有附加的内容),则得到附加内容的长度,
并跳过这部分内容。
    10)如果设置了flags的第3位(存在有原始文件的名称),则得到原始文件的名称。
    11)如果设置了flags的第4位(存在一段不用解析的内容,是给人提供可读信息的),
跳过这部分可读信息。
    12) 设置头部信息的长度:header_bytes,包括了最后的CRC及文件长度部分。
返回:  函数压缩方法(一般为"deflate ",程序中的返回值为8)
 
 
4/
==================================================================================
在文件gzip-1.2.4/unzip.c中:
函数:  int unzip(in, out)
       int in, out;   /* input and output file descriptors */
参数:为输入、输出文件。
功能:  1)初始化全局变量crc。
    2)调用函数inflate()进行解码操作。
    3)得到原来文件中保存的CRC及长度值。如果与当前计算出的值不同,则产生提示。
 
 
5/
==================================================================================
在文件gzip-1.2.4/inflate.c中:
函数:  int inflate()
说明: ulg bb;                         /* 是 bit buffer */
unsigned bk;                    /* 是bit buffer中还有多少位,即剩余的位数 */
功能:  1) 循环调用inflate_block(&e),一块一块的解压数据。
   2)若bk>-8,即bb中有完整的字节,则将此字节放回输入中。
    3)输出解压得到的内容。
 
 
6/
==================================================================================
在文件gzip-1.2.4/inflate.c中:
函数:  int inflate_block(e)
int *e;                 /* last block flag */
参数:如果是1,是说明当前块是最后一块。
功能:  1)得到第一位,这一位说明当前块是否为最后一块(0,不是;1,是)并相应的设置参数。
    2)得到下两位的值: 
0,本块没有压缩,
1,用固定的Huffman编码压缩,见RFC1951的3.2.6节。
2,用动态的Huffman编码压缩,见RFC1951的3.2.7节。
    3)根据前面得到的值,调用不同的函数解压:
        inflate_stored();    对于未压缩的数据,调用这个函数。
        inflate_fixed(); 对于用固定的Huffman编码压缩的数据,调用这个函数。
        inflate_dynamic();    对于用动态的Huffman编码压缩的数据,调用这个函数。
 
 
7/  
==================================================================================
在文件gzip-1.2.4/inflate.c中:
函数:  int inflate_stored()
功能:  处理非压缩的数据内容
1)丢弃不足一字节的位。由于非压缩的数据中,内容都是以字节为单位的,所以原来按
        位读取的时候,会剩余不足一字节位内容,现在要去掉这些位。
    2)读入两字节的内容,其值是未压缩的数据长度。再读入两字节的内容,其值应该是前
        两字节所表示的长度的补码,若不是,则错误。
    3)逐字节的读入内容,并输出到输出文件中。
 
 
8/
==================================================================================
在文件gzip-1.2.4/inflate.c中:
函数:  int inflate_fixed()
功能:  用固定的Huffman编码压缩的数据
1)     为0至287的文字/length值设定编码长度:
                   Lit Value    Bits        Codes
                   ---------    ----        -----
                     0 - 143     8          00110000 through
                                            10111111
                   144 - 255     9          110010000 through
                                            111111111
                   256 - 279     7          0000000 through
                                            0010111
                   280 - 287     8          11000000 through
                                            11000111
2)     调用huft_build()建造文字/length值的Huffman树
3)     设置所有distance值(从0至29)的编码长度为5。
4)     调用huft_build()建造distance值的Huffman树
5)     调用函数inflate_codes()进行解码。
 
 
9/
==================================================================================
在文件gzip-1.2.4/inflate.c中:
函数:  int inflate_dynamic()
功能:  用动态的Huffman编码压缩的数据
1)读入5位的值HLIT,算出nl = 257+HLIT。这是需要编码的最大值。
2)读入5位的值HDIST,算出nd = 1+HDIST。这是distance的最大值。
3)读入4位的值HCLEN,算出nb = 4+HCLEN。说明有多少种编码长度。
4)再读入3*nb位,每三位的值表示用多少位来表示所对应的编码长度。
5)调用huft_build()建造编码长度的Huffman树。
6)利用这个Huffman树,对接下来的若干位解码出nl+nd个值,这些值依次是0~nl-1
的编码长度(对于文字/length平说),及0~nd-1的编码长度(对于distance来说)。
7)利用上面解码出的两组长度值,两次调用huft_build()函数,建造两个Huffman树
(一个是为文字/length,另一个是为distance)。
8)调用函数inflate_codes()进行解码。
 
 
10/
==================================================================================
在文件gzip-1.2.4/inflate.c中:
函数:  int inflate_codes(tl, td, bl, bd)
struct huft *tl, *td; /* literal/length and distance decoder tables */
int bl, bd;            /* number of bits decoded by tl[] and td[] */
参数:  tl,td是进行Huffman编码解码时用到的结构体,由于length和distance用不同的编码
方式,所以要有两个指针进行解码。
    在两种编码中,用struct huft结构编码时,分别以bl,bd位进行编码。
功能:  用两个以经做好的链表来进行解码。
1)  解码一个值X,如果0<=X<=255,则X是一个字符,输出,循环1)。
2)  如果X==255,则说明块结束,函数返回。
3)  X>255,则说明读到的是一个length值,根据这个值,及其后的附加位,得到真实的
length值。
4)  继续读入一个值,这个值是distance的标志值,根据这个值及其后的附加位得到真实
的distance。
5)  在已经输出的串中,向前查找distance个字节,拷贝length个字节到输出串的末尾。
6)  循环1)
 
 
11/
==================================================================================
在文件gzip-1.2.4/inflate.c中:
函数:  int huft_build() 和函数int huft_free()比较独立,可以直接引用,不再分析。
功能:  int huft_build()    :建立Huffman解码链表。
int huft_free() :清除链表。
12/
==================================================================================
在文件gzip-1.2.4/zip.c中:
函数:  int zip(in, out)
       int in, out;   /* input and output file descriptors */
参数:为输入、输出文件。
功能:  
1)  向输出写入三字节:0x1F 0x8B 0x08。
2)  向输出写入一个含有8个标志位的字节。
3)  向输出写入4字节的系统时间。
4)  初始化CRC的值。
5)  调用bi_init(out)初始化读入位串的程序。
6)  调用ct_init()进行分配内存,初始化变量表,保存原始文件信息的
操作。
7)  调用lm_init()为新文件初始化"最长匹配"的程序。
8)  再向输出写入2字节,一个为额外的标志,一个为系统类型。
9)  如果需要,则保存原始文件名称。
10) 保存头部信息的长度。
11) 调用函数deflate()压缩。
12) 写入4字节的CRC值。
13)   写入4字节的原始内容长度值。
14)修改前面保存的头部信息长度的值。
 
 
13/
==================================================================================
在文件gzip-1.2.4/deflate.c中:
函数:  ulg deflate()
功能:  压缩数据。此函数通过一些复杂的算法来进行压缩操作,可以直接引用。
1)     如果需要快速压缩,则调用函数deflate_fast(),然后返回。
2)     将当前内容插入到哈希表中,并查找最长匹配。
3)     若找到匹配内容,则输出对的编码,否则输出字符编码。
 
 
14/
==================================================================================
在文件gzip-1.2.4/deflate.c中:
函数:  ulg deflate()
功能:  压缩数据。此函数通过一些稍简单一些的算法来进行压缩操作,可以直接引用。
1)将当前内容插入到哈希表中,并查找最长匹配。
2)若找到匹配内容,则输出对的编码,否则输出字符编码。

你可能感兴趣的:(Linux嵌入式系统相关)