真象还原之线程

参考书籍:《操作系统 真象还原》 作者 郑钢
第九章:线程。包括线程的实现,线程的调度。

进程和线程确实很难。虽然基本看懂这一章的内容。但还是不怎么懂~
本章,我们的目的比较简单,实现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);
    }
}
        

参考文章:

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