os 4 线程

ddatsh

dev #os

线程类型

pthread 实现


fork && clone

fork & pthread_create

fork & pthread_create 都调用的 clone

clone系统调用 ,参数包括如 CLONE_VM, CLONE_FILES, CLONE_SIGHAND 等,指定了克隆时需要拷贝的东西

通过 clone 的参数共享进程资源,创建出的就是 LWP

所以用户态创建一个新线程,内核态就对应生成一个新进程

LinuxThreads

所谓 “库” 实现,用clone系统调用,完全在用户级模拟了线程

与POSIX标准在一致性上面存在大量的问题

没有 OS 的支持,线程对 OS 不可见,OS 也不负责对其调度,所有的此类工作由线程库来完成

既然调度不由 OS 进行,SMP机器优势完全用不上。一个进程只能运行在一个CPU上,不管它包含多少线程

2.4内核,用一个内核线程(管理线程)来处理用户态进程中的多个线程的上下文切换(线程切换)

内核中没有线程组的概念,即一个进程的多个线程,必须依靠在pthread库中实现一个额外的线程来管理其他用户线程(即用户程序生成的线程)的建立,退出,资源分配和回收以及线程的切换

当时硬件没有线程寄存器之类来支持多线程

需要在进程的栈中为各线程划分出各自的栈数据所在位置,并且在切换时进行栈数据拷贝

最大问题是内核缺乏对线程间的同步机制的支持,pthread库不得不在底层依靠信号方式来实现同步,线程互斥中的互斥量操作和条件量操作都转换为进程的信号操作

由于内核对线程的无知,必须由管理线程来接收信号后投递给相应的线程,一方面是效率低,另外一方面由于信号产生的不确定性(比如读取一个文件的时候突然出错了),要准确投递所有的信号给正确的线程难以保证

大致过程:新建互斥锁时,在内核里把所有的进程 mask 掉一个特定信号,然后再 kill () 发出一个信号,等某个线程执行锁定时,就用 sigwait () 查看是否有发出的信号,如果没有就等待,有则返回,相当于锁定

解锁时就再 kill () 发出这个信号


NPTL(Native POSIX Thread Library)

创建线程

同样使用 1 * 1 模型,但对应内核的管理结构不再是 LWP。管理进程有进程组,此时提出线程组

在进程管理结构加 TGIP字段

getpid 返回 TGID 字段,线程号返回 PID 字段

当线程PID=TGID ,这个线程就是线程组长,线程组内的所有线程的 TGID 字段都指向线程组长的 PI

NPTL 线程同样用 clone () 创建,flag 参数新增了一个标志位 CLONE_THREAD

内核把 TGID 指向调用者的 PID,原来的 PID 位置填新线程号(也就是以前的进程号)

LinuxThread 因为在内核是一个 LWP 而产生的跟 POSIX 标准不兼容的错误都消除了

同步与互斥

从 LinuxThread 中的线程同步与互斥中可看到使用信号来模拟的缺点

内核增加新互斥同步原语 futex(fast usesapace locking system call)

进程内所有线程共享相同内存空间,所以这个锁可以保存在用户空间。这样对这个锁的操作不需要每次都切换到内核态,大大加快了存取的速度

NPTL 提供的线程同步互斥机制都建立在 futex 上,所以无论在效率上还是咋对程序的外部影响上都比 LinuxThread 的方式有了很大的改进

信号处理

此时因为同一个进程内的线程都属于同一个进程,所以信号处理跟 POSIX 标准统一

发SIGSTP 信号给进程,此进程的所有线程都会停止

因为所有线程内用同样的内存空间,所以对一个 signal 的 handler 都是一样的

但不同的线程有不同的管理结构所以不同的线程可以有不同的 mask。这一段对 LinuxThread 也成立

管理线程

线程创建与结束的管理都由内核负责