操作系統(tǒng)——實驗三

1、通過fork的方式在扰,產(chǎn)生4個進程P1,P2,P3,P4缕减,每個進程打印輸出自己的名字,例如P1輸出“I am?the process P1”芒珠。要求P1最先執(zhí)行桥狡,P2、P3互斥執(zhí)行皱卓,P4最后執(zhí)行裹芝。通過多次測試驗證實現(xiàn)是否正確。

實驗代碼文件:1.c

根據(jù)題意我們可以知道娜汁,他們相互之間的前驅(qū)關(guān)系為P1-->P2嫂易、P1-->P3、P2-->P4掐禁、P3-->P4怜械。由此,我們可以通過添加適當(dāng)?shù)男盘柫縼硗瓿蛇@些關(guān)系操作傅事。

在這里缕允,我們添加了三個信號量:

S1=sem_open("1",O_CREAT,0666,0);

S2=sem_open("2",O_CREAT,0666,0);

S3=sem_open("3",O_CREAT,0666,0);

根據(jù)我們設(shè)置的信號量關(guān)系:

sem_wait(S1); printf("I am the process P2\n"); sem_post(S1); sem_post(S2);

sem_wait(S1); printf("I am the process P3\n"); sem_post(S1); sem_post(S3);

printf("I am the process P1\n"); sem_post(S1); pid_4=fork();

sem_wait(S2); sem_wait(S3); printf("I am the process P4\n"); sem_post(S2); sem_post(S3);

我們可以看出來,P2和P3都在等待P1完成后對S1信號量的V操作蹭越。但是由于一次只產(chǎn)生一個資源單位的信號量障本,所以每次只能P2 P3中的一個進程來執(zhí)行,因此他們是互斥的般又,且前驅(qū)為P1彼绷。在此之后,P2 P3執(zhí)行完畢之后會分別對S2 S3進行V操作產(chǎn)生各一個資源單位茴迁,因此他們是P4的前驅(qū)寄悯。

在進程產(chǎn)生方面,我們采用fork()調(diào)用的方式來實現(xiàn)堕义。首先在主進程中進行fork()調(diào)用猜旬,產(chǎn)生子進程2。通過判斷pid倦卖,之后依次產(chǎn)生子進程3洒擦、子進程4:

pid_1=getpid();

pid_2=fork();

if(pid_2==0)//子進程2

{ sem_wait(S1); printf("I am the process P2\n"); sem_post(S1); sem_post(S2); }

if(pid_2>0)//主進程1

{ pid_3=fork();

if(pid_3==0)//子進程3

{ sem_wait(S1); printf("I am the process P3\n"); sem_post(S1); sem_post(S3); }

if(pid_3>0)//主進程1

{ printf("I am the process P1\n"); sem_post(S1); pid_4=fork();

if(pid_4==0)//子進程4

{ sem_wait(S2); sem_wait(S3); printf("I am the process P4\n"); sem_post(S2); sem_post(S3); } } }

通過編譯執(zhí)行,我們可以得到兩種執(zhí)行結(jié)果怕膛,如下圖所示:

1->2->3->4
-1->3->2->4

這是因為題目中間僅要求P2P3互斥熟嫩,并未指定先后順序。根據(jù)上述的前驅(qū)關(guān)系褐捻,我們可以知道掸茅,P2與P3誰先得到了P1之行結(jié)束之后對S1進行V操作得到的資源誰便先執(zhí)行。

2柠逞、火車票余票數(shù)ticketCount初始值為1000昧狮,有一個售票線程,一個退票線程板壮,各循環(huán)執(zhí)行多次逗鸣。添加同步機制,使得結(jié)果始終正確绰精。要求多次測試添加同步機制前后的實驗效果撒璧。

未添加同步機制實驗代碼文件:2_1.c

添加同步機制實驗代碼文件:2_2.c

程序的大體結(jié)構(gòu)是典型的生產(chǎn)消費者問題模型。首先我們來看未添加同步機制之前的程序運行結(jié)果:

可以看到笨使,結(jié)果并不是我們預(yù)期得到的沪悲,因為票總數(shù)是一定的1000,因此無論如何總票數(shù)只會<=1000阱表。

我們再看添加了同步機制之后的程序:

通過上面的測試結(jié)果可以看出殿如,添加同步機制之后不會發(fā)生類似前面的問題,最終的票數(shù)是期待得到的結(jié)果最爬。上面的實驗證實了增加了同步機制之后的多線程并發(fā)程序有效的解決了臟數(shù)據(jù)的讀取問題涉馁。以下為同步機制:

sem_t empty,full; //定義全局同步信號量empty,full

pthread_mutex_t mutex; //定義一個全局互斥量爱致,在不同函數(shù)中

sem_init (&empty, 0, 0); //初始化empty信號量

sem_init (&full, 0, 1000); //初始化full信號量

3烤送、一個生產(chǎn)者一個消費者線程同步。設(shè)置一個線程共享的緩沖區(qū)糠悯,char buf[10]帮坚。一個線程不斷從鍵盤輸入字符到buf,一個線程不斷的把buf的內(nèi)容輸出到顯示器妻往。要求輸出的和輸入的字符和順序完全一致。(在輸出線程中,每次輸出睡眠一秒鐘,然后以不同的速度輸入測試輸出是否正確)影兽。要求多次測試添加同步機制前后的實驗效果鸡典。

未添加同步機制實驗代碼文件:3_1.c

添加同步機制實驗代碼文件:3_2.c

未添加同步機制時的實驗結(jié)果:

添加同步機制后的實驗結(jié)果:

通過對比結(jié)果以及分析問題,我們可以知道:

此題是一個經(jīng)典的生產(chǎn)者和消費者問題:輸入線程產(chǎn)生字符,輸出線程消耗字符。

如果不考慮同步機制就讓兩個進程同時運行的話便會出現(xiàn)如下問題:

????輸入進程產(chǎn)生字符過快,buffer數(shù)組的資源被用盡拳锚,繼續(xù)輸入會導(dǎo)致數(shù)組越界或者之前輸入的字符還未打印便被覆蓋。

????輸出進程消耗字符過快寻行,繼續(xù)輸出則會訪問到為初始化的數(shù)組元素或者將之前打印過的字符再次打印

根據(jù)以上存在的問題霍掺,我們可以通過使用信號量來實現(xiàn)兩個進程之間的同步。經(jīng)分析我們需要初始化兩個信號量:full和empty拌蜘,其中full保證未打印的字符不超過10抗楔,empty保證存在需要打印的字符再進行打印。

同步機制如下所示:

pthread_mutex_init( &mutex , NULL ); //初始化互斥量

empty=*sem_open("E",O_CREAT,0666,10);

full=*sem_open("F",O_CREAT,0666,0);

因此在添加同步機制之后拦坠,在程序運行的一開始连躏,輸入線程未輸入任何字符,輸出線程被阻塞贞滨,不會打印任何字符入热。當(dāng)輸入字符串長度大于10時,輸出線程仍能按序輸出這12個字符晓铆。經(jīng)過測試不同的輸入速度勺良,均能滿足題意。

兩個進程如下所示:

void *producer( void *arg){ for(int i=0;;i++)

{

????sem_wait(&empty);

????scanf("%c",&buffer[i%10]);

????sem_post(&full); } }

void *consumer( void *arg )

{

for(int i=0;;i++)

{

sem_wait(&full);

printf("輸出:%c\n",buffer[i%10]);

sem_post(&empty); sleep(1);

} }

4骄噪、

a)通過實驗測試尚困,驗證共享內(nèi)存的代碼中,receiver能否正確讀出sender發(fā)送的字符串链蕊?如果把其中互斥的代碼刪除事甜,觀察實驗結(jié)果有何不同?如果在發(fā)送和接收進程中打印輸出共享內(nèi)存地址滔韵,他們是否相同逻谦,為什么?

原Sender程序:4_Sender.c

原Receiver程序:4_Receiver.c

刪除互斥Sender程序:4_Sender_nosem.c

刪除互斥Receiver程序:4_Receiver_nosem.c

打印共享內(nèi)存地址Sender程序:4_Sender_print.c

打印共享內(nèi)存地址Receiver程序:4_Receiver_print.c

編譯執(zhí)行原Sender與Receiver程序:

我們可以發(fā)現(xiàn)Receiver進程已經(jīng)接收到響應(yīng)字符串陪蜻,功能正常邦马。

我們刪除互斥代碼再次進行測試,查看代碼得知這兩個進程也是使用信號量實現(xiàn)的同步機制,其核心的函數(shù)為semop()和semctl()滋将。

semop()函數(shù)它的作用是改變信號量的值邻悬,原型為:

int?semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);

sem_id是由semget()返回的信號量標(biāo)識符,sembuf結(jié)構(gòu)的定義如下:

struct?sembuf{????

short?sem_num;?// 除非使用一組信號量随闽,否則它為0????

short?sem_op;??// 信號量在一次操作中需要改變的數(shù)據(jù)父丰,通常是兩個數(shù),一個是-1橱脸,即P(等待)操作,???????????????????

????????????????????????// 一個是+1分苇,即V(發(fā)送信號)操作添诉。????

short?sem_flg;?// 通常為SEM_UNDO,使操作系統(tǒng)跟蹤信號,???????????????????

????????????????????????// 并在進程沒有釋放該信號量而終止時医寿,操作系統(tǒng)釋放信號量};

semctl()函數(shù)該函數(shù)用來直接控制信號量信息栏赴,它的原型為:

int?semctl(int sem_id, int sem_num, int command, ...);

如果有第四個參數(shù),它通常是一個union semum結(jié)構(gòu)靖秩,定義如下:

union?semun {????

int?val;????

struct?semid_ds *buf;????

unsigned?short?*arry;};

前兩個參數(shù)與前面一個函數(shù)中的一樣须眷,command通常是下面兩個值中的其中一個SETVAL:用來把信號量初始化為一個已知的值。p 這個值通過union semun中的val成員設(shè)置沟突,其作用是在信號量第一次使用前對它進行設(shè)置花颗。IPC_RMID:用于刪除一個已經(jīng)無需繼續(xù)使用的信號量標(biāo)識符。

兩個進程通過共享內(nèi)存?zhèn)鬏敂?shù)據(jù)惠拭,因共享內(nèi)存不可同時讀寫扩劝,因此采用二元信號量進行進程互斥,具體操作如下:

init: 設(shè)置信號量為0职辅,此時只允許寫入棒呛,不允許讀取(因為共享內(nèi)存沒有數(shù)據(jù));

Sender: 在sem=0時,寫入數(shù)據(jù)到共享內(nèi)存(阻塞讀);寫入完成后域携,sem=1,此時可以讀取簇秒,不可以寫入;

Receiver: 在sem=1時秀鞭,讀取數(shù)據(jù)趋观;讀取完成后,sem=0,此時只允許寫入锋边。

下面對刪除互斥代碼的程序進行編譯運行:

可以看到拆内,如果刪去相關(guān)的互斥代碼Sender進程由于scanf的占用并無具體問題,但Rec進程并不會等待Sender進程宠默,一直在掃描共享內(nèi)存并打印到屏幕上麸恍,因此當(dāng)Sender發(fā)送消息時,Rec也會及時更新,并不斷打印出來抹沪。

我們在進程輸出時將其內(nèi)存打印出刻肄,下面為修改程序后編譯執(zhí)行的結(jié)果:

如圖所示,兩個進程顯示的內(nèi)存地址并不一致融欧,這與我們內(nèi)存共享的預(yù)期的機制不符敏弃。經(jīng)過之前的實驗與查詢資料,這是由于虛擬內(nèi)存機制與內(nèi)存隨機化導(dǎo)致的噪馏。

對于進程來說麦到,使用的都是虛擬地址。每個進程維護一個單獨的頁表欠肾。

頁表是一種數(shù)組結(jié)構(gòu)瓶颠,存放著各虛擬頁的狀態(tài),是否映射刺桃,是否緩存粹淋。

1)數(shù)組的索引號,表示虛擬頁號

2)數(shù)組的值若為null瑟慈,表示未映射的頁若非null桃移,第一位表示有效位,為1葛碧,表明緩存的頁借杰;為0,表明未緩存的頁进泼。其余位表示緩存到的物理頁號第步。

我們運行的兩個進程在初始化的時候使用了shmat函數(shù),此函數(shù)的作用是將共享內(nèi)存空間掛載到進程中缘琅,實則就是對進程分配字符串的虛擬內(nèi)存映射到共享內(nèi)存的物理內(nèi)存粘都,從而實現(xiàn)內(nèi)存的共享。所以雖然我們打印出來的內(nèi)存地址不一樣刷袍,但是它們實際映射的物理內(nèi)存地址是一致的翩隧。

ASLR(Address Space Layout Randomization)在2005年被引入到Linux的內(nèi)核 kernel 2.6.12 中,當(dāng)然早在2004年就以patch的形式被引入呻纹。隨著內(nèi)存地址的隨機化堆生,使得響應(yīng)的應(yīng)用變得隨機。這意味著同一應(yīng)用多次執(zhí)行所使用內(nèi)存空間完全不同雷酪,也意味著簡單的緩沖區(qū)溢出攻擊無法達到目的淑仆。

b)有名管道和無名管道通信系統(tǒng)調(diào)用是否已經(jīng)實現(xiàn)了同步機制?通過實驗驗證哥力,發(fā)送者和接收者如何同步的蔗怠。比如墩弯,在什么情況下,發(fā)送者會阻塞寞射,什么情況下渔工,接收者會阻塞?

無名管道原pipe程序:4_pipe.c

有名管道原rcv程序:4_fifo_rcv.c

有名管道原snd程序:4_fifo_snd.c

這里為了研究同步機制桥温,對原代碼進行了一些修改引矩,首先我們來研究無名管道:

/* * Filename: pipe.c */

#include <stdio.h>

#include <unistd.h> //for pipe()

#include <string.h> //for memset()

#include <stdlib.h> //for exit()

int main(){

????int fd[2];

????char buf[20];

????if(-1 == pipe(fd)) { perror("pipe"); exit(EXIT_FAILURE); }

????pid_t pid; pid = fork();

????if(!pid){ write(fd[1], "hello,world", 12);

????printf("發(fā)送已完成,內(nèi)容為:hello,world\n");

? ?memset(buf, '\0', sizeof(buf)); }

????else if(pid>0){ read(fd[0], buf, 12);

????printf("The message is: %s\n", buf); }

????else{ perror("fork"); exit(1); } return 0;}

可以看出來侵浸,程序通過pipe函數(shù)創(chuàng)建管道旺韭,函數(shù)傳遞一個整形數(shù)組fd,fd的兩個整形數(shù)表示的是兩個文件描述符掏觉,其中第一個用于讀取數(shù)據(jù)区端,第二個用于寫數(shù)據(jù)。兩個描述符相當(dāng)遠管道的兩端履腋,一段負責(zé)寫數(shù)據(jù)珊燎,一段負責(zé)讀數(shù)據(jù)惭嚣。我們這里將父進程設(shè)置為讀進程遵湖,子進程設(shè)置為寫進程,結(jié)果如下:


由此我們可以知道晚吞,通信功能正常延旧。我們使發(fā)送進程sleep 2s,接收進程sleep 1s來查看研究進程是否正常:

我們繼續(xù)修改代碼槽地,連續(xù)發(fā)送三次消息迁沫,查看結(jié)果:

可以看到輸出進程是按照輸入進程輸入的順序輸出數(shù)據(jù),并且當(dāng)輸入進程沒有數(shù)據(jù)輸入捌蚊,即管道中沒有數(shù)據(jù)的時候集畅,輸出進程會阻塞。實現(xiàn)了同步機制缅糟。

通過實驗挺智,我們對無名管道的總結(jié)如下:

無名管道由一個在基本文件系統(tǒng)存儲設(shè)備上的INODE,一個與其相連的內(nèi)存INODE窗宦,兩個打開文件控制塊(分別對應(yīng)管道的信息發(fā)送端和信息接收端)及其所屬進程的描述信息來標(biāo)識赦颇,在系統(tǒng)執(zhí)行PIPE(P)命令行之后生成。并在P[0]中返回管道的讀通道打開文件描述等赴涵,在P[1]中返回管道的寫通道打開文件描述符媒怯。

從結(jié)構(gòu)上看,無名管道沒有文件路徑名髓窜,不占用文件目錄項扇苞,因此文件目錄結(jié)構(gòu)中的鏈表不適用于這種文件,它只是存在于打開文件結(jié)構(gòu)中的一個臨時文件,隨其所依附的進程的生存而生存杨拐,當(dāng)進程終止時祈餐,無名管道也隨之消亡。送入管道的信息一旦被讀進程取用就從管道中消失了哄陶,讀寫操作之間符合先進先出的隊列原則帆阳。???

管道文件是進程間通信的工具,為了盡量少的占用系統(tǒng)存儲資源屋吨,一般系統(tǒng)均將其限制為最大長度為4096(PIPSIZ)字節(jié)的小型文件蜒谤。當(dāng)欲寫入的消息超過4096字節(jié)時,就產(chǎn)生了讀至扰、寫進程之間的同步問題鳍徽。

首先寫操作查找PIPE文件中當(dāng)前指針的偏移量F-OFFSET,然后從此位置開始盡量寫入信息敢课,當(dāng)長度達到4096字節(jié)時阶祭,系統(tǒng)控制寫進程進入睡眠狀態(tài),一直等待讀進程取走全部信息時直秆,文件長度指針置0濒募,寫進程才被喚醒繼續(xù)工作。??

?為防止多個進程同時讀寫一個管道文件而產(chǎn)生混亂圾结,在管道文件的INODE標(biāo)志字I-FLAY項中設(shè)置了ILOCK標(biāo)志項瑰剃,以設(shè)置軟件鎖的方式實現(xiàn)多進程間對管道文件的互斥使用。

無名管道存在著如下兩個嚴重的缺點:

第一筝野,無名管道只能用于連接具有共同祖先的進程晌姚。???

第二,無名管道是依附進程而臨時存在的歇竟。

接下來我們研究有名管道挥唠,通過閱讀代碼我們可以發(fā)現(xiàn):有名管道可用于更為廣泛的進程之間的通信,但其區(qū)別于無名通道的一點則是通信雙方必須同時存在焕议,否則便會阻塞宝磨。有名管道的開啟需要創(chuàng)建相關(guān)文件,隨后兩個文件再對文件進行操作号坡,但寫入和讀取的數(shù)據(jù)并不會存儲在文件中懊烤,而是直接置于內(nèi)存中。由此我們也可以知道其讀寫操作是同時進行的宽堆。寫進程fifo_send分為四個步驟執(zhí)行腌紧,首先判斷當(dāng)前目錄下是否已經(jīng)存在my_fifo文件,不存在的話在當(dāng)前目錄下通過mkfifo()函數(shù)創(chuàng)建FIFO類型的文件my_fifo畜隶;再通過open()函數(shù)打開my_fifo文件壁肋,最后向文件中寫入消息号胚;讀進程的過程和寫進程的類似,沒有了創(chuàng)建fifo文件的過程浸遗。

為了更好的研究有名管道的機制猫胁,我們分別研究以下情況:

如圖所示,當(dāng)寫進程單獨運行時跛锌,盡管管道中不存在數(shù)據(jù)弃秆,但其仍處于阻塞狀態(tài),隨后讀進程進入之后髓帽,讀寫進程之間實現(xiàn)了通信菠赚,進程得以工作并結(jié)束,這與預(yù)期想法相符合郑藏。

接下來我們研究另一種情況衡查,先單獨運行讀進程,再運行寫進程必盖,發(fā)現(xiàn)讀進程單獨運行時被阻塞拌牲,但當(dāng)寫進程運行后仍然被阻塞,這是因為我們上次實驗的管道文件并未刪除歌粥,所以讀進程使用的是之前的文件塌忽,而寫進程在創(chuàng)建文件之前會先檢查是否已有同名文件存在(此文件一般由上次運行該程序時留下,因為該程序在退出時并沒有刪除相關(guān)文件)阁吝,存在則刪除原文件再創(chuàng)建新文件砚婆。所以械拍,由于文件的殘留突勇,讀進程先運行之后先訪問了原先的文件,進入阻塞狀態(tài)坷虑,而寫進程在運行之后檢測到原文件的存在甲馋,將其進行了刪除并創(chuàng)建了新文件,如此便相當(dāng)于兩個進程并不在一個有名管道兩邊迄损,處于阻塞狀態(tài)定躏。

由此,我們可以看出芹敌,有名管道實現(xiàn)了同步機制痊远,但較依賴于管道文件。

有名管道和無名管道基本相同氏捞,但也有不同點:無名管道只能由父子進程使用碧聪;但是通過有名管道,不相關(guān)的進程也能交換數(shù)據(jù)液茎。

c)消息通信系統(tǒng)調(diào)用是否已經(jīng)實現(xiàn)了同步機制逞姿?通過實驗驗證辞嗡,發(fā)送者和接收者如何同步的。比如滞造,在什么情況下续室,發(fā)送者會阻塞,什么情況下谒养,接收者會阻塞挺狰?

客戶端代碼:4_Client.c

服務(wù)端代碼:4_Server.c

我們運行這兩個程序查看結(jié)果,功能正常執(zhí)行买窟,在客戶端沒有發(fā)送消息的時候服務(wù)端阻塞她渴,當(dāng)客戶端發(fā)送消息,服務(wù)端及時響應(yīng)蔑祟。

我們修改Server.c的代碼趁耗,使得服務(wù)器的運行速度低于客戶端輸入速度,查看結(jié)果疆虚,可以看到苛败,雖然在客戶端發(fā)送消息后,服務(wù)器沒有及時響應(yīng)径簿,但是在之后響應(yīng)的時候其順序并未錯亂罢屈,響應(yīng)正常,這說明其具備同步機制篇亭。

在此機制中缠捌,發(fā)送端傳送的消息都會加入一個消息隊列,寫進程在此機制中不會被阻塞译蒂,其寫入的字符串會一直被添加至隊列的末端曼月,而讀進程會從隊列的首端一直讀取消息,消息節(jié)點一旦被讀取便會移除隊列柔昼。當(dāng)隊列中不含其需要類型的消息時便會阻塞哑芹。在消息隊列提供了一種從一個進程向另一個進程發(fā)送一個數(shù)據(jù)塊的方法。 ?每個數(shù)據(jù)塊都被認為含有一個類型捕透,接收進程可以獨立地接收含有不同類型的數(shù)據(jù)結(jié)構(gòu)聪姿。我們可以通過發(fā)送消息來避免命名管道的同步和阻塞問題。但是消息隊列與命名管道一樣乙嘀,每個數(shù)據(jù)塊都有一個最大長度的限制末购。Linux用宏MSGMAX和MSGMNB來限制一條消息的最大長度和一個隊列的最大長度。消息隊列機制如下圖所示:

5虎谢、閱讀Pintos操作系統(tǒng)盟榴,找到并閱讀進程上下文切換的代碼,說明實現(xiàn)的保存和恢復(fù)的上下文內(nèi)容以及進程切換的工作流程嘉冒。

根據(jù)pintos的文件組織方式曹货,與進程相關(guān)的定義大都在threads/thread.h部分咆繁,于是我們在其中尋找上下文切換方面的代碼,首先我們可以看到thread的結(jié)構(gòu)體:

tid_t?tid:線程的線程標(biāo)識符顶籽。每個線程必須具有在內(nèi)核的整個生命周期內(nèi)唯一的tid玩般。默認情況下,tid_t是int的typedef礼饱,每個新線程接收數(shù)字上的下一個更高的tid坏为,從初始進程的1開始。

enum thread_status?status:線程的狀態(tài)镊绪,一共有以下四種:

????THREAD_RUNNING:線程在給定時間內(nèi)正在運行匀伏。可以通過 thread_current()函數(shù)返回正在運行的線程蝴韭。

????THREAD_READY:該線程已準備好運行够颠,但它現(xiàn)在沒有運行¢可以選擇線程以在下次調(diào)用調(diào)度程序時運行履磨。就緒線程保存在名為ready_list的雙向鏈表中

????THREAD_BLOCKED:線程正在等待某些事務(wù),例如鎖定變?yōu)榭捎们斐荆{(diào)用的中斷剃诅。在通過調(diào)用thread_unblock(函數(shù))轉(zhuǎn)換到THREAD_READY狀態(tài)之前,線程不會再次調(diào)度驶忌。

????THREAD_DYING:切換到下一個線程后矛辕,調(diào)度程序?qū)N毀該線程。char?name[16]:線程命名的字符串付魔,至少前幾個數(shù)組單元為字符聊品。uint8_t *stack:線程的棧指針。當(dāng)線程運行時抒抬,CPU的堆棧指針寄存器跟蹤堆棧的頂部杨刨,并且該成員未使用晤柄。但是當(dāng)CPU切換到另一個線程時擦剑,該成員保存線程的堆棧指針。保存線程的寄存器不需要其他成員芥颈,因為必須保存的其他寄存器保存在堆棧中惠勒。

int?priority:線程優(yōu)先級,范圍從PRI_MIN(0)到PRI_MAX(63)爬坑。較低的數(shù)字對應(yīng)較低的優(yōu)先級纠屋,因此優(yōu)先級0是最低優(yōu)先級,優(yōu)先級63是最高優(yōu)先級盾计。struct list_elem?allelem:用于將線程鏈接到所有線程的列表中售担。每個線程在創(chuàng)建時都會插入到此列表中赁遗,并在退出時刪除。應(yīng)該使用thread_foreach()函數(shù)來迭代所有線程族铆。

struct list_elem?elem:用于將線程放入雙向鏈表:ready_list(準備好運行的線程列表)或sema_down(等待信號量的線程列表)岩四。

uint32_t *pagedir:頁表指針,用于將進程結(jié)構(gòu)的虛擬地址映射到物理地址哥攘。

unsigned?magic:始終設(shè)置為THREAD_MAGIC剖煌,它只是threads / thread.c中定義的任意數(shù)字,用于檢測堆棧溢出逝淹。

在這里并沒有明顯的看到關(guān)于上下文切換的函數(shù)定義耕姊,暫時考慮其在進程函數(shù)定義中有所體現(xiàn),因此接下來我們查看進程函數(shù)部分:

通過注釋我們可以知道這一個結(jié)構(gòu)體與上下文進程切換相關(guān)栅葡,于是我們對這個結(jié)構(gòu)體進行分析:schedule()是負責(zé)切換線程的主要函數(shù)茉兰,其主要被thread_block(),thread_exit()欣簇,和thread_yield()這三次函數(shù)調(diào)用邦邦。

下面我們仔細分析一下此函數(shù)的具體實現(xiàn):首先其定義了三個thread結(jié)構(gòu)體的指針,均為局部變量醉蚁,cur指針指向running_thread ()函數(shù)的返回值燃辖。經(jīng)過查看實現(xiàn),它定位了當(dāng)前進程网棍。因此cur指針就是當(dāng)前運行進程的指針黔龟。

next_thread_to_run()此函數(shù)選擇并返回要調(diào)度的下一個線程。應(yīng)該從運行隊列返回一個線程滥玷,除非運行隊列為空氏身。如果運行隊列為空,返回idle_thread惑畴。值得注意的是如果正在運行的線程可以繼續(xù)運行蛋欣,它便仍在運行隊列中。于是這個函數(shù)的作用是返回下一個執(zhí)行進程的指針如贷。

最后一個prev指針這里定義為了NULL陷虎,說明和這里關(guān)系不大。

接下來是三個判斷杠袱,分別保證了此時中斷關(guān)閉(程序不能被中斷)尚猿、當(dāng)前進程不在運行狀態(tài)以及存在下一個進程。

經(jīng)過這三個判斷后楣富,便是上下文切換的核心部分:如果當(dāng)前進程和下一個進程不相等凿掂,則調(diào)用switch_threads (cur, next)將當(dāng)前進程和下一個進程進行切換。

switch_threads (cur, next)函數(shù)定義于switch.S中纹蝴,.S文件是匯編文件:

?分析一下這個匯編代碼: 先4個寄存器壓棧保存寄存器狀態(tài)(保護作用)庄萎, 這4個寄存器是switch_threads_frame的成員踪少。然后全局變量thread_stack_ofs記錄線程和棧之間的間隙, 我們都知道線程切換有個保存現(xiàn)場的過程糠涛,來看34,35行秉馏, 先把當(dāng)前的線程指針放到eax中, 并把線程指針保存在相對基地址偏移量為edx的地址中脱羡。38,39: 切換到下一個線程的線程棧指針萝究, 保存在ecx中, 再把這個線程相對基地址偏移量edx地址(上一次保存現(xiàn)場的時候存放的)放到esp當(dāng)中繼續(xù)執(zhí)行锉罐。這里ecx, eax起容器的作用帆竹, edx指向當(dāng)前現(xiàn)場保存的地址偏移量。簡單來說就是保存當(dāng)前線程狀態(tài)脓规, 恢復(fù)新線程之前保存的線程狀態(tài)栽连。然后再把4個寄存器拿出來, 這個是硬件設(shè)計要求的侨舆, 必須保護switch_threads_frame里面的寄存器才可以destroy掉eax, edx, ecx秒紧。然后注意到現(xiàn)在eax(函數(shù)返回值是eax)就是被切換的線程棧指針。

我們由此得到一個結(jié)論挨下, schedule先把當(dāng)前線程丟到就緒隊列熔恢,然后把線程切換如果下一個線程和當(dāng)前線程不一樣的話。

然后再看shedule最后一行的函數(shù)thread_schedule_tail做了什么臭笆, 這里參數(shù)prev是NULL或者在下一個線程的上下文中的當(dāng)前線程指針叙淌。根據(jù)函數(shù)的注釋我們可以得知其功能是:通過激活新線程的頁表完成線程切換,如果前一個線程正在死亡愁铺,則銷毀它鹰霍。接下來一步步觀察其具體的執(zhí)行步驟。首先其也會獲取當(dāng)前運行進程的指針并保證此時程序不能被中斷茵乱。接著其會將當(dāng)其運行進程的狀態(tài)改變?yōu)門HREAD_RUNNING以及初始化其時間切片茂洒,這可以看做切換進程后對新進程的一個激活。最后的部分表示如果我們切換的線程正在死亡瓶竭,銷毀它的struct線程督勺。而我們傳入的prev一定為NULL,所以在切換過程中這一部分并不會執(zhí)行在验。

下圖為進程上下文切換流程圖:

代碼鏈接:BJTU_operating-system-lesson/Lab3 at master · Jerlllly/BJTU_operating-system-lesson · GitHub

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末玷氏,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子腋舌,更是在濱河造成了極大的恐慌,老刑警劉巖渗蟹,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件块饺,死亡現(xiàn)場離奇詭異赞辩,居然都是意外死亡,警方通過查閱死者的電腦和手機授艰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門辨嗽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人淮腾,你說我怎么就攤上這事糟需。” “怎么了谷朝?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵洲押,是天一觀的道長。 經(jīng)常有香客問我圆凰,道長杈帐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任专钉,我火速辦了婚禮挑童,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘跃须。我一直安慰自己站叼,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布菇民。 她就那樣靜靜地躺著大年,像睡著了一般。 火紅的嫁衣襯著肌膚如雪玉雾。 梳的紋絲不亂的頭發(fā)上翔试,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天,我揣著相機與錄音复旬,去河邊找鬼垦缅。 笑死,一個胖子當(dāng)著我的面吹牛驹碍,可吹牛的內(nèi)容都是我干的壁涎。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼志秃,長吁一口氣:“原來是場噩夢啊……” “哼怔球!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起浮还,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤竟坛,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體担汤,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡涎跨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了崭歧。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片隅很。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖率碾,靈堂內(nèi)的尸體忽然破棺而出叔营,到底是詐尸還是另有隱情,我是刑警寧澤所宰,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布纺涤,位于F島的核電站拒啰,受9級特大地震影響蔗候,放射性物質(zhì)發(fā)生泄漏王带。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一件炉、第九天 我趴在偏房一處隱蔽的房頂上張望勘究。 院中可真熱鬧,春花似錦斟冕、人聲如沸口糕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽景描。三九已至,卻和暖如春秀撇,著一層夾襖步出監(jiān)牢的瞬間超棺,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工呵燕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留棠绘,地道東北人。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓再扭,卻偏偏與公主長得像氧苍,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子泛范,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內(nèi)容