Linux学习-进程

这篇博客详细介绍了Linux中的进程概念,包括进程的定义、内核如何管理进程、进程间的服务,以及如何创建、控制和管理进程。讨论了进程的生命周期,如死亡、僵尸进程、孤儿进程和init进程的作用。还讲解了前台与后台进程的区别,作业控制、挂起、恢复和显示进程信息的方法,以及如何使用kill和nice命令来管理和终止进程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

正在学习Linux,记录一下学习历程。奋斗


定义

进程:一个正在执行或准备执行的程序。

更精确地讲,进程就是一个加载到内存中正在执行或准备执行的程序,再加上程序所需的数据,以及跟踪管理程序状态所需的各种信息。


内核管理进程

所有的进程都由内核管理,内核是操作系统的核心部分。

当进程创建时,内核赋予其一个唯一的标识号,这个标识号称为进程ID或PID。为了跟踪管理系统中的所有进程,内核维护了一个进程表。按照PID索引,每个进程再进程表中有一个条目。进程表中的条目除了PID,还包含有描述及管理进程所需的信息。

系统中有许多进程,有一些进程是由用户运行的程序,但是,大多数进程是自动启动的,再后台执行任务。

这些进程需要共享系统的资源:处理器,内存,I/O设备,网络连接等。

为了管理这样一个复杂的工作负荷,内核提供了一个复杂的调度服务,称为调度器。

调度器一直维护着一个所有正在等待执行的进程的列表。通过使用特定的算法,调度器每次选择一个进程,给予这个进程再一个短暂的时间间隔(称为时间片)中运行的机会。(在多处理系统中,调度器可以一次选择多个进程)

每次进程结束时间片时,内核需要中断进程。内核保存每个被中断进程的数据(如程序中下一条指令的位置,环境的副本等)。当该进程再次启动时,它能够在中断处继续执行。


内核为进程提供服务

内核为进程提供基本的服务:

1. 内存管理(虚拟内存管理,包括分页)

2. 进程管理(进程创建,终止,调度)

3. 进程间通信(本地,网络)

4. 输入/输出(通过设备驱动程序,即与物理设备时间通信的程序)

5. 文件管理

6. 安全和访问控制

7. 网络访问(如TCP/IP)

当进程需要内核提供服务时,它就通过系统调用发送请求。

如:

进程使用系统调用初始化I/O操作。

当编写程序时,系统调用的使用方法取决于所使用的编程语言。如在C程序中,使用的是标准库中的函数。


创建&使用进程

常用的系统调用
fork创建当前进程的一个副本
wait等待另一个进程结束执行
exec在当前进程中执行一个新进程
exit终止当前进程
kill向另一个进程发送一个信号

以shell为例:

shell是一个充当用户界面和脚本解释器的程序。正是shell才允许你输入命令,以及间接访问内核服务。

一旦shell开始运行,它也是一个进程,是众多系统进程中的一个。

和所有的进程一样,shell也拥有自己的PID(可以通过一个特殊的shell变量$显示),并且在进程表中拥有自己的条目。

内部命令直接由shell解释,不需要创建新的进程。

外部命令需要shell运行一个单独的程序,因此每当运行外部命令时,shell必须创建一个新进程。

shell先使用fork系统调用创建一个新进程,原始进程称为父进程,新进程成为子进程。一旦进程分叉(fork)成功,就发生两件事:首先,子进程使用exec系统调用将它自身从运行shell的进程变成运行外部程序的进程。其次,父进程使用wait系统调用暂停,直到子进程结束执行。最终,外部程序结束,此时子进程使用exit系统调用停止自身。


进程死亡&僵尸进程

每当进程永远停止时,不管是由于什么原因停止,我们都称进程死亡或终止。每当进程死亡时,进程所使用的所有资源(内存,文件等)都将被释放,从而可以被其他进程所使用。

此时,称被杀死的进程为僵尸进程。尽管僵尸进程是死的,而且已经不再是一个真正的进程,但是它仍然在进程表中保留者自己的条目。因为该条目包含只最近死亡的子进程的数据,而父进程可能对这些数据感兴趣。

在子进程成为僵尸进程后,父进程(一直在耐心地等待着子进程死亡)立即被内核唤醒。现在父进程有机会查看进程表中成为僵尸进程的子进程的条目,看看发生了什么结果。然后内核将进程表中的僵尸进程条目移除。


孤儿进程

如果父进程分叉之后,出乎意料地死亡,那么子进程还会继续执行,但是它已经成为孤儿。

孤儿进程仍然可以继续完成工作,但是当它死亡时,没有父进程被唤醒。最终,死掉的孤儿进程,现在以僵尸进程的形式存在,被遗忘在一边。

以前,孤儿僵尸进程将永远停留在进程表中,直到系统重新启动。

现在,孤儿进程将自动地被#1进程,即init进程(后面会讨论)收养。通过这种方式,每当孤儿进程死亡时,init进程充当替身父进程,快速地清理孤儿僵尸进程。

幸运的是,这种现象并不常见。如果一个程序偶然以这种方式创建了一个不死的僵尸进程,那么将没有直接的办法清除它。毕竟,如何去杀死那些已经死掉的东西呢?


区分父进程与子进程

当fork系统调用结束它的工作时,它向父进程和子进程各传递一个数值,这个数值称为返回值。

子进程的返回值为0,父进程的返回值为新创建进程的PID.

因此,在分叉(fork)操作完成后,进程可以通过简单地查看返回值来识别它是父进程还是子进程。

如果返回值为0,则为子进程。如果返回值大于0,则为父进程。

以shell为例:

在shell 分叉(fork)之后,有两个相同的shell。为了识别哪个进程是父进程,哪个进程是子进程,每个进程都检查它从fork接收到的返回值。拥有大于0的返回值的shell知道它是父进程,因此它使用wait系统调用使自己暂停。拥有0返回值的shell知道它是子进程,因此它使用exec系统调用运行外部程序。


init进程

进程通过分叉(fork)创建,每个进程都是由另一个进程创建的,都拥有自己的父进程。

那么,在某个地方,必须有第一个进程,是其系统中其他所有进程的父进程。

在引导过程(启动操作系统的一组复杂步骤)的末尾,内核“手动”创建了一个特殊的进程,而不是通过分叉(fork)。

这个进程的PID为0。在执行一些重要的功能(例如初始化内核所需的数据结构)后,#0进程进行分叉(fork),创建#1号进程。然后#0进程执行一个非常简单的程序,该程序实质上是一个无穷的循环,不做任何事情(因此将该进程命名为空闲进程)。每当没有进程等待执行时,调度器就运行空闲进程。当#0号进程变成空闲进程时,它就已经有效地完成了它地目的,然后消失。实际上,如果使用ps(稍后讨论)显示#0进程的状态,那么内核将否认该进程的存在。

#1进程执行设置内核及引导过程所需的剩余步骤。基于这一原因,称它为初始化进程,并将该进程的实际程序命名为init。

具体而言,init进程打开系统控制台,挂载根文件系统,然后运行包含在文件/etc/inittab中的shell脚本。在这一过程中,init多次分叉,创建运行系统所需的基本进程,并允许用户登录。在这一过程中,init成为系统中所有其他进程的祖先。

与空闲进程(#0)不同,初始化进程(#1)永远不会停止运行,它是进程表中的第一个进程,而且它一直存在于进程表中,直到系统关闭。

在系统完成后,有时候仍然调用init来执行重要操作,如收养孤儿。


前台进程&后台进程

当运行程序时,输入和输出通常与终端相连。对于基于文本的程序来说,输入来源于键盘,输出定位到显示器,这样才有意义,因为大多数程序为了完成任务需要与用户进行交互。

但是,有一些程序可以自动完成,不需要终端的合作。

在命令的末尾键入&,这将告诉shell 该程序因该自己执行。在这种情况下,shell 不会等待程序结束。程序一启动,shell就重新获得控制,并显示一个新提示。

前台进程:当shell在提示让用户输入一条新命令之前等待当前程序结束

后台进程:当shell启动一个程序,但是又让该程序自己运行

当前台进程运行时,从键盘读取数据,将结果写入到显示器,不会出现任何问题。

后台进程会稍显复杂:

当后台进程运行时,若希望从键盘读取数据,它会一直等待,因为后台进程的标准输入与/dev/null相连。除非使用fg命令(稍后讨论)将进程移至前台。

将输出显示到显示器上是没有问题的,只是有可能会和正在处理的其他事情混杂在一起,使结果陷入混乱。

另外,后唐进程不响应intr信号(^C键)和quit信号(^\键),只能通过kill命令终止程序。

一般情况下,后台程序适用于从文件中获取数据,并将数据写入另一个文件中。不涉及与终端的交互。如:源程序的编译。


创建延迟

sleep interval [s|m|h|d]

interval是延迟的时长。

默认是以秒作为单位。还可以指定其他单位:s秒(默认),m分钟,h小时,d天。


作业控制

作业:每条输入的命令

作业号:也称作业ID,用来唯一地标识一个作业

允许同时运行多项作业,其中一个作业在前台运行,其他作业在后台运行。作业可以有选择性地挂起(暂停),重新启动,在前台和后台之间切换,以及显示状态。

这样做时,需要使用一系列的命令,变量,终端设置,shell变量和shell选项。

作业控制命令
jobs显示作业列表
ps显示进程列表
fg将作业移至前台
bg将作业移至后台
suspend挂起当前shell
^Z挂起当前前台作业
kill向作业发送信号:默认情况下,终止作业


变量
echo $$显示当前shell的PID
echo $!显示上一条移至后台的命令的PID


终端设置
stty tostop挂起试图向终端写数据的后台作业
stty -tostop关闭tostop


shell选项
set -o monitor允许作业控制
set +o nomonitor关闭monitor
set -o notify当后台作业结束时立即通报
set +o nonotify关闭notify

进程和作业之间的区别:

进程是正在执行或准备执行的程序。作业指解释整个命令行所需要的全部进程。

进程由内核控制。作业由shell控制。

内核使用进程表来记录进程,shell使用作业表来记录作业。

如以下命令:

who | cut -c 1-8 | sort | uniq -c

该命令生成4个进程,一个作业。

date; who; uptime

该命令生成3个进程,一个作业。


在后台运行作业

在命令末键入一个&

每次在后台运行作业时,shell会显示作业的作业号和进程ID。

shell从1开始为作业分配作业号。

每当后台作业结束时,shell都会发送一个短的状态消息,通知作业的结束。

为了不打扰此刻正在做的事情,不会立刻通知,直到显示下一个shell提示。


挂起作业

任何时候,每个作业都处于3种状态中的一种:前台运行,后台运行,暂停(等待信号恢复执行)

挂起:暂停,临时中止作业

^Z键可以挂起当前前台作业

如果希望恢复挂起,可以使用fg命令将该作业移回前台


挂起shell

按下^Z键将挂起在前台运行的任何作业,但是有一个进程不会挂起,就是当前shell。

如果希望暂停当前shell,则需要使用suspend命令。

同样可以使用fg命令接挂。


显示作业列表

jobs命令可以显示所有作业的列表。

包括作业号,作业标记,作业状态,作业内容。

-l 选项会额外显示作业所包含的进程的PID。

许多命令(如fg),如果没有指定作业号,就默认作用于当前作业。

作业标记跟在作业号之后,用+,-表示。

+代表是当前作业(如果有挂起的作业,就是最近挂起的作业,如果没有挂起作业,就是最近移至后台的作业)

-代表是前一个作业(队列里的下一个作业)


将作业移至前台

fg  将当前作业移至前台并执行

也可以指定作业号。

作业号
%%当前作业
%+当前作业
%-前一个作业
%n作业#n
%name含有指定命令名的作业(name不需要完整,但要足够长到足以唯一识别一个作业)
%?name命令的任何位置含有name的作业

也可以省略fg,指用作业号作为命令。


将作业移至后台

bg命令可以作业移至后台执行

默认是当前作业,也可指定作业号。

如果要将当前前台的作业移至后台执行,先用^Z挂起该作业,然后用bg移至后台执行。

因为无法在前台作业正在执行的时候进行使shell执行bg命令。


显示进程信息

ps命令

更多内容参见Linux学习-ps显示进程信息


监控系统进程

ps只显示进程的静态快照,及瞬间的进程状态。

但是进程是动态的,需要查看各种进程如何不断变化。

top程序可以每隔几秒显示整个系统的统计更新,并且实时显示最重要的进程(使用CPU时间最多)的信息。

选项:

-d 指定刷新间隔(单位为秒)

-n 指定刷新总次数

-p 指定进程ID

退出top,按q或^C。

按<Space>可以强制它top立即刷新。


显示进程树

系统中所有的进程都被安排到一个大型的树型层次结构中,初始化进程位于树的根部。

pstree可以显示这棵进程树。

选项:

-n 按PID对进程进行排序(默认是按进程名称)

-p 显示每个进程的PID

-A 强制使用ASCII字符绘制

-G 强制使用行绘制字符

-a 显示每个进程的整个命令行,而不只是程序的名称

-u 显示当子进程以不同于父进程的用户标识运行时所发生的改变


如何收集进程信息

在进程表中可以查看一些基本数据。但是,大多数动态信息必须从内核中获取。

ps和top程序使用一种称为proc文件的伪文件。

zai/proc目录中,每个进程都用自己的proc文件表示。当需要进程的现象时,就读取该进程的proc文件,并最终触发一个向内核请求提供所需数据的请求。


杀死进程

kill 进程ID|作业号

注意,进程ID一般为4位数字,作业号以%开头。

有一种常见情况:

当前台进程失去响应时

可以尝试按^Z挂起该进程,然后使用ps或jobs查找该进程,然后使用kill终止。

也可以打开一个新的终端窗口,使用ps -u列举你用户标识运行的所以有进程,然后确定是失控的进程,用kill终止。

选项:

-9  确定杀死,在杀死进程时不允许进程释放正在使用的资源(最后一种选择,不推荐)


向进程发送信号

kill [-signal] pid...|jobid...

signal 是希望发送的信号类型。

进程间通信是两个进程之间的数据交换。kill程序就是一种进程间通信方式,发送一个非常简单的消息,被称为信号。

信号只不过是发送给进程的一个数字,从而让进程知道发生了哪些类型的事情。进程会识别信号,从而决定执行什么动作。

使用kill -l 可查看系统所支持的全部信号列表。

常用信号
编号名称缩写描述
1SIGHUPHUP中止:注销或者终端失去连接时发送给进程
2SIGINTINT中断:当按下^C时发送
9SIGKILLKILL杀死:立即终止,进程不能捕获
15SIGTERMTERM终止:请求终止,进程不能捕获
18SIGGONTGONT继续:恢复挂起的进程,由fg或bg发送
19SIGSTOPSTOP停止(挂起):当按下^Z时发送

kill默认情况下发送信号TERM。


设置进程的优先级
nice [-n adjustment] command

adjustment是一个数值,command是希望运行的命令

通常是在后台运行且需要使用大量CPU时间的程序(如编译程序)

可以指定0~19的nice值,数值越高,优先级越低。

程序的默认优先级是0。

nice的默认设置是10。


改变现有进程的优先级

renice可以改变现有进程的优先级。

renice niceness -p processid

niceness是nice值,processid是PID


守护进程

不是由用户运行的程序,在后台运行,完全不与任何终端连接,目的是体哦嗯服务。

一般情况下,守护进程静静在后台等待某些事情的发生:事件,请求,中断,给定的时间间隔等。

当触发发生时,守护进程进入动作,完成执行作业所需的事情。

如:

init是一个守护进程,收养孤儿。







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值