1.本实验相关管脚介绍
1)原理图
2)由上图可以看出,4个LED灯所对应的管脚:
名称
对应管脚
管脚功能
对应逻辑
LED1
GPB5
内部上拉输出
0:灯亮
1:灯灭
LED2
GPB6
内部上拉输出
0:灯亮
1:灯灭
LED3
GPB7
内部上拉输出
0:灯亮
1:灯灭
LED4
GPB8
内部上拉输出
0:灯亮
1:灯灭
2.相关的寄存器:
1)与LED相关:
注释:
① GPBCON : 用于配置端口B的各个管脚功能
② GPBDAT : 端口B的数据寄存器
③ GPBUP : 用于配置端口B是否使用上拉功能
2)与MMU相关
① CP15 register 1(control register)
相关bit(M,A,S,R,RR,V(可有可无))
注:
这个放在最后连着开启MMU时一次性设置就好了,需要设置的有:使能Icache,Dcache;使能数据地址对齐异常检查;关于覆写算法默认是ramdom,我们选择round-robin;关于异常向量表的起始位置,我们选择放在最低地址0x0就可以了(这个实验我们还不会用到向量表,所以也可以先忽略掉);endianness选择小端就行了,默认也是小端;开启MMU;
关于R,S这两个值,要与AP关联上,由下图可以看出,当AP=00时,我们这里选择S:R=10:这样在AP=00时,SP为RO,UP为NA;AP=01时,SP为R/W,UP为NA;AP=10,SP为R/W,UP为RO;AP=11,SP为W/R,UP为R/W.
综上所属,使用配置命令如下:
MRC p15 , 0 , R1 , c1 , c0 , 0 (R1 [29:0]= 000000000000000 1 0 1 00 0 1 0 1111 1 1 1B)
②CP15 register 2(Translation table base register(TTB))
注:
在启动MMU之前我们需要设置好TTB,使用配置命令如下:
MCR p15 , 0 , R1 , c2 , c0 , 0 (R1=页表基址)
③CP15 register 3(Domainaccess control register)
以下为在domain access control寄存器里的access control bits的取值及说明:
这里我们根据顺序来设置:D0=00(No access)D1=01(Client)D2=11(Manager)其它都设置为10(reserved),使用命令配置如下:
MCR p15 , 0 , R1 , c3 , c0 , 0 (R1 = 10101010101010101010101010110100B)
④CP15 register 5(fault status register)
⑤CP15 register 7(cacheoperations register)(write-only)
这个寄存器用于管理ICache 和 DCache. 提供功能如下:
格式如下:
注:
开启MMU之前我们需要InvalidateCache,并且清除write buffer,所以使用命令配置如下:
MCR p15 , 0 , R1 , c7 , c7 , 0 (R1=SBZ)
MCR p15 , 0 , R1 , c7 , c10 , 4 (R1=SBZ)
⑥CP15 register 8(TLB operation register )
可以使无效整个TBL(s),….其中还可以无效单独有一个记录项(通过使用MVA),MVA被提供在Rd里.当需要用上MVA时,register 10 的MVA格式如下:
注:
在开启MMU之前我们需要进行一次InvalidateTLBs。这样在开启MMU之后就可以自动重新设置TLB的内容了。所以使用命令配置如下:
MCR p15 , 0 , Rd , c8 , c7 , 0 (Rd=SBZ)
⑦CP15 register 10(TLB lockdown register)
格式如下:
复位时为0x0
3.程序流程图设计:
1)主程序流程图:
2)建立页表子程序流程图:
注:这里需要注意的是:要把物理地址为0~代码的结束地址,不然使用开启MMU指令后并不正确执行后续的指令,我个人的理解是:在开启MMU之后,之后PC机上所看到的指令地址会变成VA,这时如果不进行映射到相同的PA地址,那经过MMU之后就可能映射到不可预测的物理地址了或者出现异常。
3)拷贝测试代码子程序流程图:
4)初始化MMU子程序流程图:
4.程序设计:
①Makefile
demo_mmu.bin : demo_mmu.o led_display.o
arm-linux-ld -Tmmu.lds -o demo_mmu_elf demo_mmu.o led_display.o
arm-linux-objcopy -O binary -S demo_mmu_elf demo_mmu.bin
arm-linux-objdump -D -m arm demo_mmu_elf > demo_mmu.dis
%.o : %.c
arm-linux-gcc -Wall -O2 -c -o $@ $<
%.o : %.S
arm-linux-gcc -Wall -O2 -c -o $@ $<
clean :
rm -f demo_mmu.bin demo_mmu_elf demo_mmu.dis *.o
②mmu.lds
SECTIONS {
first 0x00000000 : {demo_mmu.o}
second 0xB0005000 : AT(2048) {led_display.o}
}
③demo_mmu.S
#define WTCON 0x53000000
#define BWSCON 0x48000000
#define BANKCON6 0x4800001C
#define REFRESH 0x48000024
#define BANKSIZE 0x48000028
#define MRSRB6 0x4800002C
#define MMU_TTB 0x30000000
#define MMU_FPTB (0x30004000)
#define NCNB_CTTBTT (0x00<<2)
#define WT_CTTBTT (0x03<<2)
#define MANAGER_DOMAIN (0x02<<5)
#define SPUP_WR_SECTIONAP (0x03<<10)
#define SECTION_FLAG (0x01<<4)|(0x02)
#define FINE_FLAG (0x13)
#define SPUP_WR_TINYAP (0x03<<4)
#define TINY_FLAG (0x03)
.Text
.global _start
_start:
/******关闭看门狗**********/
ldr r0 , =WTCON
mov r1 , #0x0
str r1 , [r0]
/*****END关闭看门狗********/
/***调用初始化内存子程序***/
bl memory_init
/**********END*************/
/*****调用建立页表子程序***/
bl build_page
/***********END************/
/*****调用初始化MMU子程序**/
bl mmu_init
/***********END************/
@启动MMU
mcr p15 , 0 , r1 , c1 , c0 , 0 @设置control register.启动MMU
/**********让灯全亮************
@把LED1-4管脚置为输出
ldr r0 , =GPBCON
ldr r1 , [r0] @把GPBCON里的内容加载到r1里
ldr r2 , =(0xFF<<10)
bic r1 , r1 ,r2 @操作数取反码或上r1,用于清零工作
ldr r2 , =(0x55<<10)
orr r1 , r1 , r2
str r1 , [r0]
@灯全亮
ldr r0 , =GPBDAT
ldr r1 , [r0]
ldr r2 , =(0x0F<<5)
bic r1 , r1 , r2
str r1 , [r0]
***********END***************/
/****调用测试代码子程序****/
bl copy_from_bootsram_to_sdram
/***********END************/
@设置栈顶指针
ldr sp , =0xB4000000
/****************灯全灭**************/
ldr r0 , =GPBCON
ldr r1 , [r0] /*把GPBCON里的内容加载到r1里*/
bic r1 , r1 , #0x3FC00 /*操作数取反码或上r1,用于清零工作*/
orr r1 , r1 , #0x15400
str r1 , [r0]
ldr r0 , =GPBDAT
; ldr r1 , [r0]
; bic r1 , r1 , #0x1E0
; orr r1 , r1 , #0x1E0
; str r1 , [r0] /*此时4个LED等全灭*/
/***************END灯全灭***************/
@跳转到测试代码执行
ldr pc , =main
halt_loop:
b halt_loop
memory_init:
/*******内存初始化子程序*********/
@BWSCON[27:24] = 0 0 10B
ldr r0 , =BWSCON
ldr r1 , [r0]
ldr r2 , =(0x0F<<24)
bic r1 , r1 , r2
ldr r2 , =(0x02<<24)
orr r1 , r1 , r2
str r1 , [r0]
@BANKCON6[16:15]=11B;BANKCON6[3:0]=00 01B
ldr r0 , =BANKCON6
ldr r1 , [r0]
ldr r2 , =(0x03<<15)
bic r1 , r1 , r2
orr r1 , r1 , r2
ldr r2 , =0x0F
bic r1 , r1 , r2
ldr r2 , = 0x01
orr r1 , r1 , r2
str r1 , [r0]
@REFRESH[23:18] = 1 0 00 00B;REFRESH[10:0] = 0x7A3
ldr r0 , =REFRESH
ldr r1 , [r0]
ldr r2 , =(0x3F<<18)
bic r1 , r1 , r2
ldr r2 , =(0x20<<18)
orr r1 , r1 , r2
ldr r2 , =0x7FF
bic r1 , r1 , r2
ldr r2 , = 0x7A3
orr r1 , r1 , r2
str r1 , [r0]
@BANKSIZE[7:0] = 1 0 1 1 0 001 B
ldr r0 , =BANKSIZE
ldr r1 , [r0]
ldr r2 , =0xFF
bic r1 , r1 , r2
ldr r2 , =0xB1
orr r1 , r1 , r2
str r1 , [r0]
@MRSRB6[11:0] = 0 00 011 0 000 B
ldr r0 , =MRSRB6
ldr r1 , [r0]
ldr r2 , =0x3FF
bic r1 , r1 , r2
ldr r2 , =0x030
orr r1 , r1 , r2
str r1 , [r0]
mov pc , lr @函数返回
/******END内存初始化子程序*******/
build_page:
/********建立页表子程序***********/
/*对0地址处的映射*/
ldr r1 , =0x00000000 @VA起始地址
ldr r2 , =0x00000000 @PA起始地址
@*(MMU_TTB&0xFFFFC000+VA>>20<<2) = (PA&0xFFF00000)|(SPUP_WR(0x03)<<AP(10))|(MANAGER(0x02)<<DOMAIN(5))|(0x01<<4)|(NCNB_CTTBTT(0x00)<<Ctt_Btt(2))|(SECTION_FLAG(0x02))
ldr r0 , =(MMU_TTB & 0xFFFFC000)
mov r3 , r1 , LSR#20
add r0 , r0 , r3 , LSL#2 @r0存入段描述符所在一级页表条目地址
mov r3 , r2 , LSR#20
mov r3 , r3 , LSL#20
mov r4 , #0x0
ldr r4 , =SPUP_WR_SECTIONAP | MANAGER_DOMAIN | WT_CTTBTT | SECTION_FLAG
orr r3 , r3 , r4 @r3存入要放进页表的段描述符
str r3 , [r0] @往一级描述符地址放入对应的段描述符
/*END对0地址处的映射*/
/*对内存的映射*/
@VA:0xB0000000~0xB3FFFFFF------>PA:0x30000000~0x33FFFFFF
@using section-mapped
@总共64M,需要映射64次,每一次VA跟PA都要自加1M
ldr r1 , =0xB0000000 @VA起始地址
ldr r2 , =0x30000000 @PA起始地址
ldr r5 , =0xB4000000 @循环计数初值
l: @*(MMU_TTB&0xFFFFC000+VA>>20<<2) = (PA&0xFFF00000)|(SPUP_WR(0x03)<<AP(10))|(MANAGER(0x02)<<DOMAIN(5))|(0x01<<4)|(WT(0x03)<<Ctt_Btt(2))|(SECTION_FLAG(0x02))
ldr r0 , =(MMU_TTB & 0xFFFFC000)
mov r3 , r1 , LSR#20
add r0 , r0 , r3 , LSL#2 @r0存入段描述符所在一级页表条目地址
mov r3 , r2 , LSR#20
mov r3 , r3 , LSL#20
mov r4 , #0x0
ldr r4 , =SPUP_WR_SECTIONAP | MANAGER_DOMAIN | WT_CTTBTT | SECTION_FLAG
orr r3 , r3 , r4 @r3存入要放进页表的段描述符
str r3 , [r0] @往一级描述符地址放入对应的段描述符
add r1 , r1 , #0x100000
add r2 , r2 , #0x100000
cmp r1 , r5
bne l @如果VA没加到结尾就跳转到前一个l执行
/*END对内存的映射*/
/*对IO相关寄存器的映射*/
@VA:0xA0000000~0xA00003FF------>PA:0x56000000~0x560003FF
@using tiny page-mapped
ldr r1 , =0xA0000000 @VA起始地址
ldr r2 , =0x56000000 @PA起始地址
@因为IO相关寄存器地址范围不超过1K,所以我这里选择tiny page直接对1K进行设置
@Level one addr=(MMU_TTB&0xFFFFC000+VA>>20<<2)
@*(Level one addr)=(MMU_FPTB&0xFFFFF000)|(MANAGER(0x02)<<DOMAIN(5))|(0x01<<4)|(FINE_FLAG(0x03))
@Level two addr=(MMU_FPTB&0xFFFFF000+(VA>>8)&0xFFC)
@*(Level two addr)=(PA&0xFFFFFC00)|(SPUP_WR(0x03)<<AP(4))|(NCNB(00B)<<Ctt_Btt(2))|(TINY_FLAG(0x03))
ldr r0 , =MMU_TTB & 0xFFFFC000
mov r3 , r1 , LSR#20
add r0 , r0 , r3 , LSL#2 @r0存入小页描述符所在的一级页表条目地址
ldr r3 , =(MMU_FPTB & 0xFFFFF000) | MANAGER_DOMAIN | FINE_FLAG
str r3 , [r0] @往一级页表描述符地址放入对应的小页描述符
ldr r0 , =(MMU_FPTB & 0xFFFFF000)
mov r3 , r1 , LSR#8
ldr r4 , =0xFFC
and r3 , r3 , r4
add r0 , r0 , r3 @r0存入微小页描述符所在的二级细页表条目地址
mov r3 , r2 , LSR#10
mov r3 , r3 , LSL#10
ldr r4 , =SPUP_WR_TINYAP | NCNB_CTTBTT | TINY_FLAG
orr r3 , r3 , r4 @r3存入微小页描述符
str r3 , [r0] @往一级页表描述符索引到的地址放入对应的小页描述符
/*END对IO相关寄存器的映射*/
@返回到主程序
mov pc , lr
/********END建立页表子程序********/
copy_from_bootsram_to_sdram:
/*******拷贝测试代码子程序********/
@我们把测试代码加载在0x800=2048处(加载地址)
@拷贝到0x30005000=0x30000000(转换表基址)+0x4000(转换表大小)+0x1000(细页表大小)
mov r1 , #0x800 @源地址
ldr r2 , =0xB0005000 @目标地址
l1:
ldr r3 , [r1] , #0x04
str r3 , [r2] , #0x04
cmp r1 , #0x1000
bne l1
mov pc , lr
/******END拷贝测试代码子程序******/
mmu_init:
/*********初始化MMU子程序*********/
mov r1 , #0x0
mcr p15 , 0 , r1 , c7 , c7 , 0 @Invalidate Caches
mcr p15 , 0 , r1 , c7 , c10, 4 @清除write buffer
mcr p15 , 0 , r1 , c8 , c7 , 0 @Invalidate TLBs
ldr r1 , =MMU_TTB
mcr p15 , 0 , r1 , c2 , c0 , 0 @设置TTB register
ldr r1 , =0xAAAAAAB4
mcr p15 , 0 , r1 , c3 , c0 , 0 @设置domain access control register
mrc p15 , 0 , r1 , c1 , c0 , 0 @读取cp15 control register放入r1
ldr r2 , =0x3FFFFFFF
bic r1 , r1 , r2
ldr r2 , =0x517F
orr r1 , r1 , r2
@如果现在就启动了MMU,那么下面的返回地址就变成虚拟地址,所以跳转后再启动MMU
mov pc , lr @返回主程序
/******END初始化MMU子程序*********/
/************************************************END************************************************************/
④led_display.c
#define GPBCON (*(volatile unsigned long *)0xA0000010)
#define GPBDAT (*(volatile unsigned long *)0xA0000014)
/**
*如果不开启-O2以上优化,gcc编译器不提供inline优化,所以写入的inline只在编译时加入了-O2优化选项才会有效,这时for里面的循环参数可以不加volatile
*如果开启了-O2以上优化,gcc提供的inline优化有效,如果for循环里做编译器认为没意义的事,循环参数加上volatile声明。
*/
static inline void delay(volatile unsigned long dly)
{
for(; dly > 0; dly--)
}
int main()
{
unsigned long i = 0;
GPBCON &= ~(0xFF<<10);
GPBCON |= (0x55<<10); //把GPB5~8都置为输出功能
while(1){
delay(500000);
GPBDAT = (~(i<<5)); // 根据i的值,点亮LED1,2,3,4
if(++i == 16)
i = 0;
}
return 0;
}
关于GPIO的内部基准时钟已经打开,下一步就是给GPIO安装操作函数了,只有安装了操作函数,才能够利用的标准的接口访问GPIO.
1、文件:Io_gpio.c (source\io\gpio)中
/*FUNCTION*-------------------------------
*
* Function Name : _io_gpio_install
* Returned Value : _mqx_uint a task error code or MQX_OK
* Comments :
* Install a gpio driver.
*
*END*----------------------------------*/
_mqx_uint _io_gpio_install
(
/* [IN] A string that identifies the device for fopen */
/* input values are those identifiers defined in io_gpio.h file */
char_ptr identifier
)
{ /* Body */
if (IO_OK == gpio_cpu_init())
return _io_dev_install(identifier, _io_gpio_open, _io_gpio_close, _io_gpio_read, _io_gpio_write, gpio_cpu_ioctl, NULL);
return (_mqx_uint)IO_ERROR;
} /* Endbody */
分析:
gpio_cpu_init()函数,我们已经知道是为了实现硬件层gpio操作时钟的使能控制的。那么_io_dev_install(xxxx)函数,就是给gpio模块安装驱动函数的,驱动函数类似于linux方式,包括:打开、关闭、读、写、控制。这些控制函数都在本文件内实现,当我们调用fopen、fclose、fread、fwrite、ioctl等系统统一接口时,mqx会在内部通过映射表,调用gpio模块对应的功能函数。例如:fopen -> _io_gpio_open。
2、文件:Io_inst.c (source\io)中
/*FUNCTION*-------------------------------
*
* Function Name : _io_dev_install
* Returned Value : _mqx_uint a task error code or MQX_OK
* Comments :
* Install a device dynamically, so tasks can fopen to it.
*
*END*----------------------------------*/
_mqx_uint _io_dev_install
(
/* [IN] A string that identifies the device for fopen */
char_ptr identifier,
/* [IN] The I/O open function */
_mqx_int (_CODE_PTR_ io_open)(MQX_FILE_PTR, char _PTR_, char _PTR_),
/* [IN] The I/O close function */
_mqx_int (_CODE_PTR_ io_close)(MQX_FILE_PTR),
/* [IN] The I/O read function */
_mqx_int (_CODE_PTR_ io_read)(MQX_FILE_PTR, char _PTR_, _mqx_int),
/* [IN] The I/O write function */
_mqx_int (_CODE_PTR_ io_write)(MQX_FILE_PTR, char _PTR_, _mqx_int),
/* [IN] The I/O ioctl function */
_mqx_int (_CODE_PTR_ io_ioctl)(MQX_FILE_PTR, _mqx_uint, pointer),
/* [IN] The I/O initialization data */
pointer io_init_data_ptr
)
{ /* Body */
return (_io_dev_install_ext(identifier, io_open, io_close, io_read, io_write, io_ioctl, (_mqx_int (_CODE_PTR_)(IO_DEVICE_STRUCT_PTR))NULL, io_init_data_ptr));
} /* Endbody */
分析:
GPIO的设备驱动安装函数,该函数接收5个函数指针和一个void型指针。调用带函数指针形参的函数时,我们只需要把对应函数实参的名字传递过去就可以了,不用对函数名称取地址&,因为在C语言内部,函数名称指的就是函数的入口地址,即:该函数运行的首地址,关于这个不再多说。最后一个void*指针,是给该函数传递初始化数据的,这里传递了一个NULL。
该函数经过了一层封装,调用了_io_dev_install_ext(xxx)函数,进行具体的安装工作,与外层函数相比,它多了一个形参:
(_mqx_int (_CODE_PTR_) (IO_DEVICE_STRUCT_PTR)) NULL。主体是一个NULL,前面的是一个强制类型转换,转换成了什么,转换成了一个函数指针,该函数返回_mqx_int数值,形参IO_DEVICE_STRUCT_PTR。详细分析进入该函数即可看到。
可能有人会问:传递一个空数据,干嘛还要再进行一次函数调用?这其中就涉及到函数维护的知识了,可能_io_dev_install_ext()函数出现较早,而那个参数随着版本升级,不再需要,但是为了保持一致性,又不能轻易修改已经成型的函数,这时候怎么办。办法就是再增加一层函数,利用这层函数屏蔽掉这个变化,使上层看不到无效数据。以后,当维护函数数据较多时,肯定会用到这个方法。
对_io_dev_install_ext(xx)函数的分析,估计需要的信息较多,我们留在下一节吧!
步入11月份了,自从上个月答应老大接手手机项目的3G模块至今,已经有一个月了吧。这一个月来,一直围绕着3G模块的那几个BUG在查原因,在抓LOG,很是辛苦,但后来解决之后,倍感欣慰。
先简单描述一下这个手机项目,CPU选用三星Exynos4412,和即将在本月27号登场的MX四核手机同一款CPU,3G模块选用XMM6260,然后系统跑的是android 4.0,于是kernel选用linux 3.0。其它模块由于不是本人负责,而且与本篇文章暂时无关,就不描述了。
本人负责的是3G模块,由于接手前已经由一后来离职的工程师调通,所以基本上我只需要修改BUG。经测试发现,该模块有以下BUG:
1、开机后,只能进入一次深度休眠(以下简称“休眠”),唤醒后就再也无法进入休眠。
2、3G的加入影响了休眠唤醒速度。
这两个BUG由于涉及内容太多,在刚刚接手时一时不知道该往何处下手。来公司4年多了,基本上很多问题都是跟休眠唤醒有关,不管是以前做过的WINCE,还是现在做的android,无一例外。
废话不说,先研究一下此3G模块。后来发现6260驱动主要由三个部分构成:模块控制部分、主控USB部分和数据传输部分。模块控制主要配置基本的IO口,完成模块的上电复位等操作,以及注册中断处理函数,为上层提供一系列的控制接口。由于6260属于HSIC(即High Speed Inter Chip简称)设备,通过USB进行传输,故此部分的代码显得至关重要,在实际的调试过程中,确实花费了很多时间与精力来研究它的相关代码。数据传输部分主要为USB转串口的驱动,所有的数据都是通过这个驱动进行数据收发的。
仔细研究了数据传输部分的代码,实际就cdc_acm.c文件。在acm_resume函数中有一句代码:wake_lock_pm(acm->parent);,作用为申请锁,做过android开发的也许都知道,应用程序一旦申请了休眠锁而不释放,那么系统就无法进入休眠,在此模块中,也不例外。但其实去掉这句代码后,还有其它问题。
围绕这句代码,我改了好几天,释放锁、在别的地方申请锁及释放等……一直都得不到一个满意的结果。后来比较其它项目的驱动,发现其实cdc_acm.c中并没有用到锁,于是我把手机项目该驱动的锁都屏蔽,结果让人很意外,系统能够正常进入休眠,以及正常唤醒了,测试了好多次都如此!那么,是不是就修改好了第1个问题了呢?按照经验,需要做多次测试后才能下结论。于是把代码发给其它同事帮忙测试,也没发现有什么问题。(在后来的大规模测试中,才发现了由此引发的其它问题,我们后面会讨论)
再看第二个问题,我把3G驱动卸载,果然发现休眠后唤醒速度提高了许多,不再是之前的按下POWER键需要等待3秒才唤醒了。然后又是漫长的测试阶段……按照经验,系统唤醒后所看到的LOG具有滞后性,也就是说你看到的第一句LOG的时候,系统其实已经运行了N行代码。于是我让硬件工程师帮忙接了两个暂时用不到的IO口出来,在按下POWER键时在相应的地方置该IO口输出高或低,配置示波器,就可以知道代码卡在哪里啦。与休眠唤醒电源相关的代码无非是arch/arm/里面的几个pm.c文件,在里面加了相应的操作,没发现有什么问题。后来又在按键驱动中把IO操作也加进去量,还是没有发现什么问题,按下POWER键的时候,我置高或置低的IO电平是得到期望值的。这么东一改西一改又过去了好几天,一直很郁闷。
后来又想到一个方法,3G驱动由3部分构成,那么既然现在确定了唤醒时间与该模块驱动有关,可以分步卸载该驱动再测试看看。说做就做,于是把3G的3个部分分步去掉,当然会碰到编译不过的问题,为了这次结果,都暂时把编译不过的地方修改了。果然这么一测,还真让我找到了。当我卸载到只剩下USB驱动后,唤醒时间依旧为3秒钟,那么就应该是USB驱动的问题。
休眠唤醒都是suspend和resume函数搞的鬼,那么当然重点看这两个函数。我把suspend和resume函数对3G的操作都屏蔽掉,再测试,发现唤醒速度快了许多。这一结果令人振奋!那么就应该是这两个地方了。再一步一步查找,果然让我发现了问题:在suspend中有把mc->in_l3_state置1,但唤醒后没清0!暂且先不管这个变量是做什么用的,休眠前在保存状态,唤醒后恢复状态是亘古不变的真理,于是我在resume中把它置0,奇迹出现了,系统的唤醒速度真的快了许多!!!!
以上是我调试这两个BUG的一些过程,由于水平有限,不免有错误的地方,欢迎大家提出来并批评指正。
本以为修改完这两个BUG,3G就不会有问题了,没想到后来经过大规模测试,我们依旧发现了后续的问题,但不管怎样,BUG总会得到解决的。下一篇,我再详细描述由此带来的其它BUG以及解决方法。