进程管理知识整理

来源:互联网 发布:网络语石锤是什么意思 编辑:程序博客网 时间:2024/06/08 12:33

linux内核——进程,轻量级进程,线程,线程组

本篇文章将介绍本人对以下问题的理解。

用户空间一般为0~3GB(即PAGE_OFFSET,在X86系统中等于0xC0000000,而剩余的3GB~4GB为内核空间。每个进程的用户空间都是完全独立的、互不相干的,用户进程各自有不同的页表。内核进程是由内核负责映射的,它并不会跟着用户进程改变,是固定的,共享的。内核空间的虚拟地址独立其他程序的空间。

普通进程可以使用0~4G的用户空间和内核空间,但是使用内核空间时需要系统调用。

用户进程----》线程----》轻量级进程----》内核线程

1、进程、轻量级进程、线程、线程组之间的关系

2、及它们的标识相关说明

一、进程、轻量级进程、线程、线程组之间的关系

借助上图说明:

进程P0有四条执行流,即线程,

主线程t0是它的第一个线程,且与进程P0相关联,

之后衍生出t1t2t3三个线程,这三个线程与轻量级进程P1P2P3一一关联,

所有的进程、轻量级进程、线程组成了线程组。

轻量级进程也是进程,只不过它与某进程的某特定线程相关联。

二、它们的标识相关说明

pid是进程标识符,tgid是线程组标识符

每个进程都有自己的pid,如图中:进程pidP0= a,轻量级进程pidP1= b / pidP2= c / pidP3= d

同属于一个线程组的所有进程、轻量级进程有同样的线程组标识符,且其为第一个线程所关联的进程标识符,

例如:图中第一个线程为t0,它所关联的进程为P0pidP0= a,所以tgidP0= a / tgidP1= a / tgidP2= a / tgidP3= a

当我们使用函数getpidcurrent_p)时,返回值不是current_ppid,而是它的tgid。(current_p为当前进程)。

线程与进程概念

 

在现代操作系统中,进程支持多线程。

*进程是资源管理的最小单元;

*线程是程序执行的最小单元。

即线程作为调度和分配的基本单位,进程作为资源分配的基本单位

一个进程的组成实体可以分为两大部分:线程集和资源集。进程中的线程是动态的对象;代表了进程指令的执行。资源,包括地址空间、打开的文件、用户信息等等,由进程内的线程共享。

线程概念的产生

 

传统单线程进程的缺点

 

1.现实中有很多需要并发处理的任务,如数据库的服务器端、网络服务器、大容量计算等。

2.传统的UNIX进程是单线程的,单线程意味着程序必须是顺序执行,不能并发;既在一个时刻只能运行在一个处理器上,因此不能充分利用多处理器框架的计算机。

3.如果采用多进程的方法,则有如下问题:

* fork一个子进程的消耗是很大的,fork是一个昂贵的系统调用,即使使用现代的写时复制(copy-on-write)技术

*各个进程拥有自己独立的地址空间,进程间的协作需要复杂的IPC技术,如消息传递和共享内存等。

多线程的优缺点

 

多线程的优点和缺点实际上是对立统一的。

支持多线程的程序(进程)可以取得真正的并行(parallelism),且由于共享进程的代码和全局数据,故线程间的通信是方便的。它的缺点也是由于线程共享进程的地址空间,因此可能会导致竞争,因此对某一块有多个线程要访问的数据需要一些同步技术。

线程的设计过程演变

 

在操作系统设计上,从进程演化出线程,最主要的目的就是更好的支持SMP以及减小(进程/线程)上下文切换开销。

SMP机器上多线程的并行性

 

无论按照怎样的分法,一个进程至少需要一个线程作为它的指令执行体,进程管理着资源(比如cpu、内存、文件等等),而将线程分配到某个cpu上执 行。

一个进程当然可以拥有多个线程,此时,如果进程运行在SMP机器上,它就可以同时使用多个cpu来执行各个线程,达到最大程度的并行,以提高效率;同 时,即使是在单cpu的机器上,采用多线程模型来设计程序,正如当年采用多进程模型代替单进程模型一样,使设计更简洁、功能更完备,程序的执行效率也更 高,例如采用多个线程响应多个输入,而此时多线程模型所实现的功能实际上也可以用多进程模型来实现,而与后者相比,线程的上下文切换开销就比进程要小多 了,从语义上来说,同时响应多个输入这样的功能,实际上就是共享了除cpu以外的所有资源的。

线程模型–核心级线程和用户级线程

 

针对线程模型的两大意义,分别开发出了核心级线程用户级线程两种线程模型,分类的标准主要是线程的调度者在核内还是在核外。前者更利于并发使用多处理器的资源,而后者则更多考虑的是上下文切换开销。

关于线程的实现模型,可以参见博主的另外一篇博客线程的3种实现方式–内核级线程, 用户级线程和混合型线程

目前的实现策略

 

在目前的商用系统中,通常都将两者结合起来使用,既提供核心线程以满足smp系统的需要,也支持用线 程库的方式在用户态实现另一套线程机制,此时一个核心线程同时成为多个用户态线程的调度者。

正如很多技术一样,”混合”通常都能带来更高的效率,但同时也 带来更大的实现难度,出于”简单”的设计思路,Linux从一开始就没有实现混合模型的计划,但它在实现上采用了另一种思路的”混合”。

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

当核内既支持进程也支持线程时,就可以实现线程-进程的”多对多”模型,即一个进程的某个线程由核内调度,而同时它也可以作为用户级线程池的调度 者,选择合适的用户级线程在其空间中运行。这就是前面提到的”混合”线程模型,既可满足多处理机系统的需要,也可以最大限度的减小调度开销。

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

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

三种线程概念——内核线程、轻量级进程、用户线程

 

内核线程

 

内核线程就是内核的分身,一个分身可以处理一件特定事情。这在处理异步事件如异步IO时特别有用。内核线程的使用是廉价的,唯一使用的资源就是内核栈和上下文切换时保存寄存器的空间。支持多线程的内核叫做多线程内核(Multi-Threads kernel )。

内核线程只运行在内核态,不受用户态上下文的拖累。

*处理器竞争:可以在全系统范围内竞争处理器资源;

*使用资源:唯一使用的资源是内核栈和上下文切换时保持寄存器的空间

*调度:调度的开销可能和进程自身差不多昂贵

*同步效率:资源的同步和数据共享比整个进程的数据同步和共享要低一些。

轻量级进程

 

轻量级进程(LWP)是建立在内核之上并由内核支持的用户线程,它是内核线程的高度抽象,每一个轻量级进程都与一个特定的内核线程关联。内核线程只能由内核管理并像普通进程一样被调度。

a LWP runs in user space on top of a single kernel thread and shares its address space and system resources with other LWPs within the same process

轻量级进程由clone()系统调用创建,参数是CLONE_VM,即与父进程是共享进程地址空间和系统资源。

与普通进程区别:LWP只有一个最小的执行上下文和调度程序所需的统计信息。

*处理器竞争:因与特定内核线程关联,因此可以在全系统范围内竞争处理器资源

*使用资源:与父进程共享进程地址空间

*调度:像普通进程一样调度

轻量级线程(LWP)是一种由内核支持的用户线程。它是基于内核线程的高级抽象,因此只有先支持内核线程,才能有LWP。每一个进程有一个或多个LWPs,每个LWP由一个内核线程支持。这种模型实际上就是恐龙书上所提到的一对一线程模型。在这种实现的操作系统中,LWP就是用户线程。

由于每个LWP都与一个特定的内核线程关联,因此每个LWP都是一个独立的线程调度单元。即使有一个LWP在系统调用中阻塞,也不会影响整个进程的执行。

轻量级进程具有局限性。

*首先,大多数LWP的操作,如建立、析构以及同步,都需要进行系统调用。系统调用的代价相对较高:需要在user mode和kernel mode中切换。

*其次,每个LWP都需要有一个内核线程支持,因此LWP要消耗内核资源(内核线程的栈空间)。因此一个系统不能支持大量的LWP。

注:

1. LWP的术语是借自于SVR4/MP和Solaris 2.x。

2. 有些系统将LWP称为虚拟处理器。

3. 将之称为轻量级进程的原因可能是:在内核线程的支持下,LWP是独立的调度单元,就像普通的进程一样。所以LWP的最大特点还是每个LWP都有一个内核线程支持。

用户线程

 

用户线程是完全建立在用户空间的线程库,用户线程的创建、调度、同步和销毁全库函数在用户空间完成,不需要内核的帮助。因此这种线程是极其低消耗和高效的。

*处理器竞争:单纯的用户线程是建立在用户空间,其对内核是透明的,因此其所属进程单独参与处理器的竞争,而进程的所有线程参与竞争该进程的资源。

*使用资源:与所属进程共享进程地址空间和系统资源。

*调度:由在用户空间实现的线程库,在所属进程内进行调度

LWP虽然本质上属于用户线程,但LWP线程库是建立在内核之上的,LWP的许多操作都要进行系统调用,因此效率不高。而这里的用户线程指的是完全建立在用户空间的线程库,用户线程的建立,同步,销毁,调度完全在用户空间完成,不需要内核的帮助。因此这种线程的操作是极其快速的且低消耗的。

上图是最初的一个用户线程模型,从中可以看出,进程中包含线程,用户线程在用户空间中实现,内核并没有直接对用户线程进程调度,内核的调度对象和传统进程一样,还是进程本身,内核并不知道用户线程的存在。

用户线程之间的调度由在用户空间实现的线程库实现。

这种模型对应着恐龙书中提到的多对一线程模型,其缺点是一个用户线程如果阻塞在系统调用中,则整个进程都将会阻塞。

加强版的用户线程——用户线程+LWP

 

这种模型对应着恐龙书中多对多模型。

用户线程库还是完全建立在用户空间中,因此用户线程的操作还是很廉价,因此可以建立任意多需要的用户线程。

操作系统提供了LWP作为用户线程和内核线程之间的桥梁。LWP还是和前面提到的一样,具有内核线程支持,是内核的调度单元,并且用户线程的系统调用要通过LWP,因此进程中某个用户线程的阻塞不会影响整个进程的执行。

用户线程库将建立的用户线程关联到LWP上,LWP与用户线程的数量不一定一致。当内核调度到某个LWP上时,此时与该LWP关联的用户线程就被执行。

Linux使用的线程库

 

LinuxThreads是用户空间的线程库,所采用的是线程-进程1对1模型(即一个用户线程对应一个轻量级进程,而一个轻量级进程对应一个特定的内核线程),将线程的调度等同于进程的调度,调度交由内核完成,而线程的创建、同步、销毁由核外线程库完成(LinuxThtreads已绑定到 GLIBC中发行)。

LinuxThreads中,由专门的一个管理线程处理所有的线程管理工作。当进程第一次调用pthread_create()创建线程时就会先 创建(clone())并启动管理线程。后续进程pthread_create()创建线程时,都是管理线程作为pthread_create()的调用者的子线程,通过调用clone()来创建用户线程,并记录轻量级进程号和线程id的映射关系,因此,用户线程其实是管理线程的子线程。

LinuxThreads只支持调度范围为PTHREAD_SCOPE_SYSTEM的调度,默认的调度策略是SCHED_OTHER。 
用户线程调度策略也可修改成SCHED_FIFO或SCHED_RR方式,这两种方式支持优先级为0-99,而SCHED_OTHER只支持0。

*SCHED_OTHER 分时调度策略,

*SCHED_FIFO 实时调度策略,先到先服务

*SCHED_RR 实时调度策略,时间片轮转

SCHED_OTHER是普通进程的,后两个是实时进程的(一般的进程都是普通进程,系统中出现实时进程的机会很少)。SCHED_FIFO、 SCHED_RR优先级高于所有SCHED_OTHER的进程,所以只要他们能够运行,在他们运行完之前,所有SCHED_OTHER的进程的都没有得到 执行的机会

小结:

很多文献中都认为轻量级进程就是线程,实际上这种说法并不完全正确,从前面的分析中可以看到,只有在用户线程完全由轻量级进程构成时,才可以说轻量级进程就是线程。

 

来源---微信公众号

Linux中的进程管理-(一)

原创 2017-08-04 陈莉君 Linux内核之旅

今天开始,我们公众号为大家推出进程管理这一节的PPT内容。欢迎大家来欣赏哦!!!下来,先介绍一下第一堂课的主要内容吧!本节课,主要告诉大家内核中的进程和线程是什么?在内核中它们分别具有什么样的作用?以及进程所具有的属性。

最经典的例子就是,操作系统是一个大型工厂,现在为了满足用户的需求,我们要生产出来几样东西,而用户等待的时间也不能太长,怎么办?那就同时制造这几样东西,而这几条生产线就可比作为进程,想像一下生产线,我们就知道了进程需要地址空间,需要编号,要知道自己的继承者是谁?自己的上线又是谁?等等一系列的属性。

  那么线程呢?假设一条生产线都是生产螺丝,螺丝也有型号的区分,所以需要分别来生产,拿这些小的生产线就可以称之为线程了,它们的属性,大家可以猜猜看。好了,开始上课吧。

 

 

 

 

 

 

 

 

 

 

 

 

 

虚拟内存信息:描述每个进程拥有的地址空间。

处理器环境信息:描述进程的执行环境(处理器的寄存器及堆栈等)

 

Linux中的进程管理-(二)

原创 2017-08-06 陈莉君 Linux内核之旅

       前天已经对内核中的进程,以及进程结构体 task_struct 中的成员变量也有了了解。

       今天要讲的内容是进程的状态,以及进程之间的层次结构。在了解这一块的内容之前,

       所以,根据前天的知识,来看一下结构体在内核中是按照什么样的层次来组织的?然后运行的过程中状态又有哪些?

     

    先来看一下进程状态,它是怎么确定的呢?查看一下 task_struct 结构体,会发现里面有状态成员变量(status),进程就是根据该变量来告诉内核自己当前处于什么样的状态,你应该什么时候正确使用我。         

       进程的状态大致分为三类:运行态,阻塞态,就绪态。而后来,随着操作系统的丰富,以及进程的并发需求,而成了上图所示的状态切换。要仔细分辨,确保以后不会模糊。                                                                

 

上图是对内核中的进程层次的一个大致分析,它不仅仅是层次的框架,同时也是操作系统中所有进程衍生的一个大致过程。可以看出操作系统中最开始的进程为 init 进程,大家可以去分析该进程,记得学点汇编语言哦。                                                                          

 

      这是一段程序的显示结果,pid 代表着当前进程的编号,ppid 代表当前进程的父进程的编号。从上图中的编号中,可以找到父进程与子进程之间的对应关系,大家可以倒推,写出源代码,相信对很多人来是小菜一碟。                                                                                

       这是对上面程序产生的进程的一个层次结构的展示,层次结构是一个很重的理解点,只有清晰地梳理透彻进程层次结构,才能明确进程的状态转化,以及进程结构体中成员变量变换的含义。                                       

 

可以看到,PID 是一个很重要的标识符,下面通过两张 ppt 了解一下。

 

 

 

       现在,知道了内核中的进程是对执行程序的一种表示,在内核中由结构体 task_struct 来表示,根据该结构体中的标识符(PID)来表示的名字,状态变量(status)来表示进程的状态切换结果。

 

Linux中的进程管理-(三)

原创 2017-08-08 陈莉君 Linux内核之旅

第一堂课我为大家讲解了进程的基本概念,包括描述进程的数据结构——进程描述符task_struct。上一课大致分析了进程的状态及其转化。这一次我将为大家讲解task_struct的多样性与统一性。通过之前的讲解,我们应该已经对task_struct有所了解了。那么,task_struct的多样性与统一性指的是什么呢?下面就由我来为大家抛砖引玉。。。

 

要想了解task_struct的多样性与统一性,我们首先得了解task_struct在操作系统中扮演着何种角色。

 

既然linux内核使用task_struct统一来描述进程、线程和内核线程。那么,内核在统一处理这三者之余如何不失去他们本身具有的特性呢?看完本篇文章,你一定就会明白的。

 

 

喔。。。原来不同创建函数,最后到内核中都是由do_fork()这一个内核函数来进行处理的呀。太精妙了。不过,好像还是没明白为什么用同一个内核函数可以创建出进程、线程、内核线程如此不同的东西呢?

 

原来重点在于传入内核函数中的参数不同呀!难怪如此。我们接着看传入的不同参数所发挥的不同作用吧。

果然!不同的系统调用调用的是同一个内核函数。

看到这里我才明白了这些标志到底是干什么的了。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

关键:

看到这里我终于明白了什么是task_struct的多样性与统一性了。相信小伙伴们也一定明白了吧。原来多样性与统一性是相辅相成的。进程、线程、内核线程这些不同的玩意儿,统一用一个内核函数创建又能统一的进行调度管理。简直不能更方便了。当然,从另一个角度来讲,使用task_struct进行统一管理有没有什么弊端呢?当然,这没有标准答案的。大家可以留作思考。

 

Linux中的进程管理-(四)

原创 2017-08-10 陈莉君 Linux内核之旅

前面已经给大家讲解了进程管理的一些基本概念,包括用于描述一个进程的进程描述符、进程的状态及转化、进程描述符的多样性与统一性。其中上一次课我们了解了无论是进程、线程还是内核线程,在创建时其实都调用了内核函数do_fork()。那么,也许大家会好奇,进程创建的过程具体是什么呢?这一次我将会为大家大致讲解进程创建的过程。

由于进程、线程及内核线程都是通过调用do_fork()内核函数来创建的。所以我们需要剖析的是内核函数do_fork()做了什么。

这里展示出do_fork()代码的大体流程如下。我们先从宏观上知道它干了什么。不过细节上我们还是需要继续深入。

 

很明显,do_fork()的重点在于copy_process(),我们用它创建了进程描述符及其它重要的数据结构。

 

copy_process()的代码流程大致如下所示,可以看到copy_process()做了许多事情。

这里把复制或者共享父进程资源的重点函数列了出来

关于进程创建,其实细节上有许多值得了解的地方,不过由于篇幅所限,所以我只给大家展示了宏观的一面,如果大家想要深入了解进程的创建,推荐大家分析相关源码哦。

 

Linux中的进程管理-(五)

原创 2017-08-12 陈莉君 Linux内核之旅

 

前面已经大致介绍了进程的概念、基本属性以及进程的创建过程。这一次我们聊聊进程的调度。像实时进程、普通进程这样不同类型的进程当然是要在调度中区别对待的,实时进程在调度中肯定是要比普通进程优先级要高的。这就是linux中不同的调度策略。那么又是谁来控制整个调度的发生

介绍完了什么是调度器,接下来就要介绍什么是调度策略,调度策略实际上就是针对不同的进程有不同的调度方案。

下面我们看一些更细节的东西

读过进程调度源码的童鞋一定都知道调度的核心程序在schdule函数里,没读过源码也没关系,我们一起看看schdule函数做了什么

原创粉丝点击