Linux系统编程——进程管理

引言:

在Linux的内核的五大组成模块中,进程管理模块时非常重要的一部分,它虽然不像内存管理、虚拟文件系统等模块那样复杂,也不像进程间通信模块那样条理化,但作为五大内核模块之一,进程管理对我们理解内核的运作、对于我们以后的编程非常重要。同时,作为五大组成模块中的核心模块,它与其他四个模块都有联系。下面就对进程模块进行想写的介绍,首先要了解进程及其相关的概念。其次介绍进程的创建、切换、撤销等基本操作。除此之外,还给出了Linux内核是如何对进程进行调度管理的。

     一、进程及其相关概念

进程:进程可以理解为程序执行的一个实例,它包括可执行程序以及与其相关的系统资源,比如打开的文件、挂起的信号、内核内部数据、处理器状态、内存地址空间及包含全局变量的数据段等。从内核的角度看,进程也可以称为任务。

进程描述符:与进程相关的事情非常多,比如进程的状态、进程的优先级、进程的地址空间、允许该进程访问的文件等等,Linux内核为此专门设计了一个类型为task_struct的结构体,称之为进程描述符。进程描述符中包含了内核管理进程的所有信息,可以说,只要得到一个进程的进程描述符,就可以知道一个进程的所有信息。

进程状态:进程描述符task_struct结构体中有一个state字段,表示进程当前的所处状态。从进程的创建到进程的删除,它可以经过5种不同的状态,分别是可运行状态、可中断的等待状态、不可中断的等待状态、暂停状态、跟踪状态。除此之外,当进程被终止时,还可能会变为僵死状态、僵死撤消状态。内核可以使用宏set_current_state(state)设置当前进程的状态,用set_task_state(task,state)设置某进程的状态。

进程标示符:进程描述task_struct结构体中的pid字段可以标识唯一标识一个进程,称之为进程标识符PID。当创建一个新进程时,PID是按照顺序从小到大分配给新进程的。内核通过管理一个pidmap_array位图来表示当前已分配的PID和闲置的PID号。注意:在多线程组中,所有的线程共享相同的PID。除了进程标识符外,内核对进程的大部分访问时通过进程描述符指针进行的。

进程关系:进程之间的关系有亲属关系和非亲属关系。亲属关系包括父子关系和兄弟关系等。其中由tast_struct结构体中的parent/children/real_parent/sibling等字段描述。除了亲属关系外,还有其他关系,比如,一个进程是一个进程组或登录会话的领头进程,可能是一个线程组的领头进程,这些关系由group_leader/tgid/signal->pgrp等字段描述。

进程资源:为了防止进程过度的使用系统资源,内核为每个进程使用资源的数量进行了一些限制。其中包括进程地址空间的最大数、进程使用CPU的最大时间、堆的最大值、文件大小的最大值、文件锁数量的最大值、消息队列的最大字节数、打开文件描述符的最大数、进程拥有的页框最大数等。

   二、进程的创建、切换、撤销

进程的创建:在Linux环境编程时,一般采用fork()函数来创建新的进程,当然,那是在用户空间的函数,它会调用内核中的clone()系统调用,由clone()函数继续调用do_fork()完成进程的创建。

传统Unix系统中,创建的子进程复制父进程所拥有的资源,这种方法效率低,因为子进程需要拷贝父进程的整个地址空间。但是,子进程几乎不必读或修改父进程拥有的所有资源,因为很多情况下,子进程创建后会立即调用exec()一族的函数,并清除父进程仔细拷贝过来的地址空间。现代Unix系统用三种方式解决了这个问题:1、写实复制技术允许父子进程读相同的物理页。2、轻量级进程允许父子进程共享每进程在内核的很多数据结构。3、vfork()系统调用创建的进程能共享父进程的内存地址空间,为了防止父进程重写子进程需要的数据,阻塞父进程的执行,一直到子进程退出或执行一个新的程序为止。整个进程创建过程可能涉及到如下函数:

fork()/vfork()/_clone----------->clone()--------->do_fork()---------->copy_process()

上面的创建过程结束之后,就有了处于可运行状态的完整的子进程,新的子进程有了PID、进程描述符等各种数据结构,要想实际运行它,还需要调度程序把CPU交给新创建的子进程。

除了进程外,还有内核线程(用kernet_thread创建)的概念。在Linux中,内核线程与普通进程存在以下两个方面的不同:

1、内核线程只运行在内核态,而普通进程既可以运行在内核态,也可运行在用户态。

2、因为内核线程只运行在内核态,它只使用大于PAGE_OFFSET的线性地址空间。另一方面,不管在用户态还是在内核态,普通进程可以用4GB的线性地址空间。

撤销进程:进程终止后,需要通知内核以便内核释放进程所拥有的资源,包括内存、打开文件以及其他资源,如信号量。进程终止的一般方式是调用exit()库函数,该函数释放C函数库所分配的资源,执行编程者所注册的每个函数,并结束从系统回收进程的那个系统调用。

除了进程自己终止自己外,内核可以有选择地强迫整个线程组死掉。这发生在:当进程接收到一个不能处理或忽视的信号时,或者当内核正在代表进程运行时再内核态产生一个不可恢复的CPU异常时。

有两个终止用户态应用的系统调用:exit_group()系统调用,它终止整个线程组,即整个基于多线程的应用。do_group_exit()是实现这个系统调用的主要内核函数。exit()系统调用,它终止一个线程,而不管该线程所属线程组中的所有其他进程。do_exit()是实现这个系统调用的主要内核函数。

进程切换:进程切换又称为任务切换、上下文切换。它是这样一种行为,为了控制进程的执行,内核挂起当前在CPU上运行的进程,并恢复以前挂起的某个进程的执行。

跟函数的调用类似,进程切换时,一般要在CPU上装载要执行进程的进程上下文。进程的硬件上下文指:可执行程序上下文的一个子集,是进程恢复执行前装入寄存器的一组数据。其中一部分放在TSS段,即任务状态段,剩余部分存放在内核态堆栈中。进程的切换只发生在内核态,在执行进程切换之前,用户态进程使用的所有寄存器内容都已保存在内核态堆栈上。

进程的切换有两种方法,一种是硬件切换,一种是软件切换。软件切换就是利用程序逐步执行切换,它的优点是,可以对切换时装入的数据进行合法性检查,执行时间虽与硬件切换大致相同,但仍有可改进的地方。

进程切换使用schedule()函数完成,在本质上,每个进程切换由两部分组成:1、切换页全局目录以安装一个新的地址空间。2、切换内核态堆栈和硬件上下文,因为硬件上下文提供了内核执行新进程所需要的所有信息,包括CPU寄存器,主要有switch_to函数完成。

三、进程调度

调度策略:调度策略就是这样一组规则:决定什么时候以怎样的方式选择一个新进程运行的规则。Linux的调度基于分时技术:多个进程以“时间多路复用”方式运行,因为CPU的时间被分成“片”,给每个可运行进程分配一片。调度策略也是根据进程的优先级对它们进行分类。在Linux中,进程的优先级是动态的。调度程序跟踪进程正在做什么,并周期性地调整它们的优先级。根据不同的分类标准,可以把进程分成不同的类型。比如可以把一个进程看作是“I/O受限”或“CPU受限”。也可把进程区分为以下三类:交互式进程、批处理进程、实时进程。Linux的进程是抢占式的,无论是处于内核态还是用户态。时间片的长短对系统性能是很关键的:它既不能太长也不能太短。如果平均时间片太短,由进程切换引起的系统额外开销就变得非常高。如果平均时间片太长,进程看起来就不再是并发执行的。对时间片大小的选择始终是一种折中。Linux采用单凭经验的方法,即选择尽可能长、同时能保持良好响应时间的一个时间片。

调度算法:早起的Linux中,调度算法是根据进程的优先级选择“最佳”进程来执行,它的缺点是时间开销与“可运行进程数量”有关。现代的Linux中,调度算法可以在固定时间内(与可运行进程数量无关)选中要运行的进程。首先,我们必须知道进程可以分为实时进程与普通进程。每个LInux进程总是按照如下的调度类型被调度:先进先出的实时进程、时间片轮转的实时进程、普通的分时进程。调度算法根据进程是普通进程还是实时进程而有很大不同。

普通进程的调度:每个普通进程都有它自己的静态优先级(值是从100到139),调度程序使用静态优先级来估价系统中这个进程与其他普通进程之间调度的程度。静态优先级决定进程的基本时间片,即进程用完了以前的时间片时,系统分配给进程的时间片长度。普通进程除了静态优先级,还有动态优先级。动态优先级是调度程序在选择新进程来运行的时候使用的数。平均睡眠时间是进程在睡眠状态所消耗的平均纳秒数。即使具有较高静态优先级的普通进程获得了较大的CPU时间片,也不应该使静态优先级较低的进程无法运行。为了避免这个问题,提出了活动进程和过期进程的概念,活动进程指进程的时间片还未用完,过期进程指进程的时间片已用完,即使过期进程的优先级更高,也不能继续运行,除非等到所有活动进程都过期以后。

实时进程的调度:每个实时进程都与一个试试优先级相关,实时优先级是一个范围从1到99的值。跟普通进程不同,实时进程总是被当作活动进程。

调度程序所使用的主要数据结构:数据结构runqueue和进程描述符

数据结构runqueue:runqueue数据结构中最重要的字段是与可运行进程 的链表相关的字段。其中的arrays字段是活动进程和过期进程的两个集合,active字段是指向活动进程链表的指针,expired字段是指向过期进程链表的指针。

进程描述符:每个进程描述符都包括几个与调度相关的字段。其中的time_slice字段是在进程的时间片中还剩余的时钟节拍数。它由copy_process函数设置:父进程的剩余节拍数被划分为两等分,一份给父进程,一份给子进程。

     四、调度程序所使用的函数

调度程序依靠几个函数来完成调度工作,其中最重要的函数如下:

try_to_wake_up()函数通过把进程状态设置为TASK_RUNNING,并把该进程插入本地CPU的运行队列来唤醒睡眠或停止的进程。

recalc_task_prio()函数更新进程的平均睡眠时间和动态优先级。

schedule()憾事实现调度程序,它的任务时从运行队列的链表中找到一个进程,并随后将CPU分配给这个进程。schedule()可以由几个内核控制路径调用,可以采用直接调用或延迟调用的方式

总结:

Linux内核中的进程管理模块非常重要,它是连接其他4大模块的重要桥梁,它也非常复杂,理解它的一些基本原理,对于理解Linux内核非常重要,上面只是对它进行了一些简单的描述,并没有深入到具体实现细节,希望已有有机会能深入分析实现的细节。

转自:http://blog.csdn.net/tennysonsky/article/details/45168799

时间: 2024-11-06 19:35:14

Linux系统编程——进程管理的相关文章

Linux系统编程@进程通信(一)

进程间通信概述 需要进程通信的原因: 数据传输 资源共享 通知事件 进程控制 Linux进程间通信(IPC)发展由来 Unix进程间通信 基于System V进程间通信(System V:UNIX系统的一个分支) POSIX进程间通信(POSIX:可移植操作系统接口,为了提高UNIX环境下应用程序的可移植性.很多其他系统也支持POSIX标准(如:DEC OpenVMS和Windows).) 现在Linux使用的进程间通信方式包括: 管道(pipe).有名管道(FIFO) 信号(signal) 消

linux系统编程-进程

进程 现实生活中 在很多的场景中的事情都是同时进行的,比如开车的时候 手和脚共同来驾驶汽车,再比如唱歌跳舞也是同时进行的: 如下是一段视频,迈克杰克逊的一段视频: http://v.youku.com/v_show/id_XMzE5NjEzNjA0.html?&sid=40117&from=y1.2-1.999.6 试想:如果把唱歌和跳舞这2件事分开以此完成的话,估计就没有那么好的效果了 程序中 如下程序,来模拟"唱歌跳舞" 这件事情 11 from time impo

[Linux] Linux系统(进程管理)

进程:当我们运行程序时,Linux会为程序创建一个特殊的环境,包含程序运行的所有资源,这个环境就称为进程 前台进程:一般我们使用一些命令,都属于前台进程,直接输出结果到显示器 后台进程:在命令的末尾加上&可以创建最简单的后台进程 常驻进程:系统级进程,以root权限运行在后台,可以处理其他进程请求 孤儿进程:杀掉父进程后,子进程没了父亲,成了孤儿进程,init进程成了它们父亲 僵尸进程:进程被中止了,ps命令还能看到,我们可以通过杀死他们的父进程来杀死僵尸 使用命令ps,查看运行的进程,参数:-

Linux系统编程——进程替换:exec 函数族

在 Windows 平台下.我们能够通过双击运行可运行程序.让这个可运行程序成为一个进程:而在 Linux 平台.我们能够通过 ./ 运行,让一个可运行程序成为一个进程. 可是,假设我们本来就执行着一个程序(进程),我们怎样在这个进程内部启动一个外部程序.由内核将这个外部程序读入内存,使其执行起来成为一个进程呢?这里我们通过 exec 函数族实现. exec 函数族.顾名思义.就是一簇函数,在 Linux 中,并不存在 exec() 函数.exec 指的是一组函数.一共同拥有 6 个: [cpp

Linux系统编程——进程和线程的区别与联系

在许多经典的操作系统教科书中,总是把进程定义为程序的执行实例,它并不执行什么, 只是维护应用程序所需的各种资源,而线程则是真正的执行实体. 为了让进程完成一定的工作,进程必须至少包含一个线程. 进程,直观点说,保存在硬盘上的程序运行以后,会在内存空间里形成一个独立的内存体,这个内存体有自己的地址空间,有自己的堆,上级挂靠单位是操作系统.操作系统会以进程为单位,分配系统资源,所以我们也说,进程是资源分配的最小单位.更多详情,请看<进程的介绍>. 线程存在与进程当中,是操作系统调度执行的最小单位.

Linux系统编程——进程的控制:结束进程、等待进程结束

结束进程 首先,我们回顾一下 C 语言中 continue, break, return 的作用: continue: 结束本次循环 break: 跳出整个循环,或跳出 switch() 语句 return: 结束当前函数 而我们可以通过 exit() 或 _exit() 来结束当前进程. 所需头文件: #include <stdlib.h> void exit(int value); 功能: 结束调用此函数的进程. 参数: status:返回给父进程的参数(低 8 位有效),至于这个参数是多

linux系统编程--进程相关概念

程序和进程 程序:二进制文件,占用的磁盘空间,还没运行 进程:启动的程序,数据在内存中,占用系统资源(CPU,物理内存) 并行和并发 并发:不是一个时间点的概念,而是一个时间段的概念,某个时间段内处理的请求数量 并行:增加服务器或cpu对请求的处理 一个cpu把一个时间段分成若干时间碎片,每个时间碎片只能处理一个进程,交替的处理进程 pcb(进程控制块) 常见内部成员: - 进程id - 进程的状态:初始,就绪,运行,挂起,终止 - 进程切换时,需要保存和恢复一些CPU寄存器 - 描述虚拟地址空

Linux系统编程——进程的介绍

http://my.9ku.com/fuyin/daogaoo.asp?dgid=564516 http://my.9ku.com/fuyin/daogaoo.asp?dgid=564608 http://my.9ku.com/fuyin/daogaoo.asp?dgid=564687 http://my.9ku.com/fuyin/daogaoo.asp?dgid=564782 http://my.9ku.com/fuyin/daogaoo.asp?dgid=564867 http://my.

Linux系统编程之进程

前一段时间对文件I/O的基本操作基本操作做了总结,今天这里继续按照我的理解对linux系统编程的进程操作进行总结. 首先我们先理解几个概念:程序.进程.线程. 所谓程序,就是计算机指令的集合,它以文件的形式存储在磁盘上,进程是一个程序在其自身的地址空间中的一次执行活动.而线程进程内的一个执行单元,也是进程内的可调度实体.说完这个不知道大家理解了吗?反正我第一次听到这个概念以后看到的时候可以明白过后就忘记了,现在我给大家举一个例子帮助大家理解,大家都看电视剧吧,所谓程序,就是一个剧本,像什么<西游