謝邀。
其實Linux創(chuàng)建進程,就是創(chuàng)建進程運行所需的內存空間,填充描述進程的task_struct結構體,以及加載進程的程序而已。
Linux內核并無專門創(chuàng)建線程的機制
我們之前提到,Linux并不特殊對待線程,在Linux看來,線程不過就是一種特殊的進程而已。那么,Linux是如何創(chuàng)建線程的呢?
線程機制是大多數現代編程語言都會提供的機制,該機制允許在同一進程的共享內存地址空間運行一組“特殊的進程(即線程)”。這些線程不僅共享同一段內存空間,還可以共享已經打開的文件,統(tǒng)計量等其他資源。線程機制支持程序并發(fā)運行,在多處理器核心的系統(tǒng)上,該并發(fā)機制能夠實現多條線程同時運行。
Linux管理線程的方式不同于其他一些經典操作系統(tǒng),Linux并沒有線程的概念,它把線程當作進程的一個子集來管理。因此,Linux內核并未為線程提供額外調度算法,也沒有提供額外的數據結構用于描述和存儲線程。
就像進程一樣,Linux使用task_struct結構體描述和記錄線程,每個線程都有唯一屬于自己的task_struct結構。從這個角度來看,線程就是一個普通的進程,只不過線程可能和其他進程共享一些資源而已。
以Windows為代表的一些操作系統(tǒng)提供了專門用于創(chuàng)建線程的機制,在這些系統(tǒng)中,線程常常被稱作“輕量級進程”,因為相對于進程而言,線程耗費的資源較少,能夠較為迅速的創(chuàng)建和投入運行。
但是對于Linux而言,線程不過是進程之間共享資源的一種手段罷了。那么是不是Linux中的線程比Windows中的線程更加“重量級”呢?也不是,因為Linux中的進程本身就很輕量級,Linux創(chuàng)建進程所需時間,并不比Windows創(chuàng)建線程所需時間多多少。
從C語言代碼層面來看,假設某個進程包含4個線程,以Windows為代表的一些操作系統(tǒng)一般會有一個包含指向4個不同線程的指針的進程描述符,負責描述地址空間、打開的文件等共享資源,而線程本身再去描述自己獨占的資源。
與之對應的,Linux的做法就高雅許多,它僅需為這4個線程創(chuàng)建4個task_struct結構體,然后在task_struct中指定它們共享的資源就可以了。
創(chuàng)建線程
看了我最近幾篇文章的讀者應該已經明白,Linux內核中的線程其實就是進程,因此線程的創(chuàng)建與進程的創(chuàng)建過程是類似的,從C語言源代碼層面看,基本上也是通過fork()函數和exec()函數族實現的。只不過在調用clone()函數時需要傳遞一個參數用于描述共享資源,例如:
上面這行C語言代碼和調用fork()函數的結果差不多,只不過輸入的幾個參數標志位說明了子進程與父進程共享一些資源:地址空間、文件系統(tǒng)、打開的文件、信號處理程序。
對比一下,fork()基本上就相當于clone(SIGCHLD,0),這也是fork()函數創(chuàng)建的子進程之后不再與父進程共享資源的原因。
關于clone()函數的參數標志位,可以在Linux中輸入man命令查看。
Linux內核線程
就像用戶空間的C語言程序開發(fā)一樣,Linux內核也經常需要在后臺處理數據,這時就需要借助內核線程了。Linux的內核線程一般不會獨立的地址空間,它們只在內核空間運行,不會切換到用戶空間。不過調度是和普通進程一樣的,可以被調度和搶占。
Linux創(chuàng)建內核線程由kthread_create()函數實現,它的C語言源代碼如下,請看:
可見,kthread_create()函數的C語言代碼并不長,而且也可以看出,Linux內核線程是通過kthread_create_info結構體描述的,它的定義C語言代碼如下,可見,內核線程的描述和存儲也是包含task_struct結構體的:
kthread_create()函數創(chuàng)建名為namefmt的線程,不過線程被創(chuàng)建后是處于不可運行狀態(tài)的,我們可以通過wake_up_process()函數喚醒它。當然,也可以通過kthread_run()方法實現這一過程,相關的C語言代碼如下,請看:
其實就是將kthread_create()函數和wake_up_process()函數組合到一起而已。Linux的內核線程被啟動后,會一直運行到調用do_exit()退出。我們也可以調用kthread_stop()函數提前結束它,相關的C語言代碼如下,請看:
kthread_stop()函數接收的參數為kthread_create()函數創(chuàng)建的結構體的task_struct成員。從C語言代碼可以看出,kthread_stop()其實也是會調用wake_up_process()函數喚醒線程的,它在喚醒線程后,會等待線程函數退出,并不會調用threadfn()函數。
這里需要注意,如果創(chuàng)建的線程函數threadfn()調用了do_exit()函數,最好就不要再調用kthread_stop()函數了。
kthread_stop()函數等待線程退出是通過wait_for_completion()函數實現的,相關的C語言代碼如下,請看:
稍稍跟蹤一下C語言代碼,發(fā)現其實這一等待過程是由do_wait_for_common()函數實現的,它的C語言代碼如下,請看:
還是比較清晰的,這里就不再贅述了。至此,我們就了解了Linux內核是如何創(chuàng)建線程并投入運行,以及如何結束內核線程的了。
小結
本節(jié)主要討論了Linux內核中的線程的創(chuàng)建,應該能夠看出,其實核心還是圍繞對task_struct結構的管理,這與管理進程并無過多區(qū)別。因此,說Linux中的線程只是一種特殊的進程,一點也不為過。