实现内核进程
执行流
我们将程序计数器中的下一条指令地址所组成的执行轨称为程序的控制执行流。执行流对应于代码,达到可以是整个程序文件,即进程,小到可以是一个功能独立的代码块,即函数,线程本质就是函数。
thread流程图
本人习惯不太好,中间的代码并没有备份,被覆盖掉了。。。抱歉
链表实现
list.c
#include "list.h"
#include "interrupt.h"
#include "string.h"
//初始化双向链表
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();
before->prev->next = elem ;
elem->prev = before->prev ;
elem->next = before;
before->prev = elem;
intr_set_status(old_status);
}
//添加元素到列表队首,类似push操作
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){
struct list_elem* elem = plist->head.next;
list_remove(elem);
return elem;
}
//从链表中查找元素obj_elem
bool elem_find(struct list* plist,struct list_elem* obj_elem){
struct list_elem* elem = plist->head.next;
while(elem != &plist->tail){
if(elem == obj_elem){
return true;
}
elem = elem->next;
}
return false;
}
/*把链表plist中的每一个元素elem和arg传给回调函数func
arg给func用来判断elem是否符合条件
本函数的功能是遍历列表的所有元素,逐个判断是否有符合条件的元素
找到符合条件的元素返回元素指针,否则返回NULL*/
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)){
//func返回true,则认为该元素在回调函数中符合条件,命中,故停止继续遍历
return elem;
//若回调函数func返回true,则继续遍历
}
return NULL;
}
}
//返回链表长度
uint32_t list_len(struct list* plist){
struct list_elem* elem = plist->head.next;
uint32_t length=0;
while(elem != &plist->tail){
length++;
elem = elem->next;
}
return length;
}
//判断链表是否为空,空时返回true,否则返回false
bool list_empty(struct list* plist){
return (plist->head.next == &plist->tail ? true : false);
}
list.h
#ifndef __LIB_KERNEL_LIST_H
#define __LIB_KERNEL_LIST_H
#include "global.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_iterate(struct list* plist);
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
多线程调度
interrupt.c
#include "interrupt.h"
#include "stdint.h"
#include "global.h"
#include "io.h"
#include "print.h"
#define PIC_M_CTRL 0x20
#define PIC_M_DATA 0x21
#define PIC_S_CTRL 0xa0
#define PIC_S_DATA 0xa1
#define IDT_DESC_CNT 0x21 //目前支持的中断数
/*中断门描述符结构体*/
#define EFLAGS_IF 0x00000200
#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; //此项为双字计数字段,是门描述符中的第四字节
//此项固定值
uint8_t attribute;
uint16_t func_offset_high_word;
};
//静态函数声明,非必须
static void make_idt_desc(struct gate_desc* p_gdesc,uint8_t attr,intr_handler function);
static struct gate_desc idt[IDT_DESC_CNT];//idt是中断描述符表
//本质上就是个中断门描述符数组
intr_handler idt_table[IDT_DESC_CNT];//声明引用定义在kernel.S中的中断处理函数入口数组
char* intr_name[IDT_DESC_CNT];
//定义中断处理程序数组,在kernel.S中定义的intXXentry。
//只是中断处理程序的入口,最终调用的是ide_table中的处理程序
extern intr_handler intr_entry_table[IDT_DESC_CNT];
/*初始化可编程中断控制器 */
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
/*打开主片IR0,也就是目前只接受时钟产生的中断*/
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;
for(i=0;i<IDT_DESC_CNT;i++){
make_idt_desc(&idt[i],IDT_DESC_ATTR_DPL0,intr_entry_table[i]);
}
put_str("idt_desc_init done \n");
}
static void general_intr_handler(uint8_t vec_nr){
if(vec_nr==0x27||vec_nr==0x2f){
//IRQ7和IRQ15会产生伪中断
//0x2f是从从片8529A上的最后一个IRQ引脚
return;
}
set_cursor(0);
int cursor_pos=0;
while(cursor_pos < 320){
put_char(' ');
cursor_pos++;
}
set_cursor(0);
put_char('a');
put_str("!!!!!!!!!!! excetion message begin !!!!!!!!!!!!\n");
set_cursor(88);
put_str(intr_name[vec_nr]);
if(vec_nr == 14){ //若为Pagefault,将缺失的地址打印出来并悬浮
int page_fault_vaddr = 0;
asm ("movl %%cr2,%0" : "=r" (page_fault_vaddr));
//cr2是存放造成page_fault的地址
put_str("\npage fault addr is ");
put_int(page_fault_vaddr);
}
put_str("\n!!!!!!! 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_hander,以后哦会由register_hander来注册具体函数
intr_name[i]="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 Invaild 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");
}
/*开中断并返回开中断前的状态*/
enum intr_status intr_enable(){
enum intr_status old_status;
if(INTR_ON == intr_get_status()){
old_status = INTR_ON;
return old_status;
}else{
old_status = INTR_OFF;
asm volatile("sti");
return old_status;
}
}
/*关中断,并且返回关中断的状态*/
enum intr_status intr_disable(){
enum intr_status old_status;
if(INTR_ON == intr_get_status()){
old_status = INTR_ON;
asm volatile("cli":::"memory"); //关闭中断,cli指令将IF位置0
return old_status;
}
old_status=INTR_OFF;
return old_status;
}
/*将中断状态设置为status*/
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_IF & eflags) ? INTR_ON : INTR_OFF ;
}
/*在中断处理程序数组第vector_no个元素中,注册安装中断处理程序function*/
void register_handler(uint8_t vector_no,intr_handler function){
/*idt_table 数组中的函数是在进入中断后根据中断向量号调用的*/
idt_table[vector_no] = function ;
}
thread.c
#include "thread.h"
#include "stdint.h"
#include "string.h"
#include "global.h"
#include "memory.h"
#include "interrupt.h"
#include "switch.h"
#define PG_SIZE 4096
struct task_struct* main_thread; //主线程PCB
struct list thread_ready_list; //就绪队列
struct list thread_all_list; //所有任务队列
static struct list_elem* thread_tag; //用于保存队列中的线程节点
extern void switch_to(struct task_struct* cur,struct task_struct* next);
/*获取当前线程的pcb指针*/
struct task_struct* running_thread(){
uint32_t esp;
asm("mov %%esp,%0" : "=g"(esp));
/*取esp整数部分,即pcb起始地址*/
return (struct task_struct*)(esp & 0xfffff000);
}
/*由kernel_thread去执行function(func_arg)*/
static void kernel_thread(thread_func* function,void* func_arg)
/*执行前要开中断,避免后面的时钟中断被屏蔽,而无法调度其他线程*/
{
intr_enable();
function(func_arg);
}
/*初始化线程栈thread_stack,将待执行的函数和参数放到thread_stack中对应位置*/
void thread_create(struct task_struct* pthread,thread_func function,void* func_arg){
/*先预留中断使用栈空间,可见thread.h定义的结构*/
pthread->self_kstack -= sizeof(struct intr_stack);
pthread->self_kstack -= sizeof(struct thread_stack);
struct thread_stack* kthread_stack = (struct thread_stack*)pthread->self_kstack;
kthread_stack->eip = kernel_thread;
kthread_stack->function = function;
kthread_stack->func_arg = func_arg;
kthread_stack->ebp = kthread_stack->ebx = \
kthread_stack->esi = kthread_stack->edi = 0;
}
/*初始化线程基本信息*/
void init_thread(struct task_struct* pthread,char* name,int prio){
memset(pthread,0,sizeof(*pthread));
strcpy(pthread->name,name);
if(pthread == main_thread){
//由于把main函数也封装成一个线程,并且它一直是执行的,故将其直接设为TASK_RUNNING
pthread->status = TASK_RUNNING;
}else
{
pthread->status = TASK_READY;
}
/*先预留中断使用栈空间,可见thread.h定义的结构*/
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 = 0x19870916;
}
/*创建一优先级为prio的线程,线程名为name,线程名为name,
线程所执行的函数是function(func_arg)*/
struct task_struct* thread_start(char* name,\
int prio, \
thread_func function, \
void *func_arg){
/*pcb都位于内核空间,包括用户进程的pcb也是在内核空间*/
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;
}
static void make_main_thread(void){
main_thread = running_thread();
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(){
ASSERT(intr_get_status()==INTR_OFF);
struct task_struct* cur = running_thread();
if(cur->status == TASK_RUNNING){
//此线程只是cpu时间到了,将其加入就绪队尾
ASSERT(!elem_find(&thread_ready_list,&cur->general_tag));
list_append(&thread_ready_list,&cur->general_tag);
cur->ticks = cur->priority;
cur->status = TASK_READY;
}else{
/*此线程需要某事件发生后才能继续上CPU运行
不需要将其加入队列,因为当前线程不在就绪队列中*/
}
ASSERT(!list_empty(&thread_ready_list));
thread_tag = NULL; //thread_tag清空
/*将thread_ready_list队列中的第一个就绪线程弹出,准备将其调度上CPU*/
thread_tag = list_pop(&thread_ready_list);
struct task_struct* next = elem2entry(struct task_struct,\
general_tag, thread_tag);
next->status = TASK_RUNNING;
switch_to(cur,next);
}
void thread_init(void){
put_str("thread_init start \n");
list_init(&thread_ready_list);
list_init(&thread_all_list);
/*当前main函数创建为线程*/
make_main_thread();
put_str("thread_init done\n");
}
print.S
之前我是照着Love 6博主写的,发现有点不严谨,改动如下
- set_cursor 我们并没有入栈操作,所以后面不应该有popad
- 传参是通过栈传递的,我们需要将栈中参数放入ebx
- 为了方便我把set_cursor拎出来了
TI_GDT equ 0
RPL0 equ 0
SELECTOR_VIDEO equ (0x0003<<3)+TI_GDT+RPL0
SELECTOR_DATA equ (0X0002<<3) + TI_GDT + RPL0
[bits 32]
section .data
put_int_buffer dq 0
section .text
;打印以零结尾的字符串
;输入:栈中参数为打印的字符串
global put_str
put_str:
push ebx
push ecx
xor ecx,ecx
mov ebx,[esp+12]
.goon:
mov cl,[ebx]
cmp cl,0
jz .str_over
push ecx
call put_char
add esp,4
inc ebx
jmp .goon
.str_over:
pop ecx
pop ebx
ret
;--------------------将小端字节序的数字变成对应ASCII后倒置
;输入:栈中参数为待打印的数字
;输出:在屏幕上打印16位进制数字不会打印‘0x’
;打印十进制15时,打印f
;
global put_int
put_int:
pushad
mov ebp,esp
mov eax,[ebp+4*9] ;call返回地址占四字节+pushad的8字节
mov edx,eax
mov edi,7 ;在put_int_buffer中的初始偏移量
mov ecx,8 ;32位数字中,16进制的数字位数为8位
mov ebx,put_int_buffer
;将32位数字按照16进制从低到高逐个处理
;共处理8位16进制数字
.16based_4bits:
and edx,0x0000000F
cmp edx,9
jg .is_A2F
add edx,'0'
jmp .store
.is_A2F:
sub edx,10
add edx,'A'
;每位数字转化为对应字符后,应该按照大端顺序存储
;存储到缓冲区put_int_buffer
.store:
mov [ebx+edi],dl
dec edi
shr eax,4
mov edx,eax
loop .16based_4bits
;现在put_int_buffer中已经全是字符,打印前去掉高位的0
.ready_to_print:
inc edi
.skip_prefix_0:
cmp edi,8
je .full0
.go_on_skip:
mov cl,[put_int_buffer+edi]
inc edi
cmp cl,'0'
je .skip_prefix_0
dec edi
jmp .put_each_num
.full0:
mov cl,'0'
.put_each_num:
push ecx
call put_char
add esp,4
inc edi
mov cl,[put_int_buffer+edi]
cmp edi,8
jl .put_each_num
popad
ret
global put_char
;--------------------put_char----------------
;把栈中的一个字符写入光标所在处
put_char:
pushad ;备份32位寄存器环境
;需要保证gs中为正确的视频段选择子
;为保险起见,每次打印时都为gs赋值
mov ax,SELECTOR_VIDEO ;不能直接把立即数送入段寄存器
mov gs,ax
;;;;;;;;;;;;;;;获取当前光标位置
mov dx,0x03d4 ;索引寄存器
mov al,0x0e ;用于提供光标位置的高八位
out dx,al
mov dx,0x03d5 ;通过读写数据端口获得或设置光标位置
in al,dx ;得到光标位置的高八位
shl ax,0x8
;低八位
mov dx,0x03d4
mov al,0x0f
out dx,al
mov dx,0x03d5
in al,dx
;将光标存入bx
mov bx,ax
;栈中获取待打印字符
mov ecx,[esp + 36] ;pushad 压入8*4=32字节+主调函数的四字节返回地址
cmp cl,0xd
jz .is_carriage_return
cmp cl,0xa
jz .is_line_feed
cmp cl,0x8
jz .is_backspace
jmp .put_other
;;;;;;;;;;;;;;
;当为空格是,光标向前移动一格显存位置
;
;
.is_backspace:
dec bx ;光标左移动一位
shl bx,1 ;表示光标对应现存中的偏移字节
mov byte [gs:bx],0x20 ;待删除的字节补0
inc bx
mov byte [gs:bx],0x07
shr bx,1
jmp .cursor_set
;
.put_other:
shl bx,1 ;光标位置用两字节表示
mov [gs:bx],cl
inc bx
mov byte [gs:bx],0x07 ;字符属性
shr bx,1 ;恢复老的光标值
inc bx ;下一个光标值
cmp bx,2000
jl .cursor_set ;若光标值小于2000表示未写到最后
;若超出屏幕大大小则进行换行处理
.is_line_feed: ;LF换行符
.is_carriage_return: ;回车符CR
;如果是CR,光标移动到行首
xor dx,dx
mov ax,bx
mov si,80
div si
sub bx,dx
.is_carriage_return_end:
add bx,80
cmp bx,2000
.is_line_feed_end:
jl .cursor_set
.roll_screen:
cld
mov ax,SELECTOR_DATA ;我不放心 就初始化了一下
mov es,ax
mov di,es
mov ecx,920
mov ecx,960
mov esi,0xc00b80a0
mov edi,0xc00b8000
rep movsd
mov ebx,3840
mov ecx,80
.cls:
mov word [gs:ebx],0x0720
add ebx,2
loop .cls
mov bx,1920
.cursor_set:
mov dx,0x03d4
mov al,0x0e
out dx,al
mov dx,0x03d5
mov al,bh
out dx,al
mov dx,0x03d4
mov al,0x0f
out dx,al
mov dx,0x03d5
mov al,bl
out dx,al
.put_char_done:
popad
ret
global set_cursor
set_cursor:
mov ebx,dword [esp + 4]
mov dx,0x03d4
mov al,0x0e
out dx,al
mov dx,0x03d5
mov al,bh
out dx,al
mov dx,0x03d4
mov al,0x0f
out dx,al
mov dx,0x03d5
mov al,bl
out dx,al
ret
switch.S
[bits 32]
section .text
global switch_to
switch_to:
;栈此处是返回地址
push esi
push edi
push ebx
push ebp
mov eax,[esp + 20] ;得到栈中的参数cur,cur=[esp + 20]
mov [eax],esp ;保存栈顶指针esp,task_struct的self_kstack字段
;self_kstack在task_struct中的偏移为0
;所以直接往thread开头处存四字节
;-------------------以上是备份当前线程的环境,下面是恢复下一个线程的环境-----------
mov eax,[esp + 24] ;得到栈中的参数next,next =[esp + 24]
mov esp,[eax] ;pcb的第一个成员是self_kstack成员
;它用来记录0级栈顶指针,换上cpu时用来恢复0级栈
;0级栈中保存了进程或线程所有信息,包括3级栈指针
pop ebp
pop ebx
pop edi
pop esi
ret ;返回上面switch_to下面的那句注释的返回地址
;未由中断进入,第一次执行时会返回到kernel_thread
switch.h
为了方便我加了一个头文件
#ifndef __THREAD_SWITCH_H
#define __THREAD_SWITCH_H
void switch_to(struct task_struct* cur,struct task_struct* next);
#endif```