tls線程局部存儲
❶ [c++]如何編寫線程安全的DLL(轉帖)
DLL有個共同的特點就是都有一個初始化函數,一個資源釋放函數,其他幾個函數都是核心功能函數。而且這些DLL有時會被多個進程同時調用,這就牽扯到多進程的多線程調用DLL的問題。有點繞口,以下我根據我實踐中遇到的問題,分四種情況分享一下我解決此類問題的經驗:
1、動態庫只有一個導出函數。
這種情況非常少,也是最容易處理的情況。這種情況下編寫函數時,只需要考慮不要有沖突的全局數據就可以了。這里的全局數據包括了在堆中分配的數據塊和靜態全局變數等。如果存在這樣的全局數據,那麼進程中的不同線程訪問這個函數就會造成沖突。
解決辦法也很簡單,就是盡量用堆棧(stack)來解決問題。由於堆棧的所有人是線程,所以它必然是線程安全的。當然也要注意避免堆棧溢出。
我們都知道,如果要在函數再次調用時保留前一次調用的狀態,可以使用靜態變數。但如果你要保持函數的線程安全,那麼靜態變數是不能用的,因為靜態變數是全局的,是屬於進程的,也就是屬於進程內線程共享的。所以如果確實需要在同一線程中保持函數的狀態,相當於在不同次調用間傳遞參數,可以考慮使用靜態全局線程局部變數,即:
__declspec( thread ) int tls_i = 1;
該變數定義就使編譯器保證了tls_i是對應於每個線程的,即每個線程都一個tls_i的副本(),這樣必然就是線程安全的。
2、動態庫導出了多個函數,而且多個函數間存在數據傳遞。
就像前面說的,一般DLL都導出多個函數,一個初始化,一個資源釋放,其他為核心功能函數。這些函數間極有可能發生數據傳遞。如果一個初始化函數是在線程A中調用的,而核心功能函數是在線程B中調用的,那麼線程A初始化函數的資源就無法對應線程B中的核心功能,此外還有核心功能函數間的數據傳遞,這樣的DLL就不是線程安全的,必然導致錯誤。
解決辦法是由用戶(即使用DLL的人)保證這些導出函數是在一個線程中調用。但這樣會很大程度上限制介面的設計和用戶的使用自由度。所以最好的方法是函數只管自己的線程安全,不同函數傳遞數據用動態TLS,線程局部存儲。
比如:我在全局定義了一個變數,用於存儲當前線程局部存儲的index ID。
__declspec( thread ) int tls_i = 1;
當調用分配資源的函數時,調用動態TLS函數TlsAlloc,分配一個ID,將其記錄在全局的線程安全的tls_i變數,並通過TlsSetValue函數將數據保存在線程安全的區域;當調用獲取資源的函數時,通過TlsGetValue獲取資源,處理完成後,調用Tlsfree對TLS index釋放,以便新線程佔有。
這樣,只要DLL中每個函數保證其局部是線程安全的,函數間傳遞數據通過TLS(靜態和動態),就可以實現整個DLL的線程安全。
3、限制訪問DLL中某一函數的線程數目。
有時候,對於DLL中的某一個函數的訪問線程數目是有限制的,超過了限制其他線程就得等一定的時間,一定的時間過後如果還不能得到執行機會,那就返回超時。這樣的設計對用戶來說是友好的,而且很實用,有的商業程序確實是按照允許用戶訪問的通道數目來計價的。
對DLL中的函數做這樣的一個封裝,一般是簡單的待用Semaphore信號量,來解決。DLL初始化時調用CreateSemaphore函數對信號量進行初始化,其原型如下:
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
// pointer to security attributes
LONG lInitialCount, // initial count
LONG lMaximumCount, // maximum count
LPCTSTR lpName // pointer to semaphore-object name);對於信號量,它每WaitForSingleObject一次(當然是要進入),其狀態值(一個整數)就減1,使用完ReleaseSemaphore其狀態值就加1,當其狀態值為0時信號量就由有信號變為無信號。利用信號量的這一特性,我們在初始化時將信號量的初始值(第2個參數)設置為限制的線程訪問數目。在要限制訪問線程數目的函數內部,通過調用WaitForSingleOject獲取控制權,並指定一個等待時間(這個由配置文件指定),根據情況超時返回,使用完ReleaseSemaphore釋放對佔用,讓其他線程可以調用這個函數。
4、多進程情況下的多線程安全DLL。
現實情況是,多進程情況下,一般不是簡單的多進程共享一個Semaphore就可以了。多進程間需要互通很多信息。一般的解決辦法是,採用共享數據段。
#pragma data_seg("share")
int share_data;
#pragma data_seg()
通過pragam編譯器指令生成了一個名叫share的共享數據段,這樣對於變數share_data就可以多進程共享的了。
❷ 求思路:linux C上多線程接收數據怎麼進行存儲
在Linux系統中使用C/C++進行多線程編程時,我們遇到最多的就是對同一變數的多線程讀寫問題,大多情況下遇到這類問題都是通過鎖機制來處理,但這對程序的性能帶來了很大的影響,當然對於那些系統原生支持原子操作的數據類型來說,我們可以使用原子操作來處理,這能對程序的性能會得到一定的提高。那麼對於那些系統不支持原子操作的自定義數據類型,在不使用鎖的情況下如何做到線程安全呢?本文將從線程局部存儲方面,簡單講解處理這一類線程安全問題的方法。
一、數據類型
在C/C++程序中常存在全局變數、函數內定義的靜態變數以及局部變數,對於局部變數來說,其不存在線程安全問題,因此不在本文討論的范圍之內。全局變數和函數內定義的靜態變數,是同一進程中各個線程都可以訪問的共享變數,因此它們存在多線程讀寫問題。在一個線程中修改了變數中的內容,其他線程都能感知並且能讀取已更改過的內容,這對數據交換來說是非常快捷的,但是由於多線程的存在,對於同一個變數可能存在兩個或兩個以上的線程同時修改變數所在的內存內容,同時又存在多個線程在變數在修改的時去讀取該內存值,如果沒有使用相應的同步機制來保護該內存的話,那麼所讀取到的數據將是不可預知的,甚至可能導致程序崩潰。
如果需要在一個線程內部的各個函數調用都能訪問、但其它線程不能訪問的變數,這就需要新的機制來實現,我們稱之為Static memory local to a thread (線程局部靜態變數),同時也可稱之為線程特有數據(TSD: Thread-Specific Data)或者線程局部存儲(TLS: Thread-Local Storage)。這一類型的數據,在程序中每個線程都會分別維護一份變數的副本(),並且長期存在於該線程中,對此類變數的操作不影響其他線程。如下圖:
二、一次性初始化
在講解線程特有數據之前,先讓我們來了解一下一次性初始化。多線程程序有時有這樣的需求:不管創建多少個線程,有些數據的初始化只能發生一次。列如:在C++程序中某個類在整個進程的生命周期內只能存在一個實例對象,在多線程的情況下,為了能讓該對象能夠安全的初始化,一次性初始化機制就顯得尤為重要了。——在設計模式中這種實現常常被稱之為單例模式(Singleton)。Linux中提供了如下函數來實現一次性初始化:
#include <pthread.h>
// Returns 0 on success, or a positive error number on error
int pthread_once (pthread_once_t *once_control, void (*init) (void));
利用參數once_control的狀態,函數pthread_once()可以確保無論有多少個線程調用多少次該函數,也只會執行一次由init所指向的由調用者定義的函數。init所指向的函數沒有任何參數,形式如下:
void init (void)
{
// some variables initializtion in here
}
另外,參數once_control必須是pthread_once_t類型變數的指針,指向初始化為PTHRAD_ONCE_INIT的靜態變數。在C++0x以後提供了類似功能的函數std::call_once (),用法與該函數類似。使用
❸ 線程的線程的同步
線程的同步是Java多線程編程的難點,往往開發者搞不清楚什麼是競爭資源、什麼時候需要考慮同步,怎麼同步等等問題,當然,這些問題沒有很明確的答案,但有些原則問題需要考慮,是否有競爭資源被同時改動的問題?對於同步,在具體的Java代碼中需要完成以下兩個操作:把競爭訪問的資源標識為private;同步哪些修改變數的代碼,使用synchronized關鍵字同步方法或代碼。當然這不是唯一控制並發安全的途徑。synchronized關鍵字使用說明synchronized只能標記非抽象的方法,不能標識成員變數。為了演示同步方法的使用,構建了一個信用卡賬戶,起初信用額為100w,然後模擬透支、存款等多個操作。顯然銀行賬戶User對象是個競爭資源,而多個並發操作的是賬戶方法oper(int x),當然應該在此方法上加上同步,並將賬戶的余額設為私有變數,禁止直接訪問。
工作原理
線程是進程中的實體,一個進程可以擁有多個線程,一個線程必須有一個父進程。線程不擁有系統資源,只有運行必須的一些數據結構;它與父進程的其它線程共享該進程所擁有的全部資源。線程可以創建和撤消線程,從而實現程序的並發執行。一般,線程具有就緒、阻塞和運行三種基本狀態。
在多中央處理器的系統里,不同線程可以同時在不同的中央處理器上運行,甚至當它們屬於同一個進程時也是如此。大多數支持多處理器的操作系統都提供編程介面來讓進程可以控制自己的線程與各處理器之間的關聯度(affinity)。
有時候,線程也稱作輕量級進程。就象進程一樣,線程在程序中是獨立的、並發的執行路徑,每個線程有它自己的堆棧、自己的程序計數器和自己的局部變數。但是,與分隔的進程相比,進程中的線程之間的隔離程度要小。它們共享內存、文件句柄和其它每個進程應有的狀態。
進程可以支持多個線程,它們看似同時執行,但互相之間並不同步。一個進程中的多個線程共享相同的內存地址空間,這就意味著它們可以訪問相同的變數和對象,而且它們從同一堆中分配對象。盡管這讓線程之間共享信息變得更容易,但您必須小心,確保它們不會妨礙同一進程里的其它線程。
Java 線程工具和 API看似簡單。但是,編寫有效使用線程的復雜程序並不十分容易。因為有多個線程共存在相同的內存空間中並共享相同的變數,所以您必須小心,確保您的線程不會互相干擾。
線程屬性
為了正確有效地使用線程,必須理解線程的各個方面並了解Java 實時系統。必須知道如何提供線程體、線程的生命周期、實時系統如 何調度線程、線程組、什麼是幽靈線程(Demo nThread)。
線程體
所有的操作都發生在線程體中,在Java中線程體是從Thread類繼承的run()方法,或實現Runnable介面的類中的run()方法。當線程產生並初始化後,實時系統調用它的run()方法。run()方法內的代碼實現所產生線程的行為,它是線程的主要部分。
線程狀態
附圖表示了線程在它的生命周期內的任何時刻所能處的狀態以及引起狀態改變的方法。這圖並不是完整的有限狀態圖,但基本概括了線程中比較感興趣和普遍的方面。以下討論有關線程生命周期以此為據。
●新線程態(New Thread)
產生一個Thread對象就生成一個新線程。當線程處於新線程狀態時,僅僅是一個空線程對象,它還沒有分配到系統資源。因此只能啟動或終止它。任何其他操作都會引發異常。例如,一個線程調用了new方法之後,並在調用start方法之前的處於新線程狀態,可以調用start和stop方法。
●可運行態(Runnable)
start()方法產生運行線程所必須的資源,調度線程執行,並且調用線程的run()方法。在這時線程處於可運行態。該狀態不稱為運行態是因為這時的線程並不總是一直佔用處理機。特別是對於只有一個處理機的PC而言,任何時刻只能有一個處於可運行態的線程佔用處理 機。Java通過調度來實現多線程對處理機的共享。注意,如果線程處於Runnable狀態,它也有可能不在運行,這是因為還有優先順序和調度問題。
●阻塞/非運行態(Not Runnable)
當以下事件發生時,線程進入非運行態。
①suspend()方法被調用;
②sleep()方法被調用;
③線程使用wait()來等待條件變數;
④線程處於I/O請求的等待。
●死亡態(Dead)
當run()方法返回,或別的線程調用stop()方法,線程進入死亡態。通常Applet使用它的stop()方法來終止它產生的所有線程。
線程的本操作:
派生:線程在進程內派生出來,它即可由進程派生,也可由線程派生。
阻塞(Block):如果一個線程在執行過程中需要等待某個事件發生,則被阻塞。
激活(unblock):如果阻塞線程的事件發生,則該線程被激活並進入就緒隊列。
調度(schele):選擇一個就緒線程進入執行狀態。
結束(Finish):如果一個線程執行結束,它的寄存器上下文以及堆棧內容等將被釋放。
圖2 線程的狀態與操作
線程的另一個執行特性是同步。線程中所使用的同步控制機制與進程中所使用的同步控制機制相同。
線程優先順序
雖然我們說線程是並發運行的。然而事實常常並非如此。正如前面談到的,當系統中只有一個CPU時,以某種順序在單CPU情況下執行多線程被稱為調度(scheling)。Java採用的是一種簡單、固定的調度法,即固定優先順序調度。這種演算法是根據處於可運行態線程的相對優先順序來實行調度。當線程產生時,它繼承原線程的優先順序。在需要時可對優先順序進行修改。在任何時刻,如果有多條線程等待運行,系統選擇優先順序最高的可運行線程運行。只有當它停止、自動放棄、或由於某種原因成為非運行態低優先順序的線程才能運行。如果兩個線程具有相同的優先順序,它們將被交替地運行。Java實時系統的線程調度演算法還是強制性的,在任何時刻,如果一個比其他線程優先順序都高的線程的狀態變為可運行態,實時系統將選擇該線程來運行。一個應用程序可以通過使用線程中的方法setPriority(int),來設置線程的優先順序大小。
有線程進入了就緒狀態,需要有線程調度程序來決定何時執行,根據優先順序來調度。
線程中的join()可以用來邀請其他線程先執行(示例代碼如下):
packageorg.thread.test;{publicstaticvoidmain(String[]args){for(inti=0;i<20;i++){if(i==5){Join01j=newJoin01();Threadt=newThread(j);t.setName(被邀請先執行的線程.);t.start();try{//邀請這個線程,先執行t.join();}catch(InterruptedExceptione){e.printStackTrace();}}System.out.println(沒被邀請的線程。+(i+1));}}publicvoidrun(){for(inti=0;i<10;i++){System.out.println(Thread.currentThread().getName()+(i+1));}}}
yield()告訴系統把自己的CPU時間讓掉,讓其他線程或者自己運行,示例代碼如下:
packageorg.thread.test;
publicclassYield01
{
publicstaticvoidmain(String[]args)
{
YieldFirstyf=newYieldFirst();
YieldSecondys=newYieldSecond();
YieldThirdyt=newYieldThird();
yf.start();ys.start();yt.start();
}
}
classYieldFirstextendsThread
{
@Overridepublicvoidrun()
{
for(inti=0;i<10;i++)
{
System.out.println(第一個線程第+(i+1)+次運行.);//讓當前線程暫停yield();
}
}
}
classYieldSecondextendsThread
{
@Overridepublicvoidrun()
{
for(inti=0;i<10;i++)
{
System.out.println(第二個線程第+(i+1)+次運行.);//讓當前線程暫停yield();
<a href=mailto:}}}classYieldThirdextendsThread{@Overridepublicvoidrun(){for(inti=0;i}
}
}
classYieldThirdextendsThread
{
@Overridepublicvoidrun(){for(inti=0;i<10;i++)
{
System.out.println(第三個線程第+(i+1)+次運行.);//讓當前線程暫停yield();
}
}
幽靈線程
任何一個Java線程都能成為幽靈線程。它是作為運行於同一個進程內的對象和線程的服務提供者。例如,HotJava瀏覽器有一個稱為 後台圖片閱讀器的幽靈線程,它為需要圖片的對象和線程從文件系統或網路讀入圖片。幽靈線程是應用中典型的獨立線程。它為同一應用中的其他對象和線程提供服務。幽靈線程的run()方法一般都是無限循環,等待服務請求。
線程組
每個Java線程都是某個線程組的成員。線程組提供一種機制,使得多個線程集於一個對象內,能對它們實行整體操作。譬如,你能用一個方法調用來啟動或掛起組內的所有線程。Java線程組由ThreadGroup類實現。
當線程產生時,可以指定線程組或由實時系統將其放入某個預設的線程組內。線程只能屬於一個線程組,並且當線程產生後不能改變它所屬的線程組。
多線程
對於多線程的好處這就不多說了。但是,它同樣也帶來了某些新的麻煩。只要在設計程序時特別小心留意,克服這些麻煩並不算太困難。在生成線程時必須將線程放在指定的線程組,也可以放在預設的線程組中,預設的就是生成該線程的線程所在的線程組。一旦一個線程加入了某個線程組,不能被移出這個組。
同步線程
許多線程在執行中必須考慮與其他線程之間共享數據或協調執行狀態。這就需要同步機制。在Java中每個對象都有一把鎖與之對應。但Java不提供單獨的lock和unlock操作。它由高層的結構隱式實現,來保證操作的對應。(然而,我們注意到Java虛擬機提供單獨的monito renter和monitorexit指令來實現lock和
unlock操作。) synchronized語句計算一個對象引用,試圖對該對象完成鎖操作,並且在完成鎖操作前停止處理。當鎖操作完成synchronized語句體得到執行。當語句體執行完畢(無論正常或異常),解鎖操作自動完成。作為面向對象的語言,synchronized經常與方法連用。一種比較好的辦法是,如果某個變數由一個線程賦值並由別的線程引用或賦值,那麼所有對該變數的訪問都必須在某個synchromized語句或synchronized方法內。
現在假設一種情況:線程1與線程2都要訪問某個數據區,並且要求線程1的訪問先於線程2,則這時僅用synchronized是不能解決問題的。這在Unix或Windows NT中可用Simaphore來實現。而Java並不提供。在Java中提供的是wait()和notify()機制。使用如下:
synchronizedmethod_1(/*……*/){//calledbythread1.//accessdataareaavailable=true;notify();}synchronizedmethod_2(/*……*/){//calledbythread2.while(!available)try{wait();//waitfornotify().}catch(InterruptedExceptione){}//accessdataarea}
其中available是類成員變數,置初值為false。
如果在method-2中檢查available為假,則調用wait()。wait()的作用是使線程2進入非運行態,並且解鎖。在這種情況下,method-1可以被線程1調用。當執行notify()後。線程2由非運行態轉變為可運行態。當method-1調用返回後。線程2可重新對該對象加鎖,加鎖成功後執行wait()返回後的指令。這種機制也能適用於其他更復雜的情況。
死鎖
如果程序中有幾個競爭資源的並發線程,那麼保證均衡是很重要的。系統均衡是指每個線程在執行過程中都能充分訪問有限的資源。系統中沒有餓死和死鎖的線程。Java並不提供對死鎖的檢測機制。對大多數的Java程序員來說防止死鎖是一種較好的選擇。最簡單的防止死鎖的方法是對競爭的資源引入序號,如果一個線程需要幾個資源,那麼它必須先得到小序號的資源,再申請大序號的資源。
優化
Java的多線程安全是基於Lock機制實現的,而Lock的性能往往不如人意。原因是,monitorenter與monitorexit這兩個控制多線程同步的bytecode原語,是JVM依賴操作系統互斥(mutex)來實現的。而互斥是一種會導致線程掛起,並在較短的時間內又需要重新調度回原線程的,較為消耗資源的操作。所以需要進行對線程進行優化,提高效率。
輕量級鎖
輕量級鎖(Lightweight Locking)是從Java6開始引入的概念,本意是為了減少多線程進入互斥的幾率,並不是要替代互斥。它利用了CPU原語Compare-And-Swap(CAS,匯編指令CMPXCHG),嘗試在進入互斥前,進行補救。下面將詳細介紹JVM如何利用CAS,實現輕量級鎖。
Java Object Model中定義,Object Header是一個2字(1 word = 4 byte)長度的存儲區域。第一個字長度的區域用來標記同步,GC以及hash code等,官方稱之為 mark word。第二個字長度的區域是指向到對象的Class。在2個word中,mark word是輕量級鎖實現的關鍵,其結構見右表。
從表中可以看到,state為lightweight locked的那行即為輕量級鎖標記。bitfieds名為指向lock record的指針,這里的lock record,其實是一塊分配在線程堆棧上的空間區域。用於CAS前,拷貝object上的mark word。第三項是重量級鎖標記。後面的狀態單詞很有趣,inflated,譯為膨脹,在這里意思其實是鎖已升級到OS-level。一般我們只關注第二和第三項即可。lock,unlock與mark word之間的聯系如右圖所示。在圖中,提到了拷貝object mark word,由於脫離了原始mark word,官方將它冠以displaced前綴,即displaced mark word(置換標記字)。這個displaced mark word是整個輕量級鎖實現的關鍵,在CAS中的compare就需要用它作為條件。
在拷貝完object mark word之後,JVM做了一步交換指針的操作,即流程中第一個橙色矩形框內容所述。將object mark word里的輕量級鎖指針指向lock record所在的stack指針,作用是讓其他線程知道,該object monitor已被佔用。lock record里的owner指針指向object mark word的作用是為了在接下里的運行過程中,識別哪個對象被鎖住了。
最後一步unlock中,我們發現,JVM同樣使用了CAS來驗證object mark word在持有鎖到釋放鎖之間,有無被其他線程訪問。如果其他線程在持有鎖這段時間里,嘗試獲取過鎖,則可能自身被掛起,而mark word的重量級鎖指針也會被相應修改。此時,unlock後就需要喚醒被掛起的線程。
偏向鎖
Java偏向鎖(Biased Locking)是Java 6引入的一項多線程優化。它通過消除資源無競爭情況下的同步原語,進一步提高了程序的運行性能。它與輕量級鎖的區別在於,輕量級鎖是通過CAS來避免進入開銷較大的互斥操作,而偏向鎖是在無競爭場景下完全消除同步,連CAS也不執行(CAS本身仍舊是一種操作系統同步原語,始終要在JVM與OS之間來回,有一定的開銷)。所謂的無競爭場景,就是單線程訪問帶同步的資源或方法。
偏向鎖,顧名思義,它會偏向於第一個訪問鎖的線程,如果在接下來的運行過程中,該鎖沒有被其他的線程訪問,則持有偏向鎖的線程將永遠不需要觸發同步。如果在運行過程中,遇到了其他線程搶占鎖,則持有偏向鎖的線程會被掛起,JVM會嘗試消除它身上的偏向鎖,將鎖恢復到標準的輕量級鎖。(偏向鎖只能在單線程下起作用)。
偏向模式和非偏向模式,在mark word表中,主要體現在thread ID欄位是否為空。
掛起持有偏向鎖的線程,這步操作類似GC的pause,但不同之處是,它只掛起持有偏向鎖的線程(非當前線程)。
在搶占模式的橙色區域說明中有提到,指向當前堆棧中最近的一個lock record(在輕量級鎖中,lock record是進入鎖前會在stack上創建的一份內存空間)。這里提到的最近的一個lock record,其實就是當前鎖所在的stack frame上分配的lock record。整個步驟是從偏向鎖恢復到輕量級鎖的過程。
偏向鎖也會帶來額外開銷。在JDK6中,偏向鎖是默認啟用的。它提高了單線程訪問同步資源的性能。
但試想一下,如果你的同步資源或代碼一直都是多線程訪問的,那麼消除偏向鎖這一步驟對你來說就是多餘的。事實上,消除偏向鎖的開銷還是蠻大的。所以在你非常熟悉自己的代碼前提下,大可禁用偏向鎖 -XX:-UseBiasedLocking。
分類
線程有兩個基本類型:
用戶級線程:管理過程全部由用戶程序完成,操作系統內核心只對進程進行管理。
系統級線程(核心級線程):由操作系統內核進行管理。操作系統內核給應用程序提供相應的系統調用和應用程序介面API,以使用戶程序可以創建、執行、撤消線程。
舉例UNIX International 線程
UNIX International 線程的頭文件是<thread.h> ,僅適用於Sun Solaris操作系統。所以UNIX International線程也常被俗稱為Solaris線程。
1.創建線程
intthr_create(void*stack_base,size_tstack_size,void*(*start_routine)(void*),void*arg,longflags,thread_t*new_thr);
2.等待線程
intthr_join(thread_twait_for,thread_t*dead,void**status);
3.掛起線程
intthr_suspend(thread_tthr);
4.繼續線程
intthr_continue(thread_tthr);
5.退出線程
voidthr_exit(void*status);
6.返回當前線程的線程標識符
thread_tthr_self(void);POSIX線程
POSIX線程(Pthreads)的頭文件是<pthread.h>,適用於類Unix操作系統。Windows操作系統並沒有對POSIX線程提供原生的支持庫。不過Win32的POSIX線程庫的一些實現也還是有的,例如pthreads-w32 。
1.創建線程
intpthread_create(pthread_t*thread,constpthread_attr_t*attr,void*(*start_routine)(void*),void*arg);
2.等待線程
intpthread_join(pthread_tthread,void**retval);
3.退出線程
voidpthread_exit(void*retval);
4.返回當前線程的線程標識符
pthread_tpthread_self(void);
5.線程取消
intpthread_cancel(pthread_tthread);Win32線程
Win32線程的頭文件是<Windows.h>,適用於Windows操作系統。
1.創建線程
HANDLEWINAPICreateThread(LPSECURITY_ATTRIBUTESlpThreadAttributes,SIZE_TdwStackSize,LPTHREAD_START_ROUTINElpStartAddress,LPVOIDlpParameter,DWORDdwCreationFlags,LPDWORDlpThreadId);
2.結束本線程
VOIDWINAPIExitThread(DWORDdwExitCode);
3.掛起指定的線程
DWORDWINAPISuspendThread(HANDLEhThread);
4.恢復指定線程運行
DWORDWINAPIResumeThread(HANDLEhThread);
5.等待線程運行完畢
(HANDLEhHandle,DWORDdwMilliseconds);
6.返回當前線程的線程標識符
DWORDWINAPIGetCurrentThreadId(void);
7.返回當前線程的線程句柄
HANDLEWINAPIGetCurrentThread(void);C++ 11 線程
C++ 11 線程的頭文件是<thread>。 創建線程
std::thread::thread(Function&& f, Args&&... args); 等待線程結束
std::thread::join(); 脫離線程式控制制
std::thread::detach(); 交換線程
std::thread::swap( thread& other ); C 11 線程
C11線程的頭文件是<threads.h>。
C11線程僅僅是個「建議標准」,也就是說100%遵守C11標準的C編譯器是可以不支持C11線程的。根據C11標準的規定,只要編譯器預定義了__STDC_NO_THREADS__宏,就可以沒有<threads.h>頭文件,自然也就也沒有下列函數。
1.創建線程
intthrd_create(thrd_t*thr,thrd_start_tfunc,void*arg);
2.結束本線程
_Noreturnvoidthrd_exit(intres);
3.等待線程運行完畢
intthrd_join(thrd_tthr,int*res);
4.返回當前線程的線程標識符
thrd_tthrd_current();Java線程
1)最簡單的情況是,Thread/Runnable的run()方法運行完畢,自行終止。
2)對於更復雜的情況,比如有循環,則可以增加終止標記變數和任務終止的檢查點。
3)最常見的情況,也是為了解決阻塞不能執行檢查點的問題,用中斷來結束線程,但中斷只是請求,並不能完全保證線程被終止,需要執行線程協同處理。
4)IO阻塞和等鎖情況下需要通過特殊方式進行處理。
5)使用Future類的cancel()方法調用。
6)調用線程池執行器的shutdown()和shutdownNow()方法。
7)守護線程會在非守護線程都結束時自動終止。
8)Thread的stop()方法,但已不推薦使用。
線程的組成
1)一組代表處理器狀態的CPU寄存器中的內容
2)兩個棧,一個用於當線程在內核模式下執行的時候,另一個用於線程在用戶模式下執行的時候
3)一個被稱為線程局部存儲器(TLS,thread-local storage)的私有儲存區域,各個子系統、運行庫和DLL都會用到該儲存區域
4)一個被稱為線程ID(thread ID,線程標識符)的唯一標識符(在內部也被稱為客戶ID——進程ID和線程ID是在同一個名字空間中生產的,所以它們永遠 不會重疊)
5)有時候線程也有它們自己的安全環境,如果多線程伺服器應用程序要模仿其客戶的安全環境,則往往可以利用線程的安全環境
❹ 那種在記事本里寫代碼,直接保存改個擴展名直接運行的語言叫什麼
比較大的應用程序都由很多模塊組成,這些模塊分別完成相對獨立的功能,它們彼此協作來完成整個軟體系統的工作。可能存在一些模塊的功能較為通用,在構造其它軟體系統時仍會被使用。在構造軟體系統時,如果將所有模塊的源代碼都靜態編譯到整個應用程序 EXE 文件中,會產生一些問題:一個缺點是增加了應用程序的大小,它會佔用更多的磁碟空間,程序運行時也會消耗較大的內存空間,造成系統資源的浪費;另一個缺點是,在編寫大的 EXE 程序時,在每次修改重建時都必須調整編譯所有源代碼,增加了編譯過程的復雜性,也不利於階段性的單元測試。
Windows 系統平台上提供了一種完全不同的較有效的編程和運行環境,你可以將獨立的程序模塊創建為較小的 DLL (Dynamic Linkable Library) 文件,並可對它們單獨編譯和測試。在運行時,只有當 EXE 程序確實要調用這些 DLL 模塊的情況下,系統才會將它們裝載到內存空間中。這種方式不僅減少了 EXE 文件的大小和對內存空間的需求,而且使這些 DLL 模塊可以同時被多個應用程序使用。Windows 自己就將一些主要的系統功能以 DLL 模塊的形式實現。
一般來說,DLL 是一種磁碟文件,以.dll、.DRV、.FON、.SYS 和許多以 .EXE 為擴展名的系統文件都可以是 DLL。它由全局數據、服務函數和資源組成,在運行時被系統載入到調用進程的虛擬空間中,成為調用進程的一部分。如果與其它 DLL 之間沒有沖突,該文件通常映射到進程虛擬空間的同一地址上。DLL 模塊中包含各種導出函數,用於向外界提供服務。DLL 可以有自己的數據段,但沒有自己的堆棧,使用與調用它的應用程序相同的堆棧模式;一個 DLL 在內存中只有一個實例;DLL 實現了代碼封裝性;DLL 的編制與具體的編程語言及編譯器無關。
在 Win32 環境中,每個進程都復制了自己的讀/寫全局變數。如果想要與其它進程共享內存,必須使用內存映射文件或者聲明一個共享數據段。DLL 模塊需要的堆棧內存都是從運行進程的堆棧中分配出來的。Windows 在載入 DLL 模塊時將進程函數調用與 DLL 文件的導出函數相匹配。Windows 操作系統對 DLL 的操作僅僅是把 DLL 映射到需要它的進程的虛擬地址空間里去。DLL 函數中的代碼所創建的任何對象(包括變數)都歸調用它的線程或進程所有。
調用方式
1、靜態調用方式:由編譯系統完成對 DLL 的載入和應用程序結束時 DLL 卸載的編碼(如還有其它程序使用該 DLL,則 Windows 對 DLL 的應用記錄減1,直到所有相關程序都結束對該 DLL 的使用時才釋放它,簡單實用,但不夠靈活,只能滿足一般要求。
隱式的調用:需要把產生動態連接庫時產生的 .LIB 文件加入到應用程序的工程中,想使用 DLL 中的函數時,只須說明一下。隱式調用不需要調用 LoadLibrary() 和 FreeLibrary()。程序員在建立一個 DLL 文件時,鏈接程序會自動生成一個與之對應的 LIB 導入文件。該文件包含了每一個 DLL 導出函數的符號名和可選的標識號,但是並不含有實際的代碼。LIB 文件作為 DLL 的替代文件被編譯到應用程序項目中。
當程序員通過靜態鏈接方式編譯生成應用程序時,應用程序中的調用函數與 LIB 文件中導出符號相匹配,這些符號或標識號進入到生成的 EXE 文件中。LIB 文件中也包含了對應的 DL L文件名(但不是完全的路徑名),鏈接程序將其存儲在 EXE 文件內部。
當應用程序運行過程中需要載入 DLL 文件時,Windows 根據這些信息發現並載入 DLL,然後通過符號名或標識號實現對 DLL 函數的動態鏈接。所有被應用程序調用的 DLL 文件都會在應用程序 EXE 文件載入時被載入在到內存中。可執行程序鏈接到一個包含 DLL 輸出函數信息的輸入庫文件(.LIB文件)。操作系統在載入使用可執行程序時載入 DLL。可執行程序直接通過函數名調用 DLL 的輸出函數,調用方法和程序內部其 它的函數是一樣的。
2、動態調用方式:是由編程者用 API 函數載入和卸載 DLL 來達到調用 DLL 的目的,使用上較復雜,但能更加有效地使用內存,是編制大型應用程序時的重要方式。
顯式的調用:是指在應用程序中用 LoadLibrary 或 MFC 提供的 AfxLoadLibrary 顯式的將自己所做的動態連接庫調進來,動態連接庫的文件名即是上面兩個函數的參數,再用 GetProcAddress() 獲取想要引入的函數。自此,你就可以象使用如同本應用程序自定義的函數一樣來調用此引入函數了。在應用程序退出之前,應該用 FreeLibrary 或 MFC 提供的 AfxFreeLibrary 釋放動態連接庫。直接調用 Win32 的 LoadLibary 函數,並指定 DLL 的路徑作為參數。LoadLibary 返回 HINSTANCE 參數,應用程序在調用 GetProcAddress 函數時使用這一參數。GetProcAddress 函數將符號名或標識號轉換為 DLL 內部的地址。程序員可以決定 DLL 文件何時載入或不載入,顯式鏈接在運行時決定載入哪個 DLL 文件。使用 DLL 的程序在使用之前必須載入(LoadLibrary)載入DLL從而得到一個DLL模塊的句柄,然後調用 GetProcAddress 函數得到輸出函數的指針,在退出之前必須卸載DLL(FreeLibrary)。
Windows將遵循下面的搜索順序來定位 DLL:
包含EXE文件的目錄
進程的當前工作目錄
Windows系統目錄
Windows目錄
列在 Path 環境變數中的一系列目錄
MFC中的DLL
Non-MFC DLL:指的是不用 MFC 的類庫結構,直接用 C 語言寫的 DLL,其輸出的函數一般用的是標准 C 介面,並能被 非 MFC 或 MFC 編寫的應用程序所調用。
Regular DLL:和下述的 Extension DLLs 一樣,是用 MFC 類庫編寫的。明顯的特點是在源文件里有一個繼承 CWinApp 的類。其又可細分成靜態連接到 MFC 和動態連接到 MFC 上的。
靜態連接到 MFC 的動態連接庫只被 VC 的專業 版和企業版所支持。該類 DLL 應用程序里頭的輸出函數可以被任意 Win32 程序使用,包括使用 MFC 的應用程序。輸入函數有如下形式:
extern "C" EXPORT YourExportedFunction();
如果沒有 extern "C" 修飾,輸出函數僅僅能從 C 代碼中調用。
DLL 應用程序從 CWinApp 派生,但沒有消息循環。
動態鏈接到 MFC 的 規則 DLL 應用程序里頭的輸出函數可以被任意 Win32 程序使用,包括使用 MFC 的應用程序。但是,所有從 DLL 輸出的函數應該以如下語句開始:
AFX_MANAGE_STATE(AfxGetStaticMoleState( ))
此語句用來正確地切換 MFC 模塊狀態。
Regular DLL能夠被所有支持 DLL 技術的語言所編寫的應用程序所調用。在這種動態連接庫中,它必須有一個從 CWinApp 繼承下來的類,DLLMain 函數被 MFC 所提供,不用自己顯式的寫出來。
Extension DLL:用來實現從 MFC 所繼承下來的類的重新利用,也就是說,用這種類型的動態連接庫,可以用來輸出一個從 MFC 所繼承下來的類。它輸出的函數僅可以被使用 MFC 且動態鏈接到 MFC 的應用程序使用。可以從 MFC 繼承你所想要的、更適於你自己用的類,並把它提供給你的應用程序。你也可隨意的給你的應用程序提供 MFC 或 MFC 繼承類的對象指針。Extension DLL使用 MFC 的動態連接版本所創建的,並且它只被用 MFC 類庫所編寫的應用程序所調用。Extension DLLs 和 Regular DLLs 不一樣,它沒有從 CWinApp 繼承而來的類的對象,所以,你必須為自己 DLLMain 函數添加初始化代碼和結束代碼。
和規則 DLL 相比,有以下不同:
1、它沒有從 CWinApp 派生的對象;
2、它必須有一個 DLLMain 函數;
3、DLLMain 調用 AfxInitExtensionMole 函數,必須檢查該函數的返回值,如果返回0,DLLMmain 也返回 0;
4、如果它希望輸出 CRuntimeClass 類型的對象或者資源,則需要提供一個初始化函數來創建一個 CDynLinkLibrary 對象。並且,有必要把初始化函數輸出;
5、使用擴展 DLL 的 MFC 應用程序必須有一個從 CWinApp 派生的類,而且,一般在InitInstance 里調用擴展 DLL 的初始化函數。
DLL入口函數
1、每一個 DLL 必須有一個入口點,DLLMain 是一個預設的入口函數。DLLMain 負責初始化和結束工作,每當一個新的進程或者該進程的新的線程訪問 DLL 時,或者訪問 DLL 的每一個進程或者線程不再使用DLL或者結束時,都會調用 DLLMain。但是,使用 TerminateProcess 或 TerminateThread 結束進程或者線程,不會調用 DLLMain。
DLLMain的函數原型:
BOOL APIENTRY DLLMain(HANDLE hMole,DWORD ul_reason_for_call,LPVOID
lpReserved)
{
switch(ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
.......
case DLL_THREAD_ATTACH:
.......
case DLL_THREAD_DETACH:
.......
case DLL_PROCESS_DETACH:
.......
return TRUE;
}
}
參數:
hMoudle:是動態庫被調用時所傳遞來的一個指向自己的句柄(實際上,它是指向_DGROUP段的一個選擇符);
ul_reason_for_call:是一個說明動態庫被調原因的標志。當進程或線程裝入或卸載動態連接庫的時候,操作系統調用入口函數,並說明動態連接庫被調用的原因。它所有的可能值為:
DLL_PROCESS_ATTACH: 進程被調用;
DLL_THREAD_ATTACH: 線程被調用;
DLL_PROCESS_DETACH: 進程被停止;
DLL_THREAD_DETACH: 線程被停止;
lpReserved:是一個被系統所保留的參數;
2、_DLLMainCRTStartup
為了使用 "C" 運行庫 (CRT,C Run time Library) 的 DLL 版本(多線程),一個 DLL 應用程序必須指定 _DLLMainCRTStartup 為入口函數,DLL 的初始化函數必須是 DLLMain。
_DLLMainCRTStartup 完成以下任務:當進程或線程捆綁(Attach) 到 DLL 時為 "C" 運行時的數據 (C Runtime Data) 分配空間和初始化並且構造全局 "C "對象,當進程或者線程終止使用DLL(Detach) 時,清理 C Runtime Data 並且銷毀全局 "C " 對象。它還調用 DLLMain 和 RawDLLMain 函數。
RawDLLMain 在 DLL 應用程序動態鏈接到 MFC DLL 時被需要,但它是靜態鏈接到 DLL 應用程序的。在講述狀態管理時解釋其原因。
關於調用約定
動態庫輸出函數的約定有兩種:調用約定和名字修飾約定。
1)調用約定(Calling convention):決定函數參數傳送時入棧和出棧的順序,由調用者還是被調用者把參數彈出棧,以及編譯器用來識別函數名字的修飾約定。
函數調用約定有多種,這里簡單說一下:
1、__stdcall 調用約定相當於16位動態庫中經常使用的 PASCAL 調用約定。在32位的 VC 5.0 中PASCAL 調用約定不再被支持(實際上它已被定義為__stdcall。除了__pascal 外,__fortran 和__syscall也不被支持),取而代之的是 __stdcall 調用約定。兩者實質上是一致的,即函數的參數自右向左通過棧傳遞,被調用的函數在返回前清理傳送參數的內存棧,但不同的是函數名的修飾部分(關於函數名的修飾部分在後面將詳細說明)。
_stdcall 是 Pascal 程序的預設調用方式,通常用於 Win32 API 中,函數採用從右到左的壓棧方式,自己在退出時清空堆棧。VC 將函數編譯後會在函數名前面加上下劃線前綴,在函數名後加上 "@" 和參數的位元組數。
2、C 調用約定(即用__cdecl 關鍵字說明)按從右至左的順序壓參數入棧,由調用者把參數彈出棧。對於傳送參數的內存棧是由調用者來維護的(正因為如此,實現可變參數的函數只能使用該調用約定)。另外,在函數名修飾約定方面也有所不同。
_cdecl 是 C 和 C 程序預設的調用方式。每一個調用它的函數都包含清空堆棧的代碼,所以產生的可執行文件大小會比調用 _stdcall 函數的大。函數採用從右到左的壓棧方式。VC 將函數編譯後會在函數名前面加上下劃線前綴。 它是 MFC 預設調用約定。
3、__fastcall 調用約定是 "人" 如其名,它的主要特點就是快,因為它是通過寄存器來傳送參數的(實際上,它用 ECX 和 EDX 傳送前兩個雙字(DWORD)或更小的參數,剩下的參數仍舊自右向左壓棧傳送,被調用的函數在返回前清理傳送參數的內存棧),在函數名修飾約定方面,它和前兩者均不同。
_fastcall方式的函數採用寄存器傳遞參數,VC 將函數編譯後會在函數名前面加上"@"前綴,在函數名後加上"@"和參數的位元組數。
4、thiscall 僅僅應用於 "C " 成員函數。this 指針存放於 CX 寄存器,參數從右到左壓。thiscall 不是關鍵詞,因此不能被程序員指定。
5、naked call採用 1-4 的調用約定時,如果必要的話,進入函數時編譯器會產生代碼來保存ESI,EDI,EBX,EBP寄存器,退出函數時則產生代碼恢復這些寄存器的內容。
naked call不產生這樣的代碼。naked call不是類型修飾符,故必須和_declspec 共同使用。
關鍵字 __stdcall、__cdecl 和 __fastcall 可以直接加在要輸出的函數前,也可以在編譯環境的 Setting...\C/C \Code Generation 項選擇。當加在輸出函數前的關鍵字與編譯環境中的選擇不同時,直接加在輸出函數前的關鍵字有效。它們對應的命令行參數分別為/Gz、/Gd 和 /Gr。預設狀態為/Gd,即__cdecl。
要完全模仿 PASCAL 調用約定首先必須使用 __stdcall 調用約定,至於函數名修飾約定,可以通過其它方法模仿。還有一個值得一提的是 WINAPI 宏,Windows.h 支持該宏,它可以將出函數翻譯成適當的調用約定,在 WIN32 中,它被定義為 __stdcall。使用 WINAPI 宏可以創建自己的 APIs。
2)名字修飾約定
1、修飾名(Decoration name)
"C" 或者 "C " 函數在內部(編譯和鏈接)通過修飾名識別。修飾名是編譯器在編譯函數定義或者原型時生成的字元串。有些情況下使用函數的修飾名是必要的,如在模塊定義文件里頭指定輸出"C "重載函數、構造函數、析構函數,又如在匯編代碼里調用"C""或"C "函數等。
修飾名由函數名、類名、調用約定、返回類型、參數等共同決定。
2、名字修飾約定隨調用約定和編譯種類(C或C )的不同而變化。函數名修飾約定隨編譯種類和調用約定的不同而不同,下面分別說明。
a、C編譯時函數名修飾約定規則:
__stdcall 調用約定在輸出函數名前加上一個下劃線前綴,後面加上一個"@"符號和其參數的位元組數,格式為 _functionname@number。
__cdecl調用約定僅在輸出函數名前加上一個下劃線前綴,格式為 _functionname。
__fastcall調用約定在輸出函數名前加上一個"@"符號,後面也是一個"@"符號和其參數的位元組數,格式為@functionname@number。
它們均不改變輸出函數名中的字元大小寫,這和PASCAL調用約定不同,PASCAL約定輸出的函數名無任何修飾且全部大寫。
b、C 編譯時函數名修飾約定規則:
__stdcall調用約定:
1、以"?"標識函數名的開始,後跟函數名;
2、函數名後面以"@@YG"標識參數表的開始,後跟參數表;
3、參數表以代號表示:
X——void,
D——char,
E——unsigned char,
F——short,
H——int,
I——unsigned int,
J——long,
K——unsigned long,
M——float,
N——double,
_N——bool,
....
PA——表示指針,後面的代號表明指針類型,如果相同類型的指針連續出現,以"0"代替,一個"0"代表一次重復;
4、參數表的第一項為該函數的返回值類型,其後依次為參數的數據類型,指針標識在其所指數據類型前;
5、參數表後以"@Z"標識整個名字的結束,如果該函數無參數,則以"Z"標識結束。
其格式為"?functionname@@YG*****@Z"或"?functionname@@YG*XZ",
例如
int Test1(char *var1,unsigned long)-----「?Test1@@YGHPADK@Z」
void Test2() -----「?Test2@@YGXXZ」
__cdecl調用約定:
規則同上面的_stdcall調用約定,只是參數表的開始標識由上面的"@@YG"變為"@@YA"。
__fastcall調用約定:
規則同上面的_stdcall調用約定,只是參數表的開始標識由上面的"@@YG"變為"@@YI"。
VC 對函數的省缺聲明是"__cedcl",將只能被C/C 調用。
關於DLL的函數
動態鏈接庫中定義有兩種函數:導出函數(export function)和內部函數(internal function)。導出函數可以被其它模塊調用,內部函數在定義它們的DLL程序內部使用。
輸出函數的方法有以下幾種:
1、傳統的方法
在模塊定義文件的 EXPORT 部分指定要輸入的函數或者變數。語法格式如下:
entryname[=internalname] [@ordinal[NONAME]] [DATA] [PRIVATE]
其中:
entryname 是輸出的函數或者數據被引用的名稱;
internalname 同 entryname;
@ordinal 表示在輸出表中的順序號(index);
NONAME 僅僅在按順序號輸出時被使用(不使用 entryname );
DATA 表示輸出的是數據項,使用 DLL 輸出數據的程序必須聲明該數據項為 _declspec(DLLimport)。
上述各項中,只有 entryname 項是必須的,其他可以省略。
對於"C"函數來說,entryname 可以等同於函數名;但是對 "C " 函數(成員函數、非成員函數)來說,entryname 是修飾名。可以從 .map 映像文件中得到要輸出函數的修飾名,或者使用DUMPBIN /SYMBOLS 得到,然後把它們寫在 .def 文件的輸出模塊。DUMPBIN 是VC提供的一個工具。
如果要輸出一個 "C " 類,則把要輸出的數據和成員的修飾名都寫入 .def 模塊定義文件。
2、在命令行輸出
對鏈接程序 LINK 指定 /EXPORT 命令行參數,輸出有關函數。
3、使用 MFC 提供的修飾符號 _declspec(DLLexport)
在要輸出的函數、類、數據的聲明前加上 _declspec(DLLexport) 修飾符表示輸出。__declspec(DLLexport) 在 C 調用約定、C 編譯情況下可以去掉輸出函數名的下劃線前綴。extern "C" 使得在 C 中使用 C 編譯方式成為可能。在"C "下定義"C"函數需要加 extern "C" 關鍵詞。用 extern "C" 來指明該函數使用 C 編譯方式。輸出的 "C" 函數可以從 "C" 代碼里調用。
例如,在一個 C 文件中,有如下函數:
extern "C" {void __declspec(DLLexport) __cdecl Test(int var);}
其輸出函數名為:Test
MFC提供了一些宏,就有這樣的作用。
AFX_CLASS_IMPORT:__declspec(DLLexport)
AFX_API_IMPORT:__declspec(DLLexport)
AFX_DATA_IMPORT:__declspec(DLLexport)
AFX_CLASS_EXPORT:__declspec(DLLexport)
AFX_API_EXPORT:__declspec(DLLexport)
AFX_DATA_EXPORT:__declspec(DLLexport)
AFX_EXT_CLASS: #ifdef _AFXEXT
AFX_CLASS_EXPORT
#else
AFX_CLASS_IMPORT
AFX_EXT_API:#ifdef _AFXEXT
AFX_API_EXPORT
#else
AFX_API_IMPORT
AFX_EXT_DATA:#ifdef _AFXEXT
AFX_DATA_EXPORT
#else
AFX_DATA_IMPORT
像 AFX_EXT_CLASS 這樣的宏,如果用於 DLL 應用程序的實現中,則表示輸出(因為_AFX_EXT被定義,通常是在編譯器的標識參數中指定該選項 /D_AFX_EXT);如果用於使用DLL的應用程序中,則表示輸入(_AFX_EXT沒有定義)。
要輸出整個的類,對類使用_declspec(_DLLexpot);要輸出類的成員函數,則對該函數使用_declspec(_DLLexport)。如:
class AFX_EXT_CLASS CTextDoc : public CDocument
{
…
}
extern "C" AFX_EXT_API void WINAPI InitMYDLL();
這幾種方法中,最好採用第三種,方便好用;其次是第一種,如果按順序號輸出,調用效率會高些;最次是第二種。
模塊定義文件(.DEF)
模塊定義文件(.DEF)是一個或多個用於描述 DLL 屬性的模塊語句組成的文本文件,每個DEF文件至少必須包含以下模塊定義語句:
第一個語句必須是LIBRARY語句,指出DLL的名字;
EXPORTS 語句列出被導出函數的名字;將要輸出的函數修飾名羅列在 EXPORTS 之下,這個名字必須與定義函數的名字完全一致,如此就得到一個沒有任何修飾的函數名了。
可以使用DESCRIPTION語句描述DLL的用途(此句可選);
";"對一行進行注釋(可選)。 DLL程序和調用其輸出函數的程序的關系
1、DLL與進程、線程之間的關系
DLL模塊被映射到調用它的進程的虛擬地址空間。
DLL使用的內存從調用進程的虛擬地址空間分配,只能被該進程的線程所訪問。
DLL的句柄可以被調用進程使用;調用進程的句柄可以被DLL使用。
DLL使用調用進程的棧。
2、關於共享數據段
DLL定義的全局變數可以被調用進程訪問;DLL可以訪問調用進程的全局數據。使用同一DLL的每一個進程都有自己的DLL全局變數實例。如果多個線程並發訪問同一變數,則需要使用同步機制;對一個DLL的變數,如果希望每個使用DLL的線程都有自己的值,則應該使用線程局部存儲(TLS,Thread Local Strorage)。
在程序里加入預編譯指令,或在開發環境的項目設置里也可以達到設置數據段屬性的目的.必須給這些變數賦初值,否則編譯器會把沒有賦初始值的變數放在一個叫未被初始化的數據段中。
❺ CThreadSlotData 類的問題
TLS是操作系統提供的線程局部儲存機制,它的操作是只對當前線程的,估計它的儲存結構不會是鏈表而是數組,而且是放在當前線程內核對象里頭了。讀寫數據的操作只是在當前對象的一個數組成員跟據索引取值,並不需要搜索所有線程的。
CThreadSlotData/CThreadData這兩個MFC類並不是對Tls API函數的簡單封裝,而是在Tls的基礎上又實現了一個Process Local Storage。
❻ 線程特有數據(Thread Specific Data)
在單線程程序中,我們經常要使用 全局變數 來實現多個函數間共享數據。在多線程環境下,由於數據空間是共享的,因此全局變數也為所有線程所共有。但有時在應用程序設計中有必要提供 線程私有 的全局變數,僅在某個線程中有效,但可以跨多個函數訪問,這樣每個線程訪問它自己獨立的數據空間,而不用擔心和其它線程的同步訪問。
這樣在一個線程內部的各個函數都能訪問、但其它線程不能訪問的變數,我們就需要使用 線程局部靜態變數 (Static memory local to a thread) 同時也可稱之為 線程特有數據 (Thread-Specific Data 或 TSD),或者 線程局部存儲 (Thread-Local Storage 或 TLS)。
POSIX 線程庫提供了如下 API 來管理線程特有數據(TSD):
第一參數 key 指向 pthread_key_t 的對象的指針。請 注意 這里 pthread_key_t 的對象佔用的空間是用戶事先分配好的, pthread_key_create 不會動態生成 pthread_key_t 對象。
第二參數 desctructor ,如果這個參數不為空,那麼當每個線程結束時,系統將調用這個函數來釋放綁定在這個鍵上的內存塊。
有時我們在線程里初始化時,需要避免重復初始化。我們希望一個線程里只調用 pthread_key_create 一次,這時就要使用 pthread_once 與它配合。
第一個參數 once_control 指向一個 pthread_once_t 對象,這個對象必須是常量 PTHREAD_ONCE_INIT ,否則 pthread_once 函數會出現不可預料的結果。
第二個參數 init_routine ,是調用的初始化函數,不能有參數,不能有返回值。
如果成功則返回0,失敗返回非0值。
創建完鍵後,必須將其與線程數據關聯起來。關聯後也可以獲得某一鍵對應的線程數據。關聯鍵和數據使用的函數為:
第一參數 key 指向鍵。
第二參數 value 是欲關聯的數據。
函數成功則返回0,失敗返回非0值。
注意: 用 pthread_setspecific 為一個鍵指定新的線程數據時,並不會主動調用析構函數釋放之前的內存,所以調用線程必須自己釋放原有的線程數據以回收內存。
獲取與某一個鍵關聯的數據使用函數的函數為:
參數 key 指向鍵。
如果有與此鍵對應的數據,則函數返回該數據,否則返回NULL。
刪除一個鍵使用的函數為:
參數 key 為要刪除的鍵。
成功則返回0,失敗返回非0值。
注意: 該函數將鍵設置為可用,以供下一次調用 pthread_key_create() 使用。它並不檢查當前是否有線程正在使用該鍵對應的線程數據,所以它並不會觸發函數 pthread_key_create 中定義的 destructor 函數,也就不會釋放該鍵關聯的線程數據所佔用的內存資源,而且在將 key 設置為可用後,在線程退出時也不會再調用析構函數。所以在將 key 設置為可用之前,必須要確定:
在 Linux 中每個進程有一個全局的數組 __pthread_keys ,數組中存放著 稱為 key 的結構體,定義類似如下:
在 key 結構中 seq 為一個序列號,用來作為使用標志指示這個結構在數組中是否正在使用,初始化時被設為0,即表示 不在使用 。 destructor 用來存放一個析構函數指針。
pthread_create_key 會從數組中找到一個還未使用的 key 元素,將其序列號 seq 加1,並記錄析構函數地址,並將 key 在數組 __pthread_keys 中的 下標 作為返回值返回。那麼如何判斷一個 key 正在使用呢?
如果 key 的序列號 seq 為偶數則表示未分配,分配時將 seq 加1變成奇數,即表示正在使用。這個操作過程採用原子 CAS 來完成,以保證線程安全。在 pthread_key_delete() 時也將序列號 seq 加1,表示可以再被使用,通過序列號機制來保證回收的 key 不會被復用(復用 key 可能會導致線程在退出時可能會調用錯誤的析構函數)。但是一直加1會導致序列號回繞,還是會復用 key ,所以調用 pthread_create_key 獲取可用的 key 時會檢查是否有回繞風險,如果有則創建失敗。
除了進程范圍內的 key 結構數組外,系統還在進程中維護關於每個線程的控制塊 TCB(用於管理寄存器,線程棧等),裡面有一個 pthread_key_data 類型的數組。這個數組中的元素數量和進程中的 key 數組數量相等。 pthread_key_data 的定義類似如下:
根據 pthread_key_create() 返回的可用的 key 在 __pthread_keys 數組中的下標, pthread_setspecific() 在 pthread_key_data 的數組 中定位相同下標的一個元素 pthread_key_data ,並設置其序號 seq 設置為對應的 key 的序列號,數據指針 data 指向設置線程特有數據(TSD)的值。
pthread_getspecific() 用於將 pthread_setspecific() 設置的 data 取出。
線程退出時, pthread_key_data 中的序號 seq 用於判斷該 key 是否仍在使用中(即與在 __pthread_keys 中的同一個下標對應的 key 的序列號 seq 是否相同),若是則將 pthread_key_data 中 data(即 線程特有數據 TSD)作為參數調用析構函數。
由於系統在每個進程中 pthread_key_t 類型的數量是有限的,所有在進程中並不能獲取無限個 pthread_key_t 類型。Linux 中可以通過 PTHREAD_KEY_MAX(定義於 limits.h 文件中)或者系統調用 sysconf(_SC_THREAD_KEYS_MAX) 來確定當前系統最多支持多少個 key 。 Linux 中默認是 1024 個 key,這對大多數程序來書已經夠了。如果一個線程中有多個線程局部存儲變數(TLS),通常可以將這些變數封裝到一個數據結構中,然後使用封裝後的數據結構和一個線程局部變數相關聯,這樣就能減少對鍵值的使用。
https://blog.csdn.net/hustraiet/article/details/9857919
https://blog.csdn.net/hustraiet/article/details/9857919
https://blog.csdn.net/caigen1988/article/details/7901248
http://www.bitools.com/?p=2443
https://spockwangs.github.io/blog/2017/12/01/thread-local-storage/
https://www.jianshu.com/p/71c2f80d7bd1
https://blog.csdn.net/cywosp/article/details/26469435
http://www.embeddedlinux.org.cn/emblinuxappdev/117.htm
❼ 雙核/四線程是什麼意思,跟四核處理器有什麼不一樣嗎
雙核/四線程指的是採用超線程即是可在同一時間里,應用程序可以使用晶元的不同部分。雖然單線程晶元每秒鍾能夠處理成千上萬條指令,但是在任一時刻只能夠對一條指令進行操作。而超線程技術可以使晶元同時進行多線程處理,使晶元性能得到提升。
雙核四線程實際上是兩個物理核心處理器,是CPU工作時利用超線程技術可以把CPU的一個物理核心模擬出兩個處理線程,讓操作系統誤認為有兩個「物理核心」,俗稱「假四核」,而四核處理器是真正的四顆物理核心處理器。
(7)tls線程局部存儲擴展閱讀
多核心處理器的創新意義:
1、x86多核處理器標志著計算技術的一次重大飛躍。這一重要進步發生之際,正是企業和消費者面對飛速增長的數字資料和互聯網的全球化趨勢,開始要求處理器提供更多便利和優勢之時。
2、多核處理器,較之當前的單核處理器,能帶來更多的性能和生產力優勢,因而最終將成為一種廣泛普及的計算模式。
3、多核處理器還將在推動PC安全性和虛擬技術方面起到關鍵作用,虛擬技術的發展能夠提供更好的保護、更高的資源使用率和更可觀的商業計算市場價值。普通消費者也將比以往擁有更多的途徑獲得更高性能,從而提高他們家用PC和數字媒體計算系統的使用。
❽ 線程局部存儲的簡介
在一個線程修改的內存內容,對所有線程都生效。這是一個優點也是一個缺點。說它是優點,線程的數據交換變得非常快捷。說它是缺點,一個線程死掉了,其它線程也性命不保; 多個線程訪問共享數據,需要昂貴的同步開銷,也容易造成同步相關的BUG。
如果需要在一個線程內部的各個函數調用都能訪問、但其它線程不能訪問的變數(被稱為static memory local to a thread 線程局部靜態變數),就需要新的機制來實現。這就是TLS。
線程局部存儲在不同的平台有不同的實現,可移植性不太好。幸好要實現線程局部存儲並不難,最簡單的辦法就是建立一個全局表,通過當前線程ID去查詢相應的數據,因為各個線程的ID不同,查到的數據自然也不同了。但Windows系統採用了每個線程建線程專享的索引表,表的條目為線程局部存儲的地址。在線程執行的任何代碼處,都可以查詢本線程的這個索引表獲得要訪問的線程局部存儲的地址。
大多數平台都提供了線程局部存儲的方法,無需要我們自己去實現:
❾ 反調試是什麼
免殺就不知道了.反調試就是禁止調試一般有這樣的一些原理.一、反調試技術反調試技術是一種常見的反檢測技術,因為惡意軟體總是企圖監視自己的代碼以檢測是否自己正在被調試。為做到這一點,惡意軟體可以檢查自己代碼是否被設置了斷點,或者直接通過系統調用來檢測調試器。1.斷點為了檢測其代碼是否被設置斷點,惡意軟體可以查找指令操作碼0xcc(調試器會使用該指令在斷點處取得惡意軟體的控制權),它會引起一個SIGTRAP。如果惡意軟體代碼本身建立了一個單獨的處理程序的話,惡意軟體也可以設置偽斷點。用這種方法惡意軟體可以在被設置斷點的情況下繼續執行其指令。惡意軟體也可以設法覆蓋斷點,例如有的病毒採用了反向解密循環來覆蓋病毒中的斷點。相反,還有的病毒則使用漢明碼自我糾正自身的代碼。漢明碼使得程序可以檢測並修改錯誤,但是在這里卻使病毒能夠檢測並清除在它的代碼中的斷點。2.計算校驗和惡意軟體也可以計算自身的校驗和,如果校驗和發生變化,那麼病毒會假定它正在被調試,並且其代碼內部已被放置斷點。VAMPiRE是一款抗反調試工具,可用來逃避斷點的檢測。VaMPiRE通過在內存中維護一張斷點表來達到目的,該表記錄已被設置的所有斷點。該程序由一個頁故障處理程序(PFH),一個通用保護故障處理程序(GPFH),一個單步處理程序和一個框架API組成。當一個斷點被觸發的時候,控制權要麼傳給PFH(處理設置在代碼、數據或者內存映射I/O中的斷點),要麼傳給GPFH(處理遺留的I/O斷點)。單步處理程序用於存放斷點,使斷點可以多次使用。3.檢測調試器在Linux系統上檢測調試器有一個簡單的方法,只要調用Ptrace即可,因為對於一個特定的進程而言無法連續地調用Ptrace兩次以上。在Windows中,如果程序目前處於被調試狀態的話,系統調用isDebuggerPresent將返回1,否則返回0。這個系統調用簡單檢查一個標志位,當調試器正在運行時該標志位被置1。直接通過進程環境塊的第二個位元組就可以完成這項檢查,以下代碼為大家展示的就是這種技術:mov eax, fs:[30h]move eax, byte [eax+2]test eax, eax jne @DdebuggerDetected在上面的代碼中,eax被設置為PEB(進程環境塊),然後訪問PEB的第二個位元組,並將該位元組的內容移入eax。通過查看eax是否為零,即可完成這項檢測。如果為零,則不存在調試器;否則,說明存在一個調試器。如果某個進程為提前運行的調試器所創建的,那麼系統就會給ntdll.dll中的堆操作常式設置某些標志,這些標志分別是FLG_HEAP_ENABLE_TAIL_CHECK、FLG_HEAP_ENABLE_FREE_CHECK和FLG_HEAP_VALIDATE_PARAMETERS。我們可以通過下列代碼來檢查這些標志:mov eax, fs:[30h]mov eax, [eax+68h]and eax, 0x70test eax, eaxjne @DebuggerDetected在上面的代碼中,我們還是訪問PEB,然後通過將PEB的地址加上偏移量68h到達堆操作常式所使用的這些標志的起始位置,通過檢查這些標志就能知道是否存在調試器。檢查堆頭部內諸如ForceFlags之類的標志也能檢測是否有調試器在運行,如下所示:mov eax, fs:[30h]mov eax, [eax+18h] ;process heapmov eax, [eax+10h] ;heap flagstest eax, eaxjne @DebuggerDetected上面的代碼向我們展示了如何通過PEB的偏移量來訪問進程的堆及堆標志,通過檢查這些內容,我們就能知道Force標志是否已經被當前運行的調試器提前設置為1了。另一種檢測調試器的方法是,使用NtQueryInformationProcess這個系統調用。我們可以將ProcessInformationClass設為7來調用該函數,這樣會引用ProcessDebugPort,如果該進程正在被調試的話,該函數將返回-1。示例代碼如下所示。push 0
push 4
push offset isdebugged
push 7 ;ProcessDebugPort
push -1
call NtQueryInformationProcess
test eax, eax
jne @ExitError
cmp isdebugged, 0
jne @DebuggerDetected在本例中,首先把NtQueryInformationProcess的參數壓入堆棧。這些參數介紹如下:第一個是句柄(在本例中是0),第二個是進程信息的長度(在本例中為4位元組),接下來是進程信息類別(在本例中是7,表示ProcessDebugPort),下一個是一個變數,用於返回是否存在調試器的信息。如果該值為非零值,那麼說明該進程正運行在一個調試器下;否則,說明一切正常。最後一個參數是返回長度。使用這些參數調用NtQueryInformationProcess後的返回值位於isdebugged中。隨後測試該返回值是否為0即可。另外,還有其他一些檢測調試器的方法,如檢查設備列表是否含有調試器的名稱,檢查是否存在用於調試器的注冊表鍵,以及通過掃描內存以檢查其中是否含有調試器的代碼等。另一種非常類似於EPO的方法是,通知PE載入器通過PE頭部中的線程局部存儲器(TLS)表項來引用程序的入口點。這會導致首先執行TLS中的代碼,而不是先去讀取程序的入口點。因此,TLS在程序啟動就可以完成反調試所需檢測。從TLS啟動時,使得病毒得以能夠在調試器啟動之前就開始運行,因為一些調試器是在程序的主入口點處切入的。4.探測單步執行惡意軟體還能夠通過檢查單步執行來檢測調試器。要想檢測單步執行的話,我們可以把一個值放進堆棧指針,然後看看這個值是否還在那裡。如果該值在那裡,這意味著,代碼正在被單步執行。當調試器單步執行一個進程時,當其取得控制時需要把某些指令壓入棧,並在執行下一個指令之前將其出棧。所以,如果該值仍然在那裡,就意味著其它正在運行的進程已經在使用堆棧。下面的示例代碼展示了惡意軟體是如何通過堆棧狀態來檢測單步執行的:Mov bp,sp;選擇堆棧指針Push ax ;將ax壓入堆棧Pop ax ;從堆棧中選擇該值Cmp word ptr [bp -2],ax ;跟堆棧中的值進行比較Jne debug ;如果不同,說明發現了調試器。 如上面的注釋所述,一個值被壓入堆棧然後又被彈出。如果存在調試器,那麼堆棧指針–2位置上的值就會跟剛才彈出堆棧的值有所不同,這時就可以採取適當的行動。5.在運行時中檢測速度衰減通過觀察程序在運行時是否減速,惡意代碼也可以檢測出調試器。如果程序在運行時速度顯著放緩,那就很可能意味著代碼正在單步執行。因此如果兩次調用的時間戳相差甚遠,那麼惡意軟體就需要採取相應的行動了。Linux跟蹤工具包LTTng/LTTV通過觀察減速問題來跟蹤病毒。當LTTng/LTTV追蹤程序時,它不需要在程序運行時添加斷點或者從事任何分析。此外,它還是用了一種無鎖的重入機制,這意味著它不會鎖定任何Linux內核代碼,即使這些內核代碼是被跟蹤的程序需要使用的部分也是如此,所以它不會導致被跟蹤的程序的減速和等待。6.指令預取如果惡意代碼篡改了指令序列中的下一條指令並且該新指令被執行了的話,那麼說明一個調試器正在運行。這是指令預取所致:如果該新指令被預取,就意味著進程的執行過程中有其他程序的切入。否則,被預取和執行的應該是原來的指令。7.自修改代碼惡意軟體也可以讓其他代碼自行修改(自行修改其他代碼),這樣的一個例子是HDSpoof。這個惡意軟體首先啟動了一些異常處理常式,然後在運行過程中將其消除。這樣一來,如果發生任何故障的話,運行中的進程會拋出一個異常,這時病毒將終止運行。此外,它在運行期間有時還會通過清除或者添加異常處理常式來篡改異常處理常式。在下面是HDSpoof清除全部異常處理常式(默認異常處理常式除外)的代碼。exception handlers before:0x77f79bb8 ntdll.dll:executehandler2@20 + 0x003a
0x0041adc9 hdspoof.exe+0x0001adc9
0x77e94809 __except_handler3exception handlers after:0x77e94809 __except_handler30x41b770: 8b44240c mov eax,dword ptr [esp+0xc]
0x41b774: 33c9 xor ecx,ecx
0x41b776: 334804 xor ecx,dword ptr [eax+0x4]
0x41b779: 334808 xor ecx,dword ptr [eax+0x8]
0x41b77c: 33480c xor ecx,dword ptr [eax+0xc]
0x41b77f: 334810 xor ecx,dword ptr [eax+0x10]
0x41b782: 8b642408 mov esp,dword ptr [esp+0x8]
0x41b786: 648f0500000000 pop dword ptr fs:[0x0] 下面是HDSpoof創建一個新的異常處理程序的代碼。0x41f52b: add dword ptr [esp],0x9ca0x41f532: push dword ptr [dword ptr fs:[0x0]0x41f539: mov dword ptr fs:[0x0],esp8.覆蓋調試程序信息一些惡意軟體使用各種技術來覆蓋調試信息,這會導致調試器或者病毒本身的功能失常。通過鉤住中斷INT 1和INT 3(INT 3是調試器使用的操作碼0xCC),惡意軟體還可能致使調試器丟失其上下文。這對正常運行中的病毒來說毫無妨礙。另一種選擇是鉤住各種中斷,並調用另外的中斷來間接運行病毒代碼。下面是Tequila 病毒用來鉤住INT 1的代碼:new_interrupt_one: push bp mov bp,sp cs cmp b[0a],1 ;masm mod. needed je 0506 ;masm mod. needed cmp w[bp+4],09b4 ja 050b ;masm mod. needed push ax push es les ax,[bp+2] cs mov w[09a0],ax ;masm mod. needed cs mov w[09a2],es ;masm mod. needed cs mov b[0a],1 pop es pop ax and w[bp+6],0feff pop bp iret一般情況下,當沒有安裝調試器的時候,鉤子常式被設置為IRET。V2Px使用鉤子來解密帶有INT 1和INT 3的病毒體。在代碼運行期間,會不斷地用到INT 1和INT 3向量,有關計算是通過中斷向量表來完成的。一些病毒還會清空調試寄存器(DRn的內容。有兩種方法達此目的,一是使用系統調用NtGetContextThread和NtSetContextThread。而是引起一個異常,修改線程上下文,然後用新的上下文恢復正常運行,如下所示: push offset handlerpush dword ptr fs:[0]mov fs:[0],espxor eax, eaxdiv eax ;generate exceptionpop fs:[0]add esp, 4;continue execution;...handler:mov ecx, [esp+0Ch] ;skip divadd dword ptr [ecx+0B8h], 2 ;skip divmov dword ptr [ecx+04h], 0 ;clean dr0mov dword ptr [ecx+08h], 0 ;clean dr1mov dword ptr [ecx+0Ch], 0 ;clean dr2mov dword ptr [ecx+10h], 0 ;clean dr3mov dword ptr [ecx+14h], 0 ;clean dr6mov dword ptr [ecx+18h], 0 ;clean dr7xor eax, eaxret上面的第一行代碼將處理程序的偏移量壓入堆棧,以確保當異常被拋出時它自己的處理程序能取得控制權。之後進行相應設置,包括用自己異或自己的方式將eax設為0,以將控制權傳送給該處理程序。div eax 指令會引起異常,因為eax為0,所以AX將被除以零。該處理程序然後跳過除法指令,清空dr0-dr7,同樣也把eax置0,表示異常將被處理,然後恢復運行。9.解除調試器線程我們可以通過系統調用NtSetInformationThread從調試器拆卸線程。為此,將ThreadInformationClass設為0x11(ThreadHideFromDebugger)來調用NtSetInformationThread,如果存在調試器的話,這會將程序的線程從調試器拆下來。以下代碼就是一個例子:push 0push 0push 11h ;ThreadHideFromDebuggerpush -2call NtSetInformationThread在本例中,首先將NtSetInformationThread的參數壓入堆棧,然後調用該函數來把程序的線程從調試器中去掉。這是因為這里的0用於線程的信息長度和線程信息,傳遞的-2用於線程句柄,傳遞的11h用於線程信息類別,這里的值表示ThreadHideFromDebugger。10.解密解密可以通過各種防止調試的方式來進行。有的解密依賴於特定的執行路徑。如果這個執行路徑沒被沿用,比如由於在程序中的某個地方啟動了一個調試器,那麼解密演算法使用的值就會出錯,因此程序就無法正確進行自身的解密。HDSpoof使用的就是這種技術。一些病毒使用堆棧來解密它們的代碼,如果在這種病毒上使用調試器,就會引起解密失敗,因為在調試的時候堆棧為INT 1所用。使用這種技術的一個例子是W95/SK病毒,它在堆棧中解密和構建其代碼;另一個例子是Cascade病毒,它將堆棧指針寄存器作為一個解密密鑰使用。代碼如下所示:lea si, Start ; position to decryptmov sp, 0682 ; length of encrypted bodyDecrypt:xor [si], si ; decryption key/counter 1xor [si], sp ; decryption key/counter 2inc si ; increment one counterdec sp ; decrement the otherjnz Decrypt ; loop until all bytes are decryptedStart: ; Virus body對於Cascade病毒如何使用堆棧指針來解密病毒體,上面代碼中的注釋已經做了很好的說明。相反,Cryptor病毒將其密鑰存儲在鍵盤緩沖區中,這些密鑰會被調試器破壞。Tequila使用解密器的代碼作為解密鑰,因此如果解密器被調試器修改後,那麼該病毒就無法解密了。下面是Tequila用於解密的代碼:perform_encryption_decryption: mov bx,0 mov si,0960 mov cx,0960 mov dl,b[si] xor b[bx],dl inc si inc bx cmp si,09a0 jb 0a61 ;masm mod. needed mov si,0960 loop 0a52 ;masm mod. needed retthe_file_decrypting_routine: push cs pop ds mov bx,4 mov si,0964 mov cx,0960 mov dl,b[si] add b[bx],dl inc si inc bx cmp si,09a4 jb 0a7e ;masm mod. needed mov si,0964 loop 0a6f ;masm mod. needed jmp 0390 ;masm mod. needed人們正在研究可用於將來的新型反調試技術,其中一個項目的課題是關於多處器計算機的,因為當進行調試時,多處理器中的一個會處於閑置狀態。這種新技術使用並行處理技術來解密代碼。二、逆轉錄病毒逆轉錄病毒會設法禁用反病毒軟體,比如可以通過攜帶一列進程名,並殺死正在運行的與表中同名的那些進程。許多逆轉錄病毒還把進程從啟動列表中踢出去,這樣該進程就無法在系統引導期間啟動了。這種類型的惡意軟體還會設法擠占反病毒軟體的CPU時間,或者阻止反病毒軟體連接到反病毒軟體公司的伺服器以使其無法更新病毒庫。三、混合技術W32.Gobi病毒是一個多態逆轉錄病毒,它結合了EPO和其他一些反調試技術。該病毒還會在TCP埠666上打開一個後門。Simile(又名Metaphor)是一個非常有名的復合型病毒,它含有大約14,000行匯編代碼。這個病毒通過尋找API調用ExitProcess()來使用EPO,它還是一個多態病毒,因為它使用多態解密技術。它的90%代碼都是用於多態解密,該病毒的主體和多態解密器在每次感染新文件時,都會放到一個半隨機的地方。Simile的第一個有效載荷只在3月、6月、9月或12月份才會激活。在這些月份的17日變體A和B顯示它們的消息。變體C在這些月份的第18日顯示它的消息。變體A和B中的第二個有效載荷只有在五月14日激活,而變體C中的第二個有效載荷只在7月14日激活。Ganda是一個使用EPO的逆轉錄病毒。它檢查啟動進程列表,並用一個return指令替換每個啟動進程的第一個指令。這會使所有防病毒程序變得毫無用處。四、小結本文中,我們介紹了惡意軟體用以阻礙對其進行逆向工程的若干反調試技術,同時介紹了逆轉錄病毒和各種反檢測技術的組合。我們應該很好的理解這些技術,只有這樣才能夠更有效地對惡意軟體進行動態檢測和分析。
❿ TLS線程局部存儲有什麼用
當然也可以使用Tls系列的函數來完成線程局部存儲 效果是一樣的 假設有多你就會想到為什麼不用一個公用的變數來存儲呢? 不然好麻煩! 這就是用到