进程和线程
线程和进程
linux中,进程和线程都用task_struct结构体表示,只是进程和线程的区别在于task_struct中的mm和files等资源是否共享。
记录层面
task_struct中有两个字段记录的是编号
- pid_t pid; /* process id */ task编号,唯一标识一个task
- pid_t tgid; /* thread group id */ 线程组编号,同一进程中的线程tgid相同
系统调用层面
getpid返回的是task的tgid,gettid返回的是task的pid(和posix的thread id不同)
用户层面
ps显示的PID是实际的tgpid,ps -aL显示的LWP是实际的pid,top -H显示的PID是实际的pid
创建上
进程使用fork函数(使用系统调用fork),线程使用pthread_create函数(使用系统调用clone),二者内部实现都是调用do_fork函数,只是传入的参数不同。
创建进程的flag是SIGCHLD,创建线程的flag包括CLONE_VM,CLONE_FS,CLONE_FILES,CLONE_SIGNAL,CLONE_THREAD,CLONE_SYSVSEM,CLONE_SETTLS,CLONE_PARENT_SETTID和CLONE_CHILD_CLEARTID。
CLONE_VM:和父进程共享内存地址空间,因此共用父进程的mm_struct结构
CLONE_FS:和父进程共享文件系统信息,因此共用父进程的fs_struct结构
CLONE_FILES:和父进程共享文件描述符,因此共用父进程的files_struct结构
而区分进程还是线程的关键在于是否拥有独立的内存空间等资源,因此常说进程是资源分配的基本单位,线程是CPU调度的基本单位。
资源占有
进程占有的资源
- 内存地址空间
- 全局变量,静态变量等
- 打开文件描述符
- 子进程
- 信号量
- 信号处理函数
线程私有
- 线程ID
- 栈
- 寄存器
- 程序计数器
为什么进程切换比线程切换效率低
CPU上下文:CPU寄存器和程序计数器
进程上下文:寄存器,内存地址空间(堆栈,全局变量,静态变量),内核栈等
进程切换时需要保存和恢复更多的上下文,甚至涉及到内存换入换出,
而同一进程的线程切换只需要保存和恢复线程私有数据,比如寄存器和栈。
。
之前不太认可页表切换开销大、进程上下文切换开销大的说法,而是认为主要原因是进程切换时涉及到页表的切换,切换之后TLB就失效了,地址转换需要重新查找页表,而线程切换不需要切换页表。
当时想的是进程只有创建时申请内存,切换时不涉及内存分配变动,且页表也在内存中,页表切换只简单涉及页表寄存器的更改。但实际上进程切换可能伴随着页调度,内存不够时进程内存数据要换出,需要的内容换入,所以这么说也可以。
附录
排查线程CPU占用过高的问题
# 查看进程,获取进程ID
top
# 查看进程的线程,获取线程ID
top -H -p <PID>
# 或者
ps -T -p <PID>
# 查看线程的栈信息
pstack <PID>