Process
进程和线程的区别
- 进程:操作系统资源分配的最小单位,拥有独立的地址空间、文件描述符等。
- 线程:CPU 调度的最小单位,共享进程的资源,包括代码段、数据段、堆等。线程不共享栈空间。
PCB
- 操作系统每创建一个进程,都会创建一个 process control block,里面存储进程的 PID 和信息
- process name
- procee status(ready/blocked/running)
- pc, stack pointer, file descriptors
- process对资源(CPU 时间、内存空间)的需求情况
线程模型
UT:用户线程
KT:内核线程
1:1 模型(内核级线程)
- java.lang.Thread, std::thread, pthread
- 多核并行,资源利用率高
- 适合 CPU 密集型任务
M:1
- Python gevent &协程。一个内核线程负责多个用户线程
- 一个用户线程的栈只占用几 KB 内存
- 切换开销低,用户态调度不需要陷入内核。内核以为这个 KT一直在执行,没有切换过
- 适合IO 密集型任务
M:N
- gorountine: 多核 + 轻量,兼顾并行与并发
协程与线程的区别是什么?协程的优势和适用场景是什么?
- 它的创建、调度、销毁都在 用户态 完成,不需要操作系统内核的参与
- 栈通常只有几 KB,切换开销小
- 协程的执行需要主动让出控制权(通常通过 yield 或 await),才能让其他协程运行。这一点与线程的抢占式调度不同。
- JS和 Python 中async/await是一种协程的实现
- 同一线程内的协程是串行执行的,无法并行
Process
进程独有的:
- PC
- stack pointer(存储返回地址)
- address space
- file descriptors
想要去 manipulate一个 process,我们必须交给内核态去做这件事,也就是在用户程序中调用 syscall。对于每个syscall,我们都必须检查它的 return value,如果为-1,则说明出错了。
进程的状态
- new(PCB 未被分配)
- ready
- running
- waiting
- terminated
running->ready: time slice 用完了正常切换/更高优先级的 process is ready
running->waiting: 被阻塞
内核空间
- 内核空间位于整个地址空间的顶部,是所有用户进程共有的。用户进程的地址空间处于地址空间的底部。
- 只要系统在运行,内核就始终处于活动状态。它不像其他进程那样启动和停止;而是在系统启动时初始化,并持续运行直到系统关闭。
进程的终止
终止一个进程有三种方法:
- 收到了default action为terminate的signal
- 从main方法返回
- 显式call exit
fork()
在fork()创建子进程时,子进程会继承父进程的一切,包括PC,stack pointer, fd; 子进程拥有和父进程一模一样,但是分开的虚拟地址空间,并且这两个虚拟地址空间指向同一块物理内存。因为父进程把 page table复制给子进程。
写时复制:父子进程拥有的同一块物理内存,在 fork()初期被设为 read-only. 当父进程或子进程尝试修改内存,会发生 page fault, 也叫写保护中断。 此时,OS 会给该进程复制一份新的内存页,然后修改应用于新的内存页上。
僵尸进程
子进程在执行完成时,会把它的return value放在某个地方,然后通知 OS 它完成工作了。此时父进程可以通过 wait()/waitpid()来获得子进程的 return value。
如果子进程已经 terminate,但是父进程没有通过wait()/waitpid()来获得退出状态,那么子进程仍然会消耗系统资源。比如OS需要存储它的 return value,也需要在process table 中留着它的 pid.
孤儿进程
如果父进程 terminate 了,它的子进程还没有 terminate,那么所有未完成的子进程被OS分配给init进程(pid=1)。init进程会负责在它们 terminate 之后,回收它们的资源。
文件描述符
在内存中,每个进程都有一个PCB,PCB中存在每个进程独有的file descriptor table,文件描述符就是这个table中的索引。
table 中存放指向资源的指针,比如指向heap中的一个 FILE 结构,这个 FILE 结构又指向磁盘中的".txt"文件。
进程间通信方式
管道
- 匿名管道:允许亲缘进程通信。
| |
shell先用fork()创建一个进程,并把它替换为cat进程;再对 grep做同样的事。
shell接着创建一个管道(内核中的一串缓存),允许cat的输出作为 grep的输入。
- 命名管道
| |
相当于在磁盘中创建了一个名为myfifo的文件,两个进程都 open()这个文件,通过文件描述符实现读写,不过只允许单向传输
消息队列
消息队列是内核中的链表结构,进程按 “消息” 为单位发送数据,每个消息有固定格式(类型 + 数据),接收方可以按类型筛选消息。
优点:可以异步通信,数据结构化
缺点:占用内存多
系统调用:用 msgget 函数创建消息队列,再用 msgsnd 发消息,msgrcv 收消息。
共享内存
shmget创建共享内存。让不同进程的虚拟地址空间映射到同一块物理内存,这样进程间就能直接读写这块区域来交换数据,效率最高
优点:读写效率高
缺点:需要实现互斥,避免并发问题
信号量
实现进程的同步/互斥,不传递数据
同步:信号量初始化为0,代表资源数
互斥:信号量初始化为1
信号
有同步信号和异步信号,可以为信号定义一个捕捉函数,或者可以忽略。
SIGKILL无法被捕获或忽略
异步信号
- 编号2-SIGINT-键盘中断(异步信号(中断)),由外部事件触发,与当前指令执行无关
- 编号9-SIGKILL-强制终止
同步信号
- SIGTRAP(调试断点),traps(有意触发的异常)
- SIGSEGV(段错误),faults,由无效内存访问或非法操作码引起,unintentional but recoverable