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

下载本文档

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

文档简介

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

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

3、函数的 local变量的首地址),AP(augument pointer,指向栈中函数调 用的实参位置),ISP(interrupt stack pointer,中断栈指针),以及其他的通用寄存器等。(3)系统层次的保存:包括proc,u,虚拟存储空间管理表格,中断处理栈。以便于该进 程再一次得到CPU时间片时能正常运行下去。多进程系统的一些突出的特点:并行化一件复杂的事件是可以分解成若干个简单事件来解决的,这在程序员的大脑中早就形成了这种概念,首先将问题分解成一个个小问题,将小问题再细分,最后在一个合适的规模上做成一个函数。在软件工程中也是这么说的。如果我们以图的方式来思考,一些小问题的计算

4、是可以互不干扰的,可以同时处理,而在关键点则需要统一在一个地方来处理,这样程序的运行就是并行的,至少从人的时间观念上来说是这样的。而每个小问题的计算又是较简单的。简单有序这样的程序对程序员来说不亚于管理一班人,程序员为每个进程设计好相应的功能,并通过一定的通讯机制将它们有机地结合在一起,对每个进程的设计是简单的,只在总控部分小心应付(其实也是蛮简单的),就可完成整个程序的施工。互不干扰这个特点是操作系统的特点,各个进程是独立的,不会串位。事务化比如在一个数据电话查询系统中,将程序设计成一个进程只处理一次查询即可,即完成一个事务。当电话查询开始时 ,产生这样一个进程对付这次查询 ;另一个电话进来

5、时,主控 程序又产生一个这样的进程对付 ,每个进程完成查询任务后消失 这样的编程多简单,只要做一次查询的程序就可以了。Lin ux是一个多进程的操作系统,进程是分离的任务,拥有各自的权利和责任。如果一个进程崩溃,它不应该让系统的另一个进程崩溃。每一个独立的进程运行在自己的虚拟地址空间,除了通过安全的核心管理的机制之外无法影响其他的进程。在一个进程的生命周期中,进程会使用许多系统资源。比如利用系统的CPU执行它的指令,用系统的物理内存来存储它和它的数据。它会打开和使用文件系统中的文件,会直接或者间接使用系统的物理设备。如果一个进程独占了系统的大部分物理内存和CPU对于其他进程就是不公平的。所以L

6、inux必须跟踪进程本身和它使用的系统资源以便公平地管理系统 中的进程。系统最宝贵的资源就是CPU通常系统只有一个 CPU Linux作为一个多进程的操作系统,它的目标就是让进程在系统的CPU上运行,充分利用 CPU如果进程数多于 CPU( 般情况都是这样),其他的进程就必须等到CPU被释放才能运行。多进程的思想就是:一个进程一直运行,直到它必须等待,通常是等待一些系统资源,等拥有了资源,它才可以继续运行。 在一个单进程的系统中,比如DOS CPU被简单地设为空闲,这样等待资源的时间就会被浪费。而在一个多进程的系统中,同一时刻许多进程在内存中,当一个进程必须等待时,操作系统将CPU从这个进程切

7、换到另一个更需要的进程。我们组分析的是Linux进程的状态转换以及标志位的作用,它没有具体对应某个系统调 用,而是分布在各个系统调用中。所以我们详细而广泛地分析了大量的原码,对进程状态转换的原因、方式和结果进行了分析,大致总结了整个Linux系统对进程状态管理的实现机制。Lin ux中,每个进程用一个 task_struct的数据结构来表示,用来管理系统中的进程。Task向量表是指向系统中每一个task_struct数据结构的指针的数组。这意味着系统中的最大进程数受到 Task向量表的限制,缺省是512。这个表让Linux可以查到系统中的所有的进程。操作系统初始化后,建立了第一个task_st

8、ruct 数据结构INIT_TASK。当新的进程创建时,从系统内存中分配一个新的task_struct ,并增加到Task向量表中。为了更容易查找,用current指针指向当前运行的进程。task_struct结构中有关于进程调度的两个重要的数据项:struct task_struct volatile long state;/* -1 unrunn able , 0 runn able , 0 stopped */un sig ned long flags;/* per process flags, defi ned below */;每个在Task向量表中登记的进程都有相应的进程状态和进程

9、标志,是进行进程调度的重要依据。进程在执行了相应的进程调度操作后,会由于某些原因改变自身的状态和标志,也就是改变state和flags这两个数据项。进程的状态不同、标志位不同对应了进程可以执行 不同操作。在Linux2.2.8 版本的sched.h中定义了六种状态,十三种标志。/进程状态#defi ne TASK_RUNNING0#defi ne TASK_INTERRUPTIBLE 1#defi ne TASK_UNINTERRUPTIBLE2#defi ne TASK_ZOMBIE4#defi ne TASK_STOPPED8#defi ne TASK_SWAPPING16它们的含义分别是

10、:TASK_RUNNING正在运行的进程(是系统的当前进程)或准备运行的进程(在Running队列中,等待被安排到系统的CPU。处于该状态的进程实际参与了进程调度。TASK_INTERRUPTIBLE处于等待队列中的进程,待资源有效时唤醒,也可由其它进程被信号中断、唤醒后进入就绪状态。TASK_UNINTERRUPTIBLE处于等待队列中的进程,直接等待硬件条件,待资源有效时唤 醒,不可由其它进程通过信号中断、唤醒。TASK_ZOMBIE终止的进程,是进程结束运行前的一个过度状态(僵死状态)。虽然此时已经释放了内存、文件等资源,但是在Task向量表中仍有一个task_struct数据结构项。

11、它不进行任何调度或状态转换,等待父进程将它彻底释放。TASK_STOPPEDt程被暂停,通过其它进程的信号才能唤醒。正在调试的进程可以在该停止状态。TASK_SWAPPINGS程页面被兑换出内存的进程。这个状态基本上没有用到,只有在sched.c的count_active_tasks()函数中判断处于该种状态的进程也属于active的进程,但没有对该状态的赋值。/进程标志位:#defi ne PF_ALIGNWARN0x00000001#defi ne PF_STARTING 0x00000002#defi ne PF_EXITING 0x00000004#defi ne PF_PTRACED

12、 0x00000010#defi ne PF_TRACESYS 0x00000020#defi ne PF_FORKNOEXEC 0x00000040#defi ne PF_SUPERPRIV 0x00000100#defi ne PF_DUMPCORE 0x00000200#defi ne PF_SIGNALED 0x00000400#defi ne PF_MEMALLOC 0x00000800#defi ne PF_VFORK0x00001000#defi ne PF_USEDFPU 0x00100000#defi ne PF_DTRACE 0x00200000其中PF_STARTING有

13、用到。PF_MEMEALLOC PF_VFOR这两个标志位是新版本中才有的。各个标志位的代表着不同含义,对应着不同调用:1、 PF_ALIGNWARN 标志打印“对齐”警告信息,只有在486机器上实现2、 PF_STARTING进程正被创建3、 PF_EXITING标志进程开始关闭。在do_exit() 时置位。current-flags |= PF_EXITING用于判断是否有效进程。在 nImclnt_proc()(在 fslockdclntproc.c),如果 current_flag 为 PF_EXITING,则进程由于正在退出清除所有的锁,将执行异步RPC调用。4、 PF_PTRAC

14、ED进程被跟踪标志,在do_fork() 时清位。p-flags &= PF_PTRACED当ptrace(0)被调用时置位,在进程释放前要清掉。curren t-flags |= PF_PTRACED在sys_trace() 中判断如果 request 为 PTRACE_TRACE,E口是则将 current_flag置为 PF_PTRACED如果 request 为 PTRACE_ATTACH则将 child_flag 置为 PF_PTRACEJD给 child 发 一个SIGSTOP信号;如果 request 为 PTRACE_DETACH 则将 child 清除 PF_PTRACED在

15、 syscall_trace() 中判断 current_flag如果为 PF_TRACED PF_TRACESY则curre nt强行退出时的出错代码置为SIGTRAP并将状态置为STOPPED5、 PF_TRACESYS正在跟踪系统调用。do_fork()时清位,在进程释放前要清掉。在 sys_trace() 中判断 request 如果为 PTRACE_SYSCALL则将 child-flags 置为 PF_TRACESYS如为 PTRACE_SYSCALL则将 child-flags 清除 PF_TRACESY;然 后唤醒 child。如果 request 为 PTRACE_SINGL

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

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

18、mp()时判断current dumpable是否等于(即判断该进程是否能将core文件放入回收站),如果等于1则将该变量置为0,在当前目 录下建立一个core dump image ,在清空用户结构前,由 gdb算出数据段和堆栈段 的位置和使用的虚地址,用户数据区和堆栈区在清空前将相应内容写入core dump将PF_DUMPCOI置位,清空数据区和堆栈区。只有在 aout_core_dump() 内调用 do_aout_core_dump(), 而没有地 方调用 aout_core_dump()。对其它文件格式也是类似。9、 PF_SIGNALED标志进程被信号杀出。在 do_signal

19、() 中判断信号,如果 current 收到信号为 SIGHUP, SIGINT, SIGIOT, SIGKILL, SIGPIPE, SIGTERM, SIGALRM, SIGSTKFLT, SIGURG, SIGXCPU, SIGXFSZ, SIGVTALRM,SIGPROF,SIGIO, SIGPOLL, SIGLOST, SIGPWR 则执行 lock_kernel(), 将信号加入current的信号队列,将 current-flag置为PF_SIGNALED然后执行do_exit()10、 PF_USEDFPU标志该进程使用 FPU此标志只在 SMP时使用。在task_struct

20、中有一变量used_math,进程是否使用 FPU。在CPU从 prev切换到next时,如果prev使用FPU则prev的flag 清除PF_USEDFP。 prev-flags&=PF_USEDFPU在 flush_thread()(archi386kernelprocess.c)、restore_i387_hard() 、save_i387_hard()(archi386kernelsignal.c)中,如果是 SMP方式,且使用 FPU贝U stts(),否则清除 PF_USEDFPUIcurre nt-flags &= PF_USEDFPU在 sys_trace() 中如果 requ

21、est 为 PTRACE_SETFPREG则将 child 的 used_math 置为 1,将 child_flag 清除 PF_USEDFPUchild-flags &= PF_USEDFPU在SMP方式下进行跟踪时,判断是否使用FPU在跟踪时出现数学错误时清位。curre nt-flags &= PF_USEDFPU11、 PF_DTRACE进程延期跟踪标志,只在m68k下使用。跟踪一个trapping 指令时置位。curren t-flags |= PF_DTRACE12、 PF_ONSIGSTK标志进程是否工作在信号栈,只在m68k方式下使用。liunx 2.1.19版本中使用此标志

22、位,而2.2.8版本中不使用。在处理信号建立 frame时如果sigaction 标志为ONSTACK则将current-flag置为 PF_ONSIGSTU13、 PF_MEMALLOC进程分配内存标志。lin ux 2.2.8版本中使用此标志位。在kpiod()和kwpad()中置位。tsk-flags |= PF_MEMALLOC14、 PF_VFORKlinux 2.2.8版本中使用此标志位。在 copy_flags(unsigned long cione_flags, struct task_struct *p),如果clone_flags 为 CLONE_VFORK贝将 p 的 f

23、lags 置为 PF_VFORU在 mm_release ()中将 current -flags 清除 PF_VFORKtsk-flags &= PF_VFORK具体的分析由我组的另外同学进行。Linux的各进程之间的状态转换的系统调用我将参与Linux的各进程之间的状态转换的系统调用总结成一张流程图:in terruptible_sleep_o n()I interruptible_sleep_on_timeou_dow n_in terruptible()current,拥有CPU运行r 1sys_exit()1do exit()TASKOMBZ_Eschedule。o_fbrk()创建进

24、_!sleep_ on()i_dow n() wait_ on _.() -i syscall_trace()ido_sig nal()1timer时间到 SIG.KILLIISIG CONTISTOPPEDwake up pr)cess() TASK RUNNING TASKwake_up() i wake_up_process() wake_up_i nterruptib -_up()le()wake_up() wake_up_process()_up()1 厂 T_j二 ui :-1I 1 TASK INTERRUPTIBLENG STATUaSK UNINTERRUPTIBLE进程的创

25、建:TASK_RUNNING第一个进程在系统启动时创建,当系统启动的时候它运行在核心态,这时,只有一个进程:初始化进程。象所有其他进程一样,初始进程有一组用堆栈、寄存器等等表示的机器状态。当系统中的其他进程创建和运行的时候这些信息存在初始进程的task_struct数据结构中。在系统初始化结束的时候,初始进程启动一个核心进程(叫做in it )然后执行空闲循环,什么也不做。当没有什么可以做的时候,调度程序会运行这个空闲的进程。这个空闲进程的task_struct 是唯个不是动态分配而是在核心连接的时候静态定义的,为了不至于混淆,叫做init_task 。系统调用sys_fork 和sys_cl

26、one 都调用函数 do_fork ()(在kernel/fork.中定义)。进程由do_fork()函数创建,先申请空间,申请核心堆栈;然后在 Task向量表中找到空 闲位置;在进行正式初始化以前,将新创建的进程的状态都置为TASK_UNINTERRUPTIBLE以免初始化过程被打断;开始初始化工作,如初始化进程时钟、信号、时间等数据;继承父 进程的资源,如文件、信号量、内存等;完成进程初始化后,由父进程调用wake_up_process() 函数将其唤醒,状态变为TASK_RUNNING挂到就绪队列run queue,返回子进程的 pid 。/ C:SRCLNXKERNELFORK.Cin

27、t do_fork(u nsig ned long cion e_flags, un sig ned long usp, struct pt_regs *regs)为新进程申请PCB空间;if (申请不到)返回错误,退出;为新进程申请核心堆栈; if (核心堆栈申请不到)返回错误,退出;为新进程在Task向量表中找到空闲位置;/*复制父进程current PCB 中的信息,继承 current的资源*/ ;p = curre nt;在进行正式初始化以前,将新创建的进程的状态都置为TASK_UNINTERRUPTIBLS免初始化过程被打断,并置一些标志位/* 为防止信号、定时中断误唤醒未创建完毕

28、的进 程,将子进程的状态设成不可中断的*/p-state = TASK_UNINTERRUPTIBLE;/*跟踪状态和超级用户特权是没有继承性的,因为在root用户为普通用户创建进程时,出于安全考虑这个普通用户的进程不允许拥有超级用户特权。*/p-flags &= (PF_PTRACED|PF_TRACESYS|PF_SUPERPRIV);/*将进程标志设成初建,在进程第一次获得CPU寸,内核将根据此标志进行一定操作*/p-flags |= PF_FORKNOEXEC;开始Task_struct的初始化工作,如初始化进程时钟、信号、时间等数据;继承父进程所有资源:拷贝父进程当前打开的文件;拷贝

29、父进程在VFS的位置;拷贝父进程的信号量;拷贝父进程运行的内存; 拷贝父进程的线程;初始化工作结束,父进程将其将其唤醒,挂入running队列中,返回子进程的pid;进程的调度(schedule。):处于TASK_RUNNIN状态的进程移到 run queue,会由schedule()按CPU调度算法在合适 的时候选中,分配给 CPU新创建的进程都是处于 TASK_RUNNIN状态,而且被挂到run queue的队首。进程调度采 用变形的轮转法(round robin )。当时间片到时(10ms的整数倍),由时钟中断引起新一轮 调度,把当前进程挂到 run queue队尾。所有的进程部分运行与

30、用户态,部分运行于系统态。底层的硬件如何支持这些状态各不 相同但是通常有一个安全机制从用户态转入系统态并转回来。用户态比系统态的权限低了很多。每一次进程执行一个系统调用,它都从用户态切换到系统态并继续执行。这时让核心执行这个进程。Linux中,进程不是互相争夺成为当前运行的进程,它们无法停止正在运行的 其它进程然后执行自身。每一个进程在它必须等待一些系统事件的时候会放弃CPU例如,一个进程可能不得不等待从一个文件中读取一个字符。这个等待发生在系统态的系统调用 中。进程使用了库函数打开并读文件,库函数又执行系统调用从打开的文件中读入字节。这时,等候的进程会被挂起,另一个更加值得的进程将会被选择执

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

32、构中。然后恢复要运行的新的进程的状态(又和处理器相关),把系统的控制交给这个进程。为了公平地在系统中所有 可以运行(runnable )的进程之间分配CPU时间,调度程序在每一个进程的task_struct结构中保存了信息。policy进程的调度策略:Linux有两种类型的进程:普通和实时。实时进程比所有其它进程的优先级高。如果有一个实时的进程准备运行,那么它总是先被运行。实时进程有两种策略:环或先进先出(round rob in and first in first out)。在环的调度策略下,每一个实时进程依次运行,而在先进先出的策略下,每一个可以运行的进程按照它在调度队列中的顺序运行,这

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

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

35、INGS保持此状态;不是RUNNINGS者 INTERRUPTIBLE勺进程被从运行队列中删除。这意味着当调度程序 查找最值得运行的进程时不会考虑这样的进程。(5)Process Selection调度程序查看运行队列中的进程,查找最值得运行的进程。如果有实时的进程(具有实时调度策略),就会比普通进程更重一些。普通进程的重量是它的counter,但是对于实时进程则是counter加1000。这意味着如果系统中存在可运行的实时进程,就总是在任何普通可运行的进程之前运行。当前的进程,因为用掉了一些时间片(它的counter减少了),所以如果系统中由其他同等优先级的进程,就会处于不利的位置:这 也是

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

37、它可能是特权模式:核心态, 行的进程。当这个进程要挂起时, 它的所有机器状态, 包括程序计数器 寄存器,必须存到进程的task_struct 数据结构中。然后,必须加载新进程的所有机器状态。 这种操作依赖于系统,不同的CPU不会完全相同地实现,不过经常都是通过一些硬件的帮助。(7)交换出去进程的上下文发生在调度的最后。前一个进程存储的上下文,就是当这个进程 在调度结束的时候系统的硬件上下文的快照。相同的,当加载新的进程的上下文时,仍旧是调度结束时的快照,包括进程的程序计数器和寄存器的内容。(8)如果前一个进程或者新的当前进程使用虚拟内存,则系统的页表需要更新。同样,这个动作适合体系结构相关。A

38、lpha AXP处理器,使用 TLT( Tran slation Look-aside Table )或者缓存的页表条目,必须清除属于前一个进程的缓存的页表条目。下面我就来总结一下进程创建以后到被杀死的整个进程生命周期中,状态可能在TASK_RUNNING TASK_INTERRUPTIBLE TASK_UNINTERRUPTIBLE TASK_STOPPED以及 TASK_ZOMBL之间转换的原因。进程在 TASK_RUNNING 以及 TASK_UNINTERRUPTIBLETASK_INTERRUPTIBE 间转换:获得CPU而正在运行的进程会由于某些原因,比如:申请不到某个资源,其状态

39、会从TASK_RUNNIN变为 TASK_INTERRUPTIBL或 TASK_UNINTERRUPTIBL的等待状态。同样在经 历了某些情况,处于等待状态的进程会被重新唤醒,等待分配给CPU。状态为 TASK_INTERRUPTIBL的睡眠进程会被唤醒, 回到TASK_RUNNIN状态,重新等待schedule。 分配给它CPU继续运行,比如:当申请资源有效时,也可以由signal或定时中断唤醒。而状态为TASK_INTERRUPTIBL的睡眠进程只有当申请资源有效时被唤醒,不能被signal、定时中断唤醒。1. 通 过 sleep_ on() 、 in terruptible_sleep_

40、 on()、 sleep_ on _timeout()、interruptible_sleep_on_timeout() 以及 wake_up() 、 wake_up_process() 、wake_up_i nterruptible() 函数对进行的转换:sleep_o n( ):TASK_RUNNING-TASK_UNINTERRUPTIBLE当拥有CPU的进程申请资源无效时,会通过sleep_on(),将进程从 TASK_RUNNIN切换到TASK_UNINTERRUPTIBL状态。sleep_on()函数的作用就是将 current 进程的状态置成 TASK_UNINTERRUPTIB

41、L并加至U等待队列中。一般来说引起状态变成 TASK_UNINTERRUPTIBLE资源申请都是对一些硬件资源的申请, 如果得不到这些资源,进程将不能执行下去,不能由sig nal信号或时钟中断唤醒,而回到TASK_RUNNIN 状态。我们总结了这种类型的转换原因有:(1) 对某些资源的操作只能由一个进程进行,所以系统对该项资源采用上锁机制。在申请 该项资源时,必须先申请资源的锁,如果已经被别的进程占用,则必须睡眠在对该锁的等待队列上。而且这种睡眠不能被中断,必须等到得到了资源才能继续进行下去。如:对网络连接表锁(Netlink table lock )的申请,sleep_on(&nl_tab

42、le_wait);对交换页进行I/O操作的锁的申请,sleep_on(&lock_queue);对Hash表操作的锁的申请,sleep_on(&hash_wait);在UMSDOS件系统创建文件或目录时,必须等待其他同样的创建工作结束,sleep_on(&dir-u.umsdos_i.u.dir_i nfo.p);(2 )某些进程在大部分时间处于睡眠状态,仅在需要时被唤醒去执行相应的操作,当执行 完后,该进程又强制去睡眠。如:wakeup_bdflush ()是对dirty buffer进行动态的响应,一旦该进程被激活,就将一定 数量的dirty buffer写回磁盘,然后调用sleep_on

43、(&bdflush_done),又去睡眠。in terruptible_sleep_o n():TASK_RUNNING-TASK_INTERRUPTIBLE与sleep_ on()函数非常地相象,当拥有CPU的进程申请资源无效时,会通过 interruptible_sleep_on(),将进程从 TASK_RUNNIN切换至U TASK_INTERRUPTIBL状态。interruptible_sleep_on()函数的作用就是将 current进程的状态置成TASK_INTERRUPTIBLE并加到等待队列中。处于 TASK_INTERRUPTIBLE状态的进程可以在资源有效时被 wake

44、_up()、 wake_up_interruptible()或 wake_up_process()唤醒,或收到 signal 信号以及时间中断后被唤醒。进行这种转换的原因基本上与sleep_on()相同,申请资源无效时进程切换到等待状态。与之不同的是处于in terruptible_sleep_o n()等待状态的进程是可以接受信号或中断而重新变为running状态。所以可以认为对这些资源的申请没有象在sleep_on()中资源的要求那么严格,必须得到该资源进程才能继续其运行下去。sleep_on_timeout():TASK_RUNNING-TASK_UNINTERRUPTIBLE slee

45、p_on_timeout(&block.b_wait, 30*HZ);interruptible_sleep_on_timeout() : TASK_RUNNING-TASK_INTERRUPTIBLE虽然在申请资源或运行中出现了某种错误,但是系统仍然给进程一次重新运行的机会。调用该函数将进程从 TASK_RUNNIN切换到TASK_INTERRUTIBL状态,并等待规定的时间片 长度,再重新试一次。如:在smb_request_ok 中产生了连接失败的错误,会在sem_retry()中给一次重新连接的机会。/interruptible_sleep_on_timeout(&server-wai

46、t, 5*HZ);wake_up():TASK_UNINTERRUPTIBLE- TASK_RUNNING;TASK_INTERRUPTIBLE- TASK_RUNNING处于TASK_UNINTERRUPTIBL状态的进程不能由signal 信号或时钟中断唤醒,只能由wake_up()或 wake_up_process()唤醒。wake_up()函数的作用是将 wait_queue 中的所有状 态为 TASK_INTERRUPTIBL或 TASK_UNINTERRUPTIBLE进程状态都置为 TASK_RUNNIN并将 它们都放到running队列中去,即唤醒了所有等待在该队列上的进程。vo

47、id wake_up(struct wait_queue *q)struct wait_queue *n ext;struct wait_queue *head;if (!q | !(next = *q) return;head = WAIT_QUEUE_HEAD(q); while (n ext != head) struct task_struct *p = n ext-task; n ext = n ext-n ext;if (p != NULL) if (p-state = TASK_UNINTERRUPTIBLE) | (p-state = TASK_INTERRUPTIBLE) w

48、ake_up_process(p);if (!n ext)goto bad;return;bad:prin tk(wait_queue is bad (eip = %p)n,_builti n_retur n_address(O);prin tk(q = %pn,q);prin tk(*q = %pn,*q);wake_up()在下列情况下被调用:(1) 这个函数通常在资源有效时调用,资源锁已经被释放,等待该资源的所有进程都被置 为TASK_RUNNIN状态,移到run queue,重新参与调度,对这一资源再次竞争。这时 又会有某个进程竞争到了该项资源,而其他的进程在申请失败后,又回到 TAS

49、K_UNINTERRUPTIBL或 TASK_INTERRUPTIBL状态。如:网络连接表锁(Netlink table lock )释放后,唤醒等待该锁的所有睡眠进程 wake_up(&n l_table_wait) ;对交换页进行I/O操作的锁释放后,唤醒等待该锁的所有睡眠进程, wake_up(&lock_queue);对Hash表操作的锁释放后,唤醒等待该锁的所有睡眠进程, wake_up(&hash_wait); 在UMSDOS件系统创建文件或目录工作结束后 ,唤醒其他由于等待它创建结束而睡眠的进程,wake_up (& dir-u.umsdos_i.u.dir_i nfo.p);(

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

51、ing 队列 中去。void wake_up_process(struct task_struct * p)un sig ned long flags;/* We want the com mon case fall through straight, thus the goto.*/spin _lock_irqsave(&runq ueue_lock, flags);p-state = TASK_RUNNING;if (p-n ext_r un)goto out;add_to_r unq ueue(p);spin_unl ock_irqrestore (&runq ueue_lock, fl

52、ags);reschedule_idle(p);return;out:spin_unl ock_irqrestore (&runq ueue_lock, flags);这个函数的实现机制与wake_up()的不同在于,它只能唤醒某一个特定的睡眠进程,而wake_up()是唤醒整个等待队列的睡眠进程。所以,它的唤醒的原因与wake_up()也有一定的区别,除了由于 wake_up()对它的调用之外,它唤醒进程并不是由于资源有效造成的,唤 醒的进程也不是因等待资源有效而睡眠的进程。有以下几种情况:(1) 父进程对子进程的唤醒:如:在sys_ptrace()中当收到的跟踪请求为:PTRACE_CO(

53、在处理完信号后继续);PTRACE_KILL (将子进程杀出);PTRACE_SINGLESTEP寸子进程进行单步跟踪);PTRACE_DETACH时候,都会在处理结束时,唤醒子进程,给子进程一个运行的机会。在do_fork()中,新建进程初始化完毕,会由父进程唤醒它,将该进程移到run queue中,置状态为 TASK_RUNNING(2) 当需要的时候唤醒某个睡眠的系统调用,进行处理:如:kswapd_process页面交换进程,通常是处于睡眠状态的, 当某个进程需要更多的内存, 而调用try_to_free_pages ()时,就会唤醒 kswapd_process页面交换进程,调入更

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

55、rruptible (): TASK_INTERRUPTIBLE- TASK_RUNNING将wait_queue中的所有状态为 TASK_INTERRUPTIBLE的进程状态都置为 TASK_RUNNING, 并将它们都放到 running queue 中去。这个函数通常在send_sig(发出信号)后调用,以使信号发出后能及时得到响应,或者当空闲下来时,希望检查一下是否有收到有效信号的能运行的进程时,也可以调用这个函数,如:在进程退出前调用notify_pare nt(), 给父进程sen d_sig ()后,将调用 wake_up_interruptible(),使信号能够得到及时的响应。usrsrcli nu xKERNELEXIT.C 中定义了void no tify_pare nt(struct task_struct * tsk, int sig nal)send_sig(signal, tsk-p_pptr, 1);wake_upn terruptible(&tsk-p_pptr-wait_chldexit);当某一进程要结束时,它可以通过调用 n otify_pare nt(curre nt,curren t-exit_sig nal)通知父进程以唤醒睡眠

温馨提示

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

评论

0/150

提交评论