因為在X86上long會被分割為兩個int進行操作, 那麼Interlocked.Increment的實現成為了一個問題。 在一番搜索後未發現有現成的文章解釋這個問題,於是我就動手分析了。 這篇是筆記,不會做過多的解釋。 首先重現環境是 .Net Core 2.0 Windows (x86) Bin ...
因為在X86上long會被分割為兩個int進行操作, 那麼Interlocked.Increment的實現成為了一個問題。
在一番搜索後未發現有現成的文章解釋這個問題,於是我就動手分析了。
這篇是筆記,不會做過多的解釋。
首先重現環境是 .Net Core 2.0 Windows (x86) Binaries, 下載可以到 https://www.microsoft.com/net/download/core#/sdk
重現的代碼如下
using System;
using System.Threading;
namespace x86program
{
class Program
{
static void Main(string[] args)
{
Console.ReadLine();
Console.WriteLine("Hello World!");
Console.ReadLine();
long a = 0x1234567887564321;
Console.WriteLine(Interlocked.Increment(ref a));
}
}
}
需要的工具:
Visual Studio 2017 15.3 RTM
OllyDbg 1.1 (or 2.0)
首先下載了x86版的dotnet後,解壓然後在命令行運行
F:\dotnet-sdk-2.0.0-win-x86\x86program>..\dotnet.exe run
運行後使用Visual Studio打開項目然後"附加到進程", 成功後在下圖中的地方下斷點
然後命令行回車, 可以觸發此斷點, 我們可以看到 Interlocked.Increment 調用了 0x76A7CA0 處的函數, 傳入參數只有一個, 就是指向long變數的指針
之後在Visual Studio中取消附加, 然後使用ollydbg的Attach, 成功後在 0x76A7CA0 下斷點
這個函數調用了函數 0x7428BE0, 傳入了分割為兩個int的long (1), ecx仍然指向原來的long變數
函數 0x7428BE0 是一個JIT樁(Stub), 第一次調用會觸發JIT編譯, 第二次調用會跳到JIT編譯結果
JIT編譯後的實現就在 0x3410F40 處,這裡的就是 Interlocked.Increment(ref long) 的實現
我們可以看到這個實現使用了x86的CMPXCHG8B指令,如果出現long變數的值被其他線程修改,會檢測出來並重試添加
當然,不是所有x86的CPU都支持CMPXCHG8B指令, 但至少可以運行.Net Core的CPU都會支持此指令, 也就是說實現 atomic long 不需要semaphore
http://www.geoffchappell.com/studies/windows/km/cpu/cx8.htm
上面的代碼是Debug下編譯得到的, Release下同樣需要調用一個helper函數, 內部的邏輯是一樣的
x64位上的 Interlocked.Increment(ref long) 就很簡單了, 可以使用現成的指令 lock xadd
分析到此為止
=================================================================================
微軟最近發佈了新的JIT文檔,比原來的文檔要容易理解很多,有興趣的可以去圍觀:
https://github.com/dotnet/coreclr/blob/master/Documentation/botr/ryujit-tutorial.md
下一篇CoreCLR源碼探索會講解JIT是如何實現的,但是還需要最少一個月的時間,有興趣的請耐心等待。