最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

簡單聊聊C#的線程本地存儲TLS到底是什么

 更新時間:2024年01月03日 14:17:47   作者:一線碼農(nóng)  
C#的ThreadStatic是假的,因?yàn)镃#完全是由CLR(C++)承載的,言外之意C#的線程本地存儲,用的就是用C++運(yùn)行時提供的 __declspec(thread)或__thread來虛構(gòu)的一套玩法,下面我們就來深入講講C#的線程本地存儲TLS到底是什么吧

一:背景

1. 講故事

有朋友在后臺留言讓我說一下C#的 ThreadStatic 線程本地存儲是怎么玩的?這么說吧,C#的ThreadStatic是假的,因?yàn)镃#完全是由CLR(C++)承載的,言外之意C#的線程本地存儲,用的就是用C++運(yùn)行時提供的 __declspec(thread) 或 __thread 來虛構(gòu)的一套玩法,這一篇我們就來簡單聊一聊。

二:C# 的線程本地存儲

1. 虛構(gòu)在哪里

在 C# 中使用ThreadStatic就可以將變量和線程進(jìn)行綁定,參考代碼如下:

    internal class Program
    {
        [ThreadStatic]
        public static int num = 10;

        static void Main(string[] args)
        {
            Console.WriteLine($"num={num}");

            Debugger.Break();
        }
    }

在 CLR 中如何將 num 與 Thread 綁定呢?研究過 CLR 源碼的朋友應(yīng)該知道是用 ThreadLocalInfo 的,參考代碼如下:

#ifdef _MSC_VER
__declspec(selectany) __declspec(thread) ThreadLocalInfo gCurrentThreadInfo;
#else
EXTERN_C __thread ThreadLocalInfo gCurrentThreadInfo;
#endif

struct ThreadLocalInfo
{
    Thread* m_pThread;
    AppDomain* m_pAppDomain; // This field is read only by the SOS plugin to get the AppDomain
    void** m_EETlsData; // ClrTlsInfo::data
};

上面的 m_pThread 就是 C# Thread 在 CLR 層面的承載,怎么去驗(yàn)證呢?可以把代碼跑起來,然后用 windbg 驗(yàn)證一下。

0:000> dt coreclr!gCurrentThreadInfo
   +0x000 m_pThread        : 0x000001e3`506c5fa0 Thread
   +0x008 m_pAppDomain     : 0x000001e3`506ba9b0 AppDomain
   +0x010 m_EETlsData      : 0x000001e3`506aa360  -> (null) 

0:000> !t
ThreadCount:      3
UnstartedThread:  0
BackgroundThread: 2
PendingThread:    0
DeadThread:       0
Hosted Runtime:   no
                                                                                                            Lock  
 DBG   ID     OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception
   0    1     2e04 000001E3506C5FA0    2a020 Preemptive  000001E3521DCE80:000001E3521DD4A8 000001e3506ba9b0 -00001 MTA 
   6    2     4ef8 000001E3506F1A30    21220 Preemptive  0000000000000000:0000000000000000 000001e3506ba9b0 -00001 Ukn (Finalizer) 
   7    3     3550 000001E3726A0AE0    2b220 Preemptive  0000000000000000:0000000000000000 000001e3506ba9b0 -00001 MTA 

從卦中可以清楚的看到 m_pThread=0x000001e3506c5fa0 就是我們的主線程,最后的 num 就是放在與之關(guān)聯(lián)的 ThreadLocalModule 中,這個比較簡單,關(guān)注下匯編代碼就好了,下面的 rax 就是 ThreadLocalModule。

00007ffb`218d2c2c 48b9b07b9921fb7f0000 mov rcx,7FFB21997BB0h
00007ffb`218d2c36 ba04000000      mov     edx,4
00007ffb`218d2c3b e8001fb55f      call    coreclr!JIT_GetSharedNonGCThreadStaticBase (00007ffb`81424b40)
00007ffb`218d2c40 8b4820          mov     ecx,dword ptr [rax+20h]
00007ffb`218d2c43 894dfc          mov     dword ptr [rbp-4],ecx

0:000> dp rax+0x20 L1
00000294`d0539790  abababab`0000000a

CLR層面用了太多的高層虛構(gòu)來玩了一套線程本地存儲,其實(shí)最核心的還要理解再下一層的 __declspec(selectany) ,接下來聊聊這玩意是怎么玩的。

2. __declspec(selectany) 是怎么玩的

在Windows層面的術(shù)語中,有兩種 TLS 技術(shù)。

動態(tài)TLS

借助 Windows 提供的 TlsAlloc, TlsSetValue 之類的方法來實(shí)現(xiàn),并且存放在線程 _TEB.TlsSlots 的槽位中,參考代碼如下:

0:000> dt 0x000000f4f0ca6000 ntdll!_TEB
   +0x000 NtTib            : _NT_TIB
   ...
   +0x1480 TlsSlots         : [64] (null) 
   ...

靜態(tài)TLS

C#的線程本地存儲用的就是靜態(tài)TLS,也就是在編譯時就已經(jīng)聲明好的,在 PE 文件里面有一個 .tls 節(jié)點(diǎn),這個節(jié)點(diǎn)的數(shù)據(jù)會被每個線程在heap堆上copy一份,存放在 _TEB.ThreadLocalStoragePointer 來指向的指針數(shù)組中,參考代碼如下:

0:000> dt 0x000000f4f0ca6000 ntdll!_TEB
   +0x000 NtTib            : _NT_TIB
   +0x058 ThreadLocalStoragePointer : 0x00000294`d0536ab0 Void
   ...

動態(tài)的TLS我就不介紹了,這里著重說一下靜態(tài)的TLS。

3. 靜態(tài)TLS詳解

為了方便講解,先上一段測試代碼。

#include <windows.h>
#include <stdio.h>
#include <limits.h>


__declspec(thread) int i = INT_MAX;
__declspec(thread) int j = INT_MAX;

int main() {
	int num1 = i;
	int num2 = j;
	printf("i=%d,j=%d", num1, num2);
}

上面的 i,j 值在編譯時就已經(jīng)放到了 PE 頭的 .tls 節(jié),可以用 PPEE 觀察下對象頭。

從卦中可以看到 .tls 占用了 0x400 字節(jié)大小,并且用 WinHex 真的觀察到了 i,j 的值,挺有意思。

在內(nèi)存中TLS區(qū)比這個還小一點(diǎn),可以觀察一下 DIRECTORY_ENTRY_TLS 節(jié)的 StartAddressOfRawData 和 EndAddressOfRawData 字段,這也是每個線程copy的原始內(nèi)存區(qū)域,可以看到只有 0x20D ,大概少了一半,截圖如下:

有了這些前置知識,接下來觀察內(nèi)存中的地址,在運(yùn)行之前先把 ASLR 關(guān)掉,匯編代碼參考如下:

   //int num1 = i;
   14 00411895 a1b4a14100      mov     eax,dword ptr [ConsoleApplication2!_tls_index (0041a1b4)]
   14 0041189a 648b0d2c000000  mov     ecx,dword ptr fs:[2Ch]
   14 004118a1 8b1481          mov     edx,dword ptr [ecx+eax*4]
   14 004118a4 8b8208010000    mov     eax,dword ptr [edx+108h]
   14 004118aa 8945f8          mov     dword ptr [ebp-8],eax

   //int num2 = j;
   15 004118ad a1b4a14100      mov     eax,dword ptr [ConsoleApplication2!_tls_index (0041a1b4)]
   15 004118b2 648b0d2c000000  mov     ecx,dword ptr fs:[2Ch]
   15 004118b9 8b1481          mov     edx,dword ptr [ecx+eax*4]
   15 004118bc 8b8204010000    mov     eax,dword ptr [edx+104h]
   15 004118c2 8945ec          mov     dword ptr [ebp-14h],eax

可以看到每一句大概會生成 5 行匯編代碼,我們簡單分析下。

ConsoleApplication2!_tls_index (0041a1b4)

這個值就是 PE 頭的 AddressOfIndex 值,可以再回頭觀察下,里面存的就是 tls 索引,當(dāng)前是 0 ,參考如下:

0:000> dp 0041a1b4 L1
0041a1b4  00000000

fs:[2Ch]

在用戶態(tài)層面上 fs 指向的是當(dāng)前線程的 TEB 結(jié)構(gòu),其中的 2C 偏移指的就是 ThreadLocalStoragePointer 結(jié)構(gòu),windbg 觀察如下:

0:000> dg fs
                                  P Si Gr Pr Lo
Sel    Base     Limit     Type    l ze an es ng Flags
---- -------- -------- ---------- - -- -- -- -- --------
0053 002bc000 00000fff Data RW Ac 3 Bg By P  Nl 000004f3

0:000> dt 0x002bc000 ntdll!_TEB
   +0x000 NtTib            : _NT_TIB
   +0x01c EnvironmentPointer : (null) 
   +0x020 ClientId         : _CLIENT_ID
   +0x028 ActiveRpcHandle  : (null) 
   +0x02c ThreadLocalStoragePointer : 0x00664400 Void
   ...

edx,dword ptr [ecx+eax*4]

這句匯編是一個數(shù)組操作,翻譯成 C 就是 ThreadLocalStoragePointer[tls]。

0:000> dp 0x00664400 L1
00664400  00664448

這里要提醒的是:上面的 00664448 所在的 heap 位置其實(shí)就是 PE 頭里的 StartAddressOfRawData~EndAddressOfRawData內(nèi)存區(qū)域的 copy,截圖如下:

eax,dword ptr [edx+108h]

這句話的意思就是在 數(shù)組元素1 這個結(jié)構(gòu)上偏移108的位置存放著我們的 num 值,用 windbg 觀察之后果然就是的。

0:000> dp 00664448+0x108 L1
00664550  7fffffff

三:總結(jié)

C# 屬于一種業(yè)務(wù)高層抽象的語言,它的很多底層被C++再次隔離了,想要理解本篇的TLS,還得需要往下一層一層的擊穿,作為C#程序員太難了。

到此這篇關(guān)于簡單聊聊C#的線程本地存儲TLS到底是什么的文章就介紹到這了,更多相關(guān)C#線程本地存儲TLS內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • c#入門之類型轉(zhuǎn)換詳解

    c#入門之類型轉(zhuǎn)換詳解

    這篇文章主要介紹了c#的類型轉(zhuǎn)換詳解,類型轉(zhuǎn)換分兩種形式:隱式轉(zhuǎn)換、顯示轉(zhuǎn)換,下面是詳細(xì)介紹
    2014-04-04
  • C#中的ComboBox控件詳細(xì)使用方法

    C#中的ComboBox控件詳細(xì)使用方法

    這篇文章主要給大家介紹了關(guān)于C#中ComboBox控件詳細(xì)使用的相關(guān)資料,ComboBox控件是一個很容易使用出錯的控件,在此將常用的操作寫下來,以備后用,需要的朋友可以參考下
    2023-09-09
  • C#從DataTable獲取數(shù)據(jù)的方法

    C#從DataTable獲取數(shù)據(jù)的方法

    這篇文章主要介紹了C#從DataTable獲取數(shù)據(jù)的方法,涉及C#操作DataTable的相關(guān)技巧,需要的朋友可以參考下
    2015-06-06
  • C# Page用于各頁面繼承功能實(shí)例

    C# Page用于各頁面繼承功能實(shí)例

    這篇文章主要介紹了C# Page用于各頁面繼承功能實(shí)例,包含了常見的頁面視圖、數(shù)據(jù)緩存、數(shù)據(jù)庫操作等技巧,需要的朋友可以參考下
    2014-10-10
  • C#字符串與數(shù)值類型、字節(jié)數(shù)組的互相轉(zhuǎn)換實(shí)戰(zhàn)案例

    C#字符串與數(shù)值類型、字節(jié)數(shù)組的互相轉(zhuǎn)換實(shí)戰(zhàn)案例

    最近由于編程的需要,對C#的類型轉(zhuǎn)換做了一些研究,下面這篇文章主要給大家介紹了關(guān)于C#字符串與數(shù)值類型、字節(jié)數(shù)組的互相轉(zhuǎn)換的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-06-06
  • Response.Redirect 正在中止線程解決方案

    Response.Redirect 正在中止線程解決方案

    這兩天在開發(fā)調(diào)試過程中,老是會出現(xiàn)在一個 "正在中止線程“(ThreadAbortException)的例外信息,很是疑惑,于是網(wǎng)上收集整理了一下,現(xiàn)在曬出來和大家分享,希望對你們有幫助
    2012-11-11
  • C#特性(Attribute)

    C#特性(Attribute)

    這篇文章介紹了C#的特性(Attribute),文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-03-03
  • C#判斷當(dāng)前程序是否通過管理員運(yùn)行的方法

    C#判斷當(dāng)前程序是否通過管理員運(yùn)行的方法

    這篇文章主要介紹了C#判斷當(dāng)前程序是否通過管理員運(yùn)行的方法,可通過非常簡單的系統(tǒng)函數(shù)調(diào)用實(shí)現(xiàn)對當(dāng)前程序是否通過管理員運(yùn)行進(jìn)行判定,是非常實(shí)用的技巧,需要的朋友可以參考下
    2014-11-11
  • C#中如何使用redis

    C#中如何使用redis

    這篇文章主要介紹了C#中如何使用redis,文中示例代碼非常詳細(xì),幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下
    2020-07-07
  • C#將Excel文件轉(zhuǎn)換為Markdown表格的三行代碼

    C#將Excel文件轉(zhuǎn)換為Markdown表格的三行代碼

    在撰寫技術(shù)文檔或整理數(shù)據(jù)時,我們常常需要將 Excel 表格轉(zhuǎn)為 Markdown 格式,以便嵌入到博客、項(xiàng)目文檔或 README 中,因此本文將展示如何使用 C# 和 Spire.XLS 庫,通過一行核心方法快速將 Excel 文件保存為 Markdown 文件,需要的朋友可以參考下
    2026-03-03

最新評論

石河子市| 石棉县| 梅河口市| 武威市| 丰县| 和政县| 枣强县| 金秀| 马边| 江门市| 饶阳县| 新乡县| 罗田县| 厦门市| 靖宇县| 连山| 普定县| 中山市| 浦东新区| 凭祥市| 乾安县| 宝丰县| 德江县| 景洪市| 新泰市| 南召县| 德江县| 武威市| 札达县| 上栗县| 平利县| 兴化市| 合肥市| 和龙市| 青田县| 奉贤区| 衡水市| 皮山县| 故城县| 刚察县| 新巴尔虎右旗|