【操作系统】GeekOS完成记录(二)project0:了解内核线程的创建过程

878 阅读4分钟

本文正在参加「金石计划」

项目要求

project0主要是为了帮助我们熟悉GeekOS的环境。它的任务如下: image.png

它的提示如下: image.png 由此可知,我们的任务是在main.c中调用kthread.c中的Start_Kernel_Thread()函数。

流程概览

先来结合英文注释看看Start_Kernel_Thread()是怎么实现的(中文字是我加的翻译注释)

/*
 * Start a kernel-mode-only thread, using given function as its body
 * and passing given argument as its parameter.  Returns pointer
 * to the new thread if successful, null otherwise.
 *
 * startFunc - is the function to be called by the new thread 
 //startFunc指的是新线程所调用的功能函数,也就是说我们可以实现一个读取字符并显示的函数,把它作为startFunc,传入Start_Kernel_Thread()
 
 * arg - is a paramter to pass to the new function
 //待传入功能函数中的参数,但我们这个读取字符的函数并不需要这样的参数,可以先定为默认0
 
 * priority - the priority of this thread (use PRIORITY_NORMAL) for
 *    most things
 //线程优先级,决定哪一个线程优先执行
 
 * detached - use false for kernel threads
 //用来区分用户线程和内核线程,false是内核线程,true是用户线程,后续被Create_Thread()调用,而Create_Thread()调用的Init_Thread()在置owner位时会区分这两者
 
 */
struct Kernel_Thread* Start_Kernel_Thread(
    Thread_Start_Func startFunc,
    ulong_t arg,
    int priority,
    bool detached
)
{
    struct Kernel_Thread* kthread = Create_Thread(priority, detached);
    if (kthread != 0) {
	/*
	 * Create the initial context for the thread to make
	 * it schedulable.
	 */
	Setup_Kernel_Thread(kthread, startFunc, arg);


	/* Atomically put the thread on the run queue. */
	Make_Runnable_Atomic(kthread);
    }

    return kthread;
}

由上述可知:

  • geekos创建内核级线程的过程是:调用Create_Thread()函数,如果创建成功,就会建立内核线程,并且使得线程具有原子性。
  • 而在Create_Thread()这一步中,主要是为了给线程和它的栈申请内存空间,并且要确保申请成功,否则得返回空指针。
/*
 * Create a new raw thread object.
 * Returns a null pointer if there isn't enough memory.
 */
static struct Kernel_Thread* Create_Thread(int priority, bool detached)
{
    struct Kernel_Thread* kthread;
    void* stackPage = 0;

    /*
     * For now, just allocate one page each for the thread context
     * object and the thread's stack.
     */
    kthread = Alloc_Page();
    if (kthread != 0)
        stackPage = Alloc_Page();    

    /* Make sure that the memory allocations succeeded. */
    if (kthread == 0)
	return 0;
    if (stackPage == 0) {
	Free_Page(kthread);
	return 0;
    }

    /*Print("New thread @ %x, stack @ %x\n", kthread, stackPage); */

    /*
     * Initialize the stack pointer of the new thread
     * and accounting info
     */
    Init_Thread(kthread, stackPage, priority, detached);

    /* Add to the list of all threads in the system. */
    Add_To_Back_Of_All_Thread_List(&s_allThreadList, kthread);

    return kthread;
}

而其中的Init_Thread(),就是在给线程这个结构体的参数赋值:

static void Init_Thread(struct Kernel_Thread* kthread, void* stackPage,
	int priority, bool detached)
{
    static int nextFreePid = 1;

    struct Kernel_Thread* owner = detached ? (struct Kernel_Thread*)0 : g_currentThread;

    memset(kthread, '\0', sizeof(*kthread));
    kthread->stackPage = stackPage;
    kthread->esp = ((ulong_t) kthread->stackPage) + PAGE_SIZE;
    kthread->numTicks = 0;
    kthread->priority = priority;
    kthread->userContext = 0;
    kthread->owner = owner;

    /*
     * The thread has an implicit self-reference.
     * If the thread is not detached, then its owner
     * also has a reference to it.
     */
    kthread->refCount = detached ? 1 : 2;

    kthread->alive = true;
    Clear_Thread_Queue(&kthread->joinQueue);
    kthread->pid = nextFreePid++;

}

内核线程的结构如kthread.h中所示:

struct Kernel_Thread {
    ulong_t esp;			 /* offset 0 */
    volatile ulong_t numTicks;		 /* offset 4 */
    int priority;
    DEFINE_LINK(Thread_Queue, Kernel_Thread);
    void* stackPage; 
    struct User_Context* userContext;
    struct Kernel_Thread* owner;
    int refCount;

    /* These fields are used to implement the Join() function */
    bool alive;
    struct Thread_Queue joinQueue;
    int exitCode;

    /* The kernel thread id; also used as process id */
    int pid;

    /* Link fields for list of all threads in the system. */
    DEFINE_LINK(All_Thread_List, Kernel_Thread);

    /* Array of MAX_TLOCAL_KEYS pointers to thread-local data. */
#define MAX_TLOCAL_KEYS 128
    const void* tlocalData[MAX_TLOCAL_KEYS];

};
  • 创建成功之后,是建立和原子化的步骤。 setup就是将一些功能函数相关的信息推入线程的栈中:
/*
 * Set up the initial context for a kernel-mode-only thread.
 */
static void Setup_Kernel_Thread(
    struct Kernel_Thread* kthread,
    Thread_Start_Func startFunc,
    ulong_t arg)
{
    /*
     * Push the argument to the thread start function, and the
     * return address (the Shutdown_Thread function, so the thread will
     * go away cleanly when the start function returns).
     */
    Push(kthread, arg);
    Push(kthread, (ulong_t) &Shutdown_Thread);

    /* Push the address of the start function. */
    Push(kthread, (ulong_t) startFunc);

    /*
     * To make the thread schedulable, we need to make it look
     * like it was suspended by an interrupt.  This means pushing
     * an "eflags, cs, eip" sequence onto the stack,
     * as well as int num, error code, saved registers, etc.
     */

    /*
     * The EFLAGS register will have all bits clear.
     * The important constraint is that we want to have the IF
     * bit clear, so that interrupts are disabled when the
     * thread starts.
     */
    Push(kthread, 0UL);  /* EFLAGS */

    /*
     * As the "return address" specifying where the new thread will
     * start executing, use the Launch_Thread() function.
     */
    Push(kthread, KERNEL_CS);
    Push(kthread, (ulong_t) &Launch_Thread);

    /* Push fake error code and interrupt number. */
    Push(kthread, 0);
    Push(kthread, 0);

    /* Push initial values for general-purpose registers. */
    Push_General_Registers(kthread);

    /*
     * Push values for saved segment registers.
     * Only the ds and es registers will contain valid selectors.
     * The fs and gs registers are not used by any instruction
     * generated by gcc.
     */
    Push(kthread, KERNEL_DS);  /* ds */
    Push(kthread, KERNEL_DS);  /* es */
    Push(kthread, 0);  /* fs */
    Push(kthread, 0);  /* gs */
}

从这个入栈函数的构造可以看出,栈是向下生长的(往内存地址减小的方向)

static __inline__ void Push(struct Kernel_Thread* kthread, ulong_t value)
{
    kthread->esp -= 4;
    *((ulong_t *) kthread->esp) = value;
}

如何保证原子性呢?关键在于将线程入队的时候要屏蔽中断。

/*
 * Atomically make a thread runnable.
 * Assumes interrupts are currently enabled.
 */
void Make_Runnable_Atomic(struct Kernel_Thread* kthread)
{
    Disable_Interrupts();
    Make_Runnable(kthread);
    Enable_Interrupts();
}

assert(断言)是一种除错机制,用于验证代码是否符合预期。感觉这里的KASSERT也有强制检查的意味。

void Make_Runnable(struct Kernel_Thread* kthread)
{
    KASSERT(!Interrupts_Enabled());

    Enqueue_Thread(&s_runQueue, kthread);
}

功能实现思路及代码

上面过完了操作系统创建内核线程的流程,现在来实现一下要用到的功能函数,由于和project0相关,为了以示区分,我们就将其命名为project0。

实现函数的思路也很简单,就是

void project0()
{
    Print("To Exit hit Ctrl + d.\n");
    Keycode keycode;
    while (1)
    {
        if (Read_Key(&keycode))
        {
            if (!((keycode & KEY_SPECIAL_FLAG) || (keycode & KEY_RELEASE_FLAG))) // 不是特殊键或者弹起
            {
                int asciiCode = keycode & 0xff;                                     //d
                if ((keycode & KEY_CTRL_FLAG) == KEY_CTRL_FLAG && asciiCode == 'd') //ctrl+d
                {
                    Print("\n---------BYE!---------\n");
                    Exit(1);
                }
                else
                {
                    Print("%c", (asciiCode == '\r') ? '\n' : asciiCode);
                }
            }
        }
    }
}
  • 为什么要涉及到KEY_SPECIAL_FLAG呢? 因为keyboard.h里有如下说明: image.png 所以要保证位8(实际上是右数第9位)为0,才能是读取ascii码

代码运行

进入build目录下
make depend

make

bochs

启动后输入的字符就会被实时显示出来。