網(wǎng)站制作公司網(wǎng)站建設(shè)/chrome官網(wǎng)下載
Linux內(nèi)核源碼進(jìn)程原理分析
- 一、Linux 內(nèi)核架構(gòu)圖
- 二、進(jìn)程基礎(chǔ)知識
- 三、Linux 進(jìn)程四要素
- 四、task_struct 數(shù)據(jù)結(jié)構(gòu)主要成員
- 五、創(chuàng)建新進(jìn)程分析
- 六、剖析進(jìn)程狀態(tài)遷移
- 七、寫時(shí)復(fù)制技術(shù)
一、Linux 內(nèi)核架構(gòu)圖
二、進(jìn)程基礎(chǔ)知識
Linux 內(nèi)核把進(jìn)程稱為任務(wù)(task),進(jìn)程的虛擬地址空間分為用戶虛擬地址空間和內(nèi)核虛擬地址空間,所有進(jìn)程共享內(nèi)核虛擬地址空間,每個(gè)進(jìn)程有獨(dú)立的用戶虛擬地址空間。
進(jìn)程有兩種特殊形式:
沒有用戶虛擬地址空間的進(jìn)程稱為內(nèi)核線程。
共享用戶虛擬地址空間的進(jìn)程稱為用戶線程。
通用在不會引起混淆的情況下把用戶線程簡稱為線程。共享同一個(gè)用戶虛擬地址空間的所有用戶線程組成一個(gè)線程組。
C 標(biāo)準(zhǔn)庫進(jìn)程術(shù)語和 Linux 內(nèi)核進(jìn)程術(shù)語對應(yīng)關(guān)系如下:
C 標(biāo)準(zhǔn)庫進(jìn)程術(shù)語 | Linux 內(nèi)核進(jìn)程術(shù)語 |
---|---|
包含多個(gè)線程的進(jìn)程 | 線程組 |
只有一個(gè)線程的進(jìn)程 | 進(jìn)程或任務(wù) |
線程 | 共享用戶虛擬地址空間的進(jìn)程 |
三、Linux 進(jìn)程四要素
- 有一段程序供其執(zhí)行。
- 有進(jìn)程專用的系統(tǒng)堆??臻g。
- 在內(nèi)核有 task_struct 數(shù)據(jù)結(jié)構(gòu)。
- 有獨(dú)立的存儲空間,擁有專有的用戶空間。
四、task_struct 數(shù)據(jù)結(jié)構(gòu)主要成員
(include/linux/sched.h)
struct task_struct {//進(jìn)程描述符
#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;//指向進(jìn)程狀態(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;//指向內(nèi)核棧refcount_t usage;/* Per task flags (PF_*), defined further below: */unsigned int flags;unsigned int ptrace;// ......
};
- task_struct:進(jìn)程描述符。
- __state:指向進(jìn)程狀態(tài)。
- *stack:指向內(nèi)核棧。
- pid:指向全局的進(jìn)程號。
- tgid:指向全局的線程組的標(biāo)識符。
- *real_parent:指向真實(shí)的父進(jìn)程
- *parent:指向當(dāng)前的父進(jìn)程。比如一個(gè)進(jìn)程被另外的進(jìn)程使用系統(tǒng)調(diào)用進(jìn)行跟蹤(ptrace),那么此時(shí)的父進(jìn)程就是跟蹤進(jìn)程。
- 進(jìn)程調(diào)度策略的優(yōu)先級:prio、static_prio、normal_prio、rt_priority。
- nr_cpus_allowed:允許進(jìn)程在哪些處理器上執(zhí)行。
- *mm:指向內(nèi)存描述符,內(nèi)核線程此項(xiàng)位NULL。
- *active_mm:指向內(nèi)存描述符,內(nèi)核線程運(yùn)行時(shí)從進(jìn)程借用。
- *fs:文件系統(tǒng)信息。
還有很多成員,這里就不一一列舉。
五、創(chuàng)建新進(jìn)程分析
在 Linux 內(nèi)核中,新進(jìn)程是從一個(gè)已經(jīng)存在的進(jìn)程復(fù)制出來的,內(nèi)核使用靜態(tài)數(shù)據(jù)結(jié)構(gòu)造出 0 號內(nèi)核線程,0 號內(nèi)核線程分叉生成 1 號內(nèi)核線程和 2 號內(nèi)核線程(kthreadd 線程)。1 號內(nèi)核線程完成初始化以后裝載用戶程序,變成 1 號進(jìn)程,其他進(jìn)程都是 1 號進(jìn)程或者它的子孫進(jìn)程分叉生成的;其他內(nèi)核線程是 kthreadd 線程分叉生成的。
Linux 3 個(gè)系統(tǒng)調(diào)用創(chuàng)建新的進(jìn)程:
- fork(分叉):子進(jìn)程是父進(jìn)程的一個(gè)副本,采用寫時(shí)復(fù)制技術(shù)。
- vfork:用于創(chuàng)建子進(jìn)程,之后子進(jìn)程立即調(diào)用 execve 以裝載新程序的情況,為了避免復(fù)制物理頁,父進(jìn)程會睡眠等待子進(jìn)程裝載新程序?,F(xiàn)在 fork 采用了寫時(shí)復(fù)制技術(shù),vfork 失去了速度優(yōu)勢,已經(jīng)被廢棄。
- clone(克隆):可以精確地控制子進(jìn)程和父進(jìn)程共享哪些資源。這個(gè)系統(tǒng)調(diào)用的主要用處是可供 pthread 庫用來創(chuàng)建線程。
clone 是功能最齊全的函數(shù),參數(shù)多、使用復(fù)雜,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 內(nèi)核定義系統(tǒng)調(diào)用的獨(dú)特方式,目前以系統(tǒng)調(diào)用 fork 為例:創(chuàng)建新進(jìn)程的 3 個(gè)系統(tǒng)調(diào)用在文件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 內(nèi)核函數(shù)_do_fork()執(zhí)行流程如下圖所示:
具體核心處理函數(shù)為 copy_process()內(nèi)核源碼如下:
/** 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)建新進(jìn)程的主要工作由此函數(shù)完成, 具體處理流程如下圖所示:
同一個(gè)線程組的所有線程必須屬于相同的用戶命名空間和進(jìn)程號命名空間。
六、剖析進(jìn)程狀態(tài)遷移
進(jìn)程主要有 7 種狀態(tài):
- 就緒狀態(tài)、
- 運(yùn)行狀態(tài)、
- 輕度睡眠、
- 中度睡眠、
- 深度睡眠、
- 僵尸狀態(tài)、
- 死亡狀態(tài)。
它們之間狀態(tài)變遷如下:
就緒:state是TASK_RUNING(沒有嚴(yán)格區(qū)別就緒和運(yùn)行),正在運(yùn)行隊(duì)列中等待調(diào)度器調(diào)度。
運(yùn)行:state是TASK_RUNING,證明調(diào)度器選中,正在CPU上執(zhí)行。
僵尸:state是TASK_DEAD,進(jìn)程退出并且父進(jìn)程關(guān)注子進(jìn)程退出事件。
死亡:state是exit_state。
七、寫時(shí)復(fù)制技術(shù)
寫時(shí)復(fù)制核心思想:只有在不得不復(fù)制數(shù)據(jù)內(nèi)容時(shí)才去復(fù)制數(shù)據(jù)內(nèi)容;降低資源浪費(fèi)。
申請新進(jìn)程的步驟:
- 申請一塊空的PCB(進(jìn)程控制塊)。
- 為 新進(jìn)程分配數(shù)據(jù)資源(這里使用寫時(shí)復(fù)制技術(shù))。
- 初始化PCB。
- 把剛才申請的新進(jìn)程插入到就緒隊(duì)列中。state是task_running,被調(diào)度器調(diào)度,進(jìn)入運(yùn)行狀態(tài)。
應(yīng)用程序(進(jìn)程 1)修改頁面 C 之前:
應(yīng)用程序(進(jìn)程 1)修改頁面 C 之后:
注意:只有可修改的頁面才需要標(biāo)記為寫時(shí)復(fù)制,不能修改的頁面可以由父進(jìn)程和子進(jìn)程共享。