tft每日頭條

 > 生活

 > 進程與線程有何區别與聯系

進程與線程有何區别與聯系

生活 更新时间:2024-11-16 00:16:44

在用戶空間我們可以通過fork()函數來創建一個新的進程。fork()是一個glibc标準庫函數,在内核裡邊會有一個系統調用與之對應--sys_fork()。同樣的,我們是通過pthread_create()來創建一個線程,内核中對應的系統調用是clone()。現在通過分析sys_fork()和clone()的異同來看進程和線程的區别。本文是基于5.0版本的Linux内核和2.21版本的glibc。

fork

首先看一下sys_fork(),定義在kernel\fork.c文件中:

SYSCALL_DEFINE0(fork) { #ifdef CONFIG_MMU return _do_fork(SIGCHLD, 0, 0, NULL, NULL, 0); #else /* can not support in nommu mode */ return -EINVAL; #endif }

可以看到sys_fork 會調用 _do_fork。

pthread_create

對于線程的創建,我們先來看glibc源碼(glibc-2.21\sysdeps\unix\sysv\linux\createthread.c):

static int create_thread (struct pthread *pd, const struct pthread_attr *attr, bool stopped_start, STACK_VARIABLES_PARMS, bool *thread_ran) { ...... const int clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SYSVSEM | CLONE_SIGHAND | CLONE_THREAD | CLONE_SETtls | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID | 0); TLS_DEFINE_INIT_TP (tp, pd); if (__glibc_unlikely (ARCH_CLONE (&start_thread, STACK_VARIABLES_ARGS, clone_flags, pd, &pd->tid, tp, &pd->tid) == -1)) return errno; ........ }

我們這裡可以看到,在glibc中先設置了一個clone_flag(這個flag标記很重要,最後和進程的一個很大的區别就在這裡),然後陷入到的clone()系統調用:

SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp, int __user *, parent_tidptr, int __user *, child_tidptr, unsigned long, tls) { return _do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr, tls);

這裡我們發現,創建線程時最後也會調用到 _do_fork函數!那麼在_do_fork()中,創建進程和創建線程有什麼區别呢?我們繼續往下看。

long _do_fork(unsigned long clone_flags, unsigned long stack_start, unsigned long stack_size, int __user *parent_tidptr, int __user *child_tidptr, unsigned long tls) { struct task_struct *p; int trace = 0; long nr; ...... p = copy_process(clone_flags, stack_start, stack_size, child_tidptr, NULL, trace, tls, NUMA_NO_NODE); ...... if (!IS_ERR(p)) { struct PID *pid; pid = get_task_pid(p, PIDTYPE_PID); nr = pid_vnr(pid); if (clone_flags & CLONE_PARENT_SETTID) put_user(nr, parent_tidptr); ...... wake_up_new_task(p); ...... put_pid(pid); } ......

相關視頻推薦

6道經典的linux操作系統面試題,助你了解操作系統底層原理

5000道C “八股文”面試題,還需要死記硬背嗎?

【C 後端】2023年最新技術圖譜,c 後端的8個技術維度,助力你快速成為大牛

需要C/C Linux服務器架構師學習資料加qun812855908獲取(資料包括C/C ,Linux,golang技術,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK,ffmpeg等),免費分享

進程與線程有何區别與聯系(從進程和線程的創建過程來看進程和線程的區别)1

在_do_fork這個函數之中最重要的步驟就是 copy_process, copy_process函數中最重要的則是創建新任務的task_struct結構體。對于内核來講進程和線程都是一個task任務,而内核用task_struct來封裝一個任務。 task_struct 的結構示意圖如下所示(圖來源于極客時間趣談Linux操作系統):

進程與線程有何區别與聯系(從進程和線程的創建過程來看進程和線程的區别)2

static __latent_entropy struct task_struct *copy_process( unsigned long clone_flags, unsigned long stack_start, unsigned long stack_size, int __user *child_tidptr, struct pid *pid, int trace, unsigned long tls, int node) { int retval; struct task_struct *p; ...... p = dup_task_struct(current, node);

dup_task_struct 主要做了下面幾件事情:

  • 調用 alloc_task_struct_node 分配一個 task_struct 結構;
  • 調用 alloc_thread_stack_node 來創建内核棧,這裡面調用 __vmalloc_node_range 分配一個連續的 THREAD_SIZE 的内存空間,賦值給 task_struct 的 void *stack 成員變量;
  • 調用 arch_dup_task_struct(struct task_struct *dst, struct task_struct *src),将 task_struct 進行複制,其實就是調用 memcpy;
  • 調用 setup_thread_stack 設置 thread_info。

到這裡,整個 task_struct 複制了一份,而且内核棧也創建好了。 繼續看copy_process:

/* copy all the process information */ shm_init_task(p); retval = security_task_alloc(p, clone_flags); if (retval) goto bad_fork_cleanup_audit; retval = copy_semundo(clone_flags, p); if (retval) goto bad_fork_cleanup_security; retval = copy_files(clone_flags, p); if (retval) goto bad_fork_cleanup_semundo; retval = copy_fs(clone_flags, p); if (retval) goto bad_fork_cleanup_files; retval = copy_sighand(clone_flags, p); if (retval) goto bad_fork_cleanup_fs; retval = copy_signal(clone_flags, p); if (retval) goto bad_fork_cleanup_sighand; retval = copy_mm(clone_flags, p); if (retval) goto bad_fork_cleanup_signal; retval = copy_namespaces(clone_flags, p); if (retval) goto bad_fork_cleanup_mm; retval = copy_io(clone_flags, p); if (retval) goto bad_fork_cleanup_namespaces; retval = copy_thread_tls(clone_flags, stack_start, stack_size, p, tls); if (retval) goto bad_fork_cleanup_io;

這一段代碼主要是用來複制任務的信息,内容比較多,我們可以從函數名稱就大概猜出具體複制些什麼,這裡主要挑五個比較重要的來講。

copy_files

主要用于複制一個進程打開的文件信息。這些信息用一個結構 files_struct 來維護,每個打開的文件都有一個文件描述符。

copy_fs

主要用于複制一個進程的目錄信息。這些信息用一個結構 fs_struct 來維護。

copy_sighand

分配一個新的 sighand_struct。這裡最主要的是維護信号處理函數

copy_signal

複制用于維護發給這個進程的信号的數據結構

copy_mm

進程都有自己的内存空間,用 mm_struct 結構來表示。copy_mm 函數中調用 dup_mm,分配一個新的 mm_struct 結構,調用 memcpy 複制這個結構。

接下來一個個分析這幾個函數

copy_files

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; out: return error; }

這個函數中我們看到了在glibc create_thread中設置的clone_flags,在上面的代碼中可以看出,如果設置了CLONE_FLES這個位,那麼隻是給主線程的task_struct的files_struct的引用計數count原子地加1,然後就返回了。而如果是進程的創建,CLONE_FLES位沒有被标記,則會在dup_fd裡邊創建一個新的 files_struct,然後将所有的文件描述符數組 fdtable 拷貝一份。dup_fd函數定義在linux-5.0\fs\file.c文件中,這裡就不貼代碼了。

copy_fs

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_fs,在創建進程時是調用 copy_fs_struct 複制一個 fs_struct。如果是創建線程則由于 CLONE_FS 标識位已經被設置,所以也是将原來的 fs_struct 的用戶數加一。這個邏輯和copy_files是一樣的。

copy_sighand

對于和信号相關的的兩個結構體,sighand_struct、signal_struct,處理過程也是類似,如果是進程創建則會複制一分,而線程創建也僅僅是将将原來的 sighand_struct 引用計數加一。

static int copy_sighand(unsigned long clone_flags, struct task_struct *tsk) { struct sighand_struct *sig; if (clone_flags & CLONE_SIGHAND) { atomic_inc(¤t->sighand->count); return 0; } sig = kmem_cache_alloc(sighand_cachep, GFP_KERNEL); atomic_set(&sig->count, 1); memcpy(sig->action, current->sighand->action, sizeof(sig->action)); return 0;

copy_signal

對于 copy_signal,進程的創建是創建一個新的 signal_struct,而線程的創建則因為 CLONE_THREAD 直接返回了。

static int copy_signal(unsigned long clone_flags, struct task_struct *tsk) { struct signal_struct *sig; if (clone_flags & CLONE_THREAD) return 0; sig = kmem_cache_zalloc(signal_cachep, GFP_KERNEL); tsk->signal = sig; init_sigpending(&sig->shared_pending); ...... }

copy_mm

對于 copy_mm,進程創建是是調用 dup_mm 複制一個 mm_struct,線程的創建則因為 CLONE_VM 标識位而直接指向了原來的 mm_struct。

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) { mmget(oldmm); mm = oldmm; goto good_mm; } mm = dup_mm(tsk); good_mm: tsk->mm = mm; tsk->active_mm = mm; return 0; }

總結

綜上所述,創建進程的時候調用的系統調用是 fork,在 copy_process 函數裡面,會将五個最重要的結構體 files_struct、fs_struct、sighand_struct、signal_struct、mm_struct 都複制一遍,從此父進程和子進程各用各的數據結構,各有一個獨立的内存空間。而創建線程的話,調用的是系統調用 clone,在 copy_process 函數裡面, 五大結構僅僅是引用計數加一,也即線程共享進程的數據結構。

這裡在次引用極客時間趣談Linux操作系統的一張圖總結一下:

進程與線程有何區别與聯系(從進程和線程的創建過程來看進程和線程的區别)3

,

更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!

查看全部

相关生活资讯推荐

热门生活资讯推荐

网友关注

Copyright 2023-2024 - www.tftnews.com All Rights Reserved