內容提綱
• 託管代碼與非託管代碼介紹
• 不安全代碼介紹
• 用戶模式與內核模式
• ETW執行流程分析
• 日誌分析工具介紹:PerfView.exe
ETW與非託管代碼
• ETW依賴的SourceEvent和TraceEvent的類庫中有不少非託管代碼。
• 而SourceEvent和TraceEvent類庫又依賴最底層的非託管的advapi32.dll來完成實際工做。
• advapi32.dll 全稱是:Advanced Windows 32 Base API DLL,它是一個高級API應用程序接口服務庫的一部分,包含的函數與對象的安全性,註冊表的操控以及事件日誌有關。此文件大小是659KB,通常位於C:\WINDOWS\system32\目錄下。
• 所以,爲了更好的理解ETW,就須要瞭解非託管代碼。
託管代碼&非託管代碼
• 託管代碼 (managed code)
由公共語言運行庫環境(而不是直接由操做系統)執行的代碼。託管代碼應用程序能夠得到公共語言運行庫服務,例如自動垃圾回收、運行庫類型檢查和安全支持等。這些服務幫助提供獨立於平臺和語言的、統一的託管代碼應用程序行爲。
託管代碼是可使用20多種支持Microsoft .NET Framework的高級語言編寫的代碼,它們包括:C#, J#, Microsoft Visual Basic .NET, Microsoft JScript .NET, 以及C++。全部的語言共享統一的類庫集合,並能被編碼成爲中間語言(IL)。運行庫編譯器(runtime-aware ompiler)在託管執行環境下編譯中間語言(IL)使之成爲本地可執行的代碼,並使用數組邊界和索引檢查,異常處理,垃圾回收等手段確保類型的安全。
在託管執行環境中使用託管代碼及其編譯,能夠避免許多典型的致使安全黑洞和不穩定程序的編程錯誤。一樣,許多不可靠的設計也自動的被加強了安全性,例如 類型安全檢查,內存管理和釋放無效對象。程序員能夠花更多的精力關注程序的應用邏輯設計並能夠減小代碼的編寫量。這就意味着更短的開發時間和更健壯的程序。
• 非託管代碼 (unmanaged code)
在公共語言運行庫環境的外部,由操做系統直接執行的代碼。非託管代碼必須提供本身的垃圾回收、類型檢查、安全支持等服務;它與託管代碼不一樣,後者從公共語言運行庫中得到這些服務。
託管代碼與非託管代碼的性能比較
• 衆所周知,全部.Net語言都將被編譯成爲一個叫作IL彙編的中間語言。可是計算機是如何執行這個中間代碼的,倒是不少人不知道,甚至理解錯誤了。JIT是.NET程序運行的重要部件之一,全稱是即時編譯器。不少人都覺得JIT其實就是跟Java VM差很少的東西,是一個Interpreter,在運行時讀取IL彙編代碼,而後模擬成x86代碼(也就是俗稱的虛擬機)。可是事實上,.NET使用的是更爲高級的技術。 .Net程序被加載入內存之後,當某段IL代碼被第一次運行的時候,JIT編譯器就會將這段IL代碼,所有編譯成本地代碼,而後再執行。這也就是爲何.NET程序第一次運行都啓動很慢的緣由!
• 隨.NET庫,微軟還附帶了一個工具,能夠事先將.NET程序全部的IL代碼都編譯成本地代碼並保存在緩存區中,這樣一來,這個程序就跟c++編譯的如出一轍了,沒有任何區別,運行時也能夠脫離JIT了(這裏不要混淆了,這裏不是說能夠脫離.NET庫,而是說不須要在進行即時編譯這個過程了)。因此,請不要將.NET和Java混爲一談,兩個的運行效率根本不是一個等級的!
• JIT的優化指的是能夠針對本地CPU,在編譯時進行優化。傳統程序在編譯時,爲了保證兼容性,一般使用最通用的指令集(好比古老的386指令集)來編譯。而JIT知道CPU的具體類型,能夠充分利用這些附加指令集進行編譯,這樣的性能提高是很可觀的。
P/Invoke是什麼?有何做用?
• 問題:C#有沒有方法能夠直接都用這些本來已經存在的功能(好比Windows中的一些功能,C++中已經編寫好的一些方法)?答案是確定的,能夠經過P/Invoke的方式直接調用這些功能。
• P/Invoke = Platform Invoke,平臺調用服務。
• 平臺調用是CLR(公共語言運行時)提供的一種服務。平臺調用服務 (PInvoke) 容許託管代碼調用在 DLL 中實現的非託管函數。
• 例如:能夠經過P/Invoke使得C#程序來調用非託管的win32 API 。
C# 代碼有如下兩種能夠直接調用非託管代碼的方法。
• 方法一:直接調用從 DLL 導出的函數。
• 方法二:調用 COM 對象上的接口方法(更多信息,請參見 COM Interop教程)。
對於這兩種技術,都必須向 C# 編譯器提供非託管函數的聲明,而且還可能須要向 C# 編譯器提供如何封送與非託管代碼之間傳遞的參數和返回值的說明。
方法一:P/Invoke調用win32 API Demo
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.InteropServices; namespace ConsoleAppUnsafe { class Program { [DllImport("kernel32.dll", EntryPoint = "MoveFile", ExactSpelling = false, CharSet = CharSet.Unicode, SetLastError = true)] static extern bool MoveFile(string source, string dest); static void Main(string[] args) { string source = @"d:\Temp\20150528_041232_659.etl"; string dest = @"d:\20150528_041232_659.etl"; if (Program.MoveFile(source, dest) == true) { Console.WriteLine("Move File Ok!"); } else { Console.WriteLine("Move File Error!"); } Console.Read(); } } }
C#中extern 是什麼意思?
• extern 修飾符用於聲明在外部實現的方法。
• extern 修飾符的常見用法是在使用 Interop 服務調入非託管代碼時與 DllImport 屬性一塊兒使用。
• 在這種狀況下,還必須將方法聲明爲 static。
• 例如:
DllImport("avifil32.dll")]
private static extern void AVIFileInit();
也就是說這個方法是放在申明的類以外的類中實現 的.
C#中DllImport 屬性是什麼意思?
• MSDN中對DllImportAttribute的解釋是這樣的:可將該屬性應用於方法。DllImportAttribute 屬性提供對從非託管 DLL 導出的函數進行調用所必需的信息。做爲最低要求,必須提供包含入口點的 DLL 的名稱。
• DllImport是System.Runtime.InteropServices命名空間下的一個屬性類,其功能是提供從非託管DLL導出的函數的必要調用信息。
• DllImport屬性應用於方法,要求最少要提供包含入口點的dll的名稱。
什麼是不安全代碼unsafe?
• 在公共語言運行庫 (CLR) 中,不安全代碼是指沒法驗證的代碼。C# 中的不安全代碼不必定是危險的,只是其安全性沒法由 CLR 進行驗證的代碼。所以,CLR 只對在徹底受信任的程序集中的不安全代碼執行操做。若是使用不安全代碼,由您負責確保您的代碼不會引發安全風險或指針錯誤。
• MSDN:unsafe 關鍵字表示不安全上下文,該上下文是任何涉及指針的操做所必需的。
• 在 C# 中使用不安全代碼unsafe,指的就是使用指針的代碼。
• 指針能夠看作是對內存的直接操做,一旦沒用好就會出現問題,致使其餘的軟件部能運行,甚至系統崩潰,c#將指針定義爲不安全代碼,是爲了防止這些問題出現,你能夠理解爲使用不安全代碼是告訴系統要「當心」。
爲什麼要有unsafe?
• 爲了實現CLR類型安全的目標,默認狀況下,C#沒有提供指針的使用算法。
• 可是有時候程序員很是清楚程序的運行情況,須要使用指針直接訪問內存以便於提升性能或者調試、監控程序運行的內存的使用情況,以便於採起相應的措施。
• 在 C# 中不多須要使用指針,但仍有一些須要使用的狀況。例如,在下列狀況中使用容許採用指針的不安全上下文是正確的:
1.處理磁盤上的現有結構。
2.涉及內部包含指針結構的高級 COM 或平臺調用方案。
3.性能關鍵代碼。
• 使用不安全代碼的狀況有:
1.使用指針的不安全代碼。
2.方法、類型和可被定義爲不安全的代碼塊。
3.在某些狀況下,經過移除數組界限檢查,不安全代碼可提升應用程序的性能。
c#裏面指針爲何是不安全代碼?
• 指針能夠看作是對內存的直接操做,一旦沒用好就會出現問題,致使其餘的軟件部能運行,甚至系統崩潰,c#將指針定義爲不安全代碼,是爲了防止這些問題出現,你能夠理解爲使用不安全代碼是告訴系統要「當心」。
• 指針的使用十分不安全因此Java摒棄了。但也十分方便,因此c#保存下來了,爲了解決安全問題就定義爲unsafe。
什麼是 IntPtr?
• 什麼是IntPtr?
先來看看MSDN上說的:用於表示指針或句柄的平臺特定類型。IntPtr是託管環境中用來描述非託管環境中指針的類型。
這個其實說出了這樣兩個事實,IntPtr 能夠用來表示指針或句柄、它是一個平臺特定類型。
• 對它的解釋
It's a class that wraps a pointer that is used when calling Windows API functions. The underlying pointer may be 32 bit or 64 bit, depending on the platform.
• 用在什麼地方?
(1)C#調用WIN32 API時
(2)C#調用C/C++寫的DLL時(其實和1相同,只是這個通常是咱們在和他人合做開發時常常用到)
怎樣用 IntPtr?
• 例若有一非託管DLL中的函數原型爲:
MCIERROR mciSendString(LPCTSTR lpszCommand, LPTSTR
lpszReturnString, UINT cchReturn, HANDLE hwndCallback);
• 那麼咱們在C#中聲明時就要這樣寫:
[DllImport("winmm.dll")]
private static extern long mciSendString(string a,string b,uint c, IntPtr d);
• 在調用的時候就能夠用這樣的方法調用,最後一個參數傳入某一控件的Handle :
mciSendString("set cdaudio door open", null, 0, this.Handle);
回顧梳理不安全代碼與非託管代碼
• 託管代碼是指在CLR運行環境的監控下運行的代碼。CLR運行環境將負責處理各類「家務」工做,好比:管理對象的內存,執行類型檢查,進行內存垃圾回收。總之,用戶不用本身處理上面提到的工做。用戶不用本身去直接操做內存,由於CLR運行環境將處理這些問題。
• 非託管代碼是指在CLR環境之外運行的代碼。這個概念最好的例子就是咱們傳統的WIN 32 DLL例如Kernel32.dll , user32.dll 和 安裝在咱們系統上的COM組件。如何爲其分配內存空間,如何釋放內存,怎麼(若是須要)進行類型檢測這些工做由本身來作。典型的C++編程中內存分配指針指向也是非託管代碼的另外一例子,由於你做爲程序員須要本身來負責處理這些工做:調用內存分配函數, 確保生成的正確性,確保當任務結束時候內存被釋放。
• 不安全代碼是託管代碼與非託管代碼之間的紐帶。
• 不安全代碼在CLR託管環境的監管下運行, 就像託管代碼那樣, 但容許你經過使用指針直接訪問內存, 就像非託管代碼中的作法那樣。這樣, 你同時得到了兩個世界裏最好的東西。你也許要寫的程序須要使用傳統WIN 32 DLL中的函數, 這些函數又須要使用指針。這個時候,不安全代碼就派上用場了。
• 總結:C#可使用Pinvoke平臺調用服務來調用非託管世界中的Win32 DLL的函數,若是這些函數沒有指針參數,就沒必要用不安全代碼,若是有指針參數,則須要啓用不安全代碼。
C#中的fixed是什麼
• 在討論fixed以前,咱們先回顧一下託管代碼和非託管代碼,所謂託管代碼就是由CLR去執行的代碼而不是操做系統去執行的代碼,而非託管代碼就是繞過CLR,由操做系統直接執行,它有本身的垃圾回收、類型安全檢查等服務。
• 而不安全代碼就是容許本身使用指針訪問內存,但同時又要使用CLR提供的垃圾回收機制、類型安全檢查等服務,有的資料認爲是介於CLR和非託管代碼之間的一種代碼運行機制。
• 正由於如此,咱們自定義的指針地址就有可能被CLR垃圾回收機制從新調整位置,因此就引入了fixed ,MSDN對fixed的解釋是:fixed 語句設置指向託管變量的指針,並在執行該語句期間"固定"此變量。這樣就能夠防止變量的重定位。
爲何要引入fixed?
• 咱們知道,不安全代碼是託管代碼,所以將在CLR託管環境的監管下運行.如今,CLR運行環境能夠有權利移動內存中的對象.這是一個能夠減小內存碎片的緣由.但這樣的操做,對程序員來講是不知道的,是對程序員透明的,被指針指向的變量的內存可能被重現安排倒另外的內存位置.(這是由CLR完成的)
• 所以, 若是 *pInt指向的變量的原始地址是1001 , CLR執行了一些內存從新安排以便減小內存碎片後,該變量的地址以前爲1001 , 在從新進行內存安排後可能存儲在內存中地址爲2003的位置.這會是一個大災難 , 由於指針指向的1001地址什麼都沒有了,指針變成了無效的.可能這是在.NET下指針使用被弱化的一個緣由.你怎麼認爲呢?
• 這時就須要fixed救場了. 當在一段代碼中使用該關鍵字時,就告訴了CLR不用去動內存中的那些"問題"對象,它就不會去動它們了.這樣,當在C#中使用指針時,使用fixed關鍵字就很好的避免了在運行時指針無效的問題.
• 如今,由於CLR被告知當"固定"代碼段執行過程當中不容許CLR移動其位置,當"固定"代碼段的語句在執行時,指針所指向的變量在內存中的位置將不會改變,內存中的變量將不會被CLR從新部署內存位置.
• 這就是在C#中指針的使用.確保該函數是用unsafe標註的,確保被指向的對象用fixed標註,你也就已經有了在C#中使用指針的能力!
指針的定義方法
• 指針的定義:當在同一個聲明中聲明多個指針時,* 僅與基礎類型一塊兒使用,而不是做爲每一個指針名稱的前綴。例如:
1.int* p1, p2, p3; // Ok,p1 是指向整數的指針。
2.int *p1, *p2, *p3; // Invalid in C#。
• 指針間接尋址運算符 * :可用於訪問位於指針變量所指向的位置的內容。例如,對於下面的聲明,
int* myVariable;
表達式 *myVariable 表示在 myVariable 中包含
的地址處找到的 int 變量。
stackalloc
• Stackalloc:在不安全的代碼上下文中使用,能夠在堆棧上分配內存塊。
• int* fib = stackalloc int[100];
• 上面的示例在堆棧而不是堆上分配了一個內存塊,它的大小足以包含 100 個 int 類型的元素;該塊的地址存儲在 fib 指針中。此內存不受垃圾回收的制約,所以沒必要將其釘住(經過 fixed)。內存塊的生存期受定義它的方法的生存期的限制(沒有在方法返回以前釋放內存的途徑)。
• stackalloc 僅在局部變量的初始值設定項中有效。
• 因爲涉及指針類型,stackalloc 要求不安全上下文。
• stackalloc 相似於 C 運行時庫中的 _alloca。
指針*與&的實際做用
• & 指針變量的內存地址
• * 指針變量實際的變量值
用戶模式和內核模式
• 運行 Windows 的計算機中的處理器有兩個不一樣模式:「用戶模式」和「內核模式」。根據處理器上運行的代碼的類型,處理器在兩個模式之間切換。應用程序在用戶模式下運行,核心操做系統組件在內核模式下運行。多個驅動程序在內核模式下運行,但某些驅動程序在用戶模式下運行。
• 當啓動用戶模式的應用程序時,Windows 會爲該應用程序建立「進程」。進程爲應用程序提供專用的「虛擬地址空間」和專用的「句柄表格」。因爲應用程序的虛擬地址空間爲專用空間,一個應用程序沒法更改屬於其餘應用程序的數據。每一個應用程序都孤立運行,若是一個應用程序損壞,則損壞會限制到該應用程序。其餘應用程序和操做系統不會受該損壞的影響。
• 用戶模式應用程序的虛擬地址空間除了爲專用空間之外,還會受到限制。在用戶模式下運行的處理器沒法訪問爲該操做系統保留的虛擬地址。限制用戶模式應用程序的虛擬地址空間可防止應用程序更改而且可能損壞關鍵的操做系統數據。
• 在內核模式下運行的全部代碼都共享單個虛擬地址空間。這表示內核模式驅動程序未從其餘驅動程序和操做系統自身獨立開來。若是內核模式驅動程序意外寫入錯誤的虛擬地址,則屬於操做系統或其餘驅動程序的數據可能會受到損壞。若是內核模式驅動程序損壞,則整個操做系統會損壞。
ETW與用戶模式和內核模式
• 咱們寫的應用程序,例如:Web站點,通常來講就是運行在用戶模式下。
• ETW 使用內核中實現的緩衝和日誌記錄機制,提供對用戶模式應用程序和內核模式設備驅動程序引起的事件的跟蹤機制。
• 小結:ETW用戶模式和內核模式通吃,不管哪一種模式下的程序,都能用ETW。
ETW執行流程分析
• 有了前面的知識儲備(平臺調用Pinvoke,非託管代碼,不安全代碼,指針等),如今再來分析ETW的底層類庫TraceEvent和EventSource就不困難了。
啓動ETW Session的流程
• 啓動ETW Session 時,會執行TraceEvent\TraceEventSession.cs的InsureStarted()方法,它又調用TraceEventNativeMethods類的StartTraceW()方法。
• TraceEvent\TraceEventNativeMethods.cs的代碼以下,它其實是經過平臺調用服務PInvoke調用了advapi32.dll的來嗎來完成實際工做的。
中止ETW Session的流程
• 中止ETW Session時,會執行TraceEvent\TraceEventSession.cs的Stop()方法,它又調用TraceEventNativeMethods類的ControlTrace()方法。
• TraceEvent\TraceEventNativeMethods.cs的代碼以下,它實際上也是經過平臺調用服務PInvoke調用了advapi32.dll的來嗎來完成實際工做的。
啓用Provider的流程
• 在啓動ETWSession以後,還要啓用Provider,並與ETWSession關聯。
• 啓用Provider 時,會執行TraceEvent\TraceEventSession.cs的EnableProvider()方法,該方法又調用了TraceEventNativeMethods.cs的EnableTraceEx2()方法。
• TraceEvent\TraceEventNativeMethods.cs的EnableTraceEx2方法代碼以下,它實際上也是經過平臺調用服務PInvoke調用了advapi32.dll的來嗎來完成實際工做的。
向ETW Session中寫入的流程
• 數據提供程序Provider向ETW Session中寫入的流程:在本身的程序裏建立一個繼承自EventSource類的WFEventProvider類,做爲數據提供程序,而後就能夠調用EventSource類的WriteEvent方法記日誌了。
• 流程以下:->Microsoft.Diagnostics.Tracing.EventSource.WriteEvent(),執行WriteEvent方法前,首先首先執行EventSource()構造函數,用來向ETW註冊event source provider。
• ->EventProvider.cs類的Register(eventSourceGuid)函數,裏面註冊了回調函數,而後執行EventProvider.cs類的 EventRegister(ref this.m_providerId, this.m_etwCallback),加了callback函數。
• ->接着執行UnsafeNativeMethods.ManifestEtw.EventRegister(),最終調用advapi32.dll的EventRegister()函數,若是此時, ETW Session已經啓動了,Provider也已啓用,則馬上執行回調函數,將this.m_eventSourceEnabled設置爲true,若是ETW Session沒有啓動,則回調函數不執行, 此時this.m_eventSourceEnabled仍爲默認值false;
• ->接着返回頭執行EventSource.WriteEvent()後面的語句EventSource.WriteEventVarargs(),首先要判斷event source provider是否被啓用了。若是未啓用,就會發現if (this.m_eventSourceEnabled) {} 爲false,就不執行了,不記就直接走了;若是爲true,就寫入日誌。
日誌分析工具介紹:PerfView.exe
• 系統性能實時查看(PerfView)能夠看到一些其餘的系統硬件檢測工具沒法查看的數據與信息,其實是內存和處理器的性能評估和肯定RAM和CPU的問題。
• PerfView可以收集Windows事件跟蹤(ETW)數據來追蹤程序的調用流向,這些程序經過調用哪一個函數識別頻率。除了配置程序性能數據(Perfmon、PAL和Xperf等工具不能輕鬆完成),PerfView還能分析程序內存堆來幫助肯定內存的運用是否高效。它還有一個Diff功能,可讓你肯定跟蹤間的任意差異來幫助你認出全部逆行。最後,該工具還有一個Dump功能能夠生成一個程序內存轉儲。
• 安裝PerfView:
• 從微軟下載的 PerfView 包括一個zip壓縮文件,其中只有一個可執行的文件perfview.exe,這簡化了安裝。你能夠將這個文件複製到多個你想跟蹤的服務器上,而後在這些服務器或你本地的工做站中分析數據。PerfView在Windows Vista、Windows 七、Windows Server 200八、Windows Server 2008 R2和Windows Server 2012上都受到支持,要求.NET FX 2.0以上。
• 該軟件須要在 .Net 環鏡下才能運行,請安裝 .net framework V2.0 可再發行組件包: http://www.cr173.com/soft/2572.html
• 收集配置數據:
• PerfView利用Windows事件追蹤,而ETW從Windows 2000 Server以來就一直內置於操做系統中。只是最近纔有XPerf和PerfView一類的工具利用ETW數據來解決性能問題。事件數據被收集到一個事件跟蹤日誌(ETL)中。根據你想要跟蹤事件的數量和時間的長度,ETL文件可能會很是大。你能夠限制這個日誌文件的大小,若是空間受限或者你不知道問題什麼時候發生的話,你還可讓它們循環。默認每毫秒一次的採樣間隔在收集時間內產生了大概百分之十的CPU開支。建議大概5000個樣本(5秒)用於一次表明性配置採樣。
• 開始一次數據收集有兩種方式,用運行命令啓動一個程序或者用收集命令在計算機範圍內收集數據。這些命令能夠由收集下拉菜單下的GUI引起,或者從CLI或腳本中執行「PerfView run」或「PerfView collect」命令。下圖顯示運行命令tutorial.exe時收集數據的過程,tutorial.exe是一個內置的訓練練習。
• 查看結果:
• 一旦你在些之間針對性能問題收集了數據,你能夠用PerfView分析ETL文件。該ETL文件會出如今左邊的窗口,有收集日誌或運行命令期間你提供的名字。經過雙擊該RTL文件,十來個獨立的節點會和指代它們內容的名字一塊兒出現。例如,你會在下圖中看到跟蹤信息、程序、事件、CPU堆棧。雙擊各個節點,適當的查看器會打開這些內容。
• 爲了針對一個特定程序分析計算密集型性能問題,你將須要學習要調用的堆棧和函數。這能夠經過雙擊左側窗口中的「CPU堆棧」節點完成。接着你會獲得提示來選擇你感興趣的程序。最後,該CPU堆棧查看器會在獨立的窗口中打開,以下圖QQ進程的信息 ,你能夠肯定調用了哪一個函數以及它們的頻率。
• PerfView是一個便於用戶的工具,能夠用來收集和分析ETW數據用於解決配置程序性能數據的問題。這個工具能夠快速地顯示爲這個程序執行的操做系統函數,瞭解性能問題可能潛藏的位置。
資源連接
不安全代碼:
• https://msdn.microsoft.com/zh-cn/library/aa288474(v=vs.71).aspx
• https://msdn.microsoft.com/zh-cn/library/t2yzs44b(v=VS.80).aspx
• http://www.cnblogs.com/jxnclyk/archive/2010/05/28/1746132.html
指針:
• https://msdn.microsoft.com/zh-cn/library/y31yhkeb(v=vs.80).aspx
• https://msdn.microsoft.com/zh-cn/library/tcy5wf0h(v=vs.80).aspx
用戶模式與內核模式:
• https://msdn.microsoft.com/zh-cn/library/windows/hardware/ff554836(v=vs.85).aspx
• http://blog.csdn.net/wzy198852/article/details/32335371
點此下載ETW提升.Pdf文件