进程
- 进程是程序在资源集合上的一次运行
- 进程= 代码+外设+内存+CPU(使用周期划分时间片轮流分割,满足并发)
- 进程 = 线程集合+资源集合
- 进程 = 进程PCB+资源+全局data+code +(线程PCB+线程用户栈/核心栈)*n
引入线程的原因
- 把进程的两项功能
“独立分配资源”
和“被调度分派执行”
分离开来。前一任务由进程完成,后一项交给线程这个实体 - 多线程切换只需改变堆栈和寄存器。地址空间不变
- 线程组成部分有:线程唯一标识符、线程状态信息(运行态、就绪态、阻塞态和终止态)。
- 线程是一条执行路径,它有独立的程序计数器,未运行时保护线程上下文。
- 线程有执行栈和存放局部变量的私用存储空间。
- 下图为同源多线程结构的进程
上图 进程虚存映像
*线程无挂起状态,线程不是资源拥有单位,挂起无意义。进程挂起后被对换出内存,它的所有线程因共享地址空间也必须全部对换出去。
- 线程封装执行信息:状态信息、寄存器、执行栈(用户栈指针与核心栈指针)、局部变量、过程调用参数、返回值等私有部分。
- 进程封装管理信息:对指令代码、全局数据、寄存器、打开的文件和信号量等共享部分。
Linux 进程描述符
task_struct{
pid; uid; gid;
state;
flag;
*thread_info;
prio ; //static_prio normal_prio
policy; //run_list sleep_avg *arry rt_priority time_slice
*mm; *active_mm;
通信信息:管道,消息队列,共享内存
*files; *file; total_link_count;
*signal; //blocked signal sighand pending
*real_parent; children ; sibling;
real_timer; utime; stime; thread_group; 哈希链表; ptrace;
}
- 在Linux系统中,线程被认为是一个与其他进程共享资源的进程,当做进程来实现。每个线程也用task_struck结构体描述。
- 系统调用clone() 创建线程,带有多个标志,允许定义父子进程所共享的内容,如果不定义内容共享,则clone()和fork() 完全相同。
因而,Linux系统中线程就是共享上下文的进程。
创建线程
实现clone()的主要工作为:对于task_struck结构中的fs指针、files指针、sig指针和mm指针不复制,仅将数据结构成员中的使用计数器count +1
这样只有当父进程中相关数据结构成员的count =0 才释放这些数据结构成员所占用的空间。
此外,父子进程共享内存地址空间,copy_on_write策略。
即仅当父进程或子进程对虚存地址进行写操作时,才为子进程的指针所指数据结构分配页面,并复制父进程mm指针所指内容
调用内核函数do_fork()
alloc_pidmap()
//子进程分配pidcopy_process()
//它完成创建的大部分工作,如调用dup_task_struct()
创建新的task_struck结构,调用alloc_task_struck()
为子进程描述符分配空间alloc_thread_info()
//为子进程分配thread_info 结构和核心栈kmem_cache_alloc()
//从slab分配task_struct结构,复制父进程的task_struct信息和thread_info信息audit_alloc()
//分配和初始化进程记账信息copy_semundo()、copy_files() 、copy_fs() 、copy_signal() 、copy_mm() 、copy_namespace()
//分别复制并继承父进程的信号量信息、文件信息、信号处理信息和进程地址空间和名字空间。copy_thread()
//初始化子进程核心栈。调用sched_fork()
设置进程调度信息,把子进程状态设置为TASK_RUNNING,并将父进程时间片余额的一半分给它,调用set_task_cpu设置进程所在处理器编号且父子进程在同一处理器上。SET_LINKS
//子进程插入进程双向链表。attach_pid()
把子进程插入哈希链表- 如果
clone_flags
包含CLONE_STOPPED标志,把子进程状态改为TASK_STOPPED;否则调用wake_up_new_task()
,它又调用_activate_task()
将子进程加入运行队列。 - 如果
clone_flags
包含CLONE_VFORK标志,把父进程设置为阻塞状态直到子进程释放进程地址空间。
创建成功后,内核让子进程先运行,子进程立即调用exec()
避免copy_on_write额外开销。
进程终止
do_exit()
:
- 设置标志,表明进程正在被销毁。
- 若进程在定时器队列或信号量队列中等待,则将其移出。
exit_mm()、exit_files()、_exit_fs() 、exit_sem()、exit_sighand()、exit_namespace()、exit_thread()
//释放各种资源,其中exit_mm()
释放进程地址空间,检测该进程是否由vfork()创建,若是则唤醒父进程。释放资源时,先将其共享计数器 -1 若不为0 表名有其他进程共享该资源,此时直接返回;否则才真正释放资源。- 设置进程退出码,
exit_notify()
处理该进程与其父进程和子进程的各种关系,设置状态TASK_ZOMBIE schedule()
调度切换其他进程
do_exit()
结束,但留下task_struct结构。为了留下pid,父进程会wait()
检查子进程是否结束,wait()
回收TASK_ZOMBIE状态的进程task_struck和pid,若父进程先于子进程结束。init进程会变成子进程的父进程,使task_struct总能回收。