`
kongweile
  • 浏览: 517330 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

linux线程浅析

 
阅读更多

http://hi.baidu.com/_kouu/item/282b80a933ccc3a829ce9dd9

关于linux线程

在许多经典的操作系统教科书中, 总是把进程定义为程序的执行实例, 它并不执行什么, 只是维护应用程序所需的各种资源. 而线程则是真正的执行实体. 
为了让进程完成一定的工作, 进程必须至少包含一个线程. 如图1.

进程所维护的是程序所包含的资源(静态资源), 如: 地址空间, 打开的文件句柄集, 文件系统状态, 信号处理handler, 等;
线程所维护的运行相关的资源(动态资源), 如: 运行栈, 调度相关的控制信息, 待处理的信号集, 等;



然而, 一直以来, linux内核并没有线程的概念. 每一个执行实体都是一个task_struct结构, 通常称之为进程. 如图2.

进程是一个执行单元, 维护着执行相关的动态资源. 同时, 它又引用着程序所需的静态资源.
通过系统调用clone创建子进程时, 可以有选择性地让子进程共享父进程所引用的资源. 这样的子进程通常称为轻量级进程. 

linux上的线程就是基于轻量级进程, 由用户态的pthread库实现的.
使用pthread以后, 在用户看来, 每一个task_struct就对应一个线程, 而一组线程以及它们所共同引用的一组资源就是一个进程.

但是, 一组线程并不仅仅是引用同一组资源就够了, 它们还必须被视为一个整体.
对此, POSIX标准提出了如下要求:
1, 查看进程列表的时候, 相关的一组task_struct应当被展现为列表中的一个节点;
2, 发送给这个"进程"的信号(对应kill系统调用), 将被对应的这一组task_struct所共享, 并且被其中的任意一个"线程"处理;
3, 发送给某个"线程"的信号(对应pthread_kill), 将只被对应的一个task_struct接收, 并且由它自己来处理;
4, 当"进程"被停止或继续时(对应SIGSTOP/SIGCONT信号), 对应的这一组task_struct状态将改变;
5, 当"进程"收到一个致命信号(比如由于段错误收到SIGSEGV信号), 对应的这一组task_struct将全部退出;
6, 等等(以上可能不够全);

linuxthreads

在linux 2.6以前, pthread线程库对应的实现是一个名叫linuxthreads的lib. 
linuxthreads利用前面提到的轻量级进程来实现线程, 但是对于POSIX提出的那些要求, linuxthreads除了第5点以外, 都没有实现(实际上是无能为力):
1, 如果运行了A程序, A程序创建了10个线程, 那么在shell下执行ps命令时将看到11个A进程, 而不是1个(注意, 也不是10个, 下面会解释);
2, 不管是kill还是pthread_kill, 信号只能被一个对应的线程所接收;
3, SIGSTOP/SIGCONT信号只对一个线程起作用;

还好linuxthreads实现了第5点, 我认为这一点是最重要的. 如果某个线程"挂"了, 整个进程还在若无其事地运行着, 可能会出现很多的不一致状态. 进程将不是一个整体, 而线程也不能称为线程. 
或许这也是为什么linuxthreads虽然与POSIX的要求差距甚远, 却能够存在, 并且还被使用了好几年的原因吧~
但是, linuxthreads为了实现这个"第5点", 还是付出了很多代价, 并且创造了linuxthreads本身的一大性能瓶颈.

接下来要说说, 为什么A程序创建了10个线程, 但是ps时却会出现11个A进程了. 因为linuxthreads自动创建了一个管理线程. 上面提到的"第5点"就是靠管理线程来实现的.
当程序开始运行时, 并没有管理线程存在(因为尽管程序已经链接了pthread库, 但是未必会使用多线程). 
程序第一次调用pthread_create时, linuxthreads发现管理线程不存在, 于是创建这个管理线程. 这个管理线程是进程中的第一个线程(主线程)的儿子.
然后在pthread_create中, 会通过pipe向管理线程发送一个命令, 告诉它创建线程. 即是说, 除主线程外, 所有的线程都是由管理线程来创建的, 管理线程是它们的父亲.
于是, 当任何一个子线程退出时, 管理线程将收到SIGUSER1信号(这是在通过clone创建子线程时指定的). 管理线程在对应的sig_handler中会判断子线程是否正常退出, 如果不是, 则杀死所有线程, 然后自杀.
那么, 主线程怎么办呢? 主线程是管理线程的父亲, 其退出时并不会给管理线程发信号. 于是, 在管理线程的主循环中通过getppid检查父进程的ID号, 如果ID号是1, 说明父亲已经退出, 并把自己托管给了init进程(1号进程). 这时候, 管理线程也会杀掉所有子线程, 然后自杀. 那么, 如果主线程是调用pthread_exit主动退出的呢? 按照posix的标准,这种情况下其他子线程是应该继续运行的. 于是, 在linuxthreads中, 主线程调用pthread_exit以后并不会真正退出, 而是会在pthread_exit函数中阻塞等待所有子线程都退出了, pthread_exit才会让主线程退出. (在这个等等过程中, 主线程一直处于睡眠状态.)

可见, 线程的创建与销毁都是通过管理线程来完成的, 于是管理线程就成了linuxthreads的一个性能瓶颈. 
创建与销毁需要一次进程间通信, 一次上下文切换之后才能被管理线程执行, 并且多个请求会被管理线程串行地执行.


NPTL

到了linux 2.6, glibc中有了一种新的pthread线程库--NPTL(Native POSIX Threading Library). 
NPTL实现了前面提到的POSIX的全部5点要求. 但是, 实际上, 与其说是NPTL实现了, 不如说是linux内核实现了.

在linux 2.6中, 内核有了线程组的概念, task_struct结构中增加了一个tgid(thread group id)字段. 
如果这个task是一个"主线程", 则它的tgid等于pid, 否则tgid等于进程的pid(即主线程的pid).
在clone系统调用中, 传递CLONE_THREAD参数就可以把新进程的tgid设置为父进程的tgid(否则新进程的tgid会设为其自身的pid).
类似的XXid在task_struct中还有两个:task->signal->pgid保存进程组的打头进程的pid、task->signal->session保存会话打头进程的pid。通过这两个id来关联进程组和会话。

有了tgid, 内核或相关的shell程序就知道某个tast_struct是代表一个进程还是代表一个线程, 也就知道在什么时候该展现它们, 什么时候不该展现(比如在ps的时候, 线程就不要展现了).
而getpid(获取进程ID)系统调用返回的也是tast_struct中的tgid, 而tast_struct中的pid则由gettid系统调用来返回.
在执行ps命令的时候不展现子线程,也是有一些问题的。比如程序a.out运行时,创建了一个线程。假设主线程的pid是10001、子线程是10002(它们的tgid都是10001)。这时如果你kill 10002,是可以把10001和10002这两个线程一起杀死的,尽管执行ps命令的时候根本看不到10002这个进程。如果你不知道linux线程背后的故事,肯定会觉得遇到灵异事件了。

为了应付"发送给进程的信号"和"发送给线程的信号", task_struct里面维护了两套signal_pending, 一套是线程组共享的, 一套是线程独有的.
通过kill发送的信号被放在线程组共享的signal_pending中, 可以由任意一个线程来处理; 通过pthread_kill发送的信号(pthread_kill是pthread库的接口, 对应的系统调用中tkill)被放在线程独有的signal_pending中, 只能由本线程来处理.

当线程停止/继续, 或者是收到一个致命信号时, 内核会将处理动作施加到整个线程组中.


NGPT

说到这里, 也顺便提一下NGPT(Next Generation POSIX Threads). 
上面提到的两种线程库使用的都是内核级线程(每个线程都对应内核中的一个调度实体), 这种模型称为1:1模型(1个线程对应1个内核级线程);
而NGPT则打算实现M:N模型(M个线程对应N个内核级线程), 也就是说若干个线程可能是在同一个执行实体上实现的. 
线程库需要在一个内核提供的执行实体上抽象出若干个执行实体, 并实现它们之间的调度. 这样被抽象出来的执行实体称为用户级线程.
大体上, 这可以通过为每个用户级线程分配一个栈, 然后通过longjmp的方式进行上下文切换. (百度一下"setjmp/longjmp", 你就知道.)
但是实际上要处理的细节问题非常之多. 
目前的NGPT好像并没有实现所有预期的功能, 并且暂时也不准备去实现.

用户级线程的切换显然要比内核级线程的切换快一些, 前者可能只是一个简单的长跳转, 而后者则需要保存/装载寄存器, 进入然后退出内核态. (进程切换则还需要切换地址空间等.)
而用户级线程则不能享受多处理器, 因为多个用户级线程对应到一个内核级线程上, 一个内核级线程在同一时刻只能运行在一个处理器上.
不过, M:N的线程模型毕竟提供了这样一种手段, 可以让不需要并行执行的线程运行在一个内核级线程对应的若干个用户级线程上, 可以节省它们的切换开销.

据说一些类UNIX系统(如Solaris)已经实现了比较成熟的M:N线程模型, 其性能比起linux的线程还是有着一定的优势.

分享到:
评论

相关推荐

    Linux 线程浅析

    ### Linux线程浅析 #### 一、线程的基本概念 在典型的UNIX进程中,我们可以将一个进程视为只有一个控制线程的实体,也就是说,在同一时刻进程只能处理一件事情。然而,随着多线程技术的发展,现代的进程可以包含多...

    浅析Linux下一个简单的多线程互斥锁的例子

    在Linux系统中,多线程编程是实现并发执行任务的重要方式。为了确保多个线程在共享资源时的正确性,我们需要引入同步机制,其中互斥锁(Mutex)是一种常用的方法。本文将通过一个简单的例子来深入浅出地解析Linux...

    浅析Linux技术在高职教育中的价值.pdf

    此外,Linux的多用户、多线程、多进程能力以及实时性良好,使其在服务器、嵌入式系统等领域的应用广泛,为学生未来就业提供了更多的可能性。 在高职教育中引入Linux技术,有助于培养学生的创新精神和实践能力。通过...

    Linux下的网络通讯程序设计浅析.pdf

    此外,文章可能还涵盖了网络编程中的错误处理、并发连接管理、多线程或异步IO策略,以及性能优化等方面的知识。这些内容对于系统开发人员来说具有很高的参考价值,能帮助他们更好地理解和利用Linux的网络特性,开发...

    浅析linux环境下一个进程最多能有多少个线程

    因此,在设计多线程程序时,应该根据具体需求和系统资源来确定合理的线程数量,避免无谓的资源浪费和性能瓶颈。合理的线程池管理、负载均衡策略以及适当的同步机制都是优化多线程应用的关键。 总的来说,Linux环境...

    Linux环境下基于TCP的Socket编程浅析.pdf

    同时,为了提高效率,可以使用多线程或异步I/O来处理并发连接。 总之,Linux环境下的TCP Socket编程是一项基础而关键的技术,它涉及到网络编程的许多核心概念和实践。理解并掌握Socket编程,能够帮助开发者构建稳定...

    基于Linux的Android OS平台应用浅析.pdf

    【Android OS平台应用浅析】 Android是由Google开发的基于Linux平台的开源移动操作系统,它以其开放性和灵活性在智能手机和平板电脑市场占据主导地位。Android操作系统采用分层架构,包括应用程序层、应用程序框架...

    浅析如何在c语言中调用Linux脚本

    一、引言对于没有接触过Unix/Linux操作系统的人来说,fork是最难理解的概念之一:它执行一次却返回两个...多线程程序设计的概念早在六十年代就被提出,但直到八十年代中期,Unix系统中才引入多线程机制,如今,由于自身

    嵌入式操作系统浅析.docx

    嵌入式操作系统浅析 嵌入式操作系统是 ARMCPU 的软件基础,由 8 位/16 位单片机发展到以 ARMCPU 核为代表的 32 位嵌入式处理器,嵌入式操作系统将替代传统的由手工编制的监控程序或调度程序,成为重要的基础组件。...

    Qtopia部分内部机制浅析.rar

    4. **多线程支持**:为了充分利用多核处理器的优势,Qtopia支持多线程编程,允许开发者将耗时任务放在后台线程执行,避免了主线程阻塞导致的界面卡顿。 5. **图形渲染**:Qtopia的图形渲染引擎使用硬件加速,提供...

    oSIP协议桟浅析

    8. **oSIP库特性**:oSIP库提供了线程安全的API,方便开发者在多线程环境中使用。它还支持DTLS和SIPS(加密的SIP)以增强安全性。oSIP还提供了丰富的回调机制,让开发者能够自定义处理SIP消息的逻辑。 9. **错误...

    浅析Java Web开发环境的搭建.pdf

    Java语言与平台无关、面向对象、安全性好、多线程等优异的特性非常适合进行Web开发。目前有很多优秀的开源软件都支持Java Web开发,如Struts2、Spring、Hibernate等。 Web的出现使得更多的人开始了解计算机网络,...

    网游服务器设计浅析字数字数

    此外,网络游戏的交互性强,多线程同步会带来额外的复杂性。因此,使用非阻塞I/O可以更高效地处理网络通信。每个连接的数据结构通常包括用户信息、文件描述符、地址信息、读写缓冲、时间戳以及状态标志等,以便跟踪...

    浅析Linux下精确控制时间的函数

    为了解决这些问题,可以使用`rdtscp`(带有前缀的`rdtsc`,保证在多线程环境下的精确性)或者`rdtscpll`(在某些Intel处理器中提供,考虑了PLL锁相环的时钟频率变化)。 在提供的代码示例中,`GetCurCpuHopCount`...

    LINUX系统开发技术详解---基于ARM

    2.1.3 Linux 与ARM处理器........................................................................................ 12 2.2 ARM指令集...........................................................................

    内存分配器dlmalloc_2.8.3源码浅析

    ### 内存分配器dlmalloc_2.8.3源码浅析 #### 1. 文档介绍 dlmalloc是一款由Doug Lea自1987年开始开发的高性能内存分配器,历经多年优化,其最新版本2.8.3广受好评。这款内存分配器以其高效性在Linux系统及多种应用...

    浅析PHP7的多进程及实例

    PHP的多线程也曾被人提及,但进程内多线程资源共享和分配的问题难以解决。PHP也有多线程想关的扩展 pthreads ,但据说不太稳定,且要求环境为线程安全,所用不多。 以前PHP群里的一位大神曾指导说后台PHP想进阶必然...

    嵌入式系统学习基础知识浅析

    学习多线程编程方法,解决同步问题,编写多线程程序。 9. **网络编程**:嵌入式Linux中的TCP/IP网络结构和socket编程是网络通信的基础。掌握常用API,如ping命令的实现、UDP套接字编程,以及PPP、GPRS协议。 10. *...

Global site tag (gtag.js) - Google Analytics