博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
OK6410A 开发板 (三) 12 u-boot-2021.01 boot 解析 U-boot 镜像运行部分 boot 详细解析1
阅读量:4286 次
发布时间:2019-05-27

本文共 17813 字,大约阅读时间需要 59 分钟。

url 		: git@github.com:lisider/u-boot.gitbranch 		: ok6410acommit id 	: e63a4077ad3aea53107495b0b68b95e720fe6033config 		: ok6410a_mini_defconfig// 涉及的 .S .s .c 文件 有 223个
reset														arch/arm/cpu/arm1176/start.S 39	lowlevel_init(108)										board/samsung/ok6410a/lowlevel_init.S 72	_main(110) 												arch/arm/lib/crt0.S 91		board_init_f(117)									common/board_f.c 954			initcall_run_list(init_sequence_f)(959) 		include/initcall.h 21				init_sequence_f								common/board_f.c 818		board_init_r(177) 									common/board_r.c 901			initcall_run_list(init_sequence_r)(927) 		include/initcall.h 21				init_sequence_r 							common/board_f.c 695					run_main_loop(898)						common/board_r.c 678						main_loop(685) 						common/main.c 39
  • 解析概要
// 入口 为 arch/arm/lib/vectors.S 中的 _start 标号处的 b resetreset	lowlevel_init		时钟		串口		ddr	_main		// 从代码运行起始位置0x5FB00000 往下 找 gd 和栈 的位置		bl board_init_f_alloc_reserve		// 设置栈		mov sp, r0		// 设置gd 		mov r9, r0		// 初始化gd		bl board_init_f_init_reserve 		bl board_init_f 			查找 设备树的地址			准备 early malloc			驱动模型的前期准备			定时器初始化,为delay做准备			env 初始化,为查找环境变量做准备			串口波特率环境变量的查找和串口波特率的设置,为printf做准备			从 ddr 顶端 0x5FFF FFFF 往下 找 空间				1. mmu				2. u-boot				3. malloc				4. new_gd				5. fdt				6. irq 栈				7. 栈			搬移 gd			搬移设备树(fdt)					// 搬移 u-boot 并 fixloop		b relocate_code		// 搬移向量表 到 0x0000 0000		bl relocate_vectors		CLEAR_BSS		// 清.bss 段 , 这是u-boot阶段第一次清, relocate 之前没有清过		ldr pc,=board_init_r			// 开 icache 和 dcache			initr_caches			// 为 full malloc 做准备			initr_malloc			// 驱动模型的后期准备			initr_dm			// 初始化 board ID			board_init			// 串口及控制台的初始化			stdio_init_tables			serial_initialize 			stdio_add_devices			console_init_r			// 其他设备的初始化			initr_mmc			initr_ethaddr			initr_net			// env 的后初始化			initr_env			// 跳转表???			// the jump table contains pointers to exported functions.			// A pointer to the jump table is passed to standalone applications.			// 也就是 说 导出 让 standalone 应用用的			// u-boot 命令行 可以 加载 bin 文件,并执行 这些bin文件就是 standalone applications			// 查看 doc/README.standalone			initr_jumptable			// 中断相关的初始化(开中断,相关变量)			interrupt_init			main_loop				bootcmd以及cmdline
  • 详细解析
// common/init/board_init.c// register volatile gd_t *gd asm ("r9");// arch/arm/include/asm/global_data.h:112:#define DECLARE_GLOBAL_DATA_PTR          register volatile gd_t *gd asm ("r9")reset // 运行在 5FB0 0000// sdram : 0x5000_0000 0x5FFF_FFFF	lowlevel_init		spl_config_uart_baudrate			ulong val = spl_uart_getclk(is_mpll) / 115200;			_UBRDIV = val / 16 - 1;			_UDIVSLOT = udivslot[val % 16];		mem_ctrl_asm_init		// ddr初始化	_main		board_init_f_alloc_reserve // 算出gd的地址为 0x5f9fff20			// 过程与数据和spl 完全一样			申请 struct global_data 个字节 用来存储 gd_t *gd 指向的 gd_t 结构体			// include/asm-generic/global_data.h 中 有 gd_t 的 定义		board_init_f_init_reserve // 根据 gd的地址 0x5f9fff20 算出结构体			// 过程与数据和spl 完全一样			初始化 gd_t *gd 指向的 gd_t 结构体 空间 为 0			初始化 gd_t *gd 指向的 gd_t 结构体 的 成员 malloc_base = 5F9F FFF0						// u-boot过程和 spl 过程用到的 gd 指针是同一个位置			// 但是 内容不同,因为在 初始化的时候,已经将 gd指针指向的位置全部 memset 0 了				// spl 加载 u-boot 的过程中, 就是 A加载B			// A 加载 B 可以 通过 寄存器 来传值			// 但是 spl 加载 u-boot 的过程中可以说没有传递任何值			// 虽然 都用r9 保存了 gd的指针值,但是 r9 是算出来的,不是 spl 传过来的.					/*		下面的这几行有两个目标		1. 设置sp		2. 存储gd指针变量并clear gd指针变量指向的空间,并设置 gd->malloc_base = 0x5f9f ff20;				ldr r0, =((0x5FB00000 - 0x0C)) 		=> r0 : 5FAF FFF4		bic r0, r0, #7 						=> r0 : 5FAF FFF0		mov sp, r0 							=> sp : 5FAF FFF0		bl board_init_f_alloc_reserve 		=> r0 : 5f9f ff20 (5FAF FFF0 - 0x10 0000 - 0xD0)		mov sp, r0 							=> sp : 5f9f ff20             ***** 设置了 栈指针		mov r9, r0 							=> r9 : 5f9f ff20 			  ***** 存储了 gd指针变量的值		bl board_init_f_init_reserve 		=> 初始化 (5f9fff20 - 5f9fff20+0xD0) 的空间		*/		/*		// .code .rodata .data .bss .stack .heap		此时的内存分布		5FFF FFFF---------------------5FFF FFFF					5120KB(0x500000)大小,被512KB u-boot.bin占据 // 不管u-boot.bin有没有512KB,都会从mmc上拷贝512KB过来 // U-boot.bin实际大小 248K					内存中的u-boot.bin 中存在 .bss段,起始为5FB3 61F0,结束为5FB3 BAD4					内存中的u-boot.bin 也存在 .code .rodata .data		5FB0 0000---------------------					16B(0x10)大小,空洞		5FAF FFF0---------------------					1024KB(0x100000)大小,用于sys_malloc (.heap)		5F9F FFF0---------------------					208B(0xD0)大小,用于gd (很特殊的一个全局变量)		5F9F FF20---------------------					255999KB(约250MB)大小,用于栈 (.stack)		5000 0000---------------------5000 0000		*/		board_init_f // 被调用的时候传递的参数为 0 ,用来设置flags.			gd->flags = 0;			gd->have_console = 0;			setup_mon_len // 第一次使用栈,(sp) // 重要参考点1				gd->mon_len = (ulong)&__bss_end - (ulong)_start;				// = 0x3bc4c				// 为 reserve_uboot 做准备			fdtdec_setup // 为设备树的解析做准备				gd->fdt_blob = board_fdt_blob_setup();					return (ulong *)&_end;				gd->fdt_blob = map_sysmem (env_get_ulong("fdtcontroladdr", 16, (unsigned long)map_to_sysmem(gd->fdt_blob)), 0);					// 解码fdtcontroladdr环境变量的值,16进制,如果为空则为gd->fdt_blob				fdtdec_prepare_fdt 					// 检查gd->fdt_blob为空,则报错				fdtdec_board_setup					// null			initf_malloc // 为 early malloc 做准备				// 用于 early malloc				// 此前 gd->malloc_base 已经被初始化为 5F9F FFF0 // base address of early malloc				gd->malloc_limit = 0x100000; // limit address of early malloc				gd->malloc_ptr = 0; // current address of early malloc			log_init				// null			initf_bootstage				bootstage_init(1)					// null				bootstage_mark_name(BOOTSTAGE_ID_START_UBOOT_F, "board_init_f")					show_boot_progress(id);						//null // TODO, 另一个路径大概率是 printf			setup_spl_handoff				// null			initf_console_record				// null			arch_cpu_init				// null			mach_cpu_init				// null			initf_dm // dm 架构(driver-model),比较复杂,后续有空更新专题 // 为 驱动和设备的注册做准备				bootstage_start(BOOTSTAGE_ID_ACCUM_DM_F, "dm_f");					// null				dm_init_and_scan(1)					dm_init(0)						INIT_LIST_HEAD(&(((gd_t *)gd)->uclass_root));							list->next = list;							list->prev = list;						device_bind_by_name(0,0,&root_info, &(((gd_t *)gd)->dm_root)));							lists_driver_lookup_name(info->name);							device_bind_common(parent, drv, info->name, (void *)info->platdata, 0, ofnode_null(), platdata_size, devp);						(((gd_t *)gd)->dm_root)->node = offset_to_ofnode(0)						device_probe((((gd_t *)gd)->dm_root))					dm_scan_platdata(1)						lists_bind_drivers((((gd_t *)gd)->dm_root), 1)					dm_extended_scan_fdt(gd->fdt_blob, 1)						dm_scan_fdt(gd->fdt_blob,1)							dm_scan_fdt_node(gd->dm_root, blob, 0, 1);						for_each_node dm_scan_fdt_ofnode_path(blob, nodes[i], 1);												dm_scan_other(1)						// null				bootstage_accum(BOOTSTAGE_ID_ACCUM_DM_F);					// null			arch_cpu_init_dm				// null			timer_init // 为 delay 做准备				// 设置 s3c6410的 硬件timer				gd->arch.timer_rate_hz = pre_freq/1;				gd->arch.timer_rate_hz *= 1000;				gd->arch.lastinc = timers->TCNTB4 = gd->arch.timer_rate_hz;				gd->arch.timer_reset_value = 0;			env_init				env_driver_lookup					env_get_location						return env_locations[prio] ; // ENVL_MMC					_env_driver_lookup						n_ents = xxx						in section .u_boot_list_2_env_driver ,根据 ENVL_MMC 找到对应的 驱动对应的结构体 struct env_driver				drv->init	 // drv 为找到的结构体 struct env_driver _u_boot_list_2_env_driver_2_mmc 中没有 init 成员				env_set_inited(drv->location)					gd->env_has_init |= (1UL << (location));									gd->env_addr = (ulong)&default_environment[0]; // 待研究				gd->env_valid = ENV_VALID;			init_baud_rate				gd->baudrate = env_get_ulong("baudrate", 10, 115200);					const char *str = env_get(name);						env_get_f							env_get_char							env_match					return str ? simple_strtoul(str,0,10) : default_val;								serial_init				serial_find_console_or_panic					serial_check_stdout						str = fdtdec_get_chosen_prop(blob, "stdout-path");						node = fdt_path_offset_namelen(blob, str, namelen);						lists_bind_fdt						device_probe(&dev)					gd->cur_serial_dev = dev;				gd->flags |= GD_FLG_SERIAL_READY;				serial_setbrg					gd->cur_serial_dev->driver->ops->setbrg(gd->cur_serial_dev, gd->baudrate)// s3c_serial_setbrg						s3c_serial_setbrg_internal((struct s3c64xx_uart * )CONFIG_DEBUG_UART_BASE, 0 /*CFG_SERIAL_ID*/,CONFIG_BAUDRATE);							u32 uclk = get_uart_clk(id)							s3c64xx_serial_baud(uart, uclk, baudrate);								uart->UBRDIV = uclk / baudrate / 16 - 1;								uart->UDIVSLOT = udivslot[val];								barrier(); // 内存顺序模型相关			console_init_f				gd->have_console = 1;				console_update_silent					// null				print_pre_console_buffer(PRE_CONSOLE_FLUSHPOINT1_SERIAL);					// null TODO					// PRE_CONSOLE_BUFFER			display_options // printf 可以使用 // 重要参考点2				display_options_get_banner(true, buf, sizeof(buf));					display_options_get_banner_priv						snprintf				printf("%s", buf);			display_text_info				bss_start = (ulong)&__bss_start;				bss_end = (ulong)&__bss_end;				text_base = 0x5FB00000;			checkcpu				// null			print_cpuinfo				printf("**    Updated for OK6410A Board        **\r\n");			show_board_info				model = fdt_getprop(gd->fdt_blob, 0, "model", ((void *)0));				printf("Model: %s\n", model);				// 实际打印 为 Model: Samsung SMDK6410 based on S3C6410				// 打印的字符串为 arch/arm/dts/s3c64xx-ok6410a.dts 中的 model 节点的属性				checkboard					printf("Board:   OK6410A\n");			announce_dram_init				puts("DRAM:  ");			dram_init				gd->ram_size += SDRAM_BANK_SIZE; // = 0x1000 0000			setup_dest_addr				gd->ram_base = 0x50000000;				gd->ram_top = gd->ram_base + get_effective_memsize();					// get_effective_memsize: gd->ram_size					// = 0x6000 0000				gd->ram_top = board_get_usable_ram_top(gd->mon_len);					// = gd->ram_top 					// = 0x6000 0000				gd->relocaddr = gd->ram_top;					// = 0x6000 0000			reserve_round_4k				gd->relocaddr &= ~(4096 - 1);					// hex 后三位与0					// 0x6000 0000			arch_reserve_mmu				arm_reserve_mmu					gd->arch.tlb_size = (4096 * 4);						// 0x4000 16K					gd->relocaddr -= gd->arch.tlb_size;						// 0x5FFF C000					gd->relocaddr &= ~(0x10000 - 1);						// hex 后四位 与0						// 0x5fff 0000					gd->arch.tlb_addr = gd->relocaddr;						// 0x5fff 0000			reserve_video				// null			reserve_trace				// null			reserve_uboot				gd->relocaddr -= gd->mon_len;					// = 5FFB 33B4				gd->relocaddr &= ~(4096 - 1);					// hex 后三位与0					// 5FFB 3000				gd->start_addr_sp = gd->relocaddr;					// 5FFB 3000			reserve_malloc				gd->start_addr_sp = reserve_stack_aligned((1024*1024));					// 0x5feb3000			reserve_board					// gd->bd 不为null , 什么都不做			setup_machine				gd->bd->bi_arch_number = 1626;			reserve_global_data				gd->start_addr_sp = reserve_stack_aligned(sizeof(gd_t));					// 0x5feb2f30				gd->new_gd = (gd_t *)map_sysmem(gd->start_addr_sp, sizeof(gd_t));					// 0x5feb2f30			reserve_fdt				gd->fdt_size = ALIGN(fdt_totalsize(gd->fdt_blob), 32);					// 0xe80				gd->start_addr_sp = reserve_stack_aligned(gd->fdt_size);					// 0x5feb20b0				gd->new_fdt = map_sysmem(gd->start_addr_sp, gd->fdt_size);					// 0x5feb20b0			reserve_bootstage				// null			reserve_bloblist				// null			reserve_arch				// null			reserve_stacks				gd->start_addr_sp = reserve_stack_aligned(16);					// 这16个字节应该用于 irq的栈					// 0x5feb20a0				arch_reserve_stacks					gd->irq_sp = gd->start_addr_sp;						// 0x5feb20a0			dram_init_banksize // 只有一个bank				for_each_bank					gd->bd->bi_dram[i].start = addr;						// 0x50000000					gd->bd->bi_dram[i].size = (0x10000000);						// 0x10000000			show_dram_config				for_each_bank // 只有一个bank					size += gd->bd->bi_dram[i].size;						// 0x10000000				print_size(size, "");					// 实际打印 256 MiB				board_add_ram_info(0);					null				putc('\n');			setup_bdinfo				// null			display_new_sp				// null			reloc_fdt				gd->fdt_blob = gd->new_fdt;			reloc_bootstage				// null			reloc_bloblist				// null			setup_reloc				gd->reloc_off = gd->relocaddr - 0x5FB00000;					// 004b4000					// 该值是 u-boot的code段 搬移目标地址 与 当前地址的差值				memcpy(gd->new_gd, (char *)gd, sizeof(gd_t));			clear_bss				// null			// 运行到此时,sp还没更改			// u-boot 还没搬运			// 此时,搬运了 gd和设备树				// 此时 r9里面存放的还是gd		ldr r0, [r9, #72] // 72 是 start_addr_sp		bic r0, r0, #7 // 与上0b1000 , 第三位清0		mov sp, r0 // 更改 sp				ldr r9, [r9, #80] // 此时 r9 中存放的 是 new_gd		adr lr, here // 存放 lr, 为  b(跳转) 做准备		ldr r0, [r9, #76] // 将 new_gd 中的 reloc_off 存放到r0,用作新的lr的计算		add lr, lr, r0 	 // 更改lr ,让 relocate_code 返回时,跳转到 已经 搬移好的代码 中去,		ldr r0, [r9, #56] // 将 new_gd 中的 relocaddr存放到r0,作为第一个参数		b relocate_code			// 搬移 u-boot的 code 段			arch/arm/lib/relocate.S +80			搬移,修复绝对地址???		// 此时已经在 搬移过后的代码 中		bl relocate_vectors // TODO			// arch/arm/lib/relocate.S +28			ldr r0, [r9, #56] // 将 new_gd 中的 relocaddr存放到r0			mcr p15, 0, r0, c12, c0, 0		bl c_runtime_cpu_setup			// null		CLEAR_BSS			// 问题,搬移过后,__bss_start的值有变化吗?			// 宏汇编			// ldr r0, =__bss_start                                                            			// ldr r3, =__bss_end                                                              			// mov r1, #0x00000000                                                             			// subs r2, r3, r0                                                                 			// bl memset		bl coloured_LED_init			// null		bl red_led_on			// null		/*		// .code .rodata .data .bss .stack .heap		此时的内存分布		5FFF FFFF---------------------5FFF FFFF					mmu					(0x4000B,16KB)		5FFF C000---------------------					空洞					(0xC000B,48KB)		5FFF 0000--------------------- // 5FFF 0000 存放在 gd->arch.tlb_addr 中					u-boot.bin (包括.code .data .ro-data .bss)					(0x3D000B,244KB)		5FFB 3000--------------------- // 5FFB 3000 存放在 new_gd->relocaddr 中					malloc					(0x100000B,1024KB)		5FEB 3000---------------------					new_gd					(0xD0B,208B)		5FEB 2F30--------------------- // 5FEB 2F30 存放在 r9中					fdt					(0xE80B,3KB)		5FEB 20B0---------------------					空洞					(0x10,16B)		5FEB 20A0--------------------- // 5FEB 20A0 存放在 start_addr_sp 中					栈					(254.695465MB)		5000 0000---------------------		*/				mov r0, r9 		// 将 new_gd 的地址 存放到r0,作为第一个参数		ldr r1, [r9, #56] // 将 new_gd 中的 relocaddr 存放到r1,作为第二个参数		ldr pc,=board_init_r			gd->flags &= ~GD_FLG_LOG_READY;				// 虽然这里用的是gd,但是和用new_gd 效果一样				// 因为 gd指针的值 是寄存器(r9中的值				// new_gd指针的值 也是寄存器(r9)中的值			initr_trace				// null			initr_reloc				gd->flags |= GD_FLG_RELOC | GD_FLG_FULL_MALLOC_INIT;				// 表示 code was relocated to RAM				// 表示 full malloc() is ready			initr_caches				enable_caches					icache_enable						cache_enable((1 << 12));							reg = get_cr();							set_cr(reg | cache_bit);					dcache_enable						cache_enable((1 << 2));							if !mmu_enabled() mmu_setup();							reg = get_cr();							set_cr(reg | cache_bit);			// 此时mmu 没有enable										initr_reloc_global_data				monitor_flash_len = _end - __image_copy_start				// 计算出 monitor_flash_len				// 之后用来填充 gd->bd->bi_flashoffset				// reserved area for startup monitor // 不知道用来干什么的				// struct bd_info 定义在 include/asm-generic/u-boot.h			initr_barrier				// null			initr_malloc				malloc_start = gd->relocaddr - (1024*1024);				// = 5FEB 3000				// malloc 的区域 在 5FEB 3000 - 5FFB 3000 , 有 1024KB				mem_malloc_init((ulong)map_sysmem(malloc_start, (1024*1024)), (1024*1024));					mem_malloc_start = start;						// 5FEB 3000					mem_malloc_end = start + size;						// 5FFB 3000					mem_malloc_brk = start;					memset((void *)mem_malloc_start, 0x0, size);					malloc_bin_reloc();						// null			log_init				// null			initr_bootstage				bootstage_mark_name(BOOTSTAGE_ID_START_UBOOT_R, "board_init_r");			initr_console_record				// null			initr_of_live				// null			initr_dm				gd->dm_root_f = gd->dm_root;				gd->dm_root = 0;				dm_init_and_scan(0);				bootstage_accum(BOOTSTAGE_ID_ACCUM_DM_R);			board_init				cs8900_pre_init // 这里不是cs8900,而是dm9000a					// SROM 控制器的初始化					(*(vu_long *)(0x70000000 +0x0)) &= ~(0xf << 4);					(*(vu_long *)(0x70000000 +0x0)) |= (1 << 7) | (1 << 6) | (1 << 4);					(*(vu_long *)(0x70000000 +0x8)) = (((0x0) << 28) + ((0x4) << 24) + ((0xE) << 16) + ((0x1) << 12) + ((0x4) << 8) + ((0x6) << 4) + (0x0));					// 0x7000_0000 0x700F_FFFF SROM SFR														gd->bd->bi_arch_number = 1626;				// CONFIG_MACH_TYPE				// unique id for this board 				gd->bd->bi_boot_params = (0x50000000 +0x100);				// where this board expects params			initr_binman				// null			initr_dm_devices				// null			stdio_init_tables				INIT_LIST_HEAD(&devs.list);			serial_initialize 				serial_init			initr_announce				// null			power_init_board				// null			initr_mmc				puts("MMC:   ");				mmc_initialize(gd->bd);			initr_env				env_set_default(0,0)				image_load_addr = env_get_ulong("loadaddr", 16, image_load_addr);			initr_secondary_cpu				cpu_secondary_init_r					// null			stdio_add_devices				drv_system_init				serial_stdio_init			initr_jumptable				jumptable_init					gd->jt = malloc(sizeof(struct jt_funcs));					gd->jt->get_version = get_version;					gd->jt->getc = getchar;					gd->jt->tstc = tstc;					gd->jt->putc = putc;					gd->jt->puts = puts;					gd->jt->printf = printf;					gd->jt->install_hdlr = dummy;					gd->jt->free_hdlr = dummy;					gd->jt->malloc = malloc;					gd->jt->free = free;					gd->jt->udelay = udelay;					gd->jt->get_timer = get_timer;					gd->jt->vprintf = vprintf;					gd->jt->do_reset = do_reset;					gd->jt->env_get = env_get;					gd->jt->env_set = env_set;					gd->jt->simple_strtoul = simple_strtoul;					gd->jt->strict_strtoul = strict_strtoul;					gd->jt->simple_strtol = simple_strtol;					gd->jt->strcmp = strcmp;					gd->jt->i2c_write = dummy;					gd->jt->i2c_read = dummy;					gd->jt->spi_setup_slave = dummy;					gd->jt->spi_free_slave = dummy;					gd->jt->spi_claim_bus = dummy;					gd->jt->spi_release_bus = dummy;					gd->jt->spi_xfer = dummy;					gd->jt->ustrtoul = ustrtoul;					gd->jt->ustrtoull = ustrtoull;					gd->jt->strcpy = strcpy;					gd->jt->mdelay = mdelay;					gd->jt->memset = memset;			console_init_r				gd->flags |= GD_FLG_DEVINIT;				print_pre_console_buffer(flushpoint);			interrupt_init				IRQ_STACK_START_IN = gd->irq_sp + 8;					// gd->irq_sp 上面有16字节可用					// 现在IRQ_STACK_START_IN在 gd->irq_sp 上面8字节					// 且arm栈向下移动					// 所以irq栈应该只有8个字节可用					// 此举和 之前的 relocate_vectors有关				enable_interrupts					// null			initr_ethaddr				eth_env_get_enetaddr("ethaddr", bd->bi_enetaddr);			initr_net				puts("Net:   ");				eth_initialize			run_main_loop				main_loop

转载地址:http://ibigi.baihongyu.com/

你可能感兴趣的文章
朴素贝叶斯分类器
查看>>
贝叶斯学习举例--学习分类文本
查看>>
hadoop HDFS原理基础知识
查看>>
数据挖掘十大算法----EM算法(最大期望算法)
查看>>
android StrictMode应用
查看>>
TabHost的两种使用方法
查看>>
Android---TextView属性详解
查看>>
K近邻算法基础:KD树的操作
查看>>
数据挖掘十大算法--K近邻算法
查看>>
android对话框(Dialog)的用法
查看>>
Android使用Application总结
查看>>
android启动第一个界面时即闪屏的核心代码(两种方式)
查看>>
数据挖掘十大经典算法(详解)
查看>>
数据挖掘十大算法--K-均值聚类算法
查看>>
java中常用的日期格式化(全)
查看>>
POI操作Excel导入和导出
查看>>
java的md5加密算法代码
查看>>
jdbc连接数据库
查看>>
Android开发四大组件概述
查看>>
Hadoop主要子项目介绍(Pig Zookeeper Hbase Hive Sqoop Avro Chukwa Cassandra )
查看>>