日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > C# >内容正文

C#

聊一聊 C# 线程切换后上下文都去了哪里

發布時間:2023/12/24 C# 46 coder
生活随笔 收集整理的這篇文章主要介紹了 聊一聊 C# 线程切换后上下文都去了哪里 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一:背景

1. 講故事

總會有一些朋友是不是問一個問題,在 Windows 中線程做了上下文切換,請問被切的線程他的寄存器上下文都去了哪里?能不能給我挖出來?這個問題其實比較底層,如果對操作系統沒有個體系層面的理解以及做過源碼分析,其實很難說明白,這篇我們就從.NET高級調試的角度試著分析一下吧。

二:寄存器上下文去哪了

1. 用戶線程的兩態空間

用C#代碼創建的線程在操作系統層面上來說屬于 用戶態線程,這種線程擁有兩個線程棧,哈哈,是不是打破了一些朋友的三觀。分別為 用戶態棧內核態棧

為了方便講解,寫一段簡單的測試代碼,不斷的調用 Sleep(1) 讓代碼在用戶態和內核態不斷的切換,也就能觀察得到這兩套棧空間,參考代碼如下:


        static void Main(string[] args)
        {
            for (int i = 0; i < int.MaxValue; i++)
            {
                Thread.Sleep(1);
                Console.WriteLine($"i={i}");
            }
        }

將程序跑起來后我們用 windbg 附加,觀察這個程序的上下文,參考如下:


0: kd> !process 0 2 ConsoleApp7.exe
PROCESS ffffe00185e33440
    SessionId: 2  Cid: 0f4c    Peb: 7ff73b7a8000  ParentCid: 15f4
    DirBase: 1573c1000  ObjectTable: ffffc00165357840  HandleCount: <Data Not Accessible>
    Image: ConsoleApp7.exe

        THREAD ffffe0018917a080  Cid 0f4c.0f50  Teb: 00007ff73b7ae000 Win32Thread: ffffe00185e3db20 WAIT: (DelayExecution) UserMode Alertable
            ffffffffffffffff  NotificationEvent
...

2: kd> dt nt!_KTHREAD ffffe0018917a080
   +0x028 InitialStack     : 0xffffd001`f8b64c90 Void
   +0x030 StackLimit       : 0xffffd001`f8b5f000 Void
   +0x038 StackBase        : 0xffffd001`f8b65000 Void
   ...
   +0x058 KernelStack      : 0xffffd001`f8b63c80 Void
   ...
   +0x0f0 Teb              : 0x00007ff7`3b7ae000 Void
   ...

2: kd> dt ntdll!_NT_TIB 0x00007ff7`3b7ae000
   +0x000 ExceptionList    : (null) 
   +0x008 StackBase        : 0x00000035`35790000 Void
   +0x010 StackLimit       : 0x00000035`3577e000 Void
   +0x018 SubSystemTib     : (null) 
   +0x020 FiberData        : 0x00000000`00001e00 Void
   +0x020 Version          : 0x1e00
   +0x028 ArbitraryUserPointer : (null) 
   +0x030 Self             : 0x00007ff7`3b7ae000 _NT_TIB
   ...

上面的信息非常清晰,兩套棧空間 StackBase ~ StackLimit,分別為 0x0000003535790000 ~ 0x000000353577e0000xffffd001f8b5f000~0xffffd001f8b65000

2. 理解系統調用

理解了線程的兩套棧空間之后,接下來說的就是系統調用,簡單來說就是C#線程從 用戶態 進入到 內核態 時,他的用戶態寄存器上下文會存放到 _KTRAP_FRAME 結構體中,而這個結構體會放在內核態的線程棧上,有些朋友可能有點懵,畫個圖如下:

接下來的問題是如何驗證呢?非常簡單,第一種是通過 !thread 觀察線程棧上的 TrapFrame 標記,第二種是提取內核線程的 _KTHREAD.TrapFrame 字段,為了方便測試,直接在 Sleep 的內核函數 NtDelayExecution 處下一個進程級別的斷點,輸出如下:


1: kd> bp /p ffffe00185e33440  nt!NtDelayExecution
breakpoint 0 redefined
1: kd> g
Breakpoint 0 hit
nt!NtDelayExecution:
fffff802`e4e8dfb0 4883ec28        sub     rsp,28h

3: kd> !thread ffffe0018917a080
THREAD ffffe0018917a080  Cid 0f4c.0f50  Teb: 00007ff73b7ae000 Win32Thread: ffffe00185e3db20 RUNNING on processor 3
IRP List:
    ffffe00187633ca0: (0006,0358) Flags: 00060800  Mdl: 00000000
Not impersonating
DeviceMap                 ffffc0015d587160
Owning Process            ffffe00185e33440       Image:         ConsoleApp7.exe
Attached Process          N/A            Image:         N/A
Wait Start TickCount      21032          Ticks: 1 (0:00:00:00.015)
Context Switch Count      8187           IdealProcessor: 3             
UserTime                  00:00:00.015
KernelTime                00:00:00.125
Win32 Start Address ConsoleApp7_exe!wmainCRTStartup (0x00007ff73beb3c60)
Stack Init ffffd001f8b64c90 Current ffffd001f8b64550
Base ffffd001f8b65000 Limit ffffd001f8b5f000 Call 0000000000000000
Priority 10 BasePriority 8 PriorityDecrement 2 IoPriority 2 PagePriority 5
Child-SP          RetAddr               : Args to Child                                                           : Call Site
ffffd001`f8b64af8 fffff802`e4be9b63     : ffffe001`8917a080 00000000`00000014 ffffffff`ffffd8f0 ffffe001`886c3fe0 : nt!NtDelayExecution
ffffd001`f8b64b00 00007ff8`cf383b6a     : 00007ff8`cc0d3777 00000035`3578e198 00000000`00000001 00000000`00000000 : nt!KiSystemServiceCopyEnd+0x13 (TrapFrame @ ffffd001`f8b64b00)
00000035`3578e0d8 00007ff8`cc0d3777     : 00000035`3578e198 00000000`00000001 00000000`00000000 00000000`00000000 : ntdll!NtDelayExecution+0xa
00000035`3578e0e0 00007ff8`aec355f2     : 00000035`35977a40 00000000`00000001 00000035`00000000 00000000`00000000 : KERNELBASE!SleepEx+0xa7
(Inline Function) --------`--------     : --------`-------- --------`-------- --------`-------- --------`-------- : coreclr!ClrSleepEx+0xd (Inline Function @ 00007ff8`aec355f2) 
00000035`3578e180 00007ff8`aec354eb     : 06000000`00000001 00007ff8`aec35450 04000000`00000001 00000000`00000000 : coreclr!Thread::UserSleep+0xb2
00000035`3578e1d0 00007ff8`4f1ea095     : 00000035`3578e3c0 00000035`3578e4b8 00000000`00000001 00000000`00000001 : coreclr!ThreadNative::Sleep+0x9b 

3: kd> dt nt!_KTRAP_FRAME ffffd001`f8b64b00
   ...
   +0x030 Rax              : 0x00007ff7`3b770002
   +0x038 Rcx              : 0x00000035`358d33a0
   +0x040 Rdx              : 0x00000035`37b5c9b8
   +0x048 R8               : 0x00000035`37b5c9c8
   +0x050 R9               : 0x00000035`3578dd70
   +0x058 R10              : 0x00007ff7`3b780022
   +0x060 R11              : 0x00000035`3578e170
   +0x068 GsBase           : 0x00007ff7`3b7ae000
   +0x068 GsSwap           : 0x00007ff7`3b7ae000
   ...
   +0x0d0 FaultAddress     : 0x00000035`37b7b000
   ...
   +0x140 Rbx              : 1
   +0x148 Rdi              : 0
   +0x150 Rsi              : 1
   +0x158 Rbp              : 0x503b1
   +0x168 Rip              : 0x7ff8cf383b6a [Type: unsigned __int64]
   +0x180 Rsp              : 0x353578e0d8 [Type: unsigned __int64]
   ...

仔細觀察上面的 RIP 和 RSP 值,都能看到它是在 Ring3 上的現場,分別對應著用戶態的 ret 和 ntdll!NtDelayExecution,輸出如下:


3: kd> uf 0x7ff8cf383b6a
ntdll!NtDelayExecution:
00007ff8`cf383b60 4c8bd1          mov     r10,rcx
00007ff8`cf383b63 b834000000      mov     eax,34h
00007ff8`cf383b68 0f05            syscall
00007ff8`cf383b6a c3              ret

3: kd> k
 # Child-SP          RetAddr               Call Site
00 ffffd001`f8b64af8 fffff802`e4be9b63     nt!NtDelayExecution
01 ffffd001`f8b64b00 00007ff8`cf383b6a     nt!KiSystemServiceCopyEnd+0x13
02 00000035`3578e0d8 00007ff8`cc0d3777     ntdll!NtDelayExecution+0xa
03 00000035`3578e0e0 00007ff8`aec355f2     KERNELBASE!SleepEx+0xa7
04 (Inline Function) --------`--------     coreclr!ClrSleepEx+0xd 
05 00000035`3578e180 00007ff8`aec354eb     coreclr!Thread::UserSleep+0xb2 
06 00000035`3578e1d0 00007ff8`4f1ea095     coreclr!ThreadNative::Sleep+0x9b
07 00000035`3578e320 00000035`3578e3c0     0x00007ff8`4f1ea095

3. 內核態線程上下文切換

上一節的_KTRAP_FRAME結構只是保存了 Ring3 -> Ring0 的現場,其實還有一個現場,很顯然是調用線程執行 Sleep(1) 后讓自己暫停并出讓cpu核,為了讓自己下一次得到完美的調度,此次必須要保存現場,那這個保存現場的邏輯在哪里的?其實是通過內核的 nt!KiSwapContext 函數實現的。

本來想在 nt!KiSwapContext 處下個斷點,發現命中不了我的 Sleep 函數的 SwapContext,懷疑有cli之類的屏蔽外部中斷導致的,這里只能反匯編源碼了,參考如下:


3: kd> uf nt!KiSwapContext
nt!KiSwapContext:
fffff802`e4be3f30 4881ec38010000  sub     rsp,138h
fffff802`e4be3f37 488d842400010000 lea     rax,[rsp+100h]
fffff802`e4be3f3f 0f29742430      movaps  xmmword ptr [rsp+30h],xmm6
fffff802`e4be3f44 0f297c2440      movaps  xmmword ptr [rsp+40h],xmm7
fffff802`e4be3f49 440f29442450    movaps  xmmword ptr [rsp+50h],xmm8
fffff802`e4be3f4f 440f294c2460    movaps  xmmword ptr [rsp+60h],xmm9
fffff802`e4be3f55 440f29542470    movaps  xmmword ptr [rsp+70h],xmm10
fffff802`e4be3f5b 440f295880      movaps  xmmword ptr [rax-80h],xmm11
fffff802`e4be3f60 440f296090      movaps  xmmword ptr [rax-70h],xmm12
fffff802`e4be3f65 440f2968a0      movaps  xmmword ptr [rax-60h],xmm13
fffff802`e4be3f6a 440f2970b0      movaps  xmmword ptr [rax-50h],xmm14
fffff802`e4be3f6f 440f2978c0      movaps  xmmword ptr [rax-40h],xmm15
fffff802`e4be3f74 488918          mov     qword ptr [rax],rbx
fffff802`e4be3f77 48897808        mov     qword ptr [rax+8],rdi
fffff802`e4be3f7b 48897010        mov     qword ptr [rax+10h],rsi
fffff802`e4be3f7f 4c896018        mov     qword ptr [rax+18h],r12
fffff802`e4be3f83 4c896820        mov     qword ptr [rax+20h],r13
fffff802`e4be3f87 4c897028        mov     qword ptr [rax+28h],r14
fffff802`e4be3f8b 4c897830        mov     qword ptr [rax+30h],r15
fffff802`e4be3f8f 65488b1c2520000000 mov   rbx,qword ptr gs:[20h]
fffff802`e4be3f98 488bf9          mov     rdi,rcx
fffff802`e4be3f9b 488bf2          mov     rsi,rdx
fffff802`e4be3f9e 418bc8          mov     ecx,r8d
fffff802`e4be3fa1 e8ba020000      call    nt!SwapContext (fffff802`e4be4260)
fffff802`e4be3fa6 488d8c2400010000 lea     rcx,[rsp+100h]
fffff802`e4be3fae 0f28742430      movaps  xmm6,xmmword ptr [rsp+30h]
fffff802`e4be3fb3 0f287c2440      movaps  xmm7,xmmword ptr [rsp+40h]
fffff802`e4be3fb8 440f28442450    movaps  xmm8,xmmword ptr [rsp+50h]
fffff802`e4be3fbe 440f284c2460    movaps  xmm9,xmmword ptr [rsp+60h]
fffff802`e4be3fc4 440f28542470    movaps  xmm10,xmmword ptr [rsp+70h]
fffff802`e4be3fca 440f285980      movaps  xmm11,xmmword ptr [rcx-80h]
fffff802`e4be3fcf 440f286190      movaps  xmm12,xmmword ptr [rcx-70h]
fffff802`e4be3fd4 440f2869a0      movaps  xmm13,xmmword ptr [rcx-60h]
fffff802`e4be3fd9 440f2871b0      movaps  xmm14,xmmword ptr [rcx-50h]
fffff802`e4be3fde 440f2879c0      movaps  xmm15,xmmword ptr [rcx-40h]
fffff802`e4be3fe3 488b19          mov     rbx,qword ptr [rcx]
fffff802`e4be3fe6 488b7908        mov     rdi,qword ptr [rcx+8]
fffff802`e4be3fea 488b7110        mov     rsi,qword ptr [rcx+10h]
fffff802`e4be3fee 4c8b6118        mov     r12,qword ptr [rcx+18h]
fffff802`e4be3ff2 4c8b6920        mov     r13,qword ptr [rcx+20h]
fffff802`e4be3ff6 4c8b7128        mov     r14,qword ptr [rcx+28h]
fffff802`e4be3ffa 4c8b7930        mov     r15,qword ptr [rcx+30h]
fffff802`e4be3ffe 4881c438010000  add     rsp,138h
fffff802`e4be4005 c3              ret

1: kd> uf nt!SwapContext
nt!SwapContext:
...
nt!SwapContext+0xc9:
fffff802`1a9df329 0fae5918        stmxcsr dword ptr [rcx+18h]
fffff802`1a9df32d 48896758        mov     qword ptr [rdi+58h],rsp
fffff802`1a9df331 488b6658        mov     rsp,qword ptr [rsi+58h]
fffff802`1a9df335 f6470380        test    byte ptr [rdi+3],80h
fffff802`1a9df339 741c            je      nt!SwapContext+0xf7 (fffff802`1a9df357)  Branch
...

上面有一句非常重要的匯編代碼 rsp,qword ptr [rsi+58h],翻譯過來就是 esp=newThread.KernelStack,其實就是切換到新線程的內核態棧,并且在執行 nt!SwapContext 之前會進行現場保存,比如上面的 xmm 之類的寄存器,在切換完之后在新線程的同等位置上pop出這些現場。

最后一個問題是這個上下文保存在哪里呢?通過觀察是還是在 InitialStack ~ KernelStack 之間,并且比 _KTRAP_FRAME 的位置要低,畫個模型圖如下:

感興趣的朋友可以在那些能被 int 3 的 KiSwapContext 處下斷點,比較下大小即可,截圖如下:

三:總結

哈哈,是不是非常有意思,一個簡單的 Sleep(1) 涉及到兩塊的寄存器上下文,并都保存在內核線程棧的 InitialStack ~ KernelStack 區間,這也算是加深了自己對操作系統的理解,也幫一些朋友解答了一些困惑!

總結

以上是生活随笔為你收集整理的聊一聊 C# 线程切换后上下文都去了哪里的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 美女av在线播放 | 久久久久久久黄色片 | 久久久久久久久久久久久女过产乱 | 四虎8848 | 蜜桃99视频一区二区三区 | 99精品久久久久久久 | 国产在线麻豆精品观看 | 在线观看免费人成视频 | 久久久久久a | 国产三极片 | 久久精品三级 | 日本熟伦人妇xxxx | 亚洲精品无amm毛片 国内一区二区三区 | 2018自拍偷拍 | 老司机午夜福利视频 | 亚洲自拍偷拍欧美 | 狠狠五月婷婷 | 香蕉视频免费网站 | 免费在线黄色网址 | 黄色一级一片免费播放 | 中文字幕精品无码亚 | 日日夜夜免费精品 | 国产在线极品 | 午夜刺激视频 | 偷拍欧美另类 | 一道本久在线中文字幕 | 国产伦理久久精品久久久久 | 国产精品乱轮 | 午夜性福利视频 | 人妻互换一区二区三区四区五区 | 日韩亚洲精品视频 | 国产欧美a | 亚洲精品一 | 中文字幕在线看高清电影 | 欧美深性狂猛ⅹxxx深喉 | 2019国产在线 | 欧美brazzers | 熊猫成人网 | 夜色综合 | 欧洲性生活片 | 性欧美bbw | 91蝌蚪视频在线 | 小视频在线免费观看 | 性生生活性生交a级 | 欧美黄色激情视频 | 亚洲欧美日韩色 | 欧美r级在线观看 | 日韩专区一区二区三区 | 97超碰人人 | 韩国91视频| 在线中文字幕av | 老妇裸体性猛交视频 | 新版天堂资源中文8在线 | 亚州福利 | 久久国产欧美日韩精品 | 亚洲大胆| 久热久| 污污污污污污www网站免费 | 一区二区三区四区在线视频 | 亚洲视频一二区 | 成人一区二区免费视频 | 国产真实乱 | 精品人妻一区二区三区四区在线 | 国产精品分类 | 成人手机在线免费视频 | 韩日少妇| avtt在线观看 | 日韩一级影视 | 日韩视频国产 | 大陆av片 | 奇米影视777第四色 2019中文字幕在线免费观看 | 日韩免费网站 | 夜夜操av | 精品乱码一区二区三区 | 午夜精品久久久久久久99黑人 | 天堂中文字幕av | 日本国产在线观看 | 91精品日韩 | 黄色网址在线播放 | 国产精品无码一区二区三区免费 | 伊人一道本| 欧美一级视频在线观看 | av黄色网址 | 无码人妻久久一区二区三区蜜桃 | 日韩激情影院 | 欧美成人黑人xx视频免费观看 | 美女隐私免费看 | 日韩成人精品视频 | 激情福利在线 | 十八岁世界在线观看高清免费韩剧 | 91丝袜呻吟高潮美腿白嫩 | 伊人青青草 | av黄色在线观看 | 中国男人操女人 | 欧美日韩亚洲色图 | 美女黄色小视频 | 草久久 | 狠狠干狠狠操视频 | 在线观看视频你懂得 |