三、進程-創(chuàng)新互聯(lián)

進程、輕量級進程和線程三、進程

進程類似于人類:他們被產(chǎn)生,有或多或少有效的生命,可以產(chǎn)生一個或多個子進程,最終都要死亡。一個微小的差異是進程之間沒有性別差異——每個進程只有一個父親。

我們提供的服務有:網(wǎng)站制作、網(wǎng)站建設、微信公眾號開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認證、營口ssl等。為上千多家企事業(yè)單位解決了網(wǎng)站和推廣的問題。提供周到的售前咨詢和貼心的售后服務,是有科學管理、有技術(shù)的營口網(wǎng)站制作公司

從內(nèi)核的觀點來看:進程的目的就是擔當分配系統(tǒng)資源的實體(CPU時間、內(nèi)存等資源)。

實現(xiàn)多線程應用的一個簡單的方式是把輕量級進程與每個線程關(guān)聯(lián)起來。這樣線程之間就可以通過簡單的共享同一內(nèi)存地址空間、同一打開文件集等來訪問相同的應用數(shù)據(jù)結(jié)構(gòu)集;同時每個線程都可以由內(nèi)核獨立調(diào)度,以便一個睡眠的同時另一個仍然是可運行的。(共享內(nèi)存地址、內(nèi)核獨立調(diào)度)。

進程描述符

進程描述符都是task_struct類型結(jié)構(gòu),它的字段包含了一個進程相關(guān)的所有信息。

進程狀態(tài)

進程描述符的state字段描述了進程當前所處的狀態(tài)。它由一組標志組成,其中每個標志描述一種可能的進程狀態(tài)。在當前l(fā)inux版本中,這些狀態(tài)是互斥的,只能設置一種狀態(tài);其余的標志將被清除。

1、可運行TASK_RUNNING 進程在CPU上要么正在執(zhí)行,要么準備執(zhí)行

2、可中斷TASK_INTERUPTIBLE 進程被掛起,直到達到某個條件。產(chǎn)生一個硬件中斷,釋放進程正等待的系統(tǒng)資源,或傳遞一個信號都是可以喚醒進程的條件。

3、不可中斷 TASK_UNINTERUPTIBLE 與可中斷狀態(tài)類似,但是把信號傳遞到不可中斷狀態(tài)進程時不能改變其狀態(tài)。例如,當進程打開一個設備文件,其相應的設備驅(qū)動程序開始探測相應的硬件設備時會用到這種狀態(tài)。探測完成之前,設備驅(qū)動程序不能被中斷,否則,硬件設備會處于不可預知的狀態(tài)。

4、暫停狀態(tài)TASK_STOPPED 進程的執(zhí)行被暫停。當進程受到SIGSTOP、SIGTSTP、SIGTTIN或SIGTTOU信號后進入暫停狀態(tài)

5、TASK_TRACED跟蹤狀態(tài) 進程的執(zhí)行已經(jīng)有debugger程序暫停。

以下兩個進程狀態(tài)既可以存放到state字段又可以存放到exit_state字段

6、僵死狀態(tài)(EXIT_ZOMBLE) 進程的執(zhí)行被終止,但是父進程還沒有發(fā)布wait4()或waitpid()系統(tǒng)調(diào)用來返回有關(guān)死亡進程的信息。

7、僵死撤銷狀態(tài)(EXIT_DEAD) 最終狀態(tài):由于父進程剛發(fā)出wait4()系統(tǒng)調(diào)用,因而進程由系統(tǒng)刪除。為了防止其他執(zhí)行線程在同一個進程上也執(zhí)行wait()類系統(tǒng)調(diào)用而產(chǎn)生競爭條件,而把僵死狀態(tài)改為撤銷狀態(tài)。

標識一個進程

由于循環(huán)使用PID編號,內(nèi)核必須通過管理一個pidmap_array的位圖來標識當前已經(jīng)分配的pid號和閑置的pid號。因為一個頁框包含4*1024*8個位,所以32位體系結(jié)構(gòu)中pidmap_array位圖存放在一個單獨的頁中。一個多線程應用中的所有線程都必須有相同的pid。Linux引入線程組:一個線程組中的所有線程使用和該線程組領(lǐng)頭線程相同的pid。它被存入進程描述符的tgid字段中。getpid()系統(tǒng)調(diào)用返回當前進程的tgid值而不是pid值。

進程描述符處理

對每個進程來說,Linux都把兩個不同的數(shù)據(jù)結(jié)構(gòu)緊湊的放在一個單獨為進程分配的存儲區(qū)域中:一個是與進程描述符相關(guān)的小數(shù)據(jù)結(jié)構(gòu)thread_Info,一個是內(nèi)核態(tài)的進程堆棧。這塊存儲區(qū)域的大小一般為2頁。考慮到效率的因素,內(nèi)核讓這個8k空間占連續(xù)兩個頁框并讓第一個頁框的起始地址是2^13的倍數(shù)。

因為內(nèi)核控制路徑使用很少的棧,因此只需要幾千個字節(jié)的內(nèi)核態(tài)堆棧。對已,對棧和thread_info來說,8k足夠了。

thread_info存放在這個8k內(nèi)存區(qū)域的開始,而棧從內(nèi)存區(qū)域的末端向下增長。

esp寄存器時CPU棧指針,用來存放棧頂單元的地址。在80x86系統(tǒng)中,棧起始于末端,并朝這個內(nèi)存區(qū)域開始的方向增長。從用戶態(tài)剛切換到內(nèi)核態(tài)后,進程的內(nèi)核??偸强盏?,因此,esp寄存器這是指向這個棧的頂端。一旦數(shù)據(jù)寫入棧,esp的值就遞減。因為thread_Info的結(jié)構(gòu)式52個字節(jié)長,因此內(nèi)核棧能擴展到8140個字節(jié)。

標識當前進程

如果thread_union結(jié)構(gòu)長度是8k,則內(nèi)核屏蔽掉esp的低13位就可以獲得thread_info的基地址;如果thread_union結(jié)構(gòu)的長度是4k,內(nèi)核需要屏蔽掉esp的低12位。這項工作由current_thread_info()函數(shù)來完成,它產(chǎn)生如下匯編指令:

movl $-8192, %ecx //將19個1+00000000000000(低13位為0)的值保存到ecx寄存器

andl %esp, %ecx //取出ecx中的值與esp中當前棧頂單元的地址進行位與運算,即屏蔽esp的后13位。

movl %ecx ,p //將計算的結(jié)構(gòu)賦值給p

因為task字段在thread_info結(jié)構(gòu)中的偏移量為0,所以執(zhí)行完以上指令后,p就包含在cpu上運行進程的進程描述符指針。

雙向鏈表

linux定義了list_head數(shù)據(jù)結(jié)構(gòu),字段next和prev分別表示通用雙向鏈表向前和向后的指針元素。list_head字段的指針中存放的是另一個list_head字段的地址,而不是含有l(wèi)ist_head結(jié)構(gòu)的整個數(shù)據(jù)結(jié)構(gòu)的地址。

雙向鏈表處理函數(shù)和宏

list_add(n,p)

list_add_tail(n,p)把n指向的元素插在p指向的元素之前。

list_del(p)

list_empty(p) 檢查由第一個元素地址p指向的鏈表是否為空

list_entry(p,t,m) 返回類型為t的數(shù)據(jù)結(jié)構(gòu)地址,其中類型t含有l(wèi)ist_head字段,而list_head字段中含有名字m和地址p

list_for_each(p,h) 對表頭地址h指定的鏈表進行掃描,每次循環(huán)時,通過p返回指向鏈表元素的list_head結(jié)構(gòu)的指針

list_for_each_entry(p,h,m) 與list_for_each類似,但是返回了包含list_head結(jié)構(gòu)的數(shù)據(jù)結(jié)構(gòu)的地址,而不是list_head結(jié)構(gòu)本身的地址

進程鏈表

進程鏈表的頭是init_task描述符,init_task的tasks.prev字段指向鏈表中最后插入的額進程描述符的tasks字段。

SET_LINKS和REMOVE_LINKS宏分別用于從進程鏈表中插入和刪除一個進程描述符。

宏for_each_process掃描整個進程鏈表定義如下:

#define for_each_process(p)

for(p=&init_task;(p=list_entry((p)->tasks.next, struct task_struct,tasks))!=&init_task; )

TASK_RUNNING狀態(tài)的進程鏈表

提高調(diào)度程序運行速度的訣竅是建立多個可運行進程鏈表,每種進程優(yōu)先級權(quán)限對應一個不同的鏈表。每個task_struct描述符包含一個list_head類型字段run_list。如果進程的優(yōu)先權(quán)等于k,run_list字段把進程鏈接到優(yōu)先權(quán)為k的可運行進程鏈表中。所有這些可運行進程鏈表由一個單獨的prio_array_t數(shù)據(jù)結(jié)構(gòu)來實現(xiàn)。

prio_array_t數(shù)據(jù)結(jié)構(gòu)字段:

int nr_active 鏈表中進程描述符的數(shù)量

unsigned long[5] bitmp 優(yōu)先權(quán)位圖;當且僅當某個優(yōu)先權(quán)的進程鏈表不為空時設置相應的標志位

struct list_head[140] queue 140個優(yōu)先權(quán)隊列的頭結(jié)點。

進程間的關(guān)系

進程描述符中表示進程進程親屬字段的描述

real_parent 指向創(chuàng)建了p的進程描述符,如果p的父進程不存在,就指向1的進程描述符

parent   指向p的當前父進程。它的值常常與real_parent一致,偶爾不同:當一個進程發(fā)出監(jiān)控p的ptrace()系統(tǒng)請求時

children   鏈表的頭部,鏈表中的所有元素都是p創(chuàng)建的子進程

sibling   指向兄弟進程鏈表中的下一元素或前一個元素的指針,這些兄弟進程的父進程都是p

建立非親屬關(guān)系的進程描述符字段

group_leader   p所在進程組的領(lǐng)頭進程的描述符指針

signal->pgrp   p所在進程組領(lǐng)頭進程的pid

tgid       p所在線程組的領(lǐng)頭進程pid

signal->session p所在登錄會話領(lǐng)頭進程的pid

ptrace_children 鏈表的頭,該鏈表包含所有被debugger程序跟蹤的p的子進程

ptrace_list     指向所跟蹤進程其實際父進程鏈表的前一個和下一個元素

pidhash表及鏈表

內(nèi)核必須能從進程的pid導出對應的進程描述符指針。順序掃描進程鏈表并檢查進程描述符的pid字段是可行但是相當?shù)牡托?。為了加速查找,引入?個散列表。

4個散列表和進程描述符中的相關(guān)字段

PIDTYPE_PID    pid  進程的PID

PIDTYPE_TGID    tgid  線程組領(lǐng)頭進程的pid

PIDTPE_PGID     pgrp  進程組領(lǐng)頭進程的pid

PIDTYPE_SID    session  回話領(lǐng)頭進程的pid

內(nèi)核初始化期間動態(tài)的為這4個散列表分配空間,并把他們的地址存入pid_hash數(shù)組中。一個散列表的長度依賴于可用的ram容量。

用pid_hashfn宏把PID數(shù)值轉(zhuǎn)化為散列表的表索引。

Linux利用鏈表來處理散列表中沖突的pid:每一個表項是有沖突的進程描述符組成的雙向鏈表。

具有鏈表的散列法比從pid到表索引的線性轉(zhuǎn)換更優(yōu)越,因為在任何給定的實例中,系統(tǒng)中的進程數(shù)總是遠遠小于32768。如果在任何給定的實例中大部分表項都不使用的話,那么把表定義為32768項會是一種存儲浪費。

由于需要跟蹤進程間的關(guān)系,pid散列表中使用的數(shù)據(jù)結(jié)構(gòu)非常復雜。如果根據(jù)線程組號查找散列表,只能返回一個進程描述符,就是線程組領(lǐng)頭進程的描述符。為了能快速返回組中其他所有的進程,內(nèi)核就必須為每個線程組保留一個鏈表。PID散列表的數(shù)據(jù)結(jié)構(gòu)解決了這個問題。因為他們可以為包含在一個散列表中的任何pid號定義進程鏈表。針對四中散列表,定義了四個pid結(jié)構(gòu)的數(shù)組,它在進程描述符的pids字段中。

pid結(jié)構(gòu)的字段

int      nr      pid的數(shù)值

struct h_list_node  pid_chain  鏈接散列表的下一個和前一個元素

struct list_head  pid_list  鏈接相同pid值的進程鏈表的標頭

如何組織進程

運行隊列鏈表把處于TASK_RUNNING狀態(tài)的所有進程組織在一起;沒有為處于TASK_STOPPED、EXIT_ZOMBLE或EXIT_DEAD狀態(tài)的進程建立專門的鏈表。由于對處于這些狀態(tài)的進程訪問比較簡單,或者通過pid或者通過特定父進程的子進程鏈表,所有不必對這三種狀態(tài)進程分組。

根據(jù)不同特殊事件把處于TASK_INTERUPTIBLE和TASK_UNinteruptible狀態(tài)的進程細分為許多類,將這些進程鏈接到等待隊列。

等待隊列

等待隊列由雙向鏈表實現(xiàn),其元素包括指向進程描述符的指針。每個等待隊列有一個等待隊列頭wait_queue_head_t;因為等待隊列是有中斷處理程序和主要內(nèi)核函數(shù)修改的,因此必須對其雙向鏈表進行保護,同步是通過等待隊列頭中的lock自旋鎖達到的。等待隊列鏈表中的元素類型為wait_queue_t.

有兩種睡眠進程:互斥進程(等待隊列元素的flags字段為1)由內(nèi)核有選擇的喚醒,而非互斥進程(flags=0)總是由內(nèi)核在事件發(fā)生時喚醒。等待訪問臨界資源的進程是互斥進程的例子,等待相關(guān)事件的進程是非互斥的。

等待隊列的操作

定義一個等待隊列:DECLARE_WAIT_QUEUE_HEAD(name)定義一個等待隊列的頭init_waitqueue_head()可以用來初始化動態(tài)分配的等待隊列的頭變量。

初始化wait_queue_t結(jié)構(gòu)變量:init_waitqueue_entry(q,p);DEFINE_WAIT宏

插入等待隊列:add_wait_queue()非互斥 第一個位置;add_wait_queue_exclusive()互斥最后一個位置

移除:remove_wait_queue()

判斷隊列為空:waitqueue_active()

要等待特定條件的進程可以條用如下函數(shù):

sleep_on();

interruptible_sleep_on();

sleep_on_timeout();interruptible_sleep_on_timeout();

prepare_to_wait();finsh_wait();prepare_to_wait_exclusive();

wait_event;wait_event_interruptible;

喚醒:各種喚醒函數(shù),不舉例。

喚醒:非互斥進程p將有default_wake_function()喚醒

進程資源限制

每個進程都由一組相關(guān)資源的限制,限制了進程能使用的系統(tǒng)資源數(shù)量。對當前進程的資源限制存放在current->signal->rlim字段,即進程信號描述符的一個字段。該字段類型為rlimt結(jié)構(gòu)的數(shù)組,每個資源限制對應一個元素:

struct rlimit{

unsigned long rlim_cur;

unsigned long rlim_max;

}

rlim_cur字段是資源的當前資源限制。rlim_max字段是組員限制所允許的大值。

進程切換

為了控制進程的執(zhí)行,內(nèi)核必須有能力掛起當前cpu上運行的進程,并恢復以前掛起的某個進程執(zhí)行。這種行為被稱為進程切換、任務切換或上下文切換。

硬件上下文

進程恢復執(zhí)行前必須裝入寄存器的一組數(shù)據(jù)稱為硬件上下文。硬件上下文是進程可執(zhí)行上下文的一個子集,因為可執(zhí)行上下文包含進程執(zhí)行所需要的所有信息。linux中,進程硬件上下文的一部分存放在TSS段,而剩余部分存放在內(nèi)核態(tài)的堆棧中。

進程切換只發(fā)生在內(nèi)核態(tài)。在執(zhí)行進程切換之前,用戶態(tài)進程使用的所有寄存器內(nèi)容已經(jīng)保存在內(nèi)核態(tài)堆棧上,這也包括ss和esp這對寄存器的內(nèi)容(存儲用戶態(tài)堆棧指針的地址)。

任務狀態(tài)段

盡管linux不使用硬件上下文切換,但是強制它為系統(tǒng)中不同的cpu創(chuàng)建一個TSS。因為:

1、cpu從用戶態(tài)切換到內(nèi)核態(tài)時,它就從TSS中獲取內(nèi)核態(tài)堆棧的地址。

2、當用戶態(tài)進程視圖通過in或out指令訪問一個I/O端口時,cpu需要訪問存放在TSS中的I/O許可權(quán)位圖,以檢查該進程是否有訪問端口的權(quán)力。

tss_struct結(jié)構(gòu)描述TSS的格式。每次進程切換時,內(nèi)核都更新TSS的某些字段以便相應的cpu控制單元可以安全的檢索到它需要的信息。因此,TSS反應了CPU上當前進程的特權(quán)級別,但不必為沒有運行的進程保留TSS。

每個TSS有它自己8字節(jié)的任務狀態(tài)段描述符。這個描述符包括指向TSS起始地址的32位base字段,20位limit字段。s標志位被清0,以表示相應的TSS是系統(tǒng)段。type字段值為11或9以表示這個段實際上是TSS。每個cpu的tr寄存器包含相應TSS的TSSD選擇符,也包含了兩個隱藏的非編程字段base和limit。這樣處理器可以直接對TSS尋址而不用從GDT中檢索TSS的地址。

thread字段

每個進程描述符包含一個類型為thread_struct的thread字段,只要進程被切換出去,內(nèi)核就把硬件上下文保存在這個結(jié)構(gòu)中。

執(zhí)行進程切換

從本質(zhì)上說,進程切換由兩步組成:

1、切換頁全局目錄以安裝一個新的地址空間;

2、切換內(nèi)核堆棧和硬件上下文,因為硬件上下文提供了內(nèi)核執(zhí)行新進程所需要的所有信息,包含cpu寄存器。

switch_to宏

__switch_to()函數(shù)

創(chuàng)建進程

1、copy on write機制允許父子進程讀相同的物理頁。只要兩者中有一個視圖寫一個物理頁,內(nèi)核就把這個頁的內(nèi)容拷貝到一個新的物理頁,并把這個新的物理頁分配給正在寫的進程。

2、輕量級進程允許父子進程共享每進程在內(nèi)核的很多數(shù)據(jù)結(jié)構(gòu),如頁表、打開文件表及信號處理。

3、vfork()系統(tǒng)調(diào)用創(chuàng)建的進程能共享其父進程的內(nèi)存地址空間。為了防止父進程重寫子進程需要的數(shù)據(jù),阻塞父進程的執(zhí)行,一直到子進程退出或執(zhí)行一個新的程序為止。

clone()、fork()和vfork()系統(tǒng)調(diào)用

do_fork()函數(shù)負責處理clone()、fork()、vfork()系統(tǒng)調(diào)用

copy_process()創(chuàng)建進程描述符以及子進程執(zhí)行所需要的所有其他數(shù)據(jù)結(jié)構(gòu)

內(nèi)核線程

與普通進程的區(qū)別:1、內(nèi)核線程只運行在內(nèi)核態(tài),而普通進程既可以運行在內(nèi)核態(tài),也可以運行在用戶態(tài)。2、內(nèi)核線程只能使用大于PAGE_OFFSET的線性地址空間。

創(chuàng)建一個內(nèi)核線程

kernel_thread()函數(shù)創(chuàng)建一個新的內(nèi)核線程

進程0和進程1以及其他內(nèi)核線程

撤銷進程

進程終止:exit_group()終止線程組;exit()終止一個線程

do_group_exit();

do_exit();

進程刪除  release_task()函數(shù)從僵死進程的描述符中分離出最后的數(shù)據(jù)結(jié)構(gòu);對僵死進程的處理方式:如果父進程不需要接受來自自進程的信號,就do_exit();如果已經(jīng)給父進程信號,就調(diào)用wait4()或waitpid(),將回收進程描述符所占用的內(nèi)存空間。在前一種情況下,內(nèi)存的回收將有進程調(diào)度程序來完成。

網(wǎng)站名稱:三、進程-創(chuàng)新互聯(lián)
當前鏈接:http://muchs.cn/article42/diechc.html

成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供電子商務、商城網(wǎng)站響應式網(wǎng)站、移動網(wǎng)站建設、手機網(wǎng)站建設關(guān)鍵詞優(yōu)化

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

成都app開發(fā)公司