第二章 線程初始化
前面我們曾經談到,在OSKit中資源分配和調度的單位是線程而不是傳統意義上的進程,所以我們有必要花大量的時間對其線程機制做全面的分析。當然,要分析線程部分,首先應該從線程的初始化入手,然後才能涉及到線程間的通信以及線程調度,所以本章將向各位全面系統地介紹OSKit的線程初始化。
為了讓大家能清楚地掌握OSKit的線程部分,在以後的三章內,我將采用理論實際相結合的論述方式,即在每章的開始先論述本章所要討論的線程機制,以期讓大家在概念上對本章有總體上的認識,然後再針對OSKit的源代碼做進一步的闡述,希望能理論實踐雙豐收。
2.1 線程初始化分析
2.1.1線程的創建
在OSKit中,用戶創建一個新線程的唯一方法就是調用創建線程的線程pthread_create。
內核為系統調用pthread_create完成如下操作:
(1) 為新線程在線程表項中分配一個空槽。
(2) 賦給子線程一個唯一的標識號
(3) 制作一個父線程上下文的邏輯副本。由於線程的某些部分,如正文區,可能被幾個線程所共享,所以內核只要增加某個區的引用計數即可,而不用真的將該區拷貝到一個新的內存物理區。
(4) 增加與該線程相關的文件表和索引節點表的引用數。
(5) 向父線程返回子線程的線程號。
系統對一個用戶可以同時運行的線程數有一個限制(這個限制是可以改變的),因而任何用戶不能使用過多的線程表項,不然的話,就會妨礙其他用戶創建新線程,而且,OSKit為了防止死鎖,規定了普通用戶不能占用線程表項中的最後一個線程,這條規定是從UNIX中演變而來的。
2.1.2線程的存儲
OSKit的線程存儲方案與UNIX十分相似,由三個邏輯段組成,它們是正文段、數據段和棧。正文段含有一個線程所執行的指令集合,正文段的地址包括正文地址(用於分支和子程序調用)、數據地址(用於存取全局數據變量)和棧地址(為了存取子程序的數據結構)。
若機器將生成的地址看作物理存儲器中的地址,那麼,兩個線程在它們所生成的地址集合重疊了的情況下,是不可能並發執行的。雖然編譯器可以產生在程序之間不重疊的地址,但這樣的程序對於通用計算機是行不通的,因為一種機器上的存儲容量是有限的,而所有可能被編譯的程序集合是無限的。采取此種方法可以在很大程度上避免數據的丟失。
2.1.3線程的數據結構
以下我列出了OSKit中線程的完整的數據結構,其中大部分是從UNIX中繼承來的,但也有一些是OSKit自己的,例如,為了制作例子程序所提供的CPU資源分配的部分。
為了使各位一目了然,我對此數據結構做了全部注釋,並按照其不同應用領域劃分成了幾個部分,分別加在了下面的注釋之中。
queue_chain_t runq; /* 釋放隊列鏈 */
pcb_t *ppcb; /* 指向PCB的指針 */
oskit_u32_t pstk; /* 指向所分配的堆棧的指針*/
void *(*func)(void *); /* 指向執行函數的指針 */
size_t ssize; /* 所分配的堆棧的大小*/
size_t guardsize; /* 警戒堆棧的大小 */
pthread_t tid; /* 線程號 */
oskit_u32_t flags; /* 線程狀態標志 */
pthread_lock_t lock; /* 鎖 */
queue_chain_t chain; /* 隊列鏈 */
int preempt; /* 線程優先級 */
/*以下用於對死鎖的處理*/
pthread_mutex_t mutex; /* 互斥位(死鎖保護) */
pthread_cond_t cond; /* 等待死鎖標志=1 */
int dead; /* 死鎖了 */
/*例子所用到的資源*/
int cputime; /* 占用CPU的時間 */
int cpticks; /* 上一秒所占的節拍數 */
oskit_u32_t pctcpu; /* CPU的占用率 */
int childtime; /* 子線程所占用的CPU的開銷 */
/*發送信息所用的變量*/
pthread_lock_t waitlock; /* 等待鎖*/
oskit_u32_t waitflags; /* 等待標志位 */
pthread_cond_t *waitcond; /* 等待條件變量 */
struct osenv_sleeprec *sleeprec; /*一個osenv_sleep中的線程 */
/*以下是進程通訊要用到的*/
void *msg; /* 指向要發送的消息的指針 */
oskit_size_t msg_size; /* 消息的大小 */
pthread_t tid; /* 線程號 */
void *reply; /* 指向響應信息的指針 */
oskit_size_t reply_size; /* 響應內容的大小 */
queue_head_t senders; /* 發送隊列中的發送者 */
queue_chain_t senders_chain; /* 發送隊列指針 */
/* 以下是所用到的時鐘 */
struct oskit_timer *condtimer;
/* 線程的休眠和喚醒是用一個單獨的計時器實現的 */
struct oskit_timer *sleeptimer;
/* 以下是所用到的信號 */
pthread_lock_t siglock; /* 保護鎖信號 */
sigset_t sigmask; /* 阻塞信號 */
sigset_t sigpending; /* 未決信號 */
sigset_t sigwaiting; /* 等待信號的信號 */
oskit_u32_t eip; /* 頁處理故障信號 */
/* 鍵值通常是一個固定的隊列 */
void *keyvalues[PTHREAD_KEYS_MAX]; /* 鍵值 */
/* 以下用於清除操作 */
pthread_cleanup_t *cleanups; /* 清楚操作鏈 */
char cancelstate; /* 取消狀態 */
char canceltype; /* 取消類型 */
/* 以下是單獨用於調度的鎖 */
pthread_lock_t schedlock;
void *rtai_priv;
int policy; /* 調度策略 */
int priority; /* 當前的優先級 */
struct scheduler_entry *scheduler; /* 調度程序入口 */
int policy; /* 調度策略 */
int priority; /* 當前優先級 */
int base_priority; /* 初始優先級 */
int ticks; /* 調度所省下的節拍 */
oskit_timespec_t start; /* 下次運行的時間 */
oskit_timespec_t deadline; /* 下次運行的時間 */
oskit_timespec_t period; /* 兩次執行見的間隔 */
queue_head_t waiters; /* 正在等待的線程 */
queue_chain_t waiters_chain; /* 線程隊列鏈 */
struct pthread_thread *waiting_for; /* 所等待的線程 */
struct pthread_thread *inherits_from; /* 線程從何繼承 */
/*以下是CPU的繼承*/
struct pthread_thread *scheduler; /* 線程調度程序人口 */
schedmsg_t unblockmsg;
schedmsg_queue_t *msgqueue;
sched_wakecond_t wakeup_cond; /* 喚醒條件 */
schedflags_t schedflags; /* 標志 */
int donate_rc; /* 從捐贈線程返回的值 */
int timeout; /* 毫秒 */
queue_head_t donors; /* 捐贈資源的線程 */
queue_chain_t donors_chain; /* 捐贈隊列鏈 */
struct pthread_thread *donating_to; /* 被捐贈的線程 */
struct pthread_thread *inherits_from; /* 線程從何繼承 */
struct pthread_thread *nextup; /* 下一個要執行的線程 */
2.2 pthreads/pthread_create.c
此源碼文件包括了一套完整的線程創建機制,它是全部線程的根源所在,通過閱讀下面的函數分析,將使讀者了解OSKit到底是用什麼函數來具體實現線程創建的。我認為在上一節的理論指導下對代碼進行分析,要比泛泛的闡述理論更容易讓讀者接受,更能加深在讀者腦海中的印象。
2.2.1 創建線程
說明:這就是我們在第一節中提到的創建線程的函數,所有由用戶完成的創建線程的操作,都要調用它來實現。
int pthread_create ( pthread_t *tid, const pthread_attr_t *attr,
void * ( *function )( void * ), void *argument )
tid: 指向線程存儲位置的指針
attr: 指向線程屬性的指針
*(*function)(void *): 當線程初始化時所調用的函數
*argument: 函數功能
2.2.2 創建內部線程
說明:該函數用於系統核心部分創建核心線程,而一般的用戶沒有調用它的權利,只有系統核心才可以調用。
pthread_thread_t *pthread_create_internal ( void * ( *function)( void *), void *argument,const pthread_attr_t *attr )
注:內部線程創建時,將阻塞所有的信號
sigfillset(&pthread->sigmask)
2.2.3 為主進程創建一個備份線程
說明:出於系統安全的原因,OSKit定義了此函數,用於對主線程進行備份。由於線程創建子線程,子線程又創建自己的子線程,如此下去,將產生一個以主線程為根節點的樹形結構,所以一旦主線程丟失,將有可能導致系統崩潰,所以應對其進行備份。
pthread_thread_t *pthread_init_mainthread ( pthread_thread_t *pthread )
2.2.4 初始化創建線程的線程
說明:這和UNIX中那個創建進程的進程有些類似,它唯一的工作就是為主線程創建子線程,並且,每個線程都包含一個該線程。
pthread_thread_t *thread_init_mainthread ( pthread_thread_t *pthread )
2.2.5 為等待和休眠的線程創建一個等待時間
說明:由於調度的原因,一般線程不可能一直占用CPU,也不可能永遠被掛起,所以OSKit為規定線程的等待時間定義了此函數。
void pthread_prepare_timer ( pthread_thread_t *pthread )
2.3 pthreads/pthread_attr.c
此源碼文件包括了一套完整的線程屬性的初始化機制,它與上一節的線程創建共同作用,規定了線程在創建之初的屬性,通過閱讀下面的函數分析,將使讀者了解OSKit到底是用什麼函數來具體實現線程屬性的初始化。
2.3.1線程初始化的數據結構
說明:系統在創建新線程的時候,會調用此函數來規定被創建線程的屬性,所以此函數在線程屬性機制中站有最主要的地位,是一切線程屬性函數調用的源泉,希望讀者給以充分的重視。
int pthread_attr_init ( pthread_attr_t *attr )
attr->detachstate = PTHREAD_CREATE_JOINABLE; /* 線程與其他線程關聯*/
attr->priority = PRIORITY_NORMAL; /* 線程的優先級 */
attr->stacksize = PTHREAD_STACK_MIN; /* 線程所占堆棧的 */
attr->guardsize = DEFAULT_STACKGUARD; /* 警戒堆棧的大小 */
attr->stackaddr = 0; /* 堆棧的大小 */
attr->policy = SCHED_RR; /* 線程調度類型 */
attr->inherit = PTHREAD_EXPLICIT_SCHED; /* 線程繼承類型 */
注: memset 的數據結構 /* \oskit\libc\string\memset.c */
void *memset(void *tov, int c, size_t len)
{ register char *to = tov;
while (len-- > 0)
*to++ = c;
return tov;
}
tov: 指向內存的首地址 c: 分配類型 len: 分配長度
2.3.2線程屬性的撤銷
說明:這個函數是上一個函數的逆操作,在撤銷一個線程的同時,其屬性也應相應地被撤銷,所以OSKit定義了此函數來實現該功能。
int pthread_attr_destroy ( pthread_attr_t *attr )
memset((void *) attr, -1, sizeof(*attr)); /* 撤銷指針並釋放內存*/
2.3.3設置線程的警戒堆棧大小
說明:警戒堆棧在操作系統中被廣泛使用,它是源於UNIX的進程間通信,在UNIX系統中,接受進程的消息緩沖可能小於發送進程。所以,當消息到來的時候,為了避免溢出造成的信息丟失,系統創建了警戒堆棧,用來存儲溢出的消息。而OSKit把它用於線程間通信的保護,但用法與UNIX大同小異。
int pthread_attr_setguardsize ( pthread_attr_t *attr, size_t guardsize )
2.3.4得到警戒堆棧的大小
說明:由於OSKit為每個接受線程設置警戒堆棧,所以定義此函數,為的是系統能方便的得到每個接受線程的警戒堆棧的大小,這同樣是出於對安全的考慮。
int pthread_attr_getguardsize ( const pthread_attr_t *attr, size_t *guardsize )
2.3.5設置線程的分離狀態
說明:上邊曾經提到,系統中的線程樹,而線程與線程之間很可能並不是完全獨立的,即使他們處於不同的層次,也可能有一定的相關性或互斥性,所以定義此函數用來規定線程之間的互斥性,即分離狀態是完全有必要的。
int pthread_attr_setdetachstate( pthread_attr_t *attr, int detachstate )
2.3.6 得到線程的分離狀態
說明:在規定了分離狀態之後,若A線程要與B線程發生關聯的時候,它必須先看看B線程和其他線程的關系,所以要定義此函數來得到B線程與其他線程的分離狀態,提供給A,然後A在根據具體情況決定是否與B發生關系。
int pthread_attr_getdetachstate ( const pthread_attr_t *attr, int *detachstate )
*detachstate = attr->detachstate;
2.3.7 設置線程的繼承關系
說明:當線程創建子線程的時候,上邊我提到的那個創建線程的線程會為他們父子之間規定繼承關系,即子線程繼承父線程。
int pthread_attr_setinheritsched ( pthread_attr_t *attr, int inheritstate )
attr->inherit = inheritstate;
2.3.8 得到線程的繼承狀態
說明:當系統要在復雜的線程樹中得到繼承關系的時,調用此函數便能達到預期的目的。OSKit定義此函數,為的是能在復雜的線程樹中一眼看穿某兩個線程之間的關系。
int pthread_attr_getinheritsched ( const pthread_attr_t *attr, int *inheritstate )
*inheritstate = attr->inherit;
2.3.9 設置線程的調度參數
說明:OSKit提供了多種線程調度方式供選擇,而且還為每個線程在創建的時候規定了一種調度算法,系統在創建線程的時候調用下面的函數來完成這項工作。
int pthread_attr_setschedparam ( pthread_attr_t *attr,const struct sched_param *param )
int pri = param->priority;
attr->start = param->start; /* 線程開始參數 */
attr->period = param->period; /* 線程的周期 */
attr->deadline = param->deadline; /* 線程運行的最終時限 */
2.3.10 得到線程調度的參數
說明:在OSKit中,每個線程都有其自身的調度方式,換句話說,就是線程的調度方式仿佛是線程的一個屬性,在創建線程的時候就隨線程被規定了,所以調度程序可以通過下面的函數來得到某個線程的調度算法和此時它的優先級。
int pthread_attr_getschedparam ( pthread_attr_t *attr, struct sched_param *param )
param->priority = attr->priority;
2.3.11 設置線程調度策略
說明:在某些方面,線程的調度策略的確很像線程的屬性,但它卻是可以在調度中被更改的,系統可以調用此函數完成對線程調度策略的重定義。這樣的規定,使得線程調度靈活了許多,當然這種靈活是用增加系統開銷的代價換回來的,但卻是值得的。
int pthread_attr_setschedpolicy ( pthread_attr_t *attr, int policy )
switch (policy) {
case SCHED_FIFO: /* 先到先處理 */
case SCHED_RR: /* 時間片輪轉 */
case SCHED_EDF /* 先死先處理 */
case SCHED_RMS: /* 效率優先 */
attr->policy = policy;
break;
case SCHED_DECAY:
printf("pthread_attr_setschedpolicy: "
"SCHED_DECAY not supported yet\n");
}
2.3.12 得到線程調度策略
說明:負責調度的線程通過該函數得到被調度線程的調度策略,然後再根據此時系統內的情況,決定是否改變其調度算法。
int pthread_attr_getschedpolicy ( const pthread_attr_t *attr, int *policy )
*policy = attr->policy;
2.3.13 設置堆棧地址
說明:調用此函數,可以實現對線程屬性堆棧的底層操作,即設置指向該堆棧的指針,並保存起來,為以後的操作奠定基礎。
int pthread_attr_setstackaddr ( pthread_attr_t *attr, oskit_u32_t stackaddr )
attr->stackaddr = stackaddr;
2.3.14 得到堆棧的地址
說明:返回指向線程屬性堆棧的指針。
int pthread_attr_getstackaddr(const pthread_attr_t *attr, oskit_u32_t *stackaddr)
*stackaddr = attr->stackaddr;
2.3.15 設置堆棧的大小
說明:由於系統中的線程功能、大小各不相同,所以其屬性堆棧的大小也不盡相同,調用此函數可以設置線程屬性堆棧大小。
int pthread_attr_setstacksize ( pthread_attr_t *attr, size_t stacksize )
attr->stacksize = stacksize;
2.3.16 得到堆棧的大小
說明:返回某個線程堆棧的大小。
int pthread_attr_getstacksize ( const pthread_attr_t *attr, size_t *stacksize )
*stacksize = attr->stacksize;
2.3.17設置線程的優先級
說明:在操作系統的線程調度中,優先級調度的使用最為廣泛,OSKit定義了下面的函數來規定線程的優先級。
int oskit_pthread_attr_setprio ( pthread_attr_t *attr, int pri )
attr->priority = pri;
2.3.18 准備接受調度
說明:由於多態性原因,不是所有的線程都要參與調度,只是某些線程參加,所以,系統必須調用此函數,告訴該線程准備參加調度,而被調度的方式在上邊的幾個函數裡已經規定好了。
int pthread_attr_setopaque ( pthread_attr_t *attr, oskit_u32_t opaque )
attr->opaque = opaque;
2.3.19 聲明要進行調度的線程
說明:調用此函數是為了告訴系統,哪些線程是要被調度的。
int pthread_attr_setscheduler ( pthread_attr_t *attr, pthread_t tid )
attr->scheduler = tid;
2.3.20 設置mutex的屬性
說明:在OSKit中,廣泛使用了mutex,主要是為了更好的使用臨界資源,有效地避免了死鎖,換句話說,mutex就像臨界資源的數量一樣,線程對其進行PV操作。而當一個線程占有了一個mutex,並且在沒釋放它之前該線程又創建了子線程,此時,子線程理所當然地從父線程手中繼承了mutex的所有權,OSKit定義了下面的函數來實現所敘述的功能。
int pthread_mutexattr_init ( pthread_mutexattr_t *attr )
attr->priority_inherit = PTHREAD_PRIO_NONE; /* THREAD_PRIO_NONE=0 */
2.3.21撤銷mutex的屬性
說明:當占有mutex的線程放棄了它的時候,調用該函數來撤銷mutex的屬性。
int pthread_mutexattr_destroy ( pthread_mutexattr_t *attr )
memset((void *) attr, -1, sizeof(*attr));
2.3.22 設置muxtex的協議
說明:mutex的使用並不是簡單的占用與撤銷,OSKit還提供了更加完善的mutex協議,它根據inherit的不同,將mutex多樣化。
int thread_mutexattr_setprotocol ( pthread_mutexattr_t *attr, int inherit )
switch (inherit) {
case PTHREAD_PRIO_NONE:
case PTHREAD_PRIO_INHERIT:
attr->priority_inherit = inherit;
break;
default:
return EINVAL;
}
注:在線程調度時,根據mutex協議產生mutex變量,將線程按照優先級排隊
2.3.23 得到線程調度的mutex協議
說明:好像對協議的解析一樣,調用此函數,可以返回該線程的mutex。
int pthread_mutexattr_getprotocol ( const pthread_mutexattr_t *attr, int *inherit)
*inherit = attr->priority_inherit;
2.3.24 設置mutex的種類
說明:mutex在OSKit中是可以多種多樣的,通過調用下面的函數,可以規定線程到底占用了哪種mutex。
int pthread_mutexattr_settype ( pthread_mutexattr_t *attr, int type )
switch (type) {
case PTHREAD_MUTEX_NORMAL: /* 普通 */
case PTHREAD_MUTEX_ERRORCHECK: /* 錯誤檢查 */
case PTHREAD_MUTEX_RECURSIVE: /* 遞歸 */
case PTHREAD_MUTEX_DEFAULT: /* 默認 */
attr->type = type;
break;
default:
return EINVAL;
}
2.3.25得到mutex的類型
說明:當系統想要知道某個線程占用的是什麼類型的mutex時,調用此函數來返回mutex的類型。
int pthread_mutexattr_gettype ( const pthread_mutexattr_t *attr, int *type )
*type = attr->type;
本 章 小 結
本章從線程的創建、存儲和數據結構到各種屬性以及對它們的操作(設置、撤銷和得到)入手,系統地闡述了OSKit線程機制的根源--線程初始化。
在第一節主要從三個角度歸納了OSKit中線程所涉及的概念,它們是創建、存儲以及線程的數據結構。第二節中以源代碼中的 /pthreads/pthread_creat.c 為例,說明了第一節中概念的具體實現。第三節以源代碼中的 /pthreads/pthread_attr.c 為例,說明了Oskit對線程屬性的全部操作,包括線程初始化的數據結構、屬性的撤銷、線程間關系、對mutex的操作以及對屬性堆棧的操作等。