荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: Pazu (自大狂的教训), 信区: Linux
标  题: Linux进程管理
发信站: BBS 荔园晨风站 (Tue Jan 25 10:37:27 2000), 转信

本章重点讨论Linux内核如何在系统中创建、管理以及删除进程。

进程在操作系统中执行特定的任务。而程序是存储在磁盘上包含可执行机器指令和
数据的静态实体。进程或者任务是处于活动状态的计算机程序。

进程是一个随执行过程不断变化的实体。和程序要包含指令和数据一样,进程也包
含程序计数器和所有CPU寄存器的值,同时它的堆栈中存储着如子程序参数、返回地
址以及变量之类的临时数据。当前的执行程序,或者说进程,包含着当前处理器中
的活动状态。Linux是一个多处理操作系统。进程具有独立的权限与职责。如果系统
中某个进程崩溃,它不会影响到其余的进程。每个进程运行在其各自的虚拟地址空间
中,通过核心控制下可靠的通讯机制,它们之间才能发生联系。

进程在生命期内将使用系统中的资源。它利用系统中的CPU来执行指令,在物理内存
来放置指令和数据。使用文件系统提供的功能打开并使用文件,同时直接或者间接的
使用物理设备。Linux必须跟踪系统中每个进程以及资源,以便在进程间实现资源的
公平分配。如果系统有一个进程独占了大部分物理内存或者CPU的使用时间,这种情
况对系统中的其它进程是不公平的。

系统中最宝贵的资源是CPU,通常系统中只有一个CPU。Linux是一个多处理操作系统,
它最终的目的是:任何时刻系统中的每个CPU上都有任务执行,从而提高CPU的利用率。
如果进程个数多于CPU的个数,则有些进程必须等待到CPU空闲时才可以运行。多处理
是的思路很简单;当进程需要某个
系统资源时它将停止执行并等待到资源可用时才继续运行。单处理系统中,如DOS,
此时CPU将处于空等状态,这个时间将被浪费掉。在多处理系统中,因为可以同时存
在多个进程,所以当某个进程开始等待时,操作系碗使用系统共享库,所以对它们的
管理要具有透明性。


4.1  Linux进程
为了让Linux来管理系统中的进程,每个进程用一个task_struct数据结构来表示(任
务与进程在Linux中可以混用)。数组task包含指向系统中所有task_struct结构的指针。
这意味着系统中的最大进程数目受task数组大小的限制,缺省值一般为512。创建新进
程时,Linux将从系统内存中分配一个task_struct结构并将其加入task数组。当前运
行进程的结构用current指针来指示。

Linux还支持实时进程。这些进程必须对外部时间作出快速反应(这就是“实时”的
意思),系统将区分对待这些进程和其他进程。虽然task_struct数据结构庞大而复
杂,但它可以分成一些功能组成部分:


State
进程在执行过程中会根据环境来改变state。Linux进程有以下状态:
Running
进程处于运行(它是系统的当前进程)或者准备运行状态(它在等待系统将CPU分配给它)

Waiting
进程在等待一个事件或者资源。Linux将等待进程分成两类;可中断与不可中断。可
中断等待进程可以被信号中断;不可中断等待进程直接在硬件条件等待,并且任何情
况下都不可中断。
Stopped
进程被停止,通常是通过接收一个信号。正在被调试的进程可能处于停止状态。
Zombie
这是由于某些原因被终止的进程,但是在task数据中仍然保留task_struct结构。
它象一个已经死亡的进程。

Scheduling Information
调度器需要这些信息以便判定系统中哪个进程最迫切需要运行。

Identifiers
系统中每个进程都有进程标志。进程标志并不是task数组的索引,它仅仅是个数紫
内存、信号灯和消息队列。我们将在IPC一章中详细讨论Linux中IPC机制。

Links
Linux系统中所有进程都是相互联系的。除了初始化进程外,所有进程都有一个父进程。
新进程不是被创建,而是被复制,或者从以前的进程克隆而来。每个进程对应的
task_struct结构中包含有指向其父进程和兄弟进程(具有相同父进程的进程)以及子
进程的指针。我们可以使用pstree 命令来观察Linux系统中运行进程间的关系:

init(1)-+-crond(98)
        |-emacs(387)
        |-gpm(146)
        |-inetd(110)
        |-kerneld(18)
        |-kflushd(2)
        |-klogd(87)
        |-kswapd(3)
        |-login(160)---bash(192)---emacs(225)
        |-lpd(121)
        |-mingetty(161)
        |-mingetty(162)
        |-mingetty(163)
        |-mingetty(164)
        |-login(403)---bash(404)---pstree(594)
        |-sendmail(134)
        |-syslogd(78)
        `-update(166)


另外,系统中所有进程都用一个双向链表连接起来,而它们的根是init进程的
task_struct数据结构。这 个链表被Linux核心用来寻找系统中所有进程,它对ps
或者kill命令提供了支持。

Times and Timers
核心需要记录进程的创建时间以及在其生命期中消耗的CPU时间。时钟每跳动一次,
核心就要更新保存在jiffies变量中,记录进程在系统和用户模式下消耗的时间量。
Linux支持与进程相关的interval定时器,进程可以通过系统调用来设定定时器以便
在定时器到时后向它发送信号。这些定时器可以是一次性的或者周期性的。

File system
进程可以自由地打开或关闭文件,进程的task_struct结构中包含一个指向每个打开
文件描叙符的指针以及指向两个VFS inode的指针。每个VFS
inode唯一地标记文件中的一个目录或者文件,同时还对底层文件系统提供统一的接
口。Linux对文件系统的支持将在filesystem一章中详细描叙。这两个指针,一个指
向进程的根目录,另一个指向其当前或者pwd目录。pwd从Unix命令pwd中派生出来,
用来显示当前工作目录。这两个VFS
inode包含一个count域,当多个进程引用它们时,它的值将增加。这就是为什么你不
能删除进程当前目录,或者其子目录的原因。

Virtual memory
多数进程都有一些虚拟内存(核心线程和后台进程没有),Linux核心必须跟踪虚拟
内存与系统物理内存的映射关系。

Processor Specific Context
进程可以认为是系统当前状态的总和。进程运行时,它将使用处理器的寄存器以及堆
栈等等。进程被挂起时,进程的上下文-所有的CPU相关的状态必须保存在它的
task_struct结构中。当调度器重新调度该进程时,所有上下文被重新设定。

4.2  Identifiers
和其他Unix一样,Linux使用用户和组标志符来检查对系统中文件和可执行映象的访问
权限。Linux系统中所有的文件都有所有者和允许的权限,这些权限描叙了系统使用者
对文件或者目录的使用权。基本的权限是读、写和可执行,这些权限被分配给三类用
户:文件的所有者,属于相同组的
进程以及系统中所有进程。每类用户具有不同的权限,例如一个文件允许其拥有者读
写,但是同组的只能读而其他进程不允许访问。

Linux使用组将文件和目录的访问特权授予一组用户,而不是单个用户或者系统中所有
进程。如可以为某个软件项目中的所有用户创建一个组,并将其权限设置成只有他们
才允许读写项目中的源代码。一个进程可以同时属于多个组(最多为32个),这些组
都被放在进程的task_struct中的group数组中。只要某组进程可以存取某个文件,则
由此组派生出的进程对这个文件有相应的组访问权限。

task_struct结构中有四对进程和组标志符:


uid, gid
表示运行进程的用户标志符和组标志符。
effective uid and gid
有些程序可以在执行过程中将执行进程的uid和gid改成其程序自身的uid和gid(保存
在描叙可执行映象的VFS inode属性中)。这些程序被称为setuid程序,常在严格控
制对某些服务的访问时使用,特别是那些为别的进程而运行的进程,例如网络后台进
程。有效uid和gid是那些setuid执行过程在执行时变化出的uid 和gid。当进程试图
访问特权数据或代码时,核心将检查进程的有效gid和uid。 file system uid and
gid 它们和有效uid和gid相似但用来检验进程的文件系统访问权限。如运行在用户模
式下的NFS服务器存取文件时,NFS文件系统将使用这些标志符。此例中只有文件系统
uid和gid发生了改变(而非有效uid和gid)。这样可以避免恶意用户向NFS服务器发送
KILL信号。

saved uid and gid
POSIX标准中要求实现这两个标志符,它们被那些通过系统调用改变进程uid和gid的程
序使用。当进程的原始uid和gid变化时,它们被用来保存真正的uid和gid。

4.3  调度
所有进程部分时间运行于用户模式,部分时间运行于系统模式。如何支持这些模式,
底层硬件的实现各不相同,但是存在一种安全机制可以使它们在用户模式和系统模式
之间来回切换。用户模式的权限比系统模式下的小得多。进程通过系统调用切换到系
统模式继续执行。此时核心为进程而
执行。在Linux中,进程不能被抢占。只要能够运行它们就不能被停止。当进程必须等
待某个系统事件时,它才决定释放出CPU。例如进程可能需要从文件中读出字符。一般
等待发生在系统调用过程中,此时进程处于系统模式;处于等待状态的进程将被挂起
而其他的进程被调度管理器选出来执行。

?
Current process
当选定其他进程运行之前必须对当前进程进行一些处理。
如果当前进程的调度策略是时间片轮转,则它被放回到运行队列。


如果任务可中断且从上次被调度后接收到了一个信号,则它的状态变为Running。


如果当前进程超时,则它的状态变为Running。


如果当前进程的状态是Running,则状态保持不变。 那些既不处于Running状态又不是
可中断的进程将会从运行队列中删除。这意味着调度管理器选择运行进程时不会将这
些进程考虑在内。



Process selection
调度器在运行队列中选择一个最迫切需要运行的进程。如果运行队列中存在实时进程
(那些具有实时调度策略的进程),则它们比普通进程更多的优先级权值。普通进程
的权值是它的counter值,而实时
进程则是counter加上1000。这表明如果系统中存在可运行的实时进程,它们将总是在
任何普通进程之前运行。如果系统中存在和当前进程相同优先级的其它进程,这时当
前运行进程已经用掉了一些时间片,所以它将处在不利形势(其counter已经变小);
而原来优先级与它相同的进程的counter值显然比它大,这样位于运行队列中最前面的
进程将开始执行而当前进程被放回到运行队列中。在存在多个相同优先级进程的平衡
系统中,每个进程被依次执行,这就是Round Robin策略。然而由于进程经常需要等待
某些资源,所以它们的运行顺序也常发变化。 Swap processes
如果系统选择其他进程运行,则必须被挂起当前进程且开始执行新进程。进程执行时将
使用寄存器、物理内存以及CPU。每次调用子程序时,它将参数放在寄存器中并把返回
地址放置在堆栈中,所以调度管理器总是运行在当前进程的上下文。虽然可能在铁完成。


进程的切换发生在调度管理器运行之后。以前进程保存的上下文与当前进程加载时的
上下文相同,包括进程程序计数器和寄存器内容。

如果以前或者当前进程使用了虚拟内存,则系统必须更新其页表入口,这与具体体系
结构有关。如果处理器使用了转换旁视缓冲或者缓冲含下一次文件读写操作开始位置。
f_inode指向描叙此文件的VFS inode, f_ops指向一组可以对此文件进行操作的函数
入口地址指针数组。这些抽象接口十分强大,它们使得Linux 能够支持多种文件类型。
在Linux中,管道是用我们下面要讨论的机制实现的。

每当打开一个文件时,位于files_struct中的一个空闲文件指针将被用来指向这个新
的文件结构。Linux进 程希望在进程启动时至少有三个文件描叙符被打开,它们是标
准输入,标准输出和标准错误输出,一般进程 会从父进程中继承它们。这些描叙符用
来索引进程的fd数组,所以标准输入,标准输出和标准错误输出分别 对应文件描叙符
0,1和2。每次对文件的存取都要通过文件数据结构中的文件操作子程序和VFS inode
一起来完成,


4.5  虚拟内存
进程的虚拟内存包括可执行代码和多个资源数据。首先加载的是程序映象,例如ls。
ls和所有可执行映象一样,是由可执行代码和数据组成的。此映象文件包含所有加载
可执行代码所需的信息,同时还将程序数据连接进入进程的虚拟内存空间。然后在执
行过程中,进程定位可以使用的虚拟内存,以包含正在读取的文件内容。新分配的虚
拟内存必须连接到进程已存在的虚拟内存中才能够使用。
最后Linux进程调用通用库过程,比如文件处理子程序。如果每个进程都有库过程的
拷贝,那么共享就变得没有意义。而Linux可以使多个进程同时使用共享库。来自共
享库的代码和数据必须连接进入进程的虚拟地址空间以及共享此库的其它进程的虚拟
地址空间。

任何时候进程都不同时使用包含在其虚拟内存中的所有代码和数据。虽然它可以加载
在特定情况下使用的那些代码,如初始化或者处理特殊事件时,另外它也使用了共享
库的部分子程序。但如果接分配物理内存。它只是创建一个vm_area_struct
结构来描叙此虚拟内存,此结构被连接到进程的虚拟内存链表中。当进程试图对新分
配的虚拟内存进行写操作时,系统将产生页面错。处理器会尝试解析此虚拟地址,但
是如果找不到对应此虚拟地址的页表入口时,处理器将放弃解析并产生页面错误异常,
由Linux核心来处理。Linux则查看
此虚拟地址是否在当前进程的虚拟地址空间中。如果是Linux会创建正确的PTE并为此
进程分配物理页面。包含在此页面中的代码或数据可能需要从文件系统或者交换磁盘
上读出。然后进程将从页面错误处开始继续执行,由于物理内存已经存在,所以不会
再产生页面异常。


4.6  进程创建
系统启动时总是处于核心模式,此时只有一个进程:初始化进程。象所有进程一样,
初始化进程也有一个由堆栈、寄存器等表示的机器状态。当系统中有其它进程被创建
并运行时,这些信息将被存储在初始化进程的task_struct结构中。在系统初始化的最
后,初始化进程启动一个核心线程(init)然后保留在idle状态。 如果没有任何事要
做,调度管理器将运行idle进程。idle进程是唯一不是动态分配task_struct的进程,
它的 task_struct在核心构造时静态定义并且名字很怪,叫init_task。

由于是系统的第一个真正的进程,所以init核心线程(或进程)的标志符为1。它负责
完成系统的一些初始化设置任务(如打开系统控制台与安装根文件系统),以及执行系
统初始化程序,如/etc/init, /bin/init 或者 /sbin/init ,这些初始化程序依赖于
具体的系统。init程序使用/etc/inittab作为脚本文件来创建系统中的新进程。这些
新进程又创建各自的新进程。例如getty进程将在用户试图登录时创建一个login进程。
系 统中所有进程都是从init核心线程中派生出来。

新进程通过克隆老进程或当前进程来创建。系统调用fork或clone可以创建新任务,复
制发生在核心状态下的核心中。在系统调用的结束处有一个新进程等待调度管理器选择
它去运行。系统从物理内存中分配出来一个新的task_struct数据结构,同时还有一个
或多个包含被复制进程堆栈(用户与核心)的物理页面。然后创建唯一地标记此新任务
的进程标志符。但复制进程保留其父进程的标志符也是合理的。新创建的task_struct
将被放入task数组中,另外将被复制进程的task_struct中的内容页表拷入新的
task_struct中。

复制完成后,Linux允许两个进程共享资源而不是复制各自的拷贝。这些资源包括文件、
信号处理过程和虚拟内存。进程对共享资源用各自的count来记数。在两个进程对资源
的使用完毕之前,Linux绝不会释放此资源,例如复制进程要共享虚拟内存,则其
task_struct将包含指向原来进程的mm_struct的指针。mm_struct将增加count变量以
表示当前进程共享的次数。

复制进程虚拟空间所用技术的十分巧妙。复制将产生一组新的vm_area_struct结构和
对应的mm_struct结构,同时还有被复制进程的页表。该进程的任何虚拟内存都没有被
拷贝。由于进程的虚拟内存有的可能在物理内存中,有的可能在当前进程的可执行映
象中,有的可能在交换文件中,所以拷贝将是一个困难且繁琐的工作。Linux使用一种
"copy on write"技术:仅当两个进程之一对虚拟内存进行写操作时才拷贝此虚拟内存
块。但是不管写与不写,任何虚拟内存都可以在两个进程间共享。只读属性的内存,
如可执行代码,总是可以共享的。为了使"copy on write"策略工作,必须将那些可写
区域的页表入口标记为只读的,同时描叙它们的vm_area_struct数据都被设置为"copy
on write"。当进程之一试图对虚拟内存进行写操作时将产生页面错误。这时Linux将
拷贝这一块内存并修改两个进程的页表以及虚拟内存数据结构。

4.7  时钟和定时器
核心跟踪着进程的创建时间以及在其生命期中消耗的CPU时间。每个时钟滴答时,核心
将更新当前进程在系统 模式与用户模式下所消耗的时间(记录在jiffies中)。 除了
以上记时器外,Linux还支持几种进程相关的时间间隔定时器。

进程可以使用这些定时器在到时时向它发送各种信号,这些定时器如下:


Real
此定时器按照实时时钟记数,当时钟到期时,向进程发送SIGALRM信号。
Virtual
此定时器仅在进程运行时记数,时钟到期时将发送SIGVTALRM信号。
Profile
此定时器在进程运行和核心为其运行时都记数。当到时时向进程发送SIGPROF信号。
以上时间间隔定时器可以同时也可以单独运行,Linux将所有这些信息存储在进程的
task_struct数据结构中。通过系统调用可以设置这些时间间隔定时器并启动,甚至
是一个脚本文件。脚本文件需要恰当的命令解释器来处理它们;例如 /bin/sh解释shell
脚本。可执行目标文件包含可执行代码和数据,这样操作系统可以获得足够的信息将
其 加载到内存并执行之。Linux最常用的目标文件是ELF,但是理论上Linux可以灵活
地处理几乎所有目标文件
瘵,父进程执行的是老的映象程序,例如象bash这样的命令解释器。同时还将清除任
何信号处理过程并且关闭打开的文件,在冲刷的最后,进程已经为新的可执行映象作
好了准备。不管可执行映象是哪种格式,进程的mm_struct结构中将存入相同信息,
它们是指向映象代码和数据的指针。当ELF可执行映象从文件中读出且相关程序代码
被映射到进程虚拟地址空间后,这些指针的值都被确定下来。同时vm_area_struct
也被建立起来,进程的页表也被修改。mm_struct结构中还包含传递给程序和进程环
境变量的参数的指针。


ELF 共享库
另一方面,动态连接映象并不包含全部运行所需要的代码和数据。其中的一部分仅在
运行时才连接到共享库中。ELF共享库列表还在运行时连接到共享库时被动态连接器
使用。Linux使用几个动态连接器,如ld.so.1,libc.so.1和ld-linux.so.1,这些都
放置在/lib目录中。这些库中包含常
代码,如C语言子程序等。如果没有动态连接,所有程序将不得不将所有库过程拷贝
一份并连接进来,这样将需要更多的磁盘与虚拟内存空间。通过动态连接,每个被引
用库过程的信息都可以包含在ELF映象列表中。这些信息用来帮助动态连接器定位库
过程并将它连入程序的地址空间。




4.8.2  脚本文件
脚本文件的运行需要解释载过程与其他可执行文件相同。Linux会逐个尝试各种二进制
可执行格式直到它可以执行。



File translated from TEX by TTH, version 1.0.


--------------------------------------------------------------------------------

Top of Chapter, Table of Contents, Show

--
※ 来源:·BBS 荔园晨风站 bbs.szu.edu.cn·[FROM: 192.168.32.189]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店