Linux2.6内核实现的是NPTL

NPTL是一个1×1的线程模型,即一个线程对于一个操作系统的调度进程,优点是非常简单。而其他一些操作系统比如Solaris则是MxN的,M对应创建的线程数,N对应操作系统可以运行的实体。(N<M),优点是线程切换快,但实现稍复杂

Linux2.6内核实现的是NPTL线程模型,依然是用进程来模拟线程,但新引入了线程组(进程组)的概念,使得实现效率更好。

在2.4内核中,不存在线程组的概念,当运行一个多线程得程序时,使用ps命令,可以看到有许多个进程,在ps命令看来,线程基本上是等同于进程,在信号处理中,情况也是如此,只有指定进程号的线程,可以接收到信号。在2.6内核中引入了线程组的概念,在2.6内核中,如果使用ps命令看,一个多线程的进程,只会显示一个进程,在给线程组中的任何一个线程发送信号的时候,整个线程组中的进程都能收到信号。
在内核task_struct中相关字段如下(位于include/linux/sched.h):

代码全选

937   struct task_struct 
{

       
 ...

993    
 pid_t pid;

994 
    pid_t tgid;

         ...
1013     struct task_struct *group_leader;   /* threadgroup leader
*/

       
 ...

1017   
 struct list_head thread_group;

         ...
1198  };

pid,从字面上是process id,但其实是thread id。
tgid,从字面上,应该是thread group
id,也就是真正的process
id。
这一点,可以从系统调用getpid和gettid中看出来(位于kernel/timer.c)。

代码全选

954   asmlinkage long
sys_getpid(void)

955   {
956      return current->tgid;
957   }
1100  asmlinkage long
sys_gettid(void)

1101  {
1102     return current->pid;
1103  }

group_leader字段,指向线程组中的第一个线程,创建第一个线程的时候,group_leader指向自己,创建其后的线程时,指向第一个线程的task_struct结构;
thread_group,当前进程所有线程的队列,对于group_leader,这是个队列头,对于其后的进程而言,通过这个字段,挂入队列中,可以通过此队列,遍历所有线程。
线程组中各个线程的关系,是在do_fork中设定的,具体的代码在copy_process中(位于kernel/fork.c):代码全选

959 
 copy_process()

960   {
         ...
1112     p->tgid = p->pid;
1113     if (clone_flags &
CLONE_THREAD)

1114        p->tgid = current->tgid;
         ...
1181     p->group_leader =
p;

1182   
 INIT_LIST_HEAD(&p->thread_group);

         ...
1234     if (clone_flags &
CLONE_THREAD) {

1235        p->group_leader =
current->group_leader;

1236        list_add_tail_rcu(&p->thread_group,
&p->group_leader->thread_group);

      ...
1252     }

1254     if (likely(p->pid))
{

          
 ...

1259      
 if (thread_group_leader(p)) {

             
 ...

1266      
    list_add_tail_rcu(&p->tasks,
&init_task.tasks);

               ...
1268        }
            ...
1271     }
         ...
1320  }

1113-1114行说明在创建线程时,从父进程获取tgid,表明他们在同一个线程组中;1181-1182则对group_leader和thread_group初始化,对于第一个线程,则group_leader就是它自己;1234-1236行,将新创建的线程的group_leader设置成为父进程得group_leader,无论父进程是不是线程组中的第一个线程,它的group_leader都是指向第一个线程的task_struct,同时通过thread_group字段,挂入到第一个线程的thread_group队列中;1266行表明只有线程组中的第一个线程,才会通过tasks字段,挂入到init_task队列中。
在引入线程组概念后,退出部分也引入了一个新的系统调用exit_group(位于kernel/exit.c)

1055  NORET_TYPE
void
1056  do_group_exit(int
exit_code)
1057  {
1058     BUG_ON(exit_code
& 0x80); /* core dumps don‘t get here
*/
1059
1060     if
(current->signal->flags & SIGNAL_GROUP_EXIT)
1061   
    exit_code = current->signal->group_exit_code;
1062   
 else if (!thread_group_empty(current)) {
1063        struct
signal_struct *const sig = current->signal;
1064      
 struct sighand_struct *const sighand =
current->sighand;
1065      
 spin_lock_irq(&sighand->siglock);
1066        if
(sig->flags & SIGNAL_GROUP_EXIT)
1067           /*
Another thread got here before we took the lock.  */
1068      
    exit_code = sig->group_exit_code;
1069        else
{
1070           sig->group_exit_code =
exit_code;
1071         
 zap_other_threads(current);
1072        }
1073 
      spin_unlock_irq(&sighand->siglock);
1074 
 }
1075
1076   
 do_exit(exit_code);
1077     /* NOTREACHED
*/
1078  }

在1060行中,current->signal其实是线程组中所有线程共享的,对于调用exit_group的那个线程,如果是一个多线程的进程,就会进入1062-1074这部分代码,如果是单线程,则直接进入do_exit退出进程。这部分代码的主要操作在zap_other_threads中(位于kernel/signal.c)

void zap_other_threads(struct
task_struct *p)
982 
 {
983      struct task_struct *t;
984 
 
985      p->signal->flags =
SIGNAL_GROUP_EXIT;
986      p->signal->group_stop_count =
0;
987
988      if
(thread_group_empty(p))
989       
 return;
990
991      for (t = next_thread(p); t
!= p; t = next_thread(t)) {
992         /*
993 
        * Don‘t bother with already dead threads
994         
*/
995         if (t->exit_state)
996       
    continue;
997
998         /* SIGKILL will be
handled before any pending SIGSTOP */
999       
 sigaddset(&t->pending.signal, SIGKILL);
1000      
 signal_wake_up(t, 1);
1001     }
1002 
}

next_thread定义在include/linux/sched.h中,如下

651  static inline struct task_struct
*next_thread(const struct task_struct *p)
1652  {
1653     return
list_entry(rcu_dereference(p->thread_group.next),
1654      
      struct task_struct, thread_group);
1655 
}

其实就是通过task_struct中的thread_group队列来遍历线程组中的所有线程。
在其中,会在signal->flags中设置SIGNAL_GROUP_EXIT,同时,搜索线程组中所有进程,在每个线程中挂上一个SIGKILL信号,这样,当那些线程调度到运行的时候,就会处理SIGKILL信号,对于SIGKILL信号的处理,会调用do_group_exit,不过,当这次调用到do_group_exit的时候,将运行到1061行,然后就到了1076行的do_exit。这样,当线程组中的每个线程都运行过一遍后,整个线程组就退出了。

POSIX Thread Library (NPTL)使Linux内核可以非常有效的运行使用POSIX线程标准写的程序。这里有一个测试数据,在32位机下,NPTL成功启动100000个线程只用了2秒,而不使用NPTL将需要大约15分钟左右的时间。

历史

在内核2.6以前的调度实体都是进程,内核并没有真正支持线程。它是能过一个系统调用clone()来实现的,这个调用创建了一份调用进程的拷贝,跟fork()不同的是,这份进程拷贝完全共享了调用进程的地址空间。LinuxThread就是通过这个系统调用来提供线程在内核级的支持的(许多以前的线程实现都完全是在用户态,内核根本不知道线程的存在)。非常不幸的是,这种方法有相当多的地方没有遵循POSIX标准,特别是在信号处理,调度,进程间通信原语等方面。

很显然,为了改进LinuxThread必须得到内核的支持,并且需要重写线程库。为了实现这个需求,开始有两个相互竞争的项目:IBM启动的NGTP(Next
Generation POSIX
Threads)项目,以及Redhat公司的NPTL。在2003年的年中,IBM放弃了NGTP,也就是大约那时,Redhat发布了最初的NPTL。

NPTL最开始在redhat linux 9里发布,现在从RHEL3起内核2.6起都支持NPTL,并且完全成了GNU C库的一部分。

设计

NPTL使用了跟LinuxThread相同的办法,在内核里面线程仍然被当作是一个进程,并且仍然使用了clone()系统调用(在NPTL库里调用)。但是,NPTL需要内核级的特殊支持来实现,比如需要挂起然后再唤醒线程的线程同步原语futex.

NPTL也是一个1*1的线程库,就是说,当你使用pthread_create()调用创建一个线程后,在内核里就相应创建了一个调度实体,在linux里就是一个新进程,这个方法最大可能的简化了线程的实现。

除NPTL的1*1模型外还有一个m*n模型,通常这种模型的用户线程数会比内核的调度实体多。在这种实现里,线程库本身必须去处理可能存在的调度,这样在线程库内部的上下文切换通常都会相当的快,因为它避免了系统调用转到内核态。然而这种模型增加了线程实现的复杂性,并可能出现诸如优先级反转的问题,此外,用户态的调度如何跟内核态的调度进行协调也是很难让人满意。

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/guosha/archive/2008/09/22/2960186.aspx

---------------------------------------------------------------------------

有一个问题一直没有想清楚,请教牛人,谢谢。
资源描述:
        系统环境:4个CPU, linux2.6内核,NPTL,SMP。
       
问题:
        (进程是资源单元;线程是执行单元。)
       
一个进程拥有4个线程,这4个线程同时在4个CPU上运行。
        问1:系统的调度单位是进程还是线程呢?(针对linux2.6内核);
 
     
问2:如果调度单位是线程,每个线程都应该运行在同一个进程的上下文中,如果这样的话就需要对进程上下文加锁;同时如果有其他进程的线程也运行在同一个CPU上,那么进程的上下文切换会非常频繁?
 
           如果调度单位是进程,又没有办法解释同一个进程中的4个线程同时跑在4个CPU上?
       
问3:看到一篇文章说“SMP的负载均衡是按进程数计算的”不知道是否正确。
           
 如果正确,同一个进程的所有线程应该分配到同一个CPU上,不解?

答:

问题1:
   
linux内核中的调度单位总是一个进程。但是内核中有对线程的支持。
问题2:
   
既然在linux的线程就是一个进程.就不存在什么所有线程都在同一进程上下文。而且这个观点是错误的。一个线程是什么?线程只是一个进程的子集。也就是说是一个进程上下文的子集。而且每个线程的上下文的很少有交集。除非两个线程之间需要通信。linux中进程所传建的线程本质就是进程。只不过这些子进程和父进程看到的都是同一地址空间,共享资源。
恢复一个线程的运行只需要恢复线程上下文。既然是线程在运行,何来进程上下文? 线程的实现方式不同.但原理本质一样。
既然linux的调度单位是进程,而线程又是以进程实现的.所以进程的四个线程在4个CPU上同时运行是可能的.只要不存在互斥。

问题3:
   
这个是高级问题... 不清楚...楼主可以goolge下.应该很多文章和论文的。

Linux2.6内核实现的是NPTL

时间: 2024-11-05 13:33:47

Linux2.6内核实现的是NPTL的相关文章

Linux内核编程:Linux2.6内核源码解析_进程遍历 &nbsp; &nbsp; &nbsp; &nbsp;

/*     *File    : test.c   *Author  : DavidLin        *Date    : 2014-12-07pm        *Email   : [email protected] or [email protected]        *world   : the city of SZ, in China        *Ver     : 000.000.001        *history :     editor      time    

Linux2.6 内核的 Initrd 机制解析

Linux 的 initrd 技术是一个非常普遍使用的机制,linux2.6 内核的 initrd 的文件格式由原来的文件系统镜像文件转变成了 cpio 格式,变化不仅反映在文件格式上, linux 内核对这两种格式的 initrd 的处理有着截然的不同.本文首先介绍了什么是 initrd 技术,然后分别介绍了 Linux2.4 内核和 2.6 内核的 initrd 的处理流程.最后通过对 Linux2.6 内核的 initrd 处理部分代码的分析,使读者可以对 initrd 技术有一个全面的认

mini2440 官方linux-2.6内核文件zImage编译

官方linux-2.6内核文件zImage编译 by HYH | 2018 年 1 月 1 日 下午 5:34 一.说明 1.编译linux内核需要make和arm的交叉编译工具链(gcc),由于linux-2.6较老,采用友善之臂官方的gcc即可,不要采用最新的gcc. 2.编译过程中需要ncurses和zlib的支持库.对于Debian系的linux用一下指令即可: apt-get install ncurses-dev zlib1g-dev 3.编译过程中还可能用到tar和任意一种文本编辑

Linux2.6内核--进程调度理论

从1991年Linux的第1版到后来的2.4内核系列,Linux的调度程序都相当简陋,设计近乎原始,见0.11版内核进程调度.当然它很容易理解,但是它在众多可运行进程或者多处理器的环境下都难以胜任. 正因为如此,在Linux2.5开发系列的内核中,调度程序做了大手术.开始采用了一种叫做O(1)调度程序的新调度程序——它是因为其算法的行为而得名的.它解决了先前版本Linux调度程序的许多不足,引入了许多强大的新特性和性能特征.O(1)调度程序虽然对于大服务器的工作负载很理想,但是在有很多交互程序要

【转载】linux2.6内核initrd机制解析

题记 很久之前就分析过这部分内容,但是那个时候不够深入,姑且知道这么个东西存在,到底怎么用,来龙去脉咋回事就不知道了.前段时间工作上遇到了一个initrd的问题,没办法只能再去研究研究,还好,有点眉目,索性整理了一下. 网络上流传着很多关于ramdisk.initrd的各种版本的分析,我的这篇源于对他们的理解,非常感谢那些前辈的无私奉献,要不然我们这些晚辈学起东 西来该是多么艰难呀.在这里需要特别声明的是如果文中有引用了您的思想而没有给出参考文献,请您原谅我的疏忽.晚辈就是需要站在像您这种巨人的

从串口驱动的移植看linux2.6内核中的驱动模型 platform device &amp; platform driver【转】

转自:http://blog.csdn.net/bonnshore/article/details/7979705 写在前面的话: 博主新开了个人站点:你也可以在这里看到这篇文章,点击打开链接 本文是博主学习linux驱动移植整整两周后通过查阅资料并结合自己的一些观察所做的一些记录,旨在作为日后温习材料,由于博主尚无太多经验文中内可能会出现一些谬误,希望看到的热心朋友能拍砖指正. 在我前面的日中已经提到了我所做的SC16C550的串口移植,本来是没有什么技术难度,但对于新人来讲了解内核代码的结构

Linux2.6 内核中结构体初始化(转载)

转自:http://hnniyan123.blog.chinaunix.net/uid-29917301-id-4989879.html 在Linux2.6版本的内核中,我们经常可以看到下面的结构体的定义和初始化.这在以前的C语言书中是极少见到的.下面的一个结构体来自到Linux内核中的一部分.在这个结构体中我们可以看到有普通的整型变量,也有函数的指针. struct net_proto_family { int family; int (*create)(struct net *net, st

零敲牛皮糖:Linux2.6内核源码解析_进程遍历

/* *File : test.c *Author : DavidLin *Date : 2014-12-07pm *Email : [email protected] or [email protected] *world : the city of SZ, in China *Ver : 000.000.001 *history : editor time do * 1)LinPeng 2014-12-07 created this file! * 2) */ #include <linux

linux2.6内核netfilter架构分析

1.2.6内核的netfilter与2.4的有很大不同: ChangeLog-2.6.15 中有下面这样的描述: commit 9fb9cbb1082d6b31fb45aa1a14432449a0df6cf1 Author: Yasuyuki Kozakai <[email protected]> Date:   Wed Nov 9 16:38:16 2005 -0800     [NETFILTER]: Add nf_conntrack subsystem.         The exis