IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> 《操作系统真象还原》第九章 ---- 终进入线程动斧开刀 豁然开朗拨云见日 还需解决同步机制才能长舒气 -> 正文阅读

[系统运维]《操作系统真象还原》第九章 ---- 终进入线程动斧开刀 豁然开朗拨云见日 还需解决同步机制才能长舒气


相关查阅博客链接


C语言结构体内存分布
C语言之typedef函数指针用法
c语言typedef怎么自定义函数,c语言中typedef的用法


本书中错误勘误


1、这个地方我认为是绝对错误的地方 就是关于我们的线程部分
书上写的是我们的main线程的pcb存放于位置应该是0xc009e00
但是按照书上loader.S既然mov esp,0xc009f000 那么肯定的是我们的main线程pcb就是位于0xc009f000 而不会是0xc009e000 但是为什么这里没有出错呢 当然 把mainpcb放于这两个位置都没有问题 反正空余的空间是留够了的 放这两个地方都没有问题 个人认为这是作者思考写的思路的有的小问题


进程 线程的自我小理解


毕竟距离上次看 《现代操作系统》 已经时隔很久
很多很多原来记的很牢的东西也都还回去了 但是不重要
记忆过的东西拾起来都还是很快的

进程 = 资源 + 线程

线程这里面给我独特的记忆点在于
线程可以有属于自己的资源 是一个最小的执行单元 有寄存器 和 栈
线程也只不过是 借用了进程的资源来做事情罢了 一个个代码块而已
而一般我们常用的也只不过是 单线程进程 就是进程里面只有一个执行流 从上而下的执行 而多线程进程 则是我们显示的创造线程一般所用到的


线程 进程的状态


这个我记得在《现代操作系统》的那里我就谈到过
有几个状态 就绪态 阻塞态 运行态
这几个状态都是相互切换来的 阻塞态 例如等待I/O时 调度器就可以调度把那个进程设置为阻塞态 并把处理器分配给其他进程

之前做过哈工大操作系统Lab的hxd对这个肯定也是不陌生的


内核级线程 & 用户级线程


感觉这个也是老生常谈的事情了 我还是顺带也提一嘴我的理解
内核级线程 指的是操作系统支持线程 即提供了线程这个概念 程序员可以在操作系统的帮助下来进程创建线程操作 线程这个实物概念对于操作系统是可见的
而用户级线程 是指的是 操作系统不支持线程这个概念 用户 程序员在写进程程序的时候 自己写了调度器或者自己写了一个执行流 而线程的切换是通过进程本身来调节的

当然 有好处也有坏处 凡事都有一个两面性
用户级线程 好处是切换线程时 不需要陷入内核 切换线程的代价要小的多 速度非常快 因为毕竟是在用户级程序层面上而言 自己切换
但 坏处是 一旦发生阻塞 操作系统是不清楚有这个线程的存在 可能就直接把进程切换掉了 整个的进程就直接阻塞


初步实现内核级线程


xdm 编程完成了这个部分发生了一件十分痛苦的事情 具体是什么 待会在相关位置我会提一嘴 但是这给了我一个提示 就是做事情一定要小心谨慎一点 因为一点点偏差可能就会导致几个小时的浪费
对 没错 就是因为一个小问题 差点我愤怒的打算放弃编写操作系统 在折腾了两三个小时后 发现问题竟然是一个很小很小的问题 后面会讲

编写到这里我们来理一下思路 是怎么弄出来线程的
我们编写了thread.hthread.c 目前只是把大概轮廓给弄了出来
我们通过创建了PCB 把我们线程所需要用到的相关内容全部放在了里面 PCB下端(我们申请的一页内存) 存放着的是我们的结构体task_struct 目前里面含有的东西不多 线程状态``特权级``边界魔数``还有我们的线程内核栈的地址

我们的 中断栈所在位置 位于一页内存的最顶点位置 我们上下文切换时 如果外面有中断 我们就把我们所有的上下文环境放在那里
我们的内核栈 所在位置 是位于中断栈的下方 那里我们放着的是我们的4个寄存器 和 我们的 某些需要的函数地址


浪费两三个小时调试的辛酸史


总得来说就是这样
看局部代码真不好分析出来 还是得总拉出来一起看 才看得懂代码是什么意思
这里我就要说一下 为什么这里我浪费了两三个小时调试半天
因为不是这里要测试一下 初步的线程创建切换吗

正确的主函数main.c本来应该是这样的

#include "print.h"
#include "init.h"
#include "debug.h"
#include "string.h"
#include "memory.h"
#include "../thread/thread.h"
void test_thread(void* arg);

int main(void) {
   put_str("I am kernel\n");
   init_all();
   thread_start("kernel_thread_a",31,test_thread,"argA ");
   while(1);
   return 0;
}

void test_thread(void* arg)
{
    while(1)
    	put_str((char*)arg);
}

我没找到问题前是这样写的

#include "print.h"
#include "init.h"
#include "debug.h"
#include "string.h"
#include "memory.h"
#include "../thread/thread.h"
void test_thread(void* arg);

void test_thread(void* arg)
{
    while(1)
    	put_str((char*)arg);
}

int main(void) {
   put_str("I am kernel\n");
   init_all();
   thread_start("kernel_thread_a",31,test_thread,"argA ");
   while(1);
   return 0;
}

这样写的话就导致 main.c编译的内存位置不再是0xc0001500了 而是其他位置了 哎 就这样我就发现原来可以运行的程序现在直接不能运行了
于是我还以为是昨晚的系统更新出错了 我就挨个挨个排查错误

mar.S loader.S 一个板块一个板块排查 一点点排查 就这样我一点点摸到了Loader.S最后的历史使命 跳转到 0xc0001500 我就去看内存0xc0001500发生了什么 结果发现里面的内存全是默认的乱码 哎 给我整怒了

在调试良久之后 我那个时候都想放弃了 甚至想给书籍作者打电话问一下怎么解决 真的很无语 到最后才发现是这个问题
这里的辛酸史各位看客 且当一乐
希望各位不要这样去做 下面直接放出来thread.h thread.c的代码 里面有很多我写的注释


编写thread.h


目前的thread.h代码

#ifndef __THREAD_THREAD_H
#define __THREAD_THREAD_H
#include "stdint.h"

typedef void thread_func(void*); //这里有点不懂定义的什么意思 搜了搜博客 发现是函数声明 
                          
enum task_status
{
    TASK_RUNNING, // 0
    TASK_READY,   // 1
    TASK_BLOCKED, // 2
    TASK_WAITING, // 3
    TASK_HANGING, // 4
    TASK_DIED     // 5
};

/*         intr_stack 用于处理中断被切换的上下文环境储存 */
/*	   这里我又去查了一下 为什么是反着的 越在后面的参数 地址越高 */

struct intr_struct
{
    uint32_t vec_no; //中断号
    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;
};


/*	线程栈 保护线程环境 */

struct thread_stack
{
    uint32_t ebp;
    uint32_t ebx;
    uint32_t edi;
    uint32_t esi;

    void (*eip) (thread_func* func,void* func_arg); //和下面的相互照应 以ret 汇编代码进入kernel_thread函数调用
    
    void (*unused_retaddr);                         //占位数 在栈顶站住了返回地址的位置 因为是汇编ret 
    thread_func* function;                          //进入kernel_thread要调用的函数地址
    void* func_arg;				      //参数指针
};

struct task_struct
{
    uint32_t* self_kstack;                          //pcb中的 kernel_stack 内核栈
    enum task_status status;                        //线程状态
    uint8_t priority;				      //特权级
    char name[16];
    uint32_t stack_magic;			      //越界检查  因为我们pcb上面的就是我们要用的栈了 到时候还要越界检查
};

void kernel_thread(thread_func* function,void* func_arg);
void thread_create(struct task_struct* pthread,thread_func function,void* func_arg);
void init_thread(struct task_struct* pthread,char* name,int prio);
struct task_struct* thread_start(char* name,int prio,thread_func function,void* func_arg);

#endif

编写thread.c


#include "thread.h"   //函数声明 各种结构体
#include "stdint.h"   //前缀
#include "string.h"   //memset
#include "global.h"   //不清楚
#include "memory.h"   //分配页需要

#define PG_SIZE 4096

void kernel_thread(thread_func* function,void* func_arg)
{
    function(func_arg);
}

void thread_create(struct task_struct* pthread,thread_func function,void* func_arg)
{
    pthread->self_kstack -= sizeof(struct intr_struct);  //减去中断栈的空间
    pthread->self_kstack -= sizeof(struct thread_stack);
    struct thread_stack* kthread_stack = (struct thread_stack*)pthread->self_kstack; 
    kthread_stack->eip = kernel_thread;                 //地址为kernel_thread 由kernel_thread 执行function
    kthread_stack->function = function;
    kthread_stack->func_arg = func_arg;
    kthread_stack->ebp = kthread_stack->ebx = kthread_stack->ebx = kthread_stack->esi = 0; //初始化一下
    return;
}

void init_thread(struct task_struct* pthread,char* name,int prio)
{
    memset(pthread,0,sizeof(*pthread)); //pcb位置清0
    strcpy(pthread->name,name);
    pthread->status = TASK_RUNNING;
    pthread->priority = prio;
    pthread->self_kstack = (uint32_t*)((uint32_t)pthread + PG_SIZE); //刚开始的位置是最低位置 栈顶位置+一页
    pthread->stack_magic = 0x23333333;                               //设置的魔数 检测是否越界限
}

struct task_struct* thread_start(char* name,int prio,thread_func function,void* func_arg)
{
    struct task_struct* thread = get_kernel_pages(1);
    init_thread(thread,name,prio);
    thread_create(thread,function,func_arg);
    
    asm volatile("movl %0,%%esp; pop %%ebp; pop %%ebx; pop %%edi; pop %%esi; ret" : : "g"(thread->self_kstack) :"memory"); //栈顶的位置为 thread->self_kstack 
    return thread;
}

修改MakeFile & main.c 验证结果


makefile修改如下

BUILD_DIR = ./build
ENTRY_POINT = 0xc0001500
AS = nasm
CC = gcc
LD = ld
LIB = -I lib/ -I lib/kernel/ -I lib/user/ -I kernel/ -I device/ 
ASFLAGS = -f elf
CFLAGS = -Wall -m32 -fno-stack-protector $(LIB) -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes
LDFLAGS =  -m elf_i386 -Ttext $(ENTRY_POINT) -e main -Map $(BUILD_DIR)/kernel.map
OBJS = $(BUILD_DIR)/main.o $(BUILD_DIR)/init.o $(BUILD_DIR)/interrupt.o \
      $(BUILD_DIR)/timer.o $(BUILD_DIR)/kernel.o $(BUILD_DIR)/print.o \
      $(BUILD_DIR)/debug.o $(BUILD_DIR)/string.o $(BUILD_DIR)/memory.o \
      $(BUILD_DIR)/bitmap.o $(BUILD_DIR)/thread.o
      
##############     c代码编译     ###############
$(BUILD_DIR)/main.o: kernel/main.c lib/kernel/print.h \
        lib/stdint.h kernel/init.h lib/string.h kernel/memory.h \
        thread/thread.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/init.o: kernel/init.c kernel/init.h lib/kernel/print.h \
        lib/stdint.h kernel/interrupt.h device/timer.h kernel/memory.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/interrupt.o: kernel/interrupt.c kernel/interrupt.h \
        lib/stdint.h kernel/global.h lib/kernel/io.h lib/kernel/print.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/timer.o: device/timer.c device/timer.h lib/stdint.h\
        lib/kernel/io.h lib/kernel/print.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/debug.o: kernel/debug.c kernel/debug.h \
        lib/kernel/print.h lib/stdint.h kernel/interrupt.h
	$(CC) $(CFLAGS) $< -o $@
	
$(BUILD_DIR)/string.o: lib/string.c lib/string.h \
	kernel/debug.h kernel/global.h
	$(CC) $(CFLAGS) $< -o $@
	
$(BUILD_DIR)/memory.o: kernel/memory.c kernel/memory.h \
	lib/stdint.h lib/kernel/bitmap.h kernel/debug.h lib/string.h
	$(CC) $(CFLAGS) $< -o $@
	
$(BUILD_DIR)/bitmap.o: lib/kernel/bitmap.c lib/kernel/bitmap.h kernel/global.h \
	lib/string.h kernel/interrupt.h lib/kernel/print.h kernel/debug.h
	$(CC) $(CFLAGS) $< -o $@
	
$(BUILD_DIR)/thread.o: thread/thread.c thread/thread.h \
	lib/stdint.h lib/string.h kernel/global.h kernel/memory.h
	$(CC) $(CFLAGS) $< -o $@

##############    汇编代码编译    ###############
$(BUILD_DIR)/kernel.o: kernel/kernel.S
	$(AS) $(ASFLAGS) $< -o $@

$(BUILD_DIR)/print.o: lib/kernel/print.S
	$(AS) $(ASFLAGS) $< -o $@

##############    链接所有目标文件    #############
$(BUILD_DIR)/kernel.bin: $(OBJS)
	$(LD) $(LDFLAGS) $^ -o $@

.PHONY : mk_dir hd clean all

mk_dir:
	if [ ! -d $(BUILD_DIR) ]; then mkdir $(BUILD_DIR); fi

hd:
	dd if=$(BUILD_DIR)/kernel.bin \
           of=/home/cooiboi/bochs/hd60M.img \
           bs=512 count=200 seek=9 conv=notrunc

clean:
	cd $(BUILD_DIR) && rm -f  ./*

build: $(BUILD_DIR)/kernel.bin

all: mk_dir build hd

main.c修改如下

#include "print.h"
#include "init.h"
#include "debug.h"
#include "string.h"
#include "memory.h"
#include "../thread/thread.h"
void test_thread(void* arg);

void test_thread(void* arg)
{
    while(1)
    	put_str((char*)arg);
}

int main(void) {
   put_str("I am kernel\n");
   init_all();
   thread_start("kernel_thread_a",31,test_thread,"argA ");
   while(1);
   return 0;
}

验证结果 从目前看应该是ok的

在这里插入图片描述


编写双向链表 基础数据结构


我觉得这个部分挺好的 之前虽然没怎么接触过双向链表 但是力扣刷题的时候做到过 这个时候来看一下原理发现还是多简单的
这个就不多解释了 下面直接先放代码


编写list.h


#ifndef __LIB_KERNEL_LIST_H
#define __LIB_KERNEL_LIST_H
#include "stdint.h"

#define offset(struct_type,member) (int) (&((struct_type*)0)->member)          //不是很理解这里写的是什么
#define elem2entry(struct_type,struct_member_name,elem_ptr) \
	(struct_type*)((int)elem_ptr - offset(struct_type,struct_member_name)) //也不是很理解这里 后面会介绍
	
struct list_elem
{
    struct list_elem* prev; //前面的节点
    struct list_elem* next; //后面的节点
};

struct list
{
    struct list_elem head; // 亘古不变的头部
    struct list_elem tail; // 亘古不变的尾部
};

typedef bool (function) (struct list_elem*,int arg);

void list_init(struct list*);
void list_insert_before(struct list_elem* before,struct list_elem* elem);
void list_push(struct list* plist,struct list_elem* elem);
void list_append(struct list* plist,struct list_elem* elem);
void list_remove(struct list_elem* pelem);
struct list_elem* list_pop(struct list* plist);
bool list_empty(struct list* plist);
uint32_t list_len(struct list* plist);
struct list_elem* list_traversal(struct list* plist,function func,int arg);
bool elem_find(struct list* plist,struct list_elem* obj_elem);

#endif

编写list.c


#include "list.h"
#include "interrupt.h"
#include "stdint.h"
#include "debug.h"

#define NULL 0

//初始化双向链表
void list_init(struct list* list)
{
    list->head.prev = NULL;
    list->head.next = &list->tail;
    list->tail.prev = &list->head;
    list->tail.next = NULL;
}

//把链表 elem放在 before前面
void list_insert_before(struct list_elem* before,struct list_elem* elem)
{
    enum intr_status old_status = intr_disable();
       
    elem->next = before;
    elem->prev = before->prev;
    before->prev->next = elem;
    before->prev = elem;
    
    intr_set_status(old_status); 
    
}

//添加元素到链表队首
void list_push(struct list* plist,struct list_elem* elem)
{
    list_insert_before(plist->head.next,elem);
}

//添加元素到链表队尾
void list_append(struct list* plist,struct list_elem* elem)
{
    list_insert_before(&plist->tail,elem);
}

//让pelem脱离链表
void list_remove(struct list_elem* pelem)
{
    enum intr_status old_status = intr_disable();
    
    pelem->prev->next = pelem->next;
    pelem->next->prev = pelem->prev;
    
    intr_set_status(old_status);
}

//让链表的第一个元素脱离链表
struct list_elem* list_pop(struct list* plist)
{
    ASSERT(plist->head.next != &plist->tail);
    struct list_elem* ret = plist->head.next;
    list_remove(plist->head.next);
    return ret;
}

bool list_empty(struct list* plist)
{
    return (plist->head.next == &plist->tail ? true : false);
}

uint32_t list_len(struct list* plist)
{
    uint32_t ret = 0;
    struct list_elem* next = plist->head.next;
    while(next != &plist->tail)
    {
    	next = next->next;
    	++ret;
    }
    return ret;
}

struct list_elem* list_traversal(struct list* plist,function func,int arg)
{
    struct list_elem* elem = plist->head.next;
    if(list_empty(plist))	return NULL;
    while(elem != &plist->tail)
    {
    	if(func(elem,arg))	return elem;
    	elem = elem->next;
    }
    return NULL;
}

bool elem_find(struct list* plist,struct list_elem* obj_elem)
{
    struct list_elem* ptr = plist->head.next;
    while(ptr != &plist->tail)
    {
    	if(ptr == obj_elem)	return true; 
    	ptr = ptr->next;
    }
    return false;
}

实现线程 编写修改相关文件


这部分确实太折磨了 细节确实很多 我调试也调试了很久
因为我写代码对于自己有思考的地方 我就会按照自己的思路来写 没有按照书上的一摸一样的去copy 所以就会导致很多地方出错 很多小细节出错

这章节至少最后实现效果看样子是正确的 而且我也修改了很多的文件
我还是写一下大概思路究竟是怎么切换线程的

1、先创建线程
2、打开中断 每个时钟中断调用中断函数 减去当前时间片
3、时间片为0 简称到期了 到期之后 调用schedule调度器 切换线程
4、schedule 把在最前面的准备队列的任务的pcb获取 把当前的放到最后
5、之后转到switch_to 保存寄存器 上下文环境 切换esp 即切换线程

在细节点 说一下pcb的存储
main主线程的本来pcb的内存空间就是留了的 这部分的问题在上面已经说过了 分配了一页内存后 pcb的核心部分在最低端的内存处 最高处是中断栈 就是存放各种寄存器的位置 最高处的下面就是内核栈 里面有四个我们相关的寄存器 还有一个esp的位置 剩下的就是第一次被切换所需要的内容了

感觉也没什么好说的了 整体理解还是我认为不是那么难 总览视角看起来还是比较容易的 拉一边即可
晚上朋友又不在 又没办法玩游戏 那这样的话 我就再多写一点博客吧
下面把我的代码贴出来


thread.c


#include "thread.h"   //函数声明 各种结构体
#include "stdint.h"   //前缀
#include "string.h"   //memset
#include "global.h"   //不清楚
#include "memory.h"   //分配页需要
#include "debug.h"
#include "interrupt.h"
#include "print.h"

#define PG_SIZE 4096

struct task_struct* main_thread;                        //主线程main_thread的pcb
struct list thread_ready_list;			  //就绪队列
struct list thread_all_list;				  //总线程队列

extern void switch_to(struct task_struct* cur,struct task_struct* next);

// 获取 pcb 指针
// 这部分我可以来稍微解释一下
// 我们线程所在的esp 肯定是在 我们get得到的那一页内存 pcb页上下浮动 但是我们的pcb的最起始位置是整数的 除去后面的12位
// 那么我们对前面的取 & 则可以得到 我们的地址所在地
struct task_struct* running_thread(void)
{
    uint32_t esp;
    asm ("mov %%esp,%0" : "=g"(esp));
    return (struct task_struct*)(esp & 0xfffff000);
}


void kernel_thread(thread_func* function,void* func_arg)
{
    intr_enable();					    //开中断 防止后面的时间中断被屏蔽无法切换线程
    function(func_arg);
}

void thread_create(struct task_struct* pthread,thread_func function,void* func_arg)
{
    pthread->self_kstack -= sizeof(struct intr_struct);  //减去中断栈的空间
    pthread->self_kstack -= sizeof(struct thread_stack);
    struct thread_stack* kthread_stack = (struct thread_stack*)pthread->self_kstack; 
    kthread_stack->eip = kernel_thread;                 //地址为kernel_thread 由kernel_thread 执行function
    kthread_stack->function = function;
    kthread_stack->func_arg = func_arg;
    kthread_stack->ebp = kthread_stack->ebx = kthread_stack->ebx = kthread_stack->esi = 0; //初始化一下
    return;
}

void init_thread(struct task_struct* pthread,char* name,int prio)
{
    memset(pthread,0,sizeof(*pthread)); //pcb位置清0
    strcpy(pthread->name,name);
    
    if(pthread == main_thread)
    	pthread->status = TASK_RUNNING;                              //我们的主线程肯定是在运行的
    else
    	pthread->status = TASK_READY;					//放到就绪队列里面
    	
    pthread->self_kstack = (uint32_t*)((uint32_t)pthread + PG_SIZE); //刚开始的位置是最低位置 栈顶位置+一页得最顶部
                                                                     //后面还要对这个值进行修改
                                                                 
    pthread->priority = prio;                                        
    pthread->ticks = prio;                                           //和特权级 相同的时间片
    pthread->elapsed_ticks = 0;
    pthread->pgdir = NULL;                                           //线程没有单独的地址
    pthread->stack_magic = 0x23333333;                               //设置的魔数 检测是否越界限
}

struct task_struct* thread_start(char* name,int prio,thread_func function,void* func_arg)
{
    struct task_struct* thread = get_kernel_pages(1);
    init_thread(thread,name,prio);
    thread_create(thread,function,func_arg);
    ASSERT(!elem_find(&thread_ready_list,&thread->general_tag));     //之前不应该在就绪队列里面
    list_append(&thread_ready_list,&thread->general_tag);
    
    ASSERT(!elem_find(&thread_all_list,&thread->all_list_tag));
    list_append(&thread_all_list,&thread->all_list_tag);
     
    return thread;
}


//之前在loader.S的时候已经 mov esp,0xc0009f00
//现在的esp已经就在预留的pcb位置上了
void make_main_thread(void)
{		
    main_thread = running_thread();					//得到main_thread 的pcb指针
    init_thread(main_thread,"main",31);				
    
    ASSERT(!elem_find(&thread_all_list,&main_thread->all_list_tag));
    list_append(&thread_all_list,&main_thread->all_list_tag);
    
}

void schedule(void)
{
    ASSERT(intr_get_status() == INTR_OFF);
    
    struct task_struct* cur = running_thread();			//得到当前pcb的地址
    if(cur->status == TASK_RUNNING)
    {
    	ASSERT(!elem_find(&thread_ready_list,&cur->general_tag));     //目前在运行的肯定ready_list是不在的
    	list_append(&thread_ready_list,&cur->general_tag);           //加入尾部
    	
    	cur->status = TASK_READY;
    	cur->ticks = cur->priority;
    }
    else
    {}
    
    ASSERT(!list_empty(&thread_ready_list));
    struct task_struct* thread_tag = list_pop(&thread_ready_list);
    //书上面的有点难理解 代码我写了一个我能理解的
    struct task_struct* next = (struct task_struct*)((uint32_t)thread_tag & 0xfffff000);
    next->status = TASK_RUNNING;
    switch_to(cur,next);                                              //esp头顶的是 返回地址 +12是next +8是cur
}

void thread_init(void)
{
    put_str("thread_init start!\n");
    list_init(&thread_ready_list);
    list_init(&thread_all_list);
    make_main_thread();
    put_str("thread_init done!\n");
}

thread.h


#ifndef __THREAD_THREAD_H
#define __THREAD_THREAD_H
#include "stdint.h"
#include "list.h"

typedef void thread_func(void*); //这里有点不懂定义的什么意思 搜了搜博客 发现是函数声明 
                          
enum task_status
{
    TASK_RUNNING, // 0
    TASK_READY,   // 1
    TASK_BLOCKED, // 2
    TASK_WAITING, // 3
    TASK_HANGING, // 4
    TASK_DIED     // 5
};

/*         intr_stack 用于处理中断被切换的上下文环境储存 */
/*	   这里我又去查了一下 为什么是反着的 越在后面的参数 地址越高 */

struct intr_struct
{
    uint32_t vec_no; //中断号
    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;
};


/*	线程栈 保护线程环境 */

struct thread_stack
{
    uint32_t ebp;
    uint32_t ebx;
    uint32_t edi;
    uint32_t esi;

    void (*eip) (thread_func* func,void* func_arg); //和下面的相互照应 以ret 汇编代码进入kernel_thread函数调用
    
    void (*unused_retaddr);                         //占位数 在栈顶站住了返回地址的位置 因为是汇编ret 
    thread_func* function;                          //进入kernel_thread要调用的函数地址
    void* func_arg;				      //参数指针
};

struct task_struct
{
    uint32_t* self_kstack;                          //pcb中的 kernel_stack 内核栈
    enum task_status status;                        //线程状态
    uint8_t priority;				      //特权级
    uint8_t ticks;				      //在cpu 运行的滴答数 看ticks 来判断是否用完了时间片
    uint32_t elapsed_ticks;                         //一共执行了多久
    char name[16];
    
    struct list_elem general_tag;                   //就绪队列中的连接节点
    struct list_elem all_list_tag;		      //总队列的连接节点
    
    uint32_t* pgdir;				      //进程自己页表的虚拟地址 线程没有                  
    uint32_t stack_magic;			      //越界检查  因为我们pcb上面的就是我们要用的栈了 到时候还要越界检查
};

struct task_struct* running_thread(void);
void kernel_thread(thread_func* function,void* func_arg);
void thread_create(struct task_struct* pthread,thread_func function,void* func_arg);
void init_thread(struct task_struct* pthread,char* name,int prio);
struct task_struct* thread_start(char* name,int prio,thread_func function,void* func_arg);
void make_main_thread(void);
void schedule(void);
void thread_init(void);

#endif

interrupt.c


#include "interrupt.h"
#include "stdint.h"
#include "global.h"
#include "io.h"
#include "print.h"

#define PIC_M_CTRL 0x20	       // 这里用的可编程中断控制器是8259A,主片的控制端口是0x20
#define PIC_M_DATA 0x21	       // 主片的数据端口是0x21
#define PIC_S_CTRL 0xa0	       // 从片的控制端口是0xa0
#define PIC_S_DATA 0xa1	       // 从片的数据端口是0xa1

#define IDT_DESC_CNT 0x21      // 目前总共支持的中断数

#define EFLAGS_IF   0x00000200       // eflags寄存器中的if位为1
#define GET_EFLAGS(EFLAG_VAR) asm volatile("pushfl; popl %0" : "=g" (EFLAG_VAR))


/*中断门描述符结构体*/
struct gate_desc {
   uint16_t    func_offset_low_word;
   uint16_t    selector;
   uint8_t     dcount;   //此项为双字计数字段,是门描述符中的第4字节。此项固定值,不用考虑
   uint8_t     attribute;
   uint16_t    func_offset_high_word;
};

// 静态函数声明,非必须
static void pic_init(void);
static void make_idt_desc(struct gate_desc* p_gdesc, uint8_t attr, intr_handler function);
static void general_intr_handler(uint8_t vec_nr);
static void exception_init(void);
void idt_init(void);
void register_handler(uint8_t vec_no,intr_handler function);

static struct gate_desc idt[IDT_DESC_CNT];   // idt是中断描述符表,本质上就是个中断门描述符数组
void register_handler(uint8_t vec_no,intr_handler function);


char* intr_name[IDT_DESC_CNT];		     // 用于保存异常的名字


/********    定义中断处理程序数组    ********
 * 在kernel.S中定义的intrXXentry只是中断处理程序的入口,
 * 最终调用的是ide_table中的处理程序*/
intr_handler idt_table[IDT_DESC_CNT];

/********************************************/
extern intr_handler intr_entry_table[IDT_DESC_CNT];	    // 声明引用定义在kernel.S中的中断处理函数入口数组





/* 初始化可编程中断控制器8259A */
static void pic_init(void) {

   /* 初始化主片 */
   outb (PIC_M_CTRL, 0x11);   // ICW1: 边沿触发,级联8259, 需要ICW4.
   outb (PIC_M_DATA, 0x20);   // ICW2: 起始中断向量号为0x20,也就是IR[0-7] 为 0x20 ~ 0x27.
   outb (PIC_M_DATA, 0x04);   // ICW3: IR2接从片. 
   outb (PIC_M_DATA, 0x01);   // ICW4: 8086模式, 正常EOI

   /* 初始化从片 */
   outb (PIC_S_CTRL, 0x11);    // ICW1: 边沿触发,级联8259, 需要ICW4.
   outb (PIC_S_DATA, 0x28);    // ICW2: 起始中断向量号为0x28,也就是IR[8-15] 为 0x28 ~ 0x2F.
   outb (PIC_S_DATA, 0x02);    // ICW3: 设置从片连接到主片的IR2引脚
   outb (PIC_S_DATA, 0x01);    // ICW4: 8086模式, 正常EOI
   
   outb (PIC_M_DATA, 0xfe);
   outb (PIC_S_DATA, 0xff);

   put_str("   pic_init done\n");
}

/* 创建中断门描述符 */
static void make_idt_desc(struct gate_desc* p_gdesc, uint8_t attr, intr_handler function) { 
   p_gdesc->func_offset_low_word = (uint32_t)function & 0x0000FFFF;
   p_gdesc->selector = SELECTOR_K_CODE;
   p_gdesc->dcount = 0;
   p_gdesc->attribute = attr;
   p_gdesc->func_offset_high_word = ((uint32_t)function & 0xFFFF0000) >> 16;
}

/*初始化中断描述符表*/
static void idt_desc_init(void) {
   int i, lastindex = IDT_DESC_CNT - 1;
   for (i = 0; i < IDT_DESC_CNT; i++) {
      make_idt_desc(&idt[i], IDT_DESC_ATTR_DPL0, intr_entry_table[i]); 
   }
/* 单独处理系统调用,系统调用对应的中断门dpl为3,
 * 中断处理程序为单独的syscall_handler */
   put_str("   idt_desc_init done\n");
}



/* 通用的中断处理函数,一般用在异常出现时的处理 */
static void general_intr_handler(uint8_t vec_nr) {
   if (vec_nr == 0x27 || vec_nr == 0x2f) {	// 0x2f是从片8259A上的最后一个irq引脚,保留
      return;		//IRQ7和IRQ15会产生伪中断(spurious interrupt),无须处理。
   }
   set_cursor(0);                                         //光标设置在0号位
   int cursor_pos = 0;
   while((cursor_pos++) < 320)			    //一行80字 4行空格
   	put_char(' ');			
   
   set_cursor(0);
   put_str("!!!!!!            excetion message begin            !!!!!!\n");
   set_cursor(88);					    //第二行第八个字开始打印
   put_str(intr_name[vec_nr]);                            //打印中断向量号
   if(vec_nr == 14)
   {
   	int page_fault_vaddr = 0;
   	asm("movl %%cr2,%0" : "=r" (page_fault_vaddr));   //把虚拟地址 出错的放到了这个变量里面
	put_str("\npage fault addr is ");
	put_int(page_fault_vaddr);
   }
   put_str("!!!!!!            excetion message end              !!!!!!\n");
   
   while(1);                                              //悬停
}


/* 完成一般中断处理函数注册及异常名称注册 */
static void exception_init(void) {			    // 完成一般中断处理函数注册及异常名称注册
   int i;
   for (i = 0; i < IDT_DESC_CNT; i++) {

/* idt_table数组中的函数是在进入中断后根据中断向量号调用的,
 * 见kernel/kernel.S的call [idt_table + %1*4] */
      idt_table[i] = general_intr_handler;		    // 默认为general_intr_handler。
							    // 以后会由register_handler来注册具体处理函数。
      intr_name[i] = "unknown";				    // 先统一赋值为unknown 
   }
   intr_name[0] = "#DE Divide Error";
   intr_name[1] = "#DB Debug Exception";
   intr_name[2] = "NMI Interrupt";
   intr_name[3] = "#BP Breakpoint Exception";
   intr_name[4] = "#OF Overflow Exception";
   intr_name[5] = "#BR BOUND Range Exceeded Exception";
   intr_name[6] = "#UD Invalid Opcode Exception";
   intr_name[7] = "#NM Device Not Available Exception";
   intr_name[8] = "#DF Double Fault Exception";
   intr_name[9] = "Coprocessor Segment Overrun";
   intr_name[10] = "#TS Invalid TSS Exception";
   intr_name[11] = "#NP Segment Not Present";
   intr_name[12] = "#SS Stack Fault Exception";
   intr_name[13] = "#GP General Protection Exception";
   intr_name[14] = "#PF Page-Fault Exception";
   // intr_name[15] 第15项是intel保留项,未使用
   intr_name[16] = "#MF x87 FPU Floating-Point Error";
   intr_name[17] = "#AC Alignment Check Exception";
   intr_name[18] = "#MC Machine-Check Exception";
   intr_name[19] = "#XF SIMD Floating-Point Exception";

}


/*完成有关中断的所有初始化工作*/
void idt_init() {
   put_str("idt_init start\n");
   idt_desc_init();	   // 初始化中断描述符表
   exception_init();	   // 异常名初始化并注册通常的中断处理函数
   pic_init();		   // 初始化8259A

   /* 加载idt */
   uint64_t idt_operand = ((sizeof(idt) - 1) | ((uint64_t)(uint32_t)idt << 16));
   asm volatile("lidt %0" : : "m" (idt_operand));
   put_str("idt_init done\n");
}

void register_handler(uint8_t vec_no,intr_handler function)
{
    //把相关向量号的注册函数指针放进去了
    idt_table[vec_no] = function;
}

enum intr_status intr_enable()
{
    if(intr_get_status() != INTR_ON)
    {
    	asm volatile("sti");
    	return INTR_OFF;
    }
    return INTR_ON;
}

enum intr_status intr_disable()
{
    if(intr_get_status() != INTR_OFF)
    {
   	asm volatile("cli");
   	return INTR_ON;
    }
    return INTR_OFF;
}

enum intr_status intr_set_status(enum intr_status status)
{
    return (status & INTR_ON) ? intr_enable() : intr_disable();
}

enum intr_status intr_get_status()
{
    uint32_t eflags = 0;
    GET_EFLAGS(eflags);
    return (eflags & EFLAGS_IF) ? INTR_ON : INTR_OFF; 
}

switch.S


[bits 32]
section .text
global switch_to
switch_to:
    push esi            ;这里是根据ABI原则保护四个寄存器 放到栈里面
    push edi
    push ebx
    push ebp
    
    mov eax,[esp+20]    ;esp+20的位置是cur cur的pcb赋值给eax
    mov [eax],esp       ;[eax]为pcb的内核栈指针变量 把当前环境的esp值记录下来
    
    mov eax,[esp+24]
    mov esp,[eax]       ;把要切换的线程的pcb 内核栈esp取出来

    pop ebp
    pop ebx
    pop edi
    pop esi
    ret                 ;这里的返回地址为 kernel_thread的地址

稍加修改过的print.S


为了用set_cursor 修改了一下 把set_cursor变成可以调用的函数了

[bits 32]
section .text
global switch_to
switch_to:
    push esi            ;这里是根据ABI原则保护四个寄存器 放到栈里面
    push edi
    push ebx
    push ebp
    
    mov eax,[esp+20]    ;esp+20的位置是cur cur的pcb赋值给eax
    mov [eax],esp       ;[eax]为pcb的内核栈指针变量 把当前环境的esp值记录下来
    
    mov eax,[esp+24]
    mov esp,[eax]       ;把要切换的线程的pcb 内核栈esp取出来

    pop ebp
    pop ebx
    pop edi
    pop esi
    ret                 ;这里的返回地址为 kernel_thread的地址

稍微修改了一下的stdint.h


发现bool 经常会出错 而且true false报错 就这里加了一下

#ifndef __LIB_STDINT_H
#define __LIB_STDINT_H

#define true 1
#define false 0
typedef int bool;
typedef signed char int8_t;
typedef signed short int int16_t;
typedef signed int int32_t;
typedef signed long long int int64_t;
typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;
typedef unsigned int uint32_t;
typedef unsigned long long int uint64_t;
#endif

timer.c


#include "timer.h"
#include "io.h"
#include "print.h"
#include "../kernel/interrupt.h"
#include "../thread/thread.h"
#include "debug.h"


#define IRQ0_FREQUENCY 	100
#define INPUT_FREQUENCY        1193180
#define COUNTER0_VALUE		INPUT_FREQUENCY / IRQ0_FREQUENCY
#define COUNTER0_PORT		0X40
#define COUNTER0_NO 		0
#define COUNTER_MODE		2
#define READ_WRITE_LATCH	3
#define PIT_COUNTROL_PORT	0x43

//自中断开启以来总的滴答数
uint32_t ticks;

void frequency_set(uint8_t counter_port ,uint8_t counter_no,uint8_t rwl,uint8_t counter_mode,uint16_t counter_value)
{
    outb(PIT_COUNTROL_PORT,(uint8_t) (counter_no << 6 | rwl << 4 | counter_mode << 1));
    outb(counter_port,(uint8_t)counter_value);
    outb(counter_port,(uint8_t)counter_value >> 8);
    return;
} 

void intr_timer_handler(void)
{
    struct task_struct* cur_thread = running_thread();   //得到pcb指针
    ASSERT(cur_thread->stack_magic == 0x23333333);	       //检测栈是否溢出
    
    ++ticks;
    ++cur_thread->elapsed_ticks;
    if(!cur_thread->ticks)
    	schedule();
    else    
    	--cur_thread->ticks;
    return;
}

void timer_init(void)
{
    put_str("timer_init start!\n");
    frequency_set(COUNTER0_PORT,COUNTER0_NO,READ_WRITE_LATCH,COUNTER_MODE,COUNTER0_VALUE);
    register_handler(0x20,intr_timer_handler);	        //注册时间中断函数 0x20向量号函数更换
    put_str("timer_init done!\n");
    return;
}


timer.h


#ifndef __DEVICE_TIME_H
#define __DEVICE_TIME_H
#include "stdint.h"
void frequency_set(uint8_t counter_port ,uint8_t counter_no,uint8_t rwl,uint8_t counter_mode,uint16_t counter_value);
void intr_timer_handler(void);
void timer_init(void);
void mtime_sleep(uint32_t m_seconds);
#endif

MakeFile


BUILD_DIR = ./build
ENTRY_POINT = 0xc0001500
AS = nasm
CC = gcc
LD = ld
LIB = -I lib/ -I lib/kernel/ -I lib/user/ -I kernel/ -I device/ 
ASFLAGS = -f elf
CFLAGS = -Wall -m32 -fno-stack-protector $(LIB) -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes
LDFLAGS =  -m elf_i386 -Ttext $(ENTRY_POINT) -e main -Map $(BUILD_DIR)/kernel.map
OBJS = $(BUILD_DIR)/main.o $(BUILD_DIR)/init.o $(BUILD_DIR)/interrupt.o \
      $(BUILD_DIR)/timer.o $(BUILD_DIR)/kernel.o $(BUILD_DIR)/print.o \
      $(BUILD_DIR)/debug.o $(BUILD_DIR)/string.o $(BUILD_DIR)/memory.o \
      $(BUILD_DIR)/bitmap.o $(BUILD_DIR)/thread.o $(BUILD_DIR)/list.o $(BUILD_DIR)/switch.o
      
##############     c代码编译     ###############
$(BUILD_DIR)/main.o: kernel/main.c lib/kernel/print.h \
        lib/stdint.h kernel/init.h lib/string.h kernel/memory.h \
        thread/thread.h kernel/interrupt.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/init.o: kernel/init.c kernel/init.h lib/kernel/print.h \
        lib/stdint.h kernel/interrupt.h device/timer.h kernel/memory.h \
        thread/thread.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/interrupt.o: kernel/interrupt.c kernel/interrupt.h \
        lib/stdint.h kernel/global.h lib/kernel/io.h lib/kernel/print.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/timer.o: device/timer.c device/timer.h lib/kernel/io.h lib/kernel/print.h \
        kernel/interrupt.h thread/thread.h kernel/debug.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/debug.o: kernel/debug.c kernel/debug.h \
        lib/kernel/print.h lib/stdint.h kernel/interrupt.h
	$(CC) $(CFLAGS) $< -o $@
	
$(BUILD_DIR)/string.o: lib/string.c lib/string.h \
	kernel/debug.h kernel/global.h
	$(CC) $(CFLAGS) $< -o $@
	
$(BUILD_DIR)/memory.o: kernel/memory.c kernel/memory.h \
	lib/stdint.h lib/kernel/bitmap.h kernel/debug.h lib/string.h
	$(CC) $(CFLAGS) $< -o $@
	
$(BUILD_DIR)/bitmap.o: lib/kernel/bitmap.c lib/kernel/bitmap.h kernel/global.h \
	lib/string.h kernel/interrupt.h lib/kernel/print.h kernel/debug.h
	$(CC) $(CFLAGS) $< -o $@
	
$(BUILD_DIR)/thread.o: thread/thread.c thread/thread.h \
	lib/stdint.h lib/string.h kernel/global.h kernel/memory.h \
	kernel/debug.h kernel/interrupt.h lib/kernel/print.h
	$(CC) $(CFLAGS) $< -o $@
	
$(BUILD_DIR)/list.o: lib/kernel/list.c lib/kernel/list.o \
	kernel/interrupt.h lib/stdint.h kernel/debug.h
	$(CC) $(CFLAGS) $< -o $@
	
##############    汇编代码编译    ###############
$(BUILD_DIR)/kernel.o: kernel/kernel.S
	$(AS) $(ASFLAGS) $< -o $@

$(BUILD_DIR)/print.o: lib/kernel/print.S
	$(AS) $(ASFLAGS) $< -o $@

$(BUILD_DIR)/switch.o: thread/switch.S
	$(AS) $(ASFLAGS) $< -o $@

##############    链接所有目标文件    #############
$(BUILD_DIR)/kernel.bin: $(OBJS)
	$(LD) $(LDFLAGS) $^ -o $@

.PHONY : mk_dir hd clean all

mk_dir:
	if [ ! -d $(BUILD_DIR) ]; then mkdir $(BUILD_DIR); fi

hd:
	dd if=$(BUILD_DIR)/kernel.bin \
           of=/home/cooiboi/bochs/hd60M.img \
           bs=512 count=200 seek=9 conv=notrunc

clean:
	cd $(BUILD_DIR) && rm -f  ./*

build: $(BUILD_DIR)/kernel.bin

all: mk_dir build hd

最终实现效果


不知道是不是因为字符串的原因 动不动就会直接报错 感觉可能就是因为同步的问题 最后这里直接会卡死 希望应该后面一章的锁给做了 这里就不会报错了
如果不是的话 我脆弱的心脏哪天出事的话 我都不会感觉到意外 做操作系统心脏真的得好啊
先写到这里 吃晚饭去了 咕~

在这里插入图片描述

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2021-07-28 08:14:35  更:2021-07-28 08:14:46 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/7 11:42:53-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码