linux进程管理


我们在linux下执行的命令,除了shell内置命令,大部分命令都有对应的二进制可执行程序(program)文件,这些命令被称为shell外置命令。
大部分时候这些二进制可执行程序文件静静的存在于磁盘目录中,一旦命令被执行, 命令对应的二进制可执行程序文件就会被加载到内存中运行,变成可被linux内核调度的进程(program)。
在内存中运行的程序(program)称为进程(program)。命令执行完毕,进程也就是退出了。
linux启动后,在运行的过程中,有非常多常驻内存的服务进程在后台运行,这些在后台运行的服务进程称为系统守护进程。
进程是操作系统上非常重要的概念,进程的调度管理也是linux内核最基本最重要的功能之一。 本章就来了解linux下的进程管理。

1.什么是进程(process)

回到顶部

简单来说,进程就是在内存中运行的程序,linux内核为每一个进程分配一个ID,称为PID(进程ID). 我们前面讲过文件权限的概念,一个用户对一个文件的读写执行权限,都和文件的权限属性相关。 那么系统是怎么判断权限的呢?linux会为每个进程分配对应的实际用户(组)ID、有效用户(组)ID和保存的设置用户(组)ID,这些组成了进程的权限属性。 实际上linux正是通过用户所触发进程的有效用户(组)ID来和文件权限属性来进行匹配判断的。
我们以通过cat命令读取/etc/passwd文件为例,简单介绍linux进程,以及进程在权限判断中所承担的角色。
假设当前系统登录用户为peter,/etc/passwd文件的权限属性如下所示:

              [peter@initroot ~]# ls -l /etc/passwd
              -rw-r--r-- 1 root root 2589 Jan  7 14:54 /etc/passwd              
              
很明显peter用户对/etc/passwd文件来说是其他用户other,peter对该文件的权限为r--,只能读取该文件。
我们通过whereis命令查找到cat命令对应的二进制可执行程序文件,并通过ls -l命令查看该文件的权限属性:
                [peter@initroot ~]# whereis cat
                cat: /bin/cat /usr/share/man/man1/cat.1.gz
                [peter@initroot ~]# ls -l /bin/cat 
                -rwxr-xr-x 1 root root 35064 Jan 18  2018 /bin/cat
              
cat命令的二进制可执行程序文件为/bin/cat, peter用户对/bin/cat文件来说是其他用户other,对该文件的权限为r-x,可以读取和执行该文件。 也就是说peter用户有权限运行cat命令。于是peter在shell命令行下输入cat /etc/passwd命令。
我们在前面讲解过linux命令的执行原理。此时,shell会产生一个子进程,该子进程会将cat命令对应的二进制可执行程序文件/bin/cat加载到内存中运行,子进程变为cat进程。 因为是peter用户触发的cat进程并且/bin/cat文件没有设置用户(组)ID位,所以系统会将cat进程的实际用户ID和有效用户ID设置为peter的用户ID。
cat进程在打开并读取/etc/passwd文件的时候,就会和/etc/passwd文件相应的权限属性进行匹配判断, 我们这个例子中cat的有效用户ID为peter的用户ID,很明显cat进程有权限读取/etc/passwd, 于是cat进程读取/etc/passwd文件并将文件内容输出到屏幕,最后cat进程退出,回到shell命令行。
以上就是cat命令读取/etc/passwd文件的大概过程,以及linux是如何进程权限判断的。 这里面涉及到的一些概念,特别是实际用户(组)ID、有效用户(组)ID和保存的设置用户(组)ID,我们会在后面详细介绍。

2.进程与程序(process & program)

回到顶部

通过上面的描述,我们已经非常详细的给出了程序和进程的概念了。 程序就是二进制可执行程序文件,通常存储在磁盘目录中。 程序被加载到内存后就被称为进程。进程就是在内存中运行的程序。
程序和进程其实就是同一段计算机执行指令集合的两种不同存在形式。 他们其实是一个东西的两种称呼,在磁盘中就叫程序,加载到内存后就叫进程了。 每个进程都有唯一的标识符称为进程ID,即PID。系统通过PID识别进程。 有一些进程是用户在shell命令行下执行命令而触发的。 还有些常驻内存的进程,这些进程运行在后台,不需要控制台终端,随linux系统启动而被加载运行,这类进程被称为linux守护进程。
不同用户执行同一个程序而产生的进程,会取得不同的权限属性,例如我们上面peter执行cat命令, 那么产生的cat进程的实际用户(组)ID和有效用户(组)ID就是peter的用户(组)ID。 root的用户(组)ID为0,UID/GID = 0/0,如果是root执行cat命令,那么产生的cat进程的实际用户(组)ID和有效用户(组)ID就是0了,cat进程就会拥有root的权限了。
再比如每个用户在登录的时候,都会取得一个登录shell,可以通过/etc/passwd文件每行的最后一个字段查看每个用户的登录shell的可执行文件路径。 大部分用户包括root的登录shell可执行程序文件为/bin/bash。
用户登录后,login进程会通过/etc/passwd文件获得用户登录shell的可执行文件路径/bin/bash,将shell的二进制可执行程序文件/bin/bash加载到内存中,自此shell进程便产生了。
而这个登录shell的权限属性就对应了相应的登录用户的权限属性。例如登录用户为peter,那么shell进程的实际用户(组)ID和有效用户(组)ID就是peter的用户(组)ID。 而当用户在该登录shell中执行命令而产生的所有子进程,也都会继承shell的权限属性。
另外进程的权限属性还和二进制可执行程序文件的设置用户(组)ID位有关,关于文件的设置用户(组)ID位我们在讲解文件权限属性的时候已经提到过,稍后我们还会对相关知识进行讲解。
实际上,linux下所有的用户进程都是由一个进程衍生出来的,那就是init进程,init进程是系统开启的第一个用户进程,此后的所有用户进程都是有init进程分化出来。 例如init进程会产生login进程,login进程用来提示用户登录,用户登录后login进程就会产生出shell进程,用户在shell命令行下执行命令,例如上面的cat命令,shell就会产生相应的命令进程。 上面init进程是login进程的父进程,login进程就是shell进程的父进程,shell进程又是cat进程的父进程。反过来cat进程是shell进程的子进程,shell进程是login进程的子进程, login进程是init进程的子进程。这样进程之间就会存在父子关系,每个进程都会有一个父进程,一个进程可能会产生一个或多个子进程,拥有共同父进程的进程互为兄弟进程。 从任何一个进程往上就会追溯到init进程,init进程是所有进程的祖先。系统中所有的进程就会形成一个以init进程为树根的树状进程家谱。 可以通过pstree命令查看系统中所有进程组成的父子进程树。
我们刚才提到shell进程是登录用户登录后,由login进程产生的进程,shell进程就是login进程的子进程。shell的二进制可执行文件为/bin/bash, 那么我可不可以在shell命令行下再执行shell呢?答案是肯定的。在命令行下执行一个二进制可执行文件,只需要提供该文件的路径就可以了。
不过/bin/bash文件所在的目录已经存在$PATH环境变量中了,所以只需要在bash下直接输入bash就可以启动另一个shell了,相当于bash也是一个命令了。
同理,login进程和init进程也有对应的命令,例如可以在shell下输入login命令,系统就会给出登录提示,该命令需要root权限。
输入init命令就会重启进入不同的运行级别,关于init命令我们后面会详细介绍。
除了刚才提到的pstree命令,还可以通过ps命令观察系统中运行的进程信息。 这里先做个简单的演示,后面会详细讲解ps和pstree命令。
在命令行下输入bash命令,启动另一个bash。

              [peter@initroot ~]# bash
            
这样我们就进入到子进程bash的环境中了,在新bash的命令行下输入ps -l命令:
              [peter@initroot ~]# ps -l
              F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
              0 S  1000  1743  1736  0  80   0 -  6115 wait   pts/0    00:00:00 bash
              0 S  1000  3126  1743  0  80   0 -  6116 wait   pts/0    00:00:00 bash
              4 R  1000  3135  3126  0  80   0 -  7663 -      pts/0    00:00:00 ps
            
从上面的输出中,可以看出每个进程都有很多信息字段,我们主要关注PID和PPID这两个字段,其中PID为进程的ID,PPID为进程的父进程ID。 我们可以看到ps进程的PPID为3126,第二个进程的PID为3126,所以第二个bash进程就是ps进程的父进程。 第二个bash进程的PPID为1743,而第一个进程的PID为1743,所以第一个bash进程就是第二个bash进程的父进程。 这样这三个进程就组成了父子关系:bash(1743)---bash(3126)----ps(3135)。
我们通过pstree -p命令即可观察到这种父子关系:
              [peter@initroot ~]# pstree -p
              ...省略...gnome-terminal-(1736)─┬─bash(1743)───bash(3126)───pstree(3164)
            
上述省略掉了部分输出,注意看上面的bash(3126)的子进程变成了pstree(3164),因为我们执行的是pstree命令,所以就变成pstree进程了。 另外bash(1743)的父进程不应该是login进程吗? 怎么是gnome-terminal-(1736)这样一个进程呢? 我们上面一直在说是login进程启动了bash进程,其实上面描述的是从本地机器通过命令行模式登录的情形。 这里是在图形用户界面模式下通过terminal工具运行的命令,所以如果你平时是通过terminal来学习linux命令,那么bash进程其实就是由terminal进程启动的。 可以通过ctrl+alt+[F1-F7]快捷键切换到命令行终端,登陆后运行pstree -p命令,就会发现登录bash是有login进程启动的:
              [peter@initroot ~]# pstree -p
              ...省略...login(6643)───bash(7068)───bash(7543)───pstree(7553)
            
如果你是通过ssh远程登录的linux主机,那么就会发现bash进程是由sshd进程启动的:
              [peter@initroot ~]# pstree -p
              ...省略...sshd(23900)───bash(23904)───bash(23951)───pstree(23964)
            
直接输入exit即可退出当前bash。
上述两个bash进程的关系如下图所示: 父子bash进程关系 我们经常遇到这样的问题,有时候一个进程占用的内存空间太大,而这个进程在系统中可有可无。通常我们会用kill -9命令将该进程杀掉, 但是过了一段时间后,你会发现这个被杀掉的进程又出来了,严重托慢了系统的运行效率,真的非常让人恼火。 如果排除了crontab的原因,那么大部分可能的原因就是该进程是被父进程启动的,我们只需要找到这个进程的父进程,然后再酌情处理就可以了,比如将父进程也杀掉就可以了。

1.程序与进程

回到顶部

通过前面的讲解,我们大致了解了linux命令的来龙去脉,不管是linux命令,还是bash shell,都是计算机程序软件,都有自己的可执行程序文件。 这些可执行程序文件大部分都由c/c++程序设计语言编写的源程序代码文件通过编译链接而来. 这些可执行程序文件都存放在磁盘的某个目录中。根据计算机原理我们知道,计算机程序需要加载到内存中才能由cpu执行。 bash shell的可执行程序文件/bin/bash是什么时候加载到内存的呢? 大部分情况下bash shell是在操作系统启动后,用户登录以后就会将bash shell可执行程序文件加载到内存中执行,而负责用户登录的程序是login。 所以登录shell是由login进程启动的。 这里提到login进程?为什么不说login程序呢? 我们将存放在磁盘目录中的程序可执行文件称为程序,而程序一旦加载进内存,就成为进程了。 可以认为进程就是运行中的程序.

3.命令执行过程fork and exec

回到顶部

我们在前面很多地方已经多次提到一条shell外置命令的执行过程了,简单来说你在shell命令行下输入一条命令,shell会创建一个新的子进程, 子进程将命令对应的二进制可执行程序文件加载到内存中执行,shell创建一个新子进程的过程叫fork,fork为分叉分支的意思,创建新进程也就是创建一个新的分支 进程加载二进制可执行程序文件到内存的过程称为exec,exec为execute的缩写,表示加载执行的意思,所以整个流程称为fork-and-exec流程。 linux子进程加载执行新程序采用写时复制技术:
(1)shell先fork出一个和自己一模一样的子进程,除了父子进程的PID和PPID不同外!此时子进程中的程序代码数据和父进程shell一模一样。
(2)子进程exec加载执行命令对应的二进制可执行程序文件,将子进程中的执行代码替换为新的执行代码;
系统或网络服务:常驻在内存的进程 到目前为止,我们接触的大部分命令的执行过程都很快,例如ls、touch、rm、mkdir、cp、mv、chmod、chown、passwd等等, 这些命令执行完就结束了。也就是说这些命令执行所产生的进程很快就会终止退出,并不会一直占用内存。 linux里面还有很多进程,这些进程随linux系统的启动而被加载运行,此后便会一直常驻在内存中, 除非linux关机或者崩溃死机,否则这些进程就会一直在内存中运行,这些进程运行在后台,不需要控制台终端,这类进程被称为linux守护进程(daemon)。
一般linux守护进程大多是提供某种系统服务功能的进程,这些服务进程的名字都会在可执行文件名的基础上加上d,表示daemon的意思。 例如需要周期扫瞄/etc/crontab文件而执行例行性工作的crond和atd,提供系统日功能的rsyslogd等。 另外还有很多提供网络服务(server)功能的daemon进程,例如提供域名服务的named,提供www服务的nginx和apache,提供邮件服务的postfix,提供ftp服务的vsftpd, 提供远程连接服务的sshd等等,这些服务进程都是我们后面在服务器运维中经常见到的守护进程,也是linux服务器运维工作的基础和重点。 这些网络服务进程在开启后都会监听某个网络端口(port),以便客户端(client)可以连接相应的服务。

4.进程角度理解Linux的多用户多任务多终端环境

回到顶部

了解了linux进程,应该对linux的多人多用户机制有了更深的理解。不同的用户登陆后,都会启动该用户相对应的登录shell进程, 而不同用户的shell进程在启动的时候,都可以读取相应用户的shell环境配置文件~/.bashrc,从而每个用户都可以根据自己的喜好设置shell环境。
不同用户启动的进程都具有该用户相应的权限。 linux中运行的进程都是相互独立的,除非进程间存在通信机制,否则每个进程在内存中都是独立运行的,互不干扰。 linux通过进程调度确保每个进程都能得到公平的运行,所以即使系统中有多个用户, 但是对每个用户而言就像是自己在独占这台linux主机,看上去这台linux主机就只有自己在使用一样。
同样,我们前面提到过通过ctrl+alt+[F1-F7]组合键可以切换linux的多终端窗口,也是通过多进程实现的。每个虚拟终端就是一个tty进程。 我们可以通过配置设置tty虚拟终端的个数,也就是设置系统启动的tty进程个数。这方面的内容我们会在开启启动流程中介绍。 linux的多终端对进程管理也有很大帮助,比如我们在某个终端界面下系统死机了,怎么动都动不了,做什么都没有反应。 这时候可以尝试用ctrl+alt+[F1-F7]组合键切换到其他的终端机界面,然后用ps和top等进程管理工具找出使系统出错的那个进程, 然后使用kill命令杀掉该进程。最后再回到刚才出错的终端界面,基本就可以回复正常了。

initroot编辑整理,转载请注明www.initroot.com

100次点赞 100次阅读