精华内容
下载资源
问答
  • Linux下thread编程

    千次阅读 2012-08-27 18:23:02
    Linux下thread编程(一) Sam前些天在提供一个库给别的公司时,因为不喜欢使用pthread_jion等函数,被人骂为垃圾程序。呵呵,之前因为在写多thread程序时,习惯让每个thread都为detach属性,这样他们就可以...

    From: http://blog.sina.com.cn/s/blog_602f87700100dqmk.html


    Linux下thread编程(一)


    Sam前些天在提供一个库给别的公司时,因为不喜欢使用pthread_jion等函数,被人骂为垃圾程序。呵呵,之前因为在写多thread程序时,习惯让每个thread都为detach属性,这样他们就可以自我管理。而不需要再由别人回收资源。呵呵,不说这么多了,把POSIXthread方面的东西记下来吧。

     

    Linux下thread历史(Oldpthread与NPTL):

    Linux创建之初,并不能真正支持thread. LinuxThreads项目使用clone()这个系统调用实现对thread的模拟。在_clone本来的意图是创建一个可定义各种配置的对当前进程的拷贝。LinuxThreads项目则利用了这一点,配置了一个与调用进程拥有相同地址空间的拷贝,把它作为一个thread.所以,常常有人说,linux下面没有进程线程之分,其实就是这个意思。但这个方法也有问题,尤其是在信号处理、调度和进程间同步原语方面都存在问题。另外,这个线程模型也不符合POSIX 的要求。

     

    如果要改进LinuxThread.很明显,需要kernel层的支持。IBM和RedHat分别进行了研究,随着IBM的放弃,RedHat的Native POSIXThread Library(NPTL)就成唯一的解决方案了。这就是NPTL。

     

     

     

     

    LinuxThreads最初的设计相信相关进程之间的上下文切换速度很快,因此每个内核线程足以处理很多相关的用户级线程。这就导致了一对一线程模型的革命。

     

    LinuxThreads 设计细节的一些基本理念:

     

    LinuxThreads 非常出名的一个特性就是管理线程(managerthread)。管理线程可以满足以下要求:

    • 系统必须能够响应终止信号并杀死整个进程。
    • 以堆栈形式使用的内存回收必须在线程完成之后进行。因此,线程无法自行完成这个过程。
    • 终止线程必须进行等待,这样它们才不会进入僵尸状态。
    • 线程本地数据的回收需要对所有线程进行遍历;这必须由管理线程来进行。
    • 如果主线程需要调用pthread_exit(),那么这个线程就无法结束。主线程要进入睡眠状态,而管理线程的工作就是在所有线程都被杀死之后来唤醒这个主线程。
    • 为了维护线程本地数据和内存,LinuxThreads 使用了进程地址空间的高位内存(就在堆栈地址之下)。

    • 原语的同步是使用信号 来实现的。例如,线程会一直阻塞,直到被信号唤醒为止。

    • 在克隆系统的最初设计之下,LinuxThreads 将每个线程都是作为一个具有惟一进程 ID 的进程实现的。

    • 终止信号可以杀死所有的线程。LinuxThreads接收到终止信号之后,管理线程就会使用相同的信号杀死所有其他线程(进程)。

    • 根据 LinuxThreads的设计,如果一个异步信号被发送了,那么管理线程就会将这个信号发送给一个线程。如果这个线程现在阻塞了这个信号,那么这个信号也就会被挂起。这是因为管理线程无法将这个信号发送给进程;相反,每个线程都是作为一个进程在执行。
    • 线程之间的调度是由内核调度器来处理的。

     

    LinuxThreads及其局限性

    LinuxThreads的设计通常都可以很好地工作;但是在压力很大的应用程序中,它的性能、可伸缩性和可用性都会存在问题。下面让我们来看一下LinuxThreads 设计的一些局限性:

    • 它使用管理线程来创建线程,并对每个进程所拥有的所有线程进行协调。这增加了创建和销毁线程所需要的开销。

    • 由于它是围绕一个管理线程来设计的,因此会导致很多的上下文切换的开销,这可能会妨碍系统的可伸缩性和性能。

    • 由于管理线程只能在一个 CPU 上运行,因此所执行的同步操作在 SMP 或 NUMA系统上可能会产生可伸缩性的问题。

    • 由于线程的管理方式,以及每个线程都使用了一个不同的进程 ID,因此 LinuxThreads 与其他与 POSIX相关的线程库并不兼容。

    • 信号用来实现同步原语,这会影响操作的响应时间。另外,将信号发送到主进程的概念也并不存在。因此,这并不遵守 POSIX中处理信号的方法。

    • LinuxThreads中对信号的处理是按照每线程的原则建立的,而不是按照每进程的原则建立的,这是因为每个线程都有一个独立的进程ID。由于信号被发送给了一个专用的线程,因此信号是串行化的 —— 也就是说,信号是透过这个线程再传递给其他线程的。这与POSIX 标准对线程进行并行处理的要求形成了鲜明的对比。例如,在 LinuxThreads 中,通过kill()所发送的信号被传递到一些单独的线程,而不是集中整体进行处理。这意味着如果有线程阻塞了这个信号,那么 LinuxThreads就只能对这个线程进行排队,并在线程开放这个信号时在执行处理,而不是像其他没有阻塞信号的线程中一样立即处理这个信号。

    • 由于 LinuxThreads 中的每个线程都是一个进程,因此用户和组 ID的信息可能对单个进程中的所有线程来说都不是通用的。例如,一个多线程的setuid()/setgid()进程对于不同的线程来说可能都是不同的。

    • 有一些情况下,所创建的多线程核心转储中并没有包含所有的线程信息。同样,这种行为也是每个线程都是一个进程这个事实所导致的结果。如果任何线程发生了问题,我们在系统的核心文件中只能看到这个线程的信息。不过,这种行为主要适用于早期版本的LinuxThreads 实现。

    • 由于每个线程都是一个单独的进程,因此 /proc 目录中会充满众多的进程项,而这实际上应该是线程。

    • 由于每个线程都是一个进程,因此对每个应用程序只能创建有限数目的线程。例如,在 IA32 系统上,可用进程总数 ——也就是可以创建的线程总数 —— 是 4,090。

    • 由于计算线程本地数据的方法是基于堆栈地址的位置的,因此对于这些数据的访问速度都很慢。另外一个缺点是用户无法可信地指定堆栈的大小,因为用户可能会意外地将堆栈地址映射到本来要为其他目的所使用的区域上了。按需增长(growon demand) 的概念(也称为浮动堆栈 的概念)是在 2.4.10 版本的 Linux内核中实现的。在此之前,LinuxThreads 使用的是固定堆栈。

     

     

     

     

    关于 NPTL

    NPTL,或称为 Native POSIX Thread Library,是 Linux 线程的一个新实现,它克服了LinuxThreads 的缺点,同时也符合 POSIX 的需求。与 LinuxThreads相比,它在性能和稳定性方面都提供了重大的改进。与 LinuxThreads 一样,NPTL 也实现了一对一的模型。

    Ulrich Drepper 和 Ingo Molnar 是 Red Hat 参与 NPTL设计的两名员工。他们的总体设计目标如下:

    • 这个新线程库应该兼容 POSIX 标准。

    • 这个线程实现应该在具有很多处理器的系统上也能很好地工作。

    • 为一小段任务创建新线程应该具有很低的启动成本。

    • NPTL 线程库应该与 LinuxThreads 是二进制兼容的。注意,为此我们可以使用LD_ASSUME_KERNEL,这会在本文稍后进行讨论。

    • 这个新线程库应该可以利用 NUMA 支持的优点。

    NPTL 的优点

    与 LinuxThreads 相比,NPTL 具有很多优点:

    • NPTL没有使用管理线程。管理线程的一些需求,例如向作为进程一部分的所有线程发送终止信号,是并不需要的;因为内核本身就可以实现这些功能。内核还会处理每个线程堆栈所使用的内存的回收工作。它甚至还通过在清除父线程之前进行等待,从而实现对所有线程结束的管理,这样可以避免僵尸进程的问题。

    • 由于 NPTL 没有使用管理线程,因此其线程模型在 NUMA 和 SMP 系统上具有更好的可伸缩性和同步机制。

    • 使用 NPTL 线程库与新内核实现,就可以避免使用信号来对线程进行同步了。为了这个目的,NPTL 引入了一种名为futex 的新机制。futex 在共享内存区域上进行工作,因此可以在进程之间进行共享,这样就可以提供进程间 POSIX同步机制。我们也可以在进程之间共享一个 futex。这种行为使得进程间同步成为可能。实际上,NPTL 包含了一个PTHREAD_PROCESS_SHARED宏,使得开发人员可以让用户级进程在不同进程的线程之间共享互斥锁。

    • 由于 NPTL 是 POSIX 兼容的,因此它对信号的处理是按照每进程的原则进行的;getpid()会为所有的线程返回相同的进程 ID。例如,如果发送了 SIGSTOP 信号,那么整个进程都会停止;使用LinuxThreads,只有接收到这个信号的线程才会停止。这样可以在基于 NPTL 的应用程序上更好地利用调试器,例如GDB。

    • 由于在 NPTL 中所有线程都具有一个父进程,因此对父进程汇报的资源使用情况(例如 CPU和内存百分比)都是对整个进程进行统计的,而不是对一个线程进行统计的。

    • NPTL 线程库所引入的一个实现特性是对 ABI(应用程序二进制接口)的支持。这帮助实现了与 LinuxThreads的向后兼容性。这个特性是通过使用 LD_ASSUME_KERNEL 实现的,下面就来介绍这个特性。

     

     Sam对上面的这些描述其实很多也不是很清楚,放在这里作为备份,未来理解多了,慢慢读。

     

    GNU_LIBPTHREAD_VERSION宏

    大部分现代 Linux 发行版都预装了 LinuxThreads 和NPTL,因此它们提供了一种机制来在二者之间进行切换。要查看您的系统上正在使用的是哪个线程库,请运行下面的命令:

    $ getconf GNU_LIBPTHREAD_VERSION

    这会产生类似于下面的输出结果:

    NPTL 0.34

    或者:

    linuxthreads-0.10

     

    Linux 发行版所使用的线程模型、glibc版本和内核版本

    表 1 列出了一些流行的 Linux 发行版,以及它们所采用的线程实现的类型、glibc 库和内核版本。

     

    表 1. Linux发行版及其线程实现
    线程实现C 库发行版内核
    LinuxThreads 0.7, 0.71 (for libc5)libc 5.xRed Hat 4.2 
    LinuxThreads 0.7, 0.71 (for glibc 2)glibc 2.0.xRed Hat 5.x 
    LinuxThreads 0.8glibc 2.1.1Red Hat 6.0 
    LinuxThreads 0.8glibc 2.1.2Red Hat 6.1 and 6.2 
    LinuxThreads 0.9 Red Hat 7.22.4.7
    LinuxThreads 0.9glibc 2.2.4Red Hat 2.1 AS2.4.9
    LinuxThreads 0.10glibc 2.2.93Red Hat 8.02.4.18
    NPTL 0.6glibc 2.3Red Hat 9.02.4.20
    NPTL 0.61glibc 2.3.2Red Hat 3.0 EL2.4.21
    NPTL 2.3.4glibc 2.3.4Red Hat 4.02.6.9
    LinuxThreads 0.9glibc 2.2SUSE Linux Enterprise Server 7.12.4.18
    LinuxThreads 0.9glibc 2.2.5SUSE Linux Enterprise Server 82.4.21
    LinuxThreads 0.9glibc 2.2.5United Linux2.4.21
    NPTL 2.3.5glibc 2.3.3SUSE Linux Enterprise Server 92.6.5

     

    注意,从 2.6.x 版本的内核和 glibc 2.3.3 开始,NPTL所采用的版本号命名约定发生了变化:这个库现在是根据所使用的 glibc 的版本进行编号的。

     

    Sam记得好像某篇文章讲,从2.6kernel开始,NPTL支持被加入。只需要glibc用NPTL就可以了。就算我们的系统里装上了NPTL库,也不会影响原来的程序,就算是那些老的程序,即使用了linuxthreads的头文件且在编译,连接的时候使用了linuxthreads的库的程序,我们也能够让它在执行的时候,动态连接到我们的NPTL库。从而发挥NPTL的作用。

     

    另外,从GNU libc 2.4开始使用了NPTL方式,但Kernel版本需要 Linux2.6.0以上。

    所以判断一个嵌入式平台是否支持NPTL,首先看kernel版本号,如果低于2.6.则肯定不支持NPTL.(因为如果kernel不支持NPTL.则libc再怎么做都没办法)。其次看libc版本号。即/lib/libc.so.xxxx。 另外:截至到目前:uclibc版本为0.9.29。 不支持NPTL。

     

     

    所以:

    Intel CE3100: 

    Kernel Version:2.6.23

    LibC Version: libc-2.7.so

    支持NPTL。

     

    Intel CE2110:

    Kernel Version:2.6.18

    LibC Version: libc-2.3.6.so

    不支持NPTL。

     

    BCM7403:

    Kernel Version:2.6.12-4.2

    LibC Version: libuClibc-0.9.28.so

    不支持NPTL。


    Linux下thread编程(二)


    thread属性:

    pthread_create()中,第二个参数(pthread_attr_t)为将要创建的thread属性。通常情况下配置为NULL,使用缺省设置就可以了。

    但了解这些属性,有利于更好的理解thread.

    属性对象(pthread_attr_t)是不透明的,而且不能通过赋值直接进行修改。系统提供了一组函数,用于初始化、配置和销毁每种对象类型。

     

    创建属性:

    int pthread_attr_init(pthread_attr_t *attr);

    创建的属性设定为缺省设置。

     

    销毁属性:

    int pthread_attr_destroy(pthread_attr_t *attr);

     

    一:设置分离状态:

    线程的分离状态有2种:PTHREAD_CREATE_JOINABLE(非分离状态),PTHREAD_CREATE_DETACHED(分离状态)

    分离状态含义如下:

    如果使用 PTHREAD_CREATE_JOINABLE创建非分离线程,则假设应用程序将等待线程完成。也就是说,程序将对线程执行pthread_join。 非分离线程在终止后,必须要有一个线程用 join来等待它。否则,不会释放该线程的资源以供新线程使用,而这通常会导致内存泄漏。因此,如果不希望线程被等待,请将该线程作为分离线程来创建。

     

    如果使用 PTHREAD_CREATE_DETACHED创建分离thread,则表明此thread在退出时会自动回收资源和threadID.

     

    Sam之前很喜欢使用分离thread.但现在慢慢使用中觉得这样是个不好的习惯。因为分离thread有个问题:主程序退出时,很难确认子thread已经退出。只好使用全局变量来标明子thread已经正常退出了。

    另外:不管创建分离还是非分离的thread.在子thread全部退出之前退出主程序都是很有风险的。如果主thread选择return,或者调用exit()退出,则所有thread都会被kill掉。这样很容易出错。Sam上次出的问题其实就是这个。但如果主thread只是调用pthread_exit().则仅主线程本身终止。进程及进程内的其他线程将继续存在。所有线程都已终止时,进程也将终止。

     

    int pthread_attr_getdetachstate(constpthread_attr_t *attr,int *detachstate);
    int pthread_attr_setdetachstate(pthread_attr_t *attr, intdetachstate);

    得到当前和分离状态和设置当前的分离状态。

     

    二:设置栈溢出保护区大小:

    栈溢出概念:

    • 溢出保护可能会导致系统资源浪费。如果应用程序创建大量线程,并且已知这些线程永远不会溢出其栈,则可以关闭溢出保护区。通过关闭溢出保护区,可以节省系统资源。

    • 线程在栈上分配大型数据结构时,可能需要较大的溢出保护区来检测栈溢出。

    int pthread_attr_getguardsize(const pthread_attr_t *restrictattr,size_t *restrict guardsize);
    int pthread_attr_setguardsize(pthread_attr_t *attr,size_tguardsize);

    设置和得到栈溢出保护区。如果guardsize设为0。则表示不设置栈溢出保护区。guardsize的值向上舍入为 PAGESIZE 的倍数。

     

    三:设置thread竞用范围:

    竞用范围(PTHREAD_SCOPE_SYSTEMPTHREAD_SCOPE_PROCESS) 使用PTHREAD_SCOPE_SYSTEM 时,此线程将与系统中的所有线程进行竞争。使用PTHREAD_SCOPE_PROCESS 时,此线程将与进程中的其他线程进行竞争。

     

    int pthread_attr_getscope(const pthread_attr_t *restrictattr,int *restrict contentionscope);
    int pthread_attr_setscope(pthread_attr_t *attr, intcontentionscope);

     

    四:设置线程并行级别:

    int pthread_getconcurrency(void);
    int pthread_setconcurrency(int new_level);

    Sam不理解这个意思。

     

    五:设置调度策略:

    POSIX 标准指定SCHED_FIFO(先入先出)、SCHED_RR(循环)或SCHED_OTHER(实现定义的方法)的调度策略属性。

    • SCHED_FIFO

      如果调用进程具有有效的用户 ID 0,则争用范围为系统(PTHREAD_SCOPE_SYSTEM) 的先入先出线程属于实时 (RT)调度类。如果这些线程未被优先级更高的线程抢占,则会继续处理该线程,直到该线程放弃或阻塞为止。对于具有进程争用范围(PTHREAD_SCOPE_PROCESS)) 的线程或其调用进程没有有效用户 ID 0 的线程,请使用SCHED_FIFOSCHED_FIFO 基于 TS 调度类。

    • SCHED_RR

      如果调用进程具有有效的用户 ID 0,则争用范围为系统(PTHREAD_SCOPE_SYSTEM)) 的循环线程属于实时 (RT)调度类。如果这些线程未被优先级更高的线程抢占,并且这些线程没有放弃或阻塞,则在系统确定的时间段内将一直执行这些线程。对于具有进程争用范围(PTHREAD_SCOPE_PROCESS) 的线程,请使用 SCHED_RR(基于TS 调度类)。此外,这些线程的调用进程没有有效的用户 ID 0

    SCHED_FIFO是基于队列的调度程序,对于每个优先级都会使用不同的队列。SCHED_RR 与 FIFO相似,不同的是前者的每个线程都有一个执行时间配额。

     

    int pthread_attr_getschedpolicy(const pthread_attr_t *restrictattr,int *restrict policy);
    int pthread_attr_setschedpolicy(pthread_attr_t *attr, intpolicy);

     

    六:设置优先级:

    int pthread_attr_getschedparam(const pthread_attr_t *restrictattr,struct sched_param *restrict param);

    int pthread_attr_setschedparam(pthread_attr_t *restrictattr,
                 const struct sched_param *restrict param);

    比较复杂,Sam没去研究。

     

    七:设置栈大小:

    当创建一个thread时,会给它分配一个栈空间,线程栈是从页边界开始的。任何指定的大小都被向上舍入到下一个页边界。不具备访问权限的页将被附加到栈的溢出端(第二项设置中设置)。

    指定栈时,还应使用 PTHREAD_CREATE_JOINABLE 创建线程。在该线程的pthread_join()调用返回之前,不会释放该栈。在该线程终止之前,不会释放该线程的栈。了解这类线程是否已终止的唯一可靠方式是使用pthread_join。

    一般情况下,不需要为线程分配栈空间。系统会为每个线程的栈分配指定大小的虚拟内存。

    #ulimit -a可以看到这个缺省大小


    Linux下thread编程(三)

    1.创建thread.

    int pthread_create(pthread_t *restrict thread, constpthread_attr_t *restrict attr,
                 void *(*start_routine)(void*), void *restrict arg);

     

    参数1:pthread_t *restrict thread:创建thread的thread ID.

    参数2:const pthread_attr_t *restrict attr:创建线程的属性。

    参数3:void *(*start_routine)(void*):thread服务程序。

    参数4:void *restrict arg:thread服务程序参数。

     

    2. 等待目标线程终止:

    pthread_join() 函数会一直阻塞调用线程,直到指定的线程终止。

    指定的线程必须位于当前的进程中,而且不得是分离线程。所有创建时属性为PTHREAD_CREATE_JOINABLE的非分离thread.最终都需要调用pthread_join() or pthread_detach() 。这样thread所占资源和 Thread ID才被释放。

     

    3. 分离thread:

    int pthread_detach(pthread_t thread);

    pthread_detach()指出当thread 结束时,thread所占资源和ThreadID会被释放和再利用。如果调用pthread_detach()时,thread没有结束,它并不会导致thread退出。它只对PTHREAD_CREATE_JOINABLE非分离thread有效。

     

    4. 获取threadID:

    pthread_t pthread_self(void);

    返回调用thread的thread ID.

     

    5. 比较thread ID:

    int pthread_equal(pthread_t t1, pthread_t t2);如果 tid1tid2 相等,pthread_equal()将返回非零值,否则将返回

     

    6. 向thread发信号:

    int pthread_kill(pthread_t thread, int sig);

    tid 所指定的线程必须与调用线程在同一个进程中。sig 参数必须来自signal(5) 提供的列表。

     

    7.退出线程:

    void pthread_exit(void *value_ptr);

    pthread_exit()用来终止调用thread并置位value_ptr.这个值会交给pthread_join。

     

     

    Thread的取消:

    同一进程内,某个Thread可以向其它thread发送取消要求,要求目标thread退出运行。

    取消请求的处理方式取决于目标线程的状态。状态由以下两个函数确定:pthread_setcancelstate()pthread_setcanceltype()

     pthread_setcancelstate()启用或禁用线程取消功能。创建线程时,缺省情况下线程取消功能处于启用状态。

    pthread_setcanceltype()可以将取消类型设置为延迟或异步模式。创建线程时,缺省情况下会将取消类型设置为延迟模式。在延迟模式下,只能在取消点取消线程。在异步模式下,可以在执行过程中的任意一点取消线程。因此建议不使用异步模式。

     

    执行取消操作存在一定的危险。大多数危险都与完全恢复不变量和释放共享资源有关。取消线程时一定要格外小心,否则可能会使互斥保留为锁定状态,从而导致死锁。或者,已取消的线程可能保留已分配的内存区域,但是系统无法识别这一部分内存,从而无法释放它。

     

    如果创建thread时使用缺省设置,则thread可以被取消,并为异步方式,所以向某一thread发送pthread_cancel()后,并不保证什么时候目标thread会被取消。只有当目标thread运行至取消点时才会真正退出。

     

    类似Read,write等阻塞函数可以被看作取消点,但Sam记得并不能保证。所以建议使用手动添加取消点

    pthread_testcancel();

    当线程取消功能处于启用状态且取消类型设置为延迟模式时,pthread_testcancel()函数有效。如果在取消功能处于禁用状态下调用pthread_testcancel(),则该函数不起作用。

    请务必仅在线程取消操作安全的序列中插入 pthread_testcancel()

    例如:Sam一直以为poll()函数这样的阻赛类函数为cancel点,但其实不是,需要手动添加cancel点。

     

     

     

     几个误区及注意点:

     误区1: 分离线程不能被cancel.

    这是将pthread_join与pthread_cancel搞混了。

    thread分离可以在创建时设定,也可以用pthread_detach()在创建后设定。

    被设定成分离线程后,表明它在退出thread时会自动回收资源。所以不需要pthread_join.但分离thread完全可以接收pthread_cancel()来退出。

     

    误区2:已经退出的thread,再去对它pthread_cancel()会出错。

    不会出错,如果某thread已经退出,再向它发送pthread_cancel().不会出错。但会返回ESRCH。此值为3。

     ESRCH  No thread could befound corresponding to that specified by the given thread ID.

     

    这里显示出:一个thread,不管自身return或pthread_exit().此thread都算停掉了。只是不分离thread需要使用pthread_join来回收资源而已。

     

     

    注意点1:不管是否分离,主thread先于其它thread退出,都是不可控的。也就是说会不可预知错误。

    所以,主thread不要使用return,exit等退出。 而是使用pthread_exit().

    主thread使用pthread_exit(). 则会阻赛之,直到所有子thread退出后才退出。

     

     

    推荐的做法:

    常常有这样的需求,一个子thread既需要在某些事件发生时自己退出,也可能被主thread要求退出。

    则可以做如下设计:

    子thread自己退出时,使用pthread_exit().

    其它thread要求它退出时,是用pthread_cancel(). pthread_join().

     

    则当其它thread先要求它退出时,走正常途径,pthread_cancel()导致其退出。pthread_join()确保其退出并回收资源。

    当其自动使用pthread_exit()退出时,最终主thread也会调用pthread_cancel(),则返回错误。但pthread_join()则确保回收资源。

     

     

    pthread系列函数错误码:

    大多数系统函数执行正确返回0。否则返回-1。错误码在errno中。所以可以使用perror()来显示错误。

    但pthread系列函数却通过返回值传递error code.并不向errno中写入错误码。所以不能使用perror()来查看错误原因。

     

    可以使用strerror(pthread_rel) 来打印错误原因:

     

    iRel_pthread = pthread_create(&mRtid, NULL,thread_Read_Data, this);
    if(iRel_pthread != 0)
    {
      std::cout<<"kDriver: Create Scan_Device threadError." <<strerror(iRel_pthread) <<std::endl;
     }



    展开全文
  • tcl threadlinux 的安装包tcl threadlinux 的安装包tcl threadlinux 的安装包
  • Linux thread

    千次阅读 2008-10-29 23:08:00
    http://linas.org/linux/threads-faq.htmlCautionThis FAQ is more than a two years out-of-date. POSIX-threads are now a standard part of all modern Linux distributions. The new glibc version 2 (linux

    http://linas.org/linux/threads-faq.html

    Caution

    This FAQ is more than a two years out-of-date. POSIX-threads are now a standard part of all modern Linux distributions. The new glibc version 2 (linux libc version 6.0) is fully re-entrant and supports threads in a fully compliant manner. The default Linux thread implementation is with kernel-space threads, not user-space threads; these threads will schedule properly on an SMP architecture.

    You may still find this FAQ useful if you are looking for user-space threads, DCE threads, a non-standard threads API, or for threads tools for a language other than C/C++/perl/tcl/scheme, or if you are upgrading an older system. The "LinuxThreads" package (below) has become the default package for Linux distributions, so you will probably want that if upgrading. The MIT (Provenzano) threads package is popular among some folks.

    Since Sean Walton has moved on to bigger & better things, there is currently no maintainer for the FAQ. If anyone wants to bring this FAQ up-to-date, or can offer an otherwise improved and updated FAQ, please contact me, Linas Vepstas, linas@linas.org and I will post your updates and/or redirect this page to your page.

    Introduction

    • This FAQ is designed to answer several frequently asked questions about Linux threads. For an in-depth view of threads or Linux threads join the discussions on comp.os.linux.development.system (c.o.l.d.s).

      Since I am only conveying the information (and not an expert), please direct your specific questions to c.o.l.d.s or comp.programming.threads. If you have suggestions/additions/revisions to this document, please email me.

    Limitations

    • This is not a discussion on the various implementations of threads: it is specifically for Linux threading. If you want to see a comparison between systems or would like to promote some other system than is intended here, write your own FAQ. Nevertheless, I welcome comments pertinent to the topic and will gladly add your input.

    Credits

    • A special thanks to those who significantly helped me put this together: Byron A Jeff (for a lot of theory) and Steven L. Blake (for his list of sources).


    INDEX

    1. What are threads (user/kernel)?
    2. What are the common problems with threads?
    3. Does Linux support threads?
    4. Are Linux threads the same as other implementations?
    5. Is the kernel 100% reentrant?
    6. Do the libraries support multithreading?
    7. What kinds of things should be threaded/multitasked?
    8. Are there threading libraries? Where?
    9. How are Linux threads accessed?
    10. Is there a system call I can use to access kernel threads?
    11. Are there ways to determine thread schedule ordering?
    12. Are there languages that support threads?
    13. How does one debug threads?
    14. What do the individual flags mean and do in clone()?
    15. What applications or libraries currently use threads?
    16. Where can I learn more about threads?


    What are threads (user/kernel)?

    • Threads are "light weight processes" (LWPs). The idea is a process has five fundamental parts: code ("text"), data (VM), stack, file I/O, and signal tables. "Heavy-weight processes" (HWPs) have a significant amount of overhead when switching: all the tables have to be flushed from the processor for each task switch. Also, the only way to achieve shared information between HWPs is through pipes and "shared memory". If a HWP spawns a child HWP using fork(), the only part that is shared is the text.

      Threads reduce overhead by sharing fundamental parts. By sharing these parts, switching happens much more frequently and efficiently. Also, sharing information is not so "difficult" anymore: everything can be shared. There are two types of threads: user-level and kernel-level.

    User-Level Threads

    • User-level avoids the kernel and manages the tables itself. Often this is called "cooperative multitasking" where the task defines a set of routines that get "switched to" by manipulating the stack pointer. Typically each thread "gives-up" the CPU by calling an explicit switch, sending a signal or doing an operation that involves the switcher. Also, a timer signal can force switches. User threads typically can switch faster than kernel threads [however, Linux kernel threads' switching is actually pretty close in performance].

      Disadvantages. User-level threads have a problem that a single thread can monopolize the timeslice thus starving the other threads within the task. Also, it has no way of taking advantage of SMPs (Symmetric MultiProcessor systems, e.g. dual-/quad-Pentiums). Lastly, when a thread becomes I/O blocked, all other threads within the task lose the timeslice as well.

      Solutions/work arounds. Some user-thread libraries have addressed these problems with several work-arounds. First timeslice monopolization can be controlled with an external monitor that uses its own clock tick. Second, some SMPs can support user-level multithreading by firing up tasks on specified CPUs then starting the threads from there [this form of SMP threading seems tenuous, at best]. Third, some libraries solve the I/O blocking problem with special wrappers over system calls, or the task can be written for nonblocking I/O.

    Kernel-Level Threads

    • Kernel-level threads often are implemented in the kernel using several tables (each task gets a table of threads). In this case, the kernel schedules each thread within the timeslice of each process. There is a little more overhead with mode switching from user->kernel-> user and loading of larger contexts, but initial performance measures indicate a negligible increase in time.

      Advantages. Since the clocktick will determine the switching times, a task is less likely to hog the timeslice from the other threads within the task. Also I/O blocking is not a problem. Lastly, if properly coded, the process automatically can take advantage of SMPs and will run incrementally faster with each added CPU.

    Combination

    • Some implementations support both user- and kernel-level threads. This gives the advantages of each to the running task. However, since Linux's kernel-level threads nearly perform as well as user-level, the only advantage of using user-threads would be the cooperative multitasking.


    What are the common problems with threads?

    • Several problems with threads originate from a classic view and its intrinsic concurrency complexity.

    Classic View

    • In many other multithreaded OSs, threads are not processes merely parts of a parent task. Therefore, the question of "what happens if a thread calls fork() or (worse) if a thread execve()'s some external program" becomes problematic: the whole task could be replaced. The POSIX standard defines a thread calling fork() to duplicate only the calling thread in the new process; and an execve() from a thread would stop all threads of that process.

      Having two different implementations and schedulers for processes is a flaw that has perpetuated from implementation to implementation. In fact, some multitasking OSs have opted not to support threads due to these problems (not to mention the effort needed to make the kernel and libraries 100% reentrant). For example, the POSIX Windows NT appears not to support threads [author note: it was my understanding that they did support threads... that's part of Win32NT].

    Concurrency Complexity

    • Most people have a hard enough time understanding tasks, never mind "chopped up tasks" or threads. The first problem while programming is answering the question: "What can be threaded in my app?". That, in itself, can be very laborious (see section on "What kinds of things should be threaded/multitasked?").

      Another problem is locking. All the nightmares about sharing, locking, deadlock, race conditions, etc. come vividly alive in threads. Processes don't usually have to deal with this, since most shared data is passed through pipes. Now, threads can share file handles, pipes, variables, signals, etc. Trying to test and duplicate error conditions can cause more gray hair than a wayward child.


    Does Linux support threads?

    • Yes. As of 1.3.56, Linux has supported kernel-level multithreading. There also have been user-level thread libraries around as early as 1.0.9.


    Are Linux threads the same as other implementations?

    • No. They are better -- while mostly keeping the same API. As stated above, most multithreaded OSs define a thread separately from processes. Linus Torvalds has defined that a thread is a "context of execution" (COE). This means that only one process/thread table and one scheduler is needed.

      Traditionally, a thread was just a CPU (and some other minimal state) state with the process containing the remains (data, stack, I/O, signals). This would lend itself to very fast switching but would cause basic problems (e.g. what do "fork()" or "execve()" calls mean when executed by a thread?).

      Consider Linux threads as a superset of this functionality: they still can switch fast and share process parts, but they can also identify what parts get shared and have no problems with execve() calls. There are four flags that determine the level of sharing:

        • #define CLONE_VM 0x00000100

          #define CLONE_FS 0x00000200

          #define CLONE_FILES 0x00000400

          #define CLONE_SIGHAND 0x00000800

          #define CLONE_PID /* not fully implemented */

      There has been a lot of talk about "clone()". The system call (please note: low level) clone() is an extension to fork(). In fact, clone(0) == fork(). But with the above #define's, any combination of the VM, filesystem, I/O and signal handlers may be shared.


    Is the kernel 100% reentrant?

    • No. It's getting there though. As of this date 12-Sep-96, some of the drivers are still not reentrant. What does this mean? It means that threads making various system calls will block other threads until the call is completed. Will it crash? No. When will the fine-threading (not treating system calls as a single operation) work? That's currently in the works.


    Do the libraries support multithreading?

    • No. These are more likely to crash. There are several objects that are hidden from applications and may be shared inadvertently (e.g. FILE* or emulated NDP registers). Additionally there are conflicts between some libraries. For example, SVGAlib & LinuxThreads both use SIGUSR1 and SIGUSR2 (application-reserved signals). If an app were to use these libraries together, the app would minimally have problems (or likely crash) and debugging would be a sequel to "Friday the 13th".

      Several individuals are working hard to get the libraries in sync with this new functionality. The initial work is to provide wrappers around some of the critical, data-shared functions (e.g. open, close). In fact some threads libraries have work-arounds for these as well.

      Please note that revising the standard libraries is no small task (gratuitous pun;). If you want to help, contact ----@----.----.


    What kinds of things should be threaded or multitasked?

    • If you are a programmer and would like to take advantage of multithreading, the natural question is what parts of the program should/ should not be threaded. Here are a few rules of thumb (if you say "yes" to these, have fun!):

      • Are there groups of lengthy operations that don't necessarily depend on other processing (like painting a window, printing a document, responding to a mouse-click, calculating a spreadsheet column, signal handling, etc.)?
      • Will there be few locks on data (the amount of shared data is identifiable and "small")?
      • Are you prepared to worry about locking (mutually excluding data regions from other threads), deadlocks (a condition where two COEs have locked data that other is trying to get) and race conditions (a nasty, intractable problem where data is not locked properly and gets corrupted through threaded reads & writes)?
      • Could the task be broken into various "responsibilities"? E.g. Could one thread handle the signals, another handle GUI stuff, etc.?


    Are there threading libraries? Where?

    • Yes, there are several. Here are a several URLs Steven Blake (2-Aug-1996) has compiled for our use. Please note that I have emailed the authors (if known) to get more information about their libraries.

    Linux Threads Packages

    Title:Bare-Bones Threads
    Author:Christopher Neufeld
    Repositories:[Documentation] [Source]
    API:Non-standard
    Description:This is a very basic, bare-bones threading package which has been tested on both single-CPU and SMP Linux boxes.
    License: GPL(? Source is included)
    Title:DCEthreads
    Author:Michael T. Peterson
    Repositories:[Documentation] [Source]
    API:POSIX 1003.4c Draft 4 (?)
    Description:PCthreads (tm) is a multithreading library for Linux-based Intel systems and is based on the POSIX 1003.1c standard. The kit contains the sources for the library (libpthreads), a build environment for building both ELF and A.OUT versions of the library, and a complete set of man pages for all POSIX .1c functions.
    License: GPL(? Source is included)
    Title:FSU Pthreads
    Author:Frank Mueller
    Repositories:[Documentation] [Documentation mirror] [Source] [Source mirror]
    API:POSIX 1003.1c
    Description:Pthreads is a C library which implements POSIX threads for SunOS 4.1.x, Solaris 2.x, SCO UNIX, FreeBSD and Linux. Used for GNU Ada Runtime; partial libc support.
    License: Gnu Public Library License
    Title:JKthread
    Author:Jeff Koftinoff
    Repositories:[Documentation] [Source]
    API:Non-standard
    Description:This is an experiment with the Linux 2.0 clone() call to implement usable kernel threads in a user program. These jkthreads have an API that has NOTHING to do with pthreads or Win32 threads or BeBox threads.
    License: GPL (? Source is included)
    Title:LinuxThreads
    Author:Xavier Leroy
    Repositories:[Documentation] [Source]
    API:POSIX 1003.1c
    Description:LinuxThreads is an implementation of the Posix 1003.1c thread package for Linux. Unlike other implementations of Posix threads, LinuxThreads provides kernel-level threads: threads are created with the new clone() system call and all scheduling is done in the kernel.
    License: GNU LGPL
    Title:LWP
    Author:Stephen Crane
    Repositories:[Documentation] [Source]
    API:Non-standard
    Description:A small portable lightweight process library for sun[34], mips-ultrix, 386BSD, HP-UX and Linux. (Man pages included)
    License: GPL
    Title:PCthreads
    Author:Michael T. Peterson
    Repositories:[Documentation] [Source]
    API:POSIX 1003.1c
    Description:User-space pthreads library; includes non-blocking select(), read(), and write(). Man pages included. Requires DCEThreads.
    License: GPL (? Source is included)
    Title:Provenzano Pthreads
    Author:Christopher A. Provenzano
    Repositories:[Documentation] [Source]
    API:POSIX 1003.1c subset (lacks thread cancellation)
    Description:User-space pthreads library distributed with Linux libc source but may not be built by default.
    License: GPL (?)
    Title:QuickThreads
    Author:David Keppel
    Repositories:[Documentation] [Source]
    API:Non-standard
    Description:A portable user-space threads package. Documentation written in PostScript.
    License: Freeware (source).
    Title:Radke Threads
    Author:Thomas Radke
    Repositories:[Documentation] [Source]
    API:Non-standard
    Description:User-level threads package included with patches to Linux kernel to support kernel threading. (Includes man pages)
    License: GPL (? Source is included)


    How are Linux kernel threads accessed?

    • Since kernel threads are individual tasks with various shared parts, the question naturally arises: how are the threads associated with the parent and how are they accessed? There appear to be two ways: through the language or through the kernel.

    Language Access

    • There exist several languages that support threads intrinsicly: Modula-3, Java, Python 1.4, Smalltalk/X, Objective-C/Gnustep and Ada. Each have language elements to program/access individual threads. All of these languages are available to the Linux community. However, they only support user threads; no "clone()" calls are made to the new Linux kernels. There appears to be effort, however, in revising these languages to support the newer kernels.

    Kernel Access

    • Each PID is 32bits, wrapping (modulus) at 30000 for really old software. If CLONE_PID is not used, each thread will get its own PID like any other process. However, if the PID is to be shared, the kernel uses the upper 16bits to assign the thread ID (TID) [please note that this is probably not in the 2.0.* kernel version; we'll see it in 2.1.* for sure.]

      Furthermore, each process has at least one thread (the parent). Each new thread will be assigned a TID beginning with 1. A TID of 0 (e.g. 0x0000FFFF mask) will address all threads within a process. Suppose an app has three threads (parent and two task managers) and the threads share the parent's PID. Suppose, the PIDs for each might be 0x00011234 (parent), 0x00021234 (child thread #1) and 0x00031234 (child thread #2). Each thread can be accessed or signaled individually -or- the whole task could be addressed with 0x00001234 (note that the first four digits are zero masking the TIDs).

      It is the intent that the long format will work with existing apps. And, older apps that signal the whole task will still work (by accessing the whole task at once). However a shorthand has been proposed: PID.TID (e.g. 46.2 would be the second thread of PID 46).


    Is there a system call I can use to access kernel threads?

    • Recent versions of glibc has clone() defined thus:

    • int clone(int (*fn)(), void **stack, int flags, int argc,... /* args */);

        • fnThe thread routine
          stack The thread's stack
          flags Flags as defined above
          argc number of remaining parameters
          /* args */the parameters expected by the thread.


    Are there ways currently to determine thread schedule ordering?

    • Not 100%. There are realtime extensions in the 2.0.0 kernels that will grant finer-tuned control over threads/tasks via `sched_setscheduler'. [Author's note: I have no idea about this. I will update this as I get more info.]


    Are there languages that support threads?

    • Currently, Ada, Modula-3, Python 1.4, SmallTalk/X, Objective-C/ Gnustep and Java have intrinsic language elements to support threads (for example, Ada has the 'Select' statement which is a "thread guard"). However, the Linux implementations of these languages do not yet support the new kernel threads.

      Naturally, C and C++ can make calls to spawn threads and processes, but there are no language elements to support them beyond these system calls. See below for a listing of available languages:

    Threading Languages

    Titles:Ada/Ed
    Author:New York University
    Repositories:[Documentation] [Source]
    Newsgroup:comp.lang.ada
    Threads Lib:LinuxThreads
    Description:Ada/Ed is a translator-interpreter for Ada. It is intended as a teaching tool and does not have the capacity, performance or robustness of commercial Ada compilers. Ada/Ed was developed as a long-range project in language definition and software prototyping.
    License: GPL
    Title:Gnat
    Author:New York University
    Repositories:[Documentation] [Source][SVGA Bindings]
    Newsgroup:comp.lang.ada
    Threads Lib:LinuxThreads
    Description:GNAT is the Ada 95 compiler produced by a collaboration between a team at New York University and the Free Software Foundation, 'owner' of the GNU software project.
    License: GPL
    Title:Guavac & Kaffe (Java)
    Author:???
    Repositories:[Guavac Documentation] [Kaffe Documentation]

    [Gauvac Source] [Kaffe Source]

    Newsgroup:
    Threads Lib:???
    Description:Guavac is a new compiler for the Java language, written by Effective Edge Technologies and distributed under the Gnu Public License. You should feel free to use, copy and modify it, based on the terms in the COPYING file included in this distribution.

    Kaffe is a virtual machine design to execute Java bytecode. Unlike other virtual machines available, this machine performs "just-in-time" code conversion from the abstract code to the host machine's native code. This will ultimately allow execution of Java code at the same speed as standard compiled code but while maintaining the advantages and flexibility of code independence.

    License: GPL
    Title:Modula-3/m3gdb
    Author:DEC Systems Research Center
    Repositories:[Documentation] [Source]
    Newsgroup:comp.lang.modula-3
    Threads Lib:(Uses own?)
    Description:Compiler, tools, applications and libraries for Modula-3, a simple and efficient modular, imperative language. Modula-3 has objects, threads, exceptions and generics. The libraries include X toolkits, a user interface builder, an embedded interpreted language and network objects.

    m3gdb is a GPL debugger for Modula-3.

    License: (see copyright, freely usable and redistributable)
    Title:Objective-C/Gnustep
    Author:www.gnustep.org
    Repositories:[Documentation] [Source]
    Newsgroup:comp.lang.objective-c
    Threads Lib:pthreads (user-level [kernel-level in development])
    Description:objc-shared-patches contains a complete source of GNU Objective-C runtime and a diff file for libobjects-0.1.14, both patched to generate a shared Linux ELF library.
    License: GPL
    Title:Python 1.4
    Author:www.python.org
    Repositories:[Documentation][Binary][Source]
    Newsgroup:comp.lang.python
    Threads Lib:POSIX threads (Python Makefile supports user-/ kernel-level)
    Description:Python is an interpreted, interactive, object-oriented programming language. It is often compared to Tcl, Perl, Scheme or Java.
    License: Freeware
    Title:Sather
    Author:ICSI
    Repositories:[Documentation] [Source]
    Newsgroup:comp.lang.sather
    Threads Lib:POSIX Threads
    Description:Sather is an object oriented language designed to be simple, efficient, safe, flexible and non-proprietary. One way of placing it in the "space of languages" is to say that it aims to be as efficient as C, C++, or Fortran, as elegant as and safer than Eiffel, and support higher-order functions and iteration abstraction as well as Common Lisp, CLU or Scheme.
    License: Freeware (?)
    Title:SmallTalkX
    Author:???
    Repositories:[Documentation] [Source]
    Newsgroup:comp.lang.smalltalk
    Threads Lib:(Internal implementation of threads.)
    Description:SmallTalk interpreter for X11. SmallTalk is an object-oriented, interpreted programming language. Often it is used in simulations or rapid prototyping.
    License: Noncommercial (see `LICENSE')


    How does one debug threads?

    • (Author's note: Good question. Any thoughts?)

    Modula-3

    • Modula-3 supports user-level thread breakpoints. For further help here, please refer to the Modula-3 specifications.

    C/C++ (and anything compatible with gdb)

    • Gdb supports children processes and threads equally (since they are based on the task paradigm) if they do not share PIDs. Those threads which share PIDs can be accessed using the good-old-fashioned printf debugging (for now). (Can someone guide me on accessing specific processes within gdb?)


    What do the individual flags mean and do in clone()?

    • The clone() system call has several flags that will indicate how much will be shared between threads. Below you will find a table listing each flag, its function & its implementation status.

    Flag

    Status

    Description

    CLONE_VMDoneShare data and stack
    CLONE_FSDoneShare filesystem info
    CLONE_FILESDoneShare open files
    CLONE_SIGHANDDoneShare signals
    CLONE_PIDAlmost DoneShare PID with parent (problems with /proc and signals go to parent)


    What applications or libraries currently use threads?

    • There are few applications and libraries that currently use threads (user or kernel) which have been ported to Linux. However, upon becoming known, they will appear in this list. This list will also hopefully indicate which apps need which threading library.

      (Author's Note: If you know of any applications or libraries that should appear in this list, please send me information about it in the same format.)

    Thread-Using Packages/Libraries

    Library:Adaptive Communication Environment (ACE)
    Author:Douglas C. Schmidt
    Repositories:[Documentation] [Source] [Mirror]
    Threads Lib:LinuxThreads
    Description:The ADAPTIVE Communication Environment (ACE) is an object-oriented programming toolkit for concurrent network applications and services. ACE encapsulates user-level UNIX and Win32 (Windows NT and Windows '95) OS mechanisms via portable, type-secure, efficient, and object-oriented interfaces. In addition, ACE contains a number of higher-level class categories and network programming frameworks. The following diagram illustrates the key components in ACE and their hierarchical relationships.
    License: [License]
    Title:AolServer
    Author:www.aolserver.com
    Repositories:[Source]
    Threads Lib:(Uses internal system calls to clone()?)
    Description:A free webserver (multiplatform) [Feature Sheet]
    License: [License]
    Title:Executor
    Author:www.ardi.com
    Repositories:[Source]
    Threads Lib:???
    Description:A100% native software Macintosh emulator for PCs. Executor lets you read and write Mac-formatted high-density floppies and Mac SCSI drives, read Mac CDs, and run many Macintosh programs.
    License: (commerial software)


    Where can I learn more about threads?




    Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1; with no Invariant Sections, with no Front-Cover Texts, and with no Back-Cover Texts. A copy of the license is included at the URL http://www.linas.org/fdl.html, the web page titled "GNU Free Documentation License".

    展开全文
  • linux thread

    千次阅读 2012-04-30 23:25:21
    三.LinuxThread的线程机制 LinuxThreads 是目前Linux平台上使用最为广泛的线程库,由Xavier Leroy (Xavier.Leroy@inria.fr)负责开发完成,并已绑定在GLIBC中发行。它所实现的就是基于核心轻量级进程的"一对一...
     
    

    一.基础知识:线程和进程

    按照教科书上的定义,进程是资源管理的最小单位,线程是程序执行的最小单位。在操作系统设计上,从进程演化出线程,最主要的目的就是更好的支持SMP以及减小(进程/线程)上下文切换开销。

    无 论按照怎样的分法,一个进程至少需要一个线程作为它的指令执行体,进程管理着资源(比如cpu、内存、文件等等),而将线程分配到某个cpu上执行。一个 进程当然可以拥有多个线程,此时,如果进程运行在SMP机器上,它就可以同时使用多个cpu来执行各个线程,达到最大程度的并行,以提高效率;同时,即使 是在单cpu的机器上,采用多线程模型来设计程序,正如当年采用多进程模型代替单进程模型一样,使设计更简洁、功能更完备,程序的执行效率也更高,例如采 用多个线程响应多个输入,而此时多线程模型所实现的功能实际上也可以用多进程模型来实现,而与后者相比,线程的上下文切换开销就比进程要小多了,从语义上 来说,同时响应多个输入这样的功能,实际上就是共享了除cpu以外的所有资源的。

    针对线程模型的两大意义,分别开发出了核 心级线程和用户级线程两种线程模型,分类的标准主要是线程的调度者在核内还是在核外。前者更利于并发使用多处理器的资源,而后者则更多考虑的是上下文切换 开销。在目前的商用系统中,通常都将两者结合起来使用,既提供核心线程以满足smp系统的需要,也支持用线程库的方式在用户态实现另一套线程机制,此时一 个核心线程同时成为多个用户态线程的调度者。正如很多技术一样,"混合"通常都能带来更高的效率,但同时也带来更大的实现难度,出于"简单"的设计思路, Linux从一开始就没有实现混合模型的计划,但它在实现上采用了另一种思路的"混合"。

    在线程机制的具体实现上,可以在 操作系统内核上实现线程,也可以在核外实现,后者显然要求核内至少实现了进程,而前者则一般要求在核内同时也支持进程。核心级线程模型显然要求前者的支 持,而用户级线程模型则不一定基于后者实现。这种差异,正如前所述,是两种分类方式的标准不同带来的。

    当核内既支持进程也支 持线程时,就可以实现线程-进程的"多对多"模型,即一个进程的某个线程由核内调度,而同时它也可以作为用户级线程池的调度者,选择合适的用户级线程在其 空间中运行。这就是前面提到的"混合"线程模型,既可满足多处理机系统的需要,也可以最大限度的减小调度开销。绝大多数商业操作系统(如Digital Unix、Solaris、Irix)都采用的这种能够完全实现POSIX1003.1c标准的线程模型。在核外实现的线程又可以分为"一对一"、"多对 一"两种模型,前者用一个核心进程(也许是轻量进程)对应一个线程,将线程调度等同于进程调度,交给核心完成,而后者则完全在核外实现多线程,调度也在用 户态完成。后者就是前面提到的单纯的用户级线程模型的实现方式,显然,这种核外的线程调度器实际上只需要完成线程运行栈的切换,调度开销非常小,但同时因 为核心信号(无论是同步的还是异步的)都是以进程为单位的,因而无法定位到线程,所以这种实现方式不能用于多处理器系统,而这个需求正变得越来越大,因 此,在现实中,纯用户级线程的实现,除算法研究目的以外,几乎已经消失了。

    Linux内核只提供了轻量进程的支持,限制了 更高效的线程模型的实现,但Linux着重优化了进程的调度开销,一定程度上也弥补了这一缺陷。目前最流行的线程机制LinuxThreads所采用的就 是线程-进程"一对一"模型,调度交给核心,而在用户级实现一个包括信号处理在内的线程管理机制。Linux-LinuxThreads的运行机制正是本 文的描述重点。


     

     


    二.Linux 2.4内核中的轻量进程实现

    最 初的进程定义都包含程序、资源及其执行三部分,其中程序通常指代码,资源在操作系统层面上通常包括内存资源、IO资源、信号处理等部分,而程序的执行通常 理解为执行上下文,包括对cpu的占用,后来发展为线程。在线程概念出现以前,为了减小进程切换的开销,操作系统设计者逐渐修正进程的概念,逐渐允许将进 程所占有的资源从其主体剥离出来,允许某些进程共享一部分资源,例如文件、信号,数据内存,甚至代码,这就发展出轻量进程的概念。Linux内核在 2.0.x版本就已经实现了轻量进程,应用程序可以通过一个统一的clone()系统调用接口,用不同的参数指定创建轻量进程还是普通进程。在内核中, clone()调用经过参数传递和解释后会调用do_fork(),这个核内函数同时也是fork()、vfork()系统调用的最终实现:

    
    int do_fork(unsigned long clone_flags, unsigned long stack_start, 
    struct pt_regs *regs, unsigned long stack_size)
    其中的clone_flags取自以下宏的"或"值:
    
    #define CSIGNAL			0x000000ff	/* signal mask to be sent at exit */
    #define CLONE_VM		0x00000100	/* set if VM shared between processes */
    #define CLONE_FS        0x00000200	/* set if fs info shared between processes */
    #define CLONE_FILES     0x00000400	/* set if open files shared between processes */
    #define CLONE_SIGHAND	0x00000800	/* set if signal handlers and blocked signals shared */
    #define CLONE_PID		0x00001000	/* set if pid shared */
    #define CLONE_PTRACE	0x00002000	/* set if we want to let tracing continue on the child too */
    #define CLONE_VFORK	0x00004000	/* set if the parent wants the child to wake it up on mm_release */
    #define CLONE_PARENT	0x00008000	/* set if we want to have the same parent as the cloner */
    #define CLONE_THREAD	0x00010000	/* Same thread group? */
    #define CLONE_NEWNS	0x00020000	/* New namespace group? */
    #define CLONE_SIGNAL	 (CLONE_SIGHAND | CLONE_THREAD)
    
    
    

    在do_fork()中,不同的clone_flags将导致不同的行为,对于LinuxThreads,它使用(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND)参数来调用clone()创建"线程",表示共享内存、共享文件系统访问计数、共享文件描述符表,以及共享信号处理方式。本 节就针对这几个参数,看看Linux内核是如何实现这些资源的共享的。

    1.CLONE_VM

    do_fork ()需要调用copy_mm()来设置task_struct中的mm和active_mm项,这两个mm_struct数据与进程所关联的内存空间相对 应。如果do_fork()时指定了CLONE_VM开关,copy_mm()将把新的task_struct中的mm和active_mm设置成与 current的相同,同时提高该mm_struct的使用者数目(mm_struct::mm_users)。也就是说,轻量级进程与父进程共享内存地 址空间,由下图示意可以看出mm_struct在进程中的地位:


     2.CLONE_FS

    task_struct中利用fs (struct fs_struct *)记录了进程所在文件系统的根目录和当前目录信息,do_fork()时调用copy_fs()复制了这个结构;而对于轻量级进程则仅增加fs- >count计数,与父进程共享相同的fs_struct。也就是说,轻量级进程没有独立的文件系统相关的信息,进程中任何一个线程改变当前目录、 根目录等信息都将直接影响到其他线程。

    3.CLONE_FILES

    一 个进程可能打开了一些文件,在进程结构task_struct中利用files(struct files_struct *)来保存进程打开的文件结构(struct file)信息,do_fork()中调用了copy_files()来处理这个进程属性;轻量级进程与父进程是共享该结构的,copy_files() 时仅增加fils->count计数。这一共享使得任何线程都能访问进程所维护的打开文件,对它们的操作会直接反映到进程中的其他线程。

    4.CLONE_SIGHAND

    每 一个Linux进程都可以自行定义对信号的处理方式,在task_struct中的sig(struct signal_struct)中使用一个struct k_sigaction结构的数组来保存这个配置信息,do_fork()中的copy_sighand()负责复制该信息;轻量级进程不进行复制,而仅 仅增加signal_struct::count计数,与父进程共享该结构。也就是说,子进程与父进程的信号处理方式完全相同,而且可以相互更改。

    do_fork()中所做的工作很多,在此不详细描述。对于SMP系统,所有的进程fork出来后,都被分配到与父进程相同的cpu上,一直到该进程被调度时才会进行cpu选择。

    尽 管Linux支持轻量级进程,但并不能说它就支持核心级线程,因为Linux的"线程"和"进程"实际上处于一个调度层次,共享一个进程标识符空间,这种 限制使得不可能在Linux上实现完全意义上的POSIX线程机制,因此众多的Linux线程库实现尝试都只能尽可能实现POSIX的绝大部分语义,并在 功能上尽可能逼近。

    三.LinuxThread的线程机制

    LinuxThreads 是目前Linux平台上使用最为广泛的线程库,由Xavier Leroy (Xavier.Leroy@inria.fr)负责开发完成,并已绑定在GLIBC中发行。它所实现的就是基于核心轻量级进程的"一对一"线程模型,一 个线程实体对应一个核心轻量级进程,而线程之间的管理在核外函数库中实现。

    1.线程描述数据结构及实现限制

    LinuxThreads 定义了一个struct _pthread_descr_struct数据结构来描述线程,并使用全局数组变量__pthread_handles来描述和引用进程所辖线程。在 __pthread_handles中的前两项,LinuxThreads定义了两个全局的系统线程:__pthread_initial_thread 和__pthread_manager_thread,并用__pthread_main_thread表征 __pthread_manager_thread的父线程(初始为__pthread_initial_thread)。

    struct _pthread_descr_struct是一个双环链表结构,__pthread_manager_thread所在的链表仅包括它一个元素,实际 上,__pthread_manager_thread是一个特殊线程,LinuxThreads仅使用了其中的errno、p_pid、 p_priority等三个域。而__pthread_main_thread所在的链则将进程中所有用户线程串在了一起。经过一系列 pthread_create()之后形成的__pthread_handles数组将如下图所示:



    新创建的线程将首先在__pthread_handles数组中占据一项,然后通过数据结构中的链指针连入以__pthread_main_thread为首指针的链表中。这个链表的使用在介绍线程的创建和释放的时候将提到。

    LinuxThreads 遵循POSIX1003.1c标准,其中对线程库的实现进行了一些范围限制,比如进程最大线程数,线程私有数据区大小等等。在LinuxThreads的 实现中,基本遵循这些限制,但也进行了一定的改动,改动的趋势是放松或者说扩大这些限制,使编程更加方便。这些限定宏主要集中在 sysdeps/unix/sysv/linux/bits/local_lim.h(不同平台使用的文件位置不同)中,包括如下几个:

    每 进程的私有数据key数,POSIX定义_POSIX_THREAD_KEYS_MAX为128,LinuxThreads使用 PTHREAD_KEYS_MAX,1024;私有数据释放时允许执行的操作数,LinuxThreads与POSIX一致,定义 PTHREAD_DESTRUCTOR_ITERATIONS为4;每进程的线程数,POSIX定义为64,LinuxThreads增大到1024 (PTHREAD_THREADS_MAX);线程运行栈最小空间大小,POSIX未指定,LinuxThreads使用 PTHREAD_STACK_MIN,16384(字节)。

    2.管理线程

    " 一对一"模型的好处之一是线程的调度由核心完成了,而其他诸如线程取消、线程间的同步等工作,都是在核外线程库中完成的。在LinuxThreads中, 专门为每一个进程构造了一个管理线程,负责处理线程相关的管理工作。当进程第一次调用pthread_create()创建一个线程的时候就会创建 (__clone())并启动管理线程。

    在一个进程空间内,管理线程与其他线程之间通过一对"管理管道 (manager_pipe[2])"来通讯,该管道在创建管理线程之前创建,在成功启动了管理线程之后,管理管道的读端和写端分别赋给两个全局变量 __pthread_manager_reader和__pthread_manager_request,之后,每个用户线程都通过 __pthread_manager_request向管理线程发请求,但管理线程本身并没有直接使用 __pthread_manager_reader,管道的读端(manager_pipe[0])是作为__clone()的参数之一传给管理线程的, 管理线程的工作主要就是监听管道读端,并对从中取出的请求作出反应。

    创建管理线程的流程如下所示: 
    (全局变量pthread_manager_request初值为-1) 

    初始化结束后,在__pthread_manager_thread中记录了轻量级进程号以及核外分配和管理的线程id, 2*PTHREAD_THREADS_MAX+1这个数值不会与任何常规用户线程id冲突。管理线程作为pthread_create()的调用者线程的 子线程运行,而pthread_create()所创建的那个用户线程则是由管理线程来调用clone()创建,因此实际上是管理线程的子线程。(此处子 线程的概念应该当作子进程来理解。)

    __pthread_manager()就是管理线程的主循环所在,在进行一系列初始 化工作后,进入while(1)循环。在循环中,线程以2秒为timeout查询(__poll())管理管道的读端。在处理请求前,检查其父线程(也就 是创建manager的主线程)是否已退出,如果已退出就退出整个进程。如果有退出的子线程需要清理,则调用pthread_reap_children ()清理。

    然后才是读取管道中的请求,根据请求类型执行相应操作(switch-case)。具体的请求处理,源码中比较清楚,这里就不赘述了。

    3.线程栈

    在LinuxThreads中,管理线程的栈和用户线程的栈是分离的,管理线程在进程堆中通过malloc()分配一个THREAD_MANAGER_STACK_SIZE字节的区域作为自己的运行栈。

    用 户线程的栈分配办法随着体系结构的不同而不同,主要根据两个宏定义来区分,一个是NEED_SEPARATE_REGISTER_STACK,这个属性仅 在IA64平台上使用;另一个是FLOATING_STACK宏,在i386等少数平台上使用,此时用户线程栈由系统决定具体位置并提供保护。与此同时, 用户还可以通过线程属性结构来指定使用用户自定义的栈。因篇幅所限,这里只能分析i386平台所使用的两种栈组织方式:FLOATING_STACK方式 和用户自定义方式。

    在FLOATING_STACK方式下,LinuxThreads利用mmap()从内核空间中分配 8MB空间(i386系统缺省的最大栈空间大小,如果有运行限制(rlimit),则按照运行限制设置),使用mprotect()设置其中第一页为非访 问区。该8M空间的功能分配如下图:



    低地址被保护的页面用来监测栈溢出。

    对于用户指定的栈,在按照指针对界后,设置线程栈顶,并计算出栈底,不做保护,正确性由用户自己保证。

    不论哪种组织方式,线程描述结构总是位于栈顶紧邻堆栈的位置。

    4.线程id和进程id

    每个LinuxThreads线程都同时具有线程id和进程id,其中进程id就是内核所维护的进程号,而线程id则由LinuxThreads分配和维护。

    __pthread_initial_thread 的线程id为PTHREAD_THREADS_MAX,__pthread_manager_thread的是 2*PTHREAD_THREADS_MAX+1,第一个用户线程的线程id为PTHREAD_THREADS_MAX+2,此后第n个用户线程的线程 id遵循以下公式:

    tid=n*PTHREAD_THREADS_MAX+n+1
    

    这种分配方式保证了进程中所有的线程(包括已经退出)都不会有相同的线程id,而线程id的类型pthread_t定义为无符号长整型(unsigned long int),也保证了有理由的运行时间内线程id不会重复。

    从线程id查找线程数据结构是在pthread_handle()函数中完成的,实际上只是将线程号按PTHREAD_THREADS_MAX取模,得到的就是该线程在__pthread_handles中的索引。

    5.线程的创建

    在pthread_create ()向管理线程发送REQ_CREATE请求之后,管理线程即调用pthread_handle_create()创建新线程。分配栈、设置thread 属性后,以pthread_start_thread()为函数入口调用__clone()创建并启动新线程。pthread_start_thread ()读取自身的进程id号存入线程描述结构中,并根据其中记录的调度方法配置调度。一切准备就绪后,再调用真正的线程执行函数,并在此函数返回后调用 pthread_exit()清理现场。

    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在实时性上考虑都很少。


    四.其他的线程实现机制

    LinuxThreads 的问题,特别是兼容性上的问题,严重阻碍了Linux上的跨平台应用(如Apache)采用多线程设计,从而使得Linux上的线程应用一直保持在比较低 的水平。在Linux社区中,已经有很多人在为改进线程性能而努力,其中既包括用户级线程库,也包括核心级和用户级配合改进的线程库。目前最为人看好的有 两个项目,一个是RedHat公司牵头研发的NPTL(Native Posix Thread Library),另一个则是IBM投资开发的NGPT(Next Generation Posix Threading),二者都是围绕完全兼容POSIX 1003.1c,同时在核内和核外做工作以而实现多对多线程模型。这两种模型都在一定程度上弥补了LinuxThreads的缺点,且都是重起炉灶全新设 计的。

    1.NPTL

    NPTL的设计目标归纳可归纳为以下几点:

    • POSIX兼容性
    • SMP结构的利用
    • 低启动开销
    • 低链接开销(即不使用线程的程序不应当受线程库的影响)
    • 与LinuxThreads应用的二进制兼容性
    • 软硬件的可扩展能力
    • 多体系结构支持
    • NUMA支持
    • 与C++集成

    在 技术实现上,NPTL仍然采用1:1的线程模型,并配合glibc和最新的Linux Kernel2.5.x开发版在信号处理、线程同步、存储管理等多方面进行了优化。和LinuxThreads不同,NPTL没有使用管理线程,核心线程 的管理直接放在核内进行,这也带了性能的优化。

    主要是因为核心的问题,NPTL仍然不是100%POSIX兼容的,但就性能而言相对LinuxThreads已经有很大程度上的改进了。

    2.NGPT

    IBM的开放源码项目NGPT在2003年1月10日推出了稳定的2.2.0版,但相关的文档工作还差很多。就目前所知,NGPT是基于GNU Pth(GNU Portable Threads)项目而实现的M:N模型,而GNU Pth是一个经典的用户级线程库实现。

    按照2003年3月NGPT官方网站上的通知,NGPT考虑到NPTL日益广泛地为人所接受,为避免不同的线程库版本引起的混乱,今后将不再进行进一步开发,而今进行支持性的维护工作。也就是说,NGPT已经放弃与NPTL竞争下一代Linux POSIX线程库标准。

    3.其他高效线程机制

    此 处不能不提到Scheduler Activations。这个1991年在ACM上发表的多线程内核结构影响了很多多线程内核的设计,其中包括Mach3.0、NetBSD和商业版本 Digital Unix(现在叫Compaq True64 Unix)。它的实质是在使用用户级线程调度的同时,尽可能地减少用户级对核心的系统调用请求,而后者往往是运行开销的重要来源。采用这种结构的线程机 制,实际上是结合了用户级线程的灵活高效和核心级线程的实用性,因此,包括Linux、FreeBSD在内的多个开放源码操作系统设计社区都在进行相关研 究,力图在本系统中实现Scheduler Activations。


    /**************引用结束**************/
    展开全文
  • linux Thread

    千次阅读 2008-04-01 16:59:00
     三.LinuxThread的线程机制   LinuxThreads是目前Linux平台上使用最为广泛的线程库,由Xavier Leroy (Xavier.Leroy@inria.fr)负责开发完成,并已绑定在GLIBC中发行。它所实现的就是基于核心轻量级进程的"一...
      
    

    一.基础知识:线程和进程 

     按照教科书上的定义,进程是资源管理的最小单位,线程是程序执行的最小单位。在操作系统设计上,从进程演化出线程,最主要的目的就是更好的支持SMP以及减小(进程/线程)上下文切换开销。 

     无论按照怎样的分法,一个进程至少需要一个线程作为它的指令执行体,进程管理着资源(比如cpu、内存、文件等等),而将线程分配到某个cpu上执行。一个进程当然可以拥有多个线程,此时,如果进程运行在SMP机器上,它就可以同时使用多个cpu来执行各个线程,达到最大程度的并行,以提高效率;同时,即使是在单cpu的机器上,采用多线程模型来设计程序,正如当年采用多进程模型代替单进程模型一样,使设计更简洁、功能更完备,程序的执行效率也更高,例如采用多个线程响应多个输入,而此时多线程模型所实现的功能实际上也可以用多进程模型来实现,而与后者相比,线程的上下文切换开销就比进程要小多了,从语义上来说,同时响应多个输入这样的功能,实际上就是共享了除cpu以外的所有资源的。 

     针对线程模型的两大意义,分别开发出了核心级线程和用户级线程两种线程模型,分类的标准主要是线程的调度者在核内还是在核外。前者更利于并发使用多处理器的资源,而后者则更多考虑的是上下文切换开销。在目前的商用系统中,通常都将两者结合起来使用,既提供核心线程以满足smp系统的需要,也支持用线程库的方式在用户态实现另一套线程机制,此时一个核心线程同时成为多个用户态线程的调度者。正如很多技术一样,"混合"通常都能带来更高的效率,但同时也带来更大的实现难度,出于"简单"的设计思路,Linux从一开始就没有实现混合模型的计划,但它在实现上采用了另一种思路的"混合"。 

     在线程机制的具体实现上,可以在操作系统内核上实现线程,也可以在核外实现,后者显然要求核内至少实现了进程,而前者则一般要求在核内同时也支持进程。核心级线程模型显然要求前者的支持,而用户级线程模型则不一定基于后者实现。这种差异,正如前所述,是两种分类方式的标准不同带来的。 

     当核内既支持进程也支持线程时,就可以实现线程-进程的"多对多"模型,即一个进程的某个线程由核内调度,而同时它也可以作为用户级线程池的调度者,选择合适的用户级线程在其空间中运行。这就是前面提到的"混合"线程模型,既可满足多处理机系统的需要,也可以最大限度的减小调度开销。绝大多数商业操作系统(如Digital Unix、Solaris、Irix)都采用的这种能够完全实现POSIX1003.1c标准的线程模型。在核外实现的线程又可以分为"一对一"、"多对一"两种模型,前者用一个核心进程(也许是轻量进程)对应一个线程,将线程调度等同于进程调度,交给核心完成,而后者则完全在核外实现多线程,调度也在用户态完成。后者就是前面提到的单纯的用户级线程模型的实现方式,显然,这种核外的线程调度器实际上只需要完成线程运行栈的切换,调度开销非常小,但同时因为核心信号(无论是同步的还是异步的)都是以进程为单位的,因而无法定位到线程,所以这种实现方式不能用于多处理器系统,而这个需求正变得越来越大,因此,在现实中,纯用户级线程的实现,除算法研究目的以外,几乎已经消失了。 

     Linux内核只提供了轻量进程的支持,限制了更高效的线程模型的实现,但Linux着重优化了进程的调度开销,一定程度上也弥补了这一缺陷。目前最流行的线程机制LinuxThreads所采用的就是线程-进程"一对一"模型,调度交给核心,而在用户级实现一个包括信号处理在内的线程管理机制。Linux-LinuxThreads的运行机制正是本文的描述重点。 

     二.Linux 2.4内核中的轻量进程实现 

     最初的进程定义都包含程序、资源及其执行三部分,其中程序通常指代码,资源在操作系统层面上通常包括内存资源、IO资源、信号处理等部分,而程序的执行通常理解为执行上下文,包括对cpu的占用,后来发展为线程。在线程概念出现以前,为了减小进程切换的开销,操作系统设计者逐渐修正进程的概念,逐渐允许将进程所占有的资源从其主体剥离出来,允许某些进程共享一部分资源,例如文件、信号,数据内存,甚至代码,这就发展出轻量进程的概念。Linux内核在2.0.x版本就已经实现了轻量进程,应用程序可以通过一个统一的clone()系统调用接口,用不同的参数指定创建轻量进程还是普通进程。在内核中,clone()调用经过参数传递和解释后会调用do_fork(),这个核内函数同时也是fork()、vfork()系统调用的最终实现: 

     
     <linux-2.4.20/kernel/fork.c> 
     int do_fork(unsigned long clone_flags, unsigned long stack_start,  
     struct pt_regs *regs, unsigned long stack_size) 

     其中的clone_flags取自以下宏的"或"值: 

     
     <linux-2.4.20/include/linux/sched.h> 
     #define CSIGNAL 0x000000ff /* signal mask to be sent at exit */ 
     #define CLONE_VM 0x00000100 /* set if VM shared between processes */ 
     #define CLONE_FS        0x00000200 /* set if fs info shared between processes */ 
     #define CLONE_FILES     0x00000400 /* set if open files shared between processes */ 
     #define CLONE_SIGHAND 0x00000800 /* set if signal handlers and blocked signals shared */ 
     #define CLONE_PID 0x00001000 /* set if pid shared */ 
     #define CLONE_PTRACE 0x00002000 /* set if we want to let tracing continue on the child too */ 
     #define CLONE_VFORK 0x00004000 /* set if the parent wants the child to wake it up on mm_release */ 
     #define CLONE_PARENT 0x00008000 /* set if we want to have the same parent as the cloner */ 
     #define CLONE_THREAD 0x00010000 /* Same thread group? */ 
     #define CLONE_NEWNS 0x00020000 /* New namespace group? */ 
     #define CLONE_SIGNAL  (CLONE_SIGHAND | CLONE_THREAD) 

     在do_fork()中,不同的clone_flags将导致不同的行为,对于LinuxThreads,它使用(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND)参数来调用clone()创建"线程",表示共享内存、共享文件系统访问计数、共享文件描述符表,以及共享信号处理方式。本节就针对这几个参数,看看Linux内核是如何实现这些资源的共享的。 

     1.CLONE_VM 

     do_fork()需要调用copy_mm()来设置task_struct中的mm和active_mm项,这两个mm_struct数据与进程所关联的内存空间相对应。如果do_fork()时指定了CLONE_VM开关,copy_mm()将把新的task_struct中的mm和active_mm设置成与current的相同,同时提高该mm_struct的使用者数目(mm_struct::mm_users)。也就是说,轻量级进程与父进程共享内存地址空间,由下图示意可以看出mm_struct在进程中的地位: 

     
     2.CLONE_FS 

     task_struct中利用fs(struct fs_struct *)记录了进程所在文件系统的根目录和当前目录信息,do_fork()时调用copy_fs()复制了这个结构;而对于轻量级进程则仅增加fs->count计数,与父进程共享相同的fs_struct。也就是说,轻量级进程没有独立的文件系统相关的信息,进程中任何一个线程改变当前目录、根目录等信息都将直接影响到其他线程。 

     3.CLONE_FILES 

     一个进程可能打开了一些文件,在进程结构task_struct中利用files(struct files_struct *)来保存进程打开的文件结构(struct file)信息,do_fork()中调用了copy_files()来处理这个进程属性;轻量级进程与父进程是共享该结构的,copy_files()时仅增加files->count计数。这一共享使得任何线程都能访问进程所维护的打开文件,对它们的操作会直接反映到进程中的其他线程。 

     4.CLONE_SIGHAND 

     每一个Linux进程都可以自行定义对信号的处理方式,在task_struct中的sig(struct signal_struct)中使用一个struct k_sigaction结构的数组来保存这个配置信息,do_fork()中的copy_sighand()负责复制该信息;轻量级进程不进行复制,而仅仅增加signal_struct::count计数,与父进程共享该结构。也就是说,子进程与父进程的信号处理方式完全相同,而且可以相互更改。 

     do_fork()中所做的工作很多,在此不详细描述。对于SMP系统,所有的进程fork出来后,都被分配到与父进程相同的cpu上,一直到该进程被调度时才会进行cpu选择。 

     尽管Linux支持轻量级进程,但并不能说它就支持核心级线程,因为Linux的"线程"和"进程"实际上处于一个调度层次,共享一个进程标识符空间,这种限制使得不可能在Linux上实现完全意义上的POSIX线程机制,因此众多的Linux线程库实现尝试都只能尽可能实现POSIX的绝大部分语义,并在功能上尽可能逼近。 

     三.LinuxThread的线程机制 

     LinuxThreads是目前Linux平台上使用最为广泛的线程库,由Xavier Leroy (Xavier.Leroy@inria.fr)负责开发完成,并已绑定在GLIBC中发行。它所实现的就是基于核心轻量级进程的"一对一"线程模型,一个线程实体对应一个核心轻量级进程,而线程之间的管理在核外函数库中实现。 

     1.线程描述数据结构及实现限制 

     LinuxThreads定义了一个struct _pthread_descr_struct数据结构来描述线程,并使用全局数组变量__pthread_handles来描述和引用进程所辖线程。在__pthread_handles中的前两项,LinuxThreads定义了两个全局的系统线程:__pthread_initial_thread和__pthread_manager_thread,并用__pthread_main_thread表征__pthread_manager_thread的父线程(初始为__pthread_initial_thread)。 

     struct _pthread_descr_struct是一个双环链表结构,__pthread_manager_thread所在的链表仅包括它一个元素,实际上,__pthread_manager_thread是一个特殊线程,LinuxThreads仅使用了其中的errno、p_pid、p_priority等三个域。而__pthread_main_thread所在的链则将进程中所有用户线程串在了一起。经过一系列pthread_create()之后形成的__pthread_handles数组将如下图所示: 

     图2 __pthread_handles数组结构 

     新创建的线程将首先在__pthread_handles数组中占据一项,然后通过数据结构中的链指针连入以__pthread_main_thread为首指针的链表中。这个链表的使用在介绍线程的创建和释放的时候将提到。 

     LinuxThreads遵循POSIX1003.1c标准,其中对线程库的实现进行了一些范围限制,比如进程最大线程数,线程私有数据区大小等等。在LinuxThreads的实现中,基本遵循这些限制,但也进行了一定的改动,改动的趋势是放松或者说扩大这些限制,使编程更加方便。这些限定宏主要集中在sysdeps/unix/sysv/linux/bits/local_lim.h(不同平台使用的文件位置不同)中,包括如下几个: 

     每进程的私有数据key数,POSIX定义_POSIX_THREAD_KEYS_MAX为128,LinuxThreads使用PTHREAD_KEYS_MAX,1024;私有数据释放时允许执行的操作数,LinuxThreads与POSIX一致,定义PTHREAD_DESTRUCTOR_ITERATIONS为4;每进程的线程数,POSIX定义为64,LinuxThreads增大到1024(PTHREAD_THREADS_MAX);线程运行栈最小空间大小,POSIX未指定,LinuxThreads使用PTHREAD_STACK_MIN,16384(字节)。 

     2.管理线程 

     "一对一"模型的好处之一是线程的调度由核心完成了,而其他诸如线程取消、线程间的同步等工作,都是在核外线程库中完成的。在LinuxThreads中,专门为每一个进程构造了一个管理线程,负责处理线程相关的管理工作。当进程第一次调用pthread_create()创建一个线程的时候就会创建(__clone())并启动管理线程。 

     在一个进程空间内,管理线程与其他线程之间通过一对"管理管道(manager_pipe[2])"来通讯,该管道在创建管理线程之前创建,在成功启动了管理线程之后,管理管道的读端和写端分别赋给两个全局变量__pthread_manager_reader和__pthread_manager_request,之后,每个用户线程都通过__pthread_manager_request向管理线程发请求,但管理线程本身并没有直接使用__pthread_manager_reader,管道的读端(manager_pipe[0])是作为__clone()的参数之一传给管理线程的,管理线程的工作主要就是监听管道读端,并对从中取出的请求作出反应。 

     创建管理线程的流程如下所示: 
     (全局变量pthread_manager_request初值为-1) 

     图3 创建管理线程的流程 

     初始化结束后,在__pthread_manager_thread中记录了轻量级进程号以及核外分配和管理的线程id,2*PTHREAD_THREADS_MAX+1这个数值不会与任何常规用户线程id冲突。管理线程作为pthread_create()的调用者线程的子线程运行,而pthread_create()所创建的那个用户线程则是由管理线程来调用clone()创建,因此实际上是管理线程的子线程。(此处子线程的概念应该当作子进程来理解。) 

     __pthread_manager()就是管理线程的主循环所在,在进行一系列初始化工作后,进入while(1)循环。在循环中,线程以2秒为timeout查询(__poll())管理管道的读端。在处理请求前,检查其父线程(也就是创建manager的主线程)是否已退出,如果已退出就退出整个进程。如果有退出的子线程需要清理,则调用pthread_reap_children()清理。 

     然后才是读取管道中的请求,根据请求类型执行相应操作(switch-case)。具体的请求处理,源码中比较清楚,这里就不赘述了。 

     3.线程栈 

     在LinuxThreads中,管理线程的栈和用户线程的栈是分离的,管理线程在进程堆中通过malloc()分配一个THREAD_MANAGER_STACK_SIZE字节的区域作为自己的运行栈。 

     用户线程的栈分配办法随着体系结构的不同而不同,主要根据两个宏定义来区分,一个是NEED_SEPARATE_REGISTER_STACK,这个属性仅在IA64平台上使用;另一个是FLOATING_STACK宏,在i386等少数平台上使用,此时用户线程栈由系统决定具体位置并提供保护。与此同时,用户还可以通过线程属性结构来指定使用用户自定义的栈。因篇幅所限,这里只能分析i386平台所使用的两种栈组织方式:FLOATING_STACK方式和用户自定义方式。 

     在FLOATING_STACK方式下,LinuxThreads利用mmap()从内核空间中分配8MB空间(i386系统缺省的最大栈空间大小,如果有运行限制(rlimit),则按照运行限制设置),使用mprotect()设置其中第一页为非访问区。该8M空间的功能分配如下图: 

     图4 栈结构示意 

     低地址被保护的页面用来监测栈溢出。 

     对于用户指定的栈,在按照指针对界后,设置线程栈顶,并计算出栈底,不做保护,正确性由用户自己保证。 

     不论哪种组织方式,线程描述结构总是位于栈顶紧邻堆栈的位置。 

     4.线程id和进程id 

     每个LinuxThreads线程都同时具有线程id和进程id,其中进程id就是内核所维护的进程号,而线程id则由LinuxThreads分配和维护。 

     __pthread_initial_thread的线程id为PTHREAD_THREADS_MAX,__pthread_manager_thread的是2*PTHREAD_THREADS_MAX+1,第一个用户线程的线程id为PTHREAD_THREADS_MAX+2,此后第n个用户线程的线程id遵循以下公式: 

     
     tid=n*PTHREAD_THREADS_MAX+n+1 

     
     这种分配方式保证了进程中所有的线程(包括已经退出)都不会有相同的线程id,而线程id的类型pthread_t定义为无符号长整型(unsigned long int),也保证了有理由的运行时间内线程id不会重复。 

     从线程id查找线程数据结构是在pthread_handle()函数中完成的,实际上只是将线程号按PTHREAD_THREADS_MAX取模,得到的就是该线程在__pthread_handles中的索引。 

     5.线程的创建 

     在pthread_create()向管理线程发送REQ_CREATE请求之后,管理线程即调用pthread_handle_create()创建新线程。分配栈、设置thread属性后,以pthread_start_thread()为函数入口调用__clone()创建并启动新线程。pthread_start_thread()读取自身的进程id号存入线程描述结构中,并根据其中记录的调度方法配置调度。一切准备就绪后,再调用真正的线程执行函数,并在此函数返回后调用pthread_exit()清理现场。 

     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在实时性上考虑都很少。

    展开全文
  • ACE中的Thread Mutex在linux下的使用

    千次阅读 2011-09-26 18:01:38
    ACE库中专门对线程同步提供了两个类,一个是ACE_Thread_Mutex另一个是ACE_REcursive_Thread_Mutex。 在我看 来,在linux下进行线程同步,不要使用ACE_Thread_Mutex,用ACE_REcursive_Thread_Mutex就可
  • linux thread model

    千次阅读 2011-09-06 11:40:14
    Linux上posix线程库实现原理讨论 说明一下,这个问题困扰我好长时间,因为正如使用C编程会希望了解进程的内存映象一样,使用POSIX线程库我们也会想去了解其实现的原理。目前只是查过一些资料,或许仍然有误解的...
  • Linux下JVM中可生成的最大Thread数量

    千次阅读 2011-12-04 01:12:27
    JVM中可以生成的最大数量由JVM的堆内存大小、Thread的Stack内存大小、系统最大可创建的线程数量(Java线程的实现是基于底层系统的线程机制来实现的,Windows_beginthreadex,Linux下pthread_create)三个方面影响...
  • 其中,Linux下用C++11创建多线程分为可连接的和不可连接的。 可连接线程:需要调用thread成员函数thread::join()阻塞等待线程结束并且回收资源;thread默认创建的线程是可连接线程! 不可连接线程(也就是分离线程...
  • get linux thread id

    千次阅读 2011-12-17 18:03:18
    first a thread program sample #include #include #include static int i = 0; void *threadfunc(void* pId){ int j = *(int*)pId; while(1){ printf("thread %d,%d\n", *(int*)pId,j++
  • 这两个月要开始RT-Thread了,在此记录一下过程,也好给后人指一条路。 既然都已经到这里来了也就不用我介绍RT-Thread了吧;)。 各种工具安装 编译器、调试器 我选择了直接在系统仓库里面下载 sudo apt...
  • Linux+QEMU 搭建 RT-Thread 开发环境

    千次阅读 2019-05-28 18:14:02
    概述 嵌入式软件开发离不开...为了方便大家在没有开发板的情况体验 RT-Thread,RT-Thread 使用 QEMU 模拟了 ARM vexpress A9 开发板,并提供了相关的板级支持包(BSP)。本文主要介绍在 Linux 平台使用 RT-Thr...
  • [C++]记在Linux下使用std::thread的错误

    千次阅读 2018-08-21 17:01:56
    VS2017 使用Visual Studio For Linux远程连接到T家的云服务器上进行开发。 在某个地方用到了C++11标准之后的std::thread. 编译正常,在运行时遇到了错误: │terminate called after throwing an instance of '...
  • 我和 Linux、嵌入式实时操作系统 RT-Thread —— RT-Thread创始人熊谱翔,2015 年 接触 Linux 说起 Linux 应该从我在校园时期说起。我是在山城——重庆邮电学院念的书,1998 年时宿舍伙伴一起凑的钱买的电脑,...
  • linux下安装boost及boost::thread的使用

    万次阅读 2012-06-20 16:17:12
    下载到 boost_1_49_0.tar.bz2 (当然,其他压缩格式也可以)后,可以把它放在用户目录,即:~/ 解压缩:tar -jxvf boost_1_49_0.tar.bz2 这样,出现文件夹:~/boost_1_49_0 然后进入:$ cd boost_1_49_0...
  • LinuxThread VS NPTL

    千次阅读 2012-07-24 16:18:22
    LinuxThread ,但使用这种方式写出的多线程程序在诸多特性上并不是跟 POSIX 标准兼容的 ( 通过网址: http://www.kernel.org/doc/man-pages/online/pages/man7/pthreads.7.html 可以了解诸多的不兼容特性 ) 。这显然...
  • LinuxThread vs NPTL

    千次阅读 2014-04-27 14:18:38
     在实现LinuxThread之前,系统内核并没有提供任何对线程的支持,实现LinuxThread时也并没有针对其做任何的改动,所以LinuxThread只能使用现有的系统调用来创建一些用户接口来尽量模仿POSIX定义的API的语义,这也...
  • linux源码中thread_info的一点说明

    千次阅读 2016-02-21 03:14:59
    在读linux源码的时候,通常我们都把头文件放到include目录,这样方便引用,那么我们可以在linux/sched.h中引用了linux/thread_info.h这个头文件,但是这个头文件中仍然没有结构体thread_info的定义,但是它引用了...
  • Linux内核可以看作一个服务进程(管理软硬件资源,响应用户进程的种种合理以及不合理的请求)。 内核需要多个执行流并行,为了防止可能的阻塞,支持多线程是必要的。 内核线程就是内核的分身,一个分身可以处理一件...
  • linux下的C语言开发

    万次阅读 多人点赞 2019-03-06 10:32:01
    在很多人的眼里,C语言和linux常常是分不开的。这其中的原因很多,其中最重要的一部分我认为是linux本身就是C语言的杰出作品。当然,linux操作系统本身对C语言的支持也是相当到位的。作为一个真正的程序员来说,如果...
  • Linux下访问数据库

    万次阅读 2019-08-02 22:40:33
    Linux下访问数据库 声明:本文只简单描述Linux系统访问mysql数据库的步骤,关于连接上数据库之后的简单的对于数据库的增删改查等操作只是稍微提及,关于增删改查的语句书写,本文不再讲述。 一般来说,访问...
  • 日期 内核版本 架构 作者 GitHub CSDN 2016-06-03 Linux-4.5 X86 & arm gatieme ...进程在内核态运行时需要自己的堆栈信息, 因此linux内核为每个进程都提供了一个内核栈kernel stack, struct
  • Linux下C语言编程

    千次阅读 2015-12-06 12:56:40
    第1章 Linux下C语言编程简介 本章将简要介绍一下什么是Linux,C语言的特点,程序开发的预备知识,Linux下C语言开发的环境,程序设计的特点和原则以及编码风格等。通过本章的学习,可以对在Linux下使用C语言编程有一...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 259,946
精华内容 103,978
关键字:

linux下thread

linux 订阅