一、概述

  linux内核镜像常见到的有两种形式,zImage和uImage。这两种文件的格式稍有差别,所以启动这两种格式的内核镜像也会有所不同。目前,uboot只支持启动uImage类型的镜像,对zImage还不支持(但是可以移植,TQ2440就是这样做的)。

二、uImage和zImage

1、zImage

zImage是用命令“#make zImage”生成的,我截取了生成信息最后部分的内容如下:

  OBJCOPY arch/arm/boot/Image
Kernel: arch/arm/boot/Image is ready
GZIP arch/arm/boot/compressed/piggy.gz
AS arch/arm/boot/compressed/piggy.o
LD arch/arm/boot/compressed/vmlinux
OBJCOPY arch/arm/boot/zImage
Kernel: arch/arm/boot/zImage is ready

  从中可以看到,zImage是经过gzip压缩过的,所以在内核启动过程(不属于u-boot控制范围,在内核镜像的头部嵌有解压函数)中必然会对应一个解压过程。

2、uImage

(1) 生成方法

  uImage是u-boot专用的内核镜像,可用命令“#make uImage”生成。生成信息最后部分的内容如下:

  Kernel: arch/arm/boot/Image is ready
Kernel: arch/arm/boot/zImage is ready
UIMAGE arch/arm/boot/uImage
Image Name: Linux-2.6.30.4-EmbedSky
Created: Thu Mar ::
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: Bytes = 2260.48 kB = 2.21 MB
Load Address: 0x30008000
Entry Point: 0x30008000
Image arch/arm/boot/uImage is ready

  事实上,uImage是调用mkimage(uboot制作的工具)这个工具生成的。

root@daneiqi:/opt/EmbedSky#  mkimage -n 'linux-2.6.30' -A arm -O linux -T kernel -C none -a 0x30008000 -e 0x30008000 -d zImage uImage
Image Name: linux-2.6.
Created: Thu Mar ::
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: Bytes = 2260.48 kB = 2.21 MB
Load Address: 0x30008000
Entry Point: 0x30008000

(2)特点

  在原来的可执行映象文件zImage的前面加上一个0x40字节的头, 记录参数所指定的信息,这样uboot才能识别这个映象是针对哪个CPU体系结构的,哪个OS的, 哪种类型,加载内存中的哪个位置,入口点在内存的那个位置以及映象名是什么。

(3)image_header

  头部的结构是在include/image.h中定义的,如下所示:

typedef struct image_header {
uint32_t ih_magic; /* Image Header Magic Number */
uint32_t ih_hcrc; /* Image Header CRC Checksum */
uint32_t ih_time; /* Image Creation Timestamp */
uint32_t ih_size; /* Image Data Size */
uint32_t ih_load; /* Data Load Address */
uint32_t ih_ep; /* Entry Point Address */
uint32_t ih_dcrc; /* Image Data CRC Checksum */
uint8_t ih_os; /* Operating System */
uint8_t ih_arch; /* CPU architecture */
uint8_t ih_type; /* Image Type */
uint8_t ih_comp; /* Compression Type */
uint8_t ih_name[IH_NMLEN]; /* Image Name */
} image_header_t;

  打开上边生成的uImage文件,可以看到对应的数据。

(1)ih_magic    0x27051956  magic值,我觉得是uImage的头部开始值,根据这个值,判断是否是uImage

(2)ih_crc    0x19dbf9c6    头部校验

(3)ih_time   0x74295319   创建时间

(4)ih_size   0x002351f0     镜像大小为2260.48KB

(5)ih_load  0x30008000 内核加载地址

(6)ih_ep        0x30008000 内核运行地址,“theKernel”指向该地址,说明这里藏着进入第一个函数--解压

(7)ih_dcrc      0x38fc654e    内核校验

(8)ih_os        0x05       #define IH_OS_LINUX  5 /* Linux */

(9)ih_arch     0x02     #define IH_CPU_ARM  2 /* ARM  */

(10)ih_type   0x02         #define IH_TYPE_KERNEL  2 /* OS Kernel Image  */

(11)ih_comp  0x00        #define IH_COMP_NONE  0 /*  No  Compression Used */

(12)ih_name         Linux_2.6.30.4-EmbedSky

三、u-boot内核启动流程概述

  前文已经说明u-boot只支持uImage,步骤三、四都是针对uImage的。

  另外声明一点,步骤三四的测试uboot代码是韦东山视频提供的。

1、从NandFlash中读取内核到RAM中

2、在RAM中,给内核进行重定位

3、给内核传递参数

4、启动内核

四、u-boot启动内核细节分析

1、启动命令

从环境变量中查看启动命令:

2、从NandFlash中读取内核到RAM中

  nand read.jffs2 0x30007FC0 kernel

  此命令会激活(common/cmd_nand.c)中的do_nand函数,从而将nandflash上的kernel分区加载到0x30007fc0位置处。

OpenJTAG> mtd

device nand0 <nandflash0>, # parts =
#: name size offset mask_flags
: bootloader 0x00040000 0x00000000
: params 0x00020000 0x00040000
: kernel 0x00200000 0x00060000
: root 0x0fda0000 0x00260000 active partition: nand0, - (bootloader) 0x00040000 @ 0x00000000 defaults:
mtdids : nand0=nandflash0
mtdparts: mtdparts=nandflash0:256k@(bootloader),128k(params),2m(kernel),-(root)

  从分区表中,可以看出kernel分区的起始地址是0x60000,大小是0x200000(2M),这条命令实际上等效于

nand read.jffs2 0x30007FC0 0x60000 0x200000

  也可以使用命令

nand read 0x30007FC0 0x60000 0x200000

  nand read.jffs2可以自动页对齐,所以大小可以是非页整的;如果使用nand read的大小必须是页对齐的。

3、读取uImage头部

  bootm 0x30007fc0

  此命令会激活(common/cmd_bootm.c)中的do_bootm函数,从而开始执行

、在RAM中,给内核进行重定位
、给内核传递参数
、启动内核

image_header_t header;  定义一个全局变量header,是读取头部的缓冲区

addr = simple_strtoul(argv[1], NULL, 16);  定位头部地址,将字符串“0x30007fc0”转化为整型

printf ("## Booting image at %08lx ...\n", addr); 显示从哪儿启动

memmove (&header, (char *)addr, sizeof(image_header_t)); 读取头部到header变量中

4、判断当前的内存区是否是uImage的开始位置

 if (ntohl(hdr->ih_magic) != IH_MAGIC) {
{
puts ("Bad Magic Number\n");
SHOW_BOOT_PROGRESS (-);
return ;
}
}

注意到:

#define IH_MAGIC 0x27051956 /* Image Magic Number  */(include/image.h)

5、校验头部

    data = (ulong)&header;
len = sizeof(image_header_t); checksum = ntohl(hdr->ih_hcrc);
hdr->ih_hcrc = ; if (crc32 (, (uchar *)data, len) != checksum) {
puts ("Bad Header Checksum\n");
SHOW_BOOT_PROGRESS (-);
return ;
}

6、打印头部信息

    /* for multi-file images we need the data part, too */
print_image_hdr ((image_header_t *)addr);

7、核查内核数据

    data = addr + sizeof(image_header_t);
len = ntohl(hdr->ih_size); if (verify) {
puts (" Verifying Checksum ... ");
if (crc32 (, (uchar *)data, len) != ntohl(hdr->ih_dcrc)) {
printf ("Bad Data CRC\n");
SHOW_BOOT_PROGRESS (-);
return ;
}
puts ("OK\n");
}
SHOW_BOOT_PROGRESS ();

  注意到data已经跳过了uImage的头部,指向了真正的内核首部,也即0x30008000。

8、核查架构、内核类型、压缩类型等信息,其中会涉及到重定位

    len_ptr = (ulong *)data;

#if defined(__PPC__)
if (hdr->ih_arch != IH_CPU_PPC)
#elif defined(__ARM__)
if (hdr->ih_arch != IH_CPU_ARM)
#elif defined(__I386__)
if (hdr->ih_arch != IH_CPU_I386)
#elif defined(__mips__)
if (hdr->ih_arch != IH_CPU_MIPS)
#elif defined(__nios__)
if (hdr->ih_arch != IH_CPU_NIOS)
#elif defined(__M68K__)
if (hdr->ih_arch != IH_CPU_M68K)
#elif defined(__microblaze__)
if (hdr->ih_arch != IH_CPU_MICROBLAZE)
#elif defined(__nios2__)
if (hdr->ih_arch != IH_CPU_NIOS2)
#elif defined(__blackfin__)
if (hdr->ih_arch != IH_CPU_BLACKFIN)
#elif defined(__avr32__)
if (hdr->ih_arch != IH_CPU_AVR32)
#else
# error Unknown CPU type
#endif
{
printf ("Unsupported Architecture 0x%x\n", hdr->ih_arch);
SHOW_BOOT_PROGRESS (-);
return ;
}
SHOW_BOOT_PROGRESS (); switch (hdr->ih_type) {
case IH_TYPE_STANDALONE:
name = "Standalone Application";
/* A second argument overwrites the load address */
if (argc > ) {
hdr->ih_load = htonl(simple_strtoul(argv[], NULL, ));
}
break;
case IH_TYPE_KERNEL:
name = "Kernel Image";
break;
case IH_TYPE_MULTI:
name = "Multi-File Image";
len = ntohl(len_ptr[]);
/* OS kernel is always the first image */
data += ; /* kernel_len + terminator */
for (i=; len_ptr[i]; ++i)
data += ;
break;
default: printf ("Wrong Image Type for %s command\n", cmdtp->name);
SHOW_BOOT_PROGRESS (-);
return ;
}
SHOW_BOOT_PROGRESS (); /*
* We have reached the point of no return: we are going to
* overwrite all exception vector code, so we cannot easily
* recover from any failures any more...
*/ iflag = disable_interrupts(); #ifdef CONFIG_AMIGAONEG3SE
/*
* We've possible left the caches enabled during
* bios emulation, so turn them off again
*/
icache_disable();
invalidate_l1_instruction_cache();
flush_data_cache();
dcache_disable();
#endif switch (hdr->ih_comp) {
case IH_COMP_NONE:
if(ntohl(hdr->ih_load) == data) {
printf (" XIP %s ... ", name);
} else {
#if defined(CONFIG_HW_WATCHDOG) || defined(CONFIG_WATCHDOG)
size_t l = len;
void *to = (void *)ntohl(hdr->ih_load);
void *from = (void *)data; printf (" Loading %s ... ", name); while (l > ) {
size_t tail = (l > CHUNKSZ) ? CHUNKSZ : l;
WATCHDOG_RESET();
memmove (to, from, tail);
to += tail;
from += tail;
l -= tail;
}
#else /* !(CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG) */
memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
#endif /* CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG */
}
break;
case IH_COMP_GZIP:
printf (" Uncompressing %s ... ", name);
if (gunzip ((void *)ntohl(hdr->ih_load), unc_len,
(uchar *)data, &len) != ) {
puts ("GUNZIP ERROR - must RESET board to recover\n");
SHOW_BOOT_PROGRESS (-);
do_reset (cmdtp, flag, argc, argv);
}
break;
#ifdef CONFIG_BZIP2
case IH_COMP_BZIP2:
printf (" Uncompressing %s ... ", name);
/*
* If we've got less than 4 MB of malloc() space,
* use slower decompression algorithm which requires
* at most 2300 KB of memory.
*/
i = BZ2_bzBuffToBuffDecompress ((char*)ntohl(hdr->ih_load),
&unc_len, (char *)data, len,
CFG_MALLOC_LEN < ( * ), );
if (i != BZ_OK) {
printf ("BUNZIP2 ERROR %d - must RESET board to recover\n", i);
SHOW_BOOT_PROGRESS (-);
udelay();
do_reset (cmdtp, flag, argc, argv);
}
break;
#endif /* CONFIG_BZIP2 */
default:
if (iflag)
enable_interrupts();
printf ("Unimplemented compression type %d\n", hdr->ih_comp);
SHOW_BOOT_PROGRESS (-);
return ;
}
puts ("OK\n");
SHOW_BOOT_PROGRESS (); switch (hdr->ih_type) {
case IH_TYPE_STANDALONE:
if (iflag)
enable_interrupts(); /* load (and uncompress), but don't start if "autostart"
* is set to "no"
*/
if (((s = getenv("autostart")) != NULL) && (strcmp(s,"no") == )) {
char buf[];
sprintf(buf, "%lX", len);
setenv("filesize", buf);
return ;
}
appl = (int (*)(int, char *[]))ntohl(hdr->ih_ep);
(*appl)(argc-, &argv[]);
return ;
case IH_TYPE_KERNEL:
case IH_TYPE_MULTI:
/* handled below */
break;
default:
if (iflag)
enable_interrupts();
printf ("Can't boot image type %d\n", hdr->ih_type);
SHOW_BOOT_PROGRESS (-);
return ;
}
SHOW_BOOT_PROGRESS ();

  在这部分代码中,有这么一部分关于压缩类型的:

    switch (hdr->ih_comp) {
case IH_COMP_NONE:
if(ntohl(hdr->ih_load) == data) {
printf (" XIP %s ... ", name);
} else {
#if defined(CONFIG_HW_WATCHDOG) || defined(CONFIG_WATCHDOG)
size_t l = len;
void *to = (void *)ntohl(hdr->ih_load);
void *from = (void *)data; printf (" Loading %s ... ", name); while (l > ) {
size_t tail = (l > CHUNKSZ) ? CHUNKSZ : l;
WATCHDOG_RESET();
memmove (to, from, tail);
to += tail;
from += tail;
l -= tail;
}
#else /* !(CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG) */
memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
#endif /* CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG */
}
break;

  可以看到,u-boot会判断当前去除uImage头部内核代码所处的位置(7步骤已经说明地址是data)是否与编译时安排的重定位位置(hdr->ih_load)一致。

  如果一致,就打印一句话。

  如果不一致,则需要调用 memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);进行内核的重定位,要知道它有2M多的大小,会花费一些时间。尽量使读取内核的时候,就读取到hdr->ih_load-64的位置上,这样就不必再搬运一次。

9、根据操作系统类型,启动对应的操作系统

    switch (hdr->ih_os) {
default: /* handled by (original) Linux case */
case IH_OS_LINUX:
#ifdef CONFIG_SILENT_CONSOLE
fixup_silent_linux();
#endif
do_bootm_linux (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
case IH_OS_NETBSD:

10、执行do_bootm_linux,继续启动linux系统

  此函数在lib_arm/armlinux.c中

    void (*theKernel)(int zero, int arch, uint params);
image_header_t *hdr = &header;
bd_t *bd = gd->bd; #ifdef CONFIG_CMDLINE_TAG
char *commandline = getenv ("bootargs");
#endif theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);

  可见,已经将内核运行的首地址赋给了theKernel函数指针变量,将来可以利用这个变量调用进入内核的函数。

  另外,在进入内核之前,要给内核传递参数。方法是将参数以一定的结构放在内存指定的位置上,将来内核从该地址读取数据即可。

  命令行的启动参数存储在以bootargs命名的对象里,值为

bootargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0

  告诉内核,启动后的根文件系统位于mtd的哪个区,初始进程,以及控制台

11、判断是否是一个ramdisk或者multi镜像

    /*
* Check if there is an initrd image
*/
if (argc >= ) {
SHOW_BOOT_PROGRESS (); addr = simple_strtoul (argv[], NULL, ); printf ("## Loading Ramdisk Image at %08lx ...\n", addr); /* Copy header so we can blank CRC field for re-calculation */
#ifdef CONFIG_HAS_DATAFLASH
if (addr_dataflash (addr)) {
read_dataflash (addr, sizeof (image_header_t),
(char *) &header);
} else
#endif
memcpy (&header, (char *) addr,
sizeof (image_header_t)); if (ntohl (hdr->ih_magic) != IH_MAGIC) {
printf ("Bad Magic Number\n");
SHOW_BOOT_PROGRESS (-);
do_reset (cmdtp, flag, argc, argv);
} data = (ulong) & header;
len = sizeof (image_header_t); checksum = ntohl (hdr->ih_hcrc);
hdr->ih_hcrc = ; if (crc32 (, (unsigned char *) data, len) != checksum) {
printf ("Bad Header Checksum\n");
SHOW_BOOT_PROGRESS (-);
do_reset (cmdtp, flag, argc, argv);
} SHOW_BOOT_PROGRESS (); print_image_hdr (hdr); data = addr + sizeof (image_header_t);
len = ntohl (hdr->ih_size); #ifdef CONFIG_HAS_DATAFLASH
if (addr_dataflash (addr)) {
read_dataflash (data, len, (char *) CFG_LOAD_ADDR);
data = CFG_LOAD_ADDR;
}
#endif if (verify) {
ulong csum = ; printf (" Verifying Checksum ... ");
csum = crc32 (, (unsigned char *) data, len);
if (csum != ntohl (hdr->ih_dcrc)) {
printf ("Bad Data CRC\n");
SHOW_BOOT_PROGRESS (-);
do_reset (cmdtp, flag, argc, argv);
}
printf ("OK\n");
} SHOW_BOOT_PROGRESS (); if ((hdr->ih_os != IH_OS_LINUX) ||
(hdr->ih_arch != IH_CPU_ARM) ||
(hdr->ih_type != IH_TYPE_RAMDISK)) {
printf ("No Linux ARM Ramdisk Image\n");
SHOW_BOOT_PROGRESS (-);
do_reset (cmdtp, flag, argc, argv);
} #if defined(CONFIG_B2) || defined(CONFIG_EVB4510) || defined(CONFIG_ARMADILLO)
/*
*we need to copy the ramdisk to SRAM to let Linux boot
*/
memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
data = ntohl(hdr->ih_load);
#endif /* CONFIG_B2 || CONFIG_EVB4510 */ /*
* Now check if we have a multifile image
*/
} else if ((hdr->ih_type == IH_TYPE_MULTI) && (len_ptr[])) {
ulong tail = ntohl (len_ptr[]) % ;
int i; SHOW_BOOT_PROGRESS (); /* skip kernel length and terminator */
data = (ulong) (&len_ptr[]);
/* skip any additional image length fields */
for (i = ; len_ptr[i]; ++i)
data += ;
/* add kernel length, and align */
data += ntohl (len_ptr[]);
if (tail) {
data += - tail;
} len = ntohl (len_ptr[]); } else {
/*
* no initrd image
*/
SHOW_BOOT_PROGRESS (); len = data = ;
} #ifdef DEBUG
if (!data) {
printf ("No initrd\n");
}
#endif

12、给内核传递参数

#if defined (CONFIG_SETUP_MEMORY_TAGS) || \
defined (CONFIG_CMDLINE_TAG) || \
defined (CONFIG_INITRD_TAG) || \
defined (CONFIG_SERIAL_TAG) || \
defined (CONFIG_REVISION_TAG) || \
defined (CONFIG_LCD) || \
defined (CONFIG_VFD)
setup_start_tag (bd);
#ifdef CONFIG_SERIAL_TAG
setup_serial_tag (&params);
#endif
#ifdef CONFIG_REVISION_TAG
setup_revision_tag (&params);
#endif
#ifdef CONFIG_SETUP_MEMORY_TAGS
setup_memory_tags (bd);
#endif
#ifdef CONFIG_CMDLINE_TAG
setup_commandline_tag (bd, commandline);
#endif
#ifdef CONFIG_INITRD_TAG
if (initrd_start && initrd_end)
setup_initrd_tag (bd, initrd_start, initrd_end);
#endif
#if defined (CONFIG_VFD) || defined (CONFIG_LCD)
setup_videolfb_tag ((gd_t *) gd);
#endif
setup_end_tag (bd);
#endif

  比较重要的函数有:

   setup_start_tag (bd);

  setup_memory_tags (bd);

  setup_commandline_tag (bd, commandline);

  setup_end_tag (bd);

  其中 bd->bi_boot_params(参考uboot全局变量),bi_boot_params=>>0x30000100,启动参数存放的位置。

13、启动内核

    printf ("\nStarting kernel ...\n\n");
  theKernel (, bd->bi_arch_number, bd->bi_boot_params);

  把机器码以及启动参数存放的位置都告诉给内核。

五、启动过程展示

1、不需要重定位启动

2、重定位启动

      下例中读取到的位置,不是合适的位置,内核的入口不是0x30008000,所以还要对内核进行重定位,也就是将内核搬移到指定的位置。

六、u-boot启动zImage

 1、直接启动zImage

  既然,zImage是uImage去除头部的部分,那么可以从0x30008000直接启动zImage,我们用go命令去执行。

可见,内核的第一个函数果然是解压函数。但是程序卡到图片最后的位置,不能继续执行。

  原因是由于没有给内核传递启动参数,也就是说在执行函数theKernel之前,没有做好准备

void (*theKernel)(int zero, int arch, uint params);

2、移植u-boot支持启动zImage

  具体代码可看TQ2440开发板的u-boot代码。

  再来看一下启动大纲:

、从NandFlash中读取内核到RAM中

、在RAM中,给内核进行重定位

、给内核传递参数

、启动内核

  可以直接从nandflash中将内核zImage读取到内存0x30008000位置处,然后在0x30000100位置处传递参数

也就是调用函数 

setup_start_tag (bd);
setup_memory_tags (bd);
setup_commandline_tag (bd, commandline);
setup_end_tag (bd);

  最后,调用theKernel函数启动内核。

参考资料:韦东山u-boot启动内核视频

     uboot全局变量

       linux的uboot启动映像、zImage和uImage的区别

uboot启动linux的过程的更多相关文章

  1. Qemu搭建ARM vexpress开发环境(二)----通过u-boot启动Linux内核

    Qemu搭建ARM vexpress开发环境(二)----通过u-boot启动Linux内核 标签(空格分隔): Qemu ARM Linux 在上文<Qemu搭建ARM vexpress开发环 ...

  2. 嵌入式Linux开发之uboot启动Linux整体流程分析

    嵌入式Linux开发之uboot启动Linux整体流程分析 Uboot全称Universal Boot Loader,一个遵循GPL协议的的开源项目,其作用是引导操作系统,支持引导linux.VxWo ...

  3. uboot引导linux内核过程详解【转】

    http://blog.chinaunix.net/uid-7828352-id-4472376.html 写的不错,尤其是uboot向linux内核传递参数的过程写的比较详细.

  4. U-Boot启动过程完全分析

    U-Boot启动过程完全分析 1.1       U-Boot工作过程 U-Boot启动内核的过程可以分为两个阶段,两个阶段的功能如下: (1)第一阶段的功能 硬件设备初始化 加载U-Boot第二阶段 ...

  5. U-Boot启动过程完全分析<转>

    转载自:http://www.cnblogs.com/heaad/archive/2010/07/17/1779829.html 1.1       U-Boot工作过程 U-Boot启动内核的过程可 ...

  6. 【ARM-Linux开发】U-Boot启动过程--详细版的完全分析

    ---------------------------------------------------------------------------------------------------- ...

  7. (转载)U-boot启动完全分析

    1.1 U-Boot工作过程 U-Boot启动内核的过程可以分为两个阶段,两个阶段的功能如下: (1)第一阶段的功能 Ø 硬件设备初始化 Ø 加载U-Boot第二阶段代码到RAM空间 Ø 设置好栈 Ø ...

  8. JZ2440使用笔记之熟悉uboot和Linux的移植

    目录 一.点亮开发板:移植uboot.Linux内核.文件系统 1.1 配置上位机交叉编译环境 1.2 制作U-boot镜像文件 1.3 通过oflash.exe / openJTAG 烧写u-boo ...

  9. U-boot 启动内核

    1:什么是UBOOT,为什么要有UBOOT? UBOOT的主要作用是用来启动linux内核,因为CPU不能直接从块设备中执行代码,需要把块设备中的程序复制到内存中,而复制之前还需要进行很多初始化工作, ...

随机推荐

  1. MSSQLSERVER数据库- 打开表出现目录名无效

    打开SQLSERVER数据库,出现目录名无效,如下图: 解决方法到 临时目录:C:\Documents and Settings\Administrator\Local Settings\Temp 手 ...

  2. 界面编程模仿篇(QQ登录界面逼真篇)

    写了好多天的爬虫,偷空前前后后用了两天的时间(排除吃饭睡觉)写完了这个QQ登录界面,看起来还凑和着吧,如果是的大神的,莫见笑,纯属业余作品,废话先不多说,截图如下,其中第二幅图片中的红色方框部份有待完 ...

  3. 理解position 绝对定位和相对定位

    一.position的三种取值 1.取值 Position :   static /  absolute / fixed / relative static:静态   absolute:绝对定位    ...

  4. ARCproject中加入非ARC文件,或者非ARC环境中加入ARC文件

    ARC与非ARC在一个项目中同一时候使用, 选择项目中的Targets,选中你所要操作的Target,选Build Phases,在当中Complie Sources中选择须要ARC的文件双击,并在输 ...

  5. 使用tuple返回多个值

    17.4编写并测试findbook函数 #include<iostream> #include<vector> #include<string> #include& ...

  6. 初步掌握HBase

    1.HBase概述 HBase是hadoop生态系统中的重要组成部分,是一个开源的.面向列.适合存储海量非结构化数据或半结构化数据,具备高可靠性.高性能.可灵活扩展伸缩.支持实时数据读写的分布式存储系 ...

  7. How to manage and balance “Huge Data Load” for Big Kafka Clusters---reference

    1. Add Partition Tool Partitions act as unit of parallelism. Messages of a single topic are distribu ...

  8. [Eclipse]The type XXX cannot be resolved. It is indirectly referenced from required .class files

    在Eclipse中遇到The type XXX cannot be resolved. It is indirectly referenced from required .class files错误 ...

  9. Base64工具类

    public final class AbBase64 { /** The Constant base64EncodeChars. */ private static final char[] bas ...

  10. 在命令行中如何访问Program Files文件夹(转)

    通常来说Program Files文件夹位于C盘,也就是C:\Program File.为了保证兼容性,在命令行中通常使用环境变量%ProgramFiles%来表示Program Files的具体路径 ...