《LINUX内核进程管理.ppt》由会员分享,可在线阅读,更多相关《LINUX内核进程管理.ppt(78页珍藏版)》请在三一办公上搜索。
1、第2章 Linux内核,进程管理,本章主要介绍:进程概念进程的组成进程的状态和调度进程间关系中断处理与定时器系统调用进程间通信,1 进程概念,20世纪60年代,进程(process)一词首先在麻省理工学院的MULTICS和IBM的CTSS/360系统中被引入。对进程下个准确定义不容易,但有必要强调一下进程具有的两个重要特性。,1.独立性,进程是系统中独立存在的实体,它可以拥有自己独立的资源,比如文件和设备描述符等。在没有经过进程本身允许的情况下,其他进程不能访问到这些资源。这一点上和线程有很大的不同。线程是共享资源的程序实体,创建一个线程所花费的系统开销要比创建一个进程小得多。,一个程序至少有
2、一个进程,一个进程至少有一个线程。线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。,2.动态性,进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合。在进程中加入了时间的概念。进程具有自己的生命周期和各种不同的状态,这些概念在程序中都是不具备的。,由于以上两个性质,又可以衍生出进程的第三个重要特性,即并发性。若干个进程可以在单处理机状态上并发执行。注意并发性(con
3、currency)和多处理机并行(parallel)是两个不同的概念。,并行指在同一时刻内,有多条指令在多个处理机上同时执行;并发指在同一时刻内可能只有一条指令执行,但多个进程的指令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果。,2 进程的组成,作为申请系统资源的基本单位,进程必须有一个对应的物理内存空间。而对这样的一块空间,首先要用数据结构进行描述,才能进一步对之进行管理。,在Linux中,进程以进程号PID(process ID)作为标识。任何对进程进行的操作都要给予其相应的PID号。每个进程都属于一个用户,进程要配备其所属的用户编号UID。此外,每个进程都属于多个用户组,所以进
4、程还要配备其归属的用户组编号GID的数组,UID和GID都分4种,UID包括uid,euid,suid和fsuid,GID包括gid,egid,sgid和fsgid。一般来说uid=euid=fsuid,gid=egid=fsgid。,进程标识:uid和gid是运行进程的用户标识和用户组标识。euid和egid又称为有效的uid和gid。出于系统安全权限的考虑,运行程序时要检查euid和egid的合法性。通常,uid等于euid,gid等于egid。有时候,系统会赋予一般用户暂时拥有root的uid和gid(作为用户进程的euid和egid),以便于进行运作。suid和sgid是根据POSIX
5、标准引入的,在系统调用改变uid和gid时,用于保留真正的uid和gid。fsuid和fsgid称为文件系统的uid和gid,用于对文件系统操作时的合法性检查,是LINUX独特的标识类型。它们一般分别和euid和egid一致,但在NFS文件系统中NFS服务器需要作为一个特殊的进程访问文件,这时只修改客户进程的fsuid和fsgid。,进程运行的环境称为进程上下文(context)。Linux中进程的上下文由进程控制块PCB(process control block)、正文段(text segment)、数据段(data segment)以及用户堆栈(stack)组成。,其中:正文段存放该进程
6、的可执行代码;数据段存放进程中静态产生的数据结构;PCB包括进程的编号、状态、优先级以及正文段和数据段中数据分布的大概情况。,个称做进程表(process table)的链表结构将系统中所有的PCB块联系起来,如图2-1所示。,图 2-1 进程的数据结构,Linux源代码中也将进程称作任务(Task),Linux上的所有进程都是由task_struct结构体来管理。生成一个进程时会产生一个task_struct结构体,此后通过task_struct结构来管理进程。当然文件也是通过task_struct结构来管理。Linux中的PCB块又称为task_struct结构,在2.4版本内核中,每个t
7、ask_struct占1680个字节。Linux根据系统物理内存的大小限制已打开进程的总数目。,系统每次访问一个进程时,内核根据PID在进程表中查找相应的进程PCB块(具体查找过程通过一个PID的hash表实现),再通过PCB块找到其对应的代码段与数据段,并进行操作。,3 进程的状态和调度,Linux系统信号,信号主要用于通知进程异步事件的发生。在Linux中可以识别29种不同的信号,这些信号中的大部分都有了预先定义好的意义,进程可以显式的用kill或killpg系统调用来向另一个进程发信号。,进程可以通过提供信号处理函数来取代对于任意信号的缺省反应,这种缺省反应一般都是终止进程。信号发生时,
8、内核中断当前的进程,进程执行处理函数来响应信号,结束后恢复正常的进程处理。信号有自己的名称和特定的编号,见表3-1所示。,3-1 Linux 系统信号,进程状态,进程是一个动态的实体,故而它是有生命的。从创建到消亡,是一个进程的整个生命周期。在这个周期中,进程可能会经历各种不同的状态。一般来说,所有进程都要经历以下3种状态。,就绪(ready)态:指进程已经获得所有所需的其他资源,并正在申请处理机资源,准备开始运行。这种情况下,称进程处于就绪态。,阻塞(blocked)态:指进程因为需要等待所需资源而放弃处理机,或者进程本不拥有处理机,且其他资源也没有满足,从而即使得到处理机资源也不能开始运行
9、。这种情况下,称进程处于阻塞态。阻塞状态又称休眠状态或者等待状态。,运行态:进程得到了处理机,并不需要等待其他任何资源,正在执行的状态,称之为运行态。只有在运行态时,进程才可以使用所申请到的资源。,内核以此感知进程的存在财产登记卡,记录着进程所占用的各项资源,Task_struct数据结构,进程结构,task_struct数据结构进程标志符PID进程所占的内存区域相关文件的文件描述符安全信息进程环境信号处理资源安排同步处理进程状态,include/Linux/sched.h,task_struct(include/linux/sch.h),进程的地址空间,task_struct中有 struc
10、t mm_struct*mm;内存描述符 mm_struct 里面有一个字段mmp 指向内存线形区链表的首部。,进程的状态,task_struct 中的state 表示进程当前的状态Linux中的进程主要有5个状态:,图 2-2 Linux 进程状态转换,RUNNING:正在运行,或者在就绪队列中等待运行的进程。也就是上面提到的运行态和就绪态进程的综合。一个进程处于RUNNING状态,并不代表它一定在被执行。,由于在多任务系统中,各个就绪进程需要并发执行,所以在某个特定时刻,这些处于RUNNING状态的进程之中,只有一个能够得到处理机,而其他进程必须在一个就绪队列中等待。即使是在多处理机的系统
11、中,Linux也只能同时让一个处理机执行任务。,UNINTERRUPTABLE:不可中断阻塞状态。处于这种状态的进程正在等待队列中,当资源有效时,可由操作系统进行唤醒,否则,将一直处于等待状态。,INTERRUPTABLE:可中断阻塞状态。与不可中断阻塞状态一样,处于这种状态的进程也在等待队列中,当资源有效时,可以由操作系统进行唤醒。与不可中断阻塞状态有所不同的是,处于此状态中的进程亦可被其他进程的信号和定时中断唤醒。,STOPPED:挂起状态。进程被暂停,需要通过其他进程的信号才能被唤醒。导致这种状态的原因有两种。其一是受到了相关信号(SIGSTOP、SIGSTP、SIGTTIN 或SIGT
12、TOU)的反应;其二是受到父进程ptrace调用的控制,而暂时将处理机交给控制进程。,ZOMBIE:僵尸状态。表示进程结束但尚未消亡的一种状态。此时进程已经结束运行并释放大部分资源,但尚未释放进程控制块。,进程调度,调度程序(scheduler)用来实现进程状态之间的转换。在Linux中,调度程序由系统调用schedule()来完成。schedule()是一个怪异的函数,它与一般C语言函数不同,因为它的调用和返回不在同一个进程中。,用户进程由fork()系统调用实现。用户进程由do_fork()函数创建,它也是fork系统调用的执行者。fork()创建一个新的进程,继承父进程的现有资源,初始化
13、进程时钟、信号、时间等数据。完成子进程初始化后,父进程将它挂到就绪队列,返回子进程的PID。,进程创建时的状态为UNINTERRUPTIBLE,在fork()结束前被父进程唤醒后,变为RUNNING。处于RUNNING状态的进程被移到就绪队列中,在适当时候由schedule()按处理机调度算法选中,获得处理机。,获得处理机而正在运行的进程若申请不到某个资源,则调用sleep()进行休眠,其PCB挂到相应的等待队列,状态变为UNINTERRUPTIBLE或者INTERRUPTIBLE。sleep()将调用schedule()函数把休眠进程释放的处理机分配给就绪队列中的某个进程.,状态为INTER
14、RUPTIBLE的休眠进程当它申请的资源有效时被唤醒,也可以由信号或定时中断唤醒。而状态为UNINTERRUPTIBLE的休眠进程只有当它申请的资源有效时被唤醒,不能被信号和定时中断唤醒。唤醒后,进程状态改为RUNNING,并进入就绪队列。,进程执行系统调用exit()或收到外部的杀死进程信号SIG_KILL时,进程状态变为ZOMBIE,释放所申请资源。同时启动schedule()把处理机分配给就绪队列中其他进程。,若进程通过系统调用设置了跟踪标志位,则在系统调用返回前,进入跟踪状态,进程状态变为STOPPED,处理机分配给就绪队列中其他进程。只有通过其他进程发送SIG_KILL信号或继续信号
15、SIG_CONT,才能把STOPPED进程唤醒。重新进入就绪队列。,对每一个进程,其PCB块中都可以记录一种调度策略。进程调度算法可采用先进先出算法(FIFO)或轮转法(round-robin),有实时(这里的“实时”,只是一种说法。实际上,未经改造的Linux很难实现“实时”)和非实时两种形式。,若采用Linux的轮转法,当时间片到时(10ms的整数倍),由时钟中断触发,引起新一轮调度,把当前进程挂到就绪队列队尾。在schedule()中有一个goodness()函数,可以用来保证实时的进程可以得到优先调用。然而这只是在调用上优先,事实上在内核态下,实时进程并不能对普通进程进行抢占。所以Li
16、nux中的实时并不是真正意义上的实时。,4 进程间关系,Linux中除了0号进程是启动时由系统创建,其余进程都是由其他进程自行创建的。为了表示这种创建关系,用父进程指代缔造者,用子进程指代被创建出的新进程。如果进程A是进程B的间接父进程,则A称做B的祖先,B为A的后代。既然提到了父子关系,那么这两个进程之间自然是有着如同父子一样的继承性。,进程的“宗族”关系树型组织task_struct中的 struct task_struct*p_pptr,*p_cptr,*p_ysptr,*p_osptr;p_pptr:parent(父进程)p_cptr:child(指向自己最年轻、最新的子进程)p_ys
17、ptr:指向比自己年轻的兄弟进程p_osptr:指向比自己老的兄弟进程,在数据结构上,父进程PCB中的指针p_cptr指向最近创建的一个子进程的PCB块,而每个子进程PCB中的指针p_pptr都指向其父进程的PCB块。这一对指针构成了进程的父子关系,如图2-3所示。,图 2-3 父子进程关系,除了最老的子进程外,每个子进程PCB块中的p_osptr 指针都指向其父进程创建的上一个子进程PCB。除了最新的子进程外,每个子进程PCB块中的p_ysptr都指向其父进程所创建的后一个子进程PCB。同一个父亲的子进程们就按“年龄”顺序构成了一个双向链表。父进程则可以通过其p_cptr指针,从最新创建的子
18、进程开始,依次访问到其每一个子进程。,task数组(实际是双向链表指针)包含指向系统中所有task_struct结构的指针数组大小限制了系统中的进程数目将所有任务串连起来Pid hash表通过pid查找进程时,利用hash快速定位双向指针run_list动态的将任务链入prio_array中的某个优先级队列中,current指针当前运行的进程的结构用current指针表示init进程系统初始化后,建立的第一个进程第一个task_struct:INIT_TASK,系统启动时,内核被加载到内存后,由start_kernel函数(完成内核初始化工作)从无到有地自行创建了一个内核进程,叫做0号进程,其
19、所运行的代码是init_task()函数,在很多链表中起表头的作用。只有当没有其他进程处于可运行状态时,调度程序才选择0号进程。该进程的作用是作为一切其他进程的父进程,就像亚当夏娃是一切人类的祖先那样。0号进程不能自动生成,必须手工将其设置到进程表中去,才能启动进程管理机制。,在启动进程管理机制以后,就可以由进程自行创建新的子进程。创建新进程的调用是fork()。fork一词在英文中是“分叉”的意思。同样,在Linux中,fork()调用也起了一个“分叉”的作用。当进程A调用fork()生成进程B时,fork()函数同时在A和B两个进程中返回。其中,父进程A里的fork()返回了子进程的PID
20、,而子进程B里的fork()返回0。如果出现错误,fork()返回一具负值。,然而,fork()函数究竟做了些什么呢?我们发现,经过fork()以后,父进程和子进程拥有相同内容的代码段、数据段和用户堆栈,就像父进程把自己克隆了一遍。事实上,父进程只复制了自己的PCB块,而代码段、数据段、用户堆栈内存空间并没有复制一份,而是与子进程共享.,fork函数演示见文件fork.swf,从概念上讲,fork()就像细胞的裂变,调用fork()的进程就是父进程,而新裂变出的进程就是子进程。新创建的进程与父进程几乎完全相同,只有少量属性必须不同。例,每个进程的PID必须是唯一的。,调用fork()后,子进程
21、被创建,此时父进程和子进程都从这个系统调用内部继续运行。为了区分父子进程,fork()给两个进程返回不同的值。对父进程,fork()返回新创建子进程的进程标识符(PID),而对子进程,fork()返回值0。,只有当子进程在运行中出现写操作时,才会产生中断,并为子进程分配内存空间。由于父进程的PCB和子进程的一样,所以在PCB中所记录的父进程占有的资源,也是与子进程共享使用的。这里的“共享”一词就意味着“竞争”。,有时候为了避免父进程和子进程竞争相同的资源或者出于代码串行性考虑,我们希望父进程可以等待子进程运行结束后再继续执行。调用vfork()可以使在子进程创建后,随即向父进程发送SIG_ST
22、OP信号,使父进程进入挂起状态,直到子进程发送信号表示其已经结束为止。,如果系统中只提供fork()调用,那么整个操作系统的所有进程就都只能运行同一个程序了,因为其代码段都是复制或者共享的。Linux为了创建进程运行新的程序,采用fork+exec方法。execve()支持在新的进程创建后,动态装入新的可执行文件作为自己新的代码段。并且execve()支持多种可执行文件的格式。,5 进程间通讯,用户态进程间处于并发状态。为了协调进程的运行,需要实现进程之间通信的机制。,在Linux中,进程间通信有以下几种方法:,1.管道机制 该机制最适用于解决生产者消费者问题。管道是一种在进程之间单向流动数据
23、的结构。源进程向管道写数据,而内核会自动将这些数据引导向目标进程。在POSIX标准中,管道必须是单向的。,虽然通过pipe()调用会产生两个描述符(写管道和读管道),但是在写之前必须关闭读管道,反之亦然。而在Linux中,使用一个管道的同时还可以使用另一个管道,这就大大增强了管道使用的简便性。,2.先进先出(FIFO)机制 管道机制的最大缺点是不能由多个进程共享,除非此管道为这些进程共同的祖先所创建。为了解决这个问题,Linux中引入了FIFO机制(又称为named pipe,命名管道)。,FIFO为“first in,first out”的简写,指一个在磁盘上的文件,它可以被所有进程所共享。
24、但是FIFO与一般文件不同,它还使用了内核中的缓冲区,所以在效率上要比一般共享文件快得多。FIFO和管道都可以使用read()和write()调用来进行读写操作。,3.IPC机制 IPC是“interprocess communication”的缩写形式。它包含了一系列系统调用,允许用户态进程通过信号量进行同步,向其他进程发消息,并且可以与其他进程共享一块内存空间.IPC首先是在一个叫做“Columbus Unix”的系统中实现的,其后在现代Unix类操作系统中广为流行。,如上文所述,IPC资源包括信号量,消息队列和共享内存几种。,(1)消息队列,消息队列是由内核创建并维护的一个数据结构,它是
25、有标识的。任何具有足够权限的进程都可以向消息队列中放置一个消息,同样,任何具有足够权限的进程都可以从中读取一个消息。这样,不同的进程通过访问相同的消息队列便可实现进程间通信。,(2)共享内存,共享内存区是这几种进程间通信方式中最快的一种。它的特点除了速度快外,而且可传递的信息量大。它是通过将一段内存区映射到一个进程的地址空间来实现。,因此,这种进程间通信就不再涉及到内核(即进程不是通过执行任何进入内核的系统调用来传递数据的。这样,内核必须建立允许各个进程共享该内存区的内存映射关系,然后一直管理该内存区)。但同时,也要有效地保证它能同步、有序且没有死锁。,用共享内存实现过程如下:,服务器取得访问
26、该共享内存区的权限。服务器从输入文件读取数据到该共享内存区。服务器读入数据完毕时,通知用户进程。用户从该共享内存区读出这些数据并输出。,(3)信号量,信号量并不是一种IPC机制,它是用于提供不同进程间或一给定进程的不同线程间同步的一种手段。,信号量主要包括以下几种类型:,二值信号量:其值为0或1。资源被锁住而不可用时,值为0;资源可用时,值为1。计数信号量:其值在0和某个限制值之间。它的值一般是可用的资源数。计数信号量集:由一个或多个信号量构成的一个集合,其中每一个都是计数信号量。每个集合的信号量数都有一个限制值,一般在25个以上。,以计数信号量为例,在有信号量机制的程序中,一个进程为了获得共
27、享资源,需要执行以下操作:,测试控制该资源的信号量,若此信号量的值为正,则进程可以使用该资源。进程将信号量值减1,表示它使用了一个资源;若此信号量的值为0,则进入睡眠状态,直至信号量的值大于0为止;若进入1状态,则进程被唤醒。进程不再使用该资源时,信号量的值就加1。如果这时有进程处在睡眠状态,则唤醒它。,一个IPC资源将永久地驻留在内存中,除非进程显式地释放它。并且,IPC资源不仅能被其创建进程的后代所共享,任何进程都可以共享它。,6 进程同步,在Linux中进程具有并发性,即系统中所有在就绪队列中的进程要按时间片轮流运行。由于一些资源为所有进程所共享(比如文件系统),在并发的情况厂,就需相关
28、中断事件所打断。,可串行性是数据库中广泛使用的一种正确标准。当事务交叉执行它们的操作,产生的结果和它们串行执行的结果一致时,这些事务便称为可串行性的。同样,在操作系统中,假设两个进程同时修改一个文件,进程A和进程B都分别从偏移量x处读出数据,加1后写回。,如果操作是可串行的,那么无论A和B的指令具体在CPU中执行的先后顺序如何,最后x的值都应该是被增加2。然而如果不加任何保护措施,则很有可能出现A和B都分别读出x的数据,再各自加1,先后写回的情况。,在这种情况下,x最终只增加了1。为了避免不可串行的情况出现,操作系统中需要将进程的读数据和加1两个操作定义为一个原子操作,并进行互斥保护。总之,Linux系统下,进程同步可以采用互斥锁、读写锁、记录上锁、信号量和条件变量等方法。,