如果要做嵌入式Linux,我们首先要在板子上烧写的往往不是kernel,而是u-boot,这时需要烧写工具帮忙。当u-boot烧写成功后,我们就可以用u-boot附带的网络功能来烧写kernel了。每当板子上电时,u-boot一般会被加载到内存的前半段,如果我们的kernel之前就已经被烧写到开发板了,那么u-boot会加载kernel到内存的后半段并跳转到kernel的起始地址处执行(或者直接跳转到kernel的起始地址处执行,如果kernel可以直接在flash上执行的话。)

如上图所示,绿色部分为u-boot,红色部分为kernel。

把loader(指u-boot)和kernel分离究竟有什么好处呢?

举个极端的例子:没有grub的话,我们就没办法做windows和linux双系统了。这就是最大的好处。

然而对于嵌入式,我倒是说不出什么上得了台面的理由,根据个人喜好,我倒是有3点理由:

1、不用再求助烧写工具了;

2、方便使用GNU交叉编译工具;

3、摆脱Windows+linux虚拟机的工作平台。

现在,我的笔记本就可以轻松一下了,只需单开fedora/ubuntu就能工作啦!

以下是源码和工程的下载链接:

kernel源码及mdk5工程

kernel的arm-gcc源码

my-boot源码及mdk5工程

注:仅可使用在stm32f10x系列

接下来,我们将分为三部分叙述:

1、系统概述;

2、kernel;

3、“my-boot”;

4、先烧写"my-boot“,然后用"my-boot”加载kernel——操作示例;

1、系统概述

接下来我们将建立两个工程,一个是用来编译kernel,一个用来编译loader(姑且命名为“my-boot”)。首先,我们先把“my-boot”和kernel都编译好,并通过烧写工具把“my-boot”烧写进stm32的flash中。然后,我们就可以重启stm32,并使之运行“my-boot”。“my-boot”等待接收烧写kernel的起始命令,当我们通过串口向“my-boot”发送了烧写起始命令后,“my-boot”将把串口设置为DMA模式,并等待我们发送kernel的bin文件。接着,我们再通过串口传送kernel的bin文件。传送结束后,kernel也就被写入stm32的RAM中了,同时“my-boot”把串口切换回通常的窗口通信模式。此时,芯片的控制权依旧被掌控在“my-boot”手中,不过,如果我们再向串口发送一条启动kernel的指令,那么stm32将跳转到kernel代码处执行。至此,我们的目标达成。

2、kernel

我们的kernel很简单,只有一个源文件,其功能就是不停的闪led。程序参考了博客http://www.cnblogs.com/sky1991/archive/2012/10/13/2722640.html的“例子一”,并加以修改与简化,代码给出如下:

 ;RCC寄存器地址映像
RCC_BASE EQU 0x40021000
RCC_CR EQU (RCC_BASE + 0x00)
RCC_CFGR EQU (RCC_BASE + 0x04)
RCC_CIR EQU (RCC_BASE + 0x08)
RCC_APB2RSTR EQU (RCC_BASE + 0x0C)
RCC_APB1RSTR EQU (RCC_BASE + 0x10)
RCC_AHBENR EQU (RCC_BASE + 0x14)
RCC_APB2ENR EQU (RCC_BASE + 0x18)
RCC_APB1ENR EQU (RCC_BASE + 0x1C)
RCC_BDCR EQU (RCC_BASE + 0x20)
RCC_CSR EQU (RCC_BASE + 0x24)
;GPIO寄存器地址映像
GPIOA_BASE EQU 0x40010800
GPIOA_CRL EQU (GPIOA_BASE + 0x00)
GPIOA_CRH EQU (GPIOA_BASE + 0x04)
GPIOA_IDR EQU (GPIOA_BASE + 0x08)
GPIOA_ODR EQU (GPIOA_BASE + 0x0C)
GPIOA_BSRR EQU (GPIOA_BASE + 0x10)
GPIOA_BRR EQU (GPIOA_BASE + 0x14)
GPIOA_LCKR EQU (GPIOA_BASE + 0x18) SETENA0 EQU 0xE000E100
SETENA1 EQU 0xE000E104 ;;FLASH缓冲寄存器地址映像
FLASH_ACR EQU 0x40022000 ;-----------------
MSP_TOP EQU 0x20005000 ;主堆栈起始值
PSP_TOP EQU 0x20004E00 ;进程堆栈起始值 DelayTime EQU ; to choose a better number to fit your cpu
CLRPEND0 EQU 0xE000E280 ;常数定义---------
Bit0 EQU 0x00000001
Bit1 EQU 0x00000002
Bit2 EQU 0x00000004
Bit3 EQU 0x00000008
Bit4 EQU 0x00000010
Bit5 EQU 0x00000020
Bit6 EQU 0x00000040
Bit7 EQU 0x00000080
Bit8 EQU 0x00000100
Bit9 EQU 0x00000200
Bit10 EQU 0x00000400
Bit11 EQU 0x00000800
Bit12 EQU 0x00001000
Bit13 EQU 0x00002000
Bit14 EQU 0x00004000
Bit15 EQU 0x00008000
Bit16 EQU 0x00010000
Bit17 EQU 0x00020000
Bit18 EQU 0x00040000
Bit19 EQU 0x00080000
Bit20 EQU 0x00100000
Bit21 EQU 0x00200000
Bit22 EQU 0x00400000
Bit23 EQU 0x00800000
Bit24 EQU 0x01000000
Bit25 EQU 0x02000000
Bit26 EQU 0x04000000
Bit27 EQU 0x08000000
Bit28 EQU 0x10000000
Bit29 EQU 0x20000000
Bit30 EQU 0x40000000
Bit31 EQU 0x80000000 ;向量表*********************************************************************************
AREA RESET, DATA, READONLY DCD MSP_TOP ;初始化主堆栈
DCD Start ;复位向量
DCD NMI_Handler ;NMI Handler
DCD HardFault_Handler ;Hard Fault Handler
;***************************************************************************************
AREA |.text|, CODE, READONLY
;主程序开始
ENTRY ;指示程序从这里开始执行
Start
CPSID I              ;关中断
ldr r0, =MSP_TOP        
msr msp, r0        ;重设MSP
mov r0, #0            
msr control, r0         ;切换MSP,并进入特权级 mov r0, #
mov r1, #
mov r2, #
mov r3, #
mov lr, # ldr r0, =CLRPEND0
ldr r1, [r0]
orr r1, #0xFFFFFFFF
str r1, [r0] ;时钟系统设置
;启动外部8M晶振 ldr r0,=RCC_CR
ldr r1,[r0]
orr r1,#Bit16
str r1,[r0]
ClkOk
ldr r1,[r0]
ands r1,#Bit17
beq ClkOk
ldr r1,[r0]
orr r1,#Bit17
str r1,[r0]
;FLASH缓冲器
ldr r0,=FLASH_ACR
mov r1,#0x00000032
str r1,[r0]
;设置PLL锁相环倍率为7,HSE输入不分频
ldr r0,=RCC_CFGR
ldr r1,[r0]
orr r1,#Bit18 | Bit19 | Bit20 | Bit16 | Bit14
orr r1,#Bit10
str r1,[r0]
;启动PLL锁相环
ldr r0,=RCC_CR
ldr r1,[r0]
orr r1,#Bit24
str r1,[r0]
PllOk
ldr r1,[r0]
ands r1,#Bit25
beq PllOk
;选择PLL时钟作为系统时钟
ldr r0,=RCC_CFGR
ldr r1,[r0]
orr r1,#Bit18 | Bit19 | Bit20 | Bit16 | Bit14
orr r1,#Bit10
orr r1,#Bit1
str r1,[r0]
;其它RCC相关设置
ldr r0,=RCC_APB2ENR
mov r1,#Bit2
str r1,[r0]
;IO端口设置
ldr r0,=GPIOA_CRH
ldr r1,[r0]
orr r1,#Bit0 | Bit1 ;PA.8输出模式,最大速度50MHz
and r1,#~Bit2 & ~Bit3 ;PA.8通用推挽输出模式
str r1,[r0] mov r5, # ; led flag ;CPSIE I
;主循环=================================================================================
main
bl Delay
bl LedFlas
b main
;子程序**********************************************************************************
LedFlas
push {r0-r3}
cmp r5,#
beq ONLED mov r5, #
;PA.8输出1
ldr r0,=GPIOA_BRR
ldr r1,[r0]
orr r1,#Bit8
str r1,[r0]
b LedEx
ONLED
mov r5, #
;PA.8输出0
ldr r0,=GPIOA_BSRR
ldr r1,[r0]
orr r1,#Bit8
str r1,[r0]
LedEx
pop {r0-r3}
bx lr Delay
push {r0-r3} ldr r0, =DelayTime
Loop CBZ r0, LoopExit
sub r0, #
b Loop
LoopExit
pop {r0-r3}
bx lr
;异常程序*******************************************************************************
NMI_Handler
;xxxxxxxxxxxxxxxxxx
bx lr
;-----------------------------
HardFault_Handler
;xxxxxxxxxxxxxxxxxx
bx lr
;***************************************************************************************
ALIGN ;通过用零或空指令NOP填充,来使当前位置与一个指定的边界对齐
;-----------------------------
END

(1)主循环程序:

main

  bl Delay     // 延时
  bl LedFlas  // 翻转led
  b main      // 跳转会main开头(即“延时”)

(2)延时程序:

Delay
  push {r0-r3}

  ldr r0, =DelayTime // r0 = DelayTime;
Loop

   CBZ r0, LoopExit // if(r0 != 0) {
  sub r0, #1    //   r0 -= 1;
  b Loop      //   goto Loop; }
LoopExit
  pop {r0-r3}
  bx lr

该延时程序是“C51式”的延时,就是纯粹的让CPU空跑n个周期,这里是“DelayTime=13000000“。“13000000”是随便设的一个数,只是为了让眼睛和耐性都能接受,时钟频率变化之后,这个数字可以自行的、随性的去进行调整。

(3)LED翻转程序:

LedFlas
  push {r0-r3}
  cmp r5,#1     // if(r5 == 1)
  beq ONLED     //   goto ONLED;

  mov r5, #1       //  r5 = 1;
  ;PA.8输出1
  ldr r0,=GPIOA_BRR 
  ldr r1,[r0]       
  orr r1,#Bit8
  str r1,[r0]
  b LedEx
ONLED
  mov r5, #0       // r5 = 0;
  ;PA.8输出0
  ldr r0,=GPIOA_BSRR
  ldr r1,[r0]
  orr r1,#Bit8
  str r1,[r0]
LedEx
  pop {r0-r3}
  bx lr

该LED翻转程序以“r5”寄存器为标志,“r5”为0或1时,分别使PA.8输出不同的电平(此处PA.8对应开发板上一个红色LED)。

注:

一般MDK会生成hex文件,但不生成bin文件,所以我们还要给MDK加一些设置:

先找到fromelf.exe文件(一般在你的MDK安装目录里的bin目录里),然后如下图输入,

如:

C:\Keil_v5\ARM\ARMCC\bin\fromelf.exe --bin --output kernel.bin kernel.axf

重新编译之后,于是我们就得到kernel的bin文件了,即kernel.bin,留着备用。

此处的kernel是可以独立运行的,所以不妨将该程序通过烧写工具烧写进开发板验证一下。

Note:

如果要用arm-gcc的kernel,首先,你的Linux必须得有arm-gcc编译工具。可使用目录中提供的脚本build.sh直接编译。此处我用的是“arm-none-eabi-as”等,如果是arm-linux-eabi-as等,需要简单修改脚本中的“PREFIX”变量。

3、“my-boot”

我们知道,在kernel和loader之间,真正的主角是kernel,loader只是一个辅助工具罢了。然而,作为loader的"my-boot"在这里却比kernel复杂许多。

“my-boot”以一步步学习操作系统(1)中的代码为基础,并将之整理了一下,把各个源文件分类到了不同的目录。

如图,除了obj目录是存放编译时所用的中间文件和hex文件外,其余4个目录都存放源码。

(1)arch目录:其中的源码均是和CPU架构相关,如中断代码、串口初始化、启动代码等;

(2)include目录:所有的头文件都在这里;

(3)kernel目录:包含主函数、任务调度、延时相关的源码;

(4)lib目录:stm32f10x库函数源码及“printf”重定向至串口的辅助代码(printf_to_serial)。

主程序一共建立3个任务:Task1, TaskBH, TaskDMA_Print。

 int main(void)
{
memset(SRAM_Buffer, , PAGE_SIZE);
OSInit(); OSTaskCreate(Task1, (void*), (OS_STK*)&Task1Stk[TASK_STACK_SIZE-]);
OSTaskCreate(TaskBH, (void*), (OS_STK*)&TaskBHStk[TASK_STACK_SIZE-]);
OSTaskCreate(TaskDMA_Print, (void*), (OS_STK*)&TaskDMA_PrintStk[TASK_STACK_SIZE-]); OSStart();
}

Task1:和kernel的功能一样,也是不断的闪led(最好是不同于kernel所使用的led),用来指示程序依旧正常运行,其功能很单纯;

TaskBH:接受串口发送过来的相关命令,并向串口打印信息以提示命令发送成功。特别是当收到“startos”指令后,会置位变量“GotoKernelFlag”,以致后续代码将跳转到kernel运行,该任务是三个任务中最复杂的一个;

TaskDMA_Print:打印RAM中的kernel代码。

其实以上三个任务的负担并不重,身上担子最重的时串口中断程序:

串口通信遵循一个自定义的协议,协议内容如下:

将以下串口中断程序与TaskBH结合着看,串口接受三种命令:

第一种:BURN命令:

如:

"BURN 0x08004000"

协议信息16进制表示为

57 41 4e 15 00 75 42 55 52 4e 20 30 78 30 38 30 30 34 30 30 30

该命令就是在通知开发板:“我要发送kernel了呦,赶紧准备接驾。”

这时,串口中断程序会启动串口的DMA模式,并开启DMA中断。

关于命令中的地址“0x08004000”,该值是设计为以后烧写flash做准备的,但现在我们只将kernel写入SRAM,所以现在还没有特别的作用,任意值都可以。

这个命令发送之后就要小心了,紧跟着必须向串口发送kernel的bin文件。发送结束后,DMA中断会被触发,并且会调用“LED1TURN()”去翻转另一个LED(不同于Task1的LED),用以指示kernel已经被写入RAM。

Note:看了以下代码后,其实对于"BURN”这个命令来说,校验和是形同虚设的,为了图方便就偷了个懒……

 volatile void IRQ_Usart1(void)
{ RecvBuffer[Index] = serial_1; // Magic handling
// Byte order: 0 1 2
if(!MagicGotten) {
if( == Index && 'W' == RecvBuffer[Index]) {
Index++;
}else if( == Index && 'A' == RecvBuffer[Index]) {
Index++;
}else if( == Index && 'N' == RecvBuffer[Index]) {
Index++;
MagicGotten = TRUE;
}else {
Index = ;
}
return;
} // Size handling
// byte order: 3 4
if(!SizeGotten) {
Index++;
if( == Index) {
SizeGotten = TRUE;
MsgSize = RecvBuffer[] + (RecvBuffer[] << );
}
if(SizeGotten && MsgSize > BUFSIZ) {
MagicGotten = FALSE;
SizeGotten = FALSE;
Index = ;
}
return;
} // Checksum handling
// byte order: 5
if(!ChecksumGotten) {
Index++;
if( == Index) {
ChecksumGotten = TRUE;
}else {
MagicGotten = FALSE;
SizeGotten = FALSE;
Index = ;
}
return;
} // Data handling:
// byte order: 6...
Index++;
if(Index >= MsgSize) {
MagicGotten = FALSE;
SizeGotten = FALSE;
ChecksumGotten = FALSE;
Index = ;
MsgGotten = TRUE;
if( == strncmp((char *)RecvBuffer + , "BURN", )) {
USART_Cmd(USART1, DISABLE);
USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);
USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);
DMA1_Channel5->CNDTR = PAGE_SIZE;//re-load
DMA_Cmd(DMA1_Channel5, ENABLE);//re-open DMA
USART_Cmd(USART1, ENABLE);
LED1TURN();
}
}
}

第二种:“startos”

协议信息16进制表示为

57 41 4e 0d 00 f8 73 74 61 72 74 6f 73

开发板接收到该命令后,TaskBH会将变量“GotoKernelFlag”设为1。之后,当SysTick中断程序(如下)再次执行时,将会调用“ModifyPC()”(这里的“PC”不是指“Personal Computer”,而是指PC指令寄存器哦)。这个函数很难懂。如果能理解这个函数,那么loader加载kernel的原理也就等于理解了80%了。我们不妨来试着啃一啃这块硬骨头!

 volatile void IRQ_SysTick(void)
{
OS_ENTER_CRITICAL();
if(GotoKernelFlag) ModifyPC();
if((--TaskTimeSlice) == ){
TaskTimeSlice = TASK_TIME_SLICE;
OSTaskSchedule();
}
TimeMS++; OS_EXIT_CRITICAL();
}

“ModifyPC()”是嵌入C语言式的汇编代码。其作用就是:

修改 PSP中存储的、“当前被SysTick中断的任务”的 PC指针,使之等于kernel代码的起始地址。当该任务再一次被调度时,由于PC被换成了kernel代码的起始地址,所以就进入了kernel。

于是,两个问题出现了:

(1)kernel的起始地址是什么?

(2)被SysTick中断的任务的PC又在哪?

或许有人会认为:“kernel在DMA传送时,被放进‘SRAM_Buffer’这个缓冲区了,那么kernel的起始地址不就是‘SRAM_Buffer’吗?”(一开始我也是这么想的……)

可惜,真正的“起始地址”要比SRAM_Buffer在靠后一点点。

不妨在MDK5下,在kernel工程里打开Debug,接着再用二进制编辑器打开kernel.bin,这样就能看出蹊跷了。

stm32烧写程序时,是将代码烧至起始地址为0x08000000的flash中,并在开机运行时也是直接从flash启动。

看到没有,我们开机时的第一条命令是“CPSID I”,对应的指令地址为0x08000010,机器码为“B672”。

再用二进制文件打开kernel.bin后,发现果然是“B672”(二进制文件为“小端法”表示,所以是“72 B6”)。

所以,我们的kernel代码的起始地址,确切来说是第一条命令“CPSID I”的地址为“SRAM_Buffer + 0x10”。

Note:

“既然代码烧写进地址为0x08000000起始的地方,那么第一条指令为什么确实0x08000010呢?”

意味0x08000010 - 0x08000000 = 0x10 = 16 = 4*4,也即代码开头的“4个DCD”,每个DCD4字节。

第二个问题,“被SysTick中断的任务”的PC到底在哪儿呢?

首先我们要知道,任务使用的是PSP(可参考“PendSV_Handler”的汇编代码)。确认了这点之后,我们就可以继续往下讲了。

根据《Cortex-M3权威指南》--“chap09中断的具体行为”--“入栈”,当SysTick中断发生时,PSP会将发生如下图的变化。

也就是说,当SysTick中断发生时,CPU会自动将被中断任务的R0-R3,R12,LR,PC,xPSR这8个寄存器装载进PSP的后续存储空间,并且PSP最后将指向被中断任务R0寄存器的存储地址。

那么被中断任务的PC寄存器的存储地址就找到啦:PSP+24!如果该任务再次被调度执行,其第一条指令就是地址“PSP+24”存储的内容,如果我“偷偷的”把这个存储内容换成kernel代码的起始地址(确切来说,是第一条指令所在的地址),那么当该任务再次被调度时,原来的任务摇身一变,就成了kernel。

那么,ModifyPC()函数的代码就比较容易理解了。

PCModifyPC伪代码可写为:

ModifyPC()

  PSP.PC = SRAM_Buffer+0x10

 __asm void ModifyPC(void) {
IMPORT SRAM_Buffer
MRS R0, PSP
LDR R1, =SRAM_Buffer
ADD R1, #0x10
STR R1, [R0, #]
BX LR
align
}

第三种:任意字符串

如:“ls”

协议信息16进制表示为

57 41 4e 08 00 31 6c 73

该命令将对TaskDMA_Print的行为产生影响(代码如下)。

不难看出,只有当“ReadDMAFlag不为0时,该任务才会打印缓冲区SRAM_Buffer的内容。而在TaskBH中,上述命令会使变量“ReadDMAFlag”在0,1之间翻转,所以该命令也就起到控制打印“SRAM_Buffer”内容的作用。

 void TaskDMA_Print(void *p_arg)
{
int i = ;
while() {
delayMs();
if(!ReadDMAFlag) continue;
printf("########DMA##########START\r\n");
for(i = ; i < PAGE_SIZE; i++) {
printf("%x ", SRAM_Buffer[i]);
}
printf("########DMA##########END\r\n"); }
}

“my-boot"中几点注意事项:

(1)宏定义PAGE_SIZE

该宏在hardware.h中定义如下:

#define PAGE_SIZE 284

“284”?这个数字怎么这么莫名其妙?其实它表示的是kernel的大小(如下图),同时它也决定了缓冲区SRAM_Buffer的大小。

如果我编译了一个新的kernel,大小不再是284字节了怎么办?

实在对不住!“my-boot”中的这个宏也要改成相应的数字。当然,这确实是个不合理的地方,但现在为使代码尽可能简洁,所以就未做完善这方面的工作了,暂且辛苦一下。

(2)预设宏定义:USE_STDPERIPH_DRIVER

为了使用stm32的函数库,且避免编译出错,故定义该宏。具体内容可查询“stm32f10x.h”第8296行附近的代码。

(3)库函数文件:

如果stm32的型号不是stm32f10x系列的,需要自备相应的函数库。

4、先烧写"my-boot“,然后用"my-boot”加载kernel——操作示例

(1)将“my-boot”烧进stm32开发板

(2)向stm32开发板发送烧写命令:

BURN 0x08004000

16进制表示为
57 41 4e 15 00 75 42 55 52 4e 20 30 78 30 38 30 30 34 30 30 30

命令发送之后,串口工具会打印信息“Addr: 8004000”。而且还有一个变化,那就是另一个LED灯亮了/灭了(如果存在第二个led的话)。

注意,是16进制发送。

(3)发送kernel.bin

这时我们会发现,刚刚亮了/灭了的LED现在又灭了/亮了(如果存在第二个led的话)。

(4)打印刚刚烧进SRAM中的kernel命令(可选):

ls

16进制表示为

57 41 4e 08 00 31 6c 73

该命令发送一次,就会打印一次“###ls###”,并且跟后会打印SRAM中的内容。如果该命令只发送一次,那么SRAM中的打印将每隔2秒打印一次,直到再一次发送该命令为止。

所以图中有2个“###ls###”,第二个就是终止打印的。

(5)启动kernel:

startos

16进制表示为

57 41 4e 0d 00 f8 73 74 61 72 74 6f 73

这时你将会看到开发板在运行kernel的程序啦!

一步步学习操作系统(2)——在STM32上实现一个可动态加载kernel的"my-boot"的更多相关文章

  1. 一步步学习NHibernate(5)——多对一,一对多,懒加载(2)

    请注明转载地址:http://www.cnblogs.com/arhat 通过上一章的学习,我们建立了Student和Clazz之间的关联属性,并从Student(many)的一方查看了Clazz的信 ...

  2. 一步步学习NHibernate(4)——多对一,一对多,懒加载(1)

    请注明转载地址:http://www.cnblogs.com/arhat 通过上一章的学习,我们学会如何使用NHibernate对数据的简单查询,删除,更新和插入,那么如果说仅仅是这样的话,那么NHi ...

  3. Android学习笔记_31_通过后台代码生成View对象以及动态加载XML布局文件到LinearLayout

    一.布局文件part.xml: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android&qu ...

  4. XV6操作系统代码阅读心得(一):启动加载、中断与系统调用

    XV6操作系统是MIT 6.828课程中使用的教学操作系统,是在现代硬件上对Unix V6系统的重写.XV6总共只有一万多行,非常适合初学者用于学习和实践操作系统相关知识. MIT 6.828的课程网 ...

  5. Flutter学习笔记(25)--ListView实现上拉刷新下拉加载

    如需转载,请注明出处:Flutter学习笔记(25)--ListView实现上拉刷新下拉加载 前面我们有写过ListView的使用:Flutter学习笔记(12)--列表组件,当列表的数据非常多时,需 ...

  6. Android动态加载学习笔记(一)

    前言 上周五DPAndroid小分队就第二阶段分享内容进行了讨论,结果形成了三个主题:性能优化.动态加载.内核远离.我选择的是第二项——动态加载.在目前的Android开发中,这一部分知识还是比较流行 ...

  7. python获取动态网站上面的动态加载的数据(初级)

    我们在处理一些网站数据的时候,有时候我们需要的数据很多都是动态加载的,而不都是静态的,以下以一个实例来介绍简单的获取动态数据,首先申明本人小白,还在学习python中,这个方法还是比较笨拙的,但是对于 ...

  8. NGUI学习笔记(四):动态加载UI和NGUI事件

    动态加载UI 我们进入一个场景后,如果将这个场景所有可能用到的UI都直接放在场景中做好,由于要在进入场景时就部署好所有的UI对象,那么当UI对象较多时会碰到的问题是:1.初始化场景会产生非常明显的卡顿 ...

  9. 从高德 SDK 学习 Android 动态加载资源

    前不久跑去折腾高德 SDK 中的 HUD 功能,相信用过该功能的用户都知道 HUD 界面上的导航转向图标是动态变化的.从高德官方导航 API 文档中 AMapNaviGuide 类的描述可知,导航转向 ...

随机推荐

  1. Oracle系统表实用操作笔记

    1.取得指定用户的所有表名: SQL1: SELECT OWNER AS "对象所有者", OBJECT_NAME AS "表名", OBJECT_ID AS ...

  2. Oracle之plsql快速入门

    打开系统输出 set serveroutput on; 只需要打开一次**书写格式 以斜杠/号 结束(基本结构) --declare --语句后面必须以;号结束 declare --用来区分变量名和表 ...

  3. 入职15天,Angular2 小记!

    ng 配置@ngModule({ imports: [ BrowserModule ], //导入模块 declarations: [ AppComponent ], //导入组件 providers ...

  4. nopCommerce 3.9 大波浪系列 之 引擎 NopEngine

    本章涉及到的内容如下 1.EngineContext初始化IEngine实例 2.Autofac依赖注入初始化 3.AutoMapper框架初始化 4.启动任务初始化 一.EngineContext初 ...

  5. 浅谈redux-form在项目中的运用

    准则 先说一下redux的使用场景,因为如果没有redux,那更不会有redux-form. redux基于Flux架构思想,是一个状态管理框架,其目标是解决单页面应用中复杂的状态管理问题. 日常前端 ...

  6. (转)Java线程:新特征-原子量,障碍器

    Java线程:新特征-原子量   所谓的原子量即操作变量的操作是“原子的”,该操作不可再分,因此是线程安全的.   为何要使用原子变量呢,原因是多个线程对单个变量操作也会引起一些问题.在Java5之前 ...

  7. 【性能】web提升性能的小总结

    1. 异步加载js文件,判断文件是否已加载,不重复加载 if (typeof echarts === 'undefined') { console.log('异步加载echarts'); $.getS ...

  8. dubbo源码分析(二):超时原理以及应用场景

    dubbo超时原理以及应用场景 *:first-child { margin-top: 0 !important; } body > *:last-child { margin-bottom: ...

  9. Struts2漏洞解决

    如果你也正在使用Struts2作为web层框架做开发或者做公司的送检产品,然后被告知有各种各样的Struts2漏洞,那本篇博客值得你花时间来喽上一两眼. 前端时间抽空为公司做了新一代的送检产品,为了方 ...

  10. Python查询SQLserver数据库备份(抛砖引玉)

    通过python pymssql直接访问SQLserver数据库,查找其数据库mode,这个脚本具有很强的抛砖引玉特性: 1.可以巡检多台多数据库服务器 2.query内容可以多样化,譬如查询死锁.连 ...