Linux下的进程1进程概念 进程切换 上下文切换 虚

本文发布时间: 2019-Mar-22
进程概述  当一个可执行程序在现代系统上运行时,操作系统会提供一种假象——好像系统上只有这个程序在运行,看上去只有这个程序在使用处理器,主存和IO设备。  处理器看上去就像在不间断的一条接一条的执行程序中的指令,即改程序的代码和数据是系统存储器中唯一的对象。这些假象是通过进程的概念来实现的。  进程是操作系统对一个正在运行的程序的一种抽象。在一个系统上可以同时运行多个进程,而每个进程都好像在独占的使用硬件。而并发运行,则是说一个进程的指令和另一个进程的指令是交错执行的。  在大多数系统中,需要运行的进程数是多于可以运行他们的CPU个数的。  传统系统在一个时刻只能执行一个程序,而现在的多核处理器同时能够执行多个程序。  无论是在多核还是单核系统中,一个CPU看上去都像是在并发的执行多个进程,这是通过处理器在进程间切换来实现的。  操作系统实现这种交错执行的机制称为上下文切换。  操作系统保持跟踪进程运行所需的所有状态信息,这种状态,也就是上下文,它包括许多信息,例如PC和寄存器文件的当前值,以及主存的内容。  在任何一个时刻,单处理器系统都只能执行一个进程的代码。  当操作系统决定要把控制权从当前进程转移到某个新进程时,就会进行上下文切换,即保存当前进程的上下文,恢复新进程的上下文,然后将控制权传递到新进程,新进程就会从上次停止的地方开始  深入计算机系统一书中对上下文切换的表达如下图:    如果现在有两个并发的进程:外壳进程和hello进程。  开始只有外壳进程在运行,即等待命令行上的输入,当我们让他运行hello程序时,外壳通过调用一个专门的函数,即系统调用,来执行我们的请求,系统调用会将控制权传递给操作系统。  操作系统保存外壳进程的上下文,创建一个新的hello进程及其上下文,然后将控制权传递给新的hello进程。  hello进程终止后,操作系统恢复外壳进程的上下文,并将控制权传回给他,外壳进程将继续等待下一个命令行输入。  这里很重要的一个思想是:一个进程是某种类型的一个活动,他有程序,输入,输出以及状态。单个处理器可以被若干进程共享,它使用某种调度算法决定何时停止一个进程, 并转而为另一个进程提供服务。  进程   进程的经典定义就是一个执行中的程序的实例。  系统中的每个程序都是运行在某个进程的上下文中的。  上下文是由程序正确运行所需的状态组成的,这个状态包括存放在存储器中的程序的代码和数据,他的栈,通用目的寄存器的内容,程序计数器,环境变量以及打开文件描述符的集合。  每次用户通过向外壳输入一个可执行目标文件的名字,并运行一个程序时,外壳就会创建一个新的进程,然后在这个新进程的上下文中运行这个可执行目标文件。  应用程序也能够创建新进程,且在这个新进程的上下文中运行他们自己的代码或其他应用程序。  进程提供给应用程序的关键抽象:  一个独立的逻辑控制流,它提供一个假象,好像我们的程序独占的使用处理器一个私有的地址空间,它提供一个假象,好像我们的程序独占地使用存储器系统  而实际上,进程是轮流使用处理器的。  每个进程执行他的流的一部分,然后被抢占,即暂时挂起,然后轮到其他进程。  对于一个运行在这些进程之一的上下文中的程序,他看上去就像是在独占的使用处理器。虚拟存储器——私有地址空间  虚拟存储器是一个抽象的概念,他为每一个进程提供了一个假象,即每个进程都在独占地使用主存。  每个进程看到的是一致的存储器,成为虚拟地址空间。  在一台有n位地址空间的机器上,地址空间是2的n次方个可能地址的集合,0,1,…,2^n - 1。  一个进程为每个程序提供他自己的私有地址空间,一般而言,和这个空间中某个地址相关联的那个存储器字节是不能被其他进程读或者写的,从这个意义上来说,这个地址空间是私有的。  下图是进程的虚拟地址空间图示。      地址空间顶部是保留给内核的,这个部分包含内核在代表进程执行指令时使用的代码,数据和栈,这对所有进程来说都是一样的。地址空间的底部区域是保留给用户程序的,包括通常的文本,数据,堆和栈段。  图中的地址是从下往上增大的  每个进程看到的虚拟地址空间由大量准确定义的区构成,每个区都有专门的功能。  1.程序代码和数据  对于多有的进程来说,代码是从同一固定地址开始,紧接着的是和C全局变量相对应的数据位置,代码和数据区是直接按照可执行目标文件的内容初始化的。  2.堆  代码和数据区后紧随着的是运行时堆。  代码和数据区是在进程已开始运行时就被规定了大小,与此不同,当调用malloc和free这样的C标准库函数时,堆可以在运行时动态的扩展和收缩。  3.共享库  在地址空间的中间部分是一块用来存放像C标准库和数学库这样共享库的代码和数据的区域。  4.栈  位于用户虚拟地址空间顶部的是用户栈,编译器用它来实现函数调用。和堆一样,用户栈在程序执行期间可以动态的扩展和收缩,特别是我们每次调用一个函数时,栈就会增长,从一个函数返回时,站就会收缩。  5.内核虚拟存储器  内核总是驻留在内存中,是操作系统的一部分。  地址空间顶部的区域是为内核保留的,不允许应用程序读写这个区域的内容或者直接调用内核代码定义的函数。用户模式和内核模式  为了使操作系用内核提供一个无懈可击的进程抽象,处理器必须提供一种机制,限制一个应用可以执行的指令以及它可以访问的地址空间范围。  处理器通常是用某个控制寄存器中的一个模式位来提供这种功能的,该寄存器描述了进程当前享有的特权。  当设置了模式位时,进程就运行在内核模式中,即超级用户模式。  一个运行在内核模式的进程可以执行指令集中的任何指令,并且可以访问系统中任何存储器位置。  没有设置模式位时,进程就运行在用户模式中。用户模式中的进程不允许执行特权指令,比如停止处理器,改变模式位,或者发起一个I/O操作。也不允许用户模式中的进程直接引用地址空间中内核区内的代码和数据。  运行应用程序代码的进程初始时是在用户模式中的。  进程从用户模式变为内核模式的唯一方法是通过诸如中断,故障或者陷入系统调用这样的异常,当异常发生时,控制传递到异常处理程序,处理器将模式从用户模式变为内核模式。  处理程序运行在内核模式中,当他返回到应用程序代码时,处理器就把模式从内核模式改回到用户模式。上下文切换  操作系统内核使用一种称为上下文切换的较高层形式的控制流来实现多任务。  内核为每一个进程维持一个上下文。  上下文就是内核重新启动一个被抢占的进程所需的状态。他有一些对象的值组成,这些对象包括:  通用目的寄存器 浮点寄存器 程序计数器 用户栈 状态寄存器 内核栈各种内核数据结构  内核数据结构,比如描绘地址空间的页表,包含有关当前进程信息的进程表,以及包含进程已打开文件的信息的文件表。  在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程。  这种决定就叫做调度,是由内核中称为调度器的代码处理的。  当内核选择一个新的进程运行时,我们就说内核调度了这个进程。在内核调度了一个新的进程运行后,他就抢占当前进程,并使用一种称为上下文切换的机制来将控制转移到新的进程。上下文切换 保存当前进程的上下文 恢复某个先前被抢占的进程被保存的上下文将控制传递给这个新恢复的进程  当内核代表用户执行系统调用时,可能会发生上下文切换,如果系统调用因为等待某个时间发生而阻塞,那么内核可以让当前进程休眠,切换到另一个进程。  比如:如果一个read系统调用请求一个磁盘访问,内核可以选择执行上下文切换,运行另外一个进程,而不是等待数据从磁盘到达。  另一个例子是sleep系统调用,它显式的请求让调用进程休眠,一般而言,即使系统调用没有阻塞,内核也可以决定执行上下文切换,而不是将控制返回给调用进程。  中断也可能引发上下文切换。  比如,所有的系统都有某种产生周期性定时器中断的机制,典型的为每一毫秒或每十毫秒,每次发生定时器中断时,内核就能判定当前进程已经运行了足够长时间了,并切换到一个新的进程。  上下文简单说来就是一个环境,相对于进程而言,就是进程执行时的环境。  具体来说就是各个变量和数据,包括所有的寄存器变量、进程打开的文件、内存信息等。  一个进程的上下文可以分为三个部分:用户级上下文、寄存器上下文以及系统级上下文。用户级上下文: 正文、数据、用户堆栈以及共享存储区;寄存器上下文: 通用寄存器、程序寄存器(IP)、处理器状态寄存器(EFLAGS)、栈指针(ESP);系统级上下文: 进程控制块task_struct、内存管理信息(mm_struct、vm_area_struct、pgd、pte)、内核栈  当发生进程调度时,进行进程切换就是上下文切换(context switch).操作系统必须对上面提到的全部信息进行切换,新调度的进程才能运行。  而系统调用进行的是模式切换(mode switch)。模式切换与进程切换比较起来,容易很多,而且节省时间,因为模式切换最主要的任务只是切换进程寄存器上下文的切换。  进程上下文主要是异常处理程序和内核线程。  内核之所以进入进程上下文是因为进程自身的一些工作需要在内核中做。例如,系统调用是为当前进程服务的,异常通常是处理进程导致的错误状态等。所以在进程上下文中引用current是有意义的。  内核进入中断上下文是因为中断信号而导致的中断处理或软中断。而中断信号的发生是随机的,中断处理程序及软中断并不能事先预测发生中断时当前运行的是哪个进程,所以在中断上下文中引用current是可以的,但没有意义。  事实上,对于A进程希望等待的中断信号,可能在B进程执行期间发生。例如,A进程启动写磁盘操作,A进程睡眠后B进程在运行,当磁盘写完后磁盘中断信号打断的是B进程,在中断处理时会唤醒A进程。  内核可以处于两种上下文:进程上下文和中断上下文。  在系统调用之后,用户应用程序进入内核空间,此后内核空间针对用户空间相应进程的代表就运行于进程上下文。异步发生的中断会引发中断处理程序被调用,中断处理程序就运行于中断上下文。  中断上下文和进程上下文不可能同时发生。  运行于进程上下文的内核代码是可抢占的,但中断上下文则会一直运行至结束,不会被抢占。  因此,内核会限制中断上下文的工作,不允许其执行如下操作:(1)进入睡眠状态或主动放弃CPU;  由于中断上下文不属于任何进程,它与current没有任何关系(尽管此时current指向被中断的进程),所以中断上下文一旦睡眠或者放弃CPU,将无法被唤醒。所以也叫原子上下文(atomic context)。(2) 占用互斥体  为了保护中断句柄临界区资源,不能使用mutexes。如果获得不到信号量,代码就会睡眠,会产生和上面相同的情况,如果必须使用锁,则使用spinlock。(3) 执行耗时的任务;  中断处理应该尽可能快,因为内核要响应大量服务和请求,中断上下文占用CPU时间太长会严重影响系统功能。在中断处理例程中执行耗时任务时,应该交由中断处理例程底半部来处理。(4) 访问用户空间虚拟内存。  因为中断上下文是和特定进程无关的,它是内核代表硬件运行在内核空间,所以在中断上下文无法访问用户空间的虚拟地址(5) 中断处理例程不应该设置成reentrant(可被并行或递归调用的例程)。因为中断发生时,preempt和irq都被disable,直到中断返回。所以中断上下文和进程上下文不一样,中断处理例程的不同实例,是不允许在SMP上并发运行的。(6)中断处理例程可以被更高级别的IRQ中断。  如果想禁止这种中断,可以将中断处理例程定义成快速处理例程,相当于告诉CPU,该例程运行时,禁止本地CPU上所有中断请求。这直接导致的结果是,由于其他中断被延迟响应,系统性能下降。并发  多个流并发的执行的一般现象称为并发,一个进程和其他进程轮流运行的概念称为多任务.  一个进程执行他的控制流的一部分的每一个时间段叫做时间片,因此多任务也叫作时间分片。


(以上内容不代表本站观点。)
---------------------------------
本网站以及域名有仲裁协议。
本網站以及域名有仲裁協議。

2024-Mar-04 02:08pm
栏目列表