技术标签: bootloader android ota Android A/B 系统 Android
Android从7.0开始引入新的OTA升级方式,A/B System Updates
,这里将其叫做A/B
系统,涉及的内容较多,分多篇对A/B
系统的各个方面进行分析。本文为第三篇,主系统和bootloader的通信。
本文为洛奇看世界(guyongqiangx)原创,转载请注明出处。
文章链接:https://blog.csdn.net/guyongqiangx/article/details/72480154
Android A/B 系统基础入门系列《Android A/B 系统》已完结,文章列表:
更多关于 Android OTA 升级相关文章,请参考《Android OTA 升级系列专栏文章导读》。
本文基于AOSP 7.1.1_r23 (NMF27D)
代码进行分析。
传统方式中,Android
主系统同bootloader
和recovery
系统通过存放于misc
分区的bootloader_message
结构进行通信。
struct bootloader_message {
char command[32];
char status[32];
char recovery[768];
/* The 'recovery' field used to be 1024 bytes. It has only ever
* been used to store the recovery command line, so 768 bytes
* should be plenty. We carve off the last 256 bytes to store the
* stage string (for multistage packages) and possible future
* expansion.*/
char stage[32];
char reserved[224];
};
android
系统或recovery
系统根据操作更新bootloader_message
的command
成员,并写入misc
分区;
bootloader
启动后读取misc
分区并解析得到bootloader_message
,根据command
内容选择相应的操作,command
可能的内容包括:
"update-radio/hboot"
bootloader
更新firmware
"boot-recovery"
bootloader
加载recovery
系统,进入recovery mode
A/B
系统的沟通机制boot_control
的接口定义A/B
系统中,指定了用于通信的HAL层boot_control
功能的定义,但没有指定通信数据具体的存储实现,这点有别于传统方式下AOSP
定义的存储于misc
分区的bootloader_message
结构。
HAL层的boot_control
,其定义位于文件中hardware/libhardware/include/hardware/boot_control.h
:
/**
* Every hardware module must have a data structure named HAL_MODULE_INFO_SYM
* and the fields of this data structure must begin with hw_module_t
* followed by module specific information.
*/
typedef struct boot_control_module {
struct hw_module_t common;
/*
* (*init)() perform any initialization tasks needed for the HAL.
* This is called only once.
*/
/* init 用于HAL初始化调用,仅启动时调用一次 */
void (*init)(struct boot_control_module *module);
/*
* (*getNumberSlots)() returns the number of available slots.
* For instance, a system with a single set of partitions would return
* 1, a system with A/B would return 2, A/B/C -> 3...
*/
/* 返回系统slot分区套数,1套slot包含boot, system和vendor分区 */
unsigned (*getNumberSlots)(struct boot_control_module *module);
/*
* (*getCurrentSlot)() returns the value letting the system know
* whether the current slot is A or B. The meaning of A and B is
* left up to the implementer. It is assumed that if the current slot
* is A, then the block devices underlying B can be accessed directly
* without any risk of corruption.
* The returned value is always guaranteed to be strictly less than the
* value returned by getNumberSlots. Slots start at 0 and
* finish at getNumberSlots() - 1
*/
/* 返回系统当前所在的slot位置 */
unsigned (*getCurrentSlot)(struct boot_control_module *module);
/*
* (*markBootSuccessful)() marks the current slot
* as having booted successfully
*
* Returns 0 on success, -errno on error.
*/
/* 标记当前slot为已经成功启动 */
int (*markBootSuccessful)(struct boot_control_module *module);
/*
* (*setActiveBootSlot)() marks the slot passed in parameter as
* the active boot slot (see getCurrentSlot for an explanation
* of the "slot" parameter). This overrides any previous call to
* setSlotAsUnbootable.
* Returns 0 on success, -errno on error.
*/
/* 标记指定slot为可启动 */
int (*setActiveBootSlot)(struct boot_control_module *module, unsigned slot);
/*
* (*setSlotAsUnbootable)() marks the slot passed in parameter as
* an unbootable. This can be used while updating the contents of the slot's
* partitions, so that the system will not attempt to boot a known bad set up.
* Returns 0 on success, -errno on error.
*/
/* 标记指定slot为不可启动 */
int (*setSlotAsUnbootable)(struct boot_control_module *module, unsigned slot);
/*
* (*isSlotBootable)() returns if the slot passed in parameter is
* bootable. Note that slots can be made unbootable by both the
* bootloader and by the OS using setSlotAsUnbootable.
* Returns 1 if the slot is bootable, 0 if it's not, and -errno on
* error.
*/
/* 返回指定slot是否可启动 */
int (*isSlotBootable)(struct boot_control_module *module, unsigned slot);
/*
* (*getSuffix)() returns the string suffix used by partitions that
* correspond to the slot number passed in parameter. The returned string
* is expected to be statically allocated and not need to be freed.
* Returns NULL if slot does not match an existing slot.
*/
/* 返回指定slot的系统分区后缀,例如“_a”/“_b”等 */
const char* (*getSuffix)(struct boot_control_module *module, unsigned slot);
/*
* (*isSlotMarkedSucessful)() returns if the slot passed in parameter has
* been marked as successful using markBootSuccessful.
* Returns 1 if the slot has been marked as successful, 0 if it's
* not the case, and -errno on error.
*/
/* 返回指定slot是否已经标记为成功启动 */
int (*isSlotMarkedSuccessful)(struct boot_control_module *module, unsigned slot);
void* reserved[31];
} boot_control_module_t;
boot_control
的存储和功能实现对于boot_control
,AOSP
仅定义了其功能接口,并没有提供具体的代码实现,各厂家根据这个头文件,自定义其存储和功能实现。
使用grep
工具搜索代码中的boot_control
关键字,可以发现AOSP
代码里面包含了三个平台的boot_control
实现:
Google
平台的Brillo
Intel
平台的edison
QualComm
Google
平台Brillo
的实现AOSP
代码中,system\extra\boot_control_copy
定义了bootctrl.default
实现:
$ ls -lh system/extras/boot_control_copy/
total 36K
-rw-r--r-- 1 ygu users 458 Mar 31 08:50 Android.mk
-rw-r--r-- 1 ygu users 11K Mar 31 08:50 NOTICE
-rw-r--r-- 1 ygu users 7.7K Mar 31 08:50 boot_control_copy.c
-rw-r--r-- 1 ygu users 5.1K Mar 31 08:50 bootinfo.c
-rw-r--r-- 1 ygu users 2.0K Mar 31 08:50 bootinfo.h
各文件的内容如下:
bootinfo.h
定义了结构体BrilloSlotInfo
和BrilloBootInfo
BrilloBootInfo
包含结构体BrilloBootInfo
,作为boot_control
的私有数据实现,定义如下:
typedef struct BrilloSlotInfo {
uint8_t bootable : 1;
uint8_t reserved[3];
} BrilloSlotInfo;
typedef struct BrilloBootInfo {
// Used by fs_mgr. Must be NUL terminated.
char bootctrl_suffix[4];
// Magic for identification - must be 'B', 'C', 'c' (short for
// "boot_control copy" implementation).
uint8_t magic[3];
// Version of BrilloBootInfo struct, must be 0 or larger.
uint8_t version;
// Currently active slot.
uint8_t active_slot;
// Information about each slot.
BrilloSlotInfo slot_info[2];
uint8_t reserved[15];
} BrilloBootInfo;
结构体BrilloBootInfo
占用32字节,系统复用misc
分区的bootloader_message
结构体,将BrilloBootInfo
存放在偏移量为864字节的成员slot_suffix[32]
中,整个misc
分区数据结构的框图如下:
bootinfo.c
实现了对BrilloBootInfo
进行存取操作的接口
bool boot_info_load(BrilloBootInfo *out_info)
bool boot_info_save(BrilloBootInfo *info)
bool boot_info_validate(BrilloBootInfo* info)
void boot_info_reset(BrilloBootInfo* info)
int boot_info_open_partition(const char *name, uint64_t *out_size, int flags)
boot_control_copy.c
实现了boot_control
模块的功能
/* This boot_control HAL implementation emulates A/B by copying the
* contents of the boot partition of the requested slot to the boot
* partition. It hence works with bootloaders that are not yet aware
* of A/B. This code is only intended to be used for development.
*/
boot_control_module_t HAL_MODULE_INFO_SYM = {
.common = {
.tag = HARDWARE_MODULE_TAG,
.module_api_version = BOOT_CONTROL_MODULE_API_VERSION_0_1,
.hal_api_version = HARDWARE_HAL_API_VERSION,
.id = BOOT_CONTROL_HARDWARE_MODULE_ID,
.name = "Copy Implementation of boot_control HAL",
.author = "The Android Open Source Project",
.methods = &module_methods,
},
.init = module_init,
.getNumberSlots = module_getNumberSlots,
.getCurrentSlot = module_getCurrentSlot,
.markBootSuccessful = module_markBootSuccessful,
.setActiveBootSlot = module_setActiveBootSlot,
.setSlotAsUnbootable = module_setSlotAsUnbootable,
.isSlotBootable = module_isSlotBootable,
.getSuffix = module_getSuffix,
};
代码实现了boot_control_module_t
模块接口的功能,这里不再对每一个函数实现进行注释,但需要特别指出的是,函数module_setActiveBootSlot
内部会根据传入的slot
参数将对应分区boot_X
内容复制到boot
分区(系统上应该存在三个分区,如boot
,boot_a
和boot_b
),bootloader
不需要改动代码去检查到底是从哪个分区启动,只管加载boot
分区就好了,带来的问题就是,一旦启动失败(例如,kernel
挂载system
分区失败,根本没有进入Android
环境),bootloader
无法切换到另外一个slot
。注释中也提到,这种方式不需要修改bootloader
,其代码实现只是用于开发目的,最终产品不应该是这样的。
Intel
平台edison
的实现AOSP
代码中,hardware\bsp\intel\soc\common\bootctrl
定义了bootctrl.edison
的实现:
$ ls -lh hardware/bsp/intel/soc/common/bootctrl/
total 20K
-rw-r--r-- 1 ygu users 860 Mar 31 08:47 Android.mk
-rw-r--r-- 1 ygu users 9.1K Mar 31 08:47 bootctrl.c
-rw-r--r-- 1 ygu users 1.5K Mar 31 08:47 bootctrl.h
各文件的内容如下:
bootctrl.h
定义了结构体slot_metadata_t
和boot_ctrl_t
boot_ctrl_t
包含结构体slot_metadata_t
,作为boot_control
的私有数据实现,定义如下:
#define BOOT_CONTROL_VERSION 1
typedef struct slot_metadata {
uint8_t priority : 4;
uint8_t tries_remaining : 3;
uint8_t successful_boot : 1;
} slot_metadata_t;
typedef struct boot_ctrl {
/* Magic for identification - '\0ABB' (Boot Contrl Magic) */
uint32_t magic;
/* Version of struct. */
uint8_t version;
/* Information about each slot. */
slot_metadata_t slot_info[2];
uint8_t recovery_tries_remaining;
} boot_ctrl_t;
跟Brillo
类似,系统复用misc
分区的bootloader_message
结构体,将boot_ctrl_t
存放在偏移量为864字节的成员slot_suffix[32]
中,整个misc
分区数据结构的框图如下:
bootctrl.c
实现了boot_ctrl_t
存取操作和boot_control
的模块功能
boot_ctrl_t
存取操作
int bootctrl_read_metadata(boot_ctrl_t *bctrl)
int bootctrl_write_metadata(boot_ctrl_t *bctrl)
boot_control
模块功能
/* Boot Control Module implementation */
boot_control_module_t HAL_MODULE_INFO_SYM = {
.common = {
.tag = HARDWARE_MODULE_TAG,
.module_api_version = BOOT_CONTROL_MODULE_API_VERSION_0_1,
.hal_api_version = HARDWARE_HAL_API_VERSION,
.id = BOOT_CONTROL_HARDWARE_MODULE_ID,
.name = "boot_control HAL",
.author = "Intel Corporation",
.methods = &bootctrl_methods,
},
.init = bootctrl_init,
.getNumberSlots = bootctrl_get_number_slots,
.getCurrentSlot = bootctrl_get_current_slot,
.markBootSuccessful = bootctrl_mark_boot_successful,
.setActiveBootSlot = bootctrl_set_active_boot_slot,
.setSlotAsUnbootable = bootctrl_set_slot_as_unbootable,
.isSlotBootable = bootctrl_is_slot_bootable,
.getSuffix = bootctrl_get_suffix,
};
由于没有bootloader
的代码,所以对于如何通过结构体slot_metadata_t
的成员priority
和priority
来选择启动哪一个slot
并不清楚,无法对结构体成员的作用有更详细的说明。
值得一提的是,通过读取linux
命令行参数androidboot.slot_suffix=
来确定当前系统在哪一个slot
上运行(见bootctrl_get_active_slot
函数)。
QualComm
平台的实现AOSP
代码中,hardware\qcom\bootctrl
定义了bootctrl.$(TARGET_BOARD_PLATFORM)
的实现(具体名字依赖于TARGET_BOARD_PLATFORM
变量设定):
$ ls -lh hardware/qcom/bootctrl/
total 28K
-rw-r--r-- 1 ygu users 944 Mar 31 08:47 Android.mk
-rw-r--r-- 1 ygu users 1.5K Mar 31 08:47 NOTICE
-rw-r--r-- 1 ygu users 19K Mar 31 08:47 boot_control.cpp
QualComm
平台的实现比较特别,没有单独定义boot_control
的私有数据,而是将A/B
系统相关信息存放到gpt
表上。
从GPT
内容的第3个逻辑块LBA 2
开始,依次存放的是每个GPT
分区的详细信息Partition Entry
,单个Partition Entry
占用128个字节,从其第48个字节开始存放的是分区属性(Attribute flags
)。A/B
系统将每个slot
分区的信息,存放到分区属性的Bit 48
开始的位置上。
QualComm
平台详细的A/B
系统分区属性如下:
关于
GPT
分区的详细信息,可以参考另外一篇文章:<<博通机顶盒平台GPT分区和制作工具>>的第1部分,关于GPT的介绍。
在代码实现中比较特别的是:
boot
开头的分区数作为slot
总数(见get_number_slots
函数)ro.boot.slot_suffix
来确定当前系统在哪一个slot
上运行(见get_current_slot
函数)Broadcom
机顶盒平台的实现在Broadcom
单独提供的代码中(非AOSP
代码),vendor/broadcom/bcm_platform/hals/boot_control
定义了bootctrl.$(TARGET_BOARD_PLATFORM)
的实现(如bootctrl.bcm7252ssffdr4
):
$ ls -lh vendor/broadcom/bcm_platform/hals/boot_control/
total 20K
-rw-r--r-- 1 ygu users 1.3K Mar 30 16:09 Android.mk
-rw-r--r-- 1 ygu users 11K May 6 16:26 boot_control.cpp
-rw-r--r-- 1 ygu users 1.1K Mar 30 16:09 eio_boot.h
eio_boot.h
定义了结构体eio_boot_slot
和eio_boot
eio_boot
包含结构体eio_boot_slot
,作为boot_control
的私有数据实现,定义如下:
struct eio_boot_slot {
char suffix[8];
int valid;
int boot_try;
int boot_ok;
int boot_fail;
};
struct eio_boot {
int magic;
int current;
struct eio_boot_slot slot[2];
};
结构体eio_boot
的数据存放在名为eio
的分区上。
Broadcom
机顶盒平台eio_boot
结构框图如下:
boot_control.cpp
实现了eio_boot
存取操作和boot_control
的模块功能
struct boot_control_module HAL_MODULE_INFO_SYM = {
.common = {
.tag = HARDWARE_MODULE_TAG,
.module_api_version = BOOT_CONTROL_MODULE_API_VERSION_0_1,
.hal_api_version = HARDWARE_HAL_API_VERSION,
.id = BOOT_CONTROL_HARDWARE_MODULE_ID,
.name = "boot control hal for bcm platform",
.author = "Broadcom",
.methods = &boot_control_module_methods,
.dso = 0,
.reserved = {0}
},
.init = init,
.getNumberSlots = getNumberSlots,
.getCurrentSlot = getCurrentSlot,
.markBootSuccessful = markBootSuccessful,
.setActiveBootSlot = setActiveBootSlot,
.setSlotAsUnbootable = setSlotAsUnbootable,
.isSlotBootable = isSlotBootable,
.getSuffix = getSuffix,
.isSlotMarkedSuccessful = isSlotMarkedSuccessful,
};
Broadcom
平台的分区后缀名不同于常见的_a/_b
,而是采用_i/_e
,这里略去对函数内容的注释。
boot_control
的测试工具除了定义HAL层的接口外,AOSP
也提供了boot_control
模块调用的工具bootctl
,位于:
system/extras/bootctl/bootctl.c
默认情况下,bootctl
不会参与编译,可以在包含update_engine
是将其添加到PRODUCT_PACKAGES
,如下:
PRODUCT_PACKAGES += \
update_engine \
update_verifier \
bootctl
bootctl
工具很简单,通过命令行调用boot_control
的功能接口,以下是在Broadcom参考平台上运行bootctl
的例子:
bcm7252ssffdr4:/ $ su
bcm7252ssffdr4:/ # which bootctl
/system/bin/bootctl
bcm7252ssffdr4:/ # bootctl --help
bootctl - command-line wrapper for the boot_control HAL.
Usage:
bootctl COMMAND
Commands:
bootctl hal-info - Show info about boot_control HAL used.
bootctl get-number-slots - Prints number of slots.
bootctl get-current-slot - Prints currently running SLOT.
bootctl mark-boot-successful - Mark current slot as GOOD.
bootctl set-active-boot-slot SLOT - On next boot, load and execute SLOT.
bootctl set-slot-as-unbootable SLOT - Mark SLOT as invalid.
bootctl is-slot-bootable SLOT - Returns 0 only if SLOT is bootable.
bootctl is-slot-marked-successful SLOT - Returns 0 only if SLOT is marked GOOD.
bootctl get-suffix SLOT - Prints suffix for SLOT.
SLOT parameter is the zero-based slot-number.
64|bcm7252ssffdr4:/ #
64|bcm7252ssffdr4:/ # bootctl hal-info
HAL name: boot control hal for bcm platform
HAL author: Broadcom
HAL module version: 0.1
bcm7252ssffdr4:/ # bootctl get-number-slots
2
bcm7252ssffdr4:/ # bootctl get-current-slot
0
bcm7252ssffdr4:/ # bootctl get-suffix 0
_i
bcm7252ssffdr4:/ # bootctl get-suffix 1
_e
最后的bootctl get-suffix
调用可以看到,在我的测试平台上,slot A
和slot B
的分区命名后缀分别为_i
和_e
。
基于bootctl
的基础上,Android
系统提供了两个基于Brillo
平台的测试代码,分别位于以下路径:
system/extras/tests/bootloader
external/autotest/server/site_tests/brillo_BootLoader
后续打算写一篇博客来单独介绍如何在Android下运行这些测试例子进行单元测试。
boot_control
的调用bootloader
读取boot_control
私有实现的数据设备启动后bootloader
会读取boot_control
私有实现的数据,来判断从哪一个slot
启动,由于各家实现的私有数据结构不一样,所以无法详细说明如何解析和处理的过程。
boot_control_android
调用boot_control
文件system/update_engine/boot_control_android.cc
中,类BootControlAndroid
有一个私有成员module_
:
// The Android implementation of the BootControlInterface. This implementation
// uses the libhardware's boot_control HAL to access the bootloader.
class BootControlAndroid : public BootControlInterface {
...
private:
// NOTE: There is no way to release/unload HAL implementations so
// this is essentially leaked on object destruction.
boot_control_module_t* module_;
...
};
在BootControlAndroid
的Init
方法内,获取boot_control_module_t
模块指针并赋值给module_
成员,然后调用module_->init
进行boot_control
的初始化,如下:
bool BootControlAndroid::Init() {
const hw_module_t* hw_module;
int ret;
#ifdef _UE_SIDELOAD
// For update_engine_sideload, we simulate the hw_get_module() by accessing it
// from the current process directly.
# 对于update_engine_sideload应用,直接将HAL_MODULE_INFO_SYM转换为hw_module
hw_module = &HAL_MODULE_INFO_SYM;
ret = 0;
if (!hw_module ||
strcmp(BOOT_CONTROL_HARDWARE_MODULE_ID, hw_module->id) != 0) {
ret = -EINVAL;
}
#else // !_UE_SIDELOAD
# 对于update_engine应用,通过BOOT_CONTROL_HARDWARE_MODULE_ID获取hw_module
ret = hw_get_module(BOOT_CONTROL_HARDWARE_MODULE_ID, &hw_module);
#endif // _UE_SIDELOAD
if (ret != 0) {
LOG(ERROR) << "Error loading boot_control HAL implementation.";
return false;
}
# 通过hw_module得到boot_control_module_t,从而后面可以愉快地调用其各种功能实现函数
module_ = reinterpret_cast<boot_control_module_t*>(const_cast<hw_module_t*>(hw_module));
# 调用boot_control的init函数
module_->init(module_);
LOG(INFO) << "Loaded boot_control HAL "
<< "'" << hw_module->name << "' "
<< "version " << (hw_module->module_api_version>>8) << "."
<< (hw_module->module_api_version&0xff) << " "
<< "authored by '" << hw_module->author << "'.";
return true;
}
初始化完成后,就可以通过module_
成员来调用各种boot_control
的操作了。
update_verifier
调用boot_control
文件bootable/recovery/update_verifier/update_verifier.cpp
中,获取boot_control_module_t
指针,检查当前slot
分区是否已经标记为successful
,如果没有,则尝试verify_image
并将当前slot
标记为successful
,具体代码如下:
int main(int argc, char** argv) {
...
# 直接根据名称"bootctrl"获取模块
const hw_module_t* hw_module;
if (hw_get_module("bootctrl", &hw_module) != 0) {
SLOGE("Error getting bootctrl module.\n");
return -1;
}
# 将"bootctrl"模块转化为"boot_control_module_t"结构体
boot_control_module_t* module = reinterpret_cast<boot_control_module_t*>(
const_cast<hw_module_t*>(hw_module));
# 调用init
module->init(module);
# 获取当前slot
unsigned current_slot = module->getCurrentSlot(module);
# 检查当前slot是否标记为successful
int is_successful= module->isSlotMarkedSuccessful(module, current_slot);
SLOGI("Booting slot %u: isSlotMarkedSuccessful=%d\n", current_slot, is_successful);
# 如果当前slot没有标记为successful,说明当前启动可能存在问题
if (is_successful == 0) {
// The current slot has not booted successfully.
# 检查"ro.boot.verifymode",是否其它原因导致失败
# 不是其它原因导致失败的情况下,重新调用verify_image验证
...
# verify_image验证成功,尝试标记当前slot为successful
int ret = module->markBootSuccessful(module);
if (ret != 0) {
SLOGE("Error marking booted successfully: %s\n", strerror(-ret));
return -1;
}
SLOGI("Marked slot %u as booted successfully.\n", current_slot);
}
# 完成操作,退出update_verifier
SLOGI("Leaving update_verifier.\n");
return 0;
}
整个A/B
系统中,基于boot_control
的上层应用操作已经实现了,各家需要单独实现boot_control
的底层操作,同时bootloader
也需要配合解析boot_control
的私有数据,从而选择相应的slot
来启动Android
系统。
到目前为止,我写过 Android OTA 升级相关的话题包括:
更多这些关于 Android OTA 升级相关文章的内容,请参考《Android OTA 升级系列专栏文章导读》。
如果您已经订阅了动态分区和虚拟分区付费专栏,请务必加我微信,备注订阅账号,拉您进“动态分区 & 虚拟分区专栏 VIP 答疑群”。我会在方便的时候,回答大家关于 A/B 系统、动态分区、虚拟分区、各种 OTA 升级和签名的问题。
除此之外,我有一个 Android OTA 升级讨论群,里面现在有 400+ 朋友,主要讨论手机,车机,电视,机顶盒,平板等各种设备的 OTA 升级话题,如果您从事 OTA 升级工作,欢迎加群一起交流,请在加我微信时注明“Android OTA 讨论组”。此群仅限 Android OTA 开发者参与~
公众号“洛奇看世界”后台回复“wx”获取个人微信。
文章浏览阅读290次,点赞8次,收藏10次。1.背景介绍稀疏编码是一种用于处理稀疏数据的编码技术,其主要应用于信息传输、存储和处理等领域。稀疏数据是指数据中大部分元素为零或近似于零的数据,例如文本、图像、音频、视频等。稀疏编码的核心思想是将稀疏数据表示为非零元素和它们对应的位置信息,从而减少存储空间和计算复杂度。稀疏编码的研究起源于1990年代,随着大数据时代的到来,稀疏编码技术的应用范围和影响力不断扩大。目前,稀疏编码已经成为计算...
文章浏览阅读217次。EasyGBS - GB28181 国标方案安装使用文档下载安装包下载,正式使用需商业授权, 功能一致在线演示在线API架构图EasySIPCMSSIP 中心信令服务, 单节点, 自带一个 Redis Server, 随 EasySIPCMS 自启动, 不需要手动运行EasySIPSMSSIP 流媒体服务, 根..._easygbs-windows-2.6.0-23042316使用文档
文章浏览阅读1.2k次,点赞27次,收藏7次。2023巅峰极客 BabyURL之前AliyunCTF Bypassit I这题考查了这样一条链子:其实就是Jackson的原生反序列化利用今天复现的这题也是大同小异,一起来整一下。_原生jackson 反序列化链子
文章浏览阅读734次,点赞9次,收藏7次。微服务架构简单的说就是将单体应用进一步拆分,拆分成更小的服务,每个服务都是一个可以独立运行的项目。这么多小服务,如何管理他们?(服务治理 注册中心[服务注册 发现 剔除])这么多小服务,他们之间如何通讯?这么多小服务,客户端怎么访问他们?(网关)这么多小服务,一旦出现问题了,应该如何自处理?(容错)这么多小服务,一旦出现问题了,应该如何排错?(链路追踪)对于上面的问题,是任何一个微服务设计者都不能绕过去的,因此大部分的微服务产品都针对每一个问题提供了相应的组件来解决它们。_spring cloud
文章浏览阅读5.9k次,点赞6次,收藏20次。Js实现图片点击切换与轮播图片点击切换<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title></title> <script type="text/ja..._点击图片进行轮播图切换
文章浏览阅读10w+次,点赞245次,收藏1.5k次。在开始安装前,如果你的电脑装过tensorflow,请先把他们卸载干净,包括依赖的包(tensorflow-estimator、tensorboard、tensorflow、keras-applications、keras-preprocessing),不然后续安装了tensorflow-gpu可能会出现找不到cuda的问题。cuda、cudnn。..._tensorflow gpu版本安装
文章浏览阅读243次。0x00 简介权限滥用漏洞一般归类于逻辑问题,是指服务端功能开放过多或权限限制不严格,导致攻击者可以通过直接或间接调用的方式达到攻击效果。随着物联网时代的到来,这种漏洞已经屡见不鲜,各种漏洞组合利用也是千奇百怪、五花八门,这里总结漏洞是为了更好地应对和预防,如有不妥之处还请业内人士多多指教。0x01 背景2014年4月,在比特币飞涨的时代某网站曾经..._使用物联网漏洞的使用者
文章浏览阅读786次。A. Epipolar geometry and triangulationThe epipolar geometry mainly adopts the feature point method, such as SIFT, SURF and ORB, etc. to obtain the feature points corresponding to two frames of images. As shown in Figure 1, let the first image be and th_normalized plane coordinates
文章浏览阅读708次,点赞2次,收藏3次。开放信息抽取(OIE)系统(三)-- 第二代开放信息抽取系统(人工规则, rule-based, 先关系再实体)一.第二代开放信息抽取系统背景 第一代开放信息抽取系统(Open Information Extraction, OIE, learning-based, 自学习, 先抽取实体)通常抽取大量冗余信息,为了消除这些冗余信息,诞生了第二代开放信息抽取系统。二.第二代开放信息抽取系统历史第二代开放信息抽取系统着眼于解决第一代系统的三大问题: 大量非信息性提取(即省略关键信息的提取)、_语义角色增强的关系抽取
文章浏览阅读1.1w次,点赞6次,收藏51次。快速完成网页设计,10个顶尖响应式HTML5网页模板助你一臂之力为了寻找一个优质的网页模板,网页设计师和开发者往往可能会花上大半天的时间。不过幸运的是,现在的网页设计师和开发人员已经开始共享HTML5,Bootstrap和CSS3中的免费网页模板资源。鉴于网站模板的灵活性和强大的功能,现在广大设计师和开发者对html5网站的实际需求日益增长。为了造福大众,Mockplus的小伙伴整理了2018年最..._html欢迎页面
文章浏览阅读282次。原标题:2018全国计算机等级考试调整,一、二级都增加了考试科目全国计算机等级考试将于9月15-17日举行。在备考的最后冲刺阶段,小编为大家整理了今年新公布的全国计算机等级考试调整方案,希望对备考的小伙伴有所帮助,快随小编往下看吧!从2018年3月开始,全国计算机等级考试实施2018版考试大纲,并按新体系开考各个考试级别。具体调整内容如下:一、考试级别及科目1.一级新增“网络安全素质教育”科目(代..._计算机二级增报科目什么意思
文章浏览阅读240次。conan简单使用。_apt install conan