免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1155 | 回复: 0

[进程管理] NPTL内核任务、用户线程和进程 [复制链接]

论坛徽章:
0
发表于 2016-02-05 00:04 |显示全部楼层
[size=12.0000pt]本篇主要描述内核任务、用户线程、进程三者之间的关系。
[size=12.0000pt]这三个术语在我们日常的使用中,经常概念混淆。其主要原因,往往是我们没有将用户线程与内核任务建立起对应的联系。
[size=12.0000pt]我们对这三个名词进行官方一点的描述。
[size=12.0000pt]进程:用于在用户空间指向正在执行的程序示例[size=12.0000pt]。一个进程通常通过一组fork()exec()函数调用来生成新的进程。初始进程调用fork()函数,产生一个子进程。[size=12.0000pt]子进程继承了母进程的整个执行环境[size=12.0000pt]。fork()函数调用将子进程的进程标识(PID)反馈给母进程,同时还包括子进程中的PID。然后,子进程调用使用exec()函数调用来执行其他的命令,改变继承来的执行环境。同时,母进程或者迅速退出,或者等待子进程回到其初始状态。
[size=12.0000pt]内核任务[size=12.0000pt]:用户进程在内核中由任务实现,对应内核数据结构中的struct task_struct;一个用户进程必然会对应一个内核任务。
[size=12.0000pt]而[size=12.0000pt]用户线程[size=12.0000pt]相对比较复杂,它在linux的发展过程中,发生过一系列版本的改变。它的模型分以下三类,[size=12.0000pt]1:1N:1N:M[size=12.0000pt]。但无论是哪种模型,用户线程与生成线程的进程是共享代码的,并且[size=12.0000pt]用户线程和用户进程在内核中,均由task_struct结构体管理[size=12.0000pt]。
[size=12.0000pt]注:目前Linux使用1:1NPTL,并且已经完全集成到Glibc中了。
[size=16.0000pt]1. [size=16.0000pt]三类模型
[size=16.0000pt]1.1. [size=16.0000pt]每种模型的特点
[size=12.0000pt]模型
[size=12.0000pt]说明
[size=12.0000pt]1:1
[size=12.0000pt]①内核级线程(kernel-level threading
[size=12.0000pt]②将用户生成的线程与内核中调度的实体1:1映射
[size=12.0000pt]③简化线程实现
[size=12.0000pt]④在SolarisNetBSDFreeBSD中使用1:1模型
[size=12.0000pt]N:1
[size=12.0000pt]①用户级线程(user-level threading)
[size=12.0000pt]②所有应用程序级线程映射为1个内核级调度实体
[size=12.0000pt]③内核完全不具有应用程序线程信息
[size=12.0000pt]④可以快速执行上下文切换,但无法获得多线程处理器(multi-thread processor)或多处理器计算机(multi-process computer)等硬件上的优点。
[size=12.0000pt]⑤无法同时调度多个线程。
[size=12.0000pt]N:M
[size=12.0000pt]①混合线程(hybrid thread)
[size=12.0000pt]②将N个应用程序线程映射到可执行M个调度的内核级线程
[size=12.0000pt]③线程库(thread library)负责调度用户线程,不执行系统调用,故可快速执行上下文切换
[size=12.0000pt]④难以实现,且优先顺序可能改变
[size=12.0000pt]⑤需要管理用户空间调度和内核空间调度
[size=16.0000pt]1.2. [size=16.0000pt]每种模型对应的实现方式
[size=12.0000pt]1:1模型实现于LinuxThread和之后的NPTLNative POSIX Thread Library,本地POSIX线程库)
[size=12.0000pt]N:1模型[size=12.0000pt]基于GNU便携式线程(Portable Thread)。它是基于POSIX/ANSI-C的用户空间线程库。对各线程的调度均通过GNU Pth库实现[size=12.0000pt],[size=12.0000pt]内核无法认知用户空间线程。该特性使得其无法活用SMP[size=12.0000pt]。
[size=12.0000pt]N:M模型实现于NGPTNext Generation POSIX Threads,下一代POSIX线程)。该项目是由IBM主持,但在2003年停止开发。
[size=12.0000pt]由于,目前Linux使用的是1:1的模型,因而下面将对1:1的两种实现方式展开具体的描述。
[size=16.0000pt]2. [size=16.0000pt]LinuxThreadNPTL
[size=12.0000pt]LinuxThreads[size=12.0000pt]针对单一进程可产生的线程数通过一个编译器设置。此外,它还使用一个进程管理器协调每个进程产生的所有线程间的关系。这样会大大增加线程建立和消除占用的资源。尽管基本上[size=12.0000pt]每个线程都有独立的进程PID[size=12.0000pt],但信号的处理仍然是在各个进程中完成的。由于种种原因,在LinuxThreads实施过程中,同时产生并工作的线程数量常常会受到限制。这些限制包括:
[size=12.0000pt]①由于LinuxThreads使用克隆系统调用(clone system call)生成新线程,每个线程拥有自己的PID,因此会发生信号处理问题。LinuxThread使用SIGUSR1SIGUSR2管理线程,这也意味着程序无法使用这两个信号。
[size=12.0000pt]②由于LinuxThread并未支持真正的线程,而是利用克隆系统调用支持的。因而存在信号处理、调度、线程间同步的问题。
[size=12.0000pt]
[size=12.0000pt]使用NPTL[size=12.0000pt]时,母进程下的[size=12.0000pt]各个线程都共享同一PID[size=12.0000pt],因此getpid()函数将为进程中的所有线程返回同一PID。在NPTL下,每一个线程的线程ID在使用时必须是唯一的,这样才能对每个线程进程正确的识别。另外,NPTL遵循POSIX规定,以进程为单位处理信号。
[size=12.0000pt]举个栗子:发出信号SIGSTOP时,LinuxThreads只有接收该信号的线程停止,但NPTL中所有进程均会停止。
[size=12.0000pt]以下是官方给出的LinuxThreadNPTLNGPL的性能比较数据。
图片1.png

[size=12.0000pt]可以明显地看出NPTL的性能要优于NGPTLinuxThreads
[size=12.0000pt]实际上,NPTL线程的实现基本是基于内核的。
[size=12.0000pt]在linux2.6之后(支持NPTL),[size=12.0000pt]内核有了线程组的概念,[size=12.0000pt] task_struct结构中增加了一个tgid(thread group id)字段[size=12.0000pt]。[size=12.0000pt]如果这个task是一个"主线程"[size=12.0000pt],[size=12.0000pt]则它的tgid等于pid,[size=12.0000pt],[size=12.0000pt]否则tgid等于进程的pid(即主线程的pid)[size=12.0000pt]。
[size=12.0000pt]此外,[size=12.0000pt]每个线程有自己的pid。在clone系统调用中[size=12.0000pt],[size=12.0000pt]传递CLONE_THREAD参数就可以把新进程的tgid设置为父进程的tgid[size=12.0000pt](否则新进程的tgid会设为其自身的pid).类似的XXidtask_struct中还有两个:[size=12.0000pt]task->signal->pgid保存进程组的打头进程的pid[size=12.0000pt]、[size=12.0000pt]task->signal->session保存会话打头进程的pid[size=12.0000pt]。通过这两个id来关联进程组和会话。
[size=12.0000pt]  有了tgid[size=12.0000pt],[size=12.0000pt]内核或相关的shell程序就知道某个tast_struct是代表一个进程还是代表一个线程[size=12.0000pt],[size=12.0000pt]也就知道在什么时候该展现它们[size=12.0000pt],[size=12.0000pt]什么时候不该展现(比如在ps的时候, 线程就不要展现了)[size=12.0000pt],[size=12.0000pt].getpid(获取进程ID)系统调用返回的也是tast_struct中的tgid[size=12.0000pt],[size=12.0000pt]而tast_struct中的pid则由gettid系统调用来返回.在执行ps命令的时候不展现子线程,也是有一些问题的。比如程序a.out运行时,创建了一个线程。假设主线程的pid10001、子线程是10002(它们的tgid都是10001)。这时如果你kill 10002,是可以把1000110002这两个线程一起杀死的,尽管执行ps命令的时候根本看不到10002这个进程。如果你不知道linux线程背后的故事,肯定会觉得遇到灵异事件了。
[size=12.0000pt]  [size=12.0000pt]为了应付"发送给进程的信号""发送给线程的信号"[size=12.0000pt],[size=12.0000pt] task_struct里面维护了两套signal_pending[size=12.0000pt],[size=12.0000pt]一套是线程组共享的[size=12.0000pt],[size=12.0000pt]一套是线程独有的[size=12.0000pt]。[size=12.0000pt]通过kill发送的信号被放在线程组共享的signal_pending, 可以由任意一个线程来处理[size=12.0000pt];[size=12.0000pt]通过pthread_kill发送的信号(pthread_killpthread库的接口, 对应的系统调用中tkill)被放在线程独有的signal_pending, 只能由本线程来处理.
[size=12.0000pt]  当线程停止/继续, 或者是收到一个致命信号时, 内核会将处理动作施加到整个线程组中[size=12.0000pt]。
[size=16.0000pt]3. [size=16.0000pt]NPTL特点
[size=16.0000pt]3.1. [size=16.0000pt]NAPT的使用
[size=12.0000pt]使用getconf GNU_LIBPTHREAD_VERSION可以得到系统gcc在编译时支持的多线程方式。
[size=9.0000pt]1[size=9.0000pt]
[size=9.0000pt]2[size=9.0000pt]
[size=9.0000pt]3
[size=9.0000pt]
root@skynet:/home/wangda# getconf GNU_LIBPTHREAD_VERSION
NPTL [size=9.0000pt]2.[size=9.0000pt]21
[size=12.0000pt]这里表示支持NPTL 2.21版本。当然,如果返回的是linuxthreads,说明gcc不支持NPTL,而是linuxThreads
[size=12.0000pt]在嵌入式系统中,一般不会安装工具链,也可以直接运行libc.so.6查看系统的glibc是否使用的是NTPL thread,如下面红色部分所示:
[size=9.0000pt]1[size=9.0000pt]
[size=9.0000pt]2[size=9.0000pt]
[size=9.0000pt]3[size=9.0000pt]
[size=9.0000pt]4[size=9.0000pt]
[size=9.0000pt]5[size=9.0000pt]
[size=9.0000pt]6[size=9.0000pt]
[size=9.0000pt]7[size=9.0000pt]
[size=9.0000pt]8[size=9.0000pt]
[size=9.0000pt]9[size=9.0000pt]
[size=9.0000pt]10[size=9.0000pt]
[size=9.0000pt]11[size=9.0000pt]
[size=9.0000pt]12[size=9.0000pt]
[size=9.0000pt]13[size=9.0000pt]
[size=9.0000pt]14[size=9.0000pt]
[size=9.0000pt]15[size=9.0000pt]
[size=9.0000pt]16
[size=9.0000pt]
root@skynet:/# /lib/x86_64-linux-gnu/libc.so.[size=9.0000pt]6
GNU C Library (Ubuntu GLIBC [size=9.0000pt]2.[size=9.0000pt]21-0ubuntu4) stable release version [size=9.0000pt]2.[size=9.0000pt]21, by Roland McGrath et al.
Copyright (C) [size=9.0000pt]2015 Free Software Foundation, Inc.
This is free software; see the source [size=9.0000pt]for copying conditions.
There is NO warranty; [size=9.0000pt]not even [size=9.0000pt]for MERCHANTABILITY [size=9.0000pt]or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version [size=9.0000pt]4.[size=9.0000pt]9.[size=9.0000pt]2.
Available extensions:
    crypt add-on version [size=9.0000pt]2.[size=9.0000pt]1 by Michael Glad [size=9.0000pt]and others
    GNU Libidn by Simon Josefsson
    [size=9.0000pt]Native POSIX Threads Library by Ulrich Drepper et al
    BIND-[size=9.0000pt]8.[size=9.0000pt]2.[size=9.0000pt]3-T5B
libc ABIs: UNIQUE IFUNC
For bug reporting instructions, please see:
<https:[size=9.0000pt]//bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
[size=16.0000pt]3.2. [size=16.0000pt]NAPT下线程呈现
[size=12.0000pt]NAPT下每个线程即对应内核一个stask_struct结构体,但pid与主进程相同。
[size=12.0000pt]这一点可以在系统上得到验证:
[size=9.0000pt]1[size=9.0000pt]
[size=9.0000pt]2[size=9.0000pt]
[size=9.0000pt]3
[size=9.0000pt]
root@skynet:/home/wangda# ps -aux |grep [size=9.0000pt]1888
wangda    [size=9.0000pt]1888  [size=9.0000pt]0.[size=9.0000pt]0  [size=9.0000pt]1.[size=9.0000pt]6 [size=9.0000pt]514284 [size=9.0000pt]16936 ?        Sl   [size=9.0000pt]11:[size=9.0000pt]48   [size=9.0000pt]0:[size=9.0000pt]00 update-notifier
[size=12.0000pt]我们看到,该进程的pid1888
[size=12.0000pt]在/proc/<pid>/task下可以看到归属于该进程的线程号。
[size=9.0000pt]1[size=9.0000pt]
[size=9.0000pt]2[size=9.0000pt]
[size=9.0000pt]3[size=9.0000pt]
[size=9.0000pt]4[size=9.0000pt]
[size=9.0000pt]5[size=9.0000pt]
[size=9.0000pt]6[size=9.0000pt]
[size=9.0000pt]7
[size=9.0000pt]
root@skynet:/home/wangda# ls -l /proc/[size=9.0000pt]1888/task/
total [size=9.0000pt]0
dr-xr-xr-x [size=9.0000pt]7 wangda wangda [size=9.0000pt]0 12月 [size=9.0000pt]20 [size=9.0000pt]11:[size=9.0000pt]49 [size=9.0000pt]1888
dr-xr-xr-x [size=9.0000pt]7 wangda wangda [size=9.0000pt]0 12月 [size=9.0000pt]20 [size=9.0000pt]11:[size=9.0000pt]49 [size=9.0000pt]1891
dr-xr-xr-x [size=9.0000pt]7 wangda wangda [size=9.0000pt]0 12月 [size=9.0000pt]20 [size=9.0000pt]11:[size=9.0000pt]49 [size=9.0000pt]1892
dr-xr-xr-x [size=9.0000pt]7 wangda wangda [size=9.0000pt]0 12月 [size=9.0000pt]20 [size=9.0000pt]11:[size=9.0000pt]49 [size=9.0000pt]1893
[size=12.0000pt]以上在pid1888的进程中,存在四个线程,分别是1888189118921893
[size=12.0000pt]
[size=12.0000pt]遗留问题:
[size=12.0000pt]SMP的负载均衡是按照进程计数的,由于NPTL1:1模型,每个线程对应一个struct task_struct,所以可以实现每个线程在不同的CPU之间进行调度。但问题是,内核或者NPTL是如何实现获取PID时,每个子线程获取到的是主进程的PID,并且实现资源的共享?


您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP