进程与线程
进程
每个进程对应一个运行中的程序
进程的内存空间布局
- 自顶向下依次(顶为最大地址,底为最小地址)为:
- 内核代码及数据
- 处在
- 内核栈
- 用户栈
- 自顶向下扩展
- 栈底在高地址,栈顶在低地址
- 代码库(只读)
- 用户堆
- 自底向上扩展
- 堆顶在高地址
- 数据与代码段
- 内核代码及数据
进程控制块 PCB
Linux 4.14中PCB对应的数据结构task_struct包含的部分:
struct task_struct {
// 进程状态
volatile long state;
// 虚拟内存状态
struct mm_struct *mm;
// 进程标识符
pid_t pid;
// 进程组标识符
pid_t tgid;
// 进程间关系
struct task_struct __rcu *parent;
struct list_head children;
// 打开的文件
struct files_struct *files;
// 其他状态(如上下文)
...
}
进程操作
进程的创建:fork
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
- 返回值:
- 0: 子进程
-
0:父进程
- <0:出错
进程的执行:exec
#include <unistd.h>
int execve(const char *pathname, char *const argv[], char *const envp[]);
- 当execve被调用时,操作系统完成以下:
- 将pathname的可执行文件的数据段和代码段载入当前进程的地址空间中
- 重新初始化堆和栈
- 将PC设置到可执行文件的入口点
进程树
- init 进程
操作系统的第一个进程
之后所有的进程都是由他直接或间接创建出来的。 - kthreadd 进程
进程是第二个进程
所有有内核创建和管理的进程都是由它fork出来的 - login 进程
最后由init创建出来,要求用户登录
之后会从login进程fork出来bash进程
进程管理
僵尸进程
- 子进程结束了,但是父进程没有回收(wait)
- 内核会为僵尸进城保留其进程描述符(PID)和终止时信息(waitpid中的status)
孤儿进程 - 父进程退出后,子进程还在运行
- 孤儿进程会被init进程(进程号为1)收养,并由init进程对他们完成状态收集工作。
进程组和会话
- 进程组是进程的集合
- task_struct中的tgid即为进程组标识符
- 进程组的作用体现在对信号的处理上
- 可以通过killpg想一个进程组发送信号,这个信号会发送给进程组内所有的进程。
- 会话是进程组的集合
- 会话和进程组主要用于shell环境中的进程管理。
fork过时了吗?
fork的优缺点
优点
- 设计上简洁 —— 不需要传递任何参数
- fork强调进程与进程之间的联系(父子进程)
局限 - fork的实现已经越来越复杂
- fork的性能太差
- fork需要创建出原进程的一份拷贝,内存拷贝效率太低
- fork存在潜在安全漏洞
替代
- 合二为一:posix_spawn
- 限定场景:vfork
- 精密控制:rfork/clone
NOTE clone 可以认为是fork的“精密控制”版本
线程
多线程的地址空间布局
重点 线程只有栈不同,其他区域都共享
对于同一个进程的多个线程:
- 内核栈和用户栈分离
- 其他区域共享
线程控制块TCB
- 线程上下文context是上下文切换的基础。
- 线程上下文主要指的是当前处理器中大部分寄存器的值。其中包括:
- 程序计数器PC
- 通用寄存器
- 特殊寄存器
存储CPU当前的一些硬件状态和配置,如页表地址等
线程操作
- 以POSIX线程库为例
线程创建:pthread_create
#include <pthread.h>
int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine) (void *),
void *arg);
- pthread_create是通过clone实现的
线程的本质
- 通过一些参数,clone实际上创建出一个从属于原进程、与原进程共享大量数据结构、拥有私有栈的实例,而这个实例就对应一个线程。
纤程
目前,主流的操作系统都是一对一的线程模型,用户态和内核态线程具有一对一关系,可以认为用户态线程的执行几乎完全受到操作系统调度器的管理。
然而,1. 应用程序相比于操作系统调度器对线程的语义和执行状态更加了解,能够做出更优的调度策略;2. 用户态线程相比于内核态线程要更加轻量级、创建和切换的开销要低得多。
在这样的背景下
- 操作系统开始提供更多对用户态与内核态多对一模型的用户态线程:即纤程(fiber)
- 除操作系统外,程序语言也提供了对纤程的支持,称为:协程(coroutine)