进程是程序的动态表现,是消耗系统资源的活动个体。在进程运行的过程中,用户有权限对这些进程进行控制,因此可以说用户是进程的管理者和控制者。本小节主要对进程的概念、进程基本信息的获取和进程状态控制进行介绍。
进程是操作系统结构的基础,是一个具有一定独立功能的程序关于某个数据集合的一次运行活动,是一个能够申请和使用系统资源的活动实体。
从狭义方面来说,进程是一段程序执行的过程。从广义方面来说,进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动,它是操作系统动态执行的基本单元。
关于进程的概念主要有两点:
1)进程是一个实体:每个进程都有独立的地址空间,一般包括文本区域、数据区域和堆栈三部分。其中,文本区域用于存储处理器执行的代码,数据区域用于存储变量和进程执行期间使用的动态分配的内存,堆栈区域用于存储活动过程调用的指令和本地变量。
2)进程是一个“执行中的程序”:程序是一个没有生命的实体,只有处理器赋予程序生命时,它才能成为一个活动的实体,成为活动的实体后就称为进程。
进程是操作系统中最基本、最重要的概念之一,它是在并发系统出现后为了更形象地描述系统内部出现的有规律的动态活动而引进的一个概念。进程通常由多种基本的元素组成,具体组成如下:
●进程当前的上下文,就是进程当前的执行状态。
●进程当前的执行目录。
●进程访问的文件和目录。
●进程的访问权限。
●内存和其他分配给进程的系统资源。
系统的内核是通过进程来控制对CPU和其他系统资源的访问的,并且决定在CPU上运行哪个程序、运行时间以及以怎样的特性运行等。系统内核的调度器负责为所有进程分配使用CPU资源的时间(也就是进程获取CPU执行权的时间),这个时间称为时间片(Time Slice),每个进程使用完自己的时间片后,CPU资源就被分配给其他的进程使用。
在进程启动后,操作系统为每一个进程分配一个唯一的进程标识符,称为进程ID(PID,是一个整数)。对于系统中的所有进程,它们都起源并受控于ID为1的init进程,该进程负责引导系统、启动守护(后台)进程和运行必要的程序。
操作系统的一个重要功能是为进程提供内存空间并对进程进行管理,系统在开始初始化后,首先产生init进程,该进程是系统所有进程的父进程,系统中其他进程的整个生命周期都受到它的控制,当然也包括创建、执行、撤销和消亡。
系统进程间的关系属于典型的“父子关系”,每个进程的产生都是从其父进程那里获取相关的数据(除init进程之外),这些进程会不断新生和死亡(但这并不意味着进程的数量会一直增加),因此这些进程之间的关系就像一棵倒立的树,而树的最顶层的进程就是init进程(根进程),其他的进程就是这个进程树中的一个节点。
[root@named ~]# pstree systemd─┬─NetworkManager───2*[{NetworkManager}] ├─VGAuthService ├─auditd───{auditd} ├─crond ├─dbus-daemon ├─login───bash ├─lvmetad ├─master─┬─pickup │ └─qmgr ├─polkitd───5*[{polkitd}] ├─rhnsd ├─rhsmcertd ├─rsyslogd───2*[{rsyslogd}] ├─sshd───sshd───bash───pstree ├─systemd-journal ├─systemd-logind ├─systemd-udevd ├─tuned───4*[{tuned}] └─vmtoolsd───{vmtoolsd}
对于系统中的每个进程,在启动时系统都会为其分配一些特定的信息,这些信息可以直接反映出进程的作用、状态等。
关于进程信息的获取,可以使用ps命令来查看系统中当前用户所启动的进程及相关信息。
[root@system ~]# ps -ef UID PID PPID C STIME TTY TIME CMD …… squid 1238 1235 0 06:49 ? 00:00:00 (squid) -f /etc/squid/squid.conf root 1252 1 0 06:49 tty1 00:00:00 /sbin/mingetty/dev/ tty1 root 1965 1811 0 Apr09 pts/1 00:00:00 -bash root 2149 1965 1 00:36 pts/1 00:00:00 ps -ef
输出字段相关参数说明:
●UID:运行该进程的用户ID,不过它们会映射成用户名。
●PID:当前进程的ID。
●PPID:当前进程的父进程ID。
●C:进程当前使用CPU资源的百分比。
●STIME:进程启动的日期。
●TTY:进程使用的虚拟终端。
●TIME:进程执行消耗的CPU时间。
●CMD:启动进程的命令。
实际上,ps命令与不同的选项组合时输出的信息不同,以下是该命令与其他选项结合时输出的进程状态符号及相关的描述:
●PRI:进程的优先级(数值越大,优先级越低)。
●ADDR:进程的内存地址。
●SZ:进程已使用的交换空间。
●WCHAN:处于等待(资源)状态的进程。
●VSZ:进程占用的虚拟内存量(以KB为单位)。
●RSS:进程使用过的且未被释放的物理内存。
●STAT:当前进程的状态(状态码通常由两个字符组成)。
●VIRT:进程所使用的虚拟内存总量。
●SHR:进程与其他进程共享的内存量。
关于ps命令的更多用法,可使用--help来获取。
另外,对于进程的状态,还可以使用systemctl命令来获取,前提条件是这个进程属于系统的后台进程。
[root@system ~]# systemctl status httpd.service
httpd.service - The Apache HTTP Server
Loaded: loaded (/usr/lib/systemd/system/httpd.service; disabled)
Active: active (running) since Sun 2019-01-17 21:30:43 CST; 30min ago
Docs: man:httpd(8)
man:apachectl(8)
Main PID: 2757 (httpd)
Status: "Total requests: 0; Current requests/sec: 0; Current traffic: 0 B/sec"
CGroup: /system.slice/httpd.service
?..2757 /usr/sbin/httpd -DFOREGROUND
?..2758 /usr/sbin/httpd -DFOREGROUND
?..2759 /usr/sbin/httpd -DFOREGROUND
?..2760 /usr/sbin/httpd -DFOREGROUND
?..2761 /usr/sbin/httpd -DFOREGROUND
?..2762 /usr/sbin/httpd -DFOREGROUND
Jan 17 21:30:43 system httpd[2757]: AH00558: httpd: Could not reliably determine the server's fully ...ssage
Jan 17 21:30:43 system systemd[1]: Started The Apache HTTP Server.
Hint: Some lines were ellipsized, use -l to show in full.
系统在初始化时实际上只建立了一个init进程,不过内核并不提供直接创建新进程的系统调用,因此init进程之外的所有进程都是通过fork机制来创建的,而且所有的进程都只存在于内存中。
每个进程在内存中都拥有属于自己的一个独立空间(Address Space),这个空间可以说是进程的发源地,在进程被创建之前系统就在内存中开辟的一个新空间,将正在运行中(某种功能)的进程数据复制到新的空间中,并为新建的进程赋予PID(新的进程中还存放着它的父进程号,就是PPID)。
实际上,使用fork()函数创建进程时会有两次返回,分别是将子进程的PID返回给父进程和将0返回给子进程。另外,子进程与父进程之间允许相互查询进程号(也就是子进程可以查询其父进程的ID,父进程也可以查询其子进程的ID)。
关于子进程和其父进程的ID,可以通过以下命令来查询:
[root@system ~]# ps -o pid,ppid,cmd PID PPID CMD 11035 11033 -bash 11070 11035 ps -o pid,ppid,cmd
对于每个子进程,当它被终止时它会通知父进程并将自己所占用的资源释放,同时要在内核中留下自己的退出信息,这些信息主要包括退出码(Exit Code,正常终止为0,错误或异常状况大于0,这些退出代码都是正整数)和进程终止的原因。父进程收到其子进程退出的信息后,调用wait()函数从内核中提取子进程的退出信息,并清空该信息所占用的内核空间。
当然,如果出现父进程先于其子进程终止,就会导致其子进程成为一个孤立进程,这时init就成了这个子进程的父进程,并负责子进程的退出处理。而如果父进程没有调用wait()函数来处理退出的子进程在内核中的信息,就会导致子进程成为僵死进程,如果有大量的僵死进程,就会占据大量的内核空间,系统会变得缓慢。
在用户空间中,如果要终止一个进程,可以向这个进程发送一个信号,系统中所支持的信号可通过带-l选项的kill/pkill命令来获取,常用于终止进程的信号及相关说明如表3-2所示。
表3-2 kill/pkill命令的常用信号描述
对于一个进程,要杀死它通常先获取进程号,或直接使用进程的名字,但如果一个进程被多次启动,就会存在多个ID,这时可通过killall命令来将该进程全部杀死。