久综合色-久综合网-玖草影视-玖草资源在线-亚洲黄色片子-亚洲黄色片在线观看

Hello! 歡迎來到小浪云!


聊聊Linux中線程和進(jìn)程的聯(lián)系與區(qū)別!


關(guān)于進(jìn)程和線程,在linux中是非常核心的概念。然而,很多人對(duì)它們之間的聯(lián)系和區(qū)別并不清楚。

在網(wǎng)上對(duì)進(jìn)程和線程的討論中,大多數(shù)集中在它們之間的差異。但實(shí)際上,在Linux系統(tǒng)中,進(jìn)程和線程的相似之處要遠(yuǎn)遠(yuǎn)多于它們的不同之處。在Linux環(huán)境下,線程甚至被稱為輕量級(jí)進(jìn)程。

今天,我將從Linux內(nèi)核實(shí)現(xiàn)的角度,深入比較進(jìn)程和線程。

一、線程的創(chuàng)建方式

redis 6.0以上的版本為例,它開始支持使用多線程提供核心服務(wù)。

一旦Redis主線程啟動(dòng),就會(huì)調(diào)用initThreadedIO函數(shù)來創(chuàng)建多個(gè)I/O線程。

?

redis 源碼地址:https://github.com/redis/redis

//file:src/networking.c void?initThreadedIO(void)?{ ?//開始?io?線程的創(chuàng)建 ?for?(int?i?=?0;?i?

創(chuàng)建線程具體調(diào)用的是 pthread_create 函數(shù),pthread_create 是在 glibc 庫(kù)中實(shí)現(xiàn)的。在 glibc 庫(kù)中,pthread_create 函數(shù)的實(shí)現(xiàn)調(diào)用路徑是 __pthread_create_2_1 -> create_thread。其中 create_thread 這個(gè)函數(shù)比較重要,它設(shè)置了創(chuàng)建線程時(shí)使用的各種 flag 標(biāo)記。

//file:nptl/sysdeps/pthread/createthread.c static?int create_thread?(struct?pthread?*pd,?...) { ?int?clone_flags?=?(CLONE_VM?|?CLONE_FS?|?CLONE_FILES?|?CLONE_SIGNAL ????|?CLONE_SETTLS?|?CLONE_PARENT_SETTID ????|?CLONE_CHILD_CLEARTID?|?CLONE_SYSVSEM ????|?0);  ?int?res?=?do_clone?(pd,?attr,?clone_flags,?start_thread, ??????STACK_VARIABLES_ARGS,?1); ?... } 

在上面的代碼中,傳入?yún)?shù)中的各個(gè) flag 標(biāo)記是非常關(guān)鍵的。這里我們先知道一下傳入了 CLONE_VM、CLONE_FS、CLONE_FILES 等標(biāo)記就行了,后面我們會(huì)講內(nèi)核中針對(duì)這些參數(shù)做的特殊處理。

接下來的 do_clone 最終會(huì)調(diào)用一段匯編程序,在匯編里進(jìn)入 clone 系統(tǒng)調(diào)用,之后會(huì)進(jìn)入內(nèi)核中進(jìn)行處理。

//file:sysdeps/unix/sysv/linux/i386/clone.S ENTRY?(BP_SYM?(__clone)) ?... ?movl?$SYS_ify(clone),%eax ?... 

二、內(nèi)核中對(duì)線程的表示

在開始介紹線程的創(chuàng)建過程之前,先給大家看看內(nèi)核中表示線程的數(shù)據(jù)結(jié)構(gòu)

開篇的時(shí)候我說了,進(jìn)程和線程的相同點(diǎn)要遠(yuǎn)遠(yuǎn)大于不同點(diǎn)。主要依據(jù)就是在 Linux 中,無(wú)論進(jìn)程還是線程,都是抽象成了 task 任務(wù),在源碼里都是用 task_struct 結(jié)構(gòu)來實(shí)現(xiàn)的。

聊聊Linux中線程和進(jìn)程的聯(lián)系與區(qū)別!

我們來看 task_struct 具體的定義,它位于 include/linux/sched.h

//file:include/linux/sched.h struct?task_struct?{ ?//1.1?task狀態(tài)? ?volatile?long?state;  ?//1.2?進(jìn)程線程的pid ?pid_t?pid; ?pid_t?tgid;  ?//1.3?task樹關(guān)系:父進(jìn)程、子進(jìn)程、兄弟進(jìn)程 ?struct?task_struct?__rcu?*parent; ?struct?list_head?children;? ?struct?list_head?sibling; ?struct?task_struct?*group_leader;?  ?//1.4?task調(diào)度優(yōu)先級(jí) ?int?prio,?static_prio,?normal_prio; ?unsigned?int?rt_priority;  ?//1.5?地址空間 ?struct?mm_struct?*mm,?*active_mm;  ?//1.6?文件系統(tǒng)信息(當(dāng)前目錄等) ?struct?fs_struct?*fs;  ?//1.7?打開的文件信息 ?struct?files_struct?*files;  ?//1.8?namespaces? ?struct?nsproxy?*nsproxy;  ?... } 

這個(gè)數(shù)據(jù)結(jié)構(gòu)已經(jīng)在上一篇文章《Linux進(jìn)程是如何創(chuàng)建出來的?》中,我們?cè)敿?xì)介紹過了。

對(duì)于線程來講,所有的字段都是和進(jìn)程一樣的(本來就是一個(gè)結(jié)構(gòu)體來表示的)。包括狀態(tài)、pid、task 樹關(guān)系、地址空間、文件系統(tǒng)信息、打開的文件信息等等字段,線程也都有。

這也就是我前面說的,進(jìn)程和線程的相同點(diǎn)要遠(yuǎn)遠(yuǎn)大于不同點(diǎn),本質(zhì)上是同一個(gè)東西,都是一個(gè) task_struct !正因?yàn)檫M(jìn)程線程如此之相像,所以在 Linux 下的線程還有另外一個(gè)名字,叫輕量級(jí)進(jìn)程。至于說輕量在哪兒,稍后我們?cè)僬f。

這里我們稍微說一下 pid 和 tgid 這兩個(gè)字段。在 Linux 中,每一個(gè) task_struct 都需要被唯一的標(biāo)識(shí),它的 pid 就是唯一標(biāo)識(shí)號(hào)。

//file:include/linux/sched.h struct?task_struct?{ ?...... ?pid_t?pid; ?pid_t?tgid; } 

對(duì)于進(jìn)程來說,這個(gè) pid 就是我們平時(shí)常說的進(jìn)程 pid。

對(duì)于線程來說,我們假如一個(gè)進(jìn)程下創(chuàng)建了多個(gè)線程出來。那么每個(gè)線程的 pid 都是不同的。但是我們一般又需要記錄線程是屬于哪個(gè)進(jìn)程的。這時(shí)候,tgid 就派上用場(chǎng)了,通過 tgid 字段來表示自己所歸屬的進(jìn)程 ID。

聊聊Linux中線程和進(jìn)程的聯(lián)系與區(qū)別!

這樣內(nèi)核通過 tgid 可以知道線程屬于哪個(gè)進(jìn)程。

三、線程創(chuàng)建過程

要想知道進(jìn)程和線程的區(qū)別到底在哪兒,我們從線程的創(chuàng)建過程來詳細(xì)看一下。

3.1 回顧進(jìn)程創(chuàng)建

在《Linux進(jìn)程是如何創(chuàng)建出來的?》一文中我們了解了進(jìn)程的創(chuàng)建過程。事實(shí)上,進(jìn)程線程創(chuàng)建的時(shí)候,使用的函數(shù)看起來不一樣。但實(shí)際在底層實(shí)現(xiàn)上,最終都是使用同一個(gè)函數(shù)來實(shí)現(xiàn)的。

聊聊Linux中線程和進(jìn)程的聯(lián)系與區(qū)別!

我們?cè)俸?jiǎn)單回顧一下創(chuàng)建進(jìn)程時(shí) fork 系統(tǒng)調(diào)用的源碼,fork 調(diào)用主要就是執(zhí)行了 do_fork 函數(shù)。注意:fork 函數(shù)調(diào)用 do_fork 的傳的參數(shù)分別是SIGCHLD、0,0,NULL,NULL

//file:kernel/fork.c SYSCALL_DEFINE0(fork) { ?return?do_fork(SIGCHLD,?0,?0,?NULL,?NULL); } 

do_fork 函數(shù)又調(diào)用 copy_process 完成進(jìn)程的創(chuàng)建。

//file:kernel/fork.c long?do_fork(...) { ?//復(fù)制一個(gè)?task_struct?出來 ?struct?task_struct?*p; ?p?=?copy_process(clone_flags,?...); ?... } 

3.2 線程的創(chuàng)建

我們?cè)诒疚牡谝恍」?jié)里介紹到 lib 庫(kù)函數(shù) pthread_create 會(huì)調(diào)用到 clone 系統(tǒng)調(diào)用,為其傳入了一組 flag。

//file:nptl/sysdeps/pthread/createthread.c static?int create_thread?(struct?pthread?*pd,?...) { ?int?clone_flags?=?(CLONE_VM?|?CLONE_FS?|?CLONE_FILES?|?CLONE_SIGNAL ????|?CLONE_SETTLS?|?CLONE_PARENT_SETTID ????|?CLONE_CHILD_CLEARTID?|?CLONE_SYSVSEM ????|?0);  ?int?res?=?do_clone?(pd,?attr,?clone_flags,?...); ?... } 

好,我們找到 clone 系統(tǒng)調(diào)用的實(shí)現(xiàn)。

//file:kernel/fork.c SYSCALL_DEFINE5(clone,?......) { ?return?do_fork(clone_flags,?newsp,?0,?parent_tidptr,?child_tidptr); } 

同樣,do_fork 函數(shù)還是會(huì)執(zhí)行到 copy_process 來完成實(shí)際的創(chuàng)建。

3.3 進(jìn)程線程創(chuàng)建異同

可見和創(chuàng)建進(jìn)程時(shí)使用的 fork 系統(tǒng)調(diào)用相比,創(chuàng)建線程的 clone 系統(tǒng)調(diào)用幾乎和 fork 差不多,也一樣使用的是內(nèi)核里的 do_fork 函數(shù),最后走到 copy_process 來完整創(chuàng)建。

不過創(chuàng)建過程的區(qū)別是二者在調(diào)用 do_fork 時(shí)傳入的 clone_flags 里的標(biāo)記不一樣!

  • 創(chuàng)建進(jìn)程時(shí)的 flag:僅有一個(gè) SIGCHLD
  • 創(chuàng)建線程時(shí)的 flag:包括 CLONE_VM、CLONE_FS、CLONE_FILES、CLONE_SIGNAL、CLONE_SETTLS、CLONE_PARENT_SETTID、CLONE_CHILD_CLEARTID、CLONE_SYSVSEM。

關(guān)于這些 flag 的含義,我們選幾個(gè)關(guān)鍵的做一個(gè)簡(jiǎn)單的介紹,后面介紹 do_fork 細(xì)節(jié)的時(shí)候會(huì)再次涉及到。

  • CLONE_VM: 新 task 和父進(jìn)程共享地址空間
  • CLONE_FS:新 task 和父進(jìn)程共享文件系統(tǒng)信息
  • CLONE_FILES:新 task 和父進(jìn)程共享文件描述符表

這些 flag 會(huì)對(duì) task_struct 產(chǎn)生啥影響,我們接著看接下來的內(nèi)容。

四、揭秘 do_fork 系統(tǒng)調(diào)用

在本節(jié)中我們以動(dòng)態(tài)的視角來看一下線程的創(chuàng)建過程.

前面我們看到,進(jìn)程和線程創(chuàng)建都是調(diào)用內(nèi)核中的 do_fork 函數(shù)來執(zhí)行的。在 do_fork 的實(shí)現(xiàn)中,核心是一個(gè) copy_process 函數(shù),它以拷貝父進(jìn)程(線程)的方式來生成一個(gè)新的 task_struct 出來。

//file:kernel/fork.c long?do_fork(unsigned?long?clone_flags,?...) { ?//復(fù)制一個(gè)?task_struct?出來 ?struct?task_struct?*p; ?p?=?copy_process(clone_flags,?stack_start,?stack_size, ????child_tidptr,?NULL,?trace);  ?//子任務(wù)加入到就緒隊(duì)列中去,等待調(diào)度器調(diào)度 ?wake_up_new_task(p); ?... } 

在創(chuàng)建完畢后,調(diào)用 wake_up_new_task 將新創(chuàng)建的任務(wù)添加到就緒隊(duì)列中,等待調(diào)度器調(diào)度執(zhí)行。這個(gè)代碼很長(zhǎng),我對(duì)其進(jìn)行了一定程度的精簡(jiǎn)。

//file:kernel/fork.c static?struct?task_struct?*copy_process(...) { ?//4.1?復(fù)制進(jìn)程?task_struct?結(jié)構(gòu)體 ?struct?task_struct?*p; ?p?=?dup_task_struct(current); ?...  ?//4.2?拷貝?files_struct ?retval?=?copy_files(clone_flags,?p);  ?//4.3?拷貝?fs_struct ?retval?=?copy_fs(clone_flags,?p);  ?//4.4?拷貝?mm_struct ?retval?=?copy_mm(clone_flags,?p);  ?//4.5?拷貝進(jìn)程的命名空間?nsproxy ?retval?=?copy_namespaces(clone_flags,?p);  ?//4.6?申請(qǐng)?pid?&&?設(shè)置進(jìn)程號(hào) ?pid?=?alloc_pid(p->nsproxy->pid_ns); ?p->pid?=?pid_nr(pid); ?p->tgid?=?p->pid; ?if?(clone_flags?&?CLONE_THREAD) ??p->tgid?=?current->tgid;  ?...... } 

可見,copy_process 先是復(fù)制了一個(gè)新的 task_struct 出來,然后調(diào)用 copy_xxx 系列的函數(shù)對(duì) task_struct 中的各種核心對(duì)象進(jìn)行拷貝處理,還申請(qǐng)了 pid 。接下來我們分小節(jié)來查看該函數(shù)的每一個(gè)細(xì)節(jié)。

4.1 復(fù)制 task_struct 結(jié)構(gòu)體

注意一下,上面調(diào)用 dup_task_struct 時(shí)傳入的參數(shù)是 current,它表示的是當(dāng)前任務(wù)。在 dup_task_struct 里,會(huì)申請(qǐng)一個(gè)新的 task_struct 內(nèi)核對(duì)象,然后將當(dāng)前任務(wù)復(fù)制給它。需要注意的是,這次拷貝只會(huì)拷貝 task_struct 結(jié)構(gòu)體本身,它內(nèi)部包含的 mm_struct 等成員不會(huì)被復(fù)制。

聊聊Linux中線程和進(jìn)程的聯(lián)系與區(qū)別!

我們來簡(jiǎn)單看下具體的代碼。

//file:kernel/fork.c static?struct?task_struct?*dup_task_struct(struct?task_struct?*orig) { ?//申請(qǐng)?task_struct?內(nèi)核對(duì)象 ?tsk?=?alloc_task_struct_node(node); ?//復(fù)制?task_struct ?err?=?arch_dup_task_struct(tsk,?orig); ?... } 

其中 alloc_task_struct_node 用于在 slab 內(nèi)核內(nèi)存管理區(qū)中申請(qǐng)一塊內(nèi)存出來。關(guān)于 slab 機(jī)制請(qǐng)參考- 內(nèi)核內(nèi)存管理

//file:kernel/fork.c static?struct?kmem_cache?*task_struct_cachep; static?inline?struct?task_struct?*alloc_task_struct_node(int?node) { ?return?kmem_cache_alloc_node(task_struct_cachep,?GFP_KERNEL,?node); } 

申請(qǐng)完內(nèi)存后,調(diào)用 arch_dup_task_struct 進(jìn)行內(nèi)存拷貝。

//file:kernel/fork.c int?arch_dup_task_struct(struct?task_struct?*dst, ?????????struct?task_struct?*src) { ?*dst?=?*src; ?return?0; } 

4.2 拷貝打開文件列表

我們先回憶一下前面的內(nèi)容,創(chuàng)建線程調(diào)用 clone 系統(tǒng)調(diào)用的時(shí)候,傳入了一的 flag,其中有一個(gè)就是 CLONE_FILES。如果傳入了 CLONE_FILES 標(biāo)記,就會(huì)復(fù)用當(dāng)前進(jìn)程的打開文件列表 – files 成員。

聊聊Linux中線程和進(jìn)程的聯(lián)系與區(qū)別!

對(duì)于創(chuàng)建進(jìn)程來講,沒有傳入這個(gè)標(biāo)志,就會(huì)新創(chuàng)建一個(gè) files 成員出來。

聊聊Linux中線程和進(jìn)程的聯(lián)系與區(qū)別!

好了,我們繼續(xù)看 copy_files 具體實(shí)現(xiàn)。

//file:kernel/fork.c static?int?copy_files(unsigned?long?clone_flags,?struct?task_struct?*tsk) { ?struct?files_struct?*oldf,?*newf; ?oldf?=?current->files;  ?if?(clone_flags?&?CLONE_FILES)?{ ??atomic_inc(&oldf->count); ??goto?out; ?} ?newf?=?dup_fd(oldf,?&error); ?tsk->files?=?newf; ?... } 

從代碼看出,如果指定了 CLONE_FILES(創(chuàng)建線程的時(shí)候),只是在原有的 files_struct 里面 +1 就算是完事了,指針不變,仍然是復(fù)用創(chuàng)建它的進(jìn)程的 files_struct 對(duì)象。

這就是進(jìn)程和線程的其中一個(gè)區(qū)別,對(duì)于進(jìn)程來講,每一個(gè)進(jìn)程都需要獨(dú)立的 files_struct。但是對(duì)于線程來講,它是和創(chuàng)建它的線程復(fù)用 files_struct 的。

4.3 拷貝文件目錄信息

再回憶一下創(chuàng)建線程的時(shí)候,傳入的 flag 里也包括 CLONE_FS。如果指定了這個(gè)標(biāo)志,就會(huì)復(fù)用當(dāng)前進(jìn)程的文件目錄 – fs 成員。

聊聊Linux中線程和進(jìn)程的聯(lián)系與區(qū)別!

對(duì)于創(chuàng)建進(jìn)程來講,沒有傳入這個(gè)標(biāo)志,就會(huì)新創(chuàng)建一個(gè) fs 出來。

聊聊Linux中線程和進(jìn)程的聯(lián)系與區(qū)別!

好,我們繼續(xù)看 copy_fs 的實(shí)現(xiàn)。

//file:kernel/fork.c static?int?copy_fs(unsigned?long?clone_flags,?struct?task_struct?*tsk) { ?struct?fs_struct?*fs?=?current->fs; ?if?(clone_flags?&?CLONE_FS)?{ ??fs->users++; ??return?0; ?} ?tsk->fs?=?copy_fs_struct(fs); ?return?0; } 

和 copy_files 函數(shù)類似,在 copy_fs 中如果指定了 CLONE_FS(創(chuàng)建線程的時(shí)候),并沒有真正申請(qǐng)獨(dú)立的 fs_struct 出來,近幾年只是在原有的 fs 里的 users +1 就算是完事。

而在創(chuàng)建進(jìn)程的時(shí)候,由于沒有傳遞這個(gè)標(biāo)志,會(huì)進(jìn)入到 copy_fs_struct 函數(shù)中申請(qǐng)新的 fs_struct 并進(jìn)行賦值拷貝。

4.4 拷貝內(nèi)存地址空間

創(chuàng)建線程的時(shí)候帶了 CLONE_VM 標(biāo)志,而創(chuàng)建進(jìn)程的時(shí)候沒帶。接下來在 copy_mm 函數(shù) 中會(huì)根據(jù)是否有這個(gè)標(biāo)志來決定是該和當(dāng)前線程共享一份地址空間 mm_struct,還是創(chuàng)建一份新的。

//file:kernel/fork.c static?int?copy_mm(unsigned?long?clone_flags,?struct?task_struct?*tsk) { ?struct?mm_struct?*mm,?*oldmm; ?oldmm?=?current->mm;  ?if?(clone_flags?&?CLONE_VM)?{ ??atomic_inc(&oldmm->mm_users); ??mm?=?oldmm; ??goto?good_mm; ?} ?mm?=?dup_mm(tsk); good_mm: ?return?0;? } 

對(duì)于線程來講,由于傳入了 CLONE_VM 標(biāo)記,所以不會(huì)申請(qǐng)新的 mm_struct 出來,而是共享其父進(jìn)程的。

聊聊Linux中線程和進(jìn)程的聯(lián)系與區(qū)別!

多線程程序中的所有線程都會(huì)共享其父進(jìn)程的地址空間。

聊聊Linux中線程和進(jìn)程的聯(lián)系與區(qū)別!

而對(duì)于多進(jìn)程程序來說,每一個(gè)進(jìn)程都有獨(dú)立的 mm_struct(地址空間)。

聊聊Linux中線程和進(jìn)程的聯(lián)系與區(qū)別!

因?yàn)樵趦?nèi)核中線程和進(jìn)程都是用 task_struct 來表示,只不過線程和進(jìn)程的區(qū)別是會(huì)和創(chuàng)建它的父進(jìn)程共享打開文件列表、目錄信息、虛擬地址空間等數(shù)據(jù)結(jié)構(gòu),會(huì)更輕量一些。所以在 Linux 下的線程也叫輕量級(jí)進(jìn)程

在打開文件列表、目錄信息、內(nèi)存虛擬地址空間中,內(nèi)存虛擬地址空間是最重要的。因此區(qū)分一個(gè) Task 任務(wù)該叫線程還是該叫進(jìn)程,一般習(xí)慣上就看它是否有獨(dú)立的地址空間。如果有,就叫做進(jìn)程,沒有,就叫做線程。

這里展開多說一句,對(duì)于內(nèi)核任務(wù)來說,無(wú)論有多少個(gè)任務(wù),其使用地址空間都是同一個(gè)。所以一般都叫內(nèi)核線程,而不是內(nèi)核進(jìn)程。

五 結(jié)論

創(chuàng)建線程的整個(gè)過程我們就介紹完了。回頭總結(jié)一下,對(duì)于線程來講,其地址空間 mm_struct、目錄信息 fs_struct、打開文件列表 files_struct 都是和創(chuàng)建它的任務(wù)共享的。

聊聊Linux中線程和進(jìn)程的聯(lián)系與區(qū)別!

但是對(duì)于進(jìn)程來講,地址空間 mm_struct、掛載點(diǎn) fs_struct、打開文件列表 files_struct 都要是獨(dú)立擁有的,都需要去申請(qǐng)內(nèi)存并初始化它們。

聊聊Linux中線程和進(jìn)程的聯(lián)系與區(qū)別!

總之,在 Linux 內(nèi)核中并沒有對(duì)線程做特殊處理,還是由 task_struct 來管理。從內(nèi)核的角度看,用戶態(tài)的線程本質(zhì)上還是一個(gè)進(jìn)程。只不過和普通進(jìn)程比,稍微“輕量”了那么一些。

那么線程具體能輕量多少呢?我之前曾經(jīng)做過一個(gè)進(jìn)程和線程的上下文切換開銷測(cè)試。進(jìn)程的測(cè)試結(jié)果是一次上下文切換平均 2.7 – 5.48 us 之間。線程上下文切換是 3.8 us左右。總的來說,進(jìn)程線程切換還是沒差太多。

相關(guān)閱讀

主站蜘蛛池模板: 日韩区| 久久爰www免费人成 久久曰视频 | 亚洲激情视频网站 | 国产成人免费全部网站 | 国产20岁美女一级毛片 | 黄www片| 一级亚洲 | 国产第2页 | 天堂8中文在线最新版在线 天堂8资源8在线 | 91在线一区二区三区 | 亚洲精品一区二区三区 | 久久久精品久久 | 热99re久久国超精品首页 | 久久久精品久久视频只有精品 | 日韩在线视频免费不卡一区 | 日本最色视频 | 亚洲另类在线视频 | a级黄色毛片免费播放视频 a级精品九九九大片免费看 | 成年女人午夜免费视频 | 玖玖精品视频在线 | 国产在视频线在精品 | 久久精品国产一区二区 | 国美女福利视频午夜精品 | 久久91在线 | 国产成人黄网址在线视频 | 欧美a一级片 | 怡红院亚洲怡红院首页 | 欧美做爰野外在线视频观看 | 精品在线一区二区三区 | 欧美人成在线观看网站高清 | 久草中文在线视频 | 免费国产a | 一级特黄aaa大片 | 国产一级片儿 | 久久精品国产亚洲精品2020 | 国产精品久久久久激情影院 | 99视频在线国产 | 国产精品久久久久久久久免费hd | 国产成人精品s8p视频 | 日本在线视频播放 | 亚洲欧美一区二区三区综合 |