uclibc中LinuxThread模型与nptl线程库

一、uclibc中posix thread实现

在早期Linux内核对象线程支持不是那么贴心的时候,用户态的posix线程实现也很蹩脚,通俗的说,就是上梁不正下梁歪。对于Unix下重要的posix线程库,libc的实现是通过所谓的LinuxThread模型来实现的,这个是试图在内核不支持线程的基础上模拟一个多线程,结果就是一个四不像,勉强可以用,但是很别扭。好消息是在2.6内核完善了对于线程的支持之后,用户态也咸与维新了,posix线程库的实现也更加妖娆了,这个实现就是现在大家常说的NPTL(Native Posix Thread Library),这个库用起来是比较顺手的,而且应该也在较早的版本中提供了,例如我刚一开始接触的就是NTPL线程库,没有机会接触这个古董的LinuxThread线程库实现。这就有点遗憾,就好像《围城》里三闾大学的督学,张口闭口一个“兄弟我在英国的时候”,那还是很有震撼力的。在计算机中,你要是说,兄弟我当年玩DOS的时候时候,或者兄弟我玩LinuxThread的时候……,那也是很能让人肃然起敬的。

不过皇天不负有心人,前一段时间这个古董的LinuxThread就让我逮了个正着,抓住了青春的尾巴,和它来了个亲密接触。事情还要从我们编译busbox说起,我们用的是uclibc实现,这个玩意据说编译出来的可执行文件比较小,而且偏偏版本有比较老,具体有多老呢?用的是uclibc0.9.30版本,这个版本还没有加入NPTL线程库实现,所以使用的是LinuxThread线程库,这也没有什么,偏偏它最后就出问题了,所以轻轻的围观了一下。
二、从pthread_attr_init开始
这个是Posix中一个标准的线程属性初始化函数,但是具体该如何实现,可能是POSIX没有说,没有说的地方就是大家发挥主观能动性的时候,就好像某些影视作品中的马赛克,也就是不透明的意思,计算机中称为implementation defined。这个地方uclibc的实现是无微不至的,做了很多功课,对整个结构中成员逐个初始化,兢兢业业。
uClibc-0.9.33\libpthread\linuxthreads\attr.c
int __pthread_attr_init(pthread_attr_t *attr)
{
  size_t ps = __getpagesize ();

  attr->__detachstate = PTHREAD_CREATE_JOINABLE;
  attr->__schedpolicy =  SCHED_OTHER ;
  attr->__schedparam.sched_priority = 0;
  attr->__inheritsched =  PTHREAD_EXPLICIT_SCHED ;
相对来说,glibc的实现就相对粗犷一些,其代码为
glibc-2.7\nptl\pthread_attr_init.c
int
__pthread_attr_init_2_1 (attr)
     pthread_attr_t *attr;
{
  struct pthread_attr *iattr;

  /* Many elements are initialized to zero so let us do it all at
     once.  This also takes care of clearing the bytes which are not
     internally used.  */
  memset (attr, '\0', __SIZEOF_PTHREAD_ATTR_T);
可以看到,glibc中的实现用一个词来概括,就是“简单粗暴”,不管三七二十一,直接来个清零,这一点将会对新派生的进程和父进程的继承性产生决定性影响。总体来说,就是当使用pthread_attr_init出的属性传递给pthread_create创建线程时,uclibc创建的线程是雷打不动的,就是普通进程,优先级为正常;而glibc创建的线程则会集成pthread_create调用者线程的优先级。这样一看,比较简单,好像是竹筒倒豆子——直来直去的,但是往往现象没有这里说明的这么显而易见。例如你肚子上长个瘤,跑了各大医院,问了各大老中医都没有机会而绝望时,有人告诉你,可能是裤子系的太紧,被带扣给格的一样,那种恍然大悟。至于是什么现象,三言两语说不清,所以此处省略一万字。
三、manager线程
这个是LinuxThread一个重要特点,这个通过ps看系统线程的时候,你会发现如果通过pthread_create创建一个或者多个线程之后,LinuxThread会买N送一,会额外创建一个manager线程,这样看到的系统中的线程数和调用的pthread_create次数多两个,多的两个一个是主线程,另一个是manager线程。这个线程的创建时在pthread_create执行的时候才创建的,如果不执行就不创建,执行第一次时创建。
uClibc-0.9.33\libpthread\linuxthreads\pthread.c
int __pthread_create(pthread_t *thread, const pthread_attr_t *attr,
             void * (*start_routine)(void *), void *arg)
{
  pthread_descr self = thread_self();
  struct pthread_request request;
  int retval;
  if (__builtin_expect (__pthread_manager_request, 0) < 0) {这个地方会判断manager线程是否已经创建,如果没有创建,则创建之。
    if (__pthread_initialize_manager() < 0) return EAGAIN;
  }
四、线程以进程形式展现
如果是使用uclibc中pthread_create创建的线程,通过ps可以看到,这些线程都是以进程的形式存在的,这一点乍一看,很诡异,当然也有很多人看都没看一眼,所以也没觉得诡异。这个实现就是早期内核对线程支持不够完善导致的一个问题,这个属性就是CLONE_THREAD属性,uclibc中执行clone系统调用的时候没有传递这个选项,为什么没有传递,可能就是因为当时内核还不支持这个属性。uclibc中创建代码为
uClibc-0.9.33\libpthread\linuxthreads\manager.c
      pid = __clone(pthread_start_thread_event, stack_addr,
            CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_SYSVSEM |
            __pthread_sig_cancel, new_thread);
Glibc的创建为
#define  CLONE_SIGNAL         (CLONE_SIGHAND |  CLONE_THREAD )
  int clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES |  CLONE_SIGNAL
             | CLONE_SETTLS | CLONE_PARENT_SETTID
             | CLONE_CHILD_CLEARTID | CLONE_SYSVSEM
#if __ASSUME_NO_CLONE_DETACHED == 0
             | CLONE_DETACHED
#endif
             | 0);

五、manager线程优先级动态调整
它作为管理者,优先级自然要比它创建的所有的线程的优先级要高,这样manager执行的时候就不会被其它线程抢占,这一点作为一个manager还是很给力的。具体高多少呢?其实也没必要高很多,只要高一个就可以了,这一点manager还是比较谦虚的,没有说直接提到最高的99优先级。
uClibc-0.9.33\libpthread\linuxthreads\manager.c
/* Adjust priority of thread manager so that it always run at a priority
   higher than all threads */

void __pthread_manager_adjust_prio(int thread_prio)
{
  struct sched_param param;

  if (thread_prio <= manager_thread->p_priority) return;
  param.sched_priority =
    thread_prio < __sched_get_priority_max(SCHED_FIFO)
    ?  thread_prio + 1  : thread_prio;
  __sched_setscheduler(manager_thread->p_pid, SCHED_FIFO, &param);
  manager_thread->p_priority = thread_prio;
}

你可能感兴趣的:(uclibc中LinuxThread模型与nptl线程库)