0.基本知识:
程序是适合计算机处理的一系列的指令。默认为静态程序。
微观上串行,而宏观上并行的程序被称为并发程序。是逻辑上的并行,具有动态性、制约性、并发性三个特点。
并发程序的存在是进程产生的直接原因,一般情况下进程存在于多道程序环境中,是操作系统直接处理的实体。
1. 进程是什么
进程(process)的概念最早是由美国麻省理工学院的 J.H.Sallexer 于1966年提出的,是现代操作系统最基本、最重要的概念。进程的引入很好的描述了程序的执行过程和并发行为。
定义: 进程是并发程序的一次执行过程,进程是具有一定独立功能的程序关于某个数据集合的一次运行过程。
进程的本质:
1.进程的存在必然需要程序的存在。进程和程序不是一一对应的。
2.进程是系统中独立存在的实体。 它对应特殊的描述结构并有申请、使用、释放资源的资格。
3.进程的并发特性通过对资源的竞争来体现,进程的动态特性通过状态来描述。进程的逻辑形态和物理形态不同,逻辑上进程只不过是一系列信息的说明,物理上却占用着系统的各种资源。
4.进程和数据相关,但它不是数据,在它的存在过程中要对数据进行处理。
进程作为构成系统的基本细胞,不仅是系统内部独立运行的实体,而且是独立竞争资源的基本实体。
进程既然是运行中的程序,则必定有0它的开始和结束。对于Linux系统来说,新进程由fork()与execve()等系统调用开始,然后执行,直到它们下达exit()系统调用为止。在这里,fork()、execve()、exit()都是linux提供给C语言的控制进程的接口。
Linux是一个多用户多任务的操作系统。多用户是指多个用户可以在同一时间使用同一个linux系统;多任务是指在Linux下可以同时执行多个任务,更详细的说,linux采用了分时管理的方法,所有的任务都放在一个队列中,操作系统根据每个任务的优先级为每个任务分配合适的时间片,每个时间片很短,用户根本感觉不到是多个任务在运行,从而使所有的任务共同分享系统资源,因此linux可以在一个任务还未执行完时,暂时挂起此任务,又去执行另一个任务,过一段时间以后再回来处理这个任务,直到这个任务完成,才从任务队列中去除。
进程之间的切换:CPU在一个时间点只能处理一个进程,多个进程时,CPU的做法是先用极短的时间处理一个进程,然后将这个进程搁置,将CPU的资源让给其他进程执行。这个极短的时间称为时间片。时间片很短(通常只有几微秒),并且切换很迅速。
关于时间片的调度,在操作系统内核中,有个模块称之为调度器(scheduler),它负责管理进程的执行和切换。调度算法会根据进程的优先级决定哪个进程去执行。Linux的进程都有优先级,nice和renice命令可以调整进程的优先级。
2.进程的分类
按照进程的功能和运行的程序分类,进程可划分为两大类:
(1)系统进程:完成操作系统功能的进程成为系统进程。可以执行内存资源分配和进程切换等管理工作;而且,此类进程的运行不受用户的干预,即使是root用户也不能干预系统进程的运行。
(2) 用户进程:完成用户功能的进程成为用户进程。 通过执行用户程序、应用程序或内核之外的系统程序而产生的进程,此类进程可以在用户的控制下运行或关闭。
针对用户进程,又可以分为交互进程、批处理进程和守护进程三类。
- 交互进程:由一个shell终端启动的进程,在执行过程中,需要与用户进行交互操作,可以运行于前台,也可以运行在后台。
linux操作系统中,有一种特殊的进程称为终端进程,这是系统为每一个终端机所建立的进程,当用户通过终或者端机访问主机时,系统就是在这个终端进程的控制下运行的。
用户通过命令或者其他形式要求计算机完成一定的工作时,终端进程就将这些命令生成一些新的子进程让其独立并发的运行,运行完毕后又被终端进程撤销。
- 批处理进程:该进程是一个进程集合,负责按顺序启动其他的进程。此类进程不必与用户交互, 因此经常在后台运行. 因为这样的进程不必很快响应, 因此常受到调度程序的怠慢。
- 守护进程:守护进程是一直运行的一种进程,经常在linux系统启动时启动,在系统关闭时终止。它们独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。例如httpd进程,一直处于运行状态,等待用户的访问。还有经常用的crond进程,这个进程类似与windows的计划任务,可以周期性的执行用户设定的某些任务。
3. 创建进程
fork()函数是Linux下产生新进程的系统调用,fork意即“分叉”,用来描述: 一个进程在运行中,如果使用了fork()函数,就产生了另一个进程,于是进程就“分叉”了。
本系统调用产生一个新的进程,叫子进程,调用进程叫父进程。
4. 查看进程
在Linux系统中,除了第一个进程(0号进程),其他进程都会有一个或多个子进程,所以进程是树形结构(pstree查看)。
进程编号 0 通常被称为kernel、sched或swapper,而进程编号 1 称为init。 init 是所有进程的父进程或祖先进程。
在系统中,对于过早消亡(die)的进程,它的父进程会被重新指定为init。
在关机时,进程的退出是根据ID由大到小的顺序依次执行,init进程结束后,系统便终止。
PS(process status) 命令用于查看系统正在运行的进程。(某一个时间点上)
ps aux (FreeBSD风格) ps -elf(System V)风格
ps 命令说明:
stat显示的进程状态信息:
- top 系统实时进程信息
top详解连接:
5. 进程的属性
一个进程是一个程序的一次执行过程(有开始有结束),程序是静态的,它是保存在磁盘上的可执行代码和数据集合,进程是一个动态的概念。程序是一个进程指令的集合,它可以启用一个或多个进程,同时,程序只占用磁盘空间,而不占用系统运行资源,而进程仅仅占用系统内存空间,是动态的、可变的,关闭进程,占用的内存资源随之释放。
一个进程由如下元素构成:
- 程序的读取上下文,它表示程序读取执行的状态。
- 程序当前的执行目录
- 程序服务的文件和目录
- 程序的访问权限
- 内存和其它分配给进程的系统资源
Linux一个进程在内存中由3部分数据:数据段、堆栈段、代码段。
- 代码段: 存放了程序代码的数据。(多个进程运行同一个程序时使用同一个代码段)
- 数据段: 存放程序的全局变量、常数及动态分配的数据空间。
- 堆栈段: 存放子程序的返回地址、子程序的参数及程序的局部变量。堆栈段包括在进程控制块(Process Control Block)中。PCB处于进程核心堆栈的底部,无需额外分配空间。
6.Linux 系统的进程状态:
用户状态 进程在用户状态下运行的状态(进程在执行用户自己的代码)
内核状态 进程在内核状态下运行的状态(进程在内核代码中运行)
内存中就绪 进程没有执行,但处于就绪状态,只要内核调度它,就可以执行
内存中睡眠 进程正在睡眠,并且进程存储在内存中,还没有被交换到swap
就绪且换入 进程处于就绪状态,但是必须把它换入内存,内核才能再次调度它运行
睡眠且换出 进程正在睡眠,且被换出内存
被抢先 进程从内核状态返回用户状态时,内核抢先它做了上下文切换,调度了另一个进程,原先那个进程就处于被抢先状态。(在内核状态下的进程不能被其他进程抢占)
创建状态 进程刚被创建。该进程存在,但既不是就绪状态,也不是睡眠状态。这个状态是除了进程0以外的所有进程的最初状态。
僵死状态(zombie) 进程调用exit结束,进程不再存在,但是进程表项中仍有记录(进程描述符存在),该记录可由父进程收集。
一个进程在其生存周期内,处于一组不同的状态下,称为进程状态。进程状态保存在进程任务结构的state字段中。
解释:
- 运行状态(TASK_RUNNING)
指进程正在被CPU执行,或者已经准备就绪(在运行队列中等待,没有被CPU执行,也称为就绪运行状态),随时可由调度程序执行,则称该进程处于运行状态(running)。该状态下有三种情况:用户运行态、内核运行态、就绪态。
- 就绪态:当系统资源已经可用时,进程就被唤醒而进入准备运行状态。
- 可中断睡眠状态(TASK_INTERRUPTIBIE)
进程正在等待系统资源的一种状态,称其处于睡眠等待状态。可中断睡眠状态是指可以被中断的睡眠等待状态。当系统产生一个中断或者释放了进程正在等待的资源时,或者进程收到一个信号,都可以唤醒进程转换到就绪状态(可运行状态)。
- 不可中断睡眠状态(TASK_UNINTERRUPTIBIE)
指不可以被中断的睡眠等待状态,不可以被收到的信号唤醒,只有被wake_up()函数唤醒时,才可以转换到可运行的就绪状态。处在该状态下的进程通常需要不受干扰的等待,或者所等待的事件很快发生。
- 暂停状态(TASK_STOPPED)
当进程收到信号SIGSTOP、SIGTSTP、SIGTTIN或SIGTTOU时,就会进入暂停状态。可以向其发送SIGCONT信号,让进程转换到可运行状态。
- 僵死状态(TASK_ZOMBIE)
进程已经停止,但其父进程还没有调用wait()询问其状态的进程处于僵死状态。(僵死进程的任务数据结构信息(PCB信息)还需要保留着,一旦父进程调用wait(),取得其信息,这些数据结构信息就会被释放。)
7.进程的切换:
当一个进程的时间片用完时,系统就会使用调度程序强制切换到其他的进程去执行。
当进程在内核态执行时,需要等待系统的某个资源时,此进程就会调用 sleep_on() 或 interruptible_sleep_on()就进入睡眠状态(TASK_INTERRUPTIBIE 或 TASK_UNINTERRUPTIBIE),自愿放弃CPU的使用权,而让调度程序去执行其他进程。
只有当进程从 “内核运行态” 转移到 “睡眠状 态” 时,内核才会进行进程切换操作。
在内核态下运行的进程不能被其它进程抢占,而且一个进程不能改变另一个进程的状态。为了避免进程切换时造成的内核数据错误,内核在执行临界区代码时会禁止一切中断。
8.linux 进程与任务调度
Linux提供了许多系统调用函数,用于对进程进行控制,如fork()函数,创建一个新进程; wait()函数,进程等待;exit()函数,进程的自我终止;kill()函数,进程删除;getpid()函数,获取进程的ID;getppid()函数,获取进程之父进程ID等。 这些原语都可以在Linux提供给用户的界面shell上运行。 另外,在Linux中,还有前台进程和后台进程的概念。
在Linux系统中,每个进程都有一个进程号(PID),用于系统识别和调度进程。
启动一个进程有两个主要途径:手工启动 和 调度启动。
- 调度启动是事先设置,根据用户要求自行启动。
- 手工启动进程是由用户输入命令,直接启动一个进程。
手工启动的进程分两种:前台进程 和 后台进程。
(1)前台进程:指运行时在标准输出设备上能看见其运行结果的进程,一般运行单挑命令时,多采用前台方式。
shell执行前台进程的格式: command
对于前台启动的进程,shell会等待进程执行完毕。
(2)后台进程:指运行时看不见运行结果的进程。
shell执行前台进程的格式: command &
对于前台启动的进程,shell并不等待进程执行完毕。
前后台进程调度:
终止进程:
-前台:Ctrl+c
-后台:kill(向内核发送一个系统操作信号和进程的PID,然后内核就可以停止该进程)
前台进程转后台:
Ctrl+z 暂停进程,然后 bg 命令,将当前进程转入后台
后台转前台:
fg (将当前进程转入前台)
多个后台进程的调度方法:
shell 中有两个以上的后台进程同时运行,需要用到 jobs 命令,将一个特定的后台进程带到前台。
jobs 命令是用来判断所有被挂起(停止)的进程、后台作业进程号,以及哪一个是当前进程。
jobs 显示当前会话的作业状态
输出四列: <任务编号> <当前进程> <进程状态> <进程内容>
+ 标志当前进程
- 标志前一个进程
bg 默认将当前进程带入后台
作业号可以与 wait fg kill bg 等命令一起使用,只要在作业号前加上%前缀。
Linux 的任务调度方式:
crontab 和 at
crontab是调度重复性的系统任务;at是在特定时间调用单个系统任务。
9. 利用 pstree 监控系统进程
pstree 命令以树形结构显示程序和进程之间的关系,使用格式如下:
pstree [-a|-c|-n|-p|-u|-l] [-h|-H 进程号]
[进程号 | 用户]
pstree清楚的显示了程序和进程之间的关系,如果不指定进程的PID号,或者不指定用户名称,则将以init进程为根进程,显示系统的所有程序和进程信息,若指定用户或PID,则将以用户或PID为根进程,显示用户或PID对应的所有程序和进程。
例如:
(1)如果想知道某个用户都启动了那些进程的话,pstree 用户名 就可以实现:
详细的包含子进程的信息如下:
(2) 也可以通过指定的PID查看对应的进程信息。
(3) 显示启动时的参数等信息
10. 通过 lsof 监控系统进程
lsof - list open files
lsof全名list opene files,也就是列举系统中已经被打开的文件,通过lsof,我们就可以根据文件找到对应的进程信息,也可以根据进程信息找到进程打开的文件。
lsof指令功能强大,这里介绍“-c,-g,-p,-i”这四个最常用参数的使用。更详细的介绍请参看man lsof。
(1) lsof filename 显示使用filename文件的进程
如果想知道某个特定文件有那个进程使用,可以用 lsof filename 方式得到,例如:
可以得知,/var/log/messages文件是由 进程rsyslogd在使用
(2)lsof -c 进程名 显示进程现在打开的文件
例如:
上例显示了mysqld进程打开的文件信息,
FD列表显示文件描述符,
TYPE列显示文件的类型,
SIZE列显示文件的大小,
NODE列显示本地文件的node码,
NAME列显示文件的全路径或挂载点。
(3)lsof -g 进程号 显示指定的进程组打开的文件情况
例如:
其中,PGID列表示进程组的ID编号。
上面输出,显示了httpd程序当前打开的所有文件、设备、套接字等
(4)lsof -p pid号 通过进程号显示程序打开的所有文件及相关进程,例如,想知道init进程打开了哪些文件的话,可以执行“lsof -p 1”命令,输出结果如下:
(5)lsof -i 通过监听指定的协议、端口、主机等信息,显示符合条件的进程信息
例如:
查看系统中 tcp 协议对应的80端口的进程信息
udp协议 53端口的信息
小结:通过lsof命令能够清楚的了解进程和文件的对应关系。
11. Linux中进程控制
为了让 Linux 来管理系统中的进程,每个进程的 PCB 用一个 task_struct 数据结构来表示。数组 task 包含指向系统中所有 task_struct 结构的指针。 系统中的最大进程数目受 task 数组大小的限制,默认值一般为512. 创建进程时,Linux 将从内存中分配一个 task_struct 结构并将其加入 task 数组。 当前运行进程的结构用 current 指针来指示。 另外,系统中所有的进程都用一个双向链表连接起来,而它们的根是
init 进程的 task_struct 数据结构。 这个链表被Linux核心用来寻找系统中所有的进程,它对所有进程控制命令提供了支持。
所有进程部分时间运行于用户模式,部分时间运行于系统模式。 如何支持这些模式,底层硬件的实现各不相同。用户模式的权限比系统模式的权限小得多。
进程通过系统调用切换到系统模式继续执行,此时核心为进程而执行。
系统启动时总是处于核心模式,此时只有一个进程: 初始化进程。像所有进程一样,初始化进程也有一个由堆栈、寄存器等表示的机器状态。 当系统中有其他进程被创建运行时,这样信息将被存储在初始化进程的 task_struct 结构中。 在系统初始化的最后,初始化进程启动一个核心进程(init),然后保留在 idle 状态。 如果没有任何事要做,调度管理器将运行 idle 进程。 idle 进程是唯一不是动态分配
task_struct 的进程,它对 task_struct 在核心构造时静态定义为 init_task。
由于 init 是系统第一个真正的进程,所以 init 核心进程的标识符为1。 它负责完成系统的一些初始化设置任务,以及执行系统初始化程序,这些初始化程序程序依赖于具体的系统。 系统中所有进程都是从 init 核心进程中派生出来。
当用户登录到系统时,进程 1 为用户创建一个 shell 进程,用户在 shell 下创建的进程一般都是 shell 的子进程,因此 shell 是 该用户所有进程的父进程。 当用户注销时,该进程也被撤销。
进程的终止:
每个进程退出时都调用了 exit(0),这是进程的正常终止。 在正常终止时,exit()函数返回进程结束状态。在子进程调用 exit()后,子进程的结束状态会返回给系统内核,由内核根据状态字生成终止状态,供父进程在wait()中读取数据。若子进程结束后,父进程并没有读取子进程的终止状态,则系统将子进程的终止状态置为“僵死”,并保留子进程的进程控制块等信息,等父进程读取信息后,系统才彻底释放子进程的进程控制块。