執行緒的那些事兒zz

2022-08-20 16:06:12 字數 4093 閱讀 6212

通過作業系統原理課,我們知道程序是系統資源分配的基本單位,執行緒是程式獨立執行的基本單位。執行緒有時候也被稱作小型程序,首先,這是因為多個執行緒之間是可以共享資源的;其次,多個執行緒之間的切換所花費的代價遠遠比程序低。

在使用者態下,使用最廣泛的執行緒操作介面即為posix執行緒介面,即pthread。通過這組介面可以進行執行緒的建立以及多執行緒之間的併發控制等。

按照這種執行緒機制的理解,每個使用者態的執行緒都和核心中的乙個輕量級程序相對應。多個輕量級程序之間共享資源,從而體現了多執行緒之間資源共享的特性。同時這些輕量級程序跟普通程序一樣由核心進行獨立排程,從而實現了多個程序之間的併發執行。

使用者執行緒和核心中輕量級程序的關聯通常實在符合posix執行緒標準的執行緒庫中完成的。支援輕量級程序的執行緒庫有三個:linuxthreads、ngpt(next-generation posix threads)和nptl(native posix thread library)。由於linuxthreads並不能完全相容posix標準以及ngpt的放棄,目前linux中所採用的執行緒庫即為nptl。

posix標準規定在乙個多執行緒的應用程式中,所有執行緒都必須具有相同的pid。從執行緒在核心中的實現可得知,每個執行緒其實都有自己的pid。為此,linux引入了執行緒組的概念。在乙個多執行緒的程式中,所有執行緒形成乙個執行緒組。每乙個執行緒通常是由主線程建立的,主線程即為呼叫pthread_create()的執行緒。因此該執行緒組中所有執行緒的pid即為主執行緒的pid。

對於執行緒組中的執行緒來說,其task_struct結構中的tpid欄位儲存該執行緒組中主線程的pid,而pid欄位則儲存每個輕量級程序的本身的pid。對於普通的程序而言,tgid和pid是相同的。事實上,getpid()系統呼叫中返回的是程序的tgid而不是pid。

上面所描述的都是使用者態下的執行緒,而在核心中還有一種特殊的執行緒,稱之為核心執行緒(kernel thread)。由於在核心中程序和執行緒不做區分,因此也可以將其稱為核心程序。毫無疑問,核心執行緒在核心中也是通過task_struct結構來表示的。

核心執行緒和普通程序一樣也是核心排程的實體,只不過他們有以下不同:

1).核心執行緒永遠都執行在核心態,而不同程序既可以執行在使用者態也可以執行在核心態。從另乙個角度講,核心執行緒只能之用大於page_offset(即3gb)的位址空間,而普通程序則可以使用整個4gb的位址空間。

2).核心執行緒只能呼叫核心函式,而普通程序必須通過系統呼叫才能使用核心函式。

程序、執行緒以及核心執行緒都有對應的建立函式,不過這三者所對應的建立函式最終在核心都是由do_fork()進行建立的,具體的呼叫關係圖如下:

從圖中可以看出,核心中建立程序的核心函式即為看do_fork(),該函式的原型如下:

long do_fork(unsigned long clone_flags,

unsigned long stack_start,

struct pt_regs *regs,

unsigned long stack_size,

int __user *parent_tidptr,

int __user *child_tidptr)

該函式的引數個數是固定的,每個引數的功能如下:

clone_flags:代表程序各種特性的標誌。低位元組指定子程序結束時傳送給父程序的訊號**,一般為sigchld訊號,剩餘三個位元組是若干個標誌或運算的結果。

stack_start:子程序使用者態堆疊的指標,該引數會被賦值給子程序的esp暫存器。

regs:指向通用暫存器值的指標,當程序從使用者態切換到核心態時通用暫存器中的值會被儲存到核心態堆疊中。

stack_size:未被使用,預設值為0。

既然程序、執行緒和核心執行緒在核心中都是通過do_fork()完成建立的,那麼do_fork()是如何體現其功能的多樣性?其實,clone_flags引數在這裡起到了關鍵作用,通過選取不同的標誌,從而保證了do_fork()函式實現多角色——建立程序、執行緒和核心執行緒——功能的實現。clone_flags引數可取的標誌很多,下面只介紹幾個與本文相關的標誌。

clone_vim:子程序共享父程序記憶體描述符和所有的頁表。

clone_fs:子程序共享父程序所在檔案系統的根目錄和當前工作目錄。

clone_files:子程序共享父程序開啟的檔案。

clone_sighand:子程序共享父程序的訊號處理程式、阻塞訊號和掛起的訊號。使用該標誌必須同時設定clone_vm標誌。

如果建立子程序時設定了上述標誌,那麼子程序會共享這些標誌所代表的父程序資源。

6.1 程序的建立

在使用者態程式中,可以通過fork()、vfork()和clone()三個介面函式建立程序,這三個函式在庫中分別對應同名的系統呼叫。系統呼叫函式通過128號軟中斷進入核心後,會呼叫相應的系統呼叫服務例程。這三個函式對應的服務歷程分別是sys_fork()、sys_vfork()和sys_clone()。

int sys_fork(struct pt_regs *regs)

int sys_vfork(struct pt_regs *regs)

long sys_clone(unsigned long clone_flags, unsigned long newsp,

void __user *parent_tid, void __user *child_tid, struct pt_regs *regs)

通過上述系統呼叫服務例程的原始碼可以發現,三個服務歷程內部都呼叫了do_fork(),只不過差別在於第乙個引數所傳的值不同。這也正好導致由這三個程序建立函式所建立的程序有不同的特性。下面對每種程序作以簡單說明。

fork():由於do_fork()中clone_flags引數除了子程序結束時返回給父程序的sigchld訊號外並無其他特性標誌,因此由fork()建立的程序不會共享父程序的任何資源。子程序會完全複製父程序的資源,也就是說父子程序相對獨立。不過由於寫時複製技術(copy on write,cow)的引入,子程序可以唯讀父程序的物理頁,只有當兩者之一去寫某個物理頁時,核心此時才會將這個頁的內容拷貝到乙個新的物理頁,並把這個新的物理頁分配給正在寫的程序。

vfork():do_fork()中的clone_flags使用了clone_vfork和clone_vm兩個標誌。clone_vfork標誌使得子程序先於父程序執行,父程序會阻塞到子程序結束或執行新的程式。clone_vm標誌使得子程序共享父程序的記憶體位址空間(父程序的頁表項除外)。在cow技術引入之前,vfork()適用子程序形成後立馬執行execv()的情形。因此,vfork()現如今已經沒有特別的使用之處,因為寫實複製技術完全可以取代它建立程序時所帶來的高效性。

clone():clone通常用於建立輕量級程序。通過傳遞不同的標誌可以對父子程序之間資料的共享和復製作精確的控制,一般flags的取值為clone_vm|clone_fs|clone_files|clone_sighand。由上述標誌可以看到,輕量級程序通常共享父程序的記憶體位址空間、父程序所在檔案系統的根目錄以及工作目錄資訊、父程序當前開啟的檔案以及父程序所擁有的訊號處理函式。

6.2 執行緒的建立

每個執行緒在核心中對應乙個輕量級程序,兩者的關聯是通過執行緒庫完成的。因此通過pthread_create()建立的執行緒最終在核心中是通過clone()完成建立的,而clone()最終呼叫do_fork()。

6.3 核心執行緒的建立

乙個新核心執行緒的建立是通過在現有的核心執行緒中使用kernel_thread()而建立的,其本質也是向do_fork()提供特定的flags標誌而建立的。

int kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)

從上面的組合的flag可以看出,新的核心執行緒至少會共享父核心執行緒的記憶體位址空間。這樣做其實是為了避免賦值呼叫執行緒的頁表,因為核心執行緒無論如何都不會訪問使用者位址空間。clone_untraced標誌保證核心執行緒不會被任何程序所跟蹤,

object的那些事兒

昨天和大姐qq。說給我看了,今年找到物件。我就在那裡騙他們。說我找了乙個 人。明年去 以後就沒有多少機會見到他們了。呵呵,大姐就說這樣我不聽話,那樣不聽話,不讓我去,說還借我錢買房子,不能去,去了父母白養這麼大了,我總是騙他們,逗他們。以前還騙他們說我出家。有一段時間,工作不順心,我壓力挺大的。不知...

和 的那些事兒

和 都可以用作邏輯與的運算子,表示邏輯與 and 當運算子兩邊的表示式的結果都為true時,整個運算結果才為true,否則,只要有一方為false,則結果為false。還具有短路的功能,即如果第乙個表示式為false,則不再計算第二個表示式,例如,對於if str null str.equals 表...

死鎖的那些事兒

死鎖的定義 如果乙個程序集合中的每個程序都在等待只能由該程序集合中的其他程序才能引發的事件,那麼,該程序集合就是死鎖的。產生死鎖的四個必要條件 產生死鎖的原因 預防死鎖 檢測死鎖 預防死鎖的幾種策略,會嚴重地損害系統效能。因此在避免死鎖時,要施加較弱的限制,從而獲得 較滿意的系統效能。由於在避免死鎖...