此为个人学习笔记存档

week 7 可执行程序的装载

一、预处理、编译、链接和目标文件的格式

可执行文件的创建——预处理、编译和链接

cd Code
vi hello.c
gcc -E -o hello.cpp hello.c -m32
vi hello.cpp
gcc -x cpp-output -S -o hello.s hello.cpp -m32
vi hello.s
gcc -x assembler -c hello.s -o hello.o -m32
vi hello.o
gcc -o hello hello.o -m32
vi hello
gcc -o hello.static hello.o -m32 -static
ls -l
-rwxrwxr-x 1 shiyanlou shiyanlou 7292 3\u6708 23 09:39 hello
-rw-rw-r-- 1 shiyanlou shiyanlou 64 3\u6708 23 09:30 hello.c
-rw-rw-r-- 1 shiyanlou shiyanlou 17302 3\u6708 23 09:35 hello.cpp
-rw-rw-r-- 1 shiyanlou shiyanlou 1020 3\u6708 23 09:38 hello.o
-rw-rw-r-- 1 shiyanlou shiyanlou 470 3\u6708 23 09:35 hello.s
-rwxrwxr-x 1 shiyanlou shiyanlou 733254 3\u6708 23 09:41 hello.static

ELF目标文件格式

  • ELF文件格式 -- (中文翻译版)

  • 查看ELF文件的头部

      shiyanlou:Code/ $ readelf -h hello
  • 查看该ELF文件依赖的共享库

      shiyanlou:sharelib/ $ ldd main                                       [21:25:56]
    linux-gate.so.1 => (0xf774e000) # 这个是vdso - virtual DSO:dynamically shared

object,并不存在这个共享库文件,它是内核的一部分,为了解决libc与新版本内核的系统调用不同步的问题,linux-gate.so.1里封装的系统调用与内核支持的系统调用完全匹配,因为它就是内核的一部分嘛。而libc里封装的系统调用与内核并不完全一致,因为它们各自都在版本更新。

libshlibexample.so => /home/shiyanlou/LinuxKernel/sharelib/libshlibexample.so (0xf7749000)

libdl.so.2 => /lib32/libdl.so.2 (0xf7734000)

libc.so.6 => /lib32/libc.so.6 (0xf7588000)

/lib/ld-linux.so.2 (0xf774f000)

shiyanlou:sharelib/ $ ldd /lib32/libc.so.6 [21:37:00]

/lib/ld-linux.so.2 (0xf779e000)

linux-gate.so.1 => (0xf779d000)

# readelf -d 也可以看依赖的so文件

shiyanlou:sharelib/ $ readelf -d main [21:28:04]

Dynamic section at offset 0xf04 contains 26 entries:

0x00000001 (NEEDED) 共享库:[libshlibexample.so]

0x00000001 (NEEDED) 共享库:[libdl.so.2]

0x00000001 (NEEDED) 共享库:[libc.so.6]

0x0000000c (INIT) 0x80484f0

0x0000000d (FINI) 0x8048804

0x00000019 (INIT_ARRAY) 0x8049ef8

1.可执行程序是怎么来的?

c代码,经过预处理,变成汇编代码

经过汇编器,变成目标代码

连接成可执行文件

加载到内核中执行

编译过程

预处理:gcc –E hello.c –o hello.i;	gcc –E调用cpp	生成中间文件
编 译:gcc –S hello.i –o hello.s; gcc –S调用ccl 翻译成汇编文件
汇 编:gcc –c hello.s –o hello.o; gcc -c 调用as 翻译成可重定位目标文件
链 接:gcc hello.o –o hello ; gcc -o 调用ld** 创建可执行目标文件

64位机可用参数-m32

cpp是指预处理编译文件

gcc的一些参数

(1)基本选项
-c 只编译不链接,生成目标文件.o
-S 只编译不汇编,生成汇编代码
-E 只进行预编译,不做其他处理
-g 在可执行程序中包含标准调试信息
-o file 将file文件指定为输出文件
-v 打印出编译器内部编译各过程的命令行信息和编译器的版本
-I dir 在头文件的搜索路径列表中添加dir目录 -x language filename
设定文件使用的语言,这样源程序的后缀名无效了,并对gcc后接的多个编译文件都有效。这样如果存在.c和.cpp文件联编会有问题,解决这个问题用到了下一个参数 -x none filename,在下面做介绍。因为在预处理过程中对于.c和.cpp文件的处理方式是不一样的。可以使用的参数有:'c','objective-c','c-header','c++','cpp-output','assembler','assembler-with-cpp'.编译的时候,如果有这样的一个用C语言写的test.tmp的文件,用gcc编译的时候就用gcc -x c test.tmp就可以让gcc用编译C语言的方式来编译test.tmp. -x none filename
关掉上一个选项,就是让gcc根据文件名后缀,自动识别文件类型。如用下列方式编译: gcc -x c test.tmp -x none test2.c 这样可以自由地选择编译方式 (2)库选项
-static 进行静态编译,即链接静态库,禁止使用动态库
-shared 1.可以生成动态库文件
2.进行动态编译,尽可能的链接动态库,没有动态库时才会链接同名静态库
-L dir 在库文件的搜索路径列表中添加dir目录
-lname 链接称为libname.a或者libname.so的库文件。
如果两个库文件都存在,根据编译方式是static还是shared进行链接
-fPIC 生成使用相对地址的位置无关的目标代码,
(-fpic) 然后通常使用gcc的-static选项从该pic目标文件生成动态库文件。

2.目标文件的格式ELF

.o文件,可执行文件,都是目标文件,一般使用相同的文件格式。

常用文件格式:

  1. a.out
  2. COFF
  3. PE - WINDOWS上
  4. ELF - LINUX上

ABI:应用程序二进制接口

ABI和目标文件格式的关系:

ELF文件格式中有三种主要的文件格式:

  1. 可重定位文件

    主要是.o文件,保存有代码和适当数据,用来和其他的object文件一起来创建一个可执行文件或者共享文件
  2. 可执行文件

    保存着一个用来执行的程序,指出exec(BA_OS)如何创建程序进程映象。
  3. 共享目标文件

    保存代码和合适的数据,用来和链接器链接:

    • 链接编辑器,静态链接,和其他的可重定位、共享目标文件创建其他的目标文件
    • 动态链接器,连喝一个可执行文件和其他的共享目标文件来创建一个进程映像

文件格式

Object文件参与程序的联接(创建一个程序)和程序的执行(运行一个程序)。

object 文件格式提供了一个方便有效的方法并行的视角看待文件的内容,

在他们的活动中,反映出不同的需要。例 1-1图显示了一个object文件的

组织图。

  • 图1-1: Object文件格式

    Linking 视角                      Execution 视角
    ============ ==============
    ELF header ELF header
    Program header table (optional) Program header table
    Section 1 Segment 1
    ... Segment 2
    Section n ...
    Section header table Section header table (optional)

一个ELF头在文件的开始,保存了路线图(road map),描述了该文件的组织情况。

sections保存着object文件的信息,从连接角度看:包括指令,数据,符号表,重定位信息等等。特别sections的描述会出项在以后的第一部分。

第二部分讨论了段和从程序的执行角度看文件。

假如一个程序头表(program header table)存在,那么它告诉系统如何来创建一个进程的内存映象。被用来建立进程映象(执行一个程序)的文件必须要有一个程序头表(program header table);可重定位文件不需要这个头表。一个section头表(section header table)包含了描述文件sections的信息。每个section在这个表中有一个入口;每个入口给出了该section的名字,大小,等等信息。在联接过程中的文件必须有一个section头表;其他object文件可要可不要这个section头表。

只有ELF头(elf header)是在文件的固定位置。

查看一个可执行文件头部内容:

readelf -h

头部后是代码和数据,等等。

可执行程序加载的主要工作:

当创建或者增加一个进程映像的时候,系统在理论上将拷贝一个文件的段到一个虚拟的内存段

3.静态链接的ELF可执行文件和进程的地址空间

32位x86进程地址空间共4G,1G是内核空间。

如何加载到内存?

默认从0x8048000开始加载,然后头部需要占用一定空间,程序的实际入口可以在0x8048100等地方,即可执行文件加载到内存中开始执行的第一行代码的入口处。

一般静态链接会把所有代码放在一个代码段,

动态链接会有多个代码段。

二、可执行程序、共享库和动态链接

1.装载可执行程序之前的工作

命令行参数和shell环境,一般我们执行一个程序的Shell环境,我们的实验直接使用execve系统调用。

  1. $ ls -l /usr/bin 列出/usr/bin下的目录信息【其实是执行了一个可执行参数ls
  2. Shell本身不限制命令行参数的个数,命令行参数的个数受限于命令自身,
- 例如,int main(int argc, char *argv[])
- 又如, int main(int argc, char *argv[], char *envp[])
【envp是shell的环境变量。
  1. shell怎么传递和保存命令行参数和环境变量?

    ——Shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数

    • int execve(const char * filename,char * const argv[ ],char * const envp[ ]);
    • 库函数exec*都是execve的封装例程

看一个例子:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char * argv[])
{
int pid;
/* fork another process */
pid = fork();
if (pid<0)
{
/* error occurred */
fprintf(stderr,"Fork Failed!");
exit(-1);
}
else if (pid==0)
{
/* child process */
execlp("/bin/ls","ls",NULL);
}
else
{
/* parent process */
/* parent will wait for the child to complete*/
wait(NULL);
printf("Child Complete!");
exit(0);
}
}

命令行参数和环境变量是如何进入新程序的堆栈的?

——命令行参数和环境串都放在用户态堆栈中



实际上是把命令行参数和环境变量通过系统调用传递到内核处理函数,然后内核处理函数在创建新的用户态堆栈时都拷贝进去,来初始化新的可执行程序的堆栈。

即:

shell->execve->sys_execve,然后在初始化新程序堆栈时拷贝进去。

先函数调用参数传递,再系统调用参数传递。

2.装载时动态链接和运行时动态链接应用举例

动态链接分为可执行程序装载时动态链接运行时动态链接,如下代码演示了这两种动态链接。

(1)准备.so文件【linux下的

例:共享库如何生成

shlibexample.h - 定义了一个函数原型

shlibexample.c - 实现,很简单

#include <stdio.h>
#include "shlibexample.h" int SharedLibApi()
{
printf("This is a shared libary!\n");
return SUCCESS;
}

编译成libshlibexample.so文件,命令如下:

$ gcc -shared shlibexample.c -o libshlibexample.so -m32

动态加载的过程是一样的,只是名字不同:

dllibexample.h
dllibexample.c 编译成libdllibexample.so文件 $ gcc -shared dllibexample.c -o libdllibexample.so -m32
(2)分别以共享库和动态加载共享库的方式使用libshlibexample.so文件和libdllibexample.so文件
/* main.c */

#include <stdio.h>
#include "shlibexample.h" // 共享库
#include <dlfcn.h> // 动态加载 int main()
{
printf("This is a Main program!\n");
/* 调用共享库函数 Use Shared Lib */
printf("Calling SharedLibApi() function of libshlibexample.so!\n");
SharedLibApi();
/* 调用动态装载库 Use Dynamical Loading Lib */
void * handle = dlopen("libdllibexample.so",RTLD_NOW);
if(handle == NULL)
{
printf("Open Lib libdllibexample.so Error:%s\n",dlerror());
return FAILURE;
}
int (*func)(void); // 声明了一个函数指针
char * error;
func = dlsym(handle,"DynamicalLoadingLibApi"); // 找到这个函数指针
if((error = dlerror()) != NULL)
{
printf("DynamicalLoadingLibApi not found:%s\n",error);
return FAILURE;
}
printf("Calling DynamicalLoadingLibApi() function of libdllibexample.so!\n");
func(); // 使用
dlclose(handle);
return SUCCESS;
}

编译main,注意这里只提供shlibexample的-L(库对应的接口头文件所在目录)和-l(库名,如libshlibexample.so去掉lib和.so的部分),并没有提供dllibexample的相关信息,只是指明了-ldl【动态加载器

$ gcc main.c -o main -L/path/to/your/dir -lshlibexample -ldl -m32
$ export LD_LIBRARY_PATH=$PWD #将当前目录加入默认路径,否则main找不到依赖的库文件,当然也可以将库文件copy到默认路径下。【lib,或usr/lib
$ ./main
This is a Main program!
Calling SharedLibApi() function of libshlibexample.so!
This is a shared libary!
Calling DynamicalLoadingLibApi() function of libdllibexample.so!
This is a Dynamical Loading libary!

两种方式:

  1. 在程序执行过程中由程序自身装载共享库
  2. 在装载可执行程序时完成动态链接过程

三、可执行程序的装载

1.可执行程序的装载相关关键问题分析

可执行程序的装载其实还是系统调用,execve,比较特殊的系统调用.

陷入到内核态,在内核态中加载,把当前进程的可执行程序覆盖掉;execve返回时,就是新的可执行程序了。

sys_execve内核处理过程:

sys_execve内部会解析可执行文件格式:

  • do_execve -> do_execve_common -> exec_binprm

  • search_binary_handler符合寻找文件格式对应的解析模块,如下:

    【根据文件头部信息寻找对应的文件格式处理模块】

      1369    list_for_each_entry(fmt, &formats, lh) {
    1370 if (!try_module_get(fmt->module))
    1371 continue;
    1372 read_unlock(&binfmt_lock);
    1373 bprm->recursion_depth++;
    1374 retval = fmt->load_binary(bprm); //用来解析elf文件的执行到位置。
    1375 read_lock(&binfmt_lock);
  • 对于ELF格式的可执行文件fmt->load_binary(bprm);执行的应该是load_elf_binary,其内部是和ELF文件格式解析的部分,需要和ELF文件格式标准结合起来阅读。

  • Linux内核是如何支持多种不同的可执行文件格式的?

    实现技巧

      /* 全局变量elf_format,把函数指针load_elf_binary**赋值**给了.load_binary */
    82 static struct linux_binfmt elf_format = {
    83 .module = THIS_MODULE,
    84 .load_binary = load_elf_binary,
    85 .load_shlib = load_elf_library,
    86 .core_dump = elf_core_dump,
    87 .min_coredump = ELF_EXEC_PAGESIZE,
    88 }; /* 把变量elf_format**注册**进了format链表里,就可以在链表里对应elf模式中找到对应模块 */
    2198 static int __init init_elf_binfmt(void)
    2199 {
    2200 register_binfmt(&elf_format);
    2201 return 0;
    2202 }

elf_formatinit_elf_binfmt,这里就相当于观察者模式中的观察者,elf_format是观察者,1369开始的那段代码是被观察者,当elf文件出现的时候,就会自动执行load_elf_binary。

以上这一段是解析部分。

除此之外:

在load_elf_binary中调用了start_thread这个函数:

198 start_thread(struct pt_regs *regs, unsigned long new_ip, unsigned long new_sp)/* pt_regs 是内核堆栈栈底的函数,*/
199 {
200 set_user_gs(regs, 0);
201 regs->fs = 0;
202 regs->ds = __USER_DS;
203 regs->es = __USER_DS;
204 regs->ss = __USER_DS;
205 regs->cs = __USER_CS;
206 regs->ip = new_ip; //起点位置
207 regs->sp = new_sp;
208 regs->flags = X86_EFLAGS_IF;
209 /*
210 * force it to the iret return path by making it look as if there was
211 * some work pending.
212 */
213 set_thread_flag(TIF_NOTIFY_RESUME);
214 }
215 EXPORT_SYMBOL_GPL(start_thread);
  • 可执行文件开始执行的起点在哪里?

    通过修改内核堆栈中eip的值作为新程序的起点

以上适用于静态库。

2.sys_execve的内部处理过程

1604    SYSCALL_DEFINE3(execve,
1605 const char __user *, filename,
1606 const char __user *const __user *, argv,
1607 const char __user *const __user *, envp)
1608 {
1609 return do_execve(getname(filename), argv, envp);
1610 }
1611 #ifdef CONFIG_COMPAT
1612 COMPAT_SYSCALL_DEFINE3(execve, const char __user *, filename,
1613 const compat_uptr_t __user *, argv,
1614 const compat_uptr_t __user *, envp)
1615 {
1616 return compat_do_execve(getname(filename), argv, envp);
1617 }
1618 #endif

sys_execve函数中返回了一个do_execve:

1549    int do_execve(struct filename *filename,
1550 const char __user *const __user *__argv,
1551 const char __user *const __user *__envp)
1552 {
1553 struct user_arg_ptr argv = { .ptr.native = __argv };
1554 struct user_arg_ptr envp = { .ptr.native = __envp };
1555 return do_execve_common(filename, argv, envp);
1556 } 最后一句中do_execve_common把文件名,参数和环境转换了一下。

该函数do_execve_common打开如下:

1474    	file = do_open_exec(filename);
打开了一个要加载的可执行文件,然后会加载一下它的头部,建立一个结构体,把命令行参数和环境变量拷贝到结构体中; 1513 retval = exec_binprm(bprm);
对这个可执行文件的处理过程。

打开exec_binprm这个函数,可以找到一句重要代码:

1416    	ret = search_binary_handler(bprm);
寻找这个我们打开的可执行文件的处理函数。

打开search_binary_handler,找到list_for_each_entry如下:

1369	list_for_each_entry(fmt, &formats, lh) {
1370 if (!try_module_get(fmt->module))
1371 continue;
1372 read_unlock(&binfmt_lock);
1373 bprm->recursion_depth++;
1374 retval = fmt->load_binary(bprm);
1375 read_lock(&binfmt_lock);
1376 put_binfmt(fmt);
1377 bprm->recursion_depth--;
1378 if (retval < 0 && !bprm->mm) {
1379 /* we got to flush_old_exec() and failed after it */
1380 read_unlock(&binfmt_lock);
1381 force_sigsegv(SIGSEGV, current);
1382 return retval;
1383 }
1384 if (retval != -ENOEXEC || !bprm->file) {
1385 read_unlock(&binfmt_lock);
1386 return retval;
1387 }
1388 } 在这个循环里寻找能够解析这个当前可执行文件的代码模块。 retval = fmt->load_binary(bprm); // 这一句中的load_binary,加载处理函数。这一句是函数指针,实际上是调用的load_elf_binary。

load_elf_binary的赋值和注册:见上一节

load_elf_binary -> start_thread

load_elf_binary的函数中涉及到很多文件解析的内容。

核心工作是把文件映射到进程的空间中。

ELF可执行文件会被默认映射到0x8048000这个地址。

需要动态链接?

可执行文件先加载链接器ld

动态链接库的执行过程

887	if (elf_interpreter) {
888 unsigned long interp_map_addr = 0;
889
890 elf_entry = load_elf_interp(&loc->interp_elf_ex,
891 interpreter,
892 &interp_map_addr,
893 load_bias);
需要加载连接器

静态链接的执行过程

912     else {
913 elf_entry = loc->elf_ex.e_entry;
直接把elf文件的entry地址赋给elf_entry。

但是在start_thread中是直接用的elf_entry:

start_thread(regs,elf_entry, bprm->p);

1.如果是一个静态连接的文件,elf_entry就是指的main函数开始的位置
2.如果是一个需要依赖动态链接库的文件,elf_entry指向的是动态链接器的起点,将cpu控制权交给ld来加载依赖库并完成动态链接。

※ 对于静态链接的文件,elf_entry是新程序执行的起点。

3.使用gdb跟踪sys_execve内核函数的处理过程

这一节内容参见 实验总结

4.可执行程序的装载与庄生梦蝶的故事

╮(╯_╰)╭就是一个比喻……

本体喻体对应如下:

  • 庄周 调用execve的可执行程序
  • 入睡 调用execve陷入内核
  • 醒来 系统调用execve返回用户态
  • 发现自己是蝴蝶 被execve加载的可执行程序

5.浅析动态链接的可执行程序的装载

  • 动态链接的过程内核做了什么?

    ELF文件格式需要依赖

    动态链接库也会依赖别的动态链接库。

    就相当于一颗树

    需要逐步解析然后加载。

  • 可执行文件依赖的动态链接库(共享库)是由谁负责加载以及如何递归加载的?

关注ELF文件中.interp和.dynamic

动态链接器ld负责解析,加载,解析,装载和链接后ld再将CPU的控制权交给可执行程序(头部规定的起点位置)。

——这个动作不是由内核完成,是由动态链接器完成的。

动态链接库的装载过程其实是一个图的广度遍历

所以——

两种加载的方法:

  1. 静态库:直接执行可执行程序的入口
  2. 动态库:由ld来动态链接这个程序,然后再把控制权移交给可执行程序的入口。

20135202闫佳歆--week7 可执行程序的装载--学习笔记的更多相关文章

  1. 20135202闫佳歆--week5 课本18章学习笔记

    第十八章 调试 内核级开发的调试工作远比用户级开发艰难的多. 一.准备开始 准备工作需要的是: 一个bug 一个藏匿bug的内核版本 相关内核代码的知识和运气 在这一章里,调试的主要思想是让bug重现 ...

  2. 20135202闫佳歆--week3 课本1-2章学习笔记

    第一章 Linux内核简介 一.Unix Unix是一个强大.健壮和稳定的操作系统. 简洁 绝大部分东西都被当做文件对待.这种抽象使对数据和对设备的操作都是通过一套相同的系统调用借口来进行的:open ...

  3. 20135202闫佳歆--week 7 Linux内核如何装载和启动一个可执行程序--实验及总结

    week 7 实验:Linux内核如何装载和启动一个可执行程序 1.环境搭建: rm menu -rf git clone https://github.com/megnning/menu.git c ...

  4. 20135202闫佳歆--week 9 期中总结

    期中总结 前半学期的主要学习内容是学习mooc课程<Linux内核分析>以及课本<Linux内核设计与实现>. 所涉及知识点总结如下: 1. Linux内核启动的过程--以Me ...

  5. 20135202闫佳歆--week2 一个简单的时间片轮转多道程序内核代码及分析

    一个简单的时间片轮转多道程序内核代码及分析 所用代码为课程配套git库中下载得到的. 一.进程的启动 /*出自mymain.c*/ /* start process 0 by task[0] */ p ...

  6. 20135202闫佳歆--week3 跟踪分析Linux内核的启动过程--实验及总结

    实验三:跟踪分析Linux内核的启动过程 一.调试步骤如下: 使用gdb跟踪调试内核 qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd r ...

  7. 20135202闫佳歆--week6 分析Linux内核创建一个新进程的过程——实验及总结

    week 6 实验:分析Linux内核创建一个新进程的过程 1.使用gdb跟踪创建新进程的过程 准备工作: rm menu -rf git clone https://github.com/mengn ...

  8. 20135202闫佳歆--week 8 课本第4章学习笔记

    第四章 进程调度 一.多任务 多任务操作系统就是能同时并发的交互执行多个进程的操作系统. 多任务操作系统使多个进程处于堵塞或者睡眠状态,实际不被投入执行,这些任务尽管位于内存,但是并不处于可运行状态. ...

  9. 20135202闫佳歆--week 8 进程的切换和系统的一般执行过程--学习笔记

    此为个人笔记存档 week 8 进程的切换和系统的一般执行过程 一.进程调度与进程切换 1.不同的进程有不同的调度需求 第一种分类: I/O密集型(I/O-bound) 频繁的进行I/O 通常会花费很 ...

随机推荐

  1. Q矩阵输出

    程序启动时: 1.Q矩阵在InitQX中对角阵赋初值为0.25,GPS卫星数6 2.Q矩阵初值在初始化时由GetBL获得,改变Q对角阵 Q初值第0个卫星 10000000000.000 X初值第0个卫 ...

  2. LINE学习

    LINE Abstract LINE 是一种将大规模网络结点表征成低维向量的算法,可很方便用于网络可视化,结点分类,链路预测,推荐. source code Advantage LINE相比于其他算法 ...

  3. Django商城项目笔记No.13用户部分-用户中心个人信息

    首先处理个人信息的显示 邮箱绑定: 首先给用户的模型类里添加一个字段来说明用户的邮箱是否激活 然后数据库迁移 python manage.py makemigrations python manage ...

  4. 博客系统实战——SprintBoot 集成Thymeleaf 实现用户增删查改(含源码)

    近来在学习SprintBoot +Thymeleaf +Maven搭建自己的博客系统,故在学习过程中在此记录一下,也希望能给广大正在学习SprintBoot和Thymeleaf的朋友们一个参考. 以下 ...

  5. [转]vue全面介绍--全家桶、项目实例

    慢慢了解vue及其全家桶的过程 原文http://blog.csdn.net/zhenghao35791/article/details/67639415 简介 “简单却不失优雅,小巧而不乏大匠”.  ...

  6. Python3中遇到UnicodeEncodeError: 'ascii' codec can't encode characters in ordinal not in range(128)

    在 linux服务器上运行代码报错: Python3中遇到UnicodeEncodeError: ‘ascii’ codec can’t encode characters in ordinal no ...

  7. 3.HBase In Action 第一章-HBase简介(1.1.1 大数据你好呀)

    Let's take a closer look at the term Big Data. To be honest, it's become something of a loaded term, ...

  8. Fuel 30 分钟快速安装OpenStack

    一直以来,对于openstack 的初学者来讲,安装往往是入门的头大难题.在E版本之前,要搭建一个基本能用的openstack 环境那是相当麻烦,自己要装机,自己搞源,自己照着文档敲命令,又没有靠谱的 ...

  9. 启动报错:Access denied for user 'root'@'localhost' (using password:YES)

    项目启动报错:Access denied for user 'root'@'localhost' (using password:YES) 原因:root帐户默认不开放远程访问权限,所以需要修改一下相 ...

  10. Javascript中的Form表单知识点总结

    Javascript中的Form表单知识点总结 在HTML中,表单是由form元素来表示的,但是在javascript中,表单则由HTMLFormElement类型,此元素继承了HTMLElement ...