第四章 进程描述 4.1——4.3



http://www.kerneltravel.net/kernel-book/第四章%20进程描述/4.1.htm

4.1进程和程序(Process and Program

首先我们对进程明确定义:所谓进程是由正文段(text)、用户数据段(user segment)以及系统数据段(system segment)共同组成的一个执行环境

 
 


 程序只是一个普通文件,是一个机器代码指令和数据的集合,这些指令和数据存储在磁盘上的一个可执行映象Executable Image)中,所以,程序是一个静态的实体。这里,对可执行映象做进一步解释,可执行映象就是一个可执行文件的内容,例如,你编写了一个C源程序,最终这个源程序要经过编译、连接成为一个可执行文件后才能运行。源程序中你要定义许多变量,在可执行文件中,这些变量就组成了数据段的一部分;源程序中的许多语句,例如“i++; for(i=0; i<10; i++);”等,在可执行文件中,它们对应着许多不同的机器代码指令,这些机器代码指令经CPU执行,就完成了你所期望的工作。可以这么说:程序代表你期望完成某工作的计划和步骤,它还浮在纸面上,等待具体实现。而具体的实现过程就是由进程来完成的,进程可以认为是运行中的程序,它除了包含程序中的所有内容外,还包含一些额外的数据。程序和进程的组成如图4.1所示。

                   4.1程序及进程的组成

程序装入内存后就可以运行了:在指令指针寄存器的控制下,不断的将指令取至CPU运行。这些指令控制的对象不外乎各种存储器(内存、外存和各种CPU寄存器等),这些存储器中保存有待运行的指令和待处理的数据,当然,指令只有得到CPU才能发挥其作用。可见,在计算机内部,程序的执行过程实际上就是一个执行环境的总和,这个执行环境包括程序中各种指令和数据,还有一些额外数据,比如说寄存器的值、用来保存临时数据(例如传递给某个函数的参数、函数的返回地址、保存变量等)的堆栈(包括程序堆栈和系统堆栈)、被打开文件的数量及输入输出设备的状态等等。这个执行环境的动态变化表征程序的运行。我们就把这个环境称作“进程”,它代表程序的执行过程,是一个动态的实体,它随着程序中指令的执行而不断地变化。在某个特定时刻的进程的内容被称为进程映象process image

Linux是一个多任务操作系统,也就是说,可以有多个程序同时装入内存并运行,操作系统为每个程序建立一个运行环境即创建进程,每个进程拥有自己的虚拟地址空间,它们之间互不干扰,即使要相互作用(例如多个进程合作完成某个工作),也要通过内核提供的进程间通信机制(IPC)。Linux内核支持多个进程虚拟地并发执行,这是通过不断地保存和切换程序的运行环境而实现的,选择哪个进程运行是由调度程序决定的。注意,在一些UNIX书籍中,又把“进程切换”(Process Switching)称为“环境切换”或“上下文切换”(Context Switching),这些术语表达的意思是相同的。

进程运行过程中,还需要其他的一些系统资源,例如,要用CPU来运行它的指令、要用系统的物理内存来容纳进程本身和它的有关数据、要在文件系统中打开和使用文件、并且可能直接或间接的使用系统的物理设备,例如打印机、扫描仪等。由于这些系统资源是由所有进程共享的,所以Linux必须监视进程和它所拥有的系统资源,使它们可以公平地拥有系统资源以得到运行。

系统中最宝贵的资源是CPU,通常来说,系统中只有一个CPU,当然也可以有多个CPULinux支持SMP___对称多处理机)。Linux作为多任务操作系统,其目的就是让CPU上一直都有进程在运行,以最大限度地利用这一宝贵资源。通常情况下,进程数目是多于CPU数目的,这样其他进程必须等待CPU这一资源。操作系统通过调度程序来选择下一个最应该运行的进程,并使用一系列的调度策略来确保公平和高效。

进程是一个动态实体,如图4.1所示,进程实体由三个独立的部分组成:

1)正文段(text):存放被执行的机器指令。这个段是只读的(所以,在这里不能写自己能修改的代码),它允许系统中正在运行的两个或多个进程之间能够共享这一代码。例如,有几个用户都在使用文本编辑器,在内存中仅需要该程序指令的一个副本,他们全都共享这一副本。

2)用户数据段(user segment):存放进程在执行时直接进行操作的所有数据,包括进程使用的全部变量在内。显然,这里包含的信息可以被改变。虽然进程之间可以共享正文段,但是每个进程需要有它自己的专用用户数据段。例如同时编辑文本的用户,虽然运行着同样的程序__编辑器,但是每个用户都有不同的数据:正在编辑的文本。

3)系统数据段(system segment):该段有效地存放程序运行的环境。事实上,这正是程序和进程的区别所在。如前所述,程序是由一组指令和数据组成的静态事物,它们是进程最初使用的正文段和用户数据段。作为动态事物,进程是正文段、用户数据段和系统数据段的信息的交叉综合体,其中系统数据段是进程实体最重要的一部分,之所以说它有效地存放程序运行的环境,是因为这一部分存放有进程的控制信息。系统中有许多进程,操作系统要管理它们、调度它们运行,就是通过这些控制信息。Linux为每个进程建立了task_struct数据结构来容纳这些控制信息。

总之,进程是一个程序完整的执行环境。该环境是由正文段、用户数据段、系统数据段的信息交织在一起组成的。

4.2 Linux中的进程概述

Linux中的每个进程由一个task_struct数据结构来描述,在Linux中,任务(task)、和进程(process)是两个相同的术语,task_struct其实就是通常所说的“进程控制块”即PCBtask_struct容纳了一个进程的所有信息,是系统对进程进行控制的唯一手段,也是最有效的手段。

Linux2.4中,Linux为每个新创建的进程动态地分配一个task_struct结构。系统所允许的最大进程数是由机器所拥有的物理内存的大小决定的,例如,在IA32的体系结构中,一个512M内存的机器,其最大进程数可以达到32K,这是对旧内核(2.2以前)版本的极大改进[1]

Linux支持多处理机(SMP),所以系统中允许有多个CPU Linux作为多处理机操作系统时系统中允许的最大CPU个数为32。很显然,Linux作为单机操作系统时,系统中只有一个CPU,本书主要讨论单处理机的情况。

和其他操作系统类似,Linux也支持两种进程:普通进程和实时进程。实时进程具有一定程度上的紧迫性,要求对外部事件做出非常快的响应;而普通进程则没有这种限制。所以,调度程序要区分对待这两种进程,通常,实时进程要比普通进程优先运行。这两种进程的区分也反映在task_struct数据结构中了。

总之,包含进程所有信息的task_struct数据结构是比较庞大的,但是该数据结构本身并不复杂,我们将它的所有域按其功能可做如下划分:

·进程状态(State

·进程调度信息(Scheduling Information

·各种标识符(Identifiers

·进程通信有关信息(IPCInter_Process Communication

·时间和定时器信息(Times and Timers

·进程链接信息(Links

·文件系统信息(File System

·虚拟内存信息(Virtual Memory

·      页面管理信息(page

·      对称多处理器(SMP)信息

·和处理器相关的环境(上下文)信息(Processor Specific Context

·      其它信息

下面我们对task_struct结构进行具体描述。


4.3 task_struct结构描述

 1. 进程状态(State

进程执行时,它会根据具体情况改变状态 。进程状态是调度和对换的依据。Linux中的进程主要有如下状态,如表4.1所示。

4.1          Linux进程的状态

 

内核表示

含义

TASK_RUNNING

可运行

TASK_INTERRUPTIBLE

可中断的等待状态

TASK_UNINTERRUPTIBLE

不可中断的等待状态

TASK_ZOMBIE

僵死

TASK_STOPPED

暂停

TASK_SWAPPING

换入/换出

 

·可运行状态

处于这种状态的进程,要么正在运行、要么正准备运行。正在运行的进程就是当前进程(由current所指向的进程),而准备运行的进程只要得到CPU就可以立即投入运行,CPU是这些进程唯一等待的系统资源。系统中有一个运行队列(run_queue),用来容纳所有处于可运行状态的进程,调度程序执行时,从中选择一个进程投入运行。在后面我们讨论进程调度的时候,可以看到运行队列的作用。当前运行进程一直处于该队列中,也就是说,current总是指向运行队列中的某个元素,只是具体指向谁由调度程序决定。

·等待状态

处于该状态的进程正在等待某个事件(event)或某个资源,它肯定位于系统中的某个等待队列(wait_queue)中。Linux中处于等待状态的进程分为两种:可中断的等待状态和不可中断的等待状态。处于可中断等待态的进程可以被信号唤醒,如果收到信号,该进程就从等待状态进入可运行状态,并且加入到运行队列中,等待被调度;而处于不可中断等待态的进程是因为硬件环境不能满足而等待,例如等待特定的系统资源,它任何情况下都不能被打断,只能用特定的方式来唤醒它,例如唤醒函数wake_up()等。

·暂停状态

此时的进程暂时停止运行来接受某种特殊处理。通常当进程接收到SIGSTOPSIGTSTPSIGTTIN SIGTTOU信号后就处于这种状态。例如,正接受调试的进程就处于这种状态。

·僵死状态

进程虽然已经终止,但由于某种原因,父进程还没有执行wait()系统调用,终止进程的信息也还没有回收。顾名思义,处于该状态的进程就是死进程,这种进程实际上是系统中的垃圾,必须进行相应处理以释放其占用的资源。

2. 进程调度信息

调度程序利用这部分信息决定系统中哪个进程最应该运行,并结合进程的状态信息保证系统运转的公平和高效。这一部分信息通常包括进程的类别(普通进程还是实时进程)、进程的优先级等等。如表4.2所示:

 

 

4.2               进程调度信息

域名

含义

need_resched

调度标志

Nice

静态优先级

Counter

动态优先级

Policy

调度策略

rt_priority

实时优先级

 

在下一章的进程调度中我们会看到,当need_resched被设置时,在“下一次的调度机会”就调用调度程序schedule() counter代表进程剩余的时间片,是进程调度的主要依据,也可以说是进程的动态优先级,因为这个值在不断地减少;nice是进程的静态优先级,同时也代表进程的时间片,用于对counter赋值,可以用nice()系统调用改变这个值;policy是适用于该进程的调度策略,实时进程和普通进程的调度策略是不同的;rt_priority只对实时进程有意义,它是实时进程调度的依据。

进程的调度策略有三种,如表4.3所示。

4.3          进程调度的策略

名称

解释

适用范围

SCHED_OTHER

其他调度

普通进程

SCHED_FIFO

先来先服务调度

实时进程

SCHED_RR

时间片轮转调度

 

只有root用户能通过sched_setscheduler()系统调用来改变调度策略。

 

3 .标识符(Identifiers

每个进程有进程标识符、用户标识符、组标识符,如表4.4所示。

不管对内核还是普通用户来说,怎么用一种简单的方式识别不同的进程呢?这就引入了进程标识符(PIDprocess identifier),每个进程都有一个唯一的标识符,内核通过这个标识符来识别不同的进程,同时,进程标识符PID也是内核提供给用户程序的接口,用户程序通过PID对进程发号施令。PID32位的无符号整数,它被顺序编号:新创建进程的PID通常是前一个进程的PID1。然而,为了与16位硬件平台的传统Linux系统保持兼容,在Linux上允许的最大PID号是32767,当内核在系统中创建第32768个进程时,就必须重新开始使用已闲置的PID号。

 

 

4.4                  各种标识符 

域名

含义

Pid

进程标识符

Uidgid

用户标识符、组标识符

Euidegid

有效用户标识符、有效组标识符

Suidsgid

备份用户标识符、备份组标识符

Fsuidfsgid

文件系统用户标识符、文件系统组标识符

 

另外,每个进程都属于某个用户组。task_struct结构中定义有用户标识符和组标识符。它们同样是简单的数字,这两种标识符用于系统的安全控制。系统通过这两种标识符控制进程对系统中文件和设备的访问,其它几个标识符将在文件系统中讨论。

4. 进程通信有关信息(IPCInter_Process Communication

为了使进程能在同一项任务上协调工作,进程之间必须能进行通信即交流数据。

Linux支持多种不同形式的通信机制。它支持典型的Unix 通信机制(IPC Mechanisms):信号(Signals)、管道(Pipes),也支持System V 通信机制:共享内存(Shared Memory)、信号量和消息队列(Message Queues,如表4.5

4.5                 进程通信有关信息

域名

含义

Spinlock_t sigmask_lock

信号掩码的自旋锁

Long blocked

信号掩码

Struct signal  *sig

信号处理函数

Struct sem_undo *semundo

为避免死锁而在信号量上设置的取消操作

Struct sem_queue *semsleeping

与信号量操作相关的等待队列

这些域的具体含义将在进程通信一章进行讨论。

 

5. 进程链接信息(Links

程序创建的进程具有父/子关系。因为一个进程能创建几个子进程,而子进程之间有兄弟关系,在task_struct结构中有几个域来表示这种关系。

Linux系统中,除了初始化进程init,其他进程都有一个父进程(parent process)或称为双亲进程。可以通过fork()或clone()系统调用来创建子进程,除了进程标识符(PID)等必要的信息外,子进程的task_struct结构中的绝大部分的信息都是从父进程中拷贝,或说“克隆”过来的。系统有必要记录这种“亲属”关系,使进程之间的协作更加方便,例如父进程给子进程发送杀死(kill)信号、父子进程通信等,就可以用这种关系很方便地实现。

每个进程的task_struct结构有许多指针,通过这些指针,系统中所有进程的task_struct结构就构成了一棵进程树,这棵进程树的根就是初始化进程inittask_struct结构(init进程是Linux内核建立起来后人为创建的一个进程,是所有进程的祖先进程)。表4.6是进程所有的链接信息。

4.6          进程链接信息

名称

英文解释

中文解释 [指向哪个进程]

p_opptr

Original parent

祖先

p_pptr

Parent

父进程

p_cptr

Child

子进程

p_ysptr

Younger sibling

进程

p_osptr

Older sibling

兄进程

Pidhash_next

Pidhash_pprev

 

进程在哈希表中的链接

Next_task prev_task

 

进程在双向循环链表中的链接

Run_list

 

运行队列的链表

 

6. 时间和定时器信息(Times and Timers

一个进程从创建到终止叫做该进程的生存期(lifetime)。进程在其生存期内使用CPU的时间,内核都要进行记录,以便进行统计、计费等有关操作。进程耗费CPU的时间由两部分组成:一是在用户模式(或称为用户态)下耗费的时间、一是在系统模式(或称为系统态)下耗费的时间。每个时钟滴答,也就是每个时钟中断,内核都要更新当前进程耗费CPU的时间信息。

“时间”对操作系统是极其重要的 。读者可能了解计算机时间的有关知识,例如8353/8254这些物理器件,INT 08INT 1C等时钟中断等,可能有过编程序时截获时钟中断的成就感,不管怎样,下一章我们将用较大的篇幅尽可能向读者解释清楚操作系统怎样建立完整的时间机制、并在这种机制的激励下进行调度等活动。

建立了“时间”的概念,“定时”就是轻而易举的了,无非是判断系统时间是否到达某个时刻,然后执行相关的操作而已。Linux提供了许多种定时方式,用户可以灵活使用这些方式来为自己的程序定时。

4.7是和时间有关的域,上面所说的counter指进程剩余的CPU时间片,也和时间有关,所以这里我们再次提及它。表4.8是进程的所有定时器。

  4.7        与时间有关的域

域名

含义

Start_time

进程创建时间

Per_cpu_utime

进程在某个CPU上运行时在用户态下耗费的时间

Per_cpu_stime

进程在某个CPU上运行时在系统态下耗费的时间

Counter

进程剩余的时间片

 

 

  4.8                      进程的所有定时器

定时器类型

解释

什么时候更新

用来表示此种定时器的域

ITIMER_REAL

实时定时器

实时更新,即不论该进程是否运行

it_real_value

it_real_incr

real_timer

ITIMER_VIRTUAL

虚拟定时器

只在进程运行于用户态时更新

it_virt_value

it_virt_incr

ITIMER_PROF

概况定时器

进程运行于用户态和系统态时更新

it_prof_value

it_prof_incr

 

进程有三种类型的定时器:实时定时器、虚拟定时器和概况定时器。这三种定时器的特征共有三个:到期时间、定时间隔、要触发的事件。到期时间就是定时器到什么时候完成定时操作,从而触发相应的事件;定时间隔就是两次定时操作的时间间隔,它决定了定时操作是否继续进行,如果定时间隔大于0,则在定时器到期时,该定时器的到期时间被重新赋值,使定时操作继续进行下去,直到进程结束或停止使用定时器,只不过对不同的定时器,到期时间的重新赋值操作是不同的。在表4.8中,每个定时器都有两个域来表示到期时间和定时间隔:valueincr,二者的单位都是时钟滴答,和jiffies的单位是一致的,Linux所有的时间应用都建立在jiffies之上。虚拟定时器和概况定时器到期时由内核发送相应的信号,而实时定时器比较特殊,它由内核机制提供支持,我们将在后面讨论这个问题。

每个时钟中断,当前进程所有和时间有关的信息都要更新: 当前进程耗费的CPU时间要更新,以便于最后的计费;时间片计数器counter要更新,如果counter<=0,则要执行调度程序;进程申请的延时要更新,如果延时时间到了,则唤醒该进程;所有的定时器都要更新,Linux内核检测这些定时器是否到期,如果到期,则执行相应的操作。在这里,“更新”的具体操作是不同的:对counter,内核要对它减值,而对于所有的定时器,就是检测它的值,内核把系统当前时间和其到期时间作一比较,如果到期时间小于系统时间,则表示该定时器到期。但为了方便,我们把这些操作一概称为“更新”,请读者注意。

请特别注意上面三个定时器的更新时间。实时定时器不管其所属的进程是否运行都要更新,所以,时钟中断来临时,系统中所有进程的实时定时器都被更新,如果有多个进程的实时定时器到期,则内核要一一处理这些定时器所触发的事件。而虚拟定时器和概况定时器只在进程运行时更新,所以,时钟中断来临时,只有当前进程的概况定时器得到更新,如果当前进程运行于用户态,则其虚拟定时器也得到更新。

       此外,Linux内核对这三种定时器的处理是不同的,虚拟定时器和概况定时器到期时,内核向当前进程发送相应的信号:SIGVTALRM  SIGPROF ;而实时定时器要执行的操作由real_timer决定,real_timetimer_list类型的变量(定义:struct timer_list real_timer),其中容纳了实时定时器的到期时间、定时间隔等信息,我们将在下一章详细讨论这些内容。

7. 文件系统信息(File System

进程可以打开或关闭文件,文件属于系统资源,Linux内核要对进程使用文件的情况进行记录。task_struct结构中有两个数据结构用于描述进程与文件相关的信息。其中,fs_struct中描述了两个VFS索引节点(VFS inode),这两个索引节点叫做rootpwd,分别指向进程的可执行映象所对应的根目录(home directory)和当前目录或工作目录。file_struct结构用来记录了进程打开的文件的描述符(descriptor)。如表4.9所示。

       4.9                      与文件系统相关的域

定义形式

解释

Sruct fs_struct *fs

进程的可执行映象所在的文件系统

Struct files_struct *files

进程打开的文件

 

在文件系统中,每个VFS索引节点唯一描述一个文件或目录,同时该节点也是向更低层的文件系统提供的统一的接口。

8. 虚拟内存信息(Virtual Memory

除了内核线程(kernel thread),每个进程都拥有自己的地址空间(也叫虚拟空间),用mm_struct来描述。另外Linux2.4还引入了另外一个域active_mm,这是为内核线程而引入。因为内核线程没有自己的地址空间为了让内核线程与普通进程具有统一的上下文切换方式,当内核线程进行上下文切换时,让切换进来的线程的active_mm 指向刚被调度出去的进程的active_mm(如果进程的mm域不为空,则其active_mm域与mm域相同)内存信息如表4.10所示。

4.10             虚拟内存描述信息

定义形式

解释

Struct mm_struct *mm

描述进程的地址空间

Struct mm_struct *active_mm

内核线程所借用的地址空间

 

9.页面管理信息

   当物理内存不足时,Linux内存管理子系统需要把内存中的部分页面交换到外存,其交换是以页为单位的。有关页面的描述信息如表4.11

  4.11           页面管理信息

  定义形式

解释

Int swappable

进程占用的内存页面是否可换出

Unsigned long min_flat,maj_flt,nswap

进程累计的次(minor)缺页次数、主(major)次数及累计换出、换入页面数

Unsigned long cmin_flat,cmaj_flt,cnswap

本进程作为祖先进程,其所有层次子进程的累计的次(minor)缺页次数、主(major)次数及累计换出、换入页面数

 

10.对称多处理机(SMP)信息

Linux2.4SMP进行了全面的支持,表4.12是与多处理机相关的几个域。

4.12 与多处理机相关的域

   定义形式

解释

 Int has_cpu

 进程是否当前拥有CPU

 Int processor

 进程当前正在使用的CPU

 Int lock_depth

 上下文切换时内核锁的深度

 

11 和处理器相关的环境(上下文)信息(Processor Specific Context

这里要特别注意标题:和“处理器”相关的环境信息。进程作为一个执行环境的综合,当系统调度某个进程执行,即为该进程建立完整的环境时,处理器(processor)的寄存器、堆栈等是必不可少的。因为不同的处理器对内部寄存器和堆栈的定义不尽相同,所以叫做“和处理器相关的环境”,也叫做“处理机状态”。当进程暂时停止运行时,处理机状态必须保存在进程的task_struct结构中,当进程被调度重新运行时再从中恢复这些环境,也就是恢复这些寄存器和堆栈的值。处理机信息如表4.13所示。

 

4.13         与处理机相关的信息

定义形式

解释

Struct thread_struct *tss

任务切换状态

12.其它

1  struct wait_queue *wait_chldexit

 

  在进程结束时,或发出系统调用wait4时,为了等待子进程的结束,而将自己(父进程)睡眠在该等待队列上,设置状态标志为TASK_INTERRUPTIBLE,并且把控制权转给调度程序。

2Struct rlimit rlim[RLIM_NLIMITS];

 

     每一个进程可以通过系统调用setlimitgetlimit来限制它资源的使用。

 

     3Int exit_code exit_signal;

 

     程序的返回代码以及程序异常终止产生的信号,这些数据由父进程(子进程完成后)     轮流查询。

 

     4Char comm[16]

     这个域存储进程执行的程序的名字,这个名字用在调试中。

 

     5Unsigned long personality;

    Linux可以运行X86平台上其它Unix操作系统生成的符合iBCS2标准的程序, personality进一步描述进程执行的程序属于何种Unix平台的“个性”信息。通常有PER_Linux,PER_Linux_32BIT,PER_Linux_EM86,PER_SVR4,PER_SVR3,PER_SCOSVR3,PER_WYSEV386,PER_ISCR4,PER_BSD,PER_XENIXPER_MASK等,参见includeLinux/personality.h>

 

(6) int did_exec:1;

 

POSIX要求设计的布尔量,区分进程正在执行老程序代码,还是用系统调用execve()装入一个新的程序。

 

7struct linux_binfmt *binfmt

 

     指向进程所属的全局执行文件格式结构,共有a.outscriptelfjava等四种。

 

综上所述,我们对进程task_struct结构进行了归类讨论,还有一些域没有涉及到,在第六章进程的创建与执行一节几乎涉及到所有的域,在那里可以对很多域有更深入一步的理解。task_struct结构是进程实体的核心,Linux内核通过该结构来控制进程:首先通过其中的调度信息决定该进程是否运行;当该进程运行时,根据其中保存的处理机状态信息来恢复进程运行现场,然后根据虚拟内存信息,找到程序的正文和数据;通过其中的通信信息和其他进程实现同步、通信等合作。几乎所有的操作都要依赖该结构,所以,task_struct结构是一个进程存在的唯一标志。

 


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值