IRP 與 派遣函數

來源:互聯網
上載者:User

標籤:

什麼是派遣函數:

      派遣函數是 WIndows 驅動程式中的重要概念。驅動程式的主要功能是負責處理I/O請求,其中大部分I/O請求是在派遣函數中處理的。也就是說,派遣函數是用來處理驅動程式提交過來的 I/O 請求。

      那什麼是 I/O 請求呢?

     上層程式與驅動程式之間通訊時,上層會發出I/O請求,即輸入輸出請求包(I/O Request package)

      使用者模式下(上層)與所有驅動程式之間的I/O請求,全部由作業系統轉化為一個叫 IRP 的資料結構,不同的 IRP 會被派遣到不同的派遣函數(Dispatch Function)中。

 

什麼是 IRP:

     在 WIndows 核心中,有一種資料結構叫 IRP(I/O Request Package)。它是與輸入輸出相關的重要資料結構。上層應用程式與底層驅動程式通訊時,也就是 .exe 與 .sys 之間的通訊,應用程式會發出I/O請求。作業系統將I/O請求轉化為相應的 IRP 資料結構,不同類型的 IRP 會根據不同的類型傳遞到不同的派遣函數內。

      IRP 的兩個基本屬性 MajorFunction 和 MinorFunction,分別記錄 IRP 的主類型和子類型。作業系統根據 MajorFunction 將 IRP 派遣到不同的派遣函數中,在派遣函數中還可以繼續判斷這個 IRP 屬於那種 MinorFunction。

      在進入 DriverEntry (驅動入口函數)之前,作業系統會將_IoPInvalidDeviceRequest(IoP無效的裝置請求)的地址填滿整個 MajorFunction 數組。

      所以,在用到派遣函數是,必須在驅動程式的入口函數 DriverEntry 函數過程中註冊派遣函數的操作類型。

      如:

#define PAGEDCODE cod_seg("PAGE")  //注意,是PAGEDCODE cod_seg ,名稱寫錯了會藍屏#pragma PAGEDCODEVOID Unload(PDRIVER_OBJECT pDriverObject);extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pUnicodeString){    ///////////////////////////// 註冊派遣函數和類型 ////////////////////////////    pDriverObject->MajorFunction[IRP_MJ_CREATE] = ddk_DispatchFunction;    pDriverObject->MajorFunction[IRP_MJ_READ] = ddk_DispatchFunction;    pDriverObject->MajorFunction[IRP_MJ_WRITE] = ddk_DispatchFunction;    pDriverObject->MajorFunction[IRP_MJ_CLOSE] = ddk_DispatchFunction;    pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = ddk_DispatchFunction;    ///////////////////////////// 註冊派遣函數和類型 ////////////////////////////      return STATUS_SUCCESS;}

 

 

1、IRP 與 派遣函數的聯絡

      IRP 的處理機制類似 WIndows 應用程式中的“訊息處理”機制,驅動程式接收到不同類型的 IRP 後,會進入到不同的派遣函數,在派遣函數中, IRP 會得到處理。

1.1、IRP類型

      檔案 I/O 的相關函數,如 CreateFile、ReadFile、WriteFile、CloseHandle 等函數會使作業系統產生出

          IRP_MJ_CREATE                0x00 //CreateFile;

          IRP_MJ_CLOSE                  0x02 //CloseHandle;

          IRP_MJ_READ                    0x03 //ReadFile;

          IRP_MJ_WRITE                  0x04 //WriteFile;

          IRP_MJ_DEVICE_CONTROL   0x0e //DeviceIoControl

等不同的 IRP,這些 IRP 會被傳送到驅動程式的派遣函數中。

      還有些 IRP 是由系統的某個組件建立的,比如 IRP_MJ_SHUTDOWN 是在 Windows 的隨插即用組件在即將關閉系統的時候發出的。

1.2、對派遣函數的簡單處理

      大部分的 IRP 都源於檔案的I/O處理Win32 API,如 CreateFile、ReadFile 等。處理這些IRP 最簡單的方法就是在相應的派遣函數中,將 IRP 的狀態設定為成功,然後結束 IRP 的請求,並讓派遣函數返回成功。結束 IRP 的請求使用IoCompleteRequest。

VOID IoCompleteRequest(
    IN PIRP  Irp,   //需要被結束的IRP
    IN CCHAR  PriorityBoost   //代表線程恢複的優先順序別
    );

      為瞭解釋優先順序的概念,需要瞭解一下與檔案 I/O 相關的 WIn32 API 的內部操作過程。這裡以 ReadFile 為例,ReadFile 的內部操作大體是這樣的:

      a)、ReadFile 調用 ntdll 中的 NtReadFile。其中的 ReadFile 函數的 Win32 API,而NtReadFile 函數是 Native API

      b)、ntdll中的NtReadFile進入核心模式,並調用系統服務中的NtReadFile函數。

      c)、系統服務函數 NtReadFile 建立 IRP_MJ_WRITE 類型的 IRP,然後它將這個IRP發送到某個驅動程式的派遣函數中。NtReadFile 然後去等待一個事件,這時當前線程進入“睡眠”狀態,也可以說當前線程被阻塞住或者線程處於“Pending”狀態。

      d)、在派遣函數中一般會將 IRP 請求結束,結束 IRP 是通過 IoCompleteRequest 函數。該函數內部會設定剛才等待的事件,“睡眠”的線程被恢複運行。

      在讀一個很大的檔案或者裝置時,ReadFile 不會立刻返回,而是等待一段時間。這段時間就是當前線程“睡眠”的那段時間。IRP 請求被結束,標誌這個作業系統完畢,這時“睡眠”的線程被喚醒。

     IoCompleteRequest 函數的 PriorityBoost 參數代表一種優先順序,指的是被阻塞的線程以何種優先順序恢複運行。一般情況下,優先順序設定為 IO_NO_INCREMENT(INCREMENT 增量)。對某些特殊情況,需要將阻塞的線程以“優先”的身份恢複運行。

1.3、通過裝置串連開啟裝置

      要開啟裝置,必須通過裝置的名字才能得到該裝置的控制代碼。設別名無法被使用者模式下的應用程式查詢到,裝置名稱智能被核心模式下的程式查詢到。在應用程式中,裝置可以通過符號連結進行訪問,驅動程式通過 IoCreateSymblicLink 函數建立符號連結。

1.4、編寫更通用的派遣函數

      IRP會被作業系統發送到裝置棧的頂層,如果頂層的裝置對象的派遣函數結束了 IRP 的請求,則這次 IO 請求結束,如果沒有將 IRP 的請求結束,那麼作業系統將 IRP 轉寄到裝置棧的下一層裝置處理。如果這個裝置的派遣函數依然不能結束該 IRP 請求,則繼續向下層裝置進行轉寄。

      因此,一個 IRP 可能會被轉寄多次。為了記錄 IRP 在每層裝置中做的操作,IRP 會有一個IO_STACK_LOCATION(堆棧、位置)數組。數組的元素數應該大於 IRP 穿越過的裝置數。每個IO_STACK_LOCATION 元素記錄著對應裝置中做的操作。對於本層裝置對應的IO_STACK_LOCATION,可以通過 IoGetCurrentIrpStackLocation (擷取當前的IRP在堆棧的位置)函數得到。IO_STACK_LOCATION 結構中會記錄 IRP 的類型,即IO_STACK_LOCATION中的 MajorFunction 子域。

 

2、緩衝區方式讀寫操作

       驅動程式所建立的裝置一般會有三種讀寫方式,一種是緩衝區方式,一種是直接方式,一種是其他方式。以下介紹緩衝區讀寫:

2.1、緩衝區裝置

       在驅動程式建立裝置對象的時候,需要考慮好該裝置是採用何種讀寫方式。當 IoCreateDevice 建立完裝置後,需要對裝置對象的 Flags (標誌)子域進行設定。設定不同的Flags 會導致以不同的方式操作裝置。Flags 的三個不同的值分別為:DO_BUFFERED_IO(緩衝)、DO_DIRECT_IO(直接)和 0。

       讀寫操作一般是由 ReadFile 或者 WriteFile 函數引起的,這裡先以 WriteFile 函數為例進行介紹:WriteFile 要求使用者提供一段緩衝區,並且聲明緩衝區的大小,然後WriteFile 將這段記憶體的資料傳入到驅動程式中。

       這段緩衝區記憶體是使用者模式的記憶體位址,驅動程式如果直接飲用這段記憶體是十分危險的,因為 Windows 作業系統是多任務的,它可能隨時切換到別的進程。如果驅動程式需要訪問這段記憶體,而這時作業系統可能已經切換到另外一個進程。如果這樣,驅動程式訪問的記憶體位址必定是錯誤的,這種錯誤會引起系統崩潰。

        其中一個解決方案是作業系統將應用程式提供緩衝區的資料複製到核心模式下的地址中。這樣,無論作業系統如何切換進程,核心模式地址都不會改變。IRP 的派遣函數將會對核心模式下的緩衝區操作,而不是使用者模式地址的緩衝區。但是這樣需要在使用者模式和核心模式之間複製資料,影響了運行效率。

2.2、緩衝區裝置讀寫

       以“緩衝區”方式寫裝置時,作業系統將 WriteFile 提供的使用者模式的緩衝區複製到核心模式地址下。這個地址由 WriteFile 建立的 IRP 的 AssociatedIrp.SystemBuffer (聯合IRP.系統緩衝區)子域記錄。

      以“緩衝區”方式讀裝置時,作業系統會分配一段核心模式下的記憶體。這段記憶體大小等於ReadFile 或者 WriteFile 指定的位元組數,並且 ReadFile 或者 WriteFile 建立的 IRP 的AssociatedIrp.SystemBuffer 子域會記錄這段記憶體位址。當 IRP 請求結束時,這段記憶體位址會被複製到 ReadFile 提供的緩衝區中。

      以緩衝區方式無論是讀還是寫裝置,都會發生使用者模式地址與核心模式地址的資料複製。複製的過程由作業系統負責。使用者模式地址由 ReadFile 或者 WriteFile 提供,核心模式地址由作業系統負責分配和回收。由 IO_STACK_LOCATION 中的 Parameters.Read.Length 子域知道ReadFile 請求多少位元組,由 IO_STACK_LOCATION 中的 Parameters.Write.Length 子域知道 WriteFile 請求多少位元組。

      然而,WriteFile 和 ReadFile 指定對裝置操作多少位元組並不真正意味著操作了這麼多的位元組。在派遣函數中,應該設定 IRP 的子域 IoStatus.Information (Io傳回值.資訊)這個子域記錄裝置實際操作了多少位元組。

NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,IN PIRP pIrp)

{

NTSTATUS status = STATUS_SUCCESS;

//得到當前堆棧

PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);

//得到需要讀裝置的位元組數

ULONG ulReadLength = stack->Parameters.Read.Length;

//完成IRP

pIrp->IoStatus.Status = status;

//設定IRP操作了多少位元組

pIrp->IoStatus.Information = ulReadLength;

//設定核心模式下的緩衝區

memset(pIrp->AssociatedIrp.SystemBuffer,0xAA,ulReadLength);

//處理IRP

IoCompleteRequest(pIrp,IO_NO_INCREMENT);

return status;

}

應用程式使用ReadFile對裝置進行讀寫:

#include<windows.h>

#include<stdio.h>

int main()

{

//開啟裝置控制代碼

HANDLE hDevice = CreateFile("\\\\.\\HelloDDK",GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL.NULL);

//判斷開啟是否成功

if(hDevice == INVALID_HANDLE_VALUE)

{

//開啟失敗

return 1;

}

UCHAR buffer[10];

ULONG ulRead;

//對裝置讀寫

BOOL bRet = ReadFile(hDevice,buffer,10,&ulRead,NULL);

if(bRet)

{

printf("Read %d bytes:",ulRead);

for(int i=0;i<(int)ulRead;i++)

{

printf("%02X ",buffer[i]);

}

}

//關閉裝置控制代碼

CloseHandle(hDevice);

return 0;

}

2.3、緩衝區裝置類比檔案讀寫

a)、寫操作。在應用程式中,通過WriteFile函數對裝置進行寫操作。

//應用程式的寫操作。

UCHAR buffer[10];

memset(buffer,0xBB,10);

ULONG ulRead;

ULONG ulWrite;

BOOL bRet;

//對裝置寫操作

bRet = WriteFile(hDevice,buffer,10,&ulWrite,NULL);

if(bRet)

{

//成功將ulWrite寫入裝置

}

WriteFile內部會建立IRP_MJ_WRITE類型的IRP,作業系統會將這個IRP傳遞給驅動程式。IRP_MJ_WRITE的派遣函數需要將傳送進來的資料儲存起來,以便讀取該裝置的時候讀取。在本例中這個資料存放區在一個緩衝區中,緩衝區的地址記錄在裝置擴充中。在裝置啟動的時候,驅動程式負責分配這個緩衝區,在裝置被卸載的時候,驅動程式回收該緩衝區。

對於IRP_MJ_WRITE的派遣函數,主要任務是將寫入的資料存放區在這段緩衝區中。如果寫入的位元組數過大,超過緩衝區的大小,派遣函數將IRP的狀態設定成錯誤狀態。另外,在裝置擴充中有一個變數記錄著這個虛擬檔案裝置的檔案長度。對裝置的寫操作會更改這個變數。

//驅動程式中的寫操作

NTSTATUS HelloDDKWrite(IN PDEVICE_OBJECT pDevObj,IN PIRP pIrp)

{

NTSTATUS status = STATUS_SUCCESS;

PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;

PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);

//擷取儲存的長度

ULONG ulWriteLength = stack->Parameters.Write.Length;

//擷取儲存的位移量

ULONG ulWriteOffset = (ULONG)stack->Parameters.Write.ByteOffset.QuadPart;

if(ulWriteOffset+ulWriteLength>MAX_FILE_LENGTH)

{

//如果儲存長度+位移量大於緩衝區長度,則返回無效

status = STATUS_FILE_INVALID;

ulWriteLength = 0;

}

else

{

//將寫入的資料,儲存在緩衝區內

memcpy(pDevExt->buffer+ulWriteOffset,pIrp->AssociatedIrp.SystemBuffer,ulWriteLength);

status = STATUS_SUCCESS;

//設定新的檔案長度

if(ulWriteLength+ulWriteOffset>pDevExt->file_length)

{

pDevExt->file_length = ulWriteLength+ulWriteOffset;

}

}

pIrp->IoStatus.Status = status;//設定IRP的完成狀態

pIrp->IoStatus.Information = ulWriteLength;//實際操作多少位元組

IoCompleteRequest(pIrp,IO_NO_INCREMENT);//結束IRP請求

return status;

}

b)、讀操作。在應用程式中通過ReadFile來從裝置讀取資料:

bRet = ReadFile(hDevice,buffer,10,&ulRead,NULL);//從裝置讀取10個位元組

if(bRet)

{

//顯示讀取的資料

for(int i=0;i<(int)ulRead;i++)

{

printf("%02X ",buffer[i]);

}

}

ReadFile內部會建立IRP_MJ_READ類型的IRP,作業系統會將這個IRP傳遞給驅動程式中IRP_MJ_READ的派遣函數。IRP_MJ_READ的派遣函數的主要任務是把記錄的資料複製到AssociatedIrp.SystemBuffer中。

NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,IN PIRP pIrp)

{

PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;

NTSTATUS status = STATUS_SUCCESS;

PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);

ULONG ulReadLength = stack->Parameters.Read.Length;

ULONG ulReadOffset = (ULONG)stack->Parameters.Read.ByteOffset.QuadPart;

if(ulReadOffset+ulReadLength>MAX_FILE_LENGTH)

{

status = STATUS_FILE_INVALID;

ulReadLength = 0;

}

else

{

//將資料存放區在AssociatedIrp.SystemBuffer,以便應用程式使用

memcpy(pIrp->AssociatedIrp.SystemBuffer,pDevExt->buffer+ulReadOffset,ulReadLength);

status = STATUS_SUCCESS;

}

//設定IRP完成狀態

pIrp->IoStatus.Status = status;

//設定IRP操作位元組數

pIrp->IoStatus.Information = ulReadLength;

//結束IRP

IoCompleteRequest(pIrp,IO_NO_INCREMENT);

return status;

}

c)、讀取檔案長度。讀取檔案長度依靠GetFileSize Win32 API獲得。GetFileSize內部會建立IRP_MJ_QUERY_INFORMATION類型的IRP。這個IRP請求的作用是向裝置查詢一些資訊,這包括查詢檔案長度、裝置建立的時間、裝置屬性等。在本例中,IRP_MJ_QUERY_INFORMATION派遣函數的主要任務是告訴應用程式這個裝置的長度。

 

3、直接方式讀寫操作

3.1、直接讀取裝置

       和緩衝區方式讀寫裝置不同,直接方式讀寫裝置,作業系統會將使用者模式下的緩衝區鎖住,然後操作作業系統將這段緩衝區在核心模式地址再映射一遍。這樣,使用者模式的緩衝區和核心模式的緩衝區指向的是同一地區的實體記憶體。無論作業系統如何切換進程,核心模式地址都保持不變。

       作業系統先將使用者模式的地址鎖定後,作業系統用記憶體描述表(MDL資料結構)記錄這段記憶體。使用者模式的這段緩衝區在虛擬記憶體上是連續的,但是實體記憶體可能是離散的。

       MDL記錄這段虛擬記憶體,這段虛擬記憶體的大小儲存在mdl->ByteCount裡,這段虛擬記憶體的第一個頁地址mdl->StartVa,這段虛擬記憶體的首地址對於第一個頁地址位移量是mdl->ByteOffset。因此,這段虛擬記憶體的首地址應該是mdl->StartVa+mdl->ByteOffset。

3.2、直接讀取裝置的讀寫

       應用程式調用Win32 API ReadFile,作業系統將IRP_MJ_READ轉寄到相應的派遣函數中。派遣函數通過讀取IO堆棧的stack->Parameters.Read.Length來擷取這次讀取的長度。這個長度就是ReadFile函數的第三個參數nNumberOfByteToRead

       派遣函數盛澤pIrp->IoStatus.Information告訴ReadFile實際讀取了多少位元組,這個數字對應著ReadFile的第四個參數

BOOL ReadFile(

HANDLE hFile,//檔案控制代碼

LPVOCE lpBuffer,//緩衝區

DWORD nNumberOfBytesToRead,//希望讀的位元組數

LPDWORD lpNumberOfBytesRead,//實際讀的位元組數

LPOVERLAPPED lpOverlapped//overlap資料結構地址

);

 

4、其他方式讀寫操作

4.1、其他方式裝置

       在使用其他方式讀寫裝置時,派遣函數直接讀寫應用程式提供的緩衝區地址。只有驅動程式與應用程式運行在相同線程內容相關的情況下,才能使用這種方式。

       用其他方式讀寫時,ReadFile或者WriteFile提供的緩衝區記憶體位址,可以在派遣函數中通過IRP的pIrp->UserBuffer欄位得到。讀取的位元組數可以從I/O堆棧中的stack->Parameters.Read.Length欄位中得到。使用使用者模式的記憶體時要格外小心,因為ReadFile有可能把null 指標地址或者非法地址傳遞給驅動程式。因此,驅動程式使用使用者模式地址前,需要探測這段記憶體是否可讀或者可寫,探測時應使用ProbeForWrite函數和try塊。

 

5、IO裝置控制操作

       除了用ReadFile和WriteFile以外,應用程式還可以通過另外一個Win32 API DeviceIoControl操作裝置。DeviceIoControl內部會使作業系統建立一個IRP_MJ_DEVICE_CONTROL類型的IRP,然後作業系統會將這個IRP轉寄到派遣函數中。

       程式員可以用DeviceIoControl定義除讀寫之外的其他動作,它可以讓應用程式和驅動程式進行通訊。例如,要對一個裝置進行初始化操作,程式員自訂一種I/O控制碼,然後用DeviceIoControl將這個控制碼和請求一起傳遞給驅動程式。在派遣函數中,分別對不同的I/O控制碼進行處理。

5.1、DeviceIoControl與驅動互動

BOOL DeviceIoControl(

HANDLE hDevice,//已經開啟的裝置

DWORD dwIoControlCode,//控制碼

LPVOID lpInBuffer,//輸入緩衝區

DWORD nInBufferSize,//輸入緩衝區大小

LPVOID lpOutBuffer,//輸出緩衝區

DWORD nOutBufferSize,//輸出緩衝區大小

LPDWORD lpBytesReturned,//實際返回位元組數

LPOVERLAPPED lpOverlapped//是否OVERLAP操作

);

DeviceIoControl的第二個參數是I/O控制碼,控制碼也稱IOCTL值,是一個32位的無符號整型。IOCTL需要符合DDK的規定:

DDK特意提供了一個宏CTL_CODE:

CTL_CODE(DeviceType,Function,Method,Access)

DeviceType:裝置對象的類型,這個類型應和建立裝置IoCreateDevice時的類型相匹配。一般形如FILE_DEVICE_XX的宏

Function:這是驅動程式定義的IOCTL碼。其中

0X0000到0X7FFF:為微軟保留

0X800到0XFFF:由程式員自己定義

Method:這個是操作模式,可以是下列四種模式之一:

METHOD_BUFFERED:使用緩衝區方式操作。

METHOD_IN_DIRECT:使用直接方式操作。

METHOD_OUT_DIRECT:使用直接方式操作。唯讀方式開啟將會失敗

METHOD_NEITHER:使用其他方式操作。

5.2、緩衝記憶體模式IOCTL

       在CTL_CODE宏定義這種IOCTL時,應該制定Method參數為METHOD_BUFFERED。前面曾經多次提到,在驅動中最好不要直接存取使用者模式下的記憶體位址,緩衝區方式可以避免程式員訪問記憶體模式下的記憶體位址。

       派遣函數先通過IoGetCurrentIrpStackLocation函數得到當前I/O堆棧(IO_STACK_LOCATION)。派遣函數通過stack->Parameters.DeviceIoControl.OutputBufferLength得到輸出緩衝區大小。最後通過stack->Parameters.DeviceIoControl.IoControlCode得到IOCTL。在派遣函數中通過C語言中的switch語句分別處理不同的IOCTL。

5.3、直接記憶體模式IOCTL

        在用CTL_CODE宏定義這種IOCTL時,應該指定Method參數為METHOD_OUT_DIRECT或者METHOD_IN_DIRECT。直接模式的IOCTL同樣可以避免驅動程式訪問使用者模式的記憶體位址。

        在調用DeviceIoControl時,輸入緩衝區的內容被複製到IRP中的pIrp->AssociatedIrp.SystemBuffer記憶體位址,複製的位元組數按照DeviceIoControl指定輸入的位元組數。但對於DeviceIoControl指定的輸出緩衝區的處理,直接模式的IOCTL和緩衝區模式的IOCTL卻是以不同方式處理的。作業系統會將DeviceIoControl制定的輸出緩衝區鎖定,然後在核心模式地址下重新對應一段地址。

        派遣函數中的IRP結構中的pIrp->MdlAddress記錄DeviceIoControl指定的輸出緩衝區。派遣函數應該使用MmGetSystemAddressForMdlSafe將這段記憶體映射到核心模式下的記憶體位址。

6、小結

       本章重點介紹了驅動程式中的處理IRP請求的派遣函數。所有對裝置的操作最終將轉化為IRP請求,這些IRP請求會被傳送到派遣函數處理。本章主要介紹了IRP_MJ_READ、IRP_MJ_WRITE、IRP_MJ_DEVICE_CONTROL的派遣函數。這些IRP請求分別有緩衝區方式,直接方式和其他方式的操作。其中,緩衝區方式和直接方式是驅動程式開發中經常用到的。

IRP 與 派遣函數

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.