ucore lab4

主要姓名:冯成 , 学号:19021221183 , 学院:电子工程学院

在lab4中流程算是线性的,主要增加了内核线程进程的相关实现。

首先从整体上把握一下这个lab的思路,在lab4中,切换线程的主要思路是固定一个函数入口switch_to用来切换pre进程和next进程的上下文context,并且调整好返回地址,执行ret指令的时候会跳转到forkrets入口,这个函数将栈顶指向了该进程的trapframe,然后跳转到异常返回函数__trapret。换句话说进程的切换和lab1的challenge特权级切换是一样的,主要依赖iret指令以及相应的trapframe设置。在初始化initproc线程时,kernel_thread将tf.tf_eip设置为了kernel_thread_entry入口函数,所以iret之后会跳转到kernel_thread_entry。initproc线程所需要的参数通过tf.tf_regs.reg_ebx和tf.tf_regs.reg_edx传递,而后就开始执行initproc线程。在函数执行完毕之后通过eax返回其返回值,然后跳转到do_exit函数,以上就是lab4流程。

首先进入proc_init函数,该函数主要初始化了一个进程一个线程,同属于内核,视作同一个东西。idle的主要功能是空闲进程,cpu闲置时不断查询调度,init线程则是打印hello world的线程。

 void proc_init(void) 
 {
     int i;
 
     list_init(&proc_list);//初始化进程块链表
     for (i = 0; i < HASH_LIST_SIZE; i ++) 
     {//初始化进程块哈希链表
         list_init(hash_list + i);
     }
 
     if ((idleproc = alloc_proc()) == NULL) {//创建第一个内核进程idleproc
         panic("cannot alloc idleproc.\n");
     }
 
     idleproc->pid = 0;//最初始的内核进程pid为0
     idleproc->state = PROC_RUNNABLE;//初始化为就绪态
     idleproc->kstack = (uintptr_t)bootstack;//该进程与内核共享堆栈
     idleproc->need_resched = 1;//作为空闲进程使用,设置需要被调度
     set_proc_name(idleproc, "idle");//设置进程名字
     nr_process ++;//进程数+1
 
     current = idleproc;//将当前运行进程指针指向idleproc
 
     int pid = kernel_thread(init_main, "Hello world!!", 0);//为init_main创建内核线程
     if (pid <= 0) {
         panic("create init_main failed.\n");
     }
 
     initproc = find_proc(pid);//根据pid找到进程控制块
     set_proc_name(initproc, "init");//设置其进程名字
 
     assert(idleproc != NULL && idleproc->pid == 0);
     assert(initproc != NULL && initproc->pid == 1);
 }

接着分析alloc_proc函数的功能,也就是练习1的内容,这个函数基本上没有做太多的事情,就是初始化了几个特殊的参数,在注释信息里没有,参数手册上有详细的说明。

 // alloc_proc - alloc a proc_struct and init all fields of proc_struct
 static struct proc_struct *alloc_proc(void) 
 {
     struct proc_struct *proc = kmalloc(sizeof(struct proc_struct));
     if (proc != NULL) 
     {
         memset(proc, 0, sizeof(struct proc_struct));//直接清空结构体,再对特殊的参数赋值
         proc->state = PROC_UNINIT;//程序还正在初始化,所以状态应该为PROC_UNINIT
         proc->pid = -1;
         proc->cr3 = boot_cr3;//内核线程与内核共用同一张页表
     }
     return proc;
 }

接着进入kernel_thread函数,先建立了一个暂时的trapframe,用于该进程的中断异常处理

 // kernel_thread - create a kernel thread using "fn" function
 // NOTE: the contents of temp trapframe tf will be copied to 
 //       proc->tf in do_fork-->copy_thread function
 int kernel_thread(int (*fn)(void *), void *arg, uint32_t clone_flags) 
 {
     struct trapframe tf;
     memset(&tf, 0, sizeof(struct trapframe));
     tf.tf_cs = KERNEL_CS;//内核代码段
     tf.tf_ds = tf.tf_es = tf.tf_ss = KERNEL_DS;//内核数据段
     tf.tf_regs.reg_ebx = (uint32_t)fn;//需要线程执行的函数指针
     tf.tf_regs.reg_edx = (uint32_t)arg;//传入参数
     tf.tf_eip = (uint32_t)kernel_thread_entry;//设置统一的跳转入口点
     return do_fork(clone_flags | CLONE_VM, 0, &tf);//调用do_fork复制需要的资源
 }

作为中转站,在entry.S文件中的加载代码是这样写的,这是从iret跳转过来的

 .text
 .globl kernel_thread_entry
 kernel_thread_entry:        # void kernel_thread(void)
 
     pushl %edx              # push arg
     call *%ebx              # call fn
 
     pushl %eax              # save the return value of fn(arg)
     call do_exit            # call do_exit to terminate current thread

进入do_fork函数,也就是lab4的练习2,这里大部分需要填写的代码都在注释中,不过还有一点坑需要注意

 /* do_fork -     parent process for a new child process
  * @clone_flags: used to guide how to clone the child process
  * @stack:       the parent's user stack pointer. if stack==0, It means to fork a kernel thread.
  * @tf:          the trapframe info, which will be copied to child process's proc->tf
  */
 int do_fork(uint32_t clone_flags, uintptr_t stack, struct trapframe *tf) 
 {
     int ret = -E_NO_FREE_PROC;
     struct proc_struct *proc;
     if (nr_process >= MAX_PROCESS) {
         goto fork_out;
     }
     ret = -E_NO_MEM;
 
     proc = alloc_proc();//创建进程块
     proc->parent = current;//其父进程为当前进程
 
     setup_kstack(proc);//建立其堆栈
     copy_mm(clone_flags, proc);//复制内存信息,这个lab用不到
     copy_thread(proc, stack, tf);//初始化trapframe和context
 
     proc->pid = get_pid();//获得pid,注意要在hash之前获取pid,因为哈希需要pid作为参数,否则报页错误
     hash_proc(proc);//添加至hash链表
     list_add(&proc_list, &(proc->list_link));//添加到链表中
     nr_process++;//进程数增加1
 
     wakeup_proc(proc);//唤醒该进程
 
     ret = proc->pid;//返回pid值
 fork_out:
     return ret;
 
 bad_fork_cleanup_kstack:
     put_kstack(proc);
 bad_fork_cleanup_proc:
     kfree(proc);
     goto fork_out;
 }

接着我们从调度器的角度看看线程是如何切换的,切换所需要的资源都是什么样的。一路快进到cup_idle中,这个函数是闲置线程,不断查询有没有线程需要使用cpu。

 void cpu_idle(void) 
 {
     while (1) {
         if (current->need_resched) {
             schedule();
         }
     }
 }

进入schedule函数,能够发现它首先会把标志寄存器的值保存起来,因为要关中断,接着将当前执行的线程调度关闭,也就是切换当前线程到就绪态,

 void schedule(void) 
 {
     bool intr_flag;//用于暂存标志寄存器
     list_entry_t *le, *last;
     struct proc_struct *next = NULL;
     local_intr_save(intr_flag);
     {
         current->need_resched = 0;
         last = (current == idleproc) ? &proc_list : &(current->list_link);
         le = last;
         do {//找到第一个PROC_RUNNABLE的线程,或者找一圈都没有就退出循环
             if ((le = list_next(le)) != &proc_list) {
                 next = le2proc(le, list_link);
                 if (next->state == PROC_RUNNABLE) {
                     break;
                 }
             }
         } while (le != last);
         if (next == NULL || next->state != PROC_RUNNABLE) {//没找到可以运行的线程就运行idle闲置线程
             next = idleproc;
         }
         next->runs ++;//运行时长增加
         if (next != current) {//找到的线程不是当前线程就开始切换
             proc_run(next);
         }
     }
     local_intr_restore(intr_flag);
 }

接下来看看proc_run是如何完成切换线程工作的

 // proc_run - make process "proc" running on cpu
 // NOTE: before call switch_to, should load  base addr of "proc"'s new PDT
 void proc_run(struct proc_struct *proc) 
 {
     if (proc != current) {
         bool intr_flag;
         struct proc_struct *prev = current, *next = proc;
         local_intr_save(intr_flag);
         {
             current = proc;//切换当前进程标识
             load_esp0(next->kstack + KSTACKSIZE);//加载其堆栈
             lcr3(next->cr3);
             switch_to(&(prev->context), &(next->context));//切换进程
         }
         local_intr_restore(intr_flag);
     }
 }

调用switch函数

 .text
 .globl switch_to
 switch_to:                      # switch_to(from, to)
 
     # save from's registers
     movl 4(%esp), %eax          # esp指向上一层返回地址,esp+4则存放参数1即from的context指针
     popl 0(%eax)                # 把返回地址放入context中保存
     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          # 进行过一次弹栈,所以esp+4存放to的context指针
     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)               # 压入返回地址,ret指令即跳到该处执行
                              # 对应的就是之前copy_thread设置好的forkret入口
 
     ret
之后跳到forkret

   ......  
     # return falls through to trapret...
 .globl __trapret
 __trapret:
     # restore registers from stack
     popal
 
     # restore %ds, %es, %fs and %gs
     popl %gs
     popl %fs
     popl %es
     popl %ds
 
     # get rid of the trap number and error code
     addl $0x8, %esp
     iret
 
 .globl forkrets
 forkrets:
     # set stack to this new process's trapframe
     movl 4(%esp), %esp #将esp指向trapframe,之后跳转到异常返回函数
     jmp __trapret

到这里lab4思路分析完毕

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容