欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

淺析C#異步中的Overlapped是如何尋址的

 更新時(shí)間:2025年01月07日 10:41:53   作者:一線碼農(nóng)  
用ReadAsync做文件異步讀取時(shí),在Win32層面會傳lpOverlapped到內(nèi)核層,那在內(nèi)核層回頭時(shí),它是如何通過這個(gè)lpOverlapped尋找到?ReadAsync這個(gè)異步的Task的呢,下面我們就來簡單分析一下吧

一:背景

1. 講故事

前段時(shí)間訓(xùn)練營里的一位朋友提了一個(gè)問題,我用ReadAsync做文件異步讀取時(shí),我知道在Win32層面會傳 lpOverlapped 到內(nèi)核層,那在內(nèi)核層回頭時(shí),它是如何通過這個(gè) lpOverlapped 尋找到 ReadAsync 這個(gè)異步的Task的呢?

這是一個(gè)好問題,這需要回答人對異步完整的運(yùn)轉(zhuǎn)流程有一個(gè)清晰的認(rèn)識,即使有清晰的認(rèn)識也不能很好的口頭表述出來,就算表述出來對方也不一定能聽懂,所以干脆開兩篇文章來嘗試解讀一下吧。

二:lpOverlapped 如何映射

1. 測試案例

為了能夠講清楚,我們先用 fileStream.ReadAsync 方法來寫一段異步讀取來產(chǎn)生Overlapped,參考代碼如下:

        static void Main(string[] args)
        {
            UseAwaitAsync();
            Console.ReadLine();
        }

        static async Task<string> UseAwaitAsync()
        {
            string filePath = "D:\\dumps\\trace-1\\GenHome.DMP";
            Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")} 請求發(fā)起...");
            FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 16, useAsync: true);
            {
                byte[] buffer = new byte[fileStream.Length];

                int bytesRead = await fileStream.ReadAsync(buffer, 0, buffer.Length);

                string content = Encoding.UTF8.GetString(buffer, 0, bytesRead);

                var query = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")} 獲取到結(jié)果:{content.Length}";

                Console.WriteLine(query);

                return query;
            }
        }

很顯然上面的方法會調(diào)用 Win32 中的 ReadFile,接下來上一下它的簽名和 _OVERLAPPED 結(jié)構(gòu)體。

BOOL ReadFile(
  [in]                HANDLE       hFile,
  [out]               LPVOID       lpBuffer,
  [in]                DWORD        nNumberOfBytesToRead,
  [out, optional]     LPDWORD      lpNumberOfBytesRead,
  [in, out, optional] LPOVERLAPPED lpOverlapped
);

typedef struct _OVERLAPPED {
  ULONG_PTR Internal;
  ULONG_PTR InternalHigh;
  union {
    struct {
      DWORD Offset;
      DWORD OffsetHigh;
    } DUMMYSTRUCTNAME;
    PVOID Pointer;
  } DUMMYUNIONNAME;
  HANDLE    hEvent;
} OVERLAPPED, *LPOVERLAPPED;

2. 尋找映射的兩端

既然是映射嘛,肯定要找到兩個(gè)端口,即非托管層的 NativeOverlapped 和 托管層的 ThreadPoolBoundHandleOverlapped。

1.非托管 _OVERLAPPED

在 C# 中用 NativeOverlapped 結(jié)構(gòu)體表示 Win32 的 _OVERLAPPED 結(jié)構(gòu),參考如下:

public struct NativeOverlapped
{
	public nint InternalLow;
	public nint InternalHigh;
	public int OffsetLow;
	public int OffsetHigh;
	public nint EventHandle;
}

2.托管 ThreadPoolBoundHandleOverlapped

ReadAsync 所產(chǎn)生的 Task<int> 在底層是經(jīng)過ValueTask, OverlappedValueTaskSource 一陣痙攣后弄出來的,最后會藏匿在 Overlapped 子類的 ThreadPoolBoundHandleOverlapped 中,參考代碼和模型圖如下:

        public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
        {
            ValueTask<int> valueTask = this.ReadAsync(new Memory<byte>(buffer, offset, count), cancellationToken);
            if (!valueTask.IsCompletedSuccessfully)
            {
                return valueTask.AsTask();
            }
            return this._lastSyncCompletedReadTask.GetTask(valueTask.Result);
        }

        private unsafe static ValueTuple<SafeFileHandle.OverlappedValueTaskSource, int> QueueAsyncReadFile(SafeFileHandle handle, Memory<byte> buffer, long fileOffset, CancellationToken cancellationToken, OSFileStreamStrategy strategy)
        {
            SafeFileHandle.OverlappedValueTaskSource overlappedValueTaskSource = handle.GetOverlappedValueTaskSource();
            
            NativeOverlapped* ptr = overlappedValueTaskSource.PrepareForOperation(buffer, fileOffset, strategy);
            if (Interop.Kernel32.ReadFile(handle, (byte*)overlappedValueTaskSource._memoryHandle.Pointer, buffer.Length, IntPtr.Zero, ptr) == 0)
            {
                overlappedValueTaskSource.RegisterForCancellation(cancellationToken);
            }
            overlappedValueTaskSource.FinishedScheduling();
            return new ValueTuple<SafeFileHandle.OverlappedValueTaskSource, int>(overlappedValueTaskSource, -1);
        }

最后就是兩端的映射關(guān)系了,先通過 malloc 分配了一塊私有內(nèi)存,中間隔了一個(gè)refcount 的 8byte大小,模型圖如下:

3. 眼見為實(shí)

要想眼見為實(shí),可以從C#源碼中的Overlapped.AllocateNativeOverlapped方法尋找答案。

    public unsafe class Overlapped
    {
        private NativeOverlapped* AllocateNativeOverlapped(object? userData)
        {
            NativeOverlapped* pNativeOverlapped = null;

            nuint handleCount = 1;

            pNativeOverlapped = (NativeOverlapped*)NativeMemory.Alloc((nuint)(sizeof(NativeOverlapped) + sizeof(nuint)) + handleCount * (nuint)sizeof(GCHandle));

            GCHandleCountRef(pNativeOverlapped) = 0;

            pNativeOverlapped->InternalLow = default;
            pNativeOverlapped->InternalHigh = default;
            pNativeOverlapped->OffsetLow = _offsetLow;
            pNativeOverlapped->OffsetHigh = _offsetHigh;
            pNativeOverlapped->EventHandle = _eventHandle;

            GCHandleRef(pNativeOverlapped, 0) = GCHandle.Alloc(this);
            GCHandleCountRef(pNativeOverlapped)++;

            return pRet;
        }

        private static ref nuint GCHandleCountRef(NativeOverlapped* pNativeOverlapped)
                               => ref *(nuint*)(pNativeOverlapped + 1);

        private static ref GCHandle GCHandleRef(NativeOverlapped* pNativeOverlapped, nuint index)
                              => ref *((GCHandle*)((nuint*)(pNativeOverlapped + 1) + 1) + index);
    }

卦中代碼先用 NativeMemory.Alloc 方法分配了一塊私有內(nèi)存,隨后還把 Overlapped 給 GCHandle.Alloc 住了,這是防止異步期間對象被移動,有了代碼接下來上windbg去眼見為實(shí),在 Kernel32!ReadFile 中下斷點(diǎn)觀察方法的第五個(gè)參數(shù)。

0:000> bp Kernel32!ReadFile
0:000> g
Breakpoint 0 hit
KERNEL32!ReadFile:
00007ffd`fa2f56a0 ff25caca0500    jmp     qword ptr [KERNEL32!_imp_ReadFile (00007ffd`fa352170)] ds:00007ffd`fa352170={KERNELBASE!ReadFile (00007ffd`f85c5520)}
0:000> k 5
 # Child-SP          RetAddr               Call Site
00 000000ff`8837e1c8 00007ffd`96229ce3     KERNEL32!ReadFile
01 000000ff`8837e1d0 00007ffd`96411a4a     System_Private_CoreLib!Interop.Kernel32.ReadFile+0xa3 [/_/src/coreclr/System.Private.CoreLib/Microsoft.Interop.LibraryImportGenerator/Microsoft.Interop.LibraryImportGenerator/LibraryImports.g.cs @ 6797] 
02 000000ff`8837e2d0 00007ffd`96411942     System_Private_CoreLib!System.IO.RandomAccess.QueueAsyncReadFile+0x8a
03 000000ff`8837e350 00007ffd`96433677     System_Private_CoreLib!System.IO.RandomAccess.ReadAtOffsetAsync+0x112 [/_/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.Windows.cs @ 238] 
04 000000ff`8837e3f0 00007ffd`9642d5f8     System_Private_CoreLib!System.IO.Strategies.OSFileStreamStrategy.ReadAsync+0xb7 [/_/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/OSFileStreamStrategy.cs @ 290] 
0:000> uf 00007ffd`96229ce3
...
 6797 00007ffd`96229c98 4c8b7d30        mov     r15,qword ptr [rbp+30h]
 6797 00007ffd`96229c9c 4c897c2420      mov     qword ptr [rsp+20h],r15
 6797 00007ffd`96229ca1 498bce          mov     rcx,r14
 6797 00007ffd`96229ca4 48894dac        mov     qword ptr [rbp-54h],rcx
 6797 00007ffd`96229ca8 488bd3          mov     rdx,rbx
 6797 00007ffd`96229cab 488955a4        mov     qword ptr [rbp-5Ch],rdx
 6797 00007ffd`96229caf 448bc6          mov     r8d,esi
 6797 00007ffd`96229cb2 448945b4        mov     dword ptr [rbp-4Ch],r8d
 6797 00007ffd`96229cb6 4c8bcf          mov     r9,rdi
 6797 00007ffd`96229cb9 4c894d9c        mov     qword ptr [rbp-64h],r9
 6797 00007ffd`96229cbd 488d8d40ffffff  lea     rcx,[rbp-0C0h]
 6797 00007ffd`96229cc4 ff159e909e00    call    qword ptr [System_Private_CoreLib!Interop.CallStringMethod+0x5ab9c8 (00007ffd`96c12d68)]
 6797 00007ffd`96229cca 488b055708a100  mov     rax,qword ptr [System_Private_CoreLib!Interop.CallStringMethod+0x5d3188 (00007ffd`96c3a528)]
 6797 00007ffd`96229cd1 488b4dac        mov     rcx,qword ptr [rbp-54h]
 6797 00007ffd`96229cd5 488b55a4        mov     rdx,qword ptr [rbp-5Ch]
 6797 00007ffd`96229cd9 448b45b4        mov     r8d,dword ptr [rbp-4Ch]
 6797 00007ffd`96229cdd 4c8b4d9c        mov     r9,qword ptr [rbp-64h]
 6797 00007ffd`96229ce1 ff10            call    qword ptr [rax]
 6797 00007ffd`96229ce3 8bd8            mov     ebx,eax

仔細(xì)閱讀卦中的匯編代碼,通過這句 r15,qword ptr [rbp+30h] 可知 pNativeOverlapped 是保存在 r15 寄存器中。

0:000> r r15
r15=00000241ca2d4d70
0:000> dp 00000241ca2d4d70
00000241`ca2d4d70  00000000`00000000 00000000`00000000
00000241`ca2d4d80  00000000`00000000 00000000`00000000
00000241`ca2d4d90  00000000`00000001 00000241`c8761358

根據(jù)上面的模型圖,00000241ca2d4d90 保存的是引用計(jì)數(shù),00000241c8761358 就是我們的 ThreadPoolBoundHandleOverlapped ,可以 !do 它一下便知。

最后用 dnspy 在 Overlapped.GetOverlappedFromNative 方法中下一個(gè)斷點(diǎn),這個(gè)方法會在異步處理完成后,執(zhí)行NativeOverlapped尋址ThreadPoolBoundHandleOverlapped 的邏輯,截圖如下,那個(gè) ReadAsync保存在內(nèi)部的 _continuationState 字段里。

三:總結(jié)

C#的傳統(tǒng)做法大多都是采用傳參數(shù)的方式來建議映射關(guān)系,而本篇中用 malloc 開辟一塊私有區(qū)域來映射兩者的關(guān)系也真是獨(dú)一份,實(shí)屬無奈!

到此這篇關(guān)于淺析C#異步中的Overlapped是如何尋址的的文章就介紹到這了,更多相關(guān)C#異步Overlapped如何尋址內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • c#讀取圖像保存到數(shù)據(jù)庫中(數(shù)據(jù)庫保存圖片)

    c#讀取圖像保存到數(shù)據(jù)庫中(數(shù)據(jù)庫保存圖片)

    這篇文章主要介紹了使用c#讀取圖像保存到數(shù)據(jù)庫中的方法,大家參考使用吧
    2014-01-01
  • C#結(jié)束Excel進(jìn)程的步驟教學(xué)

    C#結(jié)束Excel進(jìn)程的步驟教學(xué)

    在本篇文章里小編給大家分享了關(guān)于C#結(jié)束Excel進(jìn)程的步驟教學(xué)內(nèi)容,有興趣的朋友們學(xué)習(xí)下。
    2019-01-01
  • C#如何讀取Txt大數(shù)據(jù)并更新到數(shù)據(jù)庫詳解

    C#如何讀取Txt大數(shù)據(jù)并更新到數(shù)據(jù)庫詳解

    這篇文章主要給大家介紹了關(guān)于C#如何讀取Txt大數(shù)據(jù)并更新到數(shù)據(jù)庫的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用C#具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-08-08
  • C#讀寫INI文件的方法

    C#讀寫INI文件的方法

    這篇文章主要介紹了C#讀寫INI文件的方法,涉及C#讀寫ini文件的相關(guān)實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-09-09
  • C# 如何實(shí)現(xiàn)Token

    C# 如何實(shí)現(xiàn)Token

    這篇文章主要介紹了C# 如何實(shí)現(xiàn)Token,幫助大家更好的理解和學(xué)習(xí)使用c#,感興趣的朋友可以了解下
    2021-03-03
  • C#串口通信模塊使用方法示例

    C#串口通信模塊使用方法示例

    這篇文章主要介紹了C#串口通信模塊使用方法示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-06-06
  • C#中實(shí)現(xiàn)輸入漢字獲取其拼音(漢字轉(zhuǎn)拼音)的2種方法

    C#中實(shí)現(xiàn)輸入漢字獲取其拼音(漢字轉(zhuǎn)拼音)的2種方法

    這篇文章主要介紹了C#中實(shí)現(xiàn)輸入漢字獲取其拼音(漢字轉(zhuǎn)拼音)的2種方法,本文分別給出了使用微軟語言包、手動編碼實(shí)現(xiàn)兩種實(shí)現(xiàn)方式,需要的朋友可以參考下
    2015-01-01
  • C#中Random.Next方法的使用小結(jié)

    C#中Random.Next方法的使用小結(jié)

    在C#中,Random.Next()方法用于生成一個(gè)隨機(jī)整數(shù),本文主要介紹了C#中Random.Next方法的使用小結(jié),具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-01-01
  • C#敏感詞過濾實(shí)現(xiàn)方法

    C#敏感詞過濾實(shí)現(xiàn)方法

    這篇文章主要介紹了C#敏感詞過濾實(shí)現(xiàn)方法,涉及C#針對字符串操作的常用技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2015-05-05
  • C# FileStream實(shí)現(xiàn)多線程斷點(diǎn)續(xù)傳

    C# FileStream實(shí)現(xiàn)多線程斷點(diǎn)續(xù)傳

    這篇文章主要為大家詳細(xì)介紹了C# FileStream實(shí)現(xiàn)多線程斷點(diǎn)續(xù)傳,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-03-03

最新評論