ucore lab4 内核线程管理 学习笔记
越学越简单,真是越学越简单啊
看视频的时候着实被那复杂的函数调用图吓到了.看代码的时候发现条理还是很清晰的,远没有没想象的那么复杂.
这节创建了俩内核线程,然后运行第一个线程,再由第一个切换到第二个.
kern_init:
在vmm_init后加了一个proc_init
在最末位加了个cpu_idel
proc.c&.h
枚举类proc_state定义了进程生命周期里的各种状态
// process's state in his life cycle
enum proc_state {
PROC_UNINIT = 0, // uninitialized
PROC_SLEEPING, // sleeping
PROC_RUNNABLE, // runnable(maybe running)
PROC_ZOMBIE, // almost dead, and wait parent proc to reclaim his resource
};
各状态间的转化:
alloc_proc RUNNING
+ +--<----<--+
+ + proc_run +
V +-->---->--+
PROC_UNINIT -- proc_init/wakeup_proc --> PROC_RUNNABLE -- try_free_pages/do_wait/do_sleep --> PROC_SLEEPING --
A + +
| +--- do_exit --> PROC_ZOMBIE +
+ +
-----------------------wakeup_proc----------------------------------
proc_struct,就课程里讲的那个进程控制块(PCB)
struct proc_struct {
enum proc_state state; // Process state
int pid; // Process ID
int runs; // the running times of Proces
uintptr_t kstack; // Process kernel stack
volatile bool need_resched; // bool value: need to be rescheduled to release CPU?
struct proc_struct *parent; // the parent process
struct mm_struct *mm; // Process's memory management field
struct context context; // Switch here to run process
struct trapframe *tf; // Trap frame for current interrupt
uintptr_t cr3; // CR3 register: the base addr of Page Directroy Table(PDT)
uint32_t flags; // Process flag
char name[PROC_NAME_LEN + 1]; // Process name
list_entry_t list_link; // Process link list
list_entry_t hash_link; // Process hash list
};
list_entry_t proc_list 链表形式的进程集合
list_entry_t hash_list[1024] 散列表形式的进程集合
proc_struct *idleproc 0号进程,作用是不断检查当前有无处于就绪状态的进程,有则立即运行
proc_struct *initproc 本实验中测试用的进程,打印一句 hello world
static int nr_process 线程计数器
alloc_proc:分配一个PCB并初始化
各种成员变量清零
state设为UNINIT
pid=-1
cr3=内核页目录表基址(物理地址)
kernel_thread(fn,arg,clone_flag) :创建内核线程
创建一个临时trapframe
CS,DS,SS,ES均取内核态的对应值
ebx=fn
edx=arg
eip=kernel_thread_entry //中断返回时从kernel_thread_entry继续
kernel_thread_entry在entry中定义:把arg做参数调用fn,把fn返回值做参数调用do_exit
调用do_fork(clone_flags|CLONE_VM,0,&tf)
do_fork: 根据tf,stack,clone_tf创建新线程
- 分配并初始化进程控制块(alloc_proc函数);
- 分配并初始化内核栈(setup_kstack函数,分配两个页当栈使唤);
- 根据clone_flag标志复制或共享进程内存管理结构(copy_mm函数,本实验不用mm,返回空);
- 设置进程在内核(将来也包括用户态)正常运行和调度所需的中断帧和执行上下文(copy_thread函数);
- 分配pid(get_pid函数)
- 把设置好的进程控制块放入hash_list和proc_list两个全局进程链表中;
- 自此,进程已经准备好执行了,把进程状态设置为“就绪”态;
- 设置返回码为子进程的id号。
copy_thread:
proc->tf = (struct trapframe *)(proc->kstack + KSTACKSIZE) - 1;
//在内核堆栈的顶部设置中断帧大小的一块栈空间
*(proc->tf) = *tf; //拷贝在kernel_thread函数建立的临时中断帧的初始值
proc->tf->tf_regs.reg_eax = 0;
//设置子进程/线程执行完do_fork后的返回值
proc->tf->tf_esp = esp; //设置中断帧中的栈指针esp
proc->tf->tf_eflags |= FL_IF; //使能中断
proc->context.eip = (uintptr_t)forkret; //trapentry.s定义,把esp压栈,调用__trapet
proc->context.esp = (uintptr_t)(proc->tf); //context.esp赋值为当前栈顶
get_pid:分配pid
这个比较难理解.last_pid=上一次分配的pid.当分配超过MAX_PID时从1开始重新分配
(last_pid,next_safe)指定了一段连续的未分配的pid区间.如果last_pid < next_safe时直接分配last_pid+1,否则以1为单位增加pid,每次增加都遍历整个proc_list查重,并更新next_safe,如果冲突了就再增1,从头再判断.
static int
get_pid(void) {
static_assert(MAX_PID > MAX_PROCESS);
struct proc_struct *proc;
list_entry_t *list = &proc_list, *le;
static int next_safe = MAX_PID, last_pid = MAX_PID;
if (++ last_pid >= MAX_PID) {
last_pid = 1;
goto inside;
}
if (last_pid >= next_safe) {
inside:
next_safe = MAX_PID;
repeat:
le = list;
while ((le = list_next(le)) != list) {
proc = le2proc(le, list_link);
if (proc->pid == last_pid) {
if (++ last_pid >= next_safe) {
if (last_pid >= MAX_PID) {
last_pid = 1;
}
next_safe = MAX_PID;
goto repeat;
}
}
else if (proc->pid > last_pid && next_safe > proc->pid) {
next_safe = proc->pid;
}
}
}
return last_pid;
}
proc_init:
void proc_init(void) {
int i;
//初始化proc_list和hash_list
list_init(&proc_list);
for (i = 0; i < HASH_LIST_SIZE; i ++) {
list_init(hash_list + i);
}
//给idleproc分配一个PCB
if ((idleproc = alloc_proc()) == NULL) {
panic("cannot alloc idleproc.\n");
}
idleproc->pid = 0; //设为0号进程
idleproc->state = PROC_RUNNABLE; //可运行
idleproc->kstack = (uintptr_t)bootstack; //kstack指向全句内核栈,在entry.S里用汇编定义,大小8KB
idleproc->need_resched = 1; //需要重新调度
set_proc_name(idleproc, "idle"); //命名
nr_process ++; //进程数+1
current = idleproc;
int pid = kernel_thread(init_main, "Hello world!!", 0);
if (pid <= 0) {
panic("create init_main failed.\n");
}
initproc = find_proc(pid);
set_proc_name(initproc, "init");
assert(idleproc != NULL && idleproc->pid == 0);
assert(initproc != NULL && initproc->pid == 1);
}
cpu_idle: 无限循环检查当前线程的need_resched,为真时调用schedule()
schedule:基于FIFO的调度算法
保存中断开关状态
从当前进程往后遍历,选择下一个RUNNABLE的进程调用proc_run
恢复中断开关状态
proc_run:
更新tss的特权态0下的栈顶指针esp0为新进程的栈顶
更新CR3位新进程页目录表物理地址,完成进程间页表切换
switch切换当前进程和新进程的上下文
switch_to:
.text
.globl switch_to
switch_to: # switch_to(from, to)
# save from's registers
movl 4(%esp), %eax # eax points to from
popl 0(%eax) # save eip !popl
movl %esp, 4(%eax) # save esp::context of from
movl %ebx, 8(%eax) # save ebx::context of from
movl %ecx, 12(%eax) # save ecx::context of from
movl %edx, 16(%eax) # save edx::context of from
movl %esi, 20(%eax) # save esi::context of from
movl %edi, 24(%eax) # save edi::context of from
movl %ebp, 28(%eax) # save ebp::context of from
# restore to's registers
movl 4(%esp), %eax # not 8(%esp): popped return address already
# eax now points to to
movl 28(%eax), %ebp # restore ebp::context of to
movl 24(%eax), %edi # restore edi::context of to
movl 20(%eax), %esi # restore esi::context of to
movl 16(%eax), %edx # restore edx::context of to
movl 12(%eax), %ecx # restore ecx::context of to
movl 8(%eax), %ebx # restore ebx::context of to
movl 4(%eax), %esp # restore esp::context of to
pushl 0(%eax) # push eip
ret
当调用switch_to(&(from->context), &(to->context)),进入它的第一行代码时,此时的栈布局为:
|to.context |高地址
|from.context |
|ret address |<---esp
整个switch_to的功能为:
令eax=from.context
把各个寄存器保存到from.context里
令eax=to.context
把to.context恢复到各个寄存器里,把to.context.eip压栈
此时进行ret,栈顶出栈作为eip,返回到的地址就变成了to.context.eip,进程切换完成
对于进程init而言,我们前面把它的context.eip设为了forkret,具体功能为,把esp压栈,调用中断返回函数__trapet,进而将trapframe中的值恢复到的各个寄存器中.eip再次变更为trapframe.eip,即kernel_thread_entry函数,作用为把edx做参数调用ebx对应的函数,edx和ebx也在trapframe中分别指定为"hello world"和init_main,调用完init_main后再把返回值做参数调用do_exit.而do_exit负责退出进程,整个lab4内容结束.
ucore lab4 内核线程管理 学习笔记的更多相关文章
- ucore操作系统学习(四) ucore lab4内核线程管理
1. ucore lab4介绍 什么是进程? 现代操作系统为了满足人们对于多道编程的需求,希望在计算机系统上能并发的同时运行多个程序,且彼此间互相不干扰.当一个程序受制于等待I/O完成等事件时,可以让 ...
- ucore lab5 用户进程管理 学习笔记
近几日睡眠质量不佳,脑袋一困就没法干活,今天总算时补完了.LAB5难度比LAB4要高,想要理解所有细节时比较困难.但毕竟咱不是要真去写一个OS,所以一些个实现细节就当成黑箱略过了. 这节加上了用户进程 ...
- windows内核对象管理学习笔记
目前正在阅读毛老师的<windows内核情景分析>一书对象管理章节,作此笔记. Win内核中是使用对象概念来描述管理内核中使用到的数据结构.此对象(Object)均是由对象头(Object ...
- C++内存管理学习笔记(5)
/****************************************************************/ /* 学习是合作和分享式的! /* Auth ...
- C++内存管理学习笔记(6)
/****************************************************************/ /* 学习是合作和分享式的! /* Auth ...
- C++内存管理学习笔记(7)
/****************************************************************/ /* 学习是合作和分享式的! /* Auth ...
- Docker Image管理学习笔记,ZT
Docker Image管理学习笔记 http://blog.csdn.net/junjun16818/article/details/38423391
- Linux内存管理学习笔记 转
https://yq.aliyun.com/articles/11192?spm=0.0.0.0.hq1MsD 随着要维护的服务器增多,遇到的各种稀奇古怪的问题也会增多,要想彻底解决这些“小”问题往往 ...
- Linux内存管理学习笔记——内存寻址
最近开始想稍微深入一点地学习Linux内核,主要参考内容是<深入理解Linux内核>和<深入理解Linux内核架构>以及源码,经验有限,只能分析出有限的内容,看完这遍以后再更深 ...
随机推荐
- InnoDB中加锁?
InnoDB 实现了两种类型的行锁,共享锁(S)与排他锁(X).然后由于 InnoDB引擎又支持表级锁,所以它内部又有意向共享锁(IS)与意向排他锁(IX).这两种表锁,都是InnoDB内部自动处理, ...
- 一张自增表里面总共有 7 条数据,删除了最后 2 条数据,重启 MySQL 数据库,又插入了一条数据,此时 id 是几?如何获取当前数据库版本?
一张自增表里面总共有 7 条数据,删除了最后 2 条数据,重启 MySQL 数据库,又插入了一条数据,此时 id 是几? 一般情况下,我们创建的表的类型是InnoDB,如果新增一条记录(不重启mysq ...
- thrift使用和源码分析
1 前言 thrift的官方文档比较差,很多细节没有介绍清楚,比如require.optional和default字段的区别是什么,为什么字段前面要写序号等,带着这些疑问,我们需要阅读生成的源码来了解 ...
- Spring Bean生命周期回调
参阅官方文档:https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-factory ...
- 使用kindeditor
首先在http://kindeditor.net/demo.php下载样式 点击右上角的下载按钮 点击官方下载下载之后解压出来 然后在桌面创建一个文件夹 然后回到刚才的http://kindedito ...
- python 基础数据类型汇总
数据类型小结(各数据类型常用操作) 一.数字/整型int int()强行转化数字 二.bool类型False&True bool()强行转化布尔类型. 0,None,及各个空的字符类型为Fal ...
- 滑动窗口法——Leetcode例题
滑动窗口法--Leetcode例题(连更未完结) 1. 方法简介 滑动窗口法可以理解为一种特殊的双指针法,通常用来解决数组和字符串连续几个元素满足特殊性质问题(对于字符串来说就是子串).滑动窗口法的显 ...
- USART_GetITStatus()和USART_GetFlagStatus()的区别
USART_GetITStatus()和USART_GetFlagStatus()的区别 都是访问串口的SR状态寄存器,唯一不同是,USART_GetITStatus()会判断中断是否开启,如果没开启 ...
- poj_1852_Ants(复杂问题简单化)
原题传送门 描述 一群蚂蚁走在长度为l cm的水平细杆上,以1cm/s的匀速.当一只行走的蚂蚁到达杆的一端,它就会掉下去.当两只蚂蚁相遇,它们会掉头像反方向走去.我们知道一只蚂蚁在杆上的初始位置,然而 ...
- 用JS写一个计算器(兼容手机端)
先看成果:1.PC端2. 首先确立html,有哪些东西我们要知道.布局大概的样子在心里有个数 <!DOCTYPE html> <html> <head> <m ...