此篇笔记基于sc7731 - android 5.1,对lcd的gralloc库做一个简明笔记。

第一部分 调用gralloc.sc8830.so
所谓的Gralloc模块,它就是一个模块,一个操作kernel层framebuffer驱动的动态库模块,它属于大名鼎鼎的HAL层。
用的时候就加载到内存空间,不用的时候就从内存空间中卸载掉。下面看下系统如何将该模块加载到内存空间的。
在Android系统中,所有访问HAL层模块的应用,都需要通过一个叫 hw_get_module() 的方法去获得需要的HAL 模块。

一、hw_get_module() 声明说明

定义在 7731_5.1/hardware/libhardware/hardware.c 文件中:

 /*
id : 模块ID
module : 对应ID的模块地址
*/
int hw_get_module(const char *id, const struct hw_module_t **module);

说明:
每个模块都有自己的ID,比如gralloc 模块ID 就是 GRALLOC_HARDWARE_MODULE_ID,它是一个字符串的宏:

#define GRALLOC_HARDWARE_MODULE_ID "gralloc"

模块地址: 如果一切都正确,那么根据调用者给定的ID,就一定能找到一个对应的模块。hw_get_module() 就会把这个模块的地址返回给调用者。

二、hw_get_module() 实现
可以先猜想一下,加载一个动态库?
第一步,需要先在指定的路径下(PATH)找到该库;
第二步,加载该库到内存(解析库的内容)

1. 找HAL 库

 int hw_get_module(const char *id, const struct hw_module_t **module)
{
return hw_get_module_by_class(id, NULL, module);
} int hw_get_module_by_class(const char *class_id, const char *inst,
const struct hw_module_t **module)
{
//.... //property_get(); //去对应的路径找相应的库
if (hw_module_exists(path, sizeof(path), name, prop) == ){
goto found;
} //.... found:
/* load the module, if this fails, we're doomed, and we should not try
* to load a different variant. */
//如果找到,就开始执行加载库的动作
return load(class_id, path, module);
} /*
* Check if a HAL with given name and subname exists, if so return 0, otherwise
* otherwise return negative. On success path will contain the path to the HAL.
*/
static int hw_module_exists(char *path, size_t path_len, const char *name,
const char *subname)
{
snprintf(path, path_len, "%s/%s.%s.so",
HAL_LIBRARY_PATH2, name, subname);
if (access(path, R_OK) == )
return ; snprintf(path, path_len, "%s/%s.%s.so",
HAL_LIBRARY_PATH1, name, subname);
if (access(path, R_OK) == )
return ; return -ENOENT;
}

hw_module_exists()就是找库的接口。
库的路径:

 /** Base path of the hal modules */
#if defined(__LP64__)
#define HAL_LIBRARY_PATH1 "/system/lib64/hw"
#define HAL_LIBRARY_PATH2 "/vendor/lib64/hw"
#else
#define HAL_LIBRARY_PATH1 "/system/lib/hw"
#define HAL_LIBRARY_PATH2 "/vendor/lib/hw"
#endif

很显然,库的路径是可以修改的。

access() 是测试该路径下的该库,是否可用,以及其具体权限如何(读写)。
man access 的结果:

 NAME
access - check real user's permissions for a file SYNOPSIS
#include <unistd.h> int access(const char *pathname, int mode);
....

说明:
在找库的过程中,会使用 property_get()接口去获取相关的属性。对于该接口,涉及到了Android property系统,暂时对这块未做探究。

2. 加载HAL 库( 解析HAL库)

如果找到该库,而且该库也可以读,那就load 到内存空间吧。

 /**
* Load the file defined by the variant and if successful
* return the dlopen handle and the hmi.
* @return 0 = success, !0 = failure.
*/
static int load(const char *id,
const char *path,
const struct hw_module_t **pHmi)
{
int status;
void *handle;
struct hw_module_t *hmi; /*
* load the symbols resolving undefined symbols before
* dlopen returns. Since RTLD_GLOBAL is not or'd in with
* RTLD_NOW the external symbols will not be global
*/
handle = dlopen(path, RTLD_NOW); //... /* Get the address of the struct hal_module_info. */
// #define HAL_MODULE_INFO_SYM_AS_STR "HMI"
const char *sym = HAL_MODULE_INFO_SYM_AS_STR;
hmi = (struct hw_module_t *)dlsym(handle, sym); /* Check that the id matches */
//比较模块的ID,是否符合要求
if (strcmp(id, hmi->id) != ) {
//...
} hmi->dso = handle; /* success */
status = ; done:
if (status != ) {
hmi = NULL;
if (handle != NULL) {
dlclose(handle);
handle = NULL;
}
} else {
//....
} //通过指针的方式,将对应的HAL module的地址返回给hw_get_module()函数调用者
*pHmi = hmi; return status;
}

(1) Linux 下对应动态库的操作函数列表:

 #include <dlfcn.h>

 void *dlopen(const char *filename, int flag);

 char *dlerror(void);

 void *dlsym(void *handle, const char *symbol);

 int dlclose(void *handle);

Link with -ldl.
注意使用这些库函数的时候,需要 -l 的方式连接 dl 库

(2) 模块的symbol(重要)
在上述代码片段中,dlsym()函数的操作需要注意下:
使用dlsym()函数 需要两个参数:一个是dlopen()函数打开的模块句柄;另外一个是模块的symbol。而关键的就是这个 symbol。
从上面可以看见,HAL 层所有模块的symbol 都被宏定义成了"HMI" 这个字符串。换句话说,所有的HAL模块都需要导出一个 "HMI"的 symbol。而dlsym()函数会根据"HMI" 这个symbol找到模块真正的地址。
在Linux下,所有的HAL层模块都是elf格式的二进制文件,使用readelf可以查看:

readelf -s ./out/target/product/w830_0203/system/lib/hw/gralloc.sc8830.so

 Symbol table '.dynsym' contains  entries:
Num: Value Size Type Bind Vis Ndx Name
: NOTYPE LOCAL DEFAULT UND
: FUNC GLOBAL DEFAULT UND __cxa_finalize
: FUNC GLOBAL DEFAULT UND __cxa_atexit
: FUNC GLOBAL DEFAULT UND strncmp
: 00001d3d FUNC GLOBAL DEFAULT _Z17alloc_device_openPK11
: 000027c1 FUNC GLOBAL DEFAULT _Z23framebuffer_device_op
: FUNC GLOBAL DEFAULT UND __aeabi_unwind_cpp_pr0
: FUNC GLOBAL DEFAULT UND __android_log_print
: FUNC GLOBAL DEFAULT UND ion_sync_fd
: FUNC GLOBAL DEFAULT UND memset
: FUNC GLOBAL DEFAULT UND ion_invalidate_fd
: FUNC GLOBAL DEFAULT UND getpid
: FUNC GLOBAL DEFAULT UND pthread_mutex_lock
: FUNC GLOBAL DEFAULT UND munmap
: FUNC GLOBAL DEFAULT UND __errno
: FUNC GLOBAL DEFAULT UND strerror
: FUNC GLOBAL DEFAULT UND pthread_mutex_unlock
: FUNC GLOBAL DEFAULT UND ion_open
: FUNC GLOBAL DEFAULT UND mmap
: 000015dd FUNC GLOBAL DEFAULT _ZN16private_module_tC2Ev
: FUNC GLOBAL DEFAULT UND pthread_mutex_init
: 000015dd FUNC GLOBAL DEFAULT _ZN16private_module_tC1Ev
: 0000501c OBJECT GLOBAL DEFAULT HMI
: FUNC GLOBAL DEFAULT UND ion_close
: FUNC GLOBAL DEFAULT UND close
: FUNC GLOBAL DEFAULT UND _ZdlPv
: FUNC GLOBAL DEFAULT UND ion_free
: FUNC GLOBAL DEFAULT UND ion_alloc
: FUNC GLOBAL DEFAULT UND ion_share
: FUNC GLOBAL DEFAULT UND _Znwj
: FUNC GLOBAL DEFAULT _Z24init_frame_buffer_loc
: FUNC GLOBAL DEFAULT UND dup
: 00001df1 FUNC GLOBAL DEFAULT _Z19compositionCompleteP2
: FUNC GLOBAL DEFAULT UND glFinish
: FUNC GLOBAL DEFAULT UND __aeabi_unwind_cpp_pr1
: FUNC GLOBAL DEFAULT UND __sprintf_chk
: FUNC GLOBAL DEFAULT UND fopen
: FUNC GLOBAL DEFAULT UND fseek
: FUNC GLOBAL DEFAULT UND __strlen_chk
: FUNC GLOBAL DEFAULT UND fwrite
: FUNC GLOBAL DEFAULT UND fclose
: FUNC GLOBAL DEFAULT UND __stack_chk_fail
: OBJECT GLOBAL DEFAULT UND __stack_chk_guard
: FUNC GLOBAL DEFAULT UND clock_gettime
: 00001f05 FUNC GLOBAL DEFAULT _Z17getApctFpsSupportv
: FUNC GLOBAL DEFAULT UND fread
: FUNC GLOBAL DEFAULT UND atol
: 000051d8 OBJECT GLOBAL DEFAULT gIsApctRead
: OBJECT GLOBAL DEFAULT gIsApctFpsShow
: FUNC GLOBAL DEFAULT UND __aeabi_l2f
: FUNC GLOBAL DEFAULT UND __aeabi_l2d
: FUNC GLOBAL DEFAULT UND setitimer
: FUNC GLOBAL DEFAULT UND signal
: 00002a7d FUNC GLOBAL DEFAULT _Z7dump_fbPvP17fb_var_scr
: FUNC GLOBAL DEFAULT UND ioctl
: FUNC GLOBAL DEFAULT UND memcpy
: FUNC GLOBAL DEFAULT UND __aeabi_uldivmod
: FUNC GLOBAL DEFAULT UND snprintf
: FUNC GLOBAL DEFAULT UND open
: FUNC GLOBAL DEFAULT UND property_get
: FUNC GLOBAL DEFAULT UND atoi
: 000028a5 FUNC GLOBAL DEFAULT _Z8dump_bmpPKcPvP10buffer
: FUNC GLOBAL DEFAULT UND fscanf
: NOTYPE GLOBAL DEFAULT ABS _edata
: NOTYPE GLOBAL DEFAULT ABS __bss_start
: NOTYPE GLOBAL DEFAULT ABS _end

看第 22行:
22: 0000501c 444 OBJECT GLOBAL DEFAULT 17 HMI
那么,dlsym()根据传入的"HMI" 就会匹配到该地址。至于HAL 模块怎么导出这个"HMI" 的symbol呢? 待后续分析。
这里需要注意的是,dlopen() 和 dlsym() 的返回值是一个万能指针。
特别是dlsym()返回值是一个动态库的地址。在该处调用中,它的返回值类型做了一个强制转换,转成了 struct hw_module_t 的指针形式---说明,HAL层模块需要提供相同的数据结构,起码前面结构体大小的字节要一一对应
也即是,接下来要分析的Gralloc模块中,要在模块的前 sizeof(struct hw_module_t) 个字节内(这里的前多少个字节,要除开elf文件的一系列其他说明信息,具体elf文件格式,此处略),提供一样的数据结构才行。(ps: 指针指向的是某段内存的开始地址嘛)

第二部分 Gralloc库的实现框架
既然调用者已经通过hw_get_module()函数找到了我们grallo.so 的具体地址,也就加载到了内存中。Gralloc模块就该开始工作了。
接下来简略分析下Gralloc实现。

1. 文件结构
展讯的Gralloc 库实现位于:
7731_5.1/vendor/sprd/open-source/libs/gralloc/utgard/

包含的文件如下:

其中, gralloc_module.cpp 是核心文件,其他文件是接口的封装,同gralloc_module.cpp形成调用关系。

2 代码分析
(1) 入口对象
在 gralloc_module.cpp 中,定义了一个对象,也即是展讯的Gralloc模块,就是一个HAL对象。

 /*
* HAL_MODULE_INFO_SYM will be initialized using the default constructor
* implemented above
*/
struct private_module_t HAL_MODULE_INFO_SYM;

HAL_MODULE_INFO_SYM 实质上是一个宏,定义在 7731_5.1/hardware/libhardware/include/hardware/hardware.h 文件中

 #define HAL_MODULE_INFO_SYM         HMI

这个前面在分析 模块的symbol 的时候,已经分析到了 dlsym() 需要依赖模块的导出为 "HMI"的 symbol。在这里给出来了。
至于网上有部分说法是,每个HAL 模块必须有一个 HAL_MODULE_INFO_SYM 这个东西。正确也不完全正确,我完全可以写成 struct private_module_t HMI 也没错。

(2) struct private_module_t 类

定义在了gralloc_priv.h文件中:

 struct private_module_t {
gralloc_module_t base; /*很重要的一个成员,这里需要注册许多关键性的东西*///在该类的构造函数中分析 private_handle_t* framebuffer; /* 指向图形缓冲区的句柄 */
uint32_t flags; /* 用来标志系统帧缓冲区是否支持双缓冲 */
uint32_t numBuffers; /* 表示系统帧缓冲的个数 */
uint32_t bufferMask; /* 记录系统帧缓冲的使用情况 */
pthread_mutex_t lock; /* 保护结构体private_module_t的并行访问 */
buffer_handle_t currentBuffer; /* 描述当前正在被渲染的图形缓冲区 */
int pmem_master; /* pmem设备节点的描述符 */
void* pmem_master_base; /* pmem的起始虚拟地址 */ struct fb_var_screeninfo info; /* lcd的可变参数 */
struct fb_fix_screeninfo finfo; /* lcd的固定参数 */
float xdpi; /* x方向上每英寸的像素数量 */
float ydpi; /* y方向上每英寸的像素数量 */
float fps; /* lcd的刷新率 */
};

(注释参考 http://blog.csdn.net/g_salamander/article/details/8424334)

base 成员非常重要,其一是需要注册许多关键性东西;其二,已经在前面说过,它内部的第一个变量需要是 struct hw_module_t 类型的---否则,dlsym()返回值的强制转换,将转成什么玩意儿呢?

 typedef struct gralloc_module_t {
struct hw_module_t common; //这里必须是 struct hw_module_t 类型的变量(成员) /*注册一个图形缓冲区*/
int (*registerBuffer)(struct gralloc_module_t const* module,
buffer_handle_t handle); /*注销一个图形缓冲区*/
int (*unregisterBuffer)(struct gralloc_module_t const* module,
buffer_handle_t handle); /*在使用图形缓冲区前,先调用该函数加锁*/
int (*lock)(struct gralloc_module_t const* module,
buffer_handle_t handle, int usage,
int l, int t, int w, int h,
void** vaddr); /*图形缓冲器使用完毕,调用该函数解锁*/
int (*unlock)(struct gralloc_module_t const* module,
buffer_handle_t handle); /* reserved for future use */
int (*perform)(struct gralloc_module_t const* module,
int operation, ... ); int (*lock_ycbcr)(struct gralloc_module_t const* module,
buffer_handle_t handle, int usage,
int l, int t, int w, int h,
struct android_ycbcr *ycbcr); int (*lockAsync)(struct gralloc_module_t const* module,
buffer_handle_t handle, int usage,
int l, int t, int w, int h,
void** vaddr, int fenceFd); int (*unlockAsync)(struct gralloc_module_t const* module,
buffer_handle_t handle, int* fenceFd); int (*lockAsync_ycbcr)(struct gralloc_module_t const* module,
buffer_handle_t handle, int usage,
int l, int t, int w, int h,
struct android_ycbcr *ycbcr, int fenceFd); /* reserved for future use */
void* reserved_proc[];
} gralloc_module_t;

(3) struct private_module_t 类的构造函数
c++中,一个对象的建立,都会调用其构造函数。展讯在这里借助类的构造函数,搞定了gralloc模块的一切注册

 private_module_t::private_module_t()
{
#define INIT_ZERO(obj) (memset(&(obj),0,sizeof((obj)))) base.common.tag = HARDWARE_MODULE_TAG;
base.common.version_major = ;
base.common.version_minor = ;
base.common.id = GRALLOC_HARDWARE_MODULE_ID; //Gralloc 的模块ID
base.common.name = "Graphics Memory Allocator Module";
base.common.author = "ARM Ltd.";
base.common.methods = &gralloc_module_methods;
base.common.dso = NULL;
INIT_ZERO(base.common.reserved); base.registerBuffer = gralloc_register_buffer;
base.unregisterBuffer = gralloc_unregister_buffer;
base.lock = gralloc_lock;
base.lock_ycbcr = gralloc_lock_ycbcr;
base.unlock = gralloc_unlock;
base.perform = NULL;
INIT_ZERO(base.reserved_proc); framebuffer = NULL;
flags = ;
numBuffers = ;
bufferMask = ;
pthread_mutex_init(&(lock), NULL);
currentBuffer = NULL;
INIT_ZERO(info);
INIT_ZERO(finfo);
xdpi = 0.0f;
ydpi = 0.0f;
fps = 0.0f;
swapInterval = ; initialize_blk_conf(); #undef INIT_ZERO
};

一个gralloc或者说一个HAL模块的模型,就上面那个构造函数里的样子。而需要我们自己实现的是:

gralloc_module_methods (base.common.methods)
gralloc_register_buffer(base.registerBuffer)
gralloc_unregister_buffer(base.unregisterBuffer)
gralloc_lock(base.lock)
base.unlock(gralloc_unlock)

(4) gralloc_module_methods

 static struct hw_module_methods_t gralloc_module_methods =
{
open: gralloc_device_open
};

(4.1)

 static int gralloc_device_open(const hw_module_t* module, const char* name, hw_device_t** device)
{
int status = -EINVAL; if (!strncmp(name, GRALLOC_HARDWARE_GPU0, MALI_GRALLOC_HARDWARE_MAX_STR_LEN)) //GPU0
{
status = alloc_device_open(module, name, device);
}
else if (!strncmp(name, GRALLOC_HARDWARE_FB0, MALI_GRALLOC_HARDWARE_MAX_STR_LEN)) //FB
{
status = framebuffer_device_open(module, name, device);
} return status;
}

展讯这里经过一个循环调用,最终会进入的alloc_device_open()的调用。该函数处于 alloc_device.cpp 文件。

 int alloc_device_open(hw_module_t const *module, const char *name, hw_device_t **device)
{
//..
alloc_device_t *dev; dev = new alloc_device_t;
//...
dev->alloc = alloc_device_alloc;
dev->free = alloc_device_free;
//...
}

alloc_device_alloc() 函数是与Framebuffer设备文件进行交互的。里面会调用gralloc_alloc_framebuffer() 根据LCD采用的颜色模式分配不同大小的显存空间。

(4.2)

 int framebuffer_device_open(hw_module_t const *module, const char *name, hw_device_t **device)
{
//...
status = gralloc_open(module, &gralloc_device); private_module_t *m = (private_module_t *)module;
status = init_frame_buffer(m); //...
framebuffer_device_t *dev = new framebuffer_device_t(); //...
}

以上这些函数都是以注册的方式放在了Gralloc模块中,当一个应用把gralloc模块加载到了内存时,可以通过钩子函数调用的方式,在顶层使用它们。

其他函数代码分析不再贴了,毕竟只是一种逻辑而已了。

(over)
2016-1-07

总结:

通过对展讯LCD 底层框架的简略性学习,做了三个简略性的笔记。第一篇是一个总的笔记,第二篇是针对framebuffer做了一个简略的分析,第三篇也就会这篇对HAL层gralloc做了一个简略性的总结。

以前未学驱动之前,或许驱动是如此的神秘,kernel是如此的神秘。但是经过几个月kernel/driver代码分析下来后,这种神秘的面纱或许是可以揭开了。

而这次从底层到上层的一个比较完整性的分析,更是拓宽了知识面,也改变了某些想法。也有可能,对职业发展方向有一定的影响。

lcd驱动->framebuffer驱动->gralloc, 这里没有算完。gralloc 的上面是更加宽广的世界,比如mutilplay,opengl等等。精彩的世界,未知的世界,等待着去探索。

于此,记之。

sc7731 Android 5.1 LCD驱动简明笔记之三的更多相关文章

  1. sc7731 Android 5.1 LCD驱动简明笔记之二

    此篇笔记基于sc7731 - android 5.1,对lcd的framebuffer做一个简明笔记. 一共分为两大部分:第一部分,关于LCD的硬件方面的:第二部分,关于lcd核心处理(framebu ...

  2. sc7731 Android 5.1 LCD驱动简明笔记之一

    基于展讯sc7731 - Android 5.1 代码分析浏览.将屏蔽细节,把握整体,并且不涉及其他设备和LCD的交互. 以下对sc7731 lcd大体流程进行简要说明. 第一,lcd 的两个阶段 1 ...

  3. LCD驱动学习笔记

    通过这几天的学习发现驱动的框架感觉都差不多,一般分为以下几个步骤: 分配一个结构体 struct x *x = amlloc(); 设置结构体的参数 硬件寄存器 file_operations 注册 ...

  4. 【转】Android LCD(四):LCD驱动调试篇

    关键词:android LCD TFTSN75LVDS83B  TTL-LVDS LCD电压背光电压 平台信息:内核:linux2.6/linux3.0系统:android/android4.0 平台 ...

  5. FL2440驱动添加(3)LCD驱动添加学习笔记

    FL2440 LCD内置控制器,320*240 TFT型LCD. 自我理解总结的两种添加驱动模式: 非platform方式添加驱动: 加载驱动: 1,硬件初始化,申请内存,并作地址映射 2,分配设备号 ...

  6. android系统平台显示驱动开发简要:LCD驱动调试篇『四』

    平台信息: 内核:linux3.4.39系统:android4.4 平台:S5P4418(cortex a9) 作者:瘋耔(欢迎转载,请注明作者) 欢迎指正错误,共同学习.共同进步!! 关注博主新浪博 ...

  7. AM335x(TQ335x)学习笔记——LCD驱动移植

    TI的LCD控制器驱动是非常完善的,共通的地方已经由驱动封装好了,与按键一样,我们可以通过DTS配置完成LCD的显示.下面,我们来讨论下使用DTS方式配置内核完成LCD驱动的思路. (1)初步分析 由 ...

  8. 高通 android平台LCD驱动分析

    目前手机芯片厂家提供的源码里包含整个LCD驱动框架,一般厂家会定义一个xxx_fb.c的源文件,注册一个平台设备和平台驱动,在驱动的probe函数中来调用register_framebuffer(), ...

  9. Android 开发之 ---- 底层驱动开发(一) 【转】

    转自:http://blog.csdn.net/jmq_0000/article/details/7372783 版权声明:本文为博主原创文章,未经博主允许不得转载. 驱动概述 说到 Android ...

随机推荐

  1. UE删除空行

  2. $(function(){})和jQuery(function(){})

    $(function(){})和jQuery(function(){})有没有区别,群里的屌丝争吵起来,各自讲着各种理论大道理.但还是有人给出了简而有力的证明: 区分大小写(jQuery) 但jQue ...

  3. JDBC用ResultSet访问大量数据时会遇到的问题

    我们经常需要JDBC来对数据库就行操作,一般流程为连接数据库.通过sql语句把需要的数据取出来保存到ResultSet,然后调用ResultSet方法的类似 getString,getInt()等方法 ...

  4. 【剑指offer 面试题38】数字在排序数组中出现的次数

    思路: 利用二分查找,分别查找待统计数字的头和尾的下标,最后做差加一即为结果. C++: #include <iostream> #include <vector> using ...

  5. Raspberry Pi3 ~ 配置网络

    Rpi3 有两个网卡 一个无线wlan 一个有线 eth0 无线的只需要在右上角的那个配置里面添加就行 有线的需要设置下静态IP.dns.等 在raspbain图形化界面里面 设置 Network P ...

  6. unity3d Hair real time rendering 真实头发实时渲染(转)

    惊现塞拉酱 算法是Weta Digital根据siggraph2003的论文加以改进,改进之前使用的是Kajiya and Kay’s 模型,它能量不守恒,也就是说不是基于物理的,不准确 电镜下真实头 ...

  7. C语言实现memcpy和memmove

    0.两者比较: memmove用于从src拷贝count个字符到dest,如果目标区域和源区域有重叠的话,memmove能够保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中.但复制后src内容会被 ...

  8. Asp.net MVC Bundle 的使用与扩展

    一.Asp.net 自带Bundle的使用: 1. 在Globale中注册与配置 BundleConfig.RegisterBundles(BundleTable.Bundles); public c ...

  9. HIve体系结构,hive的安装和mysql的安装,以及hive的一些简单使用

    Hive体系结构: 是建立在hadoop之上的数据仓库基础架构. 和数据库相似,只不过数据库侧重于一些事务性的一些操作,比如修改,删除,查询,在数据库这块发生的比较多.数据仓库主要侧重于查询.对于相同 ...

  10. TreeSet介绍

    一.TreeSet原理: 1.TreeSet存储对象的时候, 可以排序, 但是需要指定排序的算法 2.Integer能排序(有默认顺序), String能排序(有默认顺序), 自定义的类存储的时候出现 ...