现状(What)
基于同一个task_struct结构体,这个结构体既能代表进程也能代表线程,同时这个结构体也是内核调度的最小单位。
这个结构体有2个字段,pid字段用于唯一标识一个task_struct(用于内核调度), tgid字段标记一组task_struct, tg是thread group的首字母。
tgid=pid表示进程(主线程),tgid!=pid表示子线程。
Q: 这2个字段的值怎么来的呢,这个就要说的进程和子线程的创建过程。
当一个新进程被创建时,它作为主线程代表这个进程(pid=tgid)。当主线程用clone方法启动另一个线程时,该线程会获得自己的pid(使得自己可以被内核独立调度),tgid继承自主线程的tgid),
这样的话,子线程和主线程的pid是不同的,tgid是相同的(就是主线程的pid)。
Linux中的线程和进程的区别
- 创建时调用的函数。
创建进程用fork or exec,创建内核线程(LWP)用clone。
- 资源共享。虚拟内存空间,进程基础数据,打开的文件等。
- 调度性能。线程上线文切换时,虚拟地址空间是不变的,但是进程上下文切换时,是需要重新映射虚拟地址空间。
为什么是现在这种情况(Linux线程发展)(How,Why) // 参考附录3
- 一直以来, linux内核并没有线程的概念. 每一个执行实体都是一个task_struct结构, 通常称之为进程.
- Linux内核在 2.0.x版本就已经实现了轻量级进程LWP,clone创建轻量级进程, fork创建普通进程。
- 后来为了引入多线程,Linux2.0~2.4实现了俗称LinuxThreads的多线程
这种实现本质上是一种LWP的实现方式,即通过轻量级进程来模拟线程,内核并不知道有线程这个概念,在内核看来,都是进程。
此时:A程序创建了10个线程, 那么shell下执行ps时将看到11个A的进程。(因为还有个管理线程) - 到了2.6, 是NPTL的方式
在linux 2.6中, 内核有了线程组的概念, task_struct结构中增加了一个tgid(thread group id)字段. 如果这个task是某进程的一个"主线程", 则它的tgid等于pid, 否则tgid等于进程的pid(即主线程的pid).
在clone系统调用中, 传递CLONE_THREAD参数就可以把新进程的tgid设置为父进程的tgid(否则新进程的tgid会设为其自身的pid).
有了tgid, 内核或相关的shell程序就知道某个tast_struct是代表一个进程还是代表一个线程, 也就知道在什么时候该展现它们, 什么时候不该展现(比如在ps的时候, 线程就不要展现了).
PS: 同一个task_struct结构体,如果tgid==pid是进程,否则就是线程。
此时:A程序创建了10个线程, 那么shell下执行ps时,应该只能看到1个进程(基于我的推测,未证实). - NGPT
上述LinuxThreads和NPTL都是1:1模型(1个用户线程对应1个内核级线程),而NGPT则打算实现M:N模型(M个线程对应N个内核级线程).
其他问题
Q: 内核如何区分进程和线程呢 // 参考附录1
A: getpid(获取进程id): 从原理返回pid字段,变成返回tgid。
Q: 线程切换为什么比进程切换快吗?
A: 更确切的说,是同一个进程的线程切换要快,因为他们共享了一些内存段(.text代码段,.bss,data数据段,文件描述符)。
如果是不同进程的线程切换,那么和不同进程的切换,是没有理由更快的。