ToB企服应用市场:ToB评测及商务社交产业平台

标题: 《操作体系真象还原》第九章(一) —— 在内核空间中实现线程 [打印本页]

作者: 石小疯    时间: 2024-12-25 22:02
标题: 《操作体系真象还原》第九章(一) —— 在内核空间中实现线程
本章节所有代码托管在miniOS_32
章节使命介绍

使命简介

上一节,我们初步完成了内核的内存管理部分的内容
本节我们将正式开始操作体系进程管理的相关内容
本节的主要使命有:
     使命目标

  1. #include<pthread.h>
  2. #include<stdio.h>
  3. void* thread_work(void* args){
  4.     char* str=(char*)args;
  5.     printf("args is %s\n",str);
  6.     return NULL;
  7. }
  8. int main(){
  9.     pthread_t tid;
  10.     pthread_create(&tid,NULL,thread_work,"pthread_create\n");
  11.     pthread_join(tid,NULL);
  12.     return 0;
  13. }
复制代码
本节我们将实现一个类似于pthread_create的函数,用于创建一个线程并执行传入的执行函数,最终实现的调用代码如下所示
/kernel/main.c
  1. #include "print.h"
  2. #include "init.h"
  3. #include "thread.h"
  4. void thread_work(void *arg);
  5. int main(void)
  6. {
  7.     put_str("I am kernel\n");
  8.     init_all();
  9.     thread_start("thread_work", 31, thread_work, "pthread_create\n");
  10.     while (1);
  11.     return 0;
  12. }
  13. /* 线程执行函数 */
  14. void thread_work(void *arg)
  15. {
  16.     char *para = (char *)arg;
  17.     int i = 10;
  18.     while (i--)
  19.         put_str(para);
  20. }
复制代码
PCB简介

   犹如上一节中的位图,位图是管理内存的数据布局,对于线程大概进程,也需要有一个数据布局对其进行管理,这个数据布局就是PCB
  PCB(Process Control Block,进程控制块)操作体系内部用于存储进程信息的数据布局
  操作体系通过PCB来管理和调理进程。
  PCB 的生命周期
     PCB的内容
   PCB中包含了进程执行所需的各种信息,如进程状态、寄存器值、内存使用情况、I/O 状态等。
  PCB 的主要功能
     以下是PCB的示意布局图



在内核空间中创建并运行线程

代码目次布局

  1. .
  2. ├── bin
  3. │   ├── bitmap.o
  4. │   ├── debug.o
  5. │   ├── init.o
  6. │   ├── interrupt.o
  7. │   ├── kernel.bin
  8. │   ├── kernel.o
  9. │   ├── loader
  10. │   ├── main.o
  11. │   ├── mbr
  12. │   ├── memory.o
  13. │   ├── print.o
  14. │   ├── string.o
  15. │   └── thread.o
  16. ├── boot
  17. │   ├── include
  18. │   │   └── boot.inc
  19. │   ├── loader.S
  20. │   └── mbr.S
  21. ├── kernel
  22. │   ├── debug.c
  23. │   ├── debug.h
  24. │   ├── global.h
  25. │   ├── init.c
  26. │   ├── init.h
  27. │   ├── interrupt.c
  28. │   ├── interrupt.h
  29. │   ├── kernel.S
  30. │   ├── main.c
  31. │   ├── memory.c
  32. │   └── memory.h
  33. ├── lib
  34. │   ├── kernel
  35. │   │   ├── bitmap.c
  36. │   │   ├── bitmap.h
  37. │   │   ├── io.h
  38. │   │   ├── print.h
  39. │   │   └── print.S
  40. │   ├── stdint.h
  41. │   ├── string.c
  42. │   └── string.h
  43. ├── Makefile
  44. ├── start.sh
  45. └── thread
  46.     ├── thread.c
  47.     └── thread.h
复制代码
数据布局界说

/thread/thread.h
界说进程大概线程的使命状态
  1. /*定义进程或者线程的任务状态*/
  2. enum task_status
  3. {
  4.     TASK_RUNNGING,
  5.     TASK_READY,
  6.     TASK_BLOCKED,
  7.     TASK_WAITING,
  8.     TASK_HANGING,
  9.     TASK_DIED,
  10. };
复制代码
界说线程栈,存储线程执行时的运行信息
  1. /*定义线程栈,存储线程执行时的运行信息*/
  2. struct thread_stack
  3. {
  4.     uint32_t ebp;
  5.     uint32_t ebx;
  6.     uint32_t edi;
  7.     uint32_t esi;
  8.     // 一个函数指针,指向线程执行函数,目的是为了实现通用的线程函数调用
  9.     void (*eip)(thread_func *func, void *func_args);
  10.     // 以下三条是模仿call进入thread_start执行的栈内布局构建的,call进入就会压入参数与返回地址,因为我们是ret进入kernel_thread执行的
  11.     // 要想让kernel_thread正常执行,就必须人为给它造返回地址,参数
  12.     void(*unused_retaddr); // 一个栈结构占位
  13.     thread_func *function;
  14.     void *func_args;
  15. };
复制代码
界说PCB,PCB的信息庞大复杂,我们将来一点点对其进行填充,本节只需要以下信息即可
  1. /*PCB结构体*/
  2. struct task_struct
  3. {
  4.     // 线程栈的栈顶指针
  5.     uint32_t *self_kstack;
  6.     // 线程状态
  7.     enum task_status status;
  8.     // 线程的优先级
  9.     uint8_t priority;
  10.     // 线程函数名
  11.     char name[16];
  12.     // 用于PCB结构体的边界标记
  13.     uint32_t stack_magic;
  14. };
复制代码
代码解说

代码逻辑

     如下所示,thread_start就是我们最终要实现的用以模拟pthread_create的函数
其包含了我们上述说的代码逻辑
  1. /*根据线程栈的运行信息开始运行线程函数*/
  2. struct task_struct *thread_start(char *name, int prio, thread_func function, void *func_args)
  3. {
  4.     /*1.分配一页空间给线程作为线程执行的栈空间*/
  5.     struct task_struct *thread = get_kernel_pages(1);
  6.     /*2.初始化PCB,PCB里存放了线程的基本信息以及线程栈的栈顶指针*/
  7.     init_thread(thread, name, prio);
  8.     /*
  9.     3.根据线程栈的栈顶指针,初始化线程栈,也就是初始化线程的运行信息
  10.     比如线程要执行的函数,以及函数参数
  11.     */
  12.     thread_create(thread, function, func_args);
  13.     /*4.上述准备好线程运行时的栈信息后,即可运行执行函数了*/
  14.     asm volatile("movl %0,%%esp;    \
  15.                 pop %%ebp;          \
  16.                 pop %%ebx;          \
  17.                 pop %%edi;          \
  18.                 pop %%esi;          \
  19.                 ret"
  20.                  :
  21.                  : "g"(thread->self_kstack)
  22.                  : "memory");
  23.     return thread;
  24. }
复制代码
  关于最后执行运行函数的内联汇编代码,主要与线程栈的栈空间布局有关,我们在最后初始化栈空间的运行信息之后进行具体说明
  初始化PCB

  1. /*PCB结构体*/
  2. struct task_struct
  3. {
  4.     // 线程栈的栈顶指针
  5.     uint32_t *self_kstack;
  6.     // 线程状态
  7.     enum task_status status;
  8.     // 线程的优先级
  9.     uint8_t priority;
  10.     // 线程函数名
  11.     char name[16];
  12.     // 用于PCB结构体的边界标记
  13.     uint32_t stack_magic;
  14. };
复制代码
PCB的初始化也就是对上述布局体进行初始化
  1. /*初始化PCB*/
  2. void init_thread(struct task_struct *pthread, char *name, int prio)
  3. {
  4.     memset(pthread, 0, sizeof(*pthread));
  5.     strcpy(pthread->name, name);
  6.     pthread->status = TASK_RUNNGING;
  7.     pthread->priority = prio;
  8.     /*
  9.     一个线程的栈空间分配一页空间,将PCB放置在栈底
  10.     pthread是申请的一页空间的起始地址,因此加上一页的大小,就是栈顶指针
  11.     */
  12.     pthread->self_kstack = (uint32_t *)((uint32_t)pthread + PG_SIZE);
  13.     /*PCB的边界标记,防止栈顶指针覆盖掉PCB的内容*/
  14.     pthread->stack_magic = 0x19991030;
  15. }
复制代码
以下是创建的线程栈内存示意图


初始化线程栈运行信息

  1. /*根据PCB信息,初始化线程栈的运行信息*/
  2. void thread_create(struct task_struct *pthread, thread_func function, void *func_args)
  3. {
  4.     /*给线程栈空间的顶部预留出中断栈信息的空间*/
  5.     pthread->self_kstack = (uint32_t *)((int)(pthread->self_kstack) - sizeof(struct intr_stack));
  6.     /*给线程栈空间的顶部预留出线程栈信息的空间*/
  7.     pthread->self_kstack = (uint32_t *)((int)(pthread->self_kstack) - sizeof(struct thread_stack));
  8.     // 初始化线程栈,保存线程运行时需要的信息
  9.     struct thread_stack *kthread_stack = (struct thread_stack *)pthread->self_kstack;
  10.     // 线程执行函数
  11.     kthread_stack->eip = kernel_thread;
  12.     kthread_stack->function = function;
  13.     kthread_stack->func_args = func_args;
  14.     kthread_stack->ebp = kthread_stack->ebx = kthread_stack->edi = kthread_stack->esi = 0;
  15. }
复制代码
此中线程执行函数如下所示
  1. static void kernel_thread(thread_func *function, void *func_args)
  2. {
  3.     function(func_args);
  4. }
复制代码
以下是初始化线程栈后的内存示意图

创建并运行线程

  1.     /*4.上述准备好线程运行时的栈信息后,即可运行执行函数了*/
  2.     asm volatile("movl %0,%%esp;    \
  3.                 pop %%ebp;          \
  4.                 pop %%ebx;          \
  5.                 pop %%edi;          \
  6.                 pop %%esi;          \
  7.                 ret"
  8.                  :
  9.                  : "g"(thread->self_kstack)
  10.                  : "memory");
复制代码
如下所示,当线程栈初始化结束之后,栈顶指针首先弹出了寄存器映像
  1.                 pop %%ebp;          \
  2.                 pop %%ebx;          \
  3.                 pop %%edi;          \
  4.                 pop %%esi;          \
复制代码
如许栈顶指针就指向了通用执行函数kernel_thread,如许接下来只需要调用kernel_thread,就调用了用户的执行函数

于是接下来代码执行ret指令,ret指令会做两件事
   
  最后的结果如下所示

于是接下来,根据c语言的函数调用约定,kernel_thread会取出占位的返回所在上边的两个参数,也就是执行函数的所在与执行函数的参数,然后调用执行函数运行

完备代码

/thread/thread.h
  1. #ifndef __THREAD_THREAD_H#define __THREAD_THREAD_H#include "stdint.h"/*界说执行函数*/typedef void thread_func(void *);/*定义进程或者线程的任务状态*/
  2. enum task_status
  3. {
  4.     TASK_RUNNGING,
  5.     TASK_READY,
  6.     TASK_BLOCKED,
  7.     TASK_WAITING,
  8.     TASK_HANGING,
  9.     TASK_DIED,
  10. };/*中断发生时调用中断处置惩罚程序的压栈情况*/struct intr_stack{    uint32_t vec_no;    // pushad的压栈情况    uint32_t edi;    uint32_t esi;    uint32_t ebp;    uint32_t esp_dummy;    uint32_t ebx;    uint32_t edx;    uint32_t ecx;    uint32_t eax;    // 中断调用时处置惩罚器主动压栈的情况    uint32_t gs;    uint32_t fs;    uint32_t es;    uint32_t ds;    uint32_t err_code;    void (*eip)(void);    uint32_t cs;    uint32_t eflags;    void *esp;    uint32_t ss;};
  11. /*定义线程栈,存储线程执行时的运行信息*/
  12. struct thread_stack
  13. {
  14.     uint32_t ebp;
  15.     uint32_t ebx;
  16.     uint32_t edi;
  17.     uint32_t esi;
  18.     // 一个函数指针,指向线程执行函数,目的是为了实现通用的线程函数调用
  19.     void (*eip)(thread_func *func, void *func_args);
  20.     // 以下三条是模仿call进入thread_start执行的栈内布局构建的,call进入就会压入参数与返回地址,因为我们是ret进入kernel_thread执行的
  21.     // 要想让kernel_thread正常执行,就必须人为给它造返回地址,参数
  22.     void(*unused_retaddr); // 一个栈结构占位
  23.     thread_func *function;
  24.     void *func_args;
  25. };/*PCB结构体*/
  26. struct task_struct
  27. {
  28.     // 线程栈的栈顶指针
  29.     uint32_t *self_kstack;
  30.     // 线程状态
  31.     enum task_status status;
  32.     // 线程的优先级
  33.     uint8_t priority;
  34.     // 线程函数名
  35.     char name[16];
  36.     // 用于PCB结构体的边界标记
  37.     uint32_t stack_magic;
  38. };/*初始化PCB*/void init_thread(struct task_struct *pthread, char *name, int prio);/*根据PCB信息,初始化线程栈的运行信息*/void thread_create(struct task_struct *pthread, thread_func function, void *func_args);/*根据线程栈的运行信息开始运行线程函数*/struct task_struct *thread_start(char *name, int prio, thread_func function, void *func_args);#endif
复制代码
/thread/thread.c
  1. #include "thread.h"#include "stdint.h"#include "string.h"#include "global.h"#include "memory.h"#define PG_SIZE 4096static void kernel_thread(thread_func *function, void *func_args)
  2. {
  3.     function(func_args);
  4. }/*初始化PCB*/void init_thread(struct task_struct *pthread, char *name, int prio){    memset(pthread, 0, sizeof(*pthread));    strcpy(pthread->name, name);    pthread->status = TASK_RUNNGING;    pthread->priority = prio;    /*一个线程的栈空间分配一页空间,将PCB放置在栈底*/    pthread->self_kstack = (uint32_t *)((uint32_t)pthread + PG_SIZE);    pthread->stack_magic = 0x19991030;}/*根据PCB信息,初始化线程栈的运行信息*/
  5. void thread_create(struct task_struct *pthread, thread_func function, void *func_args)
  6. {
  7.     /*给线程栈空间的顶部预留出中断栈信息的空间*/
  8.     pthread->self_kstack = (uint32_t *)((int)(pthread->self_kstack) - sizeof(struct intr_stack));
  9.     /*给线程栈空间的顶部预留出线程栈信息的空间*/
  10.     pthread->self_kstack = (uint32_t *)((int)(pthread->self_kstack) - sizeof(struct thread_stack));
  11.     // 初始化线程栈,保存线程运行时需要的信息
  12.     struct thread_stack *kthread_stack = (struct thread_stack *)pthread->self_kstack;
  13.     // 线程执行函数
  14.     kthread_stack->eip = kernel_thread;
  15.     kthread_stack->function = function;
  16.     kthread_stack->func_args = func_args;
  17.     kthread_stack->ebp = kthread_stack->ebx = kthread_stack->edi = kthread_stack->esi = 0;
  18. }/*根据线程栈的运行信息开始运行线程函数*/struct task_struct *thread_start(char *name, int prio, thread_func function, void *func_args){    /*1.分配一页的空间给线程作为线程执行的栈空间*/    struct task_struct *thread = get_kernel_pages(1);    /*2.初始化PCB,PCB里存放了线程的根本信息以及线程栈的栈顶指针*/    init_thread(thread, name, prio);    /*    3.根据线程栈的栈顶指针,初始化线程栈,也就是初始化线程的运行信息    比如线程要执行的函数,以及函数参数    */    thread_create(thread, function, func_args);    /*4.上述准备好线程运行时的栈信息后,即可运行执行函数了*/
  19.     asm volatile("movl %0,%%esp;    \
  20.                 pop %%ebp;          \
  21.                 pop %%ebx;          \
  22.                 pop %%edi;          \
  23.                 pop %%esi;          \
  24.                 ret"
  25.                  :
  26.                  : "g"(thread->self_kstack)
  27.                  : "memory");    return thread;}
复制代码
运行结果如下所示
 可以看到,最后准期打印了执行函数中的信息

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4