数据库索引的实现

数据库索引,目前有树索引、哈希索引、全文索引等。

索引

索引是数据之外的一种数据结构,专门用来查找表中数据。
使用索引,可以大大加快检索速度,相应的,有获得必须有付出,我们需要话费额外的空间来存储索引。

索引这玩意儿,不像烧饼,做好了吃完就没了,索引有许多的优点,如:加快检索速度,数据无重复,加快表与表间的连接速度,有序的索引方便连续值查找。

索引这玩意儿,和其它的东西一样,既有优点又有缺点,它的缺点有:,创建和维护索引耗费时间、存储索引需要空间。

树索引

商业化的数据库中,一般都支持树索引,B-Tree 或 B+Tree。(实际上 B+Tree 使用更多)

和哈希索引相比,树索引的查找速度最慢会达到 O(LogN),但树索引是有序的。

哈希索引

哈希索引基于哈希表实现,将所有索引列(有时不只给一列数据建索引,要给两列三列甚至更多列建索引)结合起来,从而为每一行数据算出一个尽量独一无二的哈希值。
哈希值和该哈希值对应的数据行的行指针组合在一起,便是哈希表中的一行数据,N 行数据一起就是哈希表。

处理冲突

每当两个不同的数据拥有相同的哈希值,就意味着出现了冲突。冲突出现后,哈希索引会将彼此冲突的数据们串连起来,形成巨长的一条链(请别忘记我永远不变黄色的脸)

优点:

查找速度高达 O(1)。

缺点

  1. 只保存哈希值和行指针,不保存数据库中的数据;查找数据时,先计算数据的哈希值,使用该值来查找行指针,找到行指针后还要到数据库中验证,确保该数据行就是要查找的数据,所以每次查找都要读行
  2. 哈希表数据无序,无法用于排序
  3. 不支持部分索引列查找(建立了两列的索引,那就没法只用一列的数据来查找)。
  4. 索引维护成本高(如果哈希冲突很多,每从表中删除一行就要遍历整条冲突链,以便删除目标行)。
  5. 哈希值可能冲突

全文索引

全文索引是一种特殊的索引,它并不直接匹配 Where 条件,不直接比较索引中的值,而是像搜索引擎那样,查找的是文本中的关键词。

从0到1理解JVM_ 即时编译器JIT

JIT 概念

JIT,即时编译器(just in time compiler),是 JVM 的一种优化技术。最开始的时候,JAVA 代码被编译为字节码,字节码在各个平台上都被解释执行,这给 JAVA 带来了平台无关性的优点,也带来了执行速度慢的缺点。

为了改进该缺点,HotSpot 虚拟机在执行字节码时,若发现某个方法或代码块执行过于频繁,会把它们定义为“热点代码“,尝试性将字节码进一步编译为本地代码,不再解释字节码,而是执行机器语言,JIT 技术,指的就是这个改进。

谁会被编译

既然 JIT 会将代码编译为机器码,那它编译谁呢?

被编译的代码有两种,一种是频繁调用的方法,另一种是频繁调用的循环体(如 for 循环、while 循环)。

所谓“频繁调用的方法”,顾名思义,就是调用次数多的方法,会被编译成本地代码。频繁调用的循环体,其所在的方法会被当做“频繁调用的方法”进行编译(这就是“栈上替换“,当循环体所在的方法仍旧在栈上执行的同时,就被编译并替换)。

总之,被JIT技术编译的只有方法和循环体。

怎样才会启动编译(热点探测)

什么时候才编译方法呢?什么时候才能判定一个方法或循环体是热点代码呢?HotSpot 虚拟机使用了基于计数器的热点探测。

HotSpot 虚拟机给每个方法都加了两个计数器:方法调用计数器、回边计数器。每当方法调用一次时,计数器就加1,计数器达到一定的阈值后,就启动编译进程,将方法编译为本地代码,下一次调用该方法时,就执行编译后的本地代码。

如何编译

JIT 有两种编译器:client 版和 server版。

client编译器是个三阶段的编译器,第一阶段做方法内联/常量传播等优化,将字节码编成高级中间表示(HIR),第二阶段做 null 检查/范围检查等优化,将 HIR 编成低级中间表示(LIR), 第三阶段则是分配寄存器和生成机器码。

server 编译器会执行所有的优化动作,如方法内联,公共子表达式消除,复制传播,常量传播,无用代码消除,逃逸分析等等,优化后的代码执行速度堪比编译型语言如 C、C++等!

逃逸分析

所谓逃逸,就是一个对象在某方法里定义后,会被外部引用。

如果能确定一个对象不会逃逸,那么就可以将对象分配到栈上(栈上分配),尽可能不使用堆,减小 GC 的压力;同时,既然对象不会被外部引用,在同步时就不必考虑该对象了,减小了线程同步的复杂性;另外,还能进行标量替换(用大量的分配在栈上的原始数据类型代替对象)。

如今逃逸分析尚未成熟,是 JIT 发展的重要方向。

JDK7/8的新特性

JDK7的新特性

数值表示

JDK7之中,byte 类型的变量可以直接赋值二进制,如:

1
byte var = 0B00000101

数字之间可以加上下划线,以便更好地阅读,如:

1
int var = 4_000_000;

switch 语句接受 String

在 JDK7之前的 switch语句中,case 无法判断目标值是哪个字符串,而从 JDK7开始,你可以毫不犹豫地这么干:

1
2
3
4
5
6
7
8
9
10
String str = "hello";
switch (str) {
case "hi" :
// doSomething();
break;
case "hello":
break;
default:
break;
}

泛型对象

实例化一个泛型对象时,不必再在菱形中具体指出是类型:

1
List<String> list = new ArrayList<>();

Try with resource

以前,在 try 语句之中我们一般会打开 IO 资源进行操作,如果有异常便抛出异常进行处理,一般try 语句也会附带一个关闭语句。
如果只是在打开资源时出现异常就很好办,直接在 catch 语句中捕捉异常即可,但是如果在关闭资源时又出现异常怎么办?
答:之前的异常被抛弃,只抛出关闭异常。
这个问题的场景就引出了 try with resource,不仅自动处理资源的关闭,而且在出现 close 异常时,既抛出之前的异常,也抛出 close 异常:

1
2
3
4
5
6
// 括号中的资源会被自动关闭
try (BufferedReader bufferedReader = new BufferedReader(new FileReader(str))) {
// doSomething()
} catch (IOException e) {
e.printStackTrace();
}

Multi catch

往后,在一个 catch 语句当中,可以捕获多个异常,如:

1
2
3
4
5
6
try (xxxx)) {
// doSomething()
// 捕捉多个异常
} catch (IOException | AcceptPendingException e ) {
e.printStackTrace();
}

异常重抛

以往,我们在 try 之中抛出子异常,而在 catch 中检测到的是父异常,如果在 catch 中又将异常抛出,那么外部就只能捕获到父异常,而无法知晓具体的子异常是什么,在JDK7里可以手动地限制整个类抛出的子类(哪怕 catch 中捕获到的是父类)

JDK8 的新特性

Lambda 表达式

将函数作为另一个函数的参数

重复注解

同一个注解可以在同一个地方出现多次

接口的默认方法和静态方法

静态方法类似于单例模式,默认方法不像抽象方法那样必须被实现(加上一个default 关键字),实现类既可以实现该方法,也可以不实现该方法。

JAVA集合框架

JAVA 集合框架图(简单版)

集合框架

Collection 接口

Collection 是集合框架中最基本的接口(注意,是接口),它和 Map 接口并列为最顶级接口,集合框架中的其它集合都是从他们继承而来。
Collection 接口在集合框架中起的最重要的作用是提供了遍历每个元素的方法:iterator 方法,返回迭代子,程序员们可以根据这个迭代子来遍历所有元素。

1
2
3
4
Iterator it = collection.iterator();  
while(it.hasNext()) {
   Object obj = it.next();
}

List 接口

List 接口继承于 Collection 接口,和 Set 接口处于同一个级别。和 Set 接口相比,它有序、可以控制插入位置、允许重复、类似数组。
List 接口继承自 Collection,它比Collection多了一个 ListIterator迭代子,该迭代子使 List 可以插入/删除数据,双向遍历。

实现 List 接口的类有 ArrayList、LinkedList、Vector、Stack等。

ArrayList

ArrayList 实现了 List 接口,和古老的 Vector 相比,它放弃了同步的特性,以便追求更高的性能(Vector 在各个函数前都加上了 synchronized 关键字,导致性能下降的很厉害)。
ArrayList 的 get/set/size/isEmpty 等函数复杂度为常数级,而add 函数的复杂度则为 O(N),因为插入后要移动元素。
和 LinkedList 相比,ArrayList 底层用数组来实现,默认容量为10,填满后扩容为原先的150%。

Vector

Vector 的出现早于 Collection 集合框架,它和 HashTable 是同一个时代的产物。
Vector 极度类似 ArrayList(甚至可以说 ArrayList 是去掉了 synchronized 关键字的Vector),也是基于数组。
Vector 支持着同步,当一个线程正在操作着 iterator 的同时,另一个线程修改了 Vector,如增加一个元素或修改一个元素,第一个线程再调用iterator 的方法就会抛异常ConcurrentModificationException。

LinkedList

LinkedList 基于链来实现,插入删除极快,遍历很慢,复杂度高达O(N)
LinkedList 也不支持同步。

Stack

Stack 和 Vector 是同一时代产物,继承自 Vector,支持同步,现已被抛弃(官方建议使用 Deque来实现栈)。

Set 接口

Set 接口同样继承自 Collection 接口,和 List 接口处于同一个级别。Set 中的元素无序、不重复,无法控制插入位置。(实际上,我们将 Set 看作一个黑盒,在 Set 中,元素就像是放入黑箱子,无人知晓它们的位置,元素之间都是独特单一的,我们也不知道元素被插到哪里去)。

实现 Set 接口的类有HashSet、TreeSet、SortedSet等。

HashSet

底层是个 HashMap,不支持同步,元素间无序,无法知晓插入位置,无重复。

TreeSet

TreeSet 底层是TreeMap(一般Set的底层都是Map),而TreeMap底层是一颗红黑树(红黑树的应用还挺多的,HashMap也用红黑树)。元素插入进去之后是有序,插入删除的 时间复杂度为O(LogN)。

Map 接口

Map 接口和 Collection 接口处于同一个级别,是(Key,Value)的集合,而 Collection 则是(Value)的集合。

实现了 Map 接口的有HashMap、TreeMap(SortedMap)、HashTable、WeakHashTable。

HashMap

HashMap 是典型的 key-value 型的数据集合,用哈希实现,get的速度高达 O(1),以前get在最坏的情况下会是O(n),现在当哈希码相同的同一条链上元素数目超过8时,该链会转为平衡树,所以最坏的情况下是O(logN)。
HashMap 底层是数组,扩容时直接扩为原先的200%。
和 HashTable 相比,HashMap 不支持同步,但性能高;HashMap 允许插入 null 的 key 或 null 的 value,而 HashTable 不允许;
计算数组下标时,HashMap 会做移位优化,以便将高位扩散到低位,而 HashTable 只会做单纯的模运算。
当冲突过多时,HashMap 会将冲突链转换为平衡树,进一步降低冲突的元素的寻找速度( 从 O(N) 降为 O(LogN) )。

旋转数组里的最小元素

题目描述

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

简单解答

旋转前的数组有序递增,最小元素在数组开头处,旋转之后数组分为两个有序递增的数组,最小元素处在数组的中间。我们可以从头往后扫描,遇到最小元素前,后一个元素总是大于前一个元素,如果不是,那就找到了最小元素。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public int minNumberInRotateArray(int[] array) {
if (array.length == 0) {
return 0;
}

// 往后扫描,遇到递减则说明找到最小元素。
for (int i = 0, head = 0; i < array.length; i++) {
if (i + 1 < array.length) {
if (array[i + 1] < array[i])
return array[i + 1];
}
}

return array[0];
}

该算法简单易懂,缺点也很明显:复杂度高达O(N),一个长度为十亿的数组估计得扫描十亿次才能找到。

优化解答

首先我们可以建立一个概念,那就是,本来的数组是有序的,旋转后的数组分段有序,且最后一个元素肯定小于等于第一个元素,我们可以利用这一点来采用二分查找算法。
使 left 指向旋转数组最左边的元素,使 right 指向旋转数组最右边元素,mid 指向中间元素。如果中间元素大于 array[left],那就说明最小元素在中间元素右边,否则就在左边,前一种情况我们令 left 指向 mid,后一种情况我们令 right 指向 mid,这样扫描完毕之后,left 会紧紧挨着 right,且 left 指向最大的元素,right 指向最小的元素。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public int minNumberInRotateArray(int[] array) {
if (array.length == 0)
return 0;

int left = 0, right = array.length - 1;
int mid;

while (array[left] >= array[right]) {
mid = (left + right) / 2;

// 中间元素大于最左边的元素,说明中间元素在前面的数组里头,最小元素在后面。
if (array[mid] >= array[left]) {
left = mid;
} else if (array[mid] < array[left]) {
right = mid;
}

if (left + 1 == right)
break;
}

return array[right];
}

这样一来,复杂度可以降到 O(LogN)。

数值的整数次方

给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。

在设计算法的时候,我们需要考虑如下几个特殊情况:
1)指数为0,这时不管基数是多少,最终结果都为1(0的0次方是多少?)。
2)指数为负数,计算过程类似于指数为正的情况,但最终结果需要取倒数。

以计算9的10次方为例:
1)最简单的一种思路是循环,循环10次,每次都将结果乘以9,缺点是复杂度高达O(N)。
2)一种相对比较巧妙的思路是利用数学,将计算过程拆分为9的8次方乘以9的2次方。
先将10写成二进制形式1010,从后面往前扫描,每扫描一位就将基的大小翻倍,遇到1就将结果乘以基。

9的10次方的计算过程

扫描右边第一个0时仅将9翻倍为81,扫描右边第一个1时将81乘以结果(默认是1)并将81翻倍为9的4次方,扫描右边第二个0时仅将9的4次方再次翻倍得到9的8次方,扫描最左边第一个1时将9的8次方乘以结果,扫描完毕,计算完成。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public double Power(double base, int exponent) {
if (exponent == 0) {
return 1;
}

double result = 1, b = base;

int exp = exponent >= 0 ? exponent : -exponent;

while (exp > 0) {
if ((exp & 1) == 1) {
result *= b;
}

b *= b;
exp >>= 1;
}

return exponent > 0 ? result : 1 / result;
}

该算法的复杂度为O(LogN)。

重排序数组

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。

思路:
从前向后扫描,遇到奇数的话,就将这个奇数与在它最前面的那个偶数交换,扫到最后,所有的奇数都可以排在偶数前面。最差情况下,复杂度高达O(N*N)。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void reOrderArray(int [] array) {
int temp = 0;
for (int i = 1; i < array.length; i++) {
// 遇到了一个奇数
if (array[i] % 2 == 1) {
// 遍历它前面的所有数,将它插入到偶数前面
for (int j = i; j > 0; j--) {
if (array[j - 1] % 2 == 0) {
temp = array[j];
array[j] = array[j - 1];
array[j - 1] = temp;
}
}
}
}
}

计算机.大端小端

如果一条字符串的每个字符都只用一个字节就能存储,那么大端或小端规则是不存在的(每个字节存储一个字符即可)。

只是,如果一个字符需要用至少两个字节的话,就必须规定好该字符的高位放在何处、地位又放在何处,比如,有一个字符是0XEEFF,那么是EE放前面呢,还是FF放后面?

大端

字符串高位的内容放在低地址,所以EE放在前面。

小端

字符串高位的内容放在高地址,所以EE放在后面。

没事系列.二分查找算法

二分查找的精髓在于,每查一次,待查找范围能缩小为原先的一半,由N变N/2,再变N/4,再变N/8…,直到只剩下一个元素。

写一个二分查找很容易,写一个好的二分查找很难。

EXAMPLE

下面是一个简单示例,要求查到某元素最先出现的下标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 最简单的思路,利用中间的数作为参考,找到了就往前(或往后)遍历。
public int getPosB(int[] A, int n, int val) {

int min = 0, max = n - 1, middle;

while (min <= max) {
middle = (min + max) / 2;

if (A[middle] == val) {
// 向前遍历,找出第一次出现的那个数
while (middle - 1 >= min && A[middle - 1] == val)
middle--;
return middle;
}

if (A[middle] > val) max = middle - 1;
if (A[middle] < val) min = middle + 1;
}

return -1;
}

上面代码的缺点在于,利用二分查找找到目标值后,又使用了速度较慢的线性查找,我们可以将线性查找的内容也放入二分查找:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 给定一个整数数组A及它的大小n,同时给定要查找的元素val,请返回它在数组中的位置(从0开始),
* 若不存在该元素,返回-1。
* 若该元素出现多次,请返回第一次出现的位置。
* <p>
* 稍微复杂的思路:将最小指针min与最大指针max都逼向最左边的元素,也就是第一次出现。
*/
private int getPos(int[] A, int n, int val) {
int min = 0, max = n - 1, middle;

while (min < max) {
middle = min + (max - min) / 2; // 防止(min + max)溢出
if (A[middle] == val)
max = middle;
else if (A[middle] > val)
max = middle - 1;
else if (A[middle] < val)
min = middle + 1;
}

return A[min] == val ? min : -1;
}

改进后,中间指针middle不再作为最终结果的指针,最终指针由min担当,将min和max都逼向最终值(而不是将middle逼向最终值),当min == max时就能结束循环。

定制6410开发板的linux最小内核/文件系统

故事的背景

本学期俺老孙学习嵌入式操作系统(),最终课程项目是定制内核与开发一个视频播放程序:

1.定制和移植6410开发板上的嵌入式linux最小内核与最小文件系统
2.在最小系统的基础上加入利用6410平台视频硬编码的视频播放器

我手上拥有的硬件:Tiny6410开发板一个(屏幕型号是H43)。
目标:最短时间内最高效地完成任务。

交叉编译环境搭建

安装虚拟机

上头给的开发系统是Fedora 9(32位),有点老,安装时需要注意一些问题。

问题1

我的虚拟机是VM 11,安装Fedora 9时需将处理器数量设为1,每处理器核心设为2,如果不这样做您就会卡在安装界面,如下图:
![卡住的界面][1]

正常的安装界面是这样的:
![正常的安装界面][2]

问题2

设置磁盘大小时,最好设置为20G,太小可能出问题,到时需要重装(编译内核需要占用极大的空间)。

问题3

启动Fedora 9虚拟机时,VM11提示VMware Workstation cannot connect to the virtual machine

对付此问题,只需打开控制面板——管理工具——服务,找到VMware Authorization Service服务,右键设置启动类型为自动,再右键启动。

问题4

安装完,开机后直接进入黑乎乎的字符界面,尝试用startx命令进入图形界面时,被告知no screen found

这是由于您使用了VM11的简便安装模式,它会默认跳过图形界面的安装,只安装服务器必须的软件,默认安装只828个包,实际上需要一千多个才有图形界面:
![][3]

解决方法:在创建虚拟机时选择稍后安装操作系统,创建完毕再添加镜像文件,启动后就是自定义安装了。

问题5

一切装置完了之后,再次卡住,卡在Starting udev
![][4]

这个有点复杂,估计是硬件不兼容之类的问题,持续等待几分钟后再次提示“Wait timeout. Will continue in the background.”

于是俺百度谷歌了一番,有人说是fedora识别方面有些BUG需要修改fedora的一些文件,但我进不了系统,也就没法从这里入手解决;有人说是CPU数量大于1的关系,我将CPU数量设为1,核心数量也设为1,依旧不行。

这时我想起来,按照VM11的简便模式安装后可以进入登录界面,而自定义安装加上图形界面就无法初始化硬件,莫非,是屏幕显示器的问题?

于是我修改虚拟机的屏幕,改“将主机设置用于监视器”“制定监视器设置”,并指定分辨率为1024 * 768(我的显示器是27寸1080P,可能fedora9带不起),然后,就能成功进入设置界面了。
![][5]

注意以上问题,虚拟机很容易就安装完毕了。

交叉编译工具

准备工具包

crosstool-0.43.tar.gz
binutils-2.16.1.tar.bz2
gcc-4.0.2.tar.gz
glibc-2.3.6.tar.gz glibc-linuxthreads-2.3.6.tar.gz linux-2.6.26.tar.gz linux-libc-headers-2.6.12.0.tar.bz

使用非root用户登录

建立存放工具包源码的cross目录

1
mkdir /home/qiuyongchen/cross
将 crosstool-0.43.tar.gz 复制到 cross 目录并解压
将其它工具包复制到cross目录
进入crosstool-0.43 目录对相关文件进行修改

【1】修改 arm.dat

1
2
3
KERNELCONFIG=`pwd`/arm.config  
TARGET=arm-linux
TARGET_CFLAGS="-O"

【2】修改 demo-arm.sh:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#!/bin/sh  
# This script has one line for each known working toolchain
# for this architecture. Uncomment the one you want.
# Generated by generate-demo.pl from buildlogs/all.dats.txt

TARBALLS_DIR=/home/lit/cross(修改这里) #下载的源码包存放的路径
RESULT_TOP=/home/qiuyongchen/crosstool(修改这里)#交叉编译工具链安装的路径
export TARBALLS_DIR RESULT_TOP
GCC_LANGUAGES="c,c++"
export GCC_LANGUAGES

# Really, you should do the mkdir before running this,
# and chown /opt/crosstool to yourself so you don't need to run as root.
mkdir -p $RESULT_TOP

#eval `cat arm.dat gcc-2.95.3-glibc-2.1.3.dat` sh all.sh --notest
#eval `cat arm.dat gcc-2.95.3-glibc-2.2.2.dat` sh all.sh --notest
#eval `cat arm.dat gcc-2.95.3-glibc-2.2.5.dat` sh all.sh --notest
#eval `cat arm.dat gcc-3.2.3-glibc-2.2.5.dat` sh all.sh --notest
#eval `cat arm.dat gcc-3.2.3-glibc-2.3.2.dat` sh all.sh --notest
#eval `cat arm.dat gcc-3.2.3-glibc-2.3.2-tls.dat` sh all.sh --notest
#eval `cat arm.dat gcc-3.3.6-glibc-2.2.2.dat` sh all.sh --notest
#eval `cat arm.dat gcc-3.3.6-glibc-2.2.5.dat` sh all.sh --notest
#eval `cat arm.dat gcc-3.3.6-glibc-2.3.2.dat` sh all.sh --notest
#eval `cat arm.dat gcc-3.3.6-glibc-2.3.2-tls.dat` sh all.sh --notest
#eval `cat arm.dat gcc-3.4.5-glibc-2.2.2.dat` sh all.sh --notest
#eval `cat arm.dat gcc-3.4.5-glibc-2.2.5.dat` sh all.sh --notest
#eval `cat arm.dat gcc-3.4.5-glibc-2.3.2.dat` sh all.sh --notest
#eval `cat arm.dat gcc-3.4.5-glibc-2.3.2-tls.dat` sh all.sh --notest
#eval `cat arm.dat gcc-3.4.5-glibc-2.3.5.dat` sh all.sh --notest
#eval `cat arm.dat gcc-3.4.5-glibc-2.3.5-tls.dat` sh all.sh --notest
#eval `cat arm.dat gcc-3.4.5-glibc-2.3.6.dat` sh all.sh --notest
#eval `cat arm.dat gcc-3.4.5-glibc-2.3.6-tls.dat` sh all.sh --notest
#eval `cat arm.dat gcc-4.0.2-glibc-2.2.2.dat` sh all.sh --notest
#eval `cat arm.dat gcc-4.0.2-glibc-2.3.2.dat` sh all.sh --notest
#eval `cat arm.dat gcc-4.0.2-glibc-2.3.2-tls.dat` sh all.sh --notest
#eval `cat arm.dat gcc-4.0.2-glibc-2.3.5.dat` sh all.sh --notest
#eval `cat arm.dat gcc-4.0.2-glibc-2.3.5-tls.dat` sh all.sh --notest
#eval `cat arm.dat gcc-4.0.2-glibc-2.3.6.dat` sh all.sh --notest
#eval `cat arm.dat gcc-4.0.2-glibc-2.3.6-tls.dat` sh all.sh --notest
#eval `cat arm.dat gcc-4.1.0-glibc-2.2.2.dat` sh all.sh --notest
#eval `cat arm.dat gcc-4.1.0-glibc-2.3.2.dat` sh all.sh --notest

#eval `cat arm.dat gcc-4.1.0-glibc-2.3.2-tls.dat` sh all.sh --notest #注释掉这行
eval `cat arm.dat gcc-4.0.2-glibc-2.3.6.dat` sh all.sh --notestt #添加此行

echo Done.

【3】修改 gcc-4.0.2-glibc-2.3.6.dat

1
2
3
4
5
BINUTILS_DIR=binutils-2.16.1  
GCC_DIR=gcc-4.0.2
GLIBC_DIR=glibc-2.3.6
LINUX_DIR=linux-2.6.26 (修改这里) LINUX_SANITIZED_HEADER_DIR=linux-libc-headers-2.6.12.0
GLIBCTHREADS_FILENAME=glibc-linuxthreads-2.3.6
执行./demo-arm.sh

进行编译,等差不多一个小时就编译出交叉编译工具了,至此呢,编译工具就弄到手了。

值得注意:交叉编译工具对版本的要求很严格,各个版本之间的兼容性较差,所以尽可能都下载到上述对应版本的工具包。

定制最小内核

准备

第一步 得到默认的内核配置

拷贝6410光盘文件A下Linux文件夹中的linux-2.6.28.6-20111212.tar.gz到虚拟机中,并解压。

进入解压后的目录,执行命令

1
mv config_mini6410_h43 .config

此命令会使用已经配置好的供h43屏幕的开发板使用

第二步 找到交叉编译环境

在Terminal中执行cd命令回到你的主目录,编辑你的个人脚本配置:

1
gedit .bashrc

在最后面加上这两行:

1
2
export PRE=/home/qiuyongchen/crosstool/gcc-4.0.2-glibc-2.3.6/arm-linux/bin
export PATH=$PRE:$PATH

(保存该文件,关闭Terminal,重开一个Terminal,输入arm-再点击tab键,此时系统会弹出类似arm-linux-gcc之类的选项,这证明系统已经可以找到交叉编译器)

这是为了让你在下一步执行make命令时,系统能找到交叉编译器。

开始编译

执行make命令,如果没有错的话,应该会报错:

1
invalid option `abi=aapcs-linux'

之所以会出错,是因为配置内核时使用了OABI的选项,你需要去掉它:
执行make menuconfig打开内核配置的界面,找到Kernel Features ---->Use the ARM EABIto compile the kernel选项,不选该项。

修改一遍再执行make命令,你就可以看到编译的过程了(一行行命令快速地刷过去)。

到了这里,我们就可以正常地编译内核了,但上头的要求是:尽可能地小,所以我们需要特殊地定制一个内核。

定制的详细过程

模板

定制前,我使用了源码包中针对h43屏幕已经调好的配置(config_mini6410_h43),在它的基础上进行修改。

在修改之前,我使用make zImage命令编译一个可以用的内核,发现其大小为3M,我的目标是删减到2M:
![][6]

开始定制

执行make menuconfig进入图形化配置界面

【1】 General setup
在此选项下,我去除了Automatically append version information to the version string,其余的保留

【2】Enable loadable module support
只选择Module unloading,以便后面以模块形式加载卸载。

【3】System Type
ARM system type下选择Samsung S3C64XX,其余也保留不变

【4】Bus support / Kernel Features
均保持不变

【5】Floating point emulation
去掉NWFPE math emulationVFP-format floating point maths选项。后面可能要播放视频需要用到浮点数运算,这里先不管。

【6】Networking suppot
直接去掉这个大选项,毕竟是不用联网的

【7】Device Drivers
这个有点麻烦,我全部去掉了SCSI device support下的全部支持,不需要与外部设备链接。
而在Input device support下,我去掉了Event interface与Touchscreens的支持,不需要事件也不需要触摸屏。
Character devices下,我去掉了模块示例和Buzzer的驱动。
去掉
USB supportMMC/SD/SDIO card support**。

其余的暂且不变(包括文件系统的配置)

定制结果

重新编译生成zImage,我发现其大小为2M,符合预期目标:
![][7]

至此呢,您就得到了一个大小约为2M的内核zImage文件,可以进一步处理烧写进开发板了。

定制最小文件系统

编译BusyBox

准备

将busybox-1.14.2.tar.gz复制到虚拟机中,解压。

这里会遇到一些权限不足的问题cannot mknod operation not permitted,解决方法:在命令行中输入su,然后输入root密码,进入超级管理员权限,之后再进行解压。

在安装包中已经有别人编译好的文件,所以先执行make clean清除掉。

调整busybox配置

执行make menuconfig进入编译前的配置。

进入busybox setting -> Build Options -> prefix,修改编译的前缀为**/home/qiuyongchen/crosstool/gcc-4.0.2-glibc-2.3.6/arm-linux/bin/arm-linux-**,如下图:
![][8]

第一次编译

先退出配置界面,执行make命令,尝试编译一遍(先不管定制),可以编译!

完了之后,执行make install

在子目录_instll下面会生成3个目录bin/sbin/linuxsrc,进入各个目录,我发现虽然有很多链接,实际上只有一个busybox文件,大小为1M

建立文件系统

建立基本框架

在_install 下建立etc 目录;
在此etc 下分别建立rc,inittab,motd 三个文件;
进入刚新建的etc,在其下用”vi rc”命令建立rc 文件:

1
2
3
#!/bin/sh
mount -t proc proc /proc
cat /etc/motd

保存后退出,用chmod 命令改变rc 文件属性 :

1
[root@localhost etc]$chmod 755 rc

在etc 下新建一个inittab 文件,内容如下 :

1
2
3
4
5
6
7
8
9
10
11
# /etc/inittab init(8) configuration for BusyBox
#
# Copyright (C) 1999-2003 by Erik Andersen <andersen@codepoet.org>
#
#
::sysinit:/etc/rc.d/rcS
::askfirst:-/bin/sh
::restart:/sbin/init
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
::shutdown:/sbin/swapoff -a

继续在etc 下建立motd 文件,其内容随意。

在etc 下建立init.d 目录,而后在init.d 目录下建立rc 文件的符号连接文件rcS:

1
[root@localhost init.d]$ ln -s ../rc rcS

在_install 下建立dev 目录,创建设备文件:

1
2
#mknod console c 5 1
#mknod mdblock3 b 31 3

至此呢,基本的框架就有了,也就是上头所需的最小文件系统了:
![][9]

封装基本框架

我使用了光盘中的mkubimage-mlc2工具(光盘A的linux目录下有个mktools-20110720.tar.gz文件,内部有该打包软件)

【1】安装mkubimage-mlc2
先解压mktools-20110720.tar.gz,用如下命令:

1
tar -vxzf mktools-20110720.tar.gz -C /

上述命令将所有的打包软件全部解压到/usr/sbin/目录

【2】打包busybox
进入busybox目录,执行命令:

1
/usr/sbin/mkubimage-mlc2 _install rootfs_my.ubi

执行完后就得到了rootfs_my.ubi文件系统镜像,利用上课时说的方法将它放到SD卡中就能刷进开发板了。

执行上述命令时系统会报错:mkubimage-mlc2: error while loading shared libraries: liblzo2.so.2: cannot open shared object file: No such file or directory

原因是缺少了liblzo2.so.2,用yum安装上即可(先执行su进入root)

1
2
yum update(fedora太老,必须先更新软件库的列表)
yum install liblzo2.so.2

![][10]

至此,我们就得到了一个“最小文件系统”。先烧入开发板看一下
烧录配置:
![][11]

刚烧录进去时,没有任何响应,需要配置内核,让内核支持ubi格式才行:

1
2
3
4
5
Device Drivers   --->
Memory Technology Device(MTD) support --->
UBI - Unsorted block images --->
<*> Enable UBI
<*> MTD devices emulation driver(gluebi)(NEW)

配置mtd支持UBI接口

1
2
3
File systems   --->
Miscellaneous filesystems --->
<*> UBIFS file system support

开机效果图:![][12]
是可以开机的。

按键驱动

加入视频播放器