主要內容包括:html
一、進程描述符中Realtime Mutex相關數據結構的初始化linux
二、子進程如何複製父進程的credentials數據結構
三、per-task delay accounting的處理app
四、子進程如何複製父進程的flagdom
7、初始化Realtime Mutex相關的成員socket
static void rt_mutex_init_task(struct task_struct *p)
{
raw_spin_lock_init(&p->pi_lock);
#ifdef CONFIG_RT_MUTEXES
p->pi_waiters = RB_ROOT;
p->pi_waiters_leftmost = NULL;
p->pi_blocked_on = NULL;
p->pi_top_task = NULL;
#endif
} async
Mutex是一種人民羣衆喜聞樂見的內核同步方式,不過real time mutex是什麼呢?real time mutex是被設計用來支PI-futexes的。什麼是PI?什麼又是futex?PI是優先級繼承(Priority Inheritance),該方法是用來解決優先級翻轉問題的。什麼是優先級翻轉(Priority Inversion)?它是一種調度延遲現象。通常而言,調度器老是優先調度到優先級高的進程(線程),可是,當同步資源被較低優先級的進程所擁有(也就是說持有鎖),高優先級的進程未能獲取該同步資源,這時候,高優先級進程要等到持有鎖的進程釋放該資源後才能被調度到。下面的圖片更加詳細的描述了該問題:ide
低優先級進程和高優先級進程都須要訪問一個公共資源,所以須要一個mutex來保護對該公共資源的訪問。函數
T0時刻,只有低優先級進程處於可運行狀態,運行過程當中,在T1時刻,低優先級進程訪問公共資源,而且持有了mutex lock,T2時刻,因爲外部事件,致使中優先級進程進入可運行狀態,中優先級進程就緒進入可運行狀態,因爲優先級高於正在運行的低優先級進程,低優先級進程被搶佔(沒有unlock mutex),中優先級進程被調度執行。一樣地T3時刻,因爲外部事件,高優先級進程搶佔中優先級進程。高優先級進程運行到T4時刻,須要訪問公共資源,但該資源被更低優先級的進程所擁有,高優先級進程只能被掛起等待該資源,而此時處於可運行狀態的線程中,中優先級進程因爲優先級高而被調度執行。性能
上述現象中,優先級高的進程要等待優先級低的進程完以後才能被調度,更加嚴重的是若是中優先級進程執行很費時的操做,顯然高優先級進程的被調度時機就不能保證,整個實時調度的性能就不好了。
爲了解決上述因爲優先級翻轉引發的問題,不少操做系統引入了優先級繼承(Priority Inheritance)的解決方法。優先級繼承的方法是這樣的,當高優先級進程在等待低優先級的進程程佔用的競爭資源時,爲了使低優先級的進程可以儘快得到調度運行(以便釋放高優先級進程須要的競爭資源),由操做系統kernel把低優先級進程的優先級提升到等待競爭資源高優先級進程的優先級。
OK,瞭解完這些內容以後,咱們回到了futex。linux內核提供了一個叫作快速用戶空間互斥(Fast User-Space Mutexes)的鎖的機制,簡稱futex,經過這樣的機制用戶空間程序能夠實現對互斥資源的快速訪問。爲何提供futex這樣的機制?如何使用?在用戶空間如何互斥?爲什麼可以快速?問題太多了,下次我會啓動一個專題來描述futex。
8、process credentials
retval = -EAGAIN;
if (atomic_read(&p->real_cred->user->processes) >=
task_rlimit(p, RLIMIT_NPROC)) {
if (p->real_cred->user != INIT_USER &&
!capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN))
goto bad_fork_free;
}
current->flags &= ~PF_NPROC_EXCEEDED;retval = copy_creds(p, clone_flags);
if (retval < 0)
goto bad_fork_free;
上面的這段代碼主要是處理如何複製新建立進程的credentials。在task struct數據結構中,下面的數據結構描述了進程這個對象的credentials:
const struct cred __rcu *real_cred; /* objective and real subjective task
* credentials (COW) */
const struct cred __rcu *cred; /* effective (overridable) subjective task
* credentials (COW) */
雖然本網站的process credentials描述了一些進程credentials的相關知識,但那是基於2.6.29版本以前的描述。隨着內核的演進,從2.6.29版本開始,內核對於credential有了進一步的抽象。首先,credential再也不是進程特有的,而是內核對象(進程、file、socket等)的一個屬性集合。而這些內核對象的屬性被分紅兩類:一類是該對象做爲動做的發起者,操做其餘內核對象時候須要用的credential屬性,被稱爲subjective context。另一類是本內核對象被其餘內核對象操做時候須要用到的credential屬性,被稱爲objective context。舉一個簡單的例子:對於內核中的進程對象,可能存在下面的動做:
一、該進程訪問文件系統中的文件
二、在進行文件的asynchronous I/O操做的時候,文件會向進程發送信號。
對於進程這個內核對象而言,在上面的場景1中,要使用進程的subjective context,而在場景2中,使用進程的objective context。內核中,struct cred就是對credential的抽象,進程描述符(struct task_struct)、文件描述符(struct file )這些內核對象都須要定義一個struct cred的成員來表明該對象的credential信息。OK,瞭解了上述信息以後,咱們能夠繼續講述task struct數據結構中的credentials成員。在進程對象在操做其餘內核對象時候使用cred成員,而在其餘對象操做該進程對象的時候,須要獲取該進程的credential的時候,須要使用real_cred成員。
在copy process credential以前首先進行資源檢查
一、該用戶是否已經建立了太多的進程,若是沒有超出了resource limit,那麼繼續fork
二、若是超出了resource limit,但user ID是root的話,沒有問題,繼續
三、若是不是root,可是有CAP_SYS_RESOURCE或者CAP_SYS_ADMIN的capbility,也OK,不然的話fork失敗
檢查完成以後就正式開始copy credential了(注:略去CONFIG_KEYS的代碼,內核中支持密鑰相關的操做值得用一篇單獨的文檔來描述,此外,爲了下降文檔長度,刪除了debug相關的代碼):
int copy_creds(struct task_struct *p, unsigned long clone_flags)
{
struct cred *new;
int ret;if ( clone_flags & CLONE_THREAD ) { //建立線程相關的代碼
p->real_cred = get_cred(p->cred);
get_cred(p->cred); //對credential描述符增長兩個計數,由於新建立線程的cred和real_cred都指向父進程的credential描述符
atomic_inc(&p->cred->user->processes); //屬於該user的進程/線程數目增長1
return 0;
}new = prepare_creds(); //後段的代碼是和fork進程相關。prepare_creds是建立一個當前task的subjective context(task->cred)的副本。
if (!new)
return -ENOMEM;if (clone_flags & CLONE_NEWUSER) {//若是父子進程不共享user namespace,那麼還須要建立一個新的user namespace
ret = create_user_ns(new);
if (ret < 0)
goto error_put;
}atomic_inc(&new->user->processes);
p->cred = p->real_cred = get_cred(new); //和線程的處理相似,只不過進程須要建立一個credential的副本
return 0;error_put:
put_cred(new);
return ret;
}
對於建立線程,所謂copy,也就是共享,在以前的task struct進行copy的時候,credential相關的兩個指針都已是指向一樣的struct cred數據結構,完成了共享的操做(被建立進程/線程的real_cred指向其父進程的real_cred,被建立進程/線程的cred指向其父進程的cred),這些須要作一些修正:
一、被建立進程/線程的real_cred指向其父進程的cred。具體緣由TODO
二、修正credential描述符的reference count
對於建立進程,內核會分配一個新的cred描述符,copy 父進程的credentials,也就是說,不是共享cred描述符,而的確是copy的動做了。若是本次fork也攜帶了CLONE_NEWUSER參數,打算建立一個新的user namespace,那麼父子進程的username space也須要獨立開來,
9、進程建立總數限制
retval = -EAGAIN;
if (nr_threads >= max_threads)
goto bad_fork_cleanup_count;
nr_threads是系統當前的線程數目;max_threads是系統容許容納的最大的線程數。因爲資源(CPU、memory)受限,系統不可能無限制的建立線程,不然,系統的memory可能會被進程的內核棧消耗掉。
在系統初始化的時候(fork_init),會根據當前系統中的memory對該值進行設定。
max_threads = mempages / (8 * THREAD_SIZE / PAGE_SIZE);
if (max_threads < 20) // 最小也會被設定爲20
max_threads = 20;
max_threads能夠由用戶進行設定。在/proc/sys目錄下保存着若干的內核參數,該目錄下的kernel/threads-max文件就是對系統內的能夠建立的最大線程數的限制。
10、模塊處理
if (!try_module_get(task_thread_info(p)->exec_domain->module))
goto bad_fork_cleanup_count;
struct thread_info數據結構中的exec_domain成員指向了當前進程/線程的execution domain descriptor。linux kernel有一個很好的設計就是容許在其餘操做系統上編譯的程序在GNU/linux上執行。對於DOS或者Windows這樣的操做系統,GNU/linux支持起來有些困難,多半是經過用戶空間的仿真來作。可是對於POSIX兼容的操做系統,因爲接口API是相同的,GNU/linux應該不會花費太多的力氣,只須要處理系統調用的具體細節問題或者各類信號的編碼問題。這些信息在kernel中用struct exec_domain抽象。
既然是共享了父進程的exec_domain,那麼須要經過try_module_get去增長reference count(具體的copy在copy thread info的時候已經完成了)。
11、per-task delay accounting的處理
delayacct_tsk_init(p);
delayacct是一個縮寫,是指per-task delay accounting。這個feature是統計每個task的等待系統資源的時間(例如等待CPU、memeory或者IO)。這些統計信息有助於精準的肯定task訪問系統資源的優先級。
一個進程/線程可能會由於下面的緣由而delay:
一、該進程/線程處於runnable,可是等待調度器調度執行
二、該進程/線程發起synchronous block I/O,進程/線程處於阻塞狀態,直到I/O的完成
三、進程/線程在執行過程當中等待page swapping in。因爲內存有限,系統不可能把進程的各個段(正文段、數據段等)都保存在物理內存中,當訪問那些沒有在物理內存的段的地址的時候,就會有磁盤操做,致使進程delay,這裏有個專業的術語叫作capacity misses
四、進程/線程申請內存,可是因爲資源受限而致使page frame reclaim的動做。
系統爲什麼對這些delay信息進行統計呢?主要讓系統肯定下列的優先級的時候更有針對性:
一、task priority。若是該進程/線程長時間的等待CPU,那麼調度器能夠調高該任務的優先級。
二、IO priority。若是該進程/線程長時間的等待I/O,那麼I/O調度器能夠調高該任務的I/O優先級。
三、rss limit value。引入虛擬內存後,每一個進程都擁有4G的地址空間。系統中有那麼多進程,而物理內存就那麼多,不可能讓每個進程虛擬地址(page)都對應一個物理地址(page frame)。沒有對應物理地址的那部分虛擬地址的實際內容應該保存在文件或者swap設備上,一旦要訪問該地址,系統會產生異常,並處理好分配page frame,頁表映射,copy磁盤內容到page frame等一系列動做。rss的全稱是resident set size,表示有物理內存對應的虛擬地址空間。因爲物理內存資源有限,各個進程要合理使用。rss limit value定義了各個進程rss的上限。
struct task_delay_info *delays;
進程描述符中的delays成員記錄了該task的delay統計信息,delayacct_tsk_init就是對該數據結構進程初始化。本文先簡單描述概念,後續會有專門的文件來描述進程的統計信息。
12、複製進程描述符的flag
static void copy_flags(unsigned long clone_flags, struct task_struct *p)
{
unsigned long new_flags = p->flags;new_flags &= ~(PF_SUPERPRIV | PF_WQ_WORKER);
new_flags |= PF_FORKNOEXEC;
p->flags = new_flags;
}
copy_flags函數用來copy進程的flag,大部分的flag都是直接copy,可是下面的幾個是例外:
一、PF_SUPERPRIV,這個flag是標識進程曾經使用了super-user privileges(並不表示該進程有超級用戶的權限)。對於新建立的進程,固然不會用到super-user privileges,所以要清掉。
二、清除PF_WQ_WORKER標識。PF_WQ_WORKER是用來標識該task是一個workqueue worker。若是新建立的內核線程的確是一個workqueue worker的話,那麼在其worker thread function(worker_thread)中會進行設定的。具體worker、workqueue等概念請參考Concurrency-managed workqueues相關的描述。
三、設定PF_FORKNOEXEC標識,代表本進程/線程正在fork,尚未執行exec的動做。