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)
首先一点,进程切换必定是发生在内核空间的(只有内核才能执行进程切换这个动作),所以肯定有user mode -> supervisor mode 和supervisor mode->user mode的这个代价,也就是执行一个常规trap的代价
swtch.S保存当前上下文的代价
切换page table而产生的清空TLB缓存(过去建立的映射不适用于当前页表),而导致cpu需要多次读写内存的代价
相对来说 线程切换只需要
- 切换当前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 :