做者:世至其美
博客地址:hqber.com
轉載須註明以上信息, 更多文章,請訪問我的博客:hqber.comlinux
進程是處於運行狀態的程序和相關資源的總稱,是資源分配的最小單位。緩存
線程是進程的內部的一個執行序列,是CPU調度的最小單位。多線程
Linux系統對於線程實現很是特殊,他並不區分線程和進程,線程只是一種特殊的進程罷了。從上面四點要素來看,擁有前三點而缺第四點要素的就是線程,若是徹底沒有第四點的用戶空間,那就是系統線程,若是是共享用戶空間,那就是用戶線程。併發
進程做爲分配資源的基本單位,而把線程做爲獨立運行和獨立調度的基本單位,因爲線程比進程更小,基本上不擁有系統資源,故對它的調度所付出的開銷就會小得多,能更高效的提升系統多個程序間併發執行的程度。編輯器
進程和線程的主要差異在於它們是不一樣的操做系統資源管理方式。進程有獨立的地址空間,一個進程崩潰後,在保護模式下不會對其它進程產生影響,而線程只是一個進程中的不一樣執行路徑。線程有本身的堆棧和局部變量,但線程之間沒有單獨的地址空間,一個線程死掉就等於整個進程死掉,因此多進程的程序要比多線程的程序健壯,但在進程切換時,耗費資源較大,效率要差一些。但對於一些要求同時進行而且又要共享某些變量的併發操做,只能用線程,不能用進程。ide
總結:linux中,進程和線程惟一區別是有沒有獨立的地址空間。函數
32位機器上,大約有1.7KB,進程描述符完整描述一個正在執行的進程的全部信息。spa
任務隊列(雙向循環鏈表)操作系統
進程描述符struct task_struct(源代碼 | linnux/sched.h | v5.4).net
struct task_struct { volatile long state; // -1爲不可運行, 0爲可運行, >0爲已中斷 int lock_depth; // 鎖的深度 unsigned int policy; // 調度策略:通常有FIFO,RR,CFS pid_t pid; // 進程標識符,用來表明一個進程 struct task_struct *parent; // 父進程 struct list_head children; // 子進程 struct list_head sibling; // 兄弟進程 }
linux採用slab分配器分配task_struct結構
目的:對象複用和緩存着色。
slab分配器動態生成task_struct,只需在棧底(相對於向下增加的棧)或棧頂(相對於向上增加的棧)建立一個新結構struct thread_info。
PID最大值默認爲32768(short int 短整形的最大值<linux/threads.h>)可經過修改/proc/sys/kernel/pid_max提升上限。
current宏查找當前正在運行進程的進程描述符。
x86系統中,current把棧指針後13個有效位屏蔽掉,用來計算出thread_info的偏移。
current_thread_info函數
movl $-8192,%eax andl %esp,%eax
<img src="https://i.loli.net/2021/01/26/xnVIHZK9qjo8TXd.png" alt="1571556447032" style="zoom: 67%;" />
陷入內核執行
init進程
init進程目的:讀取系統的初始化腳本,並執行其餘的相關程序,最終完成系統啓動的整個過程。
task_struct中記錄父子進程
其餘操做系統提供產生(spawn)進程機制,首先在新地址空間裏建立進程,讀入可執行文件,最後開始執行。
UNIX將上述機制流程分紅兩步fork()和exec()
使地址空間上的頁的拷貝推遲到實際發生寫入的時候才進行。
原理:若是有進程試圖修改一個頁,就會產生一個缺頁中斷。內核處理缺頁中斷的方式就是對該頁進行一次透明覆制。這時會清除頁面的COW屬性,表示着它再也不被共享。
fork()的實際開銷就是複製父進程的頁表以及給子進程建立惟一的進程描述符。
在如今linux內核中,fork()其實是由clone()系統調用實現的
<img src="https://i.loli.net/2021/01/26/m5xWw9acZnbro4H.png" alt="1571051424824" style="zoom: 80%;" />
注:內核有意讓子進程先執行,並不是總能如此,由於通常子進程都會立刻調用exec()函數,這樣能夠避免寫時拷貝的額外開銷。由於父進程先執行,可能往地址空間寫入。
vfork()和fork()區別:vfork()不拷貝父進程的頁表項。
vfork():子進程做爲父進程的一個單獨線程在它的地址空間裏運行,父進程被阻塞,直到子進程退出或執行exec(),子進程不能向地址空間寫入。
線程建立和進程建立基本一致,經過調用clone()函數傳遞的參數標誌,指明須要共享的資源。
建立線程
clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0); // CLONE_VM : 地址空間 // CLONE_FS : 文件系統 // CLONE_FILES : 文件描述符 // CLONE_SIGHAND : 信號處理程序及被阻斷的信號
建立進程(等同fork()函數)
clone(SIGCHLD,0);
建立進程(等同vfork()函數)
clone(CLONE_VFORK | CLONE_VM | SIGCHLD, 0)
內核線程只在內核空間執行,從不切換到用戶空間。
內核線程和普通進程的區別:內核線程沒有獨立的地址空間。(task_struct的mm指針被設置爲NULL)
內核線程只能由其餘內核線程建立,經過kthreadd內核線程衍生出全部新的內核線程。(kthreadd是全部內核線程的祖宗)
kthreadd內核線程是在內核初始化時被建立,循環執行kthreadd函數,它的做用是管理調度其它的內核線程。
kthreadd函數的做用是運行kthread_create_list全局鏈表中維護的kthread。能夠調用kthread_create函數建立一個kthread,它會被加入到kthread_create_list鏈表中,同時kthread_create函數會喚醒kthreadd_task。kthreadd在執行kthread會調用老的接口,kthreadd內核線程在運行kthread時,會調用老接口kernel_thread,它會運行一個名爲「kthread」的內核線程,去運行建立kthread,被執行的kthread會從kthread_create_list鏈表中刪除,而且kthreadd會不斷地調用scheduler讓出CPU,這個線程不能關閉。
建立內核線程,不運行
kthread_create函數(源代碼 | linux/kthread.h | v5.4)是經過clone()系統調用,建立一個內核線程,但新建立的線程處於不可運行狀態。
kthread_create(threadfn, data, namefmt, arg...)
建立內核線程,並運行
kthread_run函數(源代碼 | linux/kthread.h | v5.4),經過調用kthread_create函數建立內核線程,而後調用wake_up_process()進行喚醒。
#define kthread_run(threadfn, data, namefmt, ...) \ ({ \ struct task_struct *__k \ = kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \ if (!IS_ERR(__k)) \ wake_up_process(__k); \ __k; \ })
內核線程中止
int kthread_stop(struct task_struct *k);
釋放所佔用的資源,並告知父進程。
通常來講,進程的析構是自身引發的,它發生在進程調用exit()系統調用的時候。
既能夠顯式地調用exit()這個系統調用,也能夠隱性地從某個程序的主函數返回。(C語言編輯器會在main()函數的返回點後面放置調用exit代碼)
終結的任務大部分都靠do_exit()(<kernel/exit.c>)
wait族函數都是經過惟一但很複雜的一個系統調用wait4()來實現的,掛起調用它的進程,直到其中的一個子進程退出,此時函數會返回子進程的PID。此外,調用此函數時提供的指針會包含子函數的退出代碼。
做者:世至其美
博客地址:hqber.com
轉載須註明以上信息, 更多文章,請訪問我的博客:hqber.com