热门关键字:  ubuntu  分区  Fedora  linux系统进程  函数

当前位置 :| 主页>Linux教程>内核研究>

分析内核对gzip压缩文件进行解压的方法

来源: 作者: 时间:2007-07-14 Tag: 点击:

概述
----
1) Linux的初始内核映象以gzip压缩文件的格式存放在zImage或bzImage之中, 内核的自举
代码将它解压到1M内存开始处. 在内核初始化时, 如果加载了压缩的initrd映象, 内核会将
它解压到内存盘中, 这两处解压过程都使用了lib/inflate.c文件.

2) inflate.c是从gzip源程序中分离出来的, 包含了一些对全局数据的直接引用, 在使用时
需要直接嵌入到代码中. gzip压缩文件时总是在前32K字节的范围内寻找重复的字符串进行
编码, 在解压时需要一个至少为32K字节的解压缓冲区, 它定义为window[WSIZE].
inflate.c使用get_byte()读取输入文件, 它被定义成宏来提高效率. 输入缓冲区指针必须
定义为inptr, inflate.c中对之有减量操作. inflate.c调用flush_window()来输出window
缓冲区中的解压出的字节串, 每次输出长度用outcnt变量表示. 在flush_window()中, 还必
须对输出字节串计算CRC并且刷新crc变量. 在调用gunzip()开始解压之前, 调用makecrc()
初始化CRC计算表. 最后gunzip()返回0表示解压成功.

3) zImage或bzImage由16位引导代码和32位内核自解压映象两个部分组成. 对于zImage, 内
核自解压映象被加载到物理地址0x1000, 内核被解压到1M的部位. 对于bzImage, 内核自解
压映象被加载到1M开始的地方, 内核被解压为两个片段, 一个起始于物理地址0x2000-0x90000,
另一个起始于高端解压映象之后, 离1M开始处不小于低端片段最大长度的区域. 解压完成后,
这两个片段被合并到1M的起始位置.

解压根内存盘映象文件的代码
--------------------------

; drivers/block/rd.c
#ifdef BUILD_CRAMDISK

/*
* gzip declarations
*/

#define OF(args) args ; 用于函数原型声明的宏

#ifndef memzero
#define memzero(s, n) memset ((s), 0, (n))
#endif

typedef unsigned char uch; 定义inflate.c所使用的3种数据类型
typedef unsigned short ush;
typedef unsigned long ulg;

#define INBUFSIZ 4096 用户输入缓冲区尺寸
#define WSIZE 0x8000 /* window size--must be a power of two, and */
/* at least 32K for zip's deflate method */

static uch *inbuf; 用户输入缓冲区,与inflate.c无关
static uch *window; 解压窗口

static unsigned insize; /* valid bytes in inbuf */
static unsigned inptr; /* index of next byte to be processed in inbuf */
static unsigned outcnt; /* bytes in output buffer */
static int exit_code;
static long bytes_out; 总解压输出长度,与inflate.c无关
static struct file *crd_infp, *crd_outfp;

#define get_byte() (inptr < insize ? inbuf[inptr++] : fill_inbuf()) 读取输入缓冲区中一个字节

/* Diagnostic functions (stubbed out) */ 一些调试宏
#define Assert(cond,msg)
#define Trace(x)
#define Tracev(x)
#define Tracevv(x)
#define Tracec(c,x)
#define Tracecv(c,x)

#define STATIC static

static int fill_inbuf(void);
static void flush_window(void);
static void *malloc(int size);
static void free(void *where);
static void error(char *m);
static void gzip_mark(void **);
static void gzip_release(void **);

#include "../../lib/inflate.c"

static void __init *malloc(int size)
{
return kmalloc(size, GFP_KERNEL);
}

static void __init free(void *where)
{
kfree(where);
}

static void __init gzip_mark(void **ptr)
{
; 读取用户一个标记
}

static void __init gzip_release(void **ptr)
{
; 归还用户标记
}


/* ===========================================================================
* Fill the input buffer. This is called only when the buffer is empty
* and at least one byte is really needed.
*/
static int __init fill_inbuf(void) 填充输入缓冲区
{
if (exit_code) return -1;

insize = crd_infp->f_op->read(crd_infp, inbuf, INBUFSIZ,
&crd_infp->f_pos);
if (insize == 0) return -1;

inptr = 1;

return inbuf[0];
}

/* ===========================================================================
* Write the output window window[0..outcnt-1] and update crc and bytes_out.
* (Used for the decompressed data only.)
*/
static void __init flush_window(void) 输出window缓冲区中outcnt个字节串
{
ulg c = crc; /* temporary variable */
unsigned n;
uch *in, ch;

crd_outfp->f_op->write(crd_outfp, window, outcnt, &crd_outfp->f_pos);
in = window;
for (n = 0; n < outcnt; n++) {
ch = *in++;
c = crc_32_tab[((int)c ^ ch) & 0xff] ^ (c >> 8); 计算输出串的CRC
}
crc = c;
bytes_out += (ulg)outcnt; 刷新总字节数
outcnt = 0;
}

static void __init error(char *x) 解压出错调用的函数
{
printk(KERN_ERR "%s", x);
exit_code = 1;
}

static int __init
crd_load(struct file * fp, struct file *outfp)
{

int result;

insize = 0; /* valid bytes in inbuf */
inptr = 0; /* index of next byte to be processed in inbuf */
outcnt = 0; /* bytes in output buffer */
exit_code = 0;
bytes_out = 0;
crc = (ulg)0xffffffffL; /* shift register contents */

crd_infp = fp;
crd_outfp = outfp;
inbuf = kmalloc(INBUFSIZ, GFP_KERNEL);
if (inbuf == 0) {
printk(KERN_ERR "RAMDISK: Couldn't allocate gzip buffer\n");
return -1;
}
window = kmalloc(WSIZE, GFP_KERNEL);
if (window == 0) {
printk(KERN_ERR "RAMDISK: Couldn't allocate gzip window\n");
kfree(inbuf);
return -1;
}
makecrc();
result = gunzip();
kfree(inbuf);
kfree(window);
return result;
}

#endif /* BUILD_CRAMDISK */


32位内核自解压代码
------------------

; arch/i386/boot/compressed/head.S
.text

#include
#include

.globl startup_32 对于zImage该入口地址为0x1000; 对于bzImage为0x101000

startup_32:
cld
cli
movl $(__KERNEL_DS),%eax
movl %eax,%ds
movl %eax,%es
movl %eax,%fs
movl %eax,%gs

lss SYMBOL_NAME(stack_start),%esp # 自解压代码的堆栈为misc.c中定义的16K字节的数组
xorl %eax,%eax
1: incl %eax # check that A20 really IS enabled
movl %eax,0x000000 # loop forever if it isn't
cmpl %eax,0x100000
je 1b

/*
* Initialize eflags. Some BIOS's leave bits like NT set. This would
* confuse the debugger if this code is traced.
* XXX - best to initialize before switching to protected mode.
*/
pushl $0
popfl
/*
* Clear BSS 清除解压程序的BSS段
*/
xorl %eax,%eax
movl $ SYMBOL_NAME(_edata),%edi
movl $ SYMBOL_NAME(_end),%ecx
subl %edi,%ecx
cld
rep
stosb
/*
* Do the decompression, and jump to the new kernel..
*/
subl $16,%esp # place for structure on the stack
movl %esp,%eax
pushl %esi # real mode pointer as second arg
pushl %eax # address of structure as first arg
call SYMBOL_NAME(decompress_kernel)
orl %eax,%eax # 如果返回非零,则表示为内核解压为低端和高端的两个片断
jnz 3f
popl %esi # discard address
popl %esi # real mode pointer
xorl %ebx,%ebx
ljmp $(__KERNEL_CS), $0x100000 # 运行start_kernel

/*
* We come here, if we were loaded high.
* We need to move the move-in-place routine down to 0x1000
* and then start it with the buffer addresses in registers,
* which we got from the stack.
*/
3:
movl $move_routine_start,%esi
movl $0x1000,%edi
movl $move_routine_end,%ecx
subl %esi,%ecx
addl $3,%ecx
shrl $2,%ecx # 按字取整
cld
rep
movsl # 将内核片断合并代码复制到0x1000区域, 内核的片段起始为0x2000

popl %esi # discard the address
popl %ebx # real mode pointer
popl %esi # low_buffer_start 内核低端片段的起始地址
popl %ecx # lcount 内核低端片段的字节数量
popl %edx # high_buffer_start 内核高端片段的起始地址
popl %eax # hcount 内核高端片段的字节数量
movl $0x100000,%edi 内核合并的起始地址
cli # make sure we don't get interrupted
ljmp $(__KERNEL_CS), $0x1000 # and jump to the move routine

/*
* Routine (template) for moving the decompressed kernel in place,
* if we were high loaded. This _must_ PIC-code !
*/
move_routine_start:
movl %ecx,%ebp
shrl $2,%ecx
rep
movsl # 按字拷贝第1个片段
movl %ebp,%ecx
andl $3,%ecx
rep
movsb # 传送不完全字
movl %edx,%esi
movl %eax,%ecx # NOTE: rep movsb won't move if %ecx == 0
addl $3,%ecx
shrl $2,%ecx # 按字对齐
rep
movsl # 按字拷贝第2个片段
movl %ebx,%esi # Restore setup pointer
xorl %ebx,%ebx
ljmp $(__KERNEL_CS), $0x100000 # 运行start_kernel
move_routine_end:

; arch/i386/boot/compressed/misc.c

/*
* gzip declarations
*/

#define OF(args) args
#define STATIC static

#undef memset
#undef memcpy
#define memzero(s, n) memset ((s), 0, (n))

typedef unsigned char uch;
typedef unsigned short ush;
typedef unsigned long ulg;

#define WSIZE 0x8000 /* Window size must be at least 32k, */
/* and a power of two */

static uch *inbuf; /* input buffer */
static uch window[WSIZE]; /* Sliding window buffer */

static unsigned insize = 0; /* valid bytes in inbuf */
static unsigned inptr = 0; /* index of next byte to be processed in inbuf */
static unsigned outcnt = 0; /* bytes in output buffer */



相关文章:
精通initramfs构建step by step
Linux利用kexec迅速切换内核
进程上下文VS中断上下文
内核通知链 学习笔记
linux spi子系统驱动分析
menuconfig 配置选项
《Linux操作系统内核实习》之练习一
udev详解
什么叫微内核,宏内核?
Linux 信号signal处理机制
开发简单的 Linux2.6 内核模块
删除内核的perl脚本
Linux2.6内核usb gadget驱动移植
GCC hacks in the Linux kernel
iomem
kernel学习的想法
让自己的驱动支持udev
linux内核编译步骤
内核的等待队列
Linux内核wait_queue深入分析
升级和删除内核
SD卡驱动分析2
Linux Kernel VDSO本地权限提升漏洞
内核中的TCP的追踪分析-15-TCP(IPV4)的客户端与
linux 2.6内核可加载模块的编译
内核模块HelloWorld
在环回接口上发送一个数据报
ARP初始化
1分钟编译FreeBSD内核
linux设备模型之uart驱动架构分析