OpenHarmony移植案例:如何適配服務啟動引導部件bootstrap_lite
摘要:本文介紹了startup子系統之bootstrap_lite服務啟動引導部件的移植適配案例及原理。
本文分享自華為雲社群《OpenHarmony移植案例與原理 - startup子系統之bootstrap_lite服務啟動引導部件》,作者:zhushy。
bootstrap_lite服務啟動引導元件提供了各服務和功能的啟動入口標識。在SAMGR(System ability manager,系統服務管理)啟動時,會呼叫bootstrap_lite標識的入口函式,並啟動系統服務。本文介紹下移植開發板時如何適配服務啟動引導部件bootstrap_lite,並介紹下相關的執行機制原理。bootstrap_lite啟動引導部件定義在build\lite\components\startup.json。bootstrap_lite啟動引導部件原始碼目錄如下:
base/startup/bootstrap_lite/ # 啟動引導元件
└── services
└── source # 啟動引導元件原始檔目錄
1、bootstrap_lite服務啟動引導部件適配示例
1.1 產品解決方案配置啟用部件
移植開發板適配startup子系統之bootstrap_lite服務啟動引導部件時,需要在產品解決方案的config.json增加下述配置項,可以參考vendor\bestechnic\display_demo\config.json中的配置示例:
{
"subsystem": "startup",
"components": [
{
"component": "bootstrap_lite"
},
......
]
},
1.2 使用bootstrap服務啟動部件提供的初始化巨集函式
然後就可以使用bootstrap服務啟動部件提供的初始化巨集函式SYS_SERVICE_INIT、APP_SERVICE_INIT等來自動初始化服務,示例程式碼可以參考device\board\fnlink\v200zr\liteos_m\at\at_wifi.c中的用法,片段如下,可以看到呼叫了巨集函式來初始化RegisterCustomATCmd函式實現的服務。device\board\bearpi\bearpi_hm_nano\app\目錄下有更多的使用示例。下文分析實現機制原理。
static void RegisterCustomATCmd()
{
cmd_tbl_t cmd_list[] = {
{"AT+IFCFG", 8, at_lwip_ifconfig, "AT+IFCFG - ifconfig\n"},
{"AT+STARTAP", 7, at_start_softap, "AT+STARTAP - start wifi softap\n"},
{"AT+STOPAP", 1, at_stop_softap, "AT+STOPAP - stop wifi softap\n"},
{"AT+STARTSTA", 1, at_start_wifista, "AT+STARTSTA - start wifi sta\n"},
{"AT+STOPSTA", 1, at_stop_wifista, "AT+STOPSTA - stop wifi sta\n"},
{"AT+DHCP", 3, at_setup_dhcp, "AT+DHCP - dhcp\n"},
{"AT+DHCPS", 3, at_setup_dhcps, "AT+DHCPS - dhcps\n"},
};
for (int i = 0; i < sizeof(cmd_list) / sizeof(cmd_tbl_t); i++) {
console_cmd_add(&cmd_list[i]);
}
}
SYS_SERVICE_INIT(RegisterCustomATCmd);
1.3 連結指令碼中增加zInit程式碼段
適配bootstrap_lite部件時,還需要在連結指令碼檔案中手動新增如下段,連結指令碼示例可以參考//device/soc/bestechnic/bes2600/liteos_m/sdk/bsp/out/best2600w_liteos/_best2001.lds,還可以參考device\soc\hisilicon\hi3861v100\sdk_liteos\build\link\link.ld.S。從連結指令碼片段中可以看出,有.zinitcall.bsp、.zinitcall.device、.zinitcall.core、.zinitcall.sys.service、.zinitcall.sys.feature、.zinitcall.run、.zinitcall.app.service、.zinitcall.app.feature、.zinitcall.test和.zinitcall.exit等幾種型別的段。
/* zInit code and data - will be freed after init */
.zInit (.) :
{
__zinitcall_bsp_start = .;
KEEP (*(.zinitcall.bsp0.init))
KEEP (*(.zinitcall.bsp1.init))
KEEP (*(.zinitcall.bsp2.init))
KEEP (*(.zinitcall.bsp3.init))
KEEP (*(.zinitcall.bsp4.init))
__zinitcall_bsp_end = .;
. = ALIGN(4);
__zinitcall_device_start = .;
KEEP (*(.zinitcall.device0.init))
KEEP (*(.zinitcall.device1.init))
KEEP (*(.zinitcall.device2.init))
KEEP (*(.zinitcall.device3.init))
KEEP (*(.zinitcall.device4.init))
__zinitcall_device_end = .;
. = ALIGN(4);
__zinitcall_core_start = .;
KEEP (*(.zinitcall.core0.init))
KEEP (*(.zinitcall.core1.init))
KEEP (*(.zinitcall.core2.init))
KEEP (*(.zinitcall.core3.init))
KEEP (*(.zinitcall.core4.init))
__zinitcall_core_end = .;
. = ALIGN(4);
__zinitcall_sys_service_start = .;
KEEP (*(.zinitcall.sys.service0.init))
KEEP (*(.zinitcall.sys.service1.init))
KEEP (*(.zinitcall.sys.service2.init))
KEEP (*(.zinitcall.sys.service3.init))
KEEP (*(.zinitcall.sys.service4.init))
__zinitcall_sys_service_end = .;
. = ALIGN(4);
__zinitcall_sys_feature_start = .;
KEEP (*(.zinitcall.sys.feature0.init))
KEEP (*(.zinitcall.sys.feature1.init))
KEEP (*(.zinitcall.sys.feature2.init))
KEEP (*(.zinitcall.sys.feature3.init))
KEEP (*(.zinitcall.sys.feature4.init))
__zinitcall_sys_feature_end = .;
. = ALIGN(4);
__zinitcall_run_start = .;
KEEP (*(.zinitcall.run0.init))
KEEP (*(.zinitcall.run1.init))
KEEP (*(.zinitcall.run2.init))
KEEP (*(.zinitcall.run3.init))
KEEP (*(.zinitcall.run4.init))
__zinitcall_run_end = .;
. = ALIGN(4);
__zinitcall_app_service_start = .;
KEEP (*(.zinitcall.app.service0.init))
KEEP (*(.zinitcall.app.service1.init))
KEEP (*(.zinitcall.app.service2.init))
KEEP (*(.zinitcall.app.service3.init))
KEEP (*(.zinitcall.app.service4.init))
__zinitcall_app_service_end = .;
. = ALIGN(4);
__zinitcall_app_feature_start = .;
KEEP (*(.zinitcall.app.feature0.init))
KEEP (*(.zinitcall.app.feature1.init))
KEEP (*(.zinitcall.app.feature2.init))
KEEP (*(.zinitcall.app.feature3.init))
KEEP (*(.zinitcall.app.feature4.init))
__zinitcall_app_feature_end = .;
. = ALIGN(4);
__zinitcall_test_start = .;
KEEP (*(.zinitcall.test0.init))
KEEP (*(.zinitcall.test1.init))
KEEP (*(.zinitcall.test2.init))
KEEP (*(.zinitcall.test3.init))
KEEP (*(.zinitcall.test4.init))
__zinitcall_test_end = .;
. = ALIGN(4);
__zinitcall_exit_start = .;
KEEP (*(.zinitcall.exit0.init))
KEEP (*(.zinitcall.exit1.init))
KEEP (*(.zinitcall.exit2.init))
KEEP (*(.zinitcall.exit3.init))
KEEP (*(.zinitcall.exit4.init))
__zinitcall_exit_end = .;
. = ALIGN(4);
} > FLASH
1.4 配置編譯時連結bootstrap庫
另外,bootstrap_lite部件會編譯//base/startup/bootstrap_lite/services/source/bootstrap_service.c,該檔案中,通過SYS_SERVICE_INIT將Init函式符號指定到__zinitcall_sys_service_start和__zinitcall_sys_service_end段中,由於沒有顯式呼叫Init函式,所以需要將它強制連結到最終的映象。可以參考device\board\goodix\gr5515_sk\liteos_m\config.gni檔案中的連結選項。恆玄的開發板適配時,是配置到vendor\bestechnic\display_demo\config.json檔案中的自己定義的配置項force_link_libs裡,該配置項在device\soc\bestechnic\bes2600\BUILD.gn中被解析、連結。
board_ld_flags = [
....
"-lbootstrap",
1.5 呼叫OHOS_SystemInit介面
函式void OHOS_SystemInit(void)定義在檔案base\startup\bootstrap_lite\services\source\system_init.c中,在移植適配時,需要呼叫該介面。可以參考檔案device\soc\bestechnic\bes2600\liteos_m\sdk\bsp\rtos\liteos\liteos_m\board.c中的使用示例。
int main(void);
extern void OHOS_SystemInit(void);
......
OHOS_SystemInit();
......
while (1) {
osDelay(1000);
TRACE(0, "main idle");
}
}
2、bootstrap_lite服務啟動引導部件實現原理之system_init
bootstrap_lite服務啟動部件實現了服務的自動初始化,即服務的初始化函式無需顯式呼叫,它是使用巨集定義的方式申明,在系統啟動時自動被執行。實現原理是將服務啟動的函式通過巨集申明之後,放在預定義好的zInit程式碼段中,系統啟動的時候呼叫OHOS_SystemInit介面,遍歷該程式碼段並呼叫其中的函式。因此在適配移植時,需要在device/soc/下面具體的晶片的連結指令碼中新增zInit段,並且在main函式裡呼叫OHOS_SystemInit介面。
2.1 bootstrap_lite服務啟動引導部件的初始化巨集
bootstrap_lite服務啟動引導部件的初始化巨集定義在檔案utils\native\lite\include\ohos_init.h,片段如下。初始化函式巨集SYS_SERVICE_INIT(func)用於標識核心系統服務的初始化入口,該巨集識別的函式在啟動過程中核心繫統服務優先順序2階段被呼叫;初始化巨集SYS_SERVICE_INIT_PRI(func, priority)可以指定優先順序數值,優先順序的取值範圍為[0,5),呼叫順序為0, 1, 2, 3, 4。
/**
* @brief Identifies the entry for initializing and starting a core system service by the
* priority 2.
*
* This macro is used to identify the entry called at the priority 2 in the core system
* service phase of the startup process. \n
*
* @param func Indicates the entry function for initializing and starting a core system service.
* The type is void (*)(void).
*/
#define SYS_SERVICE_INIT(func) LAYER_INITCALL_DEF(func, sys_service, "sys.service")
/**
* @brief Identifies the entry for initializing and starting a core system service by the
* specified priority.
*
* This macro is used to identify the entry called at the specified priority in the core system
* service phase of the startup process. \n
*
* @param func Indicates the entry function for initializing and starting a core system service.
* The type is void (*)(void).
* @param priority Indicates the calling priority when starting the core system service in the
* startup phase. The value range is [0,5), and the calling sequence is 0, 1, 2, 3, and 4.
*/
#define SYS_SERVICE_INIT_PRI(func, priority) LAYER_INITCALL(func, sys_service, "sys.service", priority)
更多的初始化巨集見下文的列表,處理這些初始化巨集,還有可以指定優先順序的版本XXX_PRI。
2.2 LAYER_INITCALL巨集定義
從上文已知,bootstrap_lite服務啟動引導部件的初始化巨集會呼叫LAYER_INITCALL_DEF和LAYER_INITCALL巨集。這些巨集的定義在檔案utils\native\lite\include\ohos_init.h,程式碼片段如下。⑴處宣告函式型別,無參無返回值。⑵處處理定義分層初始化共享庫巨集LAYER_INIT_SHARED_LIB的情況,如果沒有定義該巨集,則執行⑹。在檔案foundation/distributedschedule/samgr_lite/samgr/BUILD.gn中定義了該巨集,在移植適配晶片開發板時,沒有定義這個巨集。⑶處定義5個分層初始化級別,⑷處定義7個構建值(constructor value,簡稱CTOR Value)。⑸處是巨集LAYER_INITCALL的定義,該巨集需要4個引數,分別是初始化服務或功能函式func;layer是分層名稱,支援的取值為device、core、sys_service、sys_feature、app_service、app_feature和run,拼裝為CTOR_VALUE_XXX;clayer引數在定義巨集LAYER_INIT_SHARED_LIB時未使用;priority是優先順序引數。attribute((constructor))表示這段程式碼將在main函式前呼叫。當傳入引數為(myFunc, sys_feature, “sys.feature”, 2)時,函式巨集替換為:static __attribute__((constructor(130 + 2))) void BOOT_sys_featurer2myFunc {myFunc();}。等於定義個一個新的啟動引導函式BOOT_sys_featurer2myFunc()。
當沒有定義LAYER_INIT_SHARED_LIB巨集時,執行⑹,當傳入引數為(myFunc, sys_feature, “sys.feature”, 2)時,函式巨集替換為:static const InitCall __attribute__((used)) __zinitcall_sys_feature_myFunc __attribute__((section(".zinitcall.sys.feature2.init"))) = myFunc,除了__attribute__部分,等於宣告一個函式型別InitCall的變數__zinitcall_sys_feature_myFunc。該函式變數放入section段".zinitcall.sys.feature2.init"內,所以移植適配時,需要在晶片開發板的連結腳本里新增zInit程式碼段。
⑴ typedef void (*InitCall)(void);
#define USED_ATTR __attribute__((used))
⑵ #ifdef LAYER_INIT_SHARED_LIB
⑶ #define LAYER_INIT_LEVEL_0 0
#define LAYER_INIT_LEVEL_1 1
#define LAYER_INIT_LEVEL_2 2
#define LAYER_INIT_LEVEL_3 3
#define LAYER_INIT_LEVEL_4 4
⑷ #define CTOR_VALUE_device 100
#define CTOR_VALUE_core 110
#define CTOR_VALUE_sys_service 120
#define CTOR_VALUE_sys_feature 130
#define CTOR_VALUE_app_service 140
#define CTOR_VALUE_app_feature 150
#define CTOR_VALUE_run 700
⑸ #define LAYER_INITCALL(func, layer, clayer, priority) \
static __attribute__((constructor(CTOR_VALUE_##layer + LAYER_INIT_LEVEL_##priority))) \
void BOOT_##layer##priority##func() {func();}
#else
⑹ #define LAYER_INITCALL(func, layer, clayer, priority) \
static const InitCall USED_ATTR __zinitcall_##layer##_##func \
__attribute__((section(".zinitcall." clayer #priority ".init"))) = func
#endif
// Default priority is 2, priority range is [0, 4]
#define LAYER_INITCALL_DEF(func, layer, clayer) \
LAYER_INITCALL(func, layer, clayer, 2)
2.3 OHOS_SystemInit函式
函式OHOS_SystemInit()定義在檔案base\startup\bootstrap_lite\services\source\system_init.c,程式碼如下。呼叫巨集函式MODULE_INIT、SYS_INIT和函式SAMGR_Bootstrap()進行初始化啟動。
void OHOS_SystemInit(void)
{
MODULE_INIT(bsp);
MODULE_INIT(device);
MODULE_INIT(core);
SYS_INIT(service);
SYS_INIT(feature);
MODULE_INIT(run);
SAMGR_Bootstrap();
}
我們詳細分析下巨集函式MODULE_INIT和SYS_INIT的原始碼,這2個巨集函式在檔案base\startup\bootstrap_lite\services\source\core_main.h中定義,程式碼如下。這些巨集函式的引數大都為name和step,name取值為bsp、device、core、service、feature、run,step取值為0。從⑺和⑻處可以看出,分別呼叫SYS_CALL、MODULE_CALL兩個巨集,第二個引數設定為0。⑸處定義的MODULE_BEGIN函式,用於返回連結指令碼中定義的程式碼段的開始地址,當傳入引數為bsp時,返回&__zinitcall_bsp_start,MODULE_BEGIN函式被展開為如下片段:
{ extern InitCall __zinitcall_sys_start; \
InitCall *initCall = &__zinitcall_bsp_start; \
(initCall); \
}
⑹處定義的MODULE_END函式,用於返回連結指令碼中定義的程式碼段的結束地址,當傳入引數為bsp時,返回&__zinitcall_bsp_end,MODULE_END函式被展開為如下片段:
{ extern InitCall __zinitcall_bsp_end; \
InitCall *initCall = &__zinitcall_bsp_end; \
(initCall); \
}
⑶和⑷處定義的SYS_BEGIN、SYS_END程式碼類似,分於返回連結指令碼中定義的標記系統服務或特性的程式碼段的開始、結束地址,即&__zinitcall_sys_service_start、&__zinitcall_sys_service_end、&__zinitcall_sys_feature_start、&__zinitcall_sys_feature_end。⑴處定義的SYS_CALL函式,分別獲取連結指令碼中zInit程式碼段的開始initcall和結束地址initend,這些地址存放的是初始化函式的地址,語句 (*initcall)()會迴圈呼叫執行這些初始化函式。⑵處MODULE_CALL巨集類似。
⑴ #define SYS_CALL(name, step) \
do { \
InitCall *initcall = (InitCall *)(SYS_BEGIN(name, step)); \
InitCall *initend = (InitCall *)(SYS_END(name, step)); \
for (; initcall < initend; initcall++) { \
(*initcall)(); \
} \
} while (0)
⑵ #define MODULE_CALL(name, step) \
do { \
InitCall *initcall = (InitCall *)(MODULE_BEGIN(name, step)); \
InitCall *initend = (InitCall *)(MODULE_END(name, step)); \
for (; initcall < initend; initcall++) { \
(*initcall)(); \
} \
} while (0)
......
⑶ #define SYS_BEGIN(name, step) \
({ extern InitCall __zinitcall_sys_##name##_start; \
InitCall *initCall = &__zinitcall_sys_##name##_start; \
(initCall); \
})
⑷ #define SYS_END(name, step) \
({ extern InitCall __zinitcall_sys_##name##_end; \
InitCall *initCall = &__zinitcall_sys_##name##_end; \
(initCall); \
})
⑸ #define MODULE_BEGIN(name, step) \
({ extern InitCall __zinitcall_##name##_start; \
InitCall *initCall = &__zinitcall_##name##_start; \
(initCall); \
})
⑹ #define MODULE_END(name, step) \
({ extern InitCall __zinitcall_##name##_end; \
InitCall *initCall = &__zinitcall_##name##_end; \
(initCall); \
})
⑺ #define SYS_INIT(name) \
do { \
SYS_CALL(name, 0); \
} while (0)
⑻ #define MODULE_INIT(name) \
do { \
MODULE_CALL(name, 0); \
} while (0)
3、 bootstrap_lite服務啟動引導部件實現原理之bootstrap_service
在檔案base\startup\bootstrap_lite\services\source\bootstrap_service.h中定義了2個巨集函式INIT_APP_CALL和INIT_TEST_CALL,分別用來呼叫程式碼段&__zinitcall_app_XXX_start、&__zinitcall_app_XXX_end和&__zinitcall_test_start、&__zinitcall_test_end之間的初始化啟動函式。bootstrap_service.h檔案中的巨集和base\startup\bootstrap_lite\services\source\core_main.h檔案中的巨集類似,不再一一分析。
3.1 結構體struct Bootstrap
在檔案base\startup\bootstrap_lite\services\source\bootstrap_service.c中定義了結構體struct Bootstrap,如下程式碼⑵處。其中結構體中的INHERIT_SERVICE定義在檔案foundation/distributedschedule/samgr_lite/interfaces/kits/samgr/service.h,見程式碼片段⑴處。
⑴ #define INHERIT_SERVICE \
const char *(*GetName)(Service * service); \
BOOL (*Initialize)(Service * service, Identity identity); \
BOOL (*MessageHandle)(Service * service, Request * request); \
TaskConfig (*GetTaskConfig)(Service * service)
......
⑵ typedef struct Bootstrap {
INHERIT_SERVICE;
Identity identity;
uint8 flag;
} Bootstrap;
結構體Identity定義在檔案foundation\distributedschedule\samgr_lite\interfaces\kits\samgr\message.h中,用於標識一個服務或特性。
/**
* @brief Identifies a service and feature.
*
* You can use this structure to identity a {@link IUnknown} feature to which messages will be
* sent through the asynchronous function of {@link IUnknown}. \n
*
*/
struct Identity {
/** Service ID */
int16 serviceId;
/** Feature ID */
int16 featureId;
/** Message queue ID */
MQueueId queueId;
};
3.2 Init(void)函式
講解移植適配示例時,我們已經知道,bootstrap_lite部件會編譯//base/startup/bootstrap_lite/services/source/bootstrap_service.c,該檔案通過函式巨集SYS_SERVICE_INIT將Init()函式符號灌段到__zinitcall_sys_service_start和__zinitcall_sys_service_end程式碼段中,程式碼片段如下。下文再詳細分析GetName、Initialize、MessageHandle和GetTaskConfig函式。
static const char *GetName(Service *service);
static BOOL Initialize(Service *service, Identity identity);
static TaskConfig GetTaskConfig(Service *service);
static BOOL MessageHandle(Service *service, Request *request);
static void Init(void)
{
static Bootstrap bootstrap;
bootstrap.GetName = GetName;
bootstrap.Initialize = Initialize;
bootstrap.MessageHandle = MessageHandle;
bootstrap.GetTaskConfig = GetTaskConfig;
bootstrap.flag = FALSE;
SAMGR_GetInstance()->RegisterService((Service *)&bootstrap);
}
SYS_SERVICE_INIT(Init);
3.3 GetName和Initialize函式
GetName函式程式碼如下,其中BOOTSTRAP_SERVICE定義在檔案foundation\distributedschedule\samgr_lite\interfaces\kits\samgr\samgr_lite.h中,取值為"Bootstrap",表示啟動引導服務。
static const char *GetName(Service *service)
{
(void)service;
return BOOTSTRAP_SERVICE;
}
Initialize函式定義如下,用於設定啟動引導服務的標識資訊。
static BOOL Initialize(Service *service, Identity identity)
{
Bootstrap *bootstrap = (Bootstrap *)service;
bootstrap->identity = identity;
return TRUE;
}
3.4 MessageHandle函式和GetTaskConfig函式
MessageHandle函式和GetTaskConfig函式程式碼如下,MessageHandle函式用於處理各種請求,請求訊息編號定義request->msgId定義在檔案foundation\distributedschedule\samgr_lite\interfaces\kits\samgr\samgr_lite.h中的列舉enum BootMessage,如下⑴處所示。在MessageHandle函式中,當請求的訊息編號為BOOT_SYS_COMPLETED系統服務初始化完成時,檢查應用服務是否載入。當應用沒有載入時,執行INIT_APP_CALL巨集函式呼叫zInit程式碼段上的應用服務和應用特性初始化函式。然後執行⑶,呼叫函式SAMGR_SendResponseByIdentity進行響應。GetTaskConfig函式用於獲取任務配置。
⑴ typedef enum BootMessage {
/** Message indicating that the core system service is initialized */
/** 標識核心系統服務初始化完成 */
BOOT_SYS_COMPLETED,
/** Message indicating that the system and application-layer services are initialized */
/** 標識應用層服務初始化完成 */
BOOT_APP_COMPLETED,
/** Message indicating service registration during running */
/** 標識執行時的服務註冊 */
BOOT_REG_SERVICE,
/** Maximum number of message IDs */
/** 標識訊息最大值,butt是菸蒂;屁股;槍托的意思,表示尾部吧 */
BOOTSTRAP_BUTT
} BootMessage;
......
static BOOL MessageHandle(Service *service, Request *request)
{
Bootstrap *bootstrap = (Bootstrap *)service;
switch (request->msgId) {
case BOOT_SYS_COMPLETED:
⑵ if ((bootstrap->flag & LOAD_FLAG) != LOAD_FLAG) {
INIT_APP_CALL(service);
INIT_APP_CALL(feature);
bootstrap->flag |= LOAD_FLAG;
}
⑶ (void)SAMGR_SendResponseByIdentity(&bootstrap->identity, request, NULL);
break;
case BOOT_APP_COMPLETED:
(void)SAMGR_SendResponseByIdentity(&bootstrap->identity, request, NULL);
break;
case BOOT_REG_SERVICE:
(void)SAMGR_SendResponseByIdentity(&bootstrap->identity, request, NULL);
break;
default:
break;
}
return TRUE;
}
static TaskConfig GetTaskConfig(Service *service)
{
(void)service;
// The bootstrap service uses a stack of 2 KB (0x800) in size and a queue of 20 elements.
// You can adjust it according to the actual situation.
TaskConfig config = {LEVEL_HIGH, PRI_NORMAL, 0x800, 20, SHARED_TASK};
return config;
}
參考站點
- OpenHarmony / startup_bootstrap_lite
- HarmonyOS Device > 文件 > 指南 > 基礎能力: bootstrap服務啟動元件
- 啟動恢復子系統.md
- 輕量帶屏解決方案之恆玄晶片移植案例
- 帶你掌握 C 中三種類成員初始化方式
- 實踐GoF的設計模式:工廠方法模式
- DCM:一個能夠改善所有應用資料互動場景的中介軟體新秀
- 手繪圖解java類載入原理
- 關於加密通道規範,你真正用的是TLS,而非SSL
- 程式碼重構,真的只有複雜化一條路嗎?
- 解讀分散式排程平臺Airflow在華為雲MRS中的實踐
- 透過例項demo帶你認識gRPC
- 帶你聚焦GaussDB(DWS)儲存時遊標使用
- 傳統到敏捷的轉型中,誰更適合做Scrum Master?
- 輕鬆解決研發知識管理難題
- Java中觀察者模式與委託,還在傻傻分不清
- 如何使用Python實現影象融合及加法運算?
- 什麼是強化學習?
- 探索開源工作流引擎Azkaban在MRS中的實踐
- GaussDB(DWS) NOT IN優化技術解密:排他分析場景400倍效能提升
- Java中觀察者模式與委託,還在傻傻分不清
- Java中的執行緒到底有哪些安全策略
- 一圖詳解java-class類檔案原理
- Java中的執行緒到底有哪些安全策略