MIT6.S081之process &thread

Process && thread

讲到thread,就不得不谈一下process了对吧,我们知道在xv6里面 一个process 对应的其实就是一个 struct proc的结构体,里面有

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Per-process state
struct proc {
  struct spinlock lock;

  // p->lock must be held when using these:
  enum procstate state;        // Process state
  void *chan;                  // If non-zero, sleeping on chan
  int killed;                  // If non-zero, have been killed
  int xstate;                  // Exit status to be returned to parent's wait
  int pid;                     // Process ID

  // wait_lock must be held when using this:
  struct proc *parent;         // Parent process

  // these are private to the process, so p->lock need not be held.
  uint64 kstack;               // Virtual address of kernel stack
  uint64 sz;                   // Size of process memory (bytes)
  pagetable_t pagetable;       // User page table
  struct trapframe *trapframe; // data page for trampoline.S
  struct context context;      // swtch() here to run process
  struct file *ofile[NOFILE];  // Open files
  struct inode *cwd;           // Current directory
  char name[16];               // Process name (debugging)
};

这些东西,这里列出这些struct的意义是想让大家知道其实process并不难理解,简单来看就是一堆指令加上对应的内存空间加上一些状态,这不是so easy?但是要建立底层思想的话,你还需要对计算机组成有一定的概念,因为本质上,计算机就是取出指令然后执行指令的模型,

Process scheduler && thread scheduler

进程调度,如果你对os是resource management这个概念不陌生的话,进程调度对你来说也不陌生,其实就是在某些提前设定好的规则下,你的管家(os) 发现这个process 需要进行调度

为什么进程切换要比线程切换要昂贵呢? 主要原因在于切换页表这个操作(这个只是诱因,真正导致昂贵代价的原因是因为要flush TLB,重新建立缓存),

首先我们了解在进程切换的过程中发生了什么(swtch.S)

  1. 首先一点,进程切换必定是发生在内核空间的(只有内核才能执行进程切换这个动作),所以肯定有user mode -> supervisor mode 和supervisor mode->user mode的这个代价,也就是执行一个常规trap的代价

  2. swtch.S保存当前上下文的代价

  3. 切换page table而产生的清空TLB缓存(过去建立的映射不适用于当前页表),而导致cpu需要多次读写内存的代价

相对来说 线程切换只需要

  1. 切换当前stack,(类似于当前上下文)

(对应到Linux里面的就是task_struct,要复杂很多很多,关于task_struct的定义差不多有个800多行 linux-5.12.*) 但其实一开始的Linux 0.11 的task_struct 也是 比较精简的,如下

 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
//The task_struct of linux kernel 0.11
struct  task_struct {
/*  these are hardcoded - don't touch  */
     long  state;     /*  -1 unrunnable, 0 runnable, >0 stopped  */
     long  counter;
     long  priority;
     long  signal;
     struct  sigaction sigaction[ 32 ];
     long  blocked;     /*  bitmap of masked signals  */
/*  various fields  */
     int  exit_code;
    unsigned  long  start_code,end_code,end_data,brk,start_stack;
     long  pid,father,pgrp,session,leader;
    unsigned  short  uid,euid,suid;
    unsigned  short  gid,egid,sgid;
     long  alarm;
     long  utime,stime,cutime,cstime,start_time;
    unsigned  short  used_math;
/*  file system info  */
     int  tty;         /*  -1 if no tty, so it must be signed  */
    unsigned  short  umask;
     struct  m_inode  *  pwd;
     struct  m_inode  *  root;
     struct  m_inode  *  executable;
    unsigned  long  close_on_exec;
     struct  file  *  filp[NR_OPEN];
/*  ldt for this task 0 - zero 1 - cs 2 - ds&ss  */
     struct  desc_struct ldt[ 3 ];
/*  tss for this task  */
     struct  tss_struct tss;
};

thread is not only the way to support multi tasks

and we still have event-driven program,state-machine

root :

0%