转载于:  http://jexbat.com/2016/BBB-Uboot/

什么是 U-Boot

熟悉嵌入式开发的应该都听过它,U-boot 就是启动系统前的一段引导程序,虽然是引导程序,但是功能非常强大。

这一篇主要讲解如何从无到有运行 U-Boot,关于 U-Boot 引导 Linux 的部分放在另外一篇文章讲解。

U-Boot 之前的版本以版本号命名如:0.1.0, 0.2.0 这几年改为了以时间和日期命名:U-Boot 2016.03。

使用 git 获得 U-Boot 的源码:

1
git clone git://git.denx.de/u-boot.git

目前我使用的是 2016.02 的版本。

MLO 及其启动过程

上一篇文章,我们了解了 BeagleBone 有个 SPL 过程,就在这个时候读取 MLO 文件,MLO 文件其实是个精简版的 U-Boot,也是由 U-Boot 生成,但是功能有限,只初始化了部分资源如 DDR,然后启动 U-Boot。

MLO 文件是如何编译出来的

分析 MLO 的编译过程之前需要知道编译原理和 Makefile 等相关知识。
我们先找找 Makefile 看看能不能找到什么。建议使用 Sublime 编辑器。用全局查找功能查找 MLO 关键字。

找到 u-boot/scripts/Makefile.spl 文件 117行

u-boot/scripts/Makefile.spl

12
MLO MLO.byteswap: $(obj)/u-boot-spl.bin FORCE $(call if_changed,mkimage)

可以看到 MLO 文件是由 u-boot-spl.bin 文件通过 mkimage 命令生成的。
再查到 u-boot/Makefile 文件 1310 行

u-boot/Makefile

1234
spl/u-boot-spl.bin: spl/u-boot-spl @:spl/u-boot-spl: tools prepare $(if $(CONFIG_OF_SEPARATE),dts/dt.dtb) $(Q)$(MAKE) obj=spl -f $(srctree)/scripts/Makefile.spl all

u-boot-spl.bin 文件是还是由 u-boot/scripts/Makefile.spl 文件生成。
文件 u-boot/scripts/Makefile.spl 168 行 定义了 u-boot-spl.bin 的生成:

u-boot/scripts/Makefile.spl

1234567891011
ifeq ($(CONFIG_SPL_OF_CONTROL),y)$(obj)/$(SPL_BIN)-dtb.bin: $(obj)/$(SPL_BIN)-nodtb.bin $(obj)/$(SPL_BIN)-pad.bin \        $(obj)/$(SPL_BIN).dtb FORCE   $(call if_changed,cat)

$(obj)/$(SPL_BIN).bin: $(obj)/$(SPL_BIN)-dtb.bin FORCE $(call if_changed,copy)else$(obj)/$(SPL_BIN).bin: $(obj)/$(SPL_BIN)-nodtb.bin FORCE $(call if_changed,copy)endif

因为 SPL_BIN 在 第32行 定义为 u-boot-spl:

u-boot/scripts/Makefile.spl

12345
ifeq ($(CONFIG_TPL_BUILD),y)SPL_BIN := u-boot-tplelseSPL_BIN := u-boot-splendif

由 168 行 上面的定义可以知道 u-boot-spl.bin 和 u-boot-spl-nodtb.bin 有关系。

接着查找到第223行

u-boot/scripts/Makefile.spl

12
$(obj)/$(SPL_BIN)-nodtb.bin: $(obj)/$(SPL_BIN) FORCE $(call if_changed,objcopy)

u-boot-spl-nodtb.bin 是通过 objcopy 命令由 u-boot-spl 生成。

再看第246行:

u-boot/scripts/Makefile.spl

12
$(obj)/$(SPL_BIN): $(u-boot-spl-init) $(u-boot-spl-main) $(obj)/u-boot-spl.lds FORCE   $(call if_changed,u-boot-spl)

所以u-boot-spl 是由 u-boot-spl.lds 链接文件生成的 ,但是目录下面有几个u-boot-spl.lds文件,到底是哪个 lds 文件呢,上面是 $(obj)/u-boot-spl.lds, obj 在 1310 行 编译 u-boot-spl.bin 的时候赋值为 obj=spl,所以我们需要看 u-boot/spl/u-boot-spl.lds 这个文件,但是如果你之前没有编译过这个文件是没有的。这个文件是如何生成的呢?我们稍后再看,先看 lds 文件的内容:

u-boot/spl/u-boot-spl.lds

123456789101112131415161718192021222324252627282930313233343536
MEMORY { .sram : ORIGIN = 0x402F0400, LENGTH = (0x4030B800 - 0x402F0400) }MEMORY { .sdram : ORIGIN = 0x80a00000, LENGTH = 0x80000 }OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")OUTPUT_ARCH(arm)ENTRY(_start)SECTIONS{ .text : { __start = .; *(.vectors) arch/arm/cpu/armv7/start.o (.text) *(.text*) } >.sram . = ALIGN(4); .rodata : { *(SORT_BY_ALIGNMENT(.rodata*)) } >.sram . = ALIGN(4); .data : { *(SORT_BY_ALIGNMENT(.data*)) } >.sram .u_boot_list : { KEEP(*(SORT(.u_boot_list*))); } >.sram . = ALIGN(4); __image_copy_end = .; .end : { *(.__end) } >.sram .bss : { . = ALIGN(4); __bss_start = .; *(.bss*) . = ALIGN(4); __bss_end = .; } >.sdram}

链接文件里面说明了内存布局,arch/arm/cpu/armv7/start.o 代码段都放在 SRAM 中,所以 arch/arm/cpu/armv7/start.S 就是我们要找的东西了。

lds 链接文件的生成

u-boot/spl/u-boot-spl.lds 这个文件的生成在 u-boot/scripts/Makefile.spl 有解释:

u-boot/scripts/Makefile.spl

12
$(obj)/u-boot-spl.lds: $(LDSCRIPT) FORCE $(call if_changed_dep,cpp_lds)

LDSCRIPT 的定义:

u-boot/scripts/Makefile.spl

123456789101112131415161718
# Linker Scriptifdef CONFIG_SPL_LDSCRIPT# need to strip off double quotesLDSCRIPT := $(addprefix $(srctree)/,$(CONFIG_SPL_LDSCRIPT:"%"=%))endif

ifeq ($(wildcard $(LDSCRIPT)),) LDSCRIPT := $(srctree)/board/$(BOARDDIR)/u-boot-spl.ldsendififeq ($(wildcard $(LDSCRIPT)),) LDSCRIPT := $(srctree)/$(CPUDIR)/u-boot-spl.ldsendififeq ($(wildcard $(LDSCRIPT)),) LDSCRIPT := $(srctree)/arch/$(ARCH)/cpu/u-boot-spl.ldsendififeq ($(wildcard $(LDSCRIPT)),)$(error could not find linker script)endif

可见 Makefile.spl 文件中先是判断有没有指定的 lds 文件,如果没有指定的,就查找 board 文件夹中目标板目录下面有没有 lds 文件,如果没有就查找相应的 cpu 目录,因为我们目标器件是 am335x,所以发现有 u-boot/arch/arm/cpu/armv7/am33xx/u-boot-spl.lds 再通过 cpp_lds 命令编译成,cpp_lds 是一组命令的集合,具体定义还是在 Makefile.spl 文件中,我们查看 u-boot/arch/arm/cpu/armv7/am33xx/u-boot-spl.lds 也发现 MLO 文件代码是在 start.S 文件中。

MLO 程序分析

查看 start.S 分析下 MLO 程序具体的执行流程,MLO 的 makefile 会根据 CONFIG_SPL_BUILD 编译不同的源文件,同样的在源码内也通过 CONFIG_SPL_BUILD 控制不同的代码执行,前面一部分 MLO 文件和 U-Boot 是类似的,进入到 _main 函数中两个程序的功能就开始出现差异了:

123456789101112131415161718192021222324252627282930313233343536373839404142
reset //(arch/arm/cpu/armv7/start.S)save_boot_params_ret //(arch/arm/cpu/armv7/start.S)  |- disable interrupts   |- cpu_init_cp15 //(arch/arm/cpu/armv7/start.S)  |   |- Invalidate L1 I/D | |- disable MMU stuff and caches |- cpu_init_crit //(arch/arm/cpu/armv7/start.S) | |- lowlevel_init //(arch/arm/cpu/armv7/lowlevel_init.S) | |- Setup a temporary stack | |- Set up global data  | |- s_init //(arch/arm/cpu/armv7/am33xx/board.c) | |- watchdog_disable | |- set_uart_mux_conf | |- setup_clocks_for_console | |- uart_soft_reset |- _main //(arch/arm/lib/crt0.S)

 |(MLO)如果是 MLO 文件 |- board_init_f //(arch/arm/cpu/armv7/am33xx/board.c) | |- board_early_init_f //(arch/arm/cpu/armv7/am33xx/board.c) | | |- prcm_init | | |- set_mux_conf_regs | |- sdram_init //(board/ti/am335x/board.c) 初始化 DDR |- spl_relocate_stack_gd |- board_init_r //(common/spl/spl.c) |- ... |- spl_load_image //根据不同的启动方式加载 u-boot 镜像, |- jump_to_image_no_args //进入u-boot代码运行

 |(U-Boot)如果是U-Boot 镜像 |- board_init_f //(common/board_f.c) | |- ... | |- initcall_run_list(init_sequence_f)  | |- ...  |  |- relocate_code //(arch/arm/lib/relocate.S) 代码重定位 |- relocate_vectors //(arch/arm/lib/relocate.S) 向量表重定义 |- Set up final (full) environment  |- board_init_r //(common/board_r.c) |- initcall_run_list(init_sequence_r)//初始化各种外设 |- main_loop()

当 U-Boot 重定位好代码、向量表之后,运行 board_init_r 函数,此函数会调用 init_sequence_r 列表里面的函数初始化各种外设驱动,最后在 main_loop() 函数中运行,U-Boot 有个 bootdelay 延时启动,如果不手动停止 U-Boot 会自动运行 bootcmd 包含的命令。

内核引导这部分放在另外一篇文章详细讲解。

U-Boot 编译

编译 U-Boot

编译 U-Boot 前我们需要安装交叉编译器:

1
# sudo apt-get install gcc-arm-linux-gnueabihf

下载 U-Boot 源码:

1
# git clone git://git.denx.de/u-boot.git

因为 U-Boot 官方已经支持了 Beaglebone Black 所以配置文件也已经自带了,编译输入如下命令:

123
# make distclean# make am335x_boneblack_defconfig# ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- make

片刻后会生成 MLO 和 u-boot.img 文件。

配置 U-Boot 参数

有两种方式可以配置 U-Boot 的一些参数,分别是 uEnv.txt 和 boot.src 文件。
U-Boot 启动的时候会在启动分区寻找这两个文件。

boot.scr: This file is a U-Boot script. It contains instructions for U-Boot. Using these instruction, the kernel is loaded into memory, and (optionally) a ramdisk is loaded. boot.scr can also pass parameters to the kernel. This file is a compiled script, and cannot be edited directly. In some cases, boot.scr loads further instructions and configuration parameters from a text file.

uEnv.txt: A file with additional boot parameters. This file can be read by boot.scr, or by the boot sequence if there is no script file. uEnv.txt is a regular text file that can be edited. This file should have Unix line ending, so a compatible program must be used when editing this file.

U-Boot 启动的时候如果不打断会调用 bootcmd 包含的命令来执行,通常 bootcmd 会调用 bootscript 脚本也就是 boot.scr 里面的命令进行执行, boot.scr 通常也会先读取 uEnv.txt 确定额外参数,因为 boot.src文件必须通过 boot.cmd 文件编译而来, uEnv.txt 则是可以任意编辑,这样可配置性就大大提高了。如果没有 boot.src 文件,U-Boot 有默认配置的 bootcmd 命令。

在 Beagelbone Black 中我们不需要额外的 boot.scr 文件,用默认的命令即可,默认的命令为:

123
#define CONFIG_BOOTCOMMAND \   "run findfdt; " \   "run distro_bootcmd"

run distro_bootcmd 最终会调用 run mmcboot 命令加载 uEnv.txt 文件,并且会运行 uEnv.txt 文件里面 uenvcmd 指代的命令。

uEnv.txt 从网络启动例子:

1234567
console=ttyO0,115200n8ipaddr=192.168.23.2serverip=192.168.23.1rootpath=/exports/rootfsnetargs=setenv bootargs console=${console} ${optargs} root=/dev/nfs nfsroot=${serverip}:${rootpath},${nfsopts} rw ip=${ipaddr}:${serverip}:192.168.23.1:255.255.255.0:beaglebone:eth0:none:192.168.23.1netboot=echo Booting from network ...; tftp ${loadaddr} ${bootfile}; tftp ${fdtaddr} ${fdtfile}; run netargs; bootz ${loadaddr} - ${fdtaddr}uenvcmd=run netboot

制作 U-Boot 的 SD 启动卡

制作 SD 启动卡之前首先需要为 SD 卡分区, ROM Code 启动的时候如果是从 MMC 设备加载启动代码,ROM Code 会从第一个活动分区寻找名为 “MLO” 的文件,并且此分区必须为 FAT文件系统。所以制作 U-Boot 的启动卡只需要一个带有 MLO 和 U-Boot 镜像的 FAT 格式的 SD 卡,如果需要启动 Linux 内核还需要别的分区,我们以后再讲。

有两种方式可以制作包含 U-Boot 的可启动的 SD 卡,一种是用 RAW Mode 的方式,还有一种是用 FTA 的方式。

RAW Mode 和烧写方式在这篇文章里面有讲:解析 BeagleBone Black 官方镜像。

FTA 模式下只要建立一个 FTA 分区再把 MLO 和 uboot.img 文件拷贝进去即可。

我是使用的 USB 读卡器,插入后 Linux /dev/ 目录会显示 /dev/sd* 设备,我这里多出两个设备分别显示 /dev/sdb 和 /dev/sdb1 ,其中 /dev/sdb 表示一整个物理磁盘, /dev/sdb1 表示的是具体的分区。

使用命令 sudo fdisk /dev/sdb 管理磁盘:

a : toggle a bootable flag(设置或取消启动表示)

b : edit bsd disklabel(编辑 bsd disklabel)

c : toggle the dos compatibility flag

d : delete a partition (删除一个分区)

l : list known partition types (列出已知的分区类型)

m : print this menu (打印次列表)

n : add a new partition (增加一个新分区)

o : create a new empty DOS partition table (建立一个新的空 DOS 分区表)

p : print the partition table (打印分区表)

q : quit without saving changes (不保存退出)

s : create a new empty Sun disklabel

t : change a partition’s system id

u : change display/entry units

v : verify the partition table (验证分区表)

w : write table to disk and exit (把分区表写入磁盘)

x : extra functionality (experts only) (额外的功能)

新建启动分区:

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
Command (m for help): p

Disk /dev/sdb: 7746 MB, 7746879488 bytes24 heads, 20 sectors/track, 31522 cylinders, total 15130624 sectorsUnits = sectors of 1 * 512 = 512 bytesSector size (logical/physical): 512 bytes / 512 bytesI/O size (minimum/optimal): 512 bytes / 512 bytesDisk identifier: 0x00000000

 Device Boot Start End Blocks Id System/dev/sdb1 * 2048 15130623 7564288 c W95 FAT32 (LBA)

Command (m for help): mCommand action a toggle a bootable flag b edit bsd disklabel c toggle the dos compatibility flag d delete a partition l list known partition types m print this menu n add a new partition o create a new empty DOS partition table p print the partition table q quit without saving changes s create a new empty Sun disklabel t change a partition's system id u change display/entry units v verify the partition table w write table to disk and exit x extra functionality (experts only)

Command (m for help): dSelected partition 1

Command (m for help): p

Disk /dev/sdb: 7746 MB, 7746879488 bytes24 heads, 20 sectors/track, 31522 cylinders, total 15130624 sectorsUnits = sectors of 1 * 512 = 512 bytesSector size (logical/physical): 512 bytes / 512 bytesI/O size (minimum/optimal): 512 bytes / 512 bytesDisk identifier: 0x00000000

 Device Boot Start End Blocks Id System

Command (m for help): nPartition type: p primary (0 primary, 0 extended, 4 free) e extendedSelect (default p): pPartition number (1-4, default 1): Using default value 1First sector (2048-15130623, default 2048): Using default value 2048Last sector, +sectors or +size{K,M,G} (2048-15130623, default 15130623): Using default value 15130623

Command (m for help): p

Disk /dev/sdb: 7746 MB, 7746879488 bytes24 heads, 20 sectors/track, 31522 cylinders, total 15130624 sectorsUnits = sectors of 1 * 512 = 512 bytesSector size (logical/physical): 512 bytes / 512 bytesI/O size (minimum/optimal): 512 bytes / 512 bytesDisk identifier: 0x00000000

 Device Boot Start End Blocks Id System/dev/sdb1 2048 15130623 7564288 83 Linux

Command (m for help): tSelected partition 1Hex code (type L to list codes): cChanged system type of partition 1 to c (W95 FAT32 (LBA))

Command (m for help): aPartition number (1-4): 1

Command (m for help): p

Disk /dev/sdb: 7746 MB, 7746879488 bytes24 heads, 20 sectors/track, 31522 cylinders, total 15130624 sectorsUnits = sectors of 1 * 512 = 512 bytesSector size (logical/physical): 512 bytes / 512 bytesI/O size (minimum/optimal): 512 bytes / 512 bytesDisk identifier: 0x00000000

 Device Boot Start End Blocks Id System/dev/sdb1 * 2048 15130623 7564288 c W95 FAT32 (LBA)

Command (m for help): wThe partition table has been altered!

Calling ioctl() to re-read partition table.

WARNING: If you have created or modified any DOS 6.xpartitions, please see the fdisk manual page for additionalinformation.Syncing disks.

建立好新的分区之后需要命名并格式化:

1
# sudo mkfs.vfat -F 32 -n boot /dev/sdb1

格式化之后挂载磁盘并把 MLO 文件和 u-boot.img 文件拷贝进去:

123456789
# sudo mount /dev/sdb1 /media/jg/boot # sudo cp MLO /media/jg/boot/MLO# ls /media/jg/boot             MLO# sudo cp u-boot.img /media/jg/boot/u-boot.img# ls /media/jg/boot MLO u-boot.img# sync # sudo umount /media/jg/boot

接着把 SD 卡插入 Beaglebone Black 并且按着 S2 按钮上电,从串口打印出的信息我们可以看到 U-Boot 已经可以正常启动了:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
U-Boot SPL 2016.03-rc2-00084-g595af9d (Feb 29 2016 - 22:21:20)Trying to boot from MMCCard doesn't support part_switchMMC partition switch failed*** Warning - MMC partition switch failed, using default environment

reading u-boot.imgreading u-boot.img

U-Boot 2016.03-rc2-00084-g595af9d (Feb 29 2016 - 22:21:20 +0800)

 Watchdog enabledI2C: readyDRAM: 512 MiBMMC: OMAP SD/MMC: 0, OMAP SD/MMC: 1*** Warning - bad CRC, using default environment

Net: <ethaddr> not set. Validating first E-fuse MACcpsw, usb_etherPress SPACE to abort autoboot in 2 secondsswitch to partitions #0, OKmmc0 is current deviceScanning mmc 0:1...switch to partitions #0, OKmmc0 is current deviceSD/MMC found on device 0reading boot.scr** Unable to read file boot.scr **reading uEnv.txt** Unable to read file uEnv.txt **** File not found /boot/zImage **switch to partitions #0, OKmmc1(part 0) is current deviceScanning mmc 1:1...switch to partitions #0, OKmmc1(part 0) is current deviceSD/MMC found on device 1reading boot.scr** Unable to read file boot.scr **reading uEnv.txt** Unable to read file uEnv.txt **** File not found /boot/zImage **## Error: "bootcmd_nand0" not definedcpsw Waiting for PHY auto negotiation to complete......... TIMEOUT !BOOTP broadcast 1BOOTP broadcast 2BOOTP broadcast 3BOOTP broadcast 4

启动之后,前面一段打印信息是 MLO 程序打印出来的,读取 U-Boot 之后开始运行完整的 U-Boot,之后程序扫描各个设备读取 boot.scr 和 uEnv.txt 文件,接着再读取是否有 Linux 内核可以运行。

参考资料

  • Beaglebone Black——制作自己的SD启动卡
  • U-Boot on BeagleBone Black
  • AM335x U-Boot User’s Guide
  • Pandaboard bootload(uboot) 启动流程探究
  • u-boot启动流程

本作品除特别注明转载外均由 Jesse Guo 创作,采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可。修改,参照或者转载请注明出处。

转载于:https://www.cnblogs.com/tureno/articles/6650520.html

BeagleBone Black 从零到一 (2 MLO、U-Boot)_spl相关推荐

  1. Spring 官方修复零日漏洞,推出 Spring Boot 2.6.6、2.5.12 等新版本

    一.漏洞说明 这个漏洞还要从 3 月 29 日晚间说起! 彼时有不少网友爆料,Spring 框架出现 "史诗级" RCE 漏洞,平地一声雷,一时之间,快要入睡的开发者们纷纷坐起查看 ...

  2. Java零基础可以直接入门spring boot吗?

    不推荐.一般学习的路线是java基础,然后数据库,接着是spring,了解spring的核心思想,再学springmvc,之后才学springboot. 什么是Spring Boot Spring B ...

  3. Beaglebone Black教程BeagleBone Black安装最新系统映像

    Beaglebone Black教程BeagleBone Black安装最新系统映像 BeagleBone Black安装最新系统映像 Beaglebone Black虽然已经预装了Debian操作系 ...

  4. Beaglebone Black LCD 支持,BB VIEW配置

    1.   制作TF卡[PC上操作] Ø 打开官网http://beagleboard.org/latest-images,下载最新映像: https://debian.beagleboard.org/ ...

  5. openwrt (三)入门FAQ

    openwrt作为一个基于linux开发的比较完善的嵌入式系统,可以快速移植到各种平台上.初次下载开源代码后,简单浏览后很是诧异,居然没看到uboot和kernel部分的代码,甚至没看到任何模块的代码 ...

  6. AM335x-StarterWare用户手册(三)

    <编译启动裸机程序> 编译运行裸机程序本章介绍了四种方式,分别是仿真器.SD卡.nand flash和串口运行裸机程序,详细论述看下文的介绍. 3.1:准备工作 3.1.1:连接开发板和P ...

  7. 大星星学物联网概览篇-开发板

    7 开发板 选择板子的时候考虑:处理器速度.RAM.连网.USB.功耗.与传感器和其他电路的接口.物理尺寸和外形(芯片尺寸和装配的复杂性). 如果项目不涉及非常复杂的处理工作,例如只是需要有联网能力和 ...

  8. java web开源项目源码_适合Java新手的开源项目集合——在 GitHub 学编程

    作者:HelloGitHub-老荀 当今互联网份额最大的编程语言是哪一个?是 Java!这两年一直有听说 Java 要不行了.在走下坡路了.没错,Java 的确在走下坡路,未来的事情的确不好说,但是瘦 ...

  9. 8.2设备文件及磁盘分区

    2019独角兽企业重金招聘Python工程师标准>>> 概览: 设备文件的创建          权限    设备名 类型  主设备号 次设备号     mknod [-m MODE ...

  10. bootrom的类型

    bootrom有三种类型:ROM_RESIDENT.UMCOMPRESS和COMPRESS.第一种是一直运行在rom中的映象,只把data段拷贝到ram里面:第二种是非压缩方式的映象,data段和te ...

最新文章

  1. java怎么表示log2_Java程序员修炼之道 之 Logging(2/3) - 怎么写Log
  2. 使用XHProf分析PHP性能瓶颈(二)
  3. 常用的 Normalization 方法:BN、LN、IN、GN(附代码&链接)
  4. 网站快照更新不及时要怎样解决?
  5. C++ 计算并输出三角形的面积
  6. zookeeper做分布式锁
  7. C# 多线程修改控件时遇到:创建窗口句柄之前,不能在控件上调用 Invoke 或 BeginInvoke
  8. mysql 批量_mysql LOAD语句批量录入数据
  9. 网站QQ全屏PHP代码,QQ技术导航升级版 超级导航美化版带后台版 PHP源码
  10. Sightseeing Cows(POJ-3621)
  11. 老电脑慢得像蜗牛还有救吗?
  12. SpringBoot + Thymeleaf 之 HelloWorld
  13. npm install socket.io 提示缺少VCBuild.exe
  14. 瞬变抑制二极管工作原理、特性参数、封装形式
  15. 开发手机APP的一些心得体会
  16. GIS数据漫谈(六)— 投影坐标系统
  17. 致我们失去但美好回忆的青春
  18. HTTPS 到底加密了什么?
  19. 密码学数学基础,群,阿贝尔群,阶,双线性对,哈希函数,消息认证码概述
  20. POI操作excel基本使用

热门文章

  1. Python中的可变对象和不可变对象
  2. 【实践】人体红外传感器
  3. 知识付费的内容变现有哪些方式?
  4. 热搜第一!中国烟草总公司工资曝光,员工人均年收入超18万元!网友:简直是不锈钢饭碗!...
  5. mysql frm_mysqlfrm初步使用
  6. 【06】上海各个区的经纬度
  7. Suspense示例
  8. Excel技能培训之八合并计算,多区域合并计算,分类汇总,展开隐藏列
  9. 对多个Excel表中的数据进行合并计算
  10. 如何使用计算机上合并计算方法,Excel2019中合并计算的使用方法