程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 揭示同步塊索引(中):如何獲得對象的HashCode

揭示同步塊索引(中):如何獲得對象的HashCode

編輯:關於.NET

Visual Studio + SOS 小實驗

咋一看標題,覺得有些奇怪,同步塊索引和HashCode有啥關系呢。從名字上來看離著十萬八千裡。在 不知道細節之前,我也是這樣想的,知道細節之後,才發現這兩兄弟如此親密。我們還是先來用Visual Studio + SOS,看一個東西,下面是作為小白兔的示例代碼:

1: using System;
  2: public class Program
  3: {
  4:    static void Main()
  5:   {
  6:     Foo f = new Foo();
  7:      Console.WriteLine(f.GetHashCode());
  8:
  9:      Console.ReadLine();
 10:   }
 11: }
 12: //就這麼一個簡單的類
 13:  public class Foo
 14: {
 15:
 16: }

(使用Visual Studio + SOS調試的時候,請先在項目的屬性,調試欄裡設置“允許非托管代碼調試” )

我們分別在第7行,第9行設置斷點,F5運行,當程序停在第一個斷點處時(此時f.GetHashCode()還沒 有執行),我們在Visual Studio的立即窗口裡輸入:

1: .load sos.dll
  2: extension C:\Windows\Microsoft.NET\Framework\v2.0.50727\sos.dll loaded
  3:  !dso
  4: PDB symbol for mscorwks.dll not loaded
  5: OS Thread Id:  0x1730 (5936)
  6: ESP/REG Object  Name
  7: 0013ed78 01b72d58 Foo
   8: 0013ed7c 01b72d58 Foo
  9: 0013efc0 01b72d58 Foo
 10: 0013efc4 01b72d58  Foo

使用.load sos.dll加載sos模塊,然後使用!dso,我們找到了Foo類型的f對象的內存地址:01b72d58 ,然後使用Visual Studio調試菜單下的查看內存的窗口,查看f對象頭部的內容:

陰影遮住的00 00 00 00就是同步塊索引所在的地方了,可以看得出來,此時同步塊索引的值還是0( 後面會對這個做解釋),然後繼續F5,程序運行到下一個斷點處,這個時候f.GetHashCode()也已調用了 ,細心的你就會發現,原來對象同步塊索引所在的地方的值變了:

Visual Studio這個內存查看器有個很好的功能,對內存變化的以紅色標出。我們看到,原來是00 00 00 00變成了現在的4a 73 78 0f。嗯,看來HashCode的獲取和同步塊索引還是有一些關系的,不然調用 GetHashCode方法為什麼同步塊索引的值會變化呢。再來看看Console.WriteLine(f.GetHashCode())的輸 出:

不知道著兩個值有沒有什麼關系,我們先把它們都換算成二進制吧。注意,這裡的4a 73 78 0f是低位 在左,高位在右,下面的十進制是高位再左,低位在右,那4a 73 78 0f實際上就是0x0f78734a了。

0x0f78734a:00001111011110000111001101001010

58225482:00000011011110000111001101001010

Rotor源代碼

我們先用0補齊32位,突然發現這兩者低26位居然是一模一樣的(紅色標出的部分),這是巧合還是必 然?為了一探究竟只好搬出Rotor的源代碼,從源代碼裡看看是否能發現什麼東西。還是遵循老路子,我 們先從托管代碼開始:

1: public virtual int GetHashCode()
  2: {
  3:  return  InternalGetHashCode(this);
  4: }
  5: [MethodImpl (MethodImplOptions.InternalCall)]
  6: internal static extern int  InternalGetHashCode(object obj);

在本系列的第一篇文章已經提到過,標記有[MethodImpl(MethodImplOptions.InternalCall)]特性的 方法是使用Native Code的方式實現的,在Rotor中,這些代碼位於sscli20\clr\src\vm\ecall.cpp文件中 :

1: FCFuncElement("InternalGetHashCode", ObjectNative::GetHashCode)
  2:  FCIMPL1(INT32, ObjectNative::GetHashCode, Object* obj) {
  3:   DWORD idx =  0;
  4:   OBJECTREF objRef(obj);
  5:   idx = GetHashCodeEx (OBJECTREFToObject(objRef));
  6:   return idx;
  7: }
  8:  FCIMPLEND
  9: INT32 ObjectNative::GetHashCodeEx(Object *objRef)
 10: {
  11:   // This loop exists because we're inspecting the header dword of the  object
 12:   // and it may change under us because of races with other  threads.
 13:   // On top of that, it may have the spin lock bit set, in  which case we're
 14:   // not supposed to change it.
 15:   // In  all of these case, we need to retry the operation.
 16:   DWORD iter =  0;
 17:   while (true)
 18:   {
 19:     DWORD bits = objRef- >GetHeader()->GetBits();
 20:  
 21:     if (bits &  BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX)
 22:     {
 23:       if (bits &  BIT_SBLK_IS_HASHCODE)
 24:       {
 25:         // Common case:  the object already has a hash code
 26:         return bits &  MASK_HASHCODE;
 27:       }
 28:       else
 29:        {
 30:         // We have a sync block index. This means if we  already have a hash code,
 31:         // it is in the sync block,  otherwise we generate a new one and store it there
 32:          SyncBlock *psb = objRef->GetSyncBlock();
 33:         DWORD hashCode =  psb->GetHashCode();
 34:         if (hashCode != 0)
 35:            return hashCode;
 36:  
 37:         hashCode =  Object::ComputeHashCode();
 38:  
 39:         return psb- >SetHashCode(hashCode);
 40:       }
 41:     }
 42:      else
 43:     {
 44:       // If a thread is holding the thin  lock or an appdomain index is set, we need a syncblock
 45:       if  ((bits & (SBLK_MASK_LOCK_THREADID | (SBLK_MASK_APPDOMAININDEX <<  SBLK_APPDOMAIN_SHIFT))) != 0)
 46:       {
 47:         objRef- >GetSyncBlock();
 48:         // No need to replicate the above code  dealing with sync blocks
 49:         // here - in the next  iteration of the loop, we'll realize
 50:         // we have a  syncblock, and we'll do the right thing.
 51:       }
 52:        else
 53:       {
 54:         DWORD hashCode =  Object::ComputeHashCode();
 55:  
 56:         DWORD newBits = bits |  BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX | BIT_SBLK_IS_HASHCODE | hashCode;
 57:  
  58:         if (objRef->GetHeader()->SetBits(newBits, bits) == bits)
 59:           return hashCode;
 60:         // Header changed  under us - let's restart this whole thing.
 61:       }
 62:      }
 63:   }
 64: }

代碼很多,不過大部分操作都是在做與、或、移位等。而操作的對象就是這行代碼獲取的:objRef- >GetHeader()->GetBits(),實際上就是獲取同步塊索引。

想想,在第一個斷點命中的時候,同步塊索引的值還是0x00000000,那應該是下面這塊代碼執行:

1: DWORD hashCode = Object::ComputeHashCode();
  2: DWORD newBits =  bits | BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX | BIT_SBLK_IS_HASHCODE | hashCode;
  3:  if (objRef->GetHeader()->SetBits(newBits, bits) == bits)
  4:   return  hashCode;

通過Object的ComputeHashCode方法算出一個哈希值來(由於本文不是關注哈希算法的,所以這裡不討 論這個ComputeHashCode方法的實現)。然後進行幾個或操作(這裡還要與原先的bits或操作是為了保留 原來的值,說明這個同步塊索引還起了別的作用,比如上篇文章的lock),然後將同步塊索引中老的位換 掉。從這裡我們還看不出來什麼。不過,如果我們再次對這個對象調用GetHashCode()方法呢?那同步塊 索引不再為0x00000000,而是0x0f78734a,在來看看幾個定義的常量的值:

1: #define BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX  0x08000000
  2: #define  BIT_SBLK_IS_HASHCODE      0x04000000
  3: #define HASHCODE_BITS           26
  4: #define MASK_HASHCODE          ((1<<HASHCODE_BITS)-1)

從剛才設置hashcode的地方可以看到:DWORD newBits = bits | BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX | BIT_SBLK_IS_HASHCODE | hashCode;

所以開頭的兩個if都可以通過了,返回的hashcode就是bits & MASK_HASHCODE。

這個MASK_HASHCODE是將1向左移26位=100000000000000000000000000,然後減 1=00000011111111111111111111111111(低26位全部為1,高6位為0),然後與同步塊索引相與,其實這 裡的作用不就是為了取出同步塊索引的低26位的值麼。再回想一下本文開頭的那個試驗,原來不是巧合啊 。

連上上一篇,我們可以看到同步塊索引不僅僅起到lock的作用,有時還承擔著存儲HashCode的責任。 實際上同步塊索引是這樣的一個結構:總共32位,高6位作為控制位,後26的具體含義隨著高6位的不同而 變化,高6位就像很多小開關,有的打開(1),有的關閉(0),不同位的打開和關閉有著不同的意義,程序 也就知道低26位到底是干啥的了。這裡的設計真是巧妙,不斷占用內存很緊湊,程序也可以靈活處理,靈 活擴展。

後記

本篇和上一篇一樣,都是單獨將獨立的內容拿出來,這樣可以更簡單的來闡述。比如在本文中,我只 設想同步塊索引做hashcode的存儲,這個時候,同步塊索引就干干淨淨(本文前面的試驗中先得到的同步 塊索引就是一個0),但實際中同步塊索引可能擔任更多的職責,比如既lock,又要獲取HashCode,這個 時候情況就更復雜,這個在後面一篇文章會綜合各種情況更詳細的說明。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved