網(wǎng)站制作公司網(wǎng)站建設/chrome官網(wǎng)下載
Linux內核源碼進程原理分析
- 一、Linux 內核架構圖
- 二、進程基礎知識
- 三、Linux 進程四要素
- 四、task_struct 數(shù)據(jù)結構主要成員
- 五、創(chuàng)建新進程分析
- 六、剖析進程狀態(tài)遷移
- 七、寫時復制技術
一、Linux 內核架構圖
二、進程基礎知識
Linux 內核把進程稱為任務(task),進程的虛擬地址空間分為用戶虛擬地址空間和內核虛擬地址空間,所有進程共享內核虛擬地址空間,每個進程有獨立的用戶虛擬地址空間。
進程有兩種特殊形式:
沒有用戶虛擬地址空間的進程稱為內核線程。
共享用戶虛擬地址空間的進程稱為用戶線程。
通用在不會引起混淆的情況下把用戶線程簡稱為線程。共享同一個用戶虛擬地址空間的所有用戶線程組成一個線程組。
C 標準庫進程術語和 Linux 內核進程術語對應關系如下:
C 標準庫進程術語 | Linux 內核進程術語 |
---|---|
包含多個線程的進程 | 線程組 |
只有一個線程的進程 | 進程或任務 |
線程 | 共享用戶虛擬地址空間的進程 |
三、Linux 進程四要素
- 有一段程序供其執(zhí)行。
- 有進程專用的系統(tǒng)堆棧空間。
- 在內核有 task_struct 數(shù)據(jù)結構。
- 有獨立的存儲空間,擁有專有的用戶空間。
四、task_struct 數(shù)據(jù)結構主要成員
(include/linux/sched.h)
struct task_struct {//進程描述符
#ifdef CONFIG_THREAD_INFO_IN_TASK/** For reasons of header soup (see current_thread_info()), this* must be the first element of task_struct.*/struct thread_info thread_info;
#endifunsigned int __state;//指向進程狀態(tài)#ifdef CONFIG_PREEMPT_RT/* saved state for "spinlock sleepers" */unsigned int saved_state;
#endif/** This begins the randomizable portion of task_struct. Only* scheduling-critical items should be added above here.*/randomized_struct_fields_startvoid *stack;//指向內核棧refcount_t usage;/* Per task flags (PF_*), defined further below: */unsigned int flags;unsigned int ptrace;// ......
};
- task_struct:進程描述符。
- __state:指向進程狀態(tài)。
- *stack:指向內核棧。
- pid:指向全局的進程號。
- tgid:指向全局的線程組的標識符。
- *real_parent:指向真實的父進程
- *parent:指向當前的父進程。比如一個進程被另外的進程使用系統(tǒng)調用進行跟蹤(ptrace),那么此時的父進程就是跟蹤進程。
- 進程調度策略的優(yōu)先級:prio、static_prio、normal_prio、rt_priority。
- nr_cpus_allowed:允許進程在哪些處理器上執(zhí)行。
- *mm:指向內存描述符,內核線程此項位NULL。
- *active_mm:指向內存描述符,內核線程運行時從進程借用。
- *fs:文件系統(tǒng)信息。
還有很多成員,這里就不一一列舉。
五、創(chuàng)建新進程分析
在 Linux 內核中,新進程是從一個已經(jīng)存在的進程復制出來的,內核使用靜態(tài)數(shù)據(jù)結構造出 0 號內核線程,0 號內核線程分叉生成 1 號內核線程和 2 號內核線程(kthreadd 線程)。1 號內核線程完成初始化以后裝載用戶程序,變成 1 號進程,其他進程都是 1 號進程或者它的子孫進程分叉生成的;其他內核線程是 kthreadd 線程分叉生成的。
Linux 3 個系統(tǒng)調用創(chuàng)建新的進程:
- fork(分叉):子進程是父進程的一個副本,采用寫時復制技術。
- vfork:用于創(chuàng)建子進程,之后子進程立即調用 execve 以裝載新程序的情況,為了避免復制物理頁,父進程會睡眠等待子進程裝載新程序?,F(xiàn)在 fork 采用了寫時復制技術,vfork 失去了速度優(yōu)勢,已經(jīng)被廢棄。
- clone(克隆):可以精確地控制子進程和父進程共享哪些資源。這個系統(tǒng)調用的主要用處是可供 pthread 庫用來創(chuàng)建線程。
clone 是功能最齊全的函數(shù),參數(shù)多、使用復雜,fork 是 clone 的簡化函數(shù)。
(kernel/fork.c)
#ifdef __ARCH_WANT_SYS_FORK
SYSCALL_DEFINE0(fork)
{
#ifdef CONFIG_MMUstruct kernel_clone_args args = {.exit_signal = SIGCHLD,};return _do_fork(&args);
#else/* can not support in nommu mode */return -EINVAL;
#endif
}
#endif#ifdef __ARCH_WANT_SYS_VFORK
SYSCALL_DEFINE0(vfork)
{struct kernel_clone_args args = {.flags = CLONE_VFORK | CLONE_VM,.exit_signal = SIGCHLD,};return _do_fork(&args);
}
#endif#ifdef __ARCH_WANT_SYS_CLONE
#ifdef CONFIG_CLONE_BACKWARDS
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,int __user *, parent_tidptr,unsigned long, tls,int __user *, child_tidptr)
#elif defined(CONFIG_CLONE_BACKWARDS2)
SYSCALL_DEFINE5(clone, unsigned long, newsp, unsigned long, clone_flags,int __user *, parent_tidptr,int __user *, child_tidptr,unsigned long, tls)
#elif defined(CONFIG_CLONE_BACKWARDS3)
SYSCALL_DEFINE6(clone, unsigned long, clone_flags, unsigned long, newsp,int, stack_size,int __user *, parent_tidptr,int __user *, child_tidptr,unsigned long, tls)
#else
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,int __user *, parent_tidptr,int __user *, child_tidptr,unsigned long, tls)
#endif
{struct kernel_clone_args args = {.flags = (lower_32_bits(clone_flags) & ~CSIGNAL),.pidfd = parent_tidptr,.child_tid = child_tidptr,.parent_tid = parent_tidptr,.exit_signal = (lower_32_bits(clone_flags) & CSIGNAL),.stack = newsp,.tls = tls,};if (!legacy_clone_args_valid(&args))return -EINVAL;return _do_fork(&args);
}
#endif
Linux 內核定義系統(tǒng)調用的獨特方式,目前以系統(tǒng)調用 fork 為例:創(chuàng)建新進程的 3 個系統(tǒng)調用在文件kernel/fork.c中,它們把工作委托給函數(shù)_do_fork(從6.0開始,更名為kernel_clone)。具體源碼分析如下:
long _do_fork(struct kernel_clone_args *args)
{u64 clone_flags = args->flags;struct completion vfork;struct pid *pid;struct task_struct *p;int trace = 0;long nr;// ......}
Linux 內核函數(shù)_do_fork()執(zhí)行流程如下圖所示:
具體核心處理函數(shù)為 copy_process()內核源碼如下:
/** This creates a new process as a copy of the old one,* but does not actually start it yet.** It copies the registers, and all the appropriate* parts of the process environment (as per the clone* flags). The actual kick-off is left to the caller.*/
static __latent_entropy struct task_struct *copy_process(struct pid *pid,int trace,int node,struct kernel_clone_args *args)
{int pidfd = -1, retval;struct task_struct *p;struct multiprocess_signals delayed;struct file *pidfile = NULL;u64 clone_flags = args->flags;struct nsproxy *nsp = current->nsproxy;// ......}
函數(shù) copy_process():創(chuàng)建新進程的主要工作由此函數(shù)完成, 具體處理流程如下圖所示:
同一個線程組的所有線程必須屬于相同的用戶命名空間和進程號命名空間。
六、剖析進程狀態(tài)遷移
進程主要有 7 種狀態(tài):
- 就緒狀態(tài)、
- 運行狀態(tài)、
- 輕度睡眠、
- 中度睡眠、
- 深度睡眠、
- 僵尸狀態(tài)、
- 死亡狀態(tài)。
它們之間狀態(tài)變遷如下:
就緒:state是TASK_RUNING(沒有嚴格區(qū)別就緒和運行),正在運行隊列中等待調度器調度。
運行:state是TASK_RUNING,證明調度器選中,正在CPU上執(zhí)行。
僵尸:state是TASK_DEAD,進程退出并且父進程關注子進程退出事件。
死亡:state是exit_state。
七、寫時復制技術
寫時復制核心思想:只有在不得不復制數(shù)據(jù)內容時才去復制數(shù)據(jù)內容;降低資源浪費。
申請新進程的步驟:
- 申請一塊空的PCB(進程控制塊)。
- 為 新進程分配數(shù)據(jù)資源(這里使用寫時復制技術)。
- 初始化PCB。
- 把剛才申請的新進程插入到就緒隊列中。state是task_running,被調度器調度,進入運行狀態(tài)。
應用程序(進程 1)修改頁面 C 之前:
應用程序(進程 1)修改頁面 C 之后:
注意:只有可修改的頁面才需要標記為寫時復制,不能修改的頁面可以由父進程和子進程共享。