📄 多进程2.txt
字号:
每次进程试图向写入队列写入消息时,系统将把其有效用户和组标志符与此队列的ipc_perm结构中的模式进行比较。如果允许写入操作,则把此消息从此进程的地址空间拷贝到msg数据结构中,并放置到此消息队列尾部。由于 Linux严格限制可写入消息的个数和长度,队列中可能容纳不下这个消息。此时,此写入进程将被添加到这个消息队列的等待队列中,同时调用调度管理器选择新进程运行。当由消息从此队列中释放时,该进程将被唤醒。
从队列中读的过程与之类似。进程对这个写入队列的访问权限将被再次检验。读取进程将选择队列中第一个消息(不管是什么类型)或者第一个某特定类型的消息。如果没有消息可以满足此要求,读取进程将被添加 到消息队列的读取等待队列中,然后系统运行调度管理器。当有新消息写入队列时,进程将被唤醒继续执行。
5.3.3 信号灯
信号灯最简单的形式是某个可以被多个进程检验和设置(test&set)的内存单元。这个检验与设置操作对每个进程而言是不可中断或者说是一个原子性操作;一旦启动谁也终止不了。检验与设置操作的结果是信号灯当前值加1, 这个值可以是正数也可以是负数。根据这个操作的结果,进程可能可以一直睡眠到此信号灯的值被另一个进程更改为止。信号灯可用来实现临界区(critical region):某一时刻在此区域内的代码只能被一个进程执行。
如果你有多个协作进程从一个数据文件中读取与写入记录。有时你可能需要这些文件访问遵循严格的访问次序。 那么可在文件操作代码上使用一个初始值为1的信号灯,它带有两个信号灯操作,一个检验并对信号灯 值减1,而另一个检验并加1。第一个访问文件的进程将试图将信号灯值减1,如果获得成功则信号灯值变成了 0。此进程于是开始使用这个数据文件,但是此时如果另一进程也想将信号灯值减1,则信号灯值将为-1,这次操作将会失败。它将挂起执行直到第一个进程完成对此数据文件的使用。此时这个等待进程将被唤醒,这次它对信号灯的操作将成功。
图5.3 系统V IPC信号灯
每个系统V IPC信号灯对象对应一个信号灯数组,Linux使用semid_ds结构来表示。系统中所有semid_ds结构由一组semary指针来指示。在每个信号灯数组中有一个sem_nsems,它表示一个由sem_base指向的sem结构。授权的进程可以使用系统调用来操纵这些包含系统V IPC信号灯对象的信号灯数组。这个系统调用可以定义许多种操作,每个操作用三个输入来描叙:信号灯索引、操作值和一组标志。信号灯索引是一个信号灯数组的索引,而操作值是将被加到信号灯上的数值。首先Linux将检查是否所有操作已经成功。如果操作值与信号灯当前数值相加大于0,或者操作值与信号灯当前值都是0,操作将会成功。如果所有信号灯操作失败,Linux仅仅会把那些操作标志没有要求系统调用为非阻塞类型的进程挂起。进程挂起后,Linux必须保存信号灯操作的执行状态并将当前进程放入等待队列。系统还在堆栈上建立sem_queue结构并填充各个域。这个sem_queue结构将被放到此信号灯对象等待队列的尾部(使用 sem_pending和sem_pending_last指针)。系统把当前进程置入sem_queue结构中的等待队列(sleeper)中,然后启动调度管理器选择其它进程运行。
如果所有这些信号灯操作都成功则无需挂起当前进程,Linux将对信号灯数组中的其他成员进行相同操作,然后检查那些处于等待或者挂起状态的进程。首先,Linux将依次检查挂起队列(sem_pending) 中的每个成员,看信号灯操作能否继续。如果可以则将其sem_queue结构从挂起链表中删除并对信号灯数组发出信号灯操作。Linux还将唤醒处于睡眠状态的进程并使之成为下一个运行的进程。如果在对挂起队列的遍历过程中有的信号灯操作不能完成则Linux将一直重复此过程,直到所有信号灯操作完成且没有进程需要继续睡眠。
但是信号灯的使用可能产生一个严重的问题:死锁。当一个进程进入临界区时它改变了信号灯的值而离开临界区时由于运行失败或者被kill而没有改回信号灯时,死锁将会发生。Linux通过维护一组描叙信号灯数组变化的链表来防止该现象的发生。它的具体做法是让Linux将把此信号灯设置为进程对其进行操作前的状态。这些状态值被保存在使用该信号灯数组进程的semid_ds和task_struct结构的sem_undo结构中。
信号灯操作将迫使系统对它引起的状态变化进行维护。Linux为每个进程维护至少一个对应于信号灯数组的sem_undo结构。如果请求进行信号灯操作的进程没有该结构,则必要时Linux会为其创建一个。这个sem_undo 结构将同时放入此进程的task_struct结构和此信号灯数组的semid_ds结构中。当对信号灯进行操作时,信号灯变化值的负数被置入进程的sem_undo结构中该信号的入口中。所以当操作值为2时,则此信号灯的调整入口中将加入一个-2。
象正常退出一样,当进程被删除时,Linux将遍历该进程的sem_undo集合对信号灯数组使用调整值。如果信号灯集合被删除而sem_undo数据结构还在进程的task_struct结构中则此信号灯数组标志符将被置为无效。此时 信号灯清除代码只需丢弃sem_undo结构即可。
5.3.4 共享内存
共享内存允许一个或多个进程通过同时出现在它们虚拟地址空间中的内存来通讯。此虚拟内存的页面出现在每个共享进程页表中。但此页面并不一定位于所有共享进程虚拟内存的相同位置。和其它系统V IPC对象的使用方法一样,对共享内存区域的访问是通过键和访问权限检验来控制的。一旦内存被共享,则再不会检验进程对对象的使用方式。它依赖于其它机制,如系统V信号灯,来同步对共享内存的访问。
图5.4 系统V IPC共享内存
每个新创建的共享内存区域由一个shmid_ds数据结构来表示。它们被保存再shm_segs数组中。 shmid_ds数据结构描叙共享内存的大小,进程如何使用以及共享内存映射到其各自地址空间的方式。由共享内存创建者控制对此内存的存取权限以及其键是公有还是私有。如果它由足够权限,它还可以将此共享内存加载到物理内存中。
每个使用此共享内存的进程必须通过系统调用将其连接到虚拟内存上。这时进程创建新的vm_area_struct来描叙此共享内存。进程可以决定此共享内存在其虚拟地址空间的位置,或者让Linux选择一块足够大的区域。 新的vm_area_struct结构将被放到由shmid_ds指向的vm_area_struct链表中。通过vm_next_shared和vm_prev_shared 指针将它们连接起来。虚拟内存在连接时并没有创建;进程访问它时才创建。
当进程首次访问共享虚拟内存中的页面时将产生页面错。当取回此页面后,Linux找到了描叙此页面的vm_area_struct数据结构。它包含指向使用此种类型虚拟内存的处理函数地址指针。共享内存页面错误处理 代码将在此shmid_ds对应的页表入口链表中寻找是否存在此共享虚拟内存页面。如果不存在,则它将分配物理页面并为其创建页表入口。同时还将它放入当前进程的页表中,此入口被保存在shmid_ds结构中。这意味着下个试图访问此内存的进程还会产生页面错误,共享内存错误处理函数将为此进程使用其新创建的物理页面。这样,第一个访问虚拟内存页面的进程创建这块内存,随后的进程把此页面加入到各自的虚拟地址空间中。
当进程不再共享此虚拟内存时,进程和共享内存的连接将被断开。如果其它进程还在使用这个内存,则此操作只影响当前进程。其对应的vm_area_struct结构将从shmid_ds结构中删除并回收。当前进程对应此共享内存地址的页表入口也将被更新并置为无效。当最后一个进程断开与共享内存的连接时,当前位于物理内存中的共享内存页面将被释放,同时还有此共享内存的shmid_ds结构。
当共享内存没有被锁入物理内存时,情况将更加复杂。此时共享内存页面可能会在内存使用高峰期,被交换到系统的交换磁盘上。共享内存如何被交换与调入物理内存将在mm一章中详细描叙。
======================================================================
Linux 核心--5.Linux进程
原著: David A Rusling 翻译: Banyan & fifa (2001-04-27 13:55:46)
第四章 进程管理
本章重点讨论Linux内核如何在系统中创建、管理以及删除进程。
进程在操作系统中执行特定的任务。而程序是存储在磁盘上包含可执行机器指令和数据的静态实体。进程或者任务是处于活动状态的计算机程序。
进程是一个随执行过程不断变化的实体。和程序要包含指令和数据一样,进程也包含程序计数器和所有CPU寄存器的值,同时它的堆栈中存储着如子程序参数、返回地址以及变量之类的临时数据。当前的执行程序,或者说进程,包含着当前处理器中的活动状态。Linux是一个多处理操作系统。进程具有独立的权限与职责。如果系统中某个进程崩溃,它不会影响到其余的进程。每个进程运行在其各自的虚拟地址空间中,通过核心控制下可靠的通讯机制,它们之间才能发生联系。
进程在生命期内将使用系统中的资源。它利用系统中的CPU来执行指令,在物理内存来放置指令和数据。使用文件系统提供的功能打开并使用文件,同时直接或者间接的使用物理设备。Linux必须跟踪系统中每个进程以及资源,以便在进程间实现资源的公平分配。如果系统有一个进程独占了大部分物理内存或者CPU的使用时间,这种情况对系统中的其它进程是不公平的。
系统中最宝贵的资源是CPU,通常系统中只有一个CPU。Linux是一个多处理操作系统,它最终的目的是:任何时刻系统中的每个CPU上都有任务执行,从而提高CPU的利用率。如果进程个数多于CPU的个数,则有些进程必须等待到CPU空闲时才可以运行。多处理是的思路很简单;当进程需要某个系统资源时它将停止执行并等待到资源可用时才继续运行。单处理系统中,如DOS,此时CPU将处于空等状态,这个时间将被浪费掉。在多处理系统中,因为可以同时存在多个进程,所以当某个进程开始等待时,操作系统将把CPU控制权拿过来并交给其它可以运行的进程。调度器负责选择适当的进程来运行,Linux使用一些调度策略以保证CPU分配的公平性。
Linux支持多种类型的可执行文件格式,如ELF,JAVA等。由于这些进程必须使用系统共享库,所以对它们的管理要具有透明性。
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数组的索引,它仅仅是个数字。每个进程还有一个用户与组标志,它们用来控制进程对系统中文件和设备的存取权限。
Inter-Process Communication
Linux支持经典的Unix IPC机制,如信号、管道和信号灯以及系统V中IPC机制,包括共享内存、信号灯和消息队列。我们将在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结构中有四对进程和组标志符:
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -