真象還原之線程

參考書籍:《操作系統 真象還原》 作者 鄭鋼
第九章:線程。包括線程的實現,線程的調度。

進程和線程確實很難。雖然基本看懂這一章的內容。但還是不怎麼懂~
本章,我們的目的比較簡單,實現C語言中的進程創建函數:pthread_create()函數,並簡單的進行輪詢調度。
(對於線程,我不會,詳細內容略:pthread_create()

詳細內容,見書上。


進程和線程

關於進程和線程的介紹:https://my.oschina.net/cnyinlinux/blog/422207
或者看書上。
我下面僅僅列出,我需要的部分。
執行流:因爲處理器只知道加電後按照程序計數器中的地址不斷地執行下去,在不斷執行的過程中,我們把程序計數器中的下一條指令地址所組成的執行軌跡稱爲程序的控制執行流,讓我們再深入描述 下。執行流就是 段邏輯上獨立的指令區域。

調度單元:我們要做的就是:給任何想單獨上處理器的代碼塊準備好它所依賴的上下文環境,從而使其具備獨立性,使之成爲執行流,即調度單元。

線程是 套機制,此機制可以爲一般的代碼塊創造它所依賴的上下文環境,從而讓代碼塊具有獨立性,因此在原理上線程能使 段函數成爲調度單元(或稱爲執行流),使函數能被調度器“認可”,從而能夠被專門調度到處理器上執行。

強調下,只有線程才具備能動性,它纔是處理器的執行單元,因此它是調度器眼中的調度單位。進程只是個資源整合體,它將進程中所有線程運行時用到資源收集在一起,供進程中的所有線程使用,真正上處理器上運行的其實都叫線程,進程中的線程纔是一個個的執行實體、執行流,因此,經調度器送上處理器執行的程序都是錢程。進程=資源+線程​

我對上面這一段持懷疑態度,所以我用線劃掉。

線程的實現

所有代碼見:https://github.com/da1234cao/tiny-os/tree/master/chapter9

代碼中用的是PCB。(但是,我不清楚,暫時按照書上的思路,後面亦是如此)
在這裏插入圖片描述

線程的調度

採用的是雙向鏈表的數據結構。

這是一個出彩的地方,以後也很值得借鑑。

struct list_elem { 
	struct list_elem* prev; II 前軀結點
	struct list_elem* next; II 後繼結點
} ; 

鏈表的元素只有前後指針,並沒有含有我們常見的數據部分。這個鏈表僅僅起這鏈接的作用。這時候,你可能會好奇,我如何將資源放在鏈表中。或者說如何將資源鏈接起來?具體見書上(實際的例子才更容易理解)。
在這裏插入圖片描述

之後每次從ready隊列中選擇隊頭。時間片的消耗,採用的是定時中斷。

上下文的切換,書中的代碼,並不是很好。因爲中斷,我們之前設置保護上下文,中斷棧用不到。而線程棧也沒有用,代碼中直接採用壓棧和出棧的方式,並沒有寫入線程棧的內存。

有點需要注意的是:**你可能會想,程序執行一半,如何恢復,有那麼多內容需要保存?**但是,如果你站在彙編的角度去思考就會容易很多。(C語言也是需要編譯生成二進制。用匯編去思考是很合理的。)彙編很重要的是寄存器。我們按照規定保存了需要的寄存器。還需要保存的部分是內存。內存中有我們的數據。但是,目前我們並沒有涉及到頁的換入換出。所以,程序被我們加載到我們指定的內存。CPU是通過CS:EIP,跳轉到不同的內存單元去執行。原來的內存單元沒有變換。所以整體來說,我們保護好規定的寄存器,內存沒有變化,便可以回到原來的執行環境。

main函數的分析

# include "kernel/print.h"
# include "init.h"
# include "thread.h"
# include "interrupt.h"

void k_thread_function_a(void*);
void k_thread_function_b(void*);

int main(void) {
    put_str("I am kernel.\n");
    init_all();

    thread_start("k_thread_a", 31, k_thread_function_a, "threadA ");
    thread_start("k_thread_b", 8, k_thread_function_b, "threadB ");

    intr_enable();

   while (1) {
        put_str("main ");
    }
/*
    int i=10;
    for(i;i>0;i--)
            put_str("main ");
*/
    return 0;
}

void k_thread_function_a(void* args) {
    // 這裏必須是死循環,否則執行流並不會返回到main函數,所以CPU將會放飛自我,出發6號未知操作碼異常
    while (1) {
        put_str((char*) args);
    }
}

void k_thread_function_b(void* args) {
    while (1) {
        put_str((char*) args);
    }
}
            

注:main函數不需要開闢一頁的內存,因爲它是作爲內核的一部分被loader加載進入指定的內存。這也很合理,因爲系統宏觀上執行的是一個死循環。在init()中已經給出了main線程的相關信息。(詳細的代碼情況,見上面的github鏈接)

現在就緒隊列中,相當於有三個線程:main線程,k_thread_a線程,k_thread_b線程。

暫時的現象是交替輸出。

(但是,如果將上面的代碼,變成下面的樣子,當main函數結束的時候,程序結束。)
不能這麼改,因爲,我實現的只有創建,目前只能死循環,不能讓程序結束。

# include "kernel/print.h"
# include "init.h"
# include "thread.h"
# include "interrupt.h"

void k_thread_function_a(void*);
void k_thread_function_b(void*);

int main(void) {
    put_str("I am kernel.\n");
    init_all();

    thread_start("k_thread_a", 31, k_thread_function_a, "threadA ");
    thread_start("k_thread_b", 8, k_thread_function_b, "threadB ");

    intr_enable();
/*
   while (1) {
        put_str("main ");
    }
*/
    int i=10;
    for(i;i>0;i--)
            put_str("main ");

    return 0;
}

void k_thread_function_a(void* args) {
    // 這裏必須是死循環,否則執行流並不會返回到main函數,所以CPU將會放飛自我,出發6號未知操作碼異常
    while (1) {
        put_str((char*) args);
    }
}

void k_thread_function_b(void* args) {
    while (1) {
        put_str((char*) args);
    }
}
        

參考文章:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章