【轉】Android編譯系統詳解(三)——編譯流程詳解

來源:互聯網
上載者:User

標籤:

原文網址:http://www.cloudchou.com/android/post-276.html

本文原創作者:Cloud Chou. 歡迎轉載,請註明出處和本文連結1.概述

編譯Android的第三步是使用mka命令進行編譯,當然我們也可以使用make –j4,但是推薦使用mka命令。因為mka將自動計算-j選項的數字,讓我們不用糾結這個數字到底是多少(這個數字其實就是所有cpu的核心數)。在編譯時間我們可以帶上我們需要編譯的目標,假設你想產生recovery,那麼使用mka recoveryimage,如果想產生ota包,那麼需要使用mka otapackage,後續會介紹所有可以使用的目標。另外注意有一些目標只是起到修飾的作用,也就是說需要和其它目標一起使用,共有4個用於修飾的偽目標:

  • 1) showcommands 顯示編譯過程中使用的命令
  • 2) incrementaljavac用於增量編譯java代碼
  • 3) checkbuild用於檢驗那些需要檢驗的模組
  • 4) all如果使用all修飾編譯目標,會編譯所有模組

研究Android編譯系統時最頭疼的可能是變數,成百個變數我們無法記住其含義,也不知道這些變數會是什麼值,為此我專門做了一個編譯變數的參考網站android.cloudchou.com,你可以在該網站尋找變數,它能告訴你變數的含義,也會給出你該變數的樣本值,另外也詳細解釋了編譯系統裡每個Makefile的作用,這樣你在看編譯系統的代碼時不至於一頭霧水。

編譯的核心檔案是build/core/main.mk和build/core/makefile,main.mk主要作用是檢查編譯環境是否符合要求,確定產品配置,決定產品需要使用的模組,並定義了許多目標供開發人員使用,比如droid,sdk等目標,但是產生這些目標的規則主要在Makefile裡定義,而核心的編譯規則放在build/core/task/kernel.mk

我們將先整體介紹main.mk的執行流程,然後再針對在Linux上編譯預設目標時使用的關鍵代碼進行分析。Makefile主要定義了各個目標的建置規則,因此不再詳細介紹它的執行流程,若有興趣看每個目標的建置規則,可查看http://android.cloudchou.com/build/core/Makefile.php

2. main.mk執行流程2.1 檢驗編譯環境並建立產品配置
  • 1) 設定Shell變數為bash,不能使用其它shell
  • 2) 關閉make的suffix規則,rcs/sccs規則,並設定一個規則: 當某個規則失敗了,就刪除所有目標
  • 3) 檢驗make的版本,cygwin可使用任意版本make,但是linux或者mac只能使用3.81版本或者3.82版本
  • 4) 設定PWD,TOP,TOPDIR,BUILD_SYSTEM等變數,定義了預設目標變數,但是暫時並未定義預設目標的建置規則
  • 5) 包含build/core/help.mk,該makefile定義了兩個目標help和out, help用於顯示協助,out用於檢驗編譯系統是否正確
  • 6) 包含build/core/config.mk,config.mk作了很多配置,包括產品配置,包含該makefile後,會建立輸出目錄系列的變數,還會建立PRODUCT系列變數,後續介紹產品配置時,對此會有更多詳細介紹
  • 7) 包含build/core/cleanbuild.mk,該makefile會包含所有工程的CleanSpec.mk,寫了CleanSpec.mk的工程會定義每次編譯前的特殊清理步驟,cleanbuild.mk會執行這些清除步驟
  • 8) 檢驗編譯環境,先檢測上次編譯結果,如果上次檢驗的版本和此次檢驗的版本一致,則不再檢測,然後進行檢測並將此次編譯結果寫入
2.2 包含其它makefile及編譯目標檢測
  • 1) 如果目標裡含有incrementaljavac, 那麼編譯目標時將用incremental javac進行增量編譯
  • 2) 設定EMMA_INSTRUMENT變數的值,emma是用於測試程式碼涵蓋範圍的庫
  • 3) 包含build/core/definistions.mk,該makefile定義了許多輔助函數
  • 4) 包含build/core/qcom_utils.mk,該makefile定義了高通板子的一些輔助函數及宏
  • 5) 包含build/core/dex_preopt.mk,該makefile定義了最佳化dex代碼的一些宏
  • 6) 檢測編譯目標裡是否有user,userdebug,eng,如果有則告訴使用者放置在buildspec.mk或者使用lunch設定,檢測TARGET_BUILD_VARIANT變數,看是否有效
  • 7) 包含build/core/pdk_config.mk, PDK主要是能提高現有裝置升級能力,協助裝置製造商能更快的適配新版本的android
2.3 根據TARGET_BUILD_VARIANT建立配置
  • 1) 如果編譯目標裡有sdk,win_sdk或者sdk_addon,那麼設定is_sdk_build為true
  • 2) 如果定義了HAVE_SELINUX,那麼編譯時間為build prop添加屬性ro.build.selinux=1
  • 3) 如果TARGET_BUILD_VARIANT是user或者userdebug,那麼tags_to_install += debug 如果使用者未定義DISABLE_DEXPREOPT為true,並且是user模式,那麼將設定WITH_DEXPREOPT := true,該選項將開啟apk的預最佳化,即將apk分成odex代碼檔案和apk資源檔
  • 4) 判斷enable_target_debugging變數,預設是true,當build_variant是user時,則它是false。如果該變數值為true,則設定Rom的編譯屬性ro.debuggable為1,否則設定ro.debuggable為0
  • 5) 如果TARGET_BUILD_VARIANT是eng,那麼tags_to_install為debug,eng, 並設定Rom的編譯屬性ro.setupwizard.mode為OPTIONAL,因為eng模式並不要安裝嚮導
  • 6) 如果TARGET_BUILD_VARIANT是tests,那麼tags_to_install := debug eng tests
  • 7) 設定sdk相關變數
  • 8) 添加一些額外的編譯屬性
  • 9) 定義should-install-to-system宏函數
  • 10) 若除了修飾目標,沒定義任何目標,那麼將使用預設目標編譯
2.4 包含所有要編譯的模組的Makefile

如果編譯目標是clean clobber installclean dataclean,那麼設定dont_bother為true,若dont_bother為false,則將所有要編譯的模組包含進來

1) 如果主機作業系統及體繫結構為darwin-ppc(Mac電腦),那麼提示不支援編譯Sdk,並將SDK_ONLY設定為true

2) 如果主機作業系統是windows,那麼設定SDK_ONLY為true

3) 根據SDK_ONLY是否為true,編譯主機作業系統類型,BUILD_TINY_ANDROID的值,設定sudbidrs變數

4) 將所有PRODUCT_*相關變數儲存至stash_product_vars變數,稍後將驗證它是否被修改

5) 根據ONE_SHOT_MAKEFILE的值是否為空白,包含不同的makefile

6) 執行post_clean步驟,並確保產品相關變數沒有變化

7) 檢測是否有檔案加入ALL_PREBUILT

8) 包含其它必須在所有Android.mk包含之後需要包含的makefile

9) 將known_custom_modules轉化成安裝路徑得到變數CUSTOM_MODULES

10) 定義模組之間的依賴關係,$(ALL_MODULES.$(m).REQUIRED))變數指明了模組之間的依賴關係

11) 計算下述變數的值:product_MODULES,debug_MODULES,eng_MODULES,tests_MODULES,modules_to_install,overridden_packages,target_gnu_MODULES,ALL_DEFAULT_INSTALLED_MODULES

12) 包含build/core/Makefile

13) 定義變數modules_to_check

2.5 定義多個目標

這一節定義了眾多目標,prebuilt,all_copied_headers,files,checkbuild,ramdisk,factory_ramdisk,factory_bundle,systemtarball,boottarball,userdataimage,userdatatarball,cacheimage,bootimage,droidcore,dist_files,apps_only,all_modules,docs,sdk,lintall,samplecode,findbugs,clean,modules,showcommands,nothing。

後續文章將列出所有可用的目標

3 編譯預設目標時的執行流程

在介紹編譯預設目標時的執行流程之前,先介紹一下ALL_系列的變數,否則看代碼時很難搞懂這些變數的出處,這些變數在包含所有模組後被建立,每個模組都有對應的用於編譯的makefile,這些makefile會包含一個編譯類型對應的makefile,比如package.mk,而這些makefile最終都會包含base_rules.mk,在base_rules.mk裡會為ALL系列變數添加值。所有這些變數及其來源均可在android.cloudchou.com查看詳細解釋:

  • 1) ALL_DOCS所有文檔的全路徑,ALL_DOCS的賦值在droiddoc.mk裡, ALL_DOCS += $(full_target)
  • 2) ALL_MODULES系統的所有模組的簡單名字集合,編譯系統還為每一個模組還定義了其它兩個變數,ALL_MODULES.$(LOCAL_MODULE).BUILT 所有模組的產生路徑ALL_MODULES.$(LOCAL_MODULE).INSTALLED 所有模組的各自安裝路徑,詳情請見http://android.cloudchou.com/build/core/definitions.php#ALL_MODULES
  • 3) ALL_DEFAULT_INSTALLED_MODULES 所有預設要安裝的模組,在build/core/main.mk和build/core/makfile裡設定
  • 4) ALL_MODULE_TAGS 使用LOCAL_MODULE_TAGS定義的所有tag集合,每一個tag對應一個ALL_MODULE_TAGS.變數,詳情請見http://android.cloudchou.com/build/core/definitions.php#ALL_MODULE_TAGS
  • 5) ALL_MODULE_NAME_TAGS類似於ALL_MODULE_TAGS,但是它的值是 某個tag的所有模組的名稱 詳情請見http://android.cloudchou.com/build/core/definitions.php#ALL_MODULE_NAME_TAGS
  • 6) ALL_HOST_INSTALLED_FILES 安裝在pc上的程式集合
  • 7) ALL_PREBUILT 將會被拷貝的先行編譯檔案的安裝全路徑的集合
  • 8) ALL_GENERATED_SOURCES 某些工具產生的原始碼檔案的集合,比如aidl會產生java原始碼檔案
  • 9) ALL_C_CPP_ETC_OBJECTS 所有asm,c,c++,以及lex和yacc產生的c代碼檔案的全路徑
  • 10) ALL_ORIGINAL_DYNAMIC_BINARIES 沒有被最佳化,也沒有被壓縮的動態連結程式庫
  • 11) ALL_SDK_FILES 將會放在sdk的檔案
  • 12) ALL_FINDBUGS_FILES 所有findbugs程式用的xml檔案
  • 13) ALL_GPL_MODULE_LICENSE_FILES GPL 模組的 許可檔案
  • 14) ANDROID_RESOURCE_GENERATED_CLASSES Android 資源檔產生的java代碼編譯後的類的類型
3.1 關鍵代碼

定義預設目標的代碼位於main.mk:

123
.PHONY: droidDEFAULT_GOAL := droid$(DEFAULT_GOAL):

droid目標依賴的目標有:

1234567
ifneq ($(TARGET_BUILD_APPS),)……droid: apps_only #如果編譯app,那麼droid依賴apps_only目標else……droid: droidcore dist_files #預設依賴droidcore目標和dist_files目標endif

dist_files目標依賴的目標主要是一些用於打包的工具,它們都是用dist-for-goals宏添加依賴關係的:

1234567891011
$(call dist-for-goals, dist_files, $(EMMA_META_ZIP))system/core/mkbootimg/Android.mk$(call dist-for-goals, dist_files, $( LOCAL_BUILT_MODULE ))system/core/cpio/Android.mk:13:$(call dist-for-goals,dist_files,$(LOCAL_BUILT_MODULE))system/core/adb/Android.mk:88:$(call dist-for-goals,dist_files sdk,$(LOCAL_BUILT_MODULE))system/core/fastboot/Android.mk:68:$(call dist-for-goals,dist_files sdk,$(LOCAL_BUILT_MODULE))external/guava/Android.mk:26:$(call dist-for-goals, dist_files, $(LOCAL_BUILT_MODULE):guava.jar)external/yaffs2/Android.mk:28:$(call dist-for-goals, dist_files, $(LOCAL_BUILT_MODULE))external/mp4parser/Android.mk:26:$(call dist-for-goals, dist_files, $(LOCAL_BUILT_MODULE):mp4parser.jar)external/jsr305/Android.mk:25:$(call dist-for-goals, dist_files, $(LOCAL_BUILT_MODULE):jsr305.jar)frameworks/support/renderscript/v8/Android.mk:29:#$(call dist-for-goals, dist_files, $(LOCAL_BUILT_MODULE):volley.jar)frameworks/support/volley/Android.mk:29:#$(call dist-for-goals, dist_files, $(LOCAL_BUILT_MODULE):volley.jar)

我們再看droidcore目標依賴的目標有:

1234567891011121314151617181920212223
droidcore: files systemimage \ #system.img$(INSTALLED_BOOTIMAGE_TARGET) \ #boot.img$(INSTALLED_RECOVERYIMAGE_TARGET) \#recovery.img$(INSTALLED_USERDATAIMAGE_TARGET) \#data.img$(INSTALLED_CACHEIMAGE_TARGET) \#cache.img$(INSTALLED_FILES_FILE)# installed-files.txtifneq ($(TARGET_BUILD_APPS),)….else$(call dist-for-goals, droidcore,     $(INTERNAL_UPDATE_PACKAGE_TARGET) #cm_find5-img-eng.cloud.zip    $(INTERNAL_OTA_PACKAGE_TARGET) \ # cm_find5-ota-eng.cloud.zip    $(SYMBOLS_ZIP) \ # cm_find5-symbols-eng.cloud.zip    $(INSTALLED_FILES_FILE) \# installed-files.txt    $(INSTALLED_BUILD_PROP_TARGET) \# system/build.prop    $(BUILT_TARGET_FILES_PACKAGE) \# cm_find5-target_files-eng.cloud.zip    $(INSTALLED_ANDROID_INFO_TXT_TARGET) \# android-info.txt    $(INSTALLED_RAMDISK_TARGET) \# ramdisk.img    $(INSTALLED_FACTORY_RAMDISK_TARGET) \# factory_ramdisk.gz    $(INSTALLED_FACTORY_BUNDLE_TARGET) \# cm_find5-factory_bundle- eng.cloud.zip   )endif

system.img, boot.img, recovery.img, data.img,cache.img,installed_files.txt的建置規則在Makefile裡定義, 在http://android.cloudchou.com/build/core/Makefile.php裡可以看到詳細的建置規則 再看一下files目標所依賴的目標:

123
files: prebuilt         $(modules_to_install)         $(INSTALLED_ANDROID_INFO_TXT_TARGET)

prebuilt目標依賴$(ALL_PREBUILT),android-info.txt的建置規則在target/board/board.mk裡定義,而$(modules_to_install)目標是所有要安裝的模組的集合,計算比較複雜,現在以在linux下編譯預設目標為例,將涉及到的程式碼群組織如下:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
……tags_to_install :=ifneq (,$(user_variant))…..  ifeq ($(user_variant),userdebug)  tags_to_install += debug  else  …  endifendififeq ($(TARGET_BUILD_VARIANT),eng)tags_to_install := debug eng…endififeq ($(TARGET_BUILD_VARIANT),tests)tags_to_install := debug eng testsendififdef is_sdk_buildtags_to_install := debug engelse # !sdkendif……# ------------------------------------------------------------# Define a function that, given a list of module tags, returns# non-empty if that module should be installed in /system. # For most goals, anything not tagged with the "tests" tag should# be installed in /system.define should-install-to-system$(if $(filter tests,$(1)),,true)endefifdef is_sdk_build# For the sdk goal, anything with the "samples" tag should be# installed in /data even if that module also has "eng"/"debug"/"user".define should-install-to-system$(if $(filter samples tests,$(1)),,true)endefendif…#接下來根據配置計算要尋找的subdirs目錄ifneq ($(dont_bother),true)…ifeq ($(SDK_ONLY),true)include $(TOPDIR)sdk/build/sdk_only_whitelist.mkinclude $(TOPDIR)development/build/sdk_only_whitelist.mk# Exclude tools/acp when cross-compiling windows under linuxifeq ($(findstring Linux,$(UNAME)),)subdirs += build/tools/acpendif else# !SDK_ONLYifeq ($(BUILD_TINY_ANDROID), true)subdirs := bionic system/core system/extras/ext4_utils system/extras/su build/libs build/target build/tools/acp external/gcc-demangle external/mksh external/openssl external/yaffs2 external/zlibelse# !BUILD_TINY_ANDROIDsubdirs := $(TOP) FULL_BUILD := trueendif# !BUILD_TINY_ANDROIDendif# Before we go and include all of the module makefiles, stash away# the PRODUCT_* values so that later we can verify they are not modified.stash_product_vars:=trueifeq ($(stash_product_vars),true)  $(call stash-product-vars, __STASHED)endif….ifneq ($(ONE_SHOT_MAKEFILE),)include $(ONE_SHOT_MAKEFILE)CUSTOM_MODULES := $(sort $(call get-tagged-modules,$(ALL_MODULE_TAGS)))FULL_BUILD :=…else # ONE_SHOT_MAKEFILE## Include all of the makefiles in the system# # Can‘t use first-makefiles-under here because# --mindepth=2 makes the prunes not work.subdir_makefiles := $(shell build/tools/findleaves.py --prune=out --prune=.repo --prune=.git $(subdirs) Android.mk) include $(subdir_makefiles) endif # ONE_SHOT_MAKEFILE……ifeq ($(stash_product_vars),true)  $(call assert-product-vars, __STASHED)endif…# -------------------------------------------------------------------# Fix up CUSTOM_MODULES to refer to installed files rather than# just bare module names.  Leave unknown modules alone in case# they‘re actually full paths to a particular file.known_custom_modules := $(filter $(ALL_MODULES),$(CUSTOM_MODULES))unknown_custom_modules := $(filter-out $(ALL_MODULES),$(CUSTOM_MODULES))CUSTOM_MODULES := $(call module-installed-files,$(known_custom_modules)) $(unknown_custom_modules# -------------------------------------------------------------------# Figure out our module sets.## Of the modules defined by the component makefiles,# determine what we actually want to build. ifdef FULL_BUILD  # The base list of modules to build for this product is specified  # by the appropriate product definition file, which was included  # by product_config.make.  product_MODULES := $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_PACKAGES)  # Filter out the overridden packages before doing expansion  product_MODULES := $(filter-out $(foreach p, $(product_MODULES),       $(PACKAGES.$(p).OVERRIDES)), $(product_MODULES))  $(call expand-required-modules,product_MODULES,$(product_MODULES))  product_FILES := $(call module-installed-files, $(product_MODULES))  ifeq (0,1)    $(info product_FILES for $(TARGET_DEVICE) ($(INTERNAL_PRODUCT)):)    $(foreach p,$(product_FILES),$(info :   $(p)))    $(error done)  endifelse  # We‘re not doing a full build, and are probably only including  # a subset of the module makefiles.  Don‘t try to build any modules  # requested by the product, because we probably won‘t have rules  # to build them.  product_FILES :=endif# When modules are tagged with debug eng or tests, they are installed# for those variants regardless of what the product spec says.debug_MODULES := $(sort         $(call get-tagged-modules,debug)         $(call module-installed-files, $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_PACKAGES_DEBUG))     )eng_MODULES := $(sort         $(call get-tagged-modules,eng)         $(call module-installed-files, $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_PACKAGES_ENG))     )tests_MODULES := $(sort         $(call get-tagged-modules,tests)         $(call module-installed-files, $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_PACKAGES_TESTS)) )# TODO: Remove the 3 places in the tree that use ALL_DEFAULT_INSTALLED_MODULES# and get rid of it from this list.# TODO: The shell is chosen by magic.  Do we still need this?modules_to_install := $(sort     $(ALL_DEFAULT_INSTALLED_MODULES)     $(product_FILES)     $(foreach tag,$(tags_to_install),$($(tag)_MODULES))     $(call get-tagged-modules, shell_$(TARGET_SHELL))     $(CUSTOM_MODULES)   )# Some packages may override others using LOCAL_OVERRIDES_PACKAGES.# Filter out (do not install) any overridden packages.overridden_packages := $(call get-package-overrides,$(modules_to_install))ifdef overridden_packages#  old_modules_to_install := $(modules_to_install)  modules_to_install :=       $(filter-out $(foreach p,$(overridden_packages),$(p) %/$(p).apk),           $(modules_to_install))endif#$(error filtered out#           $(filter-out $(modules_to_install),$(old_modules_to_install))) # Don‘t include any GNU targets in the SDK.  It‘s ok (and necessary)# to build the host tools, but nothing that‘s going to be installed# on the target (including static libraries).ifdef is_sdk_build  target_gnu_MODULES :=               $(filter                       $(TARGET_OUT_INTERMEDIATES)/%                       $(TARGET_OUT)/%                       $(TARGET_OUT_DATA)/%,                               $(sort $(call get-tagged-modules,gnu)))  $(info Removing from sdk:)$(foreach d,$(target_gnu_MODULES),$(info : $(d)))  modules_to_install :=               $(filter-out $(target_gnu_MODULES),$(modules_to_install))   # Ensure every module listed in PRODUCT_PACKAGES* gets something installed  # TODO: Should we do this for all builds and not just the sdk?  $(foreach m, $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_PACKAGES),     $(if $(strip $(ALL_MODULES.$(m).INSTALLED)),,      $(warning $(ALL_MODULES.$(m).MAKEFILE): Module ‘$(m)‘ in PRODUCT_PACKAGES has nothing to install!)))  $(foreach m, $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_PACKAGES_DEBUG),     $(if $(strip $(ALL_MODULES.$(m).INSTALLED)),,      $(warning $(ALL_MODULES.$(m).MAKEFILE): Module ‘$(m)‘ in PRODUCT_PACKAGES_DEBUG has nothing to install!)))  $(foreach m, $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_PACKAGES_ENG),     $(if $(strip $(ALL_MODULES.$(m).INSTALLED)),,      $(warning $(ALL_MODULES.$(m).MAKEFILE): Module ‘$(m)‘ in PRODUCT_PACKAGES_ENG has nothing to install!)))  $(foreach m, $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_PACKAGES_TESTS),     $(if $(strip $(ALL_MODULES.$(m).INSTALLED)),,      $(warning $(ALL_MODULES.$(m).MAKEFILE): Module ‘$(m)‘ in PRODUCT_PACKAGES_TESTS has nothing to install!)))endif # Install all of the host modulesmodules_to_install += $(sort $(modules_to_install) $(ALL_HOST_INSTALLED_FILES)) # build/core/Makefile contains extra stuff that we don‘t want to pollute this# top-level makefile with.  It expects that ALL_DEFAULT_INSTALLED_MODULES# contains everything that‘s built during the current make, but it also further# extends ALL_DEFAULT_INSTALLED_MODULES.ALL_DEFAULT_INSTALLED_MODULES := $(modules_to_install)include $(BUILD_SYSTEM)/Makefilemodules_to_install := $(sort $(ALL_DEFAULT_INSTALLED_MODULES))ALL_DEFAULT_INSTALLED_MODULES := endif # dont_bother# These are additional goals that we build, in order to make sure that there# is as little code as possible in the tree that doesn‘t build.modules_to_check := $(foreach m,$(ALL_MODULES),$(ALL_MODULES.$(m).CHECKED)) # If you would like to build all goals, and not skip any intermediate# steps, you can pass the "all" modifier goal on the commandline.ifneq ($(filter all,$(MAKECMDGOALS)),)modules_to_check += $(foreach m,$(ALL_MODULES),$(ALL_MODULES.$(m).BUILT))endif # for easier debuggingmodules_to_check := $(sort $(modules_to_check))#$(error modules_to_check $(modules_to_check))

【轉】Android編譯系統詳解(三)——編譯流程詳解

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.