1. Oops信息来源及格式
Oops这个单词含义为“惊讶”,当内核出错时(比如访问非法地址)打印出来的信息被称为Oops信息。
Oops信息包含以下几部分内容:
(1)一段文本描述信息。
      比如类似“Unable to handle kernel NULL pointer dereference at virtual address 00000000"的信息,他说明了发生的是哪类错误。
(2)Oops信息的序号。
      比如是第几次等。这些信息与下面类似,括号内的数据表示序号。

  1. Internal error: Oops: 806 [#1]

(3)内核中加载的模块名称,也可能没有,以下面字样开头。

  1. Modules linked in:

(4)发生错误的CPU的序号,对于单处理器系统,序号为0,如:

  1. CPU: 0 Not tainted (2.6.22.6 #36)

(5)发生错误时CPU的各个寄存器值。
(6)当前进程的名字及进程ID,比如:

  1. Process swapper (pid: 1, stack limit = 0xc0480258)

这并不是说发生错误的是这个进程,而是表示发生错误时,当前进程是它。错误可能发生在内核代码、驱动程序,也可能就是这个进程的错误。
(7)栈信息。
(8)栈回溯信息,可以从中看出函数调用关系,形式如下:

  1. Backtrace:
  2. [<c001a6f4>] (s3c2410fb_probe+0x0/0x560) from [<c01bf4e8>] (platform_drv_probe+0x20/0x24)
  3. ......

(9)出错指令附近的指令机器码,比如(出错指令在小括号内):

  1. Code: e24cb004 e24dd010 e59f34e0 e3a07000 (e5873000)

2. 配置内核使Oops信息的栈回溯信息更直观
Linux 2.26.32自身具备的调试功能,可以使打印出的Oops信息更直观。通过Oops信息中PC寄存器的值可以知道出错指令的地址,通过栈回溯信息可以知道出错时的函数调用关系,根据这两点可以很快定位错误。
要让内核出错时能够打印栈回溯信息,编译内核时要增加“-fno-omit-frame-pointer"选项,这可以通过配置CONFIG_FRAME_POINTER来实现。查看内核目录下的配置文件.config,确保CONFIG_FRAME_POINTER已经被定义,如果没有,执行“make menuconfig”命令重新配置内核。CONFIG_FRAME_POINTER有可能被其他配置项目自动选上。

3使用Oops信息调试内核的实例
(1)获得Oops信息
本小节故意修改 LCD 驱动程序 drivers/video/s3c2410fb.c,加入错误代码:在 s3c2410fb_
probe 函数的开头增加下面两条代码:

  1. int *ptest = NULL;
  2. *ptest = 0x1234;

重新编译内核,启动后会出错并打印出如下 Oops 信息:

  1. Unable to handle kernel NULL pointer dereference at virtual address 00000000
  2. pgd = c0004000
  3. [00000000] *pgd=00000000
  4. Internal error: Oops: 805 [#1]
  5. last sysfs file:
  6. Modules linked in:
  7. CPU: 0 Not tainted (2.6.32 #24)
  8. PC is at s3c2410fb_probe+0xc/0x18
  9. LR is at platform_drv_probe+0x18/0x1c
  10. pc : [<c0018f78>] lr : [<c01d3f88>] psr: a0000013
  11. sp : c3823f30 ip : c3842e7c fp : 00000000
  12. r10: 00000000 r9 : 00000000 r8 : c0445008
  13. r7 : c38a0840 r6 : c0440d18 r5 : c042d178 r4 : 00000000
  14. r3 : 00001234 r2 : 00000000 r1 : 00000000 r0 : c042d170
  15. Flags: NzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment kernel
  16. Control: c000717f Table: 30004000 DAC: 00000017
  17. Process swapper (pid: 1, stack limit = 0xc3822270)
  18. Stack: (0xc3823f30 to 0xc3824000)
  19. 3f20: 00000000 c01d2e7c c0440d18 c042d178
  20. 3f40: 0000000c c042d178 c042d1ac c0440d18 c38a0840 c01d3068 00000000 c01d300c
  21. 3f60: c0440d18 c01d24e8 c3804938 c3847330 00000000 00000000 c0440d18 c01d1d48
  22. 3f80: c03a1506 c03a1506 00000001 c0022dd4 00000000 c0440d18 c0018f84 00000000
  23. 3fa0: 00000000 c01d3334 c0022dd4 00000000 00000000 c0018f84 00000000 c0018f90
  24. 3fc0: c0022dd4 c002e37c c0018f84 c03c5ab3 c0457880 c0022fc8 c0022dd4 00000000
  25. 3fe0: 00000000 00000000 00000000 c00083f8 00000000 c002fe54 00000000 00000000
  26. [<c0018f78>] (s3c2410fb_probe+0xc/0x18) from [<c01d3f88>] (platform_drv_probe+0x18/0x1c)
  27. [<c01d3f88>] (platform_drv_probe+0x18/0x1c) from [<c01d2e7c>] (driver_probe_device+0x148/0x2d8)
  28. [<c01d2e7c>] (driver_probe_device+0x148/0x2d8) from [<c01d3068>] (__driver_attach+0x5c/0x7c)
  29. [<c01d3068>] (__driver_attach+0x5c/0x7c) from [<c01d24e8>] (bus_for_each_dev+0x48/0x78)
  30. [<c01d24e8>] (bus_for_each_dev+0x48/0x78) from [<c01d1d48>] (bus_add_driver+0xe0/0x288)
  31. [<c01d1d48>] (bus_add_driver+0xe0/0x288) from [<c01d3334>] (driver_register+0xa4/0x130)
  32. [<c01d3334>] (driver_register+0xa4/0x130) from [<c0018f90>] (s3c2410fb_init+0xc/0x28)
  33. [<c0018f90>] (s3c2410fb_init+0xc/0x28) from [<c002e37c>] (do_one_initcall+0x5c/0x1b4)
  34. [<c002e37c>] (do_one_initcall+0x5c/0x1b4) from [<c00083f8>] (kernel_init+0x94/0x10c)
  35. [<c00083f8>] (kernel_init+0x94/0x10c) from [<c002fe54>] (kernel_thread_exit+0x0/0x8)
  36. Code: eafffe97 e3a02000 e59f3008 e1a01002 (e5823000)
  37. ---[ end trace da227214a82491b7 ]---
  38. swapper used greatest stack depth: 5792 bytes left
  39. Kernel panic - not syncing: Attempted to kill
  40. [<c00352ac>] (unwind_backtrace+0x0/0x150) from [<c0311c54>] (panic+0x40/0x120)
  41. [<c0311c54>] (panic+0x40/0x120) from [<c0046cbc>] (do_exit+0x64/0x5f4)
  42. [<c0046cbc>] (do_exit+0x64/0x5f4) from [<c0032f28>] (die+0x15c/0x180)
  43. [<c0032f28>] (die+0x15c/0x180) from [<c00360a0>] (__do_kernel_fault+0x64/0x74)
  44. [<c00360a0>] (__do_kernel_fault+0x64/0x74) from [<c0036268>] (do_page_fault+0x1b8/0x1cc)
  45. [<c0036268>] (do_page_fault+0x1b8/0x1cc) from [<c002e2c0>] (do_DataAbort+0x34/0x94)
  46. [<c002e2c0>] (do_DataAbort+0x34/0x94) from [<c002ea20>] (__dabt_svc+0x40/0x60)
  47. Exception stack(0xc3823ee8 to 0xc3823f30)
  48. 3ee0: c042d170 00000000 00000000 00001234 00000000 c042d178
  49. 3f00: c0440d18 c38a0840 c0445008 00000000 00000000 00000000 c3842e7c c3823f30
  50. 3f20: c01d3f88 c0018f78 a0000013 ffffffff
  51. [<c002ea20>] (__dabt_svc+0x40/0x60) from [<c0018f78>] (s3c2410fb_probe+0xc/0x18)
  52. [<c0018f78>] (s3c2410fb_probe+0xc/0x18) from [<c01d3f88>] (platform_drv_probe+0x18/0x1c)
  53. [<c01d3f88>] (platform_drv_probe+0x18/0x1c) from [<c01d2e7c>] (driver_probe_device+0x148/0x2d8)
  54. [<c01d2e7c>] (driver_probe_device+0x148/0x2d8) from [<c01d3068>] (__driver_attach+0x5c/0x7c)
  55. [<c01d3068>] (__driver_attach+0x5c/0x7c) from [<c01d24e8>] (bus_for_each_dev+0x48/0x78)
  56. [<c01d24e8>] (bus_for_each_dev+0x48/0x78) from [<c01d1d48>] (bus_add_driver+0xe0/0x288)
  57. [<c01d1d48>] (bus_add_driver+0xe0/0x288) from [<c01d3334>] (driver_register+0xa4/0x130)
  58. [<c01d3334>] (driver_register+0xa4/0x130) from [<c0018f90>] (s3c2410fb_init+0xc/0x28)
  59. [<c0018f90>] (s3c2410fb_init+0xc/0x28) from [<c002e37c>] (do_one_initcall+0x5c/0x1b4)
  60. [<c002e37c>] (do_one_initcall+0x5c/0x1b4) from [<c00083f8>] (kernel_init+0x94/0x10c)
  61. [<c00083f8>] (kernel_init+0x94/0x10c) from [<c002fe54>] (kernel_thread_exit+0x0/0x8)

(2)分析 Oops 信息

  • 确出错原因。

由出错信息“Unable to handle kernel NULL pointer dereference at virtual address 00000000”可知内核是因为非法地址访问出错,使用了空指针。

  • 根据栈回溯信息找出函数调用关系。

内核崩溃时,可以从 pc 寄存器得知崩溃发生时的函数、出错指令。但是很多情况下,错误有可能是它的调用者引入的,所以找出函数的调用关系也很重要。
部分栈回溯信息如下:

  1. [<c0018f78>] (s3c2410fb_probe+0xc/0x18) from [<c01d3f88>] (platform_drv_probe+0x18/0x1c)

这行信息分为两部分,表示后面的 platform_drv_probe 函数调用了前面的 s3c2410fb_probe函数。
前半部含义为:“c0018f78”是 s3c2410fb_probe 函数首地址偏移 0xc 的地址,这个函数大小为 0x18。
后半部含义为:“c01d3f88”是 platform_drv_probe 函数首地址偏移 0x18 的地址,这个函数大小为 0x1c。
另外,后半部的“[<c01d3f88>]”表示 s3c2410fb_probe 执行后的返回地址。
对于类似下面的栈回溯信息,其中是 r8~r4 表示 driver_probe_device 函数刚被调用时这
些寄存器的值。
从上面的栈回溯信息可以知道内核出错时的函数调用关系如下,最后在 s3c2410fb_probe函数内部崩溃。

  1. do_exit ->
  2. kernel_init ->
  3. do_one_initcall ->
  4. s3c2410fb_init ->
  5. driver_register ->
  6. bus_add_driver ->
  7. bus_for_each_dev ->
  8. __driver_attach ->
  9. driver_probe_device ->
  10. platform_drv_probe ->
  11. s3c2410fb_probe
  • 根据 pc 寄存器的值确定出错位置。

上述 Oops 信息中出错时的寄存器值如下:

  1. PC is at s3c2410fb_probe+0xc/0x18
  2. LR is at platform_drv_probe+0x18/0x1c
  3. pc : [<c0018f78>] lr : [<c01d3f88>] psr: a0000013
  4. sp : c3823f30 ip : c3842e7c fp : 00000000
  5. r10: 00000000 r9 : 00000000 r8 : c0445008
  6. r7 : c38a0840 r6 : c0440d18 r5 : c042d178 r4 : 00000000
  7. r3 : 00001234 r2 : 00000000 r1 : 00000000 r0 : c042d170

"PC is at s3c2410fb_probe+0xc/0x18"表示出错指令为 s3c2410fb_probe 函数中偏移为0xc 的指令。
"pc : [<c0018f78>] "表示出错指令的地址为 c0018f78(十六进制)。

  • 结合内核源代码和反汇编代码定位问题。

先生成内核的反汇编代码 vmlinux.dis,执行以下命令:

  1. $ cd ~/Documents/myTest/kernel/linux-2.6.32
  2. $ arm-linux-objdump -D vmlinux > vmlinux.dis

出错地址 c0018f78 附近的部分汇编代码如下:

  1. c0018f64 <s3c2412fb_probe>:
  2. c0018f64: e3a01001 mov r1, #1 ; 0x1
  3. c0018f68: eafffe97 b c00189cc <s3c24xxfb_probe>
  4. c0018f6c <s3c2410fb_probe>:
  5. c0018f6c: e3a02000 mov r2, #0 ; 0x0
  6. c0018f70: e59f3008 ldr r3, [pc, #8] ; c0018f80 <s3c2410fb_probe+0x14>
  7. c0018f74: e1a01002 mov r1, r2
  8. c0018f78: e5823000 str r3, [r2]         <===========出错指令
  9. c0018f7c: eafffe92 b c00189cc <s3c24xxfb_probe>
  10. c0018f80: 00001234 .word 0x00001234
  11. c0018f84 <s3c2410fb_init>:
  12. c0018f84: e92d4010 push {r4, lr}
  13. c0018f88: e59f0014 ldr r0, [pc, #20] ; c0018fa4 <s3c2410fb_init+0x20>
  14. c0018f8c: eb06ed06 bl c01d43ac <platform_driver_register>
  15. c0018f90: e3500000 cmp r0, #0 ; 0x0
  16. c0018f94: 18bd8010 popne {r4, pc}
  17. c0018f98: e59f0008 ldr r0, [pc, #8] ; c0018fa8 <s3c2410fb_init+0x24>
  18. c0018f9c: e8bd4010 pop {r4, lr}
  19. c0018fa0: ea06ed01 b c01d43ac <platform_driver_register>
  20. c0018fa4: c0440d04 .word 0xc0440d04

, r2 为 0。0 地址不可访问,所以出错。

  1. static int __init s3c2410fb_probe(struct platform_device *pdev)
  2. {
  3. // add for kernel panic --begin
  4. int *ptest = NULL;
  5. *ptest = 0x1234;
  6. // add for kernel panic --end
  7. return s3c24xxfb_probe(pdev, DRV_S3C2410);
  8. }

结合反汇编代码,很容易知道是“*ptest = 0x1234;”导致错误,其中的 ptest 为空。对于大多数情况,从反汇编代码定位到 C 代码并不会如此容易,这需要较强的阅读汇编程序的能力。通过栈回溯信息知道函数的调用关系,这已经可以帮助定位很多问题了。

4.使用 Oops 的栈信息手工进行栈回溯
 
前面说过,从 Oops 信息的 pc
寄存器值可知得知崩溃发生时的函数、出错指令。但是错误有可能是它的调用者引入的,所以还要找出函数的调用关系。由于内核配置了
CONFIG_FRAME_POINTER,当出现 Oops 信息时,会打印栈回溯信息。如果内核没有配置
CONFIG_FRAME_POINTER,这时可以自己分析栈信息,找到函数的调用关系。
 (1)栈的作用
一个程序包含代码段、数据
段、BSS 段、堆、栈;其中数据段用来中存储初始值不为 0 的全局数据,BSS 段用来存储初始值为 0
的全局数据,堆用于动态内存分配,栈用于实现函数调用、存储局部变量。被调用函数在执行之前,它会将一些寄存器的值保存在栈中,其中包括返回地址寄存器
lr。如果知道了所保存的 lr 寄存的值,那么就可以知道它的调用者是谁。在栈信息中,一个函数一个函数地往上找出所有保存的 lr
值,就可以知道各个调用函数,这就是栈回溯的原理。
 (2)栈回溯实例分析
仍以前面的 LCD 驱动程序为例,使用上面的 Oops 信息的栈信息进行分析,栈信息如下:

  1. Stack: (0xc3823f30 to 0xc3824000)
  2. 3f20: 00000000 c01d2e7c c0440d18 c042d178
  3. 3f40: 0000000c c042d178 c042d1ac c0440d18 c38a0840 c01d3068 00000000 c01d300c
  4. 3f60: c0440d18 c01d24e8 c3804938 c3847330 00000000 00000000 c0440d18 c01d1d48
  5. 3f80: c03a1506 c03a1506 00000001 c0022dd4 00000000 c0440d18 c0018f84 00000000
  6. 3fa0: 00000000 c01d3334 c0022dd4 00000000 00000000 c0018f84 00000000 c0018f90
  7. 3fc0: c0022dd4 c002e37c c0018f84 c03c5ab3 c0457880 c0022fc8 c0022dd4 00000000
  8. 3fe0: 00000000 00000000 00000000 c00083f8 00000000 c002fe54 00000000 00000000
  • 根据 pc 寄存器值找到第一个函数,确定它的栈大小,确定调用函数。

从 Oops 信息

  1. PC is at s3c2410fb_probe+0xc/0x18
  2. LR is at platform_drv_probe+0x18/0x1c
  3. pc : [<c0018f78>] lr : [<c01d3f88>] psr: a0000013
  4. sp : c3823f30 ip : c3842e7c fp : 00000000
  5. r10: 00000000 r9 : 00000000 r8 : c0445008
  6. r7 : c38a0840 r6 : c0440d18 r5 : c042d178 r4 : 00000000
  7. r3 : 00001234 r2 : 00000000 r1 : 00000000 r0 : c042d170

可知 pc 值为 c0018f78,使用它在内核反汇编程序 vmlinux.dis 中可以知道它位于 s3c2410fb_probe 函数内。

  1. c0018f6c <s3c2410fb_probe>:
  2. c0018f6c: e3a02000 mov r2, #0 ; 0x0
  3. c0018f70: e59f3008 ldr r3, [pc, #8] ; c0018f80 <s3c2410fb_probe+0x14>
  4. c0018f74: e1a01002 mov r1, r2
  5. c0018f78: e5823000 str r3, [r2] <===========出错指令
  6. c0018f7c: eafffe92 b c00189cc <s3c24xxfb_probe>
  7. c0018f80: 00001234 .word 0x00001234

lr存放的是函数返回值地址,为c01d3f88,根据这个地址,搜索内核反汇编程序 vmlinux.dis ,可知它位于:

  1. c01d3f70 <platform_drv_probe>:
  2. c01d3f70: e92d4010 push {r4, lr}
  3. c01d3f74: e1a03000 mov r3, r0
  4. c01d3f78: e5933044 ldr r3, [r3, #68]
  5. c01d3f7c: e2400008 sub r0, r0, #8 ; 0x8
  6. c01d3f80: e1a0e00f mov lr, pc
  7. c01d3f84: e513f014 ldr pc, [r3, #-20]
  8. c01d3f88: e8bd8010 pop {r4, pc}

也就是说,函数s3c2410fb_probe() 被platform_drv_probe()调用。再看platform_drv_probe()的反汇编代码,期中

  1. c01d3f70: e92d4010 push {r4, lr}

栈中存放的是 r4, lr
对应

  1. Stack: (0xc3823f30 to 0xc3824000)
  2. 3f20:          
                                                                    
    00000000 c01d2e7c c0440d18 c042d178
  3. 3f40: 0000000c c042d178 c042d1ac c0440d18 c38a0840 c01d3068 00000000 c01d300c
  4. 3f60: c0440d18 c01d24e8 c3804938 c3847330 00000000 00000000 c0440d18 c01d1d48
  5. 3f80: c03a1506 c03a1506 00000001 c0022dd4 00000000 c0440d18 c0018f84 00000000
  6. 3fa0: 00000000 c01d3334 c0022dd4 00000000 00000000 c0018f84 00000000 c0018f90
  7. 3fc0: c0022dd4 c002e37c c0018f84 c03c5ab3 c0457880 c0022fc8 c0022dd4 00000000
  8. 3fe0: 00000000 00000000 00000000 c00083f8 00000000 c002fe54 00000000 00000000

其中,lr对应的值为c01d2e7c,用此值检索vmlinux.dis,位于

  1. c01d2d34 <driver_probe_device>:
  2. c01d2d34: e92d40f7 push {r0, r1, r2, r4, r5, r6, r7, lr}
  3. c01d2d38: e5d13028 ldrb r3, [r1, #40]
  4. c01d2d3c: e1a05001 mov r5, r1
  5. ....
  6. c01d2e7c: e2504000 subs r4, r0, #0 ; 0x0
  7. c01d2e80: 1a000016 bne c01d2ee0 <driver_probe_device+0x1ac>
  8. c01d2e84: e1a00005 mov r0, r5
  9. c01d2e88: ebffff6e bl c01d2c48 <driver_bound>

可知,platform_drv_probe()被driver_probe_device()调用,再用同样的方法就可以找出所有函数调用关系。

附:

arm-none-eabi-objdump -Dz -S vmlinux >linux.dump

值得注意的是,arm-none-eabi-objdump的参数-S表示尽可能的把原来的代码和反汇编出来的代码一起呈现出来,-S参数需要结合 arm-linux-gcc编译参数-g,才能达到反汇编时同时输出原来的代码。所以,我在linux内核代码根目录的Makefile中增加-g编译参 数:

KBUILD_CFLAGS   := -g -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs \
     -fno-strict-aliasing -fno-common \
     -Werror-implicit-function-declaration \
     -Wno-format-security \
     -fno-delete-null-pointer-checks

修改Makefile后,重新编译内核,在根目录中生成的vmlinux文件就会包含了原来的代码信息,这时再执行arm-none-eabi-objdump -Dz -S vmlinux >linux.dump需要数个小时的时间。

Oops信息及栈回溯的更多相关文章

  1. linux中oops信息的调试及栈回溯【转】

    本文转载自:http://blog.csdn.net/kangear/article/details/8217329 ========================================= ...

  2. linux中Oops信息的调试及栈回溯

    Oops 信息来源及格式 Oops 这个单词含义为“惊讶” ,当内核出错时(比如访问非法地址)打印出来的信息被 称为 Oops 信息. Oops 信息包含以下几部分内容. 1 一段文本描述信息. 比如 ...

  3. Linux中oops信息调试【转】

    1.Oops 信息来源及格式 Oops 这个单词含义为“惊讶”,当内核出错时(比如访问非法地址)打印出来的信息被称为 Oops 信息. 2.Oops 信息包含以下几部分内容 2.1 一段文本描述信息. ...

  4. linux内核中打印栈回溯信息 - dump_stack()函数分析【转】

    转自:http://blog.csdn.net/jasonchen_gbd/article/details/45585133 版权声明:本文为博主原创文章,转载请附上原博链接.   目录(?)[-] ...

  5. 怎么从代码中拿到栈回溯信息(call stack trace)

    博客搬到了fresky.github.io - Dawei XU,请各位看官挪步.最新的一篇是:怎么从代码中拿到栈回溯信息(call stack trace).

  6. 26、驱动调试之根据oops信息和堆栈确定出错的代码

    a.驱动作为模块:1. 根据pc值确定该指令属于内核还是外加的模块pc=0xbf000018 它属于什么的地址?是内核还是通过insmod加载的驱动程序?先判断是否属于内核的地址: 看System.m ...

  7. 内核中dump_stack的实现原理(1) —— 栈回溯

    环境 Aarch64 Qemu aarch64-linux-gnu-gcc linux-4.14   概述     栈回溯的目的是将函数的调用栈打印出来,对于分析函数调用和debug系统异常会很有帮助 ...

  8. 如何解读Linux Kernel OOPS信息

    OOPS信息解读 root@firefly:~/mnt/module# insmod oops_module.ko [ 867.140514] Unable to handle kernel NULL ...

  9. 栈回溯简单实现(x86)

    0x01  栈简介  首先局部变量的分配释放是通过调整栈指针实现的,栈为函数调用和定义局部变量提供了一块简单易用的空间,定义在栈上的变量不必考虑内存申请和释放.只要调整栈指针就可以分配和释放内存.   ...

随机推荐

  1. (原+转)使用opencv的DFT计算卷积

    转载请注明出处: http://www.cnblogs.com/darkknightzh/p/5462665.html 参考网址: http://blog.csdn.net/lichengyu/art ...

  2. SQL语句 计算某段时间工作日的天数(除了周六日)

    --只是加了固定日期,可以根据需求给成变量形式(BY 少年工藤) -思路:根据日期区间循环判断每一天是周日(1).周六(7)不变,其他加1 1 DECLARE @DAY DATE,@COUNT INT ...

  3. PHP获取客户端操作系统,浏览器,语言,IP,IP归属地等

    <?php class Client { ////获得访客浏览器类型 function Get_Browser(){ if(!empty($_SERVER['HTTP_USER_AGENT']) ...

  4. shell之冒号的作用

    冒号:   :在shell中是一种命令,意思是总是为真,但是却不做任何操作,即总是为真的空命令 eg:   [root@localhost ~]# ${abc:=t1}-bash: t1: comma ...

  5. 身份证校验程序(上)- 零基础入门学习Delphi48

    身份证校验程序 让编程改变世界 Change the world by program [caption id="attachment_2699" align="alig ...

  6. js加载优化

    阻塞特性:       JS 有个很无语的阻塞特性,就是当浏览器在执行JS 代码时,不能同时做其他任何事情,无论其代码是内嵌的还是外部的. 脚本位置:       浏览器在碰到一个引入外部JS 文件的 ...

  7. android导航设计

    http://www.geekpark.net/read/view/199244 Android 应用中十大导航设计错误 http://mobile.51cto.com/design-432944.h ...

  8. POP3、SMTP、IMAP和Exchange都是个什么玩意?

    很多时候一直对POP3.SMTP.IMAP和Exchange等迷迷糊糊的.下面就整理说明一下: 当前常用的电子邮件协议有SMTP.POP3.IMAP4,它们都隶属于TCP/IP协议簇,默认状态下,分别 ...

  9. SQL Server索引的维护 - 索引碎片、填充因子 <第三篇>

    实际上,索引的维护主要包括以下两个方面: 页拆分 碎片 这两个问题都和页密度有关,虽然两者的表现形式在本质上有所区别,但是故障排除工具是一样的,因为处理是相同的. 对于非常小的表(比64KB小得多), ...

  10. SqlServer IF Exists([database]|[table]|[prop]) / Column([Operation])

    *************************** --判断数据库是否存在 IF EXISTS (SELECT * FROM MASTER..sysdatabases WHERE NAME = ' ...