临江网

 找回密码
 点这里注册

QQ登录

只需一步,快速开始

搜索
热搜: 临江老照片

编辑推荐

查看: 2847|回复: 0
打印 上一主题 下一主题

Linux Kernel核心中文手册 4

[复制链接]

1400

主题

20

好友

3822

积分

版主

Rank: 7Rank: 7Rank: 7

帖子
1453
积分
3822
注册时间
2007-4-15
跳转到指定楼层
楼主
发表于 2007-7-18 23:02:51 |只看该作者 |倒序浏览
Linux Kernel核心中文手册
来自:蓝森林自由软件

Chapter 4 Processes (进程)

    本章描述进程是什么以及 Linux 如何创建、管理和删除系统中的进程。

    进程执行操作系统中的任务。程序是存放在磁盘上的包括一系列机器代码指令和数据的可执行的映像,因此,是一个被动的实体。进程可以看作是一个执行中的计算机程序。它是动态的实体,在处理器执行机器代码指令时不断改变。处理程序的指令和数据,进程也包括程序计数器和其他 CPU 的寄存器以及包括临时数据(例如例程参数、返回地址和保存的变量)的堆栈。当前执行的程序,或者说进程,包括微处理器中所有的当前的活动。 Linux 是一个多进程的操作系统。进程是分离的任务,拥有各自的权利和责任。如果一个进程崩溃,它不应该让系统中的另一个进程崩溃。每一个独立的进程运行在自己的虚拟地址空间,除了通过安全的核心管理的机制之外无法影响其他的进程。

    在一个进程的生命周期中它会使用许多系统资源。它会用系统的 CPU 执行它的指令,用系统的物理内存来存储它和它的数据。它会打开和使用文件系统中的文件,会直接或者间接使用系统的物理设备。 Linux 必须跟踪进程本身和它使用的系统资源以便管理公平地管理该进程和系统中的其他进程。如果一个进程独占了系统的大部分物理内存和 CPU ,对于其他进程就是不公平的。

    系统中最宝贵的资源就是 CPU 。通常系统只有一个。 Linux 是一个多进程的操作系统。它的目标是让进程一直在系统的每一个 CPU 上运行,充分利用 CPU 。如果进程数多于 CPU (多数是这样),其余的进程必须等到 CPU 被释放才能运行。多进程是一个简单的思想:一个进程一直运行,直到它必须等待,通常是等待一些系统资源,等拥有了资源,它才可以继续运行。在一个单进程的系统,比如 DOS , CPU 被简单地设为空闲,这样等待的时间就会被浪费。在一个多进程的系统中,同一时刻许多进程在内存中。当一个进程必须等待时操作系统将 CPU 从这个进程拿走,并将它交给另一个更需要的进程。是调度程序选择了

    下一次最合适的进程。 Linux 使用了一系列的调度方案来保证公平。

    Linux 支持许多不同的可执行文件格式, ELF 是其中之一, Java 是另一个。 Linux 必须透明地管理这些文件,因为进程使用系统的共享的库。

4.1 Linux Processes ( Linux 的进程)

    Linux 中,每一个进程用一个 task_struct (在 Linux 中 task 和 process 互用)的数据结构来表示,用来管理系统中的进程。 Task 向量表是指向系统中每一个 task_struct 数据结构的指针的数组。这意味着系统中最大进程数受 task 向量表的限制,缺省是 512 。当新的进程创建的时候,从系统内存中分配一个新的 task_struct ,并增加到 task 向量表中。为了更容易查找,用 current 指针指向当前运行的进程。

参见 include/linux/sched.h

    除了普通进程, Linux 也支持实时进程。这些进程必须对于外界事件迅速反应(因此叫做“实时”),调度程序必须和普通用户进程区分对待。虽然 task_struct 数据结构十分巨大、复杂,但是它的域可以分为以下的功能:

    State 进程执行时它根据情况改变状态 (state) 。 Linux 进程使用以下状态:(这里漏掉了 SWAPPING ,因为看来没用到)

    Running 进程在运行 ( 是系统的当前进程 ) 或者准备运行(等待被安排到系统的一个 CPU 上)

    Waiting 进程在等待一个事件或资源。 Linux 区分两种类型的等待进程:可中断和不可中断的( interruptible and uninterruptible )。可中断的等待进程可以被信号中断,而不可中断的等待进程直接等待硬件条件,不能被任何情况中断。

    Stopped 进程停止了,通常是接收到了一个信号。正在调试的进程可以在停止状态。

    Zombie 终止的进程,因为某种原因,在 task 向量表重任旧有一个 task_struct 数据结构的条目。就想听起来一样,是一个死亡的进程。

    Scheduling Information 调度者需要这个信息用于公平地决定系统中的进程哪一个更应该运行。

    Identifiers 系统中的每一个进程都有一个进程标识符。进程标识符不是 task 向量表中的索引,而只是一个数字。每一个进程也都有用户和组( user and group )的标识符。用来控制进程对于系统中文件和设备的访问。

    Inter-Process Communication Linux 支持传统的 UNIX-IPC 机制,即信号,管道和信号灯( semaphores ),也支持系统 V 的 IPC 机制,即共享内存、信号灯和消息队列。关于 Linux 支持的 IPC 机制在第 5 章中描述。

    Links 在 Linux 系统中,没有一个进程是和其他进程完全无关的。系统中的每一个进程,除了初始的进程之外,都有一个父进程。新进程不是创建的,而是拷贝,或者说从前一个进程克隆的( cloned )。每一个进程的 task_struct 中都有指向它的父进程和兄弟进程(拥有相同的父进程的进程)以及它的子进程的的指针。在 Linux 系统中你可以用 pstree 命令看到正在运行的进程的家庭关系。

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)

    另外系统中的所有的进程信息还存放在一个 task_struct 数据结构的双向链表中,根是 init 进程。这个表让 Linux 可以查到系统中的所有的进程。它需要这个表以提供对于 ps 或者 kill 等命令的支持。

    Times and Timers 在一个进程的生命周期中,核心除了跟踪它使用的 CPU 时间还记录它的其他时间。每一个时间片( clock tick ),核心更新 jiffies 中当前进程在系统和用户态所花的时间综合。 Linux 也支持进程指定的时间间隔的计数器。进程可以使用系统调用建立计时器,在计时器到期的时候发送信号给自己。这种计时器可以是一次性的,也可是周期性的。

    File system 进程可以根据需要打开或者关闭文件,进程的 task_struct 结构存放了每一个打开的文件描述符的指针和指向两个 VFS I 节点( inode )的指针。每一个 VFS I 节点唯一描述一个文件系统中的一个文件或目录,也提供了对于底层文件系统的通用接口。 Linux 下如何支持文件系统在第 9 章中描述。第一个 I 节点是该进程的根(它的主目录),第二个是它的当前或者说 pwd 目录。 Pwd 取自 Unix 命令:印出工作目录。这两个 VFS 节点本身有计数字段,随着一个或多个进程引用它们而增长。这就是为什么你不能删除一个进程设为工作目录的目录。

    Virtual memory 多数进程都有一些虚拟内存(核心线程和核心守护进程没有), Linux 核心必须知道这些虚拟内存是如何映射到系统的物理内存中的。

    Processor Specific Context 进程可以看作是系统当前状态的总和。只要进程运行,它就要使用处理器的寄存器、堆栈等等。当一个进程暂停的时候,这些进程的上下文、和 CPU 相关的上下文必须保存到进程的 task_struct 结构中。当调度者重新启动这个进程的时候,它的上下文就从这里恢复。

4.2 Identifiers (标识)

    Linux ,象所有的 Unix ,使用用户和组标识符来检查对于系统中的文件和映像的访问权限。 Linux 系统中所有的文件都有所有权和许可,这些许可描述了系统对于该文件或目录拥有什么样的权限。基本的权限是读、写和执行,并分配了 3 组用户:文件属主、属于特定组的进程和系统中的其他进程。每一组用户都可以拥有不同的权限,例如一个文件可以让它的属主读写,它的组读,而系统中的其他进程不能访问。

    Linux 使用组来给一组用户赋予对文件或者目录的权限,而不是对系统中的单个用户或者进程赋予权限。比如你可以为一个软件项目中的所有用户创建一个组,使得只有他们才能够读写项目的源代码。一个进程可以属于几个组(缺省是 32 个),这些组放在每一个进程的 task_struct 结构中的 groups 向量表中。只要进程所属的其中一个组对于一个文件有访问权限,则这个进程就又对于这个文件的适当的组权限。

一个进程的 task_struct 中有 4 对进程和组标识符。

Uid,gid 该进程运行中所使用的用户的标识符和组的标识符

    Effective uid and gid 一些程序把执行进程的 uid 和 gid 改变为它们自己的(在 VFS I 节点执行映像的属性中)。这些程序叫做 setuid 程序。这种方式有用,因为它可以限制对于服务的访问,特别是那些用其他人的方式运行的,例如网络守护进程。有效的 uid 和 gid 来自 setuid 程序,而 uid 和 gid 仍旧是原来的。核心检查特权的时候检查有效 uid 和 gid 。

    File system uid and gid 通常和有效 uid 和 gid 相等,检查对于文件系统的访问权限。用于通过 NFS 安装的文件系统。这时用户态的 NFS 服务器需要象一个特殊进程一样访问文件。只有文件系统 uid 和 gid 改变(而非有效 uid 和 gid )。这避免了恶意用户向 NFS 的服务程序发送 Kill 信号。 Kill 用一个特别的有效 uid 和 gid 发送给进程。

    Saved uid and gid 这是 POSIX 标准的要求,让程序可以通过系统调用改变进程的 uid 和 gid 。用于在原来的 uid 和 gid 改变之后存储真实的 uid 和 gid 。

4.3 Scheduling (调度)

    所有的进程部分运行与用户态,部分运行于系统态。底层的硬件如何支持这些状态各不相同但是通常有一个安全机制从用户态转入系统态并转回来。用户态比系统态的权限低了很多。每一次进程执行一个系统调用,它都从用户态切换到系统态并继续执行。这时让核心执行这个进程。 Linux 中,进程不是互相争夺成为当前运行的进程,它们无法停止正在运行的其它进程然后执行自身。每一个进程在它必须等待一些系统事件的时候会放弃 CPU 。例如,一个进程可能不得不等待从一个文件中读取一个字符。这个等待发生在系统态的系统调用中。进程使用了库函数打开并读文件,库函数又执行系统调用从打开的文件中读入字节。这时,等候的进程会被挂起,另一个更加值得的进程将会被选择执行。进程经常调用系统调用,所以经常需要等待。即使进程执行到需要等待也有可能会用去不均衡的 CPU 事件,所以 Linux 使用抢先式的调度。用这种方案,每一个进程允许运行少量一段时间, 200 毫秒,当这个时间过去,选择另一个进程运行,原来的进程等待一段时间直到它又重新运行。这个时间段叫做时间片。

    需要调度程序选择系统中所有可以运行的进程中最值得的进程。一个可以运行的进程是一个只等待 CPU 的进程。 Linux 使用合理而简单的基于优先级的调度算法在系统当前的进程中进行选择。当它选择了准备运行的新进程,它就保存当前进程的状态、和处理器相关的寄存器和其他需要保存的上下文信息到进程的 task_struct 数据结构中。然后恢复要运行的新的进程的状态(又和处理器相关),把系统的控制交给这个进程。为了公平地在系统中所有可以运行( runnable )的进程之间分配 CPU 时间,调度程序在每一个进程的 task_struct 结构中保存了信息:

参见 kernel/sched.c schedule()

    policy 进程的调度策略。 Linux 有两种类型的进程:普通和实时。实时进程比所有其它进程的优先级高。如果有一个实时的进程准备运行,那么它总是先被运行。实时进程有两种策略:环或先进先出( round robin and first in first out )。在环的调度策略下,每一个实时进程依次运行,而在先进先出的策略下,每一个可以运行的进程按照它在调度队列中的顺序运行,这个顺序不会改变。

    Priority 进程的调度优先级。也是它允许运行的时候可以使用的时间量( jiffies )。你可以通过系统调用或者 renice 命令来改变一个进程的优先级。

    Rt_priority Linux 支持实时进程。这些进程比系统中其他非实时的进程拥有更高的优先级。这个域允许调度程序赋予每一个实时进程一个相对的优先级。实时进程的优先级可以用系统调用来修改

    Coutner 这时进程可以运行的时间量( jiffies )。进程启动的时候等于优先级( priority ),每一次时钟周期递减。

    调度程序从核心的多个地方运行。它可以在把当前进程放到等待队列之后运行,也可以在系统调用之后进程从系统态返回进程态之前运行。需要运行调度程序的另一个原因是系统时钟刚好把当前进程的计数器 (counter) 置成了 0 。每一次调度程序运行它做以下工作:

参见 kernel/sched.c schedule()

kernel work 调度程序运行 bottom half handler 并处理系统的调度任务队列。这些轻量级的核心线程在第 11 章详细描述

Current pocess 在选择另一个进程之前必须处理当前进程。

如果当前进程的调度策略是环则它放到运行队列的最后。

如果任务是可中断的而且它上次调度的时候收到过一个信号,它的状态变为 RUNNING

如果当前进程超时,它的状态成为 RUNNING

如果当前进程的状态为 RUNNING 则保持此状态

不是 RUNNING 或者 INTERRUPTIBLE 的进程被从运行队列中删除。这意味着当调度程序查找最值得运行的进程时不会考虑这样的进程。

    Process Selection 调度程序查看运行队列中的进程,查找最值得运行的进程。如果有实时的进程(具有实时调度策略),就会比普通进程更重一些。普通进程的重量是它的 counter ,但是对于实时进程则是 counter 加 1000 。这意味着如果系统中存在可运行的实时进程,就总是在任何普通可运行的进程之前运行。当前的进程,因为用掉了一些时间片(它的 counter 减少了),所以如果系统中由其他同等优先级的进程,就会处于不利的位置:这也是应该的。如果几个进程又同样的优先级,最接近运行队列前段的那个就被选中。当前进程被放到运行队列的后面。如果一个平衡的系统,拥有大量相同优先级的进程,那么回按照顺序执行这些进程。这叫做环型调度策略。不过,因为进程需要等待资源,它们的运行顺序可能会变化。

    Swap Processes 如果最值得运行的进程不是当前进程,当前进程必须被挂起,运行新的进程。当一个进程运行的时候它使用了 CPU 和系统的寄存器和物理内存。每一次它调用例程都通过寄存器或者堆栈传递参数、保存数值比如调用例程的返回地址等。因此,当调度程序运行的时候它在当前进程的上下文运行。它可能是特权模式:核心态,但是它仍旧是当前运行的进程。当这个进程要挂起时,它的所有机器状态,包括程序计数器 (PC) 和所有的处理器寄存器,必须存到进程的 task_struct 数据结构中。然后,必须加载新进程的所有机器状态。这种操作依赖于系统,不同的 CPU 不会完全相同地实现,不过经常都是通过一些硬件的帮助。

    交换出去进程的上下文发生在调度的最后。前一个进程存储的上下文,就是当这个进程在调度结束的时候系统的硬件上下文的快照。相同的,当加载新的进程的上下文时,仍旧是调度结束时的快照,包括进程的程序计数器和寄存器的内容。

    如果前一个进程或者新的当前进程使用虚拟内存,则系统的页表需要更新。同样,这个动作适合体系结构相关。 Alpha AXP 处理器,使用 TLT ( Translation Look-aside Table )或者缓存的页表条目,必须清除属于前一个进程的缓存的页表条目。

4.3.1 Scheduling in Multiprocessor Systems (多处理器系统中的调度)

    在 Linux 世界中,多 CPU 系统比较少,但是已经做了大量的工作使 Linux 成为一个 SMP (对称多处理)的操作系统。这就是,可以在系统中的 CPU 之间平衡负载的能力。负载均衡没有比在调度程序中更重要的了。

    在一个多处理器的系统中,希望的情况是:所有的处理器都繁忙地运行进程。每一个进程都独立地运行调度程序直到它的当前的进程用完时间片或者不得不等待系统资源。 SMP 系统中第一个需要注意的是系统中可能不止一个空闲( idle )进程。在一个单处理器的系统中,空闲进程是 task 向量表中的第一个任务,在一个 SMP 系统中,每一个 CPU 都有一个空闲的进程,而你可能有不止一个空闲 CPU 。另外,每一个 CPU 有一个当前进程,所以 SMP 系统必须记录每一个处理器的当前和空闲进程。

    在一个 SMP 系统中,每一个进程的 task_struct 都包含进程当前运行的处理器编号( processor )和上次运行的处理器编号( last_processor )。为什么进程每一次被选择运行时不要在不同的 CPU 上运行是没什么道理的,但是 Linux 可以使用 processor_mask 把进程限制在一个或多个 CPU 上。如果位 N 置位,则该进程可以运行在处理器 N 上。当调度程序选择运行的进程的时候,它不会考虑 processor_mask 相应位没有设置的进程。调度程序也会利用上一次在当前处理器运行的进程,因为把进程转移到另一个处理器上经常会有性能上的开支。



4.4 Files (文件)

    图 4.1 显示了描述系统每一个进程中的用于描述和文件系统相关的信息的两个数据结构。第一个 fs_struct 包括了这个进程的 VFS I 节点和它的 umask 。 Umask 是新文件创建时候的缺省模式,可以通过系统调用改变。

参见 include/linux/sched.h

    第二个数据结构, files_struct ,包括了进程当前使用的所有文件的信息。程序从标准输入读取,向标准输出写,错误信息输出到标准错误。这些可以是文件,终端输入 / 输出或者世纪的设备,但是从程序的角度它们都被看作是文件。每一个文件都有它的描述符, files_struct 包括了指向 256 个 file 数据结果,每一个描述进程形用的文件。 F_mode 域描述了文件创建的模式:只读、读写或者只写。 F_pos 记录了下一次读写操作在文件中的位置。 F_inode 指向描述该文件的 I 节点, f_ops 是指向一组例程地址的指针,每一个地址都是一个用于处理文件的函数。例如写数据的函数。这种抽象的接口非常强大,使得 Linux 可以支持大量的文件类型。我们可以看到,在 Linux 中 pipe 也是用这种机制实现的。
分享到: QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏0 顶0 踩0

发表回复

高级模式
B Color Image Link Quote Code Smilies
验证码 换一个

回顶部