Linux进程管理代码分析_第1页
Linux进程管理代码分析_第2页
Linux进程管理代码分析_第3页
Linux进程管理代码分析_第4页
Linux进程管理代码分析_第5页
已阅读5页,还剩34页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、Linux 进程管理代码分析概念:进程执行操作系统的任务。程序是存放在磁盘上的包括一系列机器代码指令和数据的可 执行的映像, 因此是一个被动的实体。 进程可以看作是一个执行中的计算机程序。 它是动态 的实体, 在处理机执行机器代码时不断改变。 进程包括处理程序的指令和数据, 以及程序计 数器、其他 CPU的寄存器和包括临时数据(例如:例程参数、返回地址和保存的变量)的堆 栈。当前执行的程序,或者说进程, 包括微处理器中所有的当前活动。 进程是操作系统的最 小调度单位。LINUX 系统是分时多用户系统 , 它有多进程系统的特点, CPU按时间片分配给各个用户 使用 , 而在实质上应该说 CPU按

2、时间片分配给各个进程使用 , 每个进程都有自己的运行环境 以使得在 CPU做进程切换时保存该进程已计算了一半的状态。进程的切换包括三个层次 :(1)用户数据的保存 : 包括正文段 (TEXT), 数据段 (DATA,BSS), 栈段 (STACK), 共享内 存段 (SHARED MEMOR的Y)保存。(2)寄存器数据的保存 : 包括 PC(program counter, 指向下一条要执行的指令的地址 ), PSW(processor status word, 处 理 机 状 态 字 ), SP(stack pointer, 栈 指 针 ), PCBP(pointer of process

3、control block,进程控制块指针 ), FP(frame pointer,指向栈中一个函数的 local 变量的首地址 ), AP(augument pointer, 指向栈中函数调 用的实参位置 ), ISP(interrupt stack pointer,中断栈指针 ), 以及其他的通用寄存器等。(3)系统层次的保存 : 包括 proc,u, 虚拟存储空间管理表格 , 中断处理栈。以便于该进 程再一次得到 CPU时间片时能正常运行下去。多进程系统的一些突出的特点 :并行化一件复杂的事件是可以分解成若干个简单事件来解决的 , 这在程序员的大脑中早就形成 了这种概念 , 首先将问题分

4、解成一个个小问题 , 将小问题再细分 , 最后在一个合适的规模上 做成一个函数。 在软件工程中也是这么说的。如果我们以图的方式来思考 , 一些小问题的 计算是可以互不干扰的 , 可以同时处理 , 而在关键点则需要统一在一个地方来处理 , 这样程 序的运行就是并行的 , 至少从人的时间观念上来说是这样的。 而每个小问题的计算又是较 简单的。, 程序员为每个进程设计好相应的功能 , 并 , 对每个进程的设计是简单的 , 只在总控部分简单有序这样的程序对程序员来说不亚于管理一班人 通过一定的通讯机制将它们有机地结合在一起 小心应付 ( 其实也是蛮简单的 ), 就可完成整个程序的施工。互不干扰这个特点

5、是操作系统的特点 , 各个进程是独立的 , 不会串位。即完成, 主控, 只要事务化比如在一个数据电话查询系统中 , 将程序设计成一个进程只处理一次查询即可 , 一个事务。当电话查询开始时 , 产生这样一个进程对付这次查询 ; 另一个电话进来时 程序又产生一个这样的进程对付 , 每个进程完成查询任务后消失 . 这样的编程多简单第 1 页 共 25 页做一次查询的程序就可以了。Linux 是一个多进程的操作系统,进程是分离的任务,拥有各自的权利和责任。如果一 个进程崩溃, 它不应该让系统的另一个进程崩溃。 每一个独立的进程运行在自己的虚拟地址 空间,除了通过安全的核心管理的机制之外无法影响其他的进

6、程。在一个进程的生命周期中,进程会使用许多系统资源。比如利用系统的CPU执行它的指令,用系统的物理内存来存储它和它的数据。 它会打开和使用文件系统中的文件, 会直接或 者间接使用系统的物理设备。如果一个进程独占了系统的大部分物理内存和CPU,对于其他进程就是不公平的。 所以 Linux 必须跟踪进程本身和它使用的系统资源以便公平地管理系统 中的进程。系统最宝贵的资源就是 CPU。通常系统只有一个 CPU。Linux 作为一个多进程的操作系统, 它的目标就是让进程在系统的 CPU上运行,充分利用 CPU。如果进程数多于 CPU(一般情况 都是这样),其他的进程就必须等到 CPU被释放才能运行。多

7、进程的思想就是:一个进程一 直运行,直到它必须等待,通常是等待一些系统资源,等拥有了资源,它才可以继续运行。 在一个单进程的系统中,比如 DOS, CPU被简单地设为空闲,这样等待资源的时间就会被浪 费。而在一个多进程的系统中, 同一时刻许多进程在内存中, 当一个进程必须等待时, 操作 系统将 CPU从这个进程切换到另一个更需要的进程。我们组分析的是 Linux 进程的状态转换以及标志位的作用,它没有具体对应某个系统调 用,而是分布在各个系统调用中。 所以我们详细而广泛地分析了大量的原码, 对进程状态转 换的原因、方式和结果进行了分析, 大致总结了整个 Linux 系统对进程状态管理的实现机制

8、。Linux 中,每个进程用一个 task_struct 的数据结构来表示,用来管理系统中的进程。 Task 向量表是指向系统中每一个 task_struct 数据结构的指针的数组。这意味着系统中的 最大进程数受到 Task 向量表的限制,缺省是 512。这个表让 Linux 可以查到系统中的所有 的进程。操作系统初始化后,建立了第一个 task_struct 数据结构 INIT_TASK。当新的进程 创建时,从系统内存中分配一个新的 task_struct ,并增加到 Task 向量表中。为了更容易 查找,用 current 指针指向当前运行的进程。task_struct 结构中有关于进程调

9、度的两个重要的数据项: struct task_struct volatile long state; unsigned long flags;/* -1 unrunnable , 0 runnable , 0 stopped */* per process flags, defined below */是进行进程调度的重 会由于某些原因改变自身的状态和标志, 也 这两个数据项。 进程的状态不同、 标志位不同对应了进程可以执行 版本的 sched.h 中定义了六种状态,十三种标志。 ;每个在 Task 向量表中登记的进程都有相应的进程状态和进程标志, 要依据。 进程在执行了相应的进程调度操作后

10、, 就是改变 state 和 flags 不同操作。在 Linux2.2.8 / 进程状态 #define TASK_RUNNING #define TASK_INTERRUPTIBLE 1 #define TASK_UNINTERRUPTIBLE2 #define TASK_ZOMBIE第 2 页 共 25 页#define TASK_STOPPED 8#define TASK_SWAPPING16它们的含义分别是:TASK_RUNNIN:G正在运行的进程(是系统的当前进程)或准备运行的进程(在 队列中,等待被安排到系统的 CPU)。处于该状态的进程实际参与了进程调度。RunningTASK

11、_INTERRUPTIBL:E处于等待队列中的进程, 待资源有效时唤醒, 也可由其它进程被 信号中断、唤醒后进入就绪状态。TASK_UNINTERRUPTIB:LE处于等待队列中的进程, 直接等待硬件条件, 待资源有效时唤 醒,不可由其它进程通过信号中断、唤醒。TASK_ZOMBI:E 终止的进程,是进程结束运行前的一个过度状态 已经释放了内存、 文件等资源, 但是在 Task向量表中仍有一个 它不进行任何调度或状态转换,等待父进程将它彻底释放。 TASK_STOPPE:D进程被暂停, 通过其它进程的信号才能唤醒。 停止状态。(僵死状态) 。虽然此时task_struct 数据结构项。正在调试

12、的进程可以在该TASK_SWAPPIN:G进程页面被兑换出内存的进程。这个状态基本上没有用到,只有在 sched.c 的 count_active_tasks ()函数中判断处于该种状态的进程也属于 active 的进 程,但没有对该状态的赋值。/ 进程标志位:#define PF_ALIGNWARN0 x00000001#define PF_STARTING 0x00000002#define PF_EXITING 0x00000004#define PF_PTRACED 0x00000010#define PF_TRACESYS 0x00000020#define PF_FORKNOEXE

13、C 0x00000040 #define PF_SUPERPRIV 0x00000100#define PF_DUMPCORE 0x00000200 #define PF_SIGNALED 0x00000400#define PF_MEMALLOC 0x00000800 #define PF_VFORK 0x00001000#define PF_USEDFPU 0x00100000#define PF_DTRACE 0x00200000其中 PF_STARTING没有用到。PF_MEMEALLO和C PF_VFORK这两个标志位是新版本中才有的。各个标志位的代表着不同含义,对应着不同调用:1、

14、2、3、PF_ALIGNWARN 标志打印“对齐”警告信息,只有在PF_STARTINGPF_EXITING在 do_exit() 时置位。进程正被创建 标志进程开始关闭。486 机器上实现第 3 页 共 25 页current-flags |= PF_EXITING 用于判断是否有效进程。 在 nlmclnt_proc()( 在 fslockdclntproc.c) ,如果 current_flag 为 PF_EXITING, 则进程由于正在退出清除所有的锁,将执行异步 RPC 调用。4、PF_PTRACED进程被跟踪标志,在 do_fork() 时清位。p-flags &= PF_PTRA

15、CED当 ptrace(0) 被调用时置位,在进程释放前要清掉。 current-flags |= PF_PTRACEDrequest 为 PTRACE_TRACE,ME如是则将 current_flag 置为 PF_PTRACE;D request 为 PTRACE_ATTAC,H则将 child_flag 置为 PF_PTRACE,D给 child 发 SIGSTOP信号 ;request 为 PTRACE_DETAC,H 则将 child 清除 PF_PTRACE。D 中判断 current_flag 如果为 PF_TRACED和 PF_TRACESYS则, SIGTRAP并将状态置为

16、STOPPE。D在 sys_trace() 中判断 如果 如果 一个 如果在 syscall_trace() current 强行退出时的出错代码置为5、PF_TRACESYS正在跟踪系统调用。do_fork() 时清位,在进程释放前要清掉。 在 sys_trace() 中判断 request 如果为 PTRACE_SYSCA,LL则将 child-flags 置为 PF_TRACESY;S如为 PTRACE_SYSCA,LL则将 child-flags 清除 PF_TRACESY;S 然 后唤醒 child 。如果 request 为 PTRACE_SINGLEST(E即P 单步跟踪) ,则

17、将 child_flag 清除 PF_TRACESYS唤, 醒 child 。6、PF_FORKNOEXEC 进 程刚创建,但还没执行。 在 do_fork() 时置位。p-flags |= PF_FORKNOEXEC 在调入格式文件时清位。p-flags &= PF_FORKNOEXEC7、PF_SUPERPRIV超级用户特权标志。如果是超级用户进程则置位, 用户特权设为超级用户, 如是超级用户, 在统计时置 统计标志 (accounting flag)为 ASU。8、PF_DUMPCORE标志进程是否清空 core 文件。Core 文件由 gdb 进行管理,给用户提供有用信息,例如查看浮点

18、寄存器的内容比 较困难,事实上我们可以从内核文件里的用户结构中得到Core 文件格式如下图:UPAGEDATASTACKCore 文件结构UPAGE 是包含用户结构的一个页面,告诉 gdb 文件中现有内容所有寄存器也在 UPAGE中,通常只有一页。 DATA存放数据区。 STACK堆栈区最小 Core 文件长度为三页( 12288 字节)在 task_struct 中定义一个 dumpable 变量,当 dumpable=1 时表示进程可以清空 core 文件(即将 core 文件放入回收站) ,等于 0 时表示该进程不能清空 core 文件 (即 core 文件以放在回收站中,不可再放到回收

19、站中) ,此变量初值为 1。例如在调用 do aout core dump() 时判断 current dumpable 是否等于 1(即判断第 4 页 共 25 页该进程是否能将 core 文件放入回收站) ,如果等于 1 则将该变量置为 0,在当前目 录下建立一个 core dump image , 在清空用户结构前,由 gdb 算出数据段和堆栈段 的位置和使用的虚地址, 用户数据区和堆栈区在清空前将相应内容写入core dump,将 PF_DUMPCOR置E位,清空数据区和堆栈区。只 有 在 aout_core_dump() 内 调 用 do_aout_core_dump(), 而 没

20、有 地 方 调 用 aout_core_dump() 。对其它文件格式也是类似。9、PF_SIGNALED标志进程被信号杀出。在 do_signal() 中判断信号,如果 current 收到信号为 SIGHUP, SIGINT, SIGIOT, SIGKILL, SIGPIPE, SIGTERM, SIGALRM, SIGSTKFLT, SIGURG, SIGXCPU, SIGXFSZ, SIGVTALRM,S IGPROF, SIGIO, SIGPOLL, SIGLOST, SIGPWR,则执行 lock_kernel(), 将信号加入 current 的信号队列,将 current-fl

21、ag 置为 PF_SIGNALED然, 后执行 do_exit()10、PF_USEDFPU标志该进程使用 FPU,此标志只在 SMP时使用。在 task_struct 中有一变量 used_math ,进程是否使用 FPU。 在 CPU从 prev 切换到 next 时,如果 prev 使用 FPU则 prev 的 flag 清除 PF_USEDFP。U prev-flags&=PF_USEDFPU、 restore_i387_hard() 、 中,如果是 SMP方式,且使用 FPU在 flush_thread()(archi386kernelprocess.c) save_i387_har

22、d()(archi386kernelsignal.c) 则 stts() ,否则清除 PF_USEDFP。U current-flags &= PF_USEDFPU在 sys_trace() 中如果 request 为 PTRACE_SETFPRE,GS则将 child 的 used_math 置为 1,将 child_flag 清除 PF_USEDFP。Uchild-flags &= PF_USEDFPU 在 SMP方式下进行跟踪时,判断是否使用FPU。在跟踪时出现数学错误时清位。m68k下使用。current-flags &= PF_USEDFPU11、PF_DTRACE进程延期跟踪标志,

23、只在跟踪一个 trapping 指令时置位。current-flags |= PF_DTRACE12、PF_ONSIGSTK标志进程是否工作在信号栈,只在m68k方式下使用。liunx 2.1.19 版本中使用此标志位,而 2.2.8 版本中不使用。在处理信号建立 frame 时如果 sigaction 标志为 ONSTAC,K则将 current-flag 置 为 PF_ONSIGST。K13、PF_MEMALLOC 进程分配内存标志。 linux 2.2.8 版本中使用此标志位。 在 kpiod() 和 kwpad() 中置位。tsk-flags |= PF_MEMALLOC14、PF_V

24、FORKlinux 2.2.8版本中使用此标志位。,如果在 copy_flags(unsigned long clone_flags, struct task_struct *p) clone_flags 为 CLONE_VFOR,K则将 p 的 flags 置为 PF_VFOR。K 在 mm_release ()中将 current -flags 清除 PF_VFOR。K tsk-flags &= PF_VFORK 具体的分析由我组的另外同学进行。第 5 页 共 25 页Linux 的各进程之间的状态转换的系统调用我将参与 Linux 的各进程之间的状态转换的系统调用总结成一张流程图:sys

25、_exit() do exit()TASKinterruptible_sleep_on()interruptible_sleep_on_timeou_down_interruptible()t()OMBLEsleep_on()_down()wait_on_.()syscall_trace() do_signal()schedule()timer 时间到do_fork()创建进程TASK_RUNSIG_KILL|SIG_CONTrowake_up()wake_up_process()wake_up_interruptible()_up()waNkeI_NupG_prowake_up() wake

26、_up_process()_up()cess()TASK_STOPPEDTASK_INTERRUP T I B WLEAITING STATUSTASK_UNINTERRUPTIBLE进程的创建: TASK_RUNNING当系统启动的时候它运行在核心态, 这时, 只有一个进 寄存器等等表示的机器状 task_struct 数据结构 init )然后执行空闲循第一个进程在系统启动时创建, 程:初始化进程。象所有其他进程一样, 初始进程有一组用堆栈、 态。当系统中的其他进程创建和运行的时候这些信息存在初始进程的 中。在系统初始化结束的时候,初始进程启动一个核心进程(叫做 环,什么也不做。当没有什么

27、可以做的时候, 调度程序会运行这个空闲的进程。这个空闲进 程的 task_struct 是唯一一个不是动态分配而是在核心连接的时候静态定义的, 为了不至于 混淆,叫做 init_task 。系统调用 sys_fork 和 sys_clone 都调用函数 do_fork ()(在 kernel/fork. 中定义)。 进程由 do_fork() 函数创建,先申请空间,申请核心堆栈;然后在 Task 向量表中找到空 闲位置;在进行正式初始化以前,将新创建的进程的状态都置为TASK_UNINTERRUPTIBL,E以免初始化过程被打断;开始初始化工作,如初始化进程时钟、信号、时间等数据;继承父 进程

28、的资源, 如文件、信号量、内存等;完成进程初始化后 ,由父进程调用 wake_up_process() 函数将其唤醒,状态变为 TASK_RUNNIN,G挂到就绪队列 run queue ,返回子进程的 pid 。第 6 页 共 25 页/ C:SRCLNXKERNELFORK.Cint do_fork(unsigned long clone_flags, unsigned long usp, struct pt_regs *regs) 为新进程申请 PCB空间 ; if ( 申请不到 )返回错误 ,退出 ; 为新进程申请核心堆栈 ; if ( 核心堆栈申请不到 )返回错误 ,退出 ;为新进程

29、在 Task 向量表中找到空闲位置 ;/* 复制父进程 current PCB 中的信息,继承 current 的资源 */ ;p = current; 在进行正式初始化以前,将新创建的进程的状态都置为 初始化过程被打断 , 并置一些标志位 ./* 为 防 止 信 号 、 定 时 中 断 误 程,将子进程的状态设成不可中断的 */p-state = TASK_UNINTERRUPTIBLE;/* 跟踪状态和超级用户特权是没有继承性的, 因为在 安全考虑这个普通用户的进程不允许拥有超级用户特权。rootTASK_UNINTERRUPTIB,LE以免醒未创建完毕用户为普通用户创建进程时,*/的进出

30、于p-flags &= (PF_PTRACED|PF_TRACESYS|PF_SUPERPRIV);*/* 将进程标志设成初建,在进程第一次获得CPU时,内核将根据此标志进行一定操作p-flags |= PF_FORKNOEXEC;开始 Task_struct 的初始化工作,如初始化进程时钟、信号、时间等数据 ; 继承父进程所有资源 :拷贝父进程当前打开的文件 ; 拷贝父进程在 VFS的位置 ; 拷贝父进程的信号量 ; 拷贝父进程运行的内存 ; 拷贝父进程的线程 ;初始化工作结束,父进程将其将其唤醒, 挂入 running 队列中,返回子进程的 pid;进程的调度( schedule() ):

31、处于 TASK_RUNNIN状G 态的进程移到 run queue,会由 schedule() 按 CPU调度算法在合适 的时候选中,分配给 CPU。新创建的进程都是处于 TASK_RUNNIN状G 态,而且被挂到 run queue 的队首。进程调度采 用变形的轮转法( round robin )。当时间片到时( 10ms 的整数倍),由时钟中断引起新一轮 调度,把当前进程挂到 run queue 队尾。所有的进程部分运行与用户态,部分运行于系统态。底层的硬件如何支持这些状态各不 相同但是通常有一个安全机制从用户态转入系统态并转回来。 用户态比系统态的权限低了很第 7 页 共 25 页多。每

32、一次进程执行一个系统调用, 它都从用户态切换到系统态并继续执行。 这时让核心执 行这个进程。 Linux 中,进程不是互相争夺成为当前运行的进程,它们无法停止正在运行的 其它进程然后执行自身。每一个进程在它必须等待一些系统事件的时候会放弃CPU。例如,这 进程经常调用系统调用, CPU事件,所以 Linux 200 毫秒,当这个时 这个时间段叫一个进程可能不得不等待从一个文件中读取一个字符。这个等待发生在系统态的系统调用 中。进程使用了库函数打开并读文件, 库函数又执行系统调用从打开的文件中读入字节。 时,等候的进程会被挂起, 另一个更加值得的进程将会被选择执行。 所以经常需要等待。 即使进程

33、执行到需要等待也有可能会用去不均衡的 使用抢先式的调度。用这种方案,每一个进程允许运行少量一段时间, 间过去, 选择另一个进程运行, 原来的进程等待一段时间直到它又重新运行。 做时间片。需要调度程序选择系统中所有可以运行的进程中最值得的进程。一个可以运行的进程是 一个只等待 CPU的进程。 Linux 使用合理而简单的基于优先级的调度算法在系统当前的进程 中进行选择。 当它选择了准备运行的新进程, 它就保存当前进程的状态、 和处理器相关的寄 存器和其他需要保存的上下文信息到进程的 task_struct 数据结构中。 然后恢复要运行的新 的进程的状态(又和处理器相关) ,把系统的控制交给这个进

34、程。为了公平地在系统中所有 可以运行( runnable )的进程之间分配 CPU 时间,调度程序在每一个进程的 task_struct 结构中保存了信息。policy 进程的调度策略: Linux 有两种类型的进程:普通和实时。实时进程比所有其它 进程的优先级高。 如果有一个实时的进程准备运行, 那么它总是先被运行。 实时进程有两种 策略:环或先进先出( round robin and first in first out)。在环的调度策略下,每一个实时进程依次运行, 而在先进先出的策略下, 每一个可以运行的进程按照它在调度队列中 的顺序运行,这个顺序不会改变。jiffies )。你Prio

35、rity 进程的调度优先级。也是它允许运行的时候可以使用的时间量( 可以通过系统调用或者 renice 命令来改变一个进程的优先级。Rt_priority Linux支持实时进程。这些进程比系统中其他非实时的进程拥有更高的优先级。这个域允许调度程序赋予每一个实时进程一个相对的优先级。 实时进程的优先级可以 用系统调用来修改 Coutner 这时进程可以运行的时间量( jiffies )。进程启动的时候等于 优先级( priority ),每一次时钟周期递减。调度程序 schedule() 从核心的多个地方运行。它可以在把当前进程放到等待队列之后运 行,也可以在系统调用之后进程从系统态返回进程态

36、之前运行。 需要运行调度程序的另一个 原因是系统时钟刚好把当前进程的计数器 (counter) 置成了 0。每一次调度程序运行它做以 下工作:kernel work 调度程序运行 bottom half handler 并处理系统的调度任务队列。 Current pocess 在选择另一个进程之前必须处理当前进程。 如果当前进程的调度策略是环则它放到运行队列的最后。如果任务状态是 TASK_INTERRUPTIBLE的而且它上次调度的时候收到过一个信号,(1)(2)(3)它的(4)状态变为 TASK_RUNNING;如果当前进程超时,它的状态成为 RUNNING; 如果当前进程的状态为 RUN

37、NING则保持此状态 ; 不是 RUNNING或者 INTERRUPTIBLE的进程被从运行队列中删除。这意味着当调度程序 查找最值得运行的进程时不会考虑这样的进程。(5) Process Selection 调度程序查看运行队列中的进程,查找最值得运行的进程。如果有 实时的进程(具有实时调度策略) ,就会比普通进程更重一些。普通进程的重量是它的 counter ,但是对于实时进程则是 counter 加 1000。这意味着如果系统中存在可运行的实时第8 页共 25 页进程, 就总是在任何普通可运行的进程之前运行。当前的进程,因为用掉了一些时间片 (它的 counter 减少了),所以如果系统

38、中由其他同等优先级的进程,就会处于不利的位置:这 也是应该的。 如果几个进程又同样的优先级, 最接近运行队列前段的那个就被选中。 当前进 程被放到运行队列的后面。 如果一个平衡的系统, 拥有大量相同优先级的进程, 那么回按照 顺序执行这些进程。 这叫做环型调度策略。不过, 因为进程需要等待资源, 它们的运行顺序 可能会变化。每一次它调用例程 因此,当调度程序 但是它仍旧是当前运 (PC) 和所有的处理器(6) Swap Processes 如果最值得运行的进程不是当前进程,当前进程必须被挂起,运行新的 进程。当一个进程运行的时候它使用了 CPU和系统的寄存器和物理内存。 都通过寄存器或者堆栈传

39、递参数、 保存数值比如调用例程的返回地址等。 运行的时候它在当前进程的上下文运行。 它可能是特权模式: 核心态, 行的进程。 当这个进程要挂起时, 它的所有机器状态, 包括程序计数器寄存器,必须存到进程的 task_struct 数据结构中。 然后,必须加载新进程的所有机器状态。 这种操作依赖于系统, 不同的 CPU不会完全相同地实现, 不过经常都是通过一些硬件的帮助。(7) 交换出去进程的上下文发生在调度的最后。前一个进程存储的上下文,就是当这个进程 在调度结束的时候系统的硬件上下文的快照。 相同的, 当加载新的进程的上下文时, 仍旧是 调度结束时的快照,包括进程的程序计数器和寄存器的内容。

40、(8) 如果前一个进程或者新的当前进程使用虚拟内存,则系统的页表需要更新。同样,这个 动作适合体系结构相关。 Alpha AXP 处理器,使用 TLT(Translation Look-aside Table 或者缓存的页表条目,必须清除属于前一个进程的缓存的页表条目。下面我就来总结一下进程创建以后到被杀死的整个进程生命周期中,状态可能在TASK_RUNNING、 TASK_INTERRUPTIBLE、 TASK_UNINTERRUPTIBLE、 TASK_STOPPED以 及 TASK_ZOMBL之E间转换的原因。进 程 在 TASK_RUNNING 以 及 TASK_UNINTERRUPT

41、IBL、ETASK_INTERRUPTIBL之E间转换:获得 CPU 而正在运行的进程会由于某些原因,比如:申请不到某个资源,其状态会从 TASK_RUNNING变为 TASK_INTERRUPTIBLE或 TASK_UNINTERRUPTIBL的E 等待状态。同样在经 历了某些情况, 处于等待状态 的进程会被重新唤醒,等 待分配给 CPU。状态 为 TASK_INTERRUPTIBLE的睡眠进程会被唤醒, 回到 TASK_RUNNIN状G 态,重新等待 schedule() 分配给它 CPU,继续运行,比如:当申请资源有效时,也可以由signal 或定时中断唤醒。而状态为 TASK_INTE

42、RRUPTIBLE的睡眠进程只有当申请资源有效时被唤醒,不能被signal 、定时中断唤醒。1. 通 过 sleep_on() interruptible_sleep_on_timeout wake_up_interruptible()interruptible_sleep_on() 、 sleep_on_timeout() ( ) 以 及 wake_up() 、 wake_up_process() 函数对进行的转换:sleep_on():TASK_RUNNING-TASK_UNINTERRUPTIBLE当拥有 CPU的进程申请资源无效时,会通过sleep_on() ,将进程从 TASK_RU

43、NNING切换到 TASK_UNINTERRUPTIBLE状态。 sleep_on() 函数的作用就是将 current 进程的状态置成 TASK_UNINTERRUPTIBL并E,加到等待队列中。第 9 页 共 25 页一般来说引起状态变成 TASK_UNINTERRUPTIBL的E资源申请都是对一些硬件资源的申请, 如果得不到这些资源,进程将不能执行下去,不能由 signal 信号或时钟中断唤醒,而回到 TASK_RUNNIN状G态。我们总结了这种类型的转换原因有:(1) 对某些资源的操作只能由一个进程进行,所以系统对该项资源采用上锁机制。在申请 该项资源时, 必须先申请资源的锁, 如果已

44、经被别的进程占用, 则必须睡眠在对该锁的等待 队列上。而且这种睡眠不能被中断,必须等到得到了资源才能继续进行下去。 如:对网络连接表锁( Netlink table lock)的申请 , sleep_on(&nl_table_wait),sleep_on对交换页进行 I/O 操作的锁的申请 , sleep_on(&lock_queue) ; 对 Hash 表操作的锁的申请 , sleep_on(&hash_wait) ; 在 UMSDOS文件系统创建文件或目录时,必须等待其他同样的创建工作结束 (&dir-u.umsdos_i.u.dir_info.p);(2) 某些进程在大部分时间处于睡眠状

45、态,仅在需要时被唤醒去执行相应的操作,当执行 完后,该进程又强制去睡眠。如:wakeup_bdflush () 是对 dirty buffer 进行动态的响应,一旦该进程被激活,就将一定 数量的 dirty buffer 写回磁盘,然后调用 sleep_on(&bdflush_done), 又去睡眠。interruptible_sleep_on():TASK_RUNNING-TASK_INTERRUPTIBLE用 就 是 将 current 进 程 的 状 态 置 成进 程 可 以 在 资 源 有 效 时 被 wake_up() 、 或 wake_up_process() 唤醒, 或收到 si

46、gnal 信号以及时间中断后与 sleep_on() 函 数 非 常 地 相 象 , 当 拥 有 CPU 的 进 程 申 请 资 源 无 效 时 , 会 通 过 interruptible_sleep_on() ,将进程从 TASK_RUNNING切换到 TASK_INTERRUPTIBLE状态。 interruptible_sleep_on() 函 数 的 作 TASK_INTERRUPTIBLE并, 加到等待队列中。处 于 TASK_INTERRUPTIBLE 状 态 的 wake_up_interruptible() 被唤醒。进行这种转换的原因基本上与 sleep_on() 相同,申请资

47、源无效时进程切换到等待状态。 与之不同的是处于 interruptible_sleep_on() 等待状态的进程是可以接受信号或中断而重 新变为 running 状态。所以可以认为对这些资源的申请没有象在 sleep_on() 中资源的要求 那么严格,必须得到该资源进程才能继续其运行下去。sleep_on_timeout():TASK_RUNNING-TASK_UNINTERRUPTIBLEsleep_on_timeout(&block.b_wait, 30*HZ);interruptible_sleep_on_timeout(): TASK_RUNNING-TASK_INTERRUPTIBL

48、E虽然在申请资源或运行中出现了某种错误,但是系统仍然给进程一次重新运行的机会。调用该函数将进程从 TASK_RUNNIN切G 换到 TASK_INTERRUTIBLE状态,并等待规定的时间片 长度 ,再重新试一次。如:在 smb_request_ok 中产生了连接失败的错误,会在 sem_retry() 中给一次重新连接的机会。 /interruptible_sleep_on_timeout(&server-wait, 5*HZ) ;wake_up():TASK_UNINTERRUPTIBLE- TASK_RUNNING;第 10 页 共 25 页TASK_INTERRUPTIBLE- TAS

49、K_RUNNING处于 TASK_UNINTERRUPTIBL状E 态的进程不能由 signal 信号或时钟中断唤醒,只能由 wake_up() 或 wake_up_process() 唤醒。 wake_up() 函数的作用是将 wait_queue 中的所有状 态为 TASK_INTERRUPTIBL或E TASK_UNINTERRUPTIBL的E进程状态都置为 TASK_RUNNING并, 将 它们都放到 running 队列中去,即唤醒了所有等待在该队列上的进程。void wake_up(struct wait_queue *q)struct wait_queue *next;struc

50、t wait_queue *head;if (!q | !(next = *q)return;head = WAIT_QUEUE_HEAD(q);while (next != head) struct task_struct *p = next-task;next = next-next;if (p != NULL) if (p-state = TASK_UNINTERRUPTIBLE) | (p-state = TASK_INTERRUPTIBLE) wake_up_process(p);if (!next)goto bad;return; bad:printk(wait_queue is

51、bad (eip = %p)n, _builtin_return_address(0);printk(q = %pn,q);printk( *q = %pn,*q);wake_up() 在下列情况下被调用:1)这个函数通常在资源有效时调用,资源锁已经被释放,等待该资源的所有进程都被置 为 TASK_RUNNIN状G 态,移到 run queue ,重新参与调度,对这一资源再次竞争。这时 又会有某个进程竞争到了该项资源,而其他的进程在申请失败后,又回到 TASK_UNINTERRUPTIBL或E TASK_INTERRUPTIBLE状态。如:网 络 连 接 表 锁 ( Netlink table

52、 lock ) 释 放 后 , 唤 醒 等 待 该 锁 的 所 有 睡 眠 进 程 wake_up(&nl_table_wait) ;对 交 换 页 进 行 I/O 操 作 的 锁 释 放 后 , 唤 醒 等 待 该 锁 的 所 有 睡 眠 进 程 , wake_up(&lock_queue) ;对 Hash 表操作的锁释放后 , 唤醒等待该锁的所有睡眠进程, wake_up(&hash_wait) ; 在 UMSDOS文件系统创建文件或目录工作结束后 , 唤醒其他由于等待它创建结束而睡眠第 11 页 共 25 页的进程, wake_up (&dir-u.umsdos_i.u.dir_info

53、.p)2)唤醒睡眠进程执行某些操作:如:bd_flush() 函数要将一些 dirty buffer 写回磁盘,就调用 wake_up(&bdflush_done) , 唤醒正在睡眠的 wakeup_bdflush () 进程去处理写回。wake_up_process() :TASK_UNINTERRUPTIBLE- TASK_RUNNING; TASK_INTERRUPTIBLE- TASK_RUNNINGwake_up_process() 函 数 的 作 用 是 将 参 数 所 指 的 那 个 进 程 状 态 从 TASK_INTERRUPTIBLE,TASK_UNINTERRUPTIB变

54、LE为 TASK_RUNNING并, 将它放到 running 队列 中去。void wake_up_process(struct task_struct * p) unsigned long flags;/* We want the common case fall through straight, thus the goto. */spin_lock_irqsave(&runqueue_lock, flags); p-state = TASK_RUNNING;if (p-next_run)goto out;add_to_runqueue(p); spin_unlock_irqrestor

55、e(&runqueue_lock, flags);reschedule_idle(p); return;out: spin_unlock_irqrestore(&runqueue_lock, flags);这个函数的实现机制与 wake_up() 的不同在于,它只能唤醒某一个特定的睡眠进程,而 wake_up() 是唤醒整个等待队列的睡眠进程。所以,它的唤醒的原因与 wake_up() 也有一定 的区别,除了由于 wake_up() 对它的调用之外,它唤醒进程并不是由于资源有效造成的,唤 醒的进程也不是因等待资源有效而睡眠的进程。有以下几种情况: (1)父进程对子进程的唤醒: 如:在 sys_

56、ptrace() 中当收到的跟踪请求为:PTRACE_CON(T在处理完信号后继续) ;PTRACE_KILL(将子进程杀出) ; PTRACE_SINGLESTE(P对子进程进行单步跟踪);PTRACE_DETAC的H时候,都会在处理结束时,唤醒子进程,给子进程一个运行的机会。run queue在 do_fork() 中,新建进程初始化完毕,会由父进程唤醒它,将该进程移到第 12 页 共 25 页中,置状态为 TASK_RUNNIN。G(2)当需要的时候唤醒某个睡眠的系统调用,进行处理: 如:kswapd_process 页面交换进程, 通常是处于睡眠状态的, 当某个进程需要更多的内存, 而

57、调用 try_to_free_pages ()时,就会唤醒 kswapd_process 页面交换进程,调入更 多的内存页面。(3) 收到信号所进行的相应处理:如:某一进程的时间片到了, process_timeout() 会调用 wake_up_process() 唤醒该进程; 收到某些 signal 信号:处于 STOPPED状态的进程收到 SIGKILL或SIGCONT会被唤醒(注: 处于 STOPPED状态的进程不能被 wake_up() 唤醒);以及收到某些非实时信号,不需加 到 signal 队列中去,处于 TASK_INTERRUPTIBLE的进程有机会被唤醒。4)资源有效时, wake_up() 对整个等待队列的唤醒是通过对每个等待队列上的进程调用 wake_up_process() 实现的

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论