Linux 线程实现机制分析(7)

6.LinuxThreads的不足

由于Linux内核的限制以及实现难度等等原因,LinuxThreads并不是完全POSIX兼容的,在它的发行README中有说明。

1)进程id问题

这个不足是最关键的不足,引起的原因牵涉到LinuxThreads的"一对一"模型。

Linux内核并不支持真正意义上的线程,LinuxThreads是用与普通进程具有同样内核调度视图的轻量级进程来实现线程支持的。这些轻量级进程拥有独立的进程id,在进程调度、信号处理、IO等方面享有与普通进程一样的能力。在源码阅读者看来,就是Linux内核的clone()没有实现对CLONE_PID参数的支持。

在内核do_fork()中对CLONE_PID的处理是这样的:

if (clone_flags & CLONE_PID) { if (current->pid) goto fork_out; }  

这段代码表明,目前的Linux内核仅在pid为0的时候认可CLONE_PID参数,实际上,仅在SMP初始化,手工创建进程的时候才会使用CLONE_PID参数。

按照POSIX定义,同一进程的所有线程应该共享一个进程id和父进程id,这在目前的"一对一"模型下是无法实现的。

2)信号处理问题

由于异步信号是内核以进程为单位分发的,而LinuxThreads的每个线程对内核来说都是一个进程,且没有实现"线程组",因此,某些语义不符合POSIX标准,比如没有实现向进程中所有线程发送信号,README对此作了说明。

如果核心不提供实时信号,LinuxThreads将使用SIGUSR1和SIGUSR2作为内部使用的restart和cancel信号,这样应用程序就不能使用这两个原本为用户保留的信号了。在Linux kernel 2.1.60以后的版本都支持扩展的实时信号(从_SIGRTMIN到_SIGRTMAX),因此不存在这个问题。

某些信号的缺省动作难以在现行体系上实现,比如SIGSTOP和SIGCONT,LinuxThreads只能将一个线程挂起,而无法挂起整个进程。

3)线程总数问题

LinuxThreads将每个进程的线程最大数目定义为1024,但实际上这个数值还受到整个系统的总进程数限制,这又是由于线程其实是核心进程。

在kernel 2.4.x中,采用一套全新的总进程数计算方法,使得总进程数基本上仅受限于物理内存的大小,计算公式在kernel/fork.c的fork_init()函数中:

max_threads = mempages / (THREAD_SIZE/PAGE_SIZE) / 8  

在i386上,THREAD_SIZE=2*PAGE_SIZE,PAGE_SIZE=2^12(4KB),mempages=物理内存大小/PAGE_SIZE,对于256M的内存的机器,mempages=256*2^20/2^12=256*2^8,此时最大线程数为4096。

但为了保证每个用户(除了root)的进程总数不至于占用一半以上物理内存,fork_init()中继续指定:

init_task.rlim[RLIMIT_NPROC].rlim_cur = max_threads/2; init_task.rlim[RLIMIT_NPROC].rlim_max = max_threads/2;  

这些进程数目的检查都在do_fork()中进行,因此,对于LinuxThreads来说,线程总数同时受这三个因素的限制。

4)管理线程问题

管理线程容易成为瓶颈,这是这种结构的通病;同时,管理线程又负责用户线程的清理工作,因此,尽管管理线程已经屏蔽了大部分的信号,但一旦管理线程死亡,用户线程就不得不手工清理了,而且用户线程并不知道管理线程的状态,之后的线程创建等请求将无人处理。

5)同步问题

LinuxThreads中的线程同步很大程度上是建立在信号基础上的,这种通过内核复杂的信号处理机制的同步方式,效率一直是个问题。

6)其他POSIX兼容性问题

Linux中很多系统调用,按照语义都是与进程相关的,比如nice、setuid、setrlimit等,在目前的LinuxThreads中,这些调用都仅仅影响调用者线程。

7)实时性问题

线程的引入有一定的实时性考虑,但LinuxThreads暂时不支持,比如调度选项,目前还没有实现。不仅LinuxThreads如此,标准的Linux在实时性上考虑都很少。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/27453.html