当前位置:   article > 正文

《操作系统真象还原》第十章 ---- 线程打印尚未成功 仍需此章锁机制完善努力 在前往最终章的路上激流勇进

《操作系统真象还原》第十章 ---- 线程打印尚未成功 仍需此章锁机制完善努力 在前往最终章的路上激流勇进


专栏博客链接


《操作系统真象还原》从零开始自制操作系统 全章节博客链接


相关查阅博客链接



本书中错误勘误


1、keyboard.c define delete '\177'八进制表示应该是define delete '\0177'
2、keyboard.c {']','}'} 应该是这个 而不是 {'}','}'}
3、在最后的 环形缓冲区 生产者消费者 还需要修改pic_init


部分缩写熟知


下面是荷兰语中的P V 介绍 原来查了半天P V的英文缩写 硬是查不到
P Proberen 减少
V Verhogen 增加
semaphore 信号量


闲聊时刻


不知不觉中 《操作系统真象还原》已经来到了第十章了
我写这个博客的时候 其实我的上午是做了五道 Leetcode 精选 TOP 面试题 中的easy题目 中午倍感疲倦赶快跑回寝室 美美的先和室友点了外卖吃饱喝足 后躺在床上玩了半个小时手机又睡了一个小时才起来 之后又跑出来来写的这篇博客

我这段时间感觉睡眠质量一直都不是很好 我真的觉得很大一部分原因就是因为做操作系统 因为我觉得做这个东西是需要一股劲一口气给做完的 而不是说今天做一点点 后面歇个几天再来做 这样子的话肯定到最后 这个庞大而繁琐的任务只会不疾而终

心里面装着事情 身后有压力 自然而然很多时候就会醒 有的时候我觉得 暑假期间本来就是一个拿出来休息的时间 我还每天早上7点自然而然就醒了 这好吗 但有的时候又觉得自己这样还是挺好的 至少心里面有事情的时候 自己还睡得不会这么死 哈哈 开个玩笑

为什么这里想聊一下天呢 其实也没别的多余的想法 哈哈 就是在感慨 我也没有想到我还有勇气与毅力写到这里 看之后的时间里面自己能不能把这十五章给写完吧 但是我觉得呢 我既然都写到了第十章 前面的铺垫都做了那么多了 这个系列不写完是不是就太对不起自己了 凡事都讲究一个有始有终

当然 我写的代码里面难免会有那么几个小错误 毕竟体量那么庞大 在不影响主要功能的情况下稍微有那么一点点错误 还是可以原谅我一下了

我觉得 把操作系统真象还原的十五章我给写完了 真的还是挺敬佩我自己的 之后可能再遇到其他的事情 自己都会有勇气与毅力去完成了
几千行代码 各种小问题 版本问题 编译器问题 细节问题 一调试动不动就是几个小时 这样子都完成了 之后还会有什么事情是完不成的 哈哈 挺好的 原谅我的夸夸其谈 寥寥自语了 闲聊就先写到这里


稍加修改的main.c


还记得上一章 我写到最后 发出来卑微的恳求 希望最后的报错不是因为我前面的内存分配 不是因为其中的代码出现问题 而是真的因为同步问题出的错吗 说实话 内存分配要是出错了 我觉得那个地方我调试起来真的可能只会选择放弃 但这里我们先按照书上的解决办法 :在put_str前后增加intr_disable()防止put_str调用时就直接被调度器切换而导致的同步出错 我认为打印函数就应该是原子操作

各位看官 main.c代码如下

#include "print.h"
#include "init.h"
#include "debug.h"
#include "string.h"
#include "memory.h"
#include "../thread/thread.h"
#include "interrupt.h"

void test_thread1(void* arg);
void test_thread2(void* arg);

int main(void) {
   put_str("I am kernel\n");
   init_all();
   thread_start("kernel_thread_a",31,test_thread1,"Arga ");
   thread_start("kernel_thread_b",31,test_thread2,"Argb ");
   intr_enable();
   
   while(1)
   {
   	intr_disable();
   	put_str("Main ");
   	intr_enable();
   }
   return 0;
}

void test_thread1(void* arg)
{
    while(1)
    {
    	intr_disable();
    	put_str((char*)arg);
    	intr_enable();
    }
}

void test_thread2(void* arg)
{
    while(1)
    {
    	intr_disable();
    	put_str((char*)arg);
    	intr_enable();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

make all一下 上天保佑不会再出现上一章最后的错误
成了 兄弟们 哦耶 没有出错! 在我至少等待运行了15秒之后 屏幕上都持续在输出字符串
这可让我一颗悬着的心 尘埃落地了啊 至少从目前来看 之前的线程切换 应该是没有什么大的问题! 哦耶~
****


看锁理论介绍时 说点想聊的


这里是我看到的 刚哥在给出锁信号量代码之前的理论部分讲解
其实我说实话 我还在看《现代操作系统》的时候 那个时候别谈什么 进程 线程 同步锁了 分页分段 那简直是天书奇谈 但是那个时候我看不下去也得看啊 那看不下去能咋的 能不能给操作系统拿着刀卡住操作系统的脖子说 你咋个这么难啊! 显然不行的 那个时候很多东西真的都觉得很高大上

但是做过了 操作系统的部分实验 做这个操作系统之前我还是完成了所有的哈工大操作系统Lab 才有勇气过来做的 那个时候我还记得 是要求自己实现一个信号量 我也自己做了一个 所以我看到这一章的时候心里面不是很紧张很有压力 毕竟原来接触过 自然自来熟 而且也觉得格外亲近

操作系统绝不可能是背书背出来的 是靠写出来的
我又想起来了 哈工大李治军老师说的一句 我记忆很深刻的话
纸上得来终觉浅,绝知此事要躬行 在计算机这个学科更是这样

好了好了 不说了 怎么今天想说这么多话 哈哈 继续往下看了


修改增添thread.c


由于我们后面的信号量 同时访问一个锁时 需要先阻塞后来的线程
这里就需要我们先把阻塞和恢复的功能先弄出来 其实这是我后面编辑的博客了 此时的我进度是在已经把终端输出给搞定了 大概花了两个小时调试锁机制 终于找到问题调试出来了 这部分后面再说 先把新增的两个函数摆出来


编写thread_block函数


我写的代码都是有注释的 配合着注释应该就能看懂了

void thread_block(enum task_status stat)
{
    //设置block状态的参数必须是下面三个以下的
    ASSERT(((stat == TASK_BLOCKED) || (stat == TASK_WAITING) || stat == TASK_HANGING));
    
    enum intr_status old_status = intr_disable();			 //关中断
    struct task_struct* cur_thread = running_thread();		 
    cur_thread->status = stat;					 //把状态重新设置
    
    //调度器切换其他进程了 而且由于status不是running 不会再被放到就绪队列中
    schedule();	
    				
    //被切换回来之后再进行的指令了
    intr_set_status(old_status);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

编写thread_unlock函数


//由锁拥有者来执行的 善良者把原来自我阻塞的线程重新放到队列中
void thread_unblock(struct task_struct* pthread)
{
    enum intr_status old_status = intr_disable();
    ASSERT(((pthread->status == TASK_BLOCKED) || (pthread->status == TASK_WAITING) || (pthread->status == TASK_HANGING)));
    if(pthread->status != TASK_READY)
    {
    	//被阻塞线程 不应该存在于就绪队列中)
    	ASSERT(!elem_find(&thread_ready_list,&pthread->general_tag));
    	if(elem_find(&thread_ready_list,&pthread->general_tag))
    	    PANIC("thread_unblock: blocked thread in ready_list\n"); //debug.h中定义过
    	
    	//让阻塞了很久的任务放在就绪队列最前面
    	list_push(&thread_ready_list,&pthread->general_tag);
    	
    	//状态改为就绪态
    	pthread->status = TASK_READY;
    }
    intr_set_status(old_status);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

编写semaphore


这部分还是相当的轻车熟路 除了一个 holder_repeat_nr让我眼前一亮我理解了一下 其他的还是挺常规的 下面给出代码 里面都有注释


编写sync.c


代码如下咯

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

void sema_init(struct semaphore* psema,uint32_t value)
{
    psema->value = value;
    list_init(&psema->waiters);
}

void lock_init(struct lock* plock)
{
    sema_init(&plock->semaphore,1);  //信号量初始值都设置成1 
    plock->holder = NULL;
    plock->holder_repeat_nr = 0;
}

void sema_down(struct semaphore* psema)
{
    //保证原子操作
    enum intr_status old_status = intr_disable();
    while(!psema->value)
    {
    	//访问锁的不应该在锁的等待队列
    	ASSERT(!elem_find(&psema->waiters,&(running_thread()->general_tag)));
    	if(elem_find(&psema->waiters,&(running_thread()->general_tag)))
    	    PANIC("sema_down: seme_down thread already in ready_list\n");
    	list_append(&psema->waiters,&(running_thread()->general_tag));  //添加到等待队列
    	thread_block(TASK_BLOCKED);                                     //阻塞并切换线程
    }
    --psema->value;							  //占用信号量
    ASSERT(!psema->value);	
    intr_set_status(old_status);	
}

//信号量value增加
void sema_up(struct semaphore* psema)
{

    enum intr_status old_status = intr_disable();
    ASSERT(!psema->value);
    if(!list_empty(&psema->waiters))
    {
    	thread_unblock((struct task_struct*)((uint32_t)list_pop(&psema->waiters) & 0xfffff000));
    }
    ++psema->value;
    ASSERT(psema->value == 1);
    intr_set_status(old_status);
}

//获取锁资源
void lock_acquire(struct lock* plock)
{
    if(plock->holder != running_thread())
    {
    	sema_down(&plock->semaphore);		//如果已经被占用 则会被阻塞
    	plock->holder = running_thread();	//之前被阻塞的线程 继续执行 没被阻塞直接继续即可
    	ASSERT(!plock->holder_repeat_nr);
    	plock->holder_repeat_nr = 1;			//访问数为1
    }
    else	++plock->holder_repeat_nr;
}

//释放锁资源
void lock_release(struct lock* plock)
{
    ASSERT(plock->holder == running_thread());	//释放锁的线程必须是其拥有者
    if(plock->holder_repeat_nr > 1)	//减少到的当前一个线程 次数只有一次访问这个锁时 才允许到下面
    {
    	--plock->holder_repeat_nr;		
    	return;
    }
    ASSERT(plock->holder_repeat_nr == 1);	//举个例子 该线程在拥有锁时 两次获取锁 第二次肯定是无法获取到的
    						//但是必须同样也要有两次release才算彻底释放 不然只有第一次的relase
    						//第二次都不需要release 就直接释放了 肯定是不行的
    plock->holder = NULL;
    plock->holder_repeat_nr = 0;
    sema_up(&plock->semaphore);   
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82

编写sync.h


#ifndef __THREAD_SYNC_H
#define __THREAD_SYNC_H
#include "list.h"
#include "stdint.h"
#include "thread.h"

struct semaphore
{
    uint8_t value;	    //信号量的值
    struct list waiters;   //等待队列
};

struct lock
{
    struct task_struct* holder;
    struct semaphore semaphore;
    uint32_t holder_repeat_nr;	//在释放的时候 通过这个值决定需要 释放锁几次
};

//揣测了一下 sema 前面加了个p 应该是ptr 指针的意思
void sema_init(struct semaphore* psema,uint32_t value);
void lock_init(struct lock* plock);
void sema_down(struct semaphore* psema);
void sema_up(struct semaphore* psema);
void lock_acquire(struct lock* plock);
void lock_release(struct lock* plock);
#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

实现终端输出


这部分说到底 只是应用一下信号量 锁机制而已 大家每次调用函数put_str的时候 抢锁 退锁 保证每次调用函数的时候 只有一个线程调用 不会导致公共资源被同时多次修改 但问题肯定也是有的 就是阻塞了线程 势必会导致输出的次序被打乱

下面直接给代码咯


编写console.c


#include "console.h"
#include "print.h"
#include "../thread/sync.h"

struct lock console_lock;

void console_init()
{
    lock_init(&console_lock);
}

void console_acquire()
{
    lock_acquire(&console_lock);
}

void console_release()
{
    lock_release(&console_lock);
}

void console_put_str(char* str)
{
    console_acquire();
    put_str(str);
    console_release();
}

void console_put_int(uint32_t num)
{
    console_acquire();
    put_int(num);
    console_release();
}

void console_put_char(uint8_t chr)
{
    console_acquire();
    put_char(chr);
    console_release();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

编写console.h


#ifndef __DEVICE__CONSOLE_H
#define __DEVICE__CONSOLE_H

#include "console.h"
#include "print.h"
#include "../thread/sync.h"


void console_init(void);
void console_acquire(void);
void console_release(void);
void console_put_str(char* str);
void console_put_int(uint32_t num);
void console_put_char(uint8_t chr);

#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

稍加修改的init.c


#include "init.h"
#include "print.h"
#include "interrupt.h"
#include "../device/timer.h"
#include "memory.h"
#include "../thread/thread.h"
#include "../device/console.h"

/*负责初始化所有模块 */
void init_all() {
   put_str("init_all\n");
   idt_init();	     // 初始化中断
   mem_init();
   timer_init();
   thread_init();
   console_init();
}


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

稍加修改的main.c


#include "print.h"
#include "init.h"
#include "debug.h"
#include "string.h"
#include "memory.h"
#include "../thread/thread.h"
#include "interrupt.h"
#include "../device/console.h"

void test_thread1(void* arg);
void test_thread2(void* arg);

int main(void) {
   put_str("I am kernel\n");
   init_all();
   thread_start("kernel_thread_a",8,test_thread1,"Arga ");
   thread_start("kernel_thread_b",8,test_thread2,"Argb ");
   intr_enable();
   
   while(1)
   {
   	console_put_str("Main ");
   }
   return 0;
}

void test_thread1(void* arg)
{
    while(1)
    {
   	console_put_str((char*)arg);
    }
}

void test_thread2(void* arg)
{
    while(1)
    {
   	console_put_str((char*)arg);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

修改后的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)/switch.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)/sync.o $(BUILD_DIR)/console.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 device/console.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 device/console.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.h \
	kernel/interrupt.h lib/stdint.h kernel/debug.h
	$(CC) $(CFLAGS) $< -o $@
	
$(BUILD_DIR)/sync.o: thread/sync.c thread/sync.h \
	lib/stdint.h thread/thread.h kernel/debug.h kernel/interrupt.h
	$(CC) $(CFLAGS) $< -o $@
	
$(BUILD_DIR)/console.o: device/console.c device/console.h \
	lib/kernel/print.h thread/sync.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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97

make all 验收成果


这里其实还是两条老命令 就能看到成果

make all
bin/bochs -f bochsrc.disk
  • 1
  • 2

这里为什么我单独又要写这个话 因为还是想说一下 写操作系统确实不容易
因为一个符号 我又调试了两个小时才找出来
这里各位看官就当一乐就好
我错在哪里了呢

下面命令的 == 我之前写的是 & 就这样错了 导致后面的中断全部关闭 没法切换schedule() 时钟中断被屏蔽 就是这么呆
但也从另一个角度说明 确实不容易 因为很多地方我都还是按照自己的思路自己写的 不是copy的书上的 还是希望这个操作系统有很多自己的痕迹

enum intr_status intr_set_status(enum intr_status status)
{
    return (status == INTR_ON) ? intr_enable() : intr_disable();
}
  • 1
  • 2
  • 3
  • 4

成果如下 从这里看应该还是ok的 这部分就先写到这里
在这里插入图片描述


初步实现键盘中断处理程序


这里的原理还是挺好懂的 看看书应该就懂了
这里就按照书上的来弄了


修改kernel.S


kernel/kernel.S

[bits 32]
%define ERROR_CODE nop		 ; 若在相关的异常中cpu已经自动压入了错误码,为保持栈中格式统一,这里不做操作.
%define ZERO push 0		 ; 若在相关的异常中cpu没有压入错误码,为了统一栈中格式,就手工压入一个0

extern put_str;
extern idt_table;

section .data
global intr_entry_table
intr_entry_table:

%macro VECTOR 2
section .text
intr%1entry:		 ; 每个中断处理程序都要压入中断向量号,所以一个中断类型一个中断处理程序,自己知道自己的中断向量号是多少

   %2				 ; 中断若有错误码会压在eip后面 
; 以下是保存上下文环境
   push ds
   push es
   push fs
   push gs
   pushad			 ; PUSHAD指令压入32位寄存器,其入栈顺序是: EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI

   ; 如果是从片上进入的中断,除了往从片上发送EOI外,还要往主片上发送EOI 
   mov al,0x20                   ; 中断结束命令EOI
   out 0xa0,al                   ; 向从片发送
   out 0x20,al                   ; 向主片发送

   push %1			 ; 不管idt_table中的目标程序是否需要参数,都一律压入中断向量号,调试时很方便
   call [idt_table + %1*4]       ; 调用idt_table中的C版本中断处理函数
   jmp intr_exit
	
section .data
   dd    intr%1entry	 ; 存储各个中断入口程序的地址,形成intr_entry_table数组
%endmacro

section .text
global intr_exit
intr_exit:	     
; 以下是恢复上下文环境
   add esp, 4			   ; 跳过中断号
   popad
   pop gs
   pop fs
   pop es
   pop ds
   add esp, 4			   ; 跳过error_code
   iretd


VECTOR 0x0 ,ZERO
VECTOR 0X1 ,ZERO
VECTOR 0X2 ,ZERO
VECTOR 0x3 ,ZERO
VECTOR 0X4 ,ZERO
VECTOR 0X5 ,ZERO
VECTOR 0x6 ,ZERO
VECTOR 0X7 ,ZERO
VECTOR 0X8 ,ERROR_CODE
VECTOR 0x9 ,ZERO
VECTOR 0XA ,ERROR_CODE
VECTOR 0XB ,ERROR_CODE
VECTOR 0XC ,ERROR_CODE
VECTOR 0XD ,ERROR_CODE
VECTOR 0XE ,ERROR_CODE
VECTOR 0XF ,ZERO
VECTOR 0X10 ,ZERO
VECTOR 0X11 ,ERROR_CODE
VECTOR 0x12 ,ZERO
VECTOR 0X13 ,ZERO
VECTOR 0X14 ,ZERO
VECTOR 0x15 ,ZERO
VECTOR 0X16 ,ZERO
VECTOR 0X17 ,ZERO
VECTOR 0X18 ,ZERO
VECTOR 0X19 ,ZERO
VECTOR 0X1A ,ZERO
VECTOR 0X1B ,ZERO
VECTOR 0X1C ,ZERO
VECTOR 0X1D ,ZERO
VECTOR 0X1E ,ERROR_CODE                               ;处理器自动推错误码
VECTOR 0X1F ,ZERO					
VECTOR 0X20 ,ZERO					;时钟中断
VECTOR 0X21 ,ZERO					;键盘中断
VECTOR 0X22 ,ZERO					;级联
VECTOR 0X23 ,ZERO					;串口2
VECTOR 0X24 ,ZERO					;串口1
VECTOR 0X25 ,ZERO					;并口2
VECTOR 0X26 ,ZERO					;软盘
VECTOR 0X27 ,ZERO					;并口1
VECTOR 0X28 ,ZERO					;实时时钟
VECTOR 0X29 ,ZERO					;重定向
VECTOR 0X2A ,ZERO					;保留
VECTOR 0x2B ,ZERO					;保留
VECTOR 0x2C ,ZERO					;ps/2 鼠标
VECTOR 0x2D ,ZERO					;fpu 浮点单元异常
VECTOR 0x2E ,ZERO					;硬盘
VECTOR 0x2F ,ZERO					;保留
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98

修改interrupt.c


主要修改这两个地方

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

outb (PIC_M_DATA, 0xfd); //这里修改了 只打开键盘中断
outb (PIC_S_DATA, 0xff);
  • 1
  • 2
  • 3
  • 4

编写keyboard.c


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

#define KBD_BUF_PORT 0X60

void intr_keyboard_handler(void)
{
    put_char('k');
    inb(KBD_BUF_PORT);
    return;
}

void keyboard_init()
{
    put_str("keyboard init start\n");
    register_handler(0x21,intr_keyboard_handler);
    put_str("keyboard init done\n");
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

编写keyboard.h


#ifndef __DEVICE_KEYBOARD_H
#define __DEVICE_KEYBOARD_H

void intr_keyboard_handler(void);
void keyboard_init(void);

#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

修改main.c


#include "print.h"
#include "init.h"
#include "debug.h"
#include "string.h"
#include "memory.h"
#include "../thread/thread.h"
#include "interrupt.h"
#include "../device/console.h"

void test_thread1(void* arg);
void test_thread2(void* arg);

int main(void) {
   put_str("I am kernel\n");
   init_all();
   //thread_start("kernel_thread_a",8,test_thread1,"Arga ");
   //thread_start("kernel_thread_b",8,test_thread2,"Argb ");
   intr_enable();
   
   while(1);
   /*{
   	console_put_str("Main ");
   }*/
   return 0;
}

void test_thread1(void* arg)
{
    while(1)
    {
   	console_put_str((char*)arg);
    }
}

void test_thread2(void* arg)
{
    while(1)
    {
   	console_put_str((char*)arg);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

修改init.c


#include "init.h"
#include "print.h"
#include "interrupt.h"
#include "../device/timer.h"
#include "memory.h"
#include "../thread/thread.h"
#include "../device/console.h"
#include "../device/keyboard.h"

/*负责初始化所有模块 */
void init_all() {
   put_str("init_all\n");
   idt_init();	     // 初始化中断
   mem_init();
   timer_init();
   thread_init();
   console_init();
   keyboard_init();  //新增
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

修改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)/switch.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)/sync.o $(BUILD_DIR)/console.o $(BUILD_DIR)/keyboard.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 device/console.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 device/console.h device/keyboard.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.h \
	kernel/interrupt.h lib/stdint.h kernel/debug.h
	$(CC) $(CFLAGS) $< -o $@
	
$(BUILD_DIR)/sync.o: thread/sync.c thread/sync.h \
	lib/stdint.h thread/thread.h kernel/debug.h kernel/interrupt.h
	$(CC) $(CFLAGS) $< -o $@
	
$(BUILD_DIR)/console.o: device/console.c device/console.h \
	lib/kernel/print.h thread/sync.h
	$(CC) $(CFLAGS) $< -o $@
	
$(BUILD_DIR)/keyboard.o: device/keyboard.c device/keyboard.h \
	lib/kernel/print.h lib/kernel/io.h kernel/interrupt.h \
	kernel/global.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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102

make all 验收成果


这里老套路 两条命令 keyboard.S
书上面写的输出扫描码就懒得弄了 看看效果 走你~
ok 接下来要编写键盘驱动~

在这里插入图片描述


实现键盘驱动软件


这里看看书就ok了 没什么其他好说的
只能说程序确实写得很好 我也是到最后彻底看懂了 再挨个挨个敲代码敲上去 直接放代码看成果吧


编写keyboard.c


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

#define KBD_BUF_PORT 0X60

#define KBD_BUF_PORT 0X60

#define esc '\033'		//esc 和 delete都没有
#define delete '\0177'
#define enter '\r'
#define tab '\t'
#define backspace '\b'

#define char_invisible 0	//功能性 不可见字符均设置为0
#define ctrl_l_char char_invisible
#define ctrl_r_char char_invisible 
#define shift_l_char char_invisible
#define shift_r_char char_invisible
#define alt_l_char char_invisible
#define alt_r_char char_invisible
#define caps_lock_char char_invisible

#define shift_l_make 0x2a
#define shift_r_make 0x36
#define alt_l_make 0x38
#define alt_r_make 0xe038
#define alt_r_break 0xe0b8
#define ctrl_l_make 0x1d
#define ctrl_r_make 0xe01d
#define ctrl_r_break 0xe09d
#define caps_lock_make 0x3a

bool ctrl_status = false,shift_status = false,alt_status = false,caps_lock_status = false,ext_scancode = false;


char keymap[][2] = {
/* 0x00 */	{0,	0},		
/* 0x01 */	{esc,	esc},		
/* 0x02 */	{'1',	'!'},		
/* 0x03 */	{'2',	'@'},		
/* 0x04 */	{'3',	'#'},		
/* 0x05 */	{'4',	'$'},		
/* 0x06 */	{'5',	'%'},		
/* 0x07 */	{'6',	'^'},		
/* 0x08 */	{'7',	'&'},		
/* 0x09 */	{'8',	'*'},		
/* 0x0A */	{'9',	'('},		
/* 0x0B */	{'0',	')'},		
/* 0x0C */	{'-',	'_'},		
/* 0x0D */	{'=',	'+'},		
/* 0x0E */	{backspace, backspace},	
/* 0x0F */	{tab,	tab},		
/* 0x10 */	{'q',	'Q'},		
/* 0x11 */	{'w',	'W'},		
/* 0x12 */	{'e',	'E'},		
/* 0x13 */	{'r',	'R'},		
/* 0x14 */	{'t',	'T'},		
/* 0x15 */	{'y',	'Y'},		
/* 0x16 */	{'u',	'U'},		
/* 0x17 */	{'i',	'I'},		
/* 0x18 */	{'o',	'O'},		
/* 0x19 */	{'p',	'P'},		
/* 0x1A */	{'[',	'{'},		
/* 0x1B */	{']',	'}'},		
/* 0x1C */	{enter,  enter},
/* 0x1D */	{ctrl_l_char, ctrl_l_char},
/* 0x1E */	{'a',	'A'},		
/* 0x1F */	{'s',	'S'},		
/* 0x20 */	{'d',	'D'},		
/* 0x21 */	{'f',	'F'},		
/* 0x22 */	{'g',	'G'},		
/* 0x23 */	{'h',	'H'},		
/* 0x24 */	{'j',	'J'},		
/* 0x25 */	{'k',	'K'},		
/* 0x26 */	{'l',	'L'},		
/* 0x27 */	{';',	':'},		
/* 0x28 */	{'\'',	'"'},		
/* 0x29 */	{'`',	'~'},		
/* 0x2A */	{shift_l_char, shift_l_char},	
/* 0x2B */	{'\\',	'|'},		
/* 0x2C */	{'z',	'Z'},		
/* 0x2D */	{'x',	'X'},		
/* 0x2E */	{'c',	'C'},		
/* 0x2F */	{'v',	'V'},		
/* 0x30 */	{'b',	'B'},		
/* 0x31 */	{'n',	'N'},		
/* 0x32 */	{'m',	'M'},		
/* 0x33 */	{',',	'<'},		
/* 0x34 */	{'.',	'>'},		
/* 0x35 */	{'/',	'?'},
/* 0x36	*/	{shift_r_char, shift_r_char},	
/* 0x37 */	{'*',	'*'},    	
/* 0x38 */	{alt_l_char, alt_l_char},
/* 0x39 */	{' ',	' '},		
/* 0x3A */	{caps_lock_char, caps_lock_char}
};

void keyboard_init()
{
    put_str("keyboard init start\n");
    register_handler(0x21,intr_keyboard_handler);
    put_str("keyboard init done\n");
}

void intr_keyboard_handler(void)
{
    bool ctrl_down_last = ctrl_status;
    bool shift_down_last = shift_status;
    bool caps_lock_last = caps_lock_status;
    
    bool break_code;
    uint16_t scancode = inb(KBD_BUF_PORT);
    
    if(scancode == 0xe0)	//多字节处理
    {
    	ext_scancode = true;
    	return;
    }
    
    break_code = ((scancode & 0x0080) != 0); //断码 = 通码 + 0x80 通码最小比0x80小 则只有断码才可以有
    
    if(break_code)
    {
    	uint16_t make_code = (scancode &= 0xff7f); //多字节不处理
    	if(make_code == ctrl_l_make || make_code == ctrl_r_make) ctrl_status = false;
    	else if(make_code == shift_l_make || make_code == shift_r_make) shift_status = false;
    	else if(make_code == alt_l_make || make_code == alt_r_make) alt_status = false;
    	return;
    }
    else if((scancode > 0x00 && scancode < 0x3b) || (scancode == alt_r_make) || (scancode == ctrl_r_make))
    {
    	bool shift = false; //先默认设置成false
    	if((scancode < 0x0e) || (scancode == 0x29) || (scancode == 0x1a) || \
    	(scancode == 0x1b) || (scancode == 0x2b) || (scancode == 0x27) || \
    	(scancode == 0x28) || (scancode == 0x33) || (scancode == 0x34) || \
    	(scancode == 0x35))
    	{
    	    if(shift_down_last)	shift = true;
    	}
    	else
    	{
    	    if(shift_down_last && caps_lock_last)	shift = false; //效果确实是这样子的 我试了一下
    	    else if(shift_down_last || caps_lock_last) shift = true; //其中任意一个都是大写的作用
    	    else shift = false;
    	}
    	
    	uint8_t index = (scancode & 0x00ff);
        char cur_char = keymap[index][shift];
    
        if(cur_char)
        {
        	put_char(cur_char);
	    	return;
	    }
    
	if(scancode == ctrl_l_make || scancode == ctrl_r_make)    	
	    ctrl_status = true;
	else if(scancode == shift_l_make || scancode == shift_r_make)
            shift_status = true;
	else if(scancode == alt_l_make || scancode == alt_r_make)
	    alt_status = true;
	else if(scancode == caps_lock_make)
	    caps_lock_status = !caps_lock_status;
	else put_str("unknown key\n");
    }
    
    return;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172

make all 验收成果


在把编译错误挨个挨个改掉了之后
直接make all 迫不及待的来试一试究竟有多出货


在试了一系列按键之后 验证无误 是我写的程序 相当的ok哈 哈哈
这篇博客还是分了两三天来写的 其实两天就搞定了 昨晚和我一起留校的朋友聊天到晚上两点没睡觉 今天就没起来 没去成图书馆 所以后面还有个环形缓冲区只能明天来弄咯

在这里插入图片描述


实现环形缓冲区 生产者消费者


这里的话 其实还是挺简单的
生产者消费者还是多有趣的 就是利用锁来确定放进来 吐出去的位置

下面就直接给代码吧 还是有很多地方需要改一下
这里就说一下为什么还要改一下pic_init() 因为上面为了调试键盘驱动软件 我们把时钟中断是给屏蔽了 但是最后的生产者消费者我们是弄了两个线程 时钟中断开启之后才会有线程的调度 所以还需要把时钟和键盘都开启


编写ioqueue.h


#ifndef __DEVICE__IOQUEUE_H
#define __DEVICE__IOQUEUE_H

#include "stdint.h"
#include "../thread/thread.h"
#include "../thread/sync.h"

#define bufsize 64

struct ioqueue
{
    struct lock lock;
    struct task_struct* consumer;
    struct task_struct* producer;
    char buf[bufsize];
    uint32_t head;			//头部读入数据
    uint32_t tail;			//尾部拿数据
};

void init_ioqueue(struct ioqueue* ioq);
uint32_t next_pos(uint32_t pos);
bool ioq_full(struct ioqueue* ioq);
bool ioq_empty(struct ioqueue* ioq);
void ioq_wait(struct task_struct** waiter); //这里是waiter的二级指针 取二级指针的原因是这样可以对指针的地址值进行修改
void wakeup(struct task_struct** waiter); 
char ioq_getchar(struct ioqueue* ioq);
void ioq_putchar(struct ioqueue* ioq,char chr);

#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

编写ioqueue.c


#include "ioqueue.h"
#include "interrupt.h"
#include "global.h"
#include "debug.h"


void init_ioqueue(struct ioqueue* ioq)
{
    lock_init(&ioq->lock);
    ioq->consumer = ioq->producer = NULL;
    ioq->head = ioq->tail = 0;
}

uint32_t next_pos(uint32_t pos)
{
    return (pos+1)%bufsize;
}

bool ioq_full(struct ioqueue* ioq)
{
    ASSERT(intr_get_status() == INTR_OFF);
    return next_pos(ioq->head) == ioq->tail;
}

bool ioq_empty(struct ioqueue* ioq)
{
    ASSERT(intr_get_status() == INTR_OFF);
    return ioq->head == ioq->tail;
}

void ioq_wait(struct task_struct** waiter)
{
    ASSERT(waiter != NULL && *waiter == NULL);
    *waiter = running_thread();
    thread_block(TASK_BLOCKED);
}

void wakeup(struct task_struct** waiter)
{
    ASSERT(*waiter != NULL);
    thread_unblock(*waiter);
    *waiter = NULL;
}

char ioq_getchar(struct ioqueue* ioq)
{
    ASSERT(intr_get_status() == INTR_OFF);
    
    //空的时候就需要动锁了 把另外的消费者卡住
    while(ioq_empty(ioq))
    {
        lock_acquire(&ioq->lock);
        ioq_wait(&ioq->consumer);
        lock_release(&ioq->lock);
    }
    
    char retchr = ioq->buf[ioq->tail];
    ioq->tail = next_pos(ioq->tail);
    
    if(ioq->producer)
    	wakeup(&ioq->producer);
    
    return retchr;
}

void ioq_putchar(struct ioqueue* ioq,char chr)
{
    ASSERT(intr_get_status() == INTR_OFF);
    
    while(ioq_full(ioq))
    {
    	lock_acquire(&ioq->lock);
    	ioq_wait(&ioq->producer);
    	lock_release(&ioq->lock);
    }
    
    ioq->buf[ioq->head] = chr;
    ioq->head = next_pos(ioq->head);
    
    if(ioq->consumer)
    	wakeup(&ioq->consumer);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82

稍修修改一下的keyboard.c


下面懒得筛选了 直接给全部代码哈

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

#define KBD_BUF_PORT 0X60
struct ioqueue ioqueue;

#define esc '\033'		//esc 和 delete都没有
#define delete '\0177'
#define enter '\r'
#define tab '\t'
#define backspace '\b'

#define char_invisible 0	//功能性 不可见字符均设置为0
#define ctrl_l_char char_invisible
#define ctrl_r_char char_invisible 
#define shift_l_char char_invisible
#define shift_r_char char_invisible
#define alt_l_char char_invisible
#define alt_r_char char_invisible
#define caps_lock_char char_invisible

#define shift_l_make 0x2a
#define shift_r_make 0x36
#define alt_l_make 0x38
#define alt_r_make 0xe038
#define alt_r_break 0xe0b8
#define ctrl_l_make 0x1d
#define ctrl_r_make 0xe01d
#define ctrl_r_break 0xe09d
#define caps_lock_make 0x3a
bool ctrl_status = false,shift_status = false,alt_status = false,caps_lock_status = false,ext_scancode = false;


/* 以通码make_code为索引的二维数组 */
static char keymap[][2] = {
/* 扫描码   未与shift组合  与shift组合*/
/* ---------------------------------- */
/* 0x00 */	{0,	0},		
/* 0x01 */	{esc,	esc},		
/* 0x02 */	{'1',	'!'},		
/* 0x03 */	{'2',	'@'},		
/* 0x04 */	{'3',	'#'},		
/* 0x05 */	{'4',	'$'},		
/* 0x06 */	{'5',	'%'},		
/* 0x07 */	{'6',	'^'},		
/* 0x08 */	{'7',	'&'},		
/* 0x09 */	{'8',	'*'},		
/* 0x0A */	{'9',	'('},		
/* 0x0B */	{'0',	')'},		
/* 0x0C */	{'-',	'_'},		
/* 0x0D */	{'=',	'+'},		
/* 0x0E */	{backspace, backspace},	
/* 0x0F */	{tab,	tab},		
/* 0x10 */	{'q',	'Q'},		
/* 0x11 */	{'w',	'W'},		
/* 0x12 */	{'e',	'E'},		
/* 0x13 */	{'r',	'R'},		
/* 0x14 */	{'t',	'T'},		
/* 0x15 */	{'y',	'Y'},		
/* 0x16 */	{'u',	'U'},		
/* 0x17 */	{'i',	'I'},		
/* 0x18 */	{'o',	'O'},		
/* 0x19 */	{'p',	'P'},		
/* 0x1A */	{'[',	'{'},		
/* 0x1B */	{']',	'}'},		
/* 0x1C */	{enter,  enter},
/* 0x1D */	{ctrl_l_char, ctrl_l_char},
/* 0x1E */	{'a',	'A'},		
/* 0x1F */	{'s',	'S'},		
/* 0x20 */	{'d',	'D'},		
/* 0x21 */	{'f',	'F'},		
/* 0x22 */	{'g',	'G'},		
/* 0x23 */	{'h',	'H'},		
/* 0x24 */	{'j',	'J'},		
/* 0x25 */	{'k',	'K'},		
/* 0x26 */	{'l',	'L'},		
/* 0x27 */	{';',	':'},		
/* 0x28 */	{'\'',	'"'},		
/* 0x29 */	{'`',	'~'},		
/* 0x2A */	{shift_l_char, shift_l_char},	
/* 0x2B */	{'\\',	'|'},		
/* 0x2C */	{'z',	'Z'},		
/* 0x2D */	{'x',	'X'},		
/* 0x2E */	{'c',	'C'},		
/* 0x2F */	{'v',	'V'},		
/* 0x30 */	{'b',	'B'},		
/* 0x31 */	{'n',	'N'},		
/* 0x32 */	{'m',	'M'},		
/* 0x33 */	{',',	'<'},		
/* 0x34 */	{'.',	'>'},		
/* 0x35 */	{'/',	'?'},
/* 0x36 */	{shift_r_char, shift_r_char},	
/* 0x37 */	{'*',	'*'},    	
/* 0x38 */	{alt_l_char, alt_l_char},
/* 0x39 */	{' ',	' '},		
/* 0x3A */	{caps_lock_char, caps_lock_char}
};

void keyboard_init()
{
    put_str("keyboard init start\n");
    register_handler(0x21,intr_keyboard_handler);
    init_ioqueue(&ioqueue);
    put_str("keyboard init done\n");
}

void intr_keyboard_handler(void)
{
    bool ctrl_down_last = ctrl_status;
    bool shift_down_last = shift_status;
    bool caps_lock_last = caps_lock_status;
    
    bool break_code;
    uint16_t scancode = inb(KBD_BUF_PORT);
    
    if(scancode == 0xe0)	//多字节处理
    {
    	ext_scancode = true;
    	return;
    }
    
    break_code = ((scancode & 0x0080) != 0); //断码 = 通码 + 0x80 通码最小比0x80小 则只有断码才可以有
    
    if(break_code)
    {
    	uint16_t make_code = (scancode &= 0xff7f); //多字节不处理
    	if(make_code == ctrl_l_make || make_code == ctrl_r_make) ctrl_status = false;
    	else if(make_code == shift_l_make || make_code == shift_r_make) shift_status = false;
    	else if(make_code == alt_l_make || make_code == alt_r_make) alt_status = false;
    	return;
    }
    else if((scancode > 0x00 && scancode < 0x3b) || (scancode == alt_r_make) || (scancode == ctrl_r_make))
    {
    	bool shift = false; //先默认设置成false
    	if((scancode < 0x0e) || (scancode == 0x29) || (scancode == 0x1a) || \
    	(scancode == 0x1b) || (scancode == 0x2b) || (scancode == 0x27) || \
    	(scancode == 0x28) || (scancode == 0x33) || (scancode == 0x34) || \
    	(scancode == 0x35))
    	{
    	    if(shift_down_last)	shift = true;
    	}
    	else
    	{
    	    if(shift_down_last && caps_lock_last)	shift = false; //效果确实是这样子的 我试了一下
    	    else if(shift_down_last || caps_lock_last) shift = true; //其中任意一个都是大写的作用
    	    else shift = false;
    	}
    	
    	uint8_t index = (scancode & 0x00ff);
        char cur_char = keymap[index][shift];
    
        if(cur_char)
        {
        	if(!ioq_full(&ioqueue))
        		ioq_putchar(&ioqueue,cur_char);
        	//put_char(cur_char);
	    	return;
	}
    
	if(scancode == ctrl_l_make || scancode == ctrl_r_make)    	
	    ctrl_status = true;
	else if(scancode == shift_l_make || scancode == shift_r_make)
            shift_status = true;
	else if(scancode == alt_l_make || scancode == alt_r_make)
	    alt_status = true;
	else if(scancode == caps_lock_make)
	    caps_lock_status = !caps_lock_status;
	else put_str("unknown key\n");
    }
    
    return;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177

稍微修改一下的keyboard.h


#ifndef __DEVICE_KEYBOARD_H
#define __DEVICE_KEYBOARD_H

extern struct ioqueue ioqueue;
void intr_keyboard_handler(void);
void keyboard_init(void);

#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

稍微修改一下的interrupt.c


这里就只把修改部分贴出来
第0位 是键盘中断 第1位 是时钟中断 这两位得是0 1表示屏蔽
1100(二进制) == 0xc(16进制)

换一下即可

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, 0xfc);
   outb (PIC_S_DATA, 0xff);

   put_str("   pic_init done\n");
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

稍微修改一下的main.c


#include "print.h"
#include "init.h"
#include "debug.h"
#include "string.h"
#include "memory.h"
#include "../thread/thread.h"
#include "interrupt.h"
#include "../device/console.h"
#include "../device/ioqueue.h"
#include "../device/keyboard.h"

void test_thread1(void* arg);
void test_thread2(void* arg);

int main(void) {
   put_str("I am kernel\n");
   init_all();
   thread_start("kernel_thread_a",31,test_thread1," A_");
   thread_start("kernel_thread_b",31,test_thread2," B_");
   intr_enable();
   
   while(1);
   return 0;
}

void test_thread1(void* arg)
{
    while(1)
    {
        enum intr_status old_status = intr_disable();
        while(!ioq_empty(&ioqueue))
        {
   	    console_put_str((char*)arg);
    	    char chr = ioq_getchar(&ioqueue);
   	    console_put_char(chr);
	}
   	intr_set_status(old_status);
    }
}

void test_thread2(void* arg)
{
    while(1)
    {
        enum intr_status old_status = intr_disable();
        while(!ioq_empty(&ioqueue))
        {
   	    console_put_str((char*)arg);
    	    char chr = ioq_getchar(&ioqueue);
   	    console_put_char(chr);
	}
   	intr_set_status(old_status);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

修改后的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)/switch.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)/sync.o $(BUILD_DIR)/console.o $(BUILD_DIR)/keyboard.o \
      $(BUILD_DIR)/ioqueue.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 device/console.h \
        device/keyboard.h device/ioqueue.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 device/console.h device/keyboard.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.h \
	kernel/interrupt.h lib/stdint.h kernel/debug.h
	$(CC) $(CFLAGS) $< -o $@
	
$(BUILD_DIR)/sync.o: thread/sync.c thread/sync.h \
	lib/stdint.h thread/thread.h kernel/debug.h kernel/interrupt.h
	$(CC) $(CFLAGS) $< -o $@
	
$(BUILD_DIR)/console.o: device/console.c device/console.h \
	lib/kernel/print.h thread/sync.h
	$(CC) $(CFLAGS) $< -o $@
	
$(BUILD_DIR)/keyboard.o: device/keyboard.c device/keyboard.h \
	lib/kernel/print.h lib/kernel/io.h kernel/interrupt.h \
	kernel/global.h lib/stdint.h device/ioqueue.h
	$(CC) $(CFLAGS) $< -o $@
	
$(BUILD_DIR)/ioqueue.o: device/ioqueue.c device/ioqueue.h \
	kernel/interrupt.h kernel/global.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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107

make all 验收成果


效果虽然不是书上的一个一换
但是是因为线程阻塞的问题 时钟中断的问题
说明还是生效了 ok了 Homie些

在这里插入图片描述


结束语


这一章可能是目前写到现在最多的一章了吧 可能是 记不清楚原来还有没有比这一章写的更多了的
但总的来说挺好的 最近的重庆太热了太热了
这两天人也比较懒 一直呆在寝室 没有去图书馆 起床也起的很晚

但一切的一切都还算不错 待会刷几道力扣就去吃午饭了 现在都下午2点了 溜溜球!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/261884
推荐阅读
相关标签
  

闽ICP备14008679号