首页 > 运维 > linux运维 > 正文

了解Linux环境下的进程

絕刀狂花
发布: 2025-06-20 13:22:01
原创
936人浏览过
  1. 进程

1.1 基本概念

当我们编写完代码并运行后,编译器会生成一个可执行文件.exe。当我们启动这个可执行文件时,程序会被加载到计算机的内存中。一些教材可能将正在运行的程序称为进程,但这并不完整。真正的进程还包括管理这个程序的进程控制块(PCB)。

课本概念:程序的一个执行实例,正在运行的程序。 内核观念:负责分配系统资源(如CPU时间、内存)的实体。

1.2 描述进程 - PCB

进程的信息被存储在一个称为进程控制块(Process Control Block)的数据结构中,这可以看作是进程属性的集合。在教材中通常称为PCB,而在Linux操作系统中,PCB被称为task_struct。PCB是一个结构体,用于管理加载到内存中的程序。

1.2.3 介绍task_struct

在Linux中,每个进程都由task_struct数据结构定义,task_struct就是通常所说的PCB,它是管理进程的唯一和最有效的手段。当我们调用fork()时,系统会为我们创建一个新的task_struct结构。从父进程继承一些数据,并将新的进程插入到进程树中,以便进行进程管理。因此,了解task_struct的结构对于理解进程调度至关重要。

task_struct是如何管理进程的?首先描述,然后组织。在task_struct结构中,有以下定义:

  • 进程状态,记录进程处于等待、运行或死锁状态。
  • 调度信息,由哪个调度函数调度以及如何调度。
  • 进程的通信情况。因为需要插入进程树,必须有指向父子兄弟的指针,这些指针也是task_struct类型。
  • 时间信息,例如计算执行时间,以便CPU分配。
  • 标号,决定进程归属。
  • 可读写的一些文件信息。
  • 进程上下文和内核上下文。
  • 处理上下文。
  • 内存信息等。

因为每个PCB都包含这些结构,才能满足另一个进程的所有需求。

task_struct

struct task_struct{
    // 标识符:描述本进程的唯一标识符,用于区分其他进程
    // 状态:任务状态,退出代码,退出信号
    // 优先级:相对于其他进程的优先级
    // 程序计数器:程序中即将被执行的下一条指令的地址。
    // 内存指针:包括程序代码的进程相关数据的指针,还有其他进程共享的内存块的指针。
    // 上下文数据:进程执行时处理的寄存器中的数据。
    // I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
    // 记账信息:可能包括处理器时间总和,使用的时钟数总和,时间限制,计账号等。
    // ...;
}
登录后复制

具体结构请参考:task_struct结构体结构。

提问:为什么程序加载到内存中,变成进程之后,我们要给进程形成一个PCB对象呢?

回答:因为操作系统需要进行管理,操作系统无法直接对正在运行的程序进行管理,需要通过一个PCB来间接管理。PCB包含进程的信息,对PCB对象的管理实际上就是对进程的管理。

所以:进程 = 内核PCB对象 + 正在运行的程序。这意味着,所有对进程的控制和操作都只与进程的PCB相关,与进程的可执行程序无关!你可以将PCB放入任何数据结构中。

了解Linux环境下的进程

1.3 查看进程

进程信息可以通过/proc系统文件查看。

当你需要获取PID为1的进程信息时,你需要查看/proc/1这个文件夹。

了解Linux环境下的进程

大多数进程信息也可以使用topps这些用户级工具来获取。

ps aux | grep test | grep -v grep
登录后复制

用于过滤与test相关的进程。

1.4 通过系统调用获取进程标识符

进程ID(PID)和父进程ID(PPID)可以通过系统调用获取。

#include <stdio.h>
#include <unistd.h>
#include <sys>
int main(){
    pid_t id = getpid(); // 获取该程序进程标识符id
    pid_t fid = getppid(); // 获取该程序进程父进程的标识符id
    while(1)
    {
        printf("i am process! pid = %d,ppid = %d\n",id,fid);
        sleep(1);
    }
    return 0;
}
</sys></unistd.h></stdio.h>
登录后复制

了解Linux环境下的进程

如何判断确实是这样呢?

while :;do ps ajx|head -1 && ps ajx|grep mybin|grep -v grep;sleep 1; done
登录后复制

使用该条语句可以在屏幕中循环打印mybin的进程是否存在。

1.5 通过系统调用创建进程 - fork

运行man fork来了解fork函数。

fork有两个返回值。父子进程代码共享,数据各自分配空间,采用写时拷贝(Copy-on-Write)。

功能:fork是复制进程的函数,程序一开始就会产生一个进程,当这个进程(代码)执行到fork()时,fork就会复制一份原来的进程来产生一个新的进程,新产生的进程为子进程,而原来的进程为父进程,此时父子进程是共存的,他们会同时向下执行代码。

关于返回值:在父进程中,fork会返回新创建子进程的进程ID;在子进程中,fork返回0。如果出现错误,fork会返回一个负值。

下面写一段代码,执行逻辑将会和过去不同。

#include <stdio.h>
#include <unistd.h>
#include <sys>
int main(){
    pid_t id = fork();
    if(id</sys></unistd.h></stdio.h>
登录后复制

了解Linux环境下的进程

运行结果:

了解Linux环境下的进程

运行结果令人惊讶!if里的内容和else里面的内容居然同时执行了。这是怎么回事呢?就像前面所说,在fork函数执行完毕后,如果创建进程成功,则会出现两个进程,一个子进程,一个父进程。子进程和父进程是同时运行的,可以看出两个程序,不过它们的代码数据是相同的。由于fork在父子进程中的返回值不同,也就造成了这种看上去if和else同时执行的情况。

  1. 进程状态

为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有多个状态,在Linux内核中,进程也可以称为任务。所谓的状态就是一个整型变量,在task_struct中的一个整型变量。

下面是进程状态在Kernel源代码中的定义:

/* 
 * The task state array is a strange "bitmap" of 
 * reasons to sleep. Thus "running" is zero, and 
 * you can test for combinations of others with 
 * simple bit tests. 
 */ 
static const char * const task_state_array[] = { 
    "R (running)", /* 0 */ 
    "S (sleeping)", /* 1 */ 
    "D (disk sleep)", /* 2 */ 
    "T (stopped)", /* 4 */ 
    "t (tracing stop)", /* 8 */ 
    "X (dead)", /* 16 */ 
    "Z (zombie)", /* 32 */ 
};
登录后复制

提问:为什么要有进程状态呢?

回答:在日常生活中,可能你感冒了,你会对室友说,我今天状态不好就不去上课了。这里的状态就决定了你的后续动作——不去上课了。在Linux中也是如此,Linux可能存在很多的进程,操作系统要根据它们的状态来决定后续对这些进程的操作。

2.1 通俗的5种状态

进程的状态,通俗地讲有5种:创建状态、就绪状态、堵塞状态、执行状态、终止状态。最基本的状态就是:运行状态、就绪状态、堵塞状态。

  • 就绪状态:进程已经具备运行条件,但是由于没有空闲的CPU,而暂时不能运行。
  • 运行状态:进程正在运行,占用CPU资源。
  • 堵塞状态:因为正在等待某一事件而暂时不能运行,如程序正在执行sleep,或者等待输入。

创建态与结束态:

  • 创建态:进程正在被创建,操作系统为其分配资源、初始化PCB。
  • 终止态:进程从系统中撤销,操作系统会回收进程拥有的资源。

2.2 进程具体的状态

上面的状态似乎与前面我们所写的状态不太一样,确实,在前面我们所写的为进程的具体状态,相当于通俗状态的具体实例。再来看看代码:

/* 
 * The task state array is a strange "bitmap" of 
 * reasons to sleep. Thus "running" is zero, and 
 * you can test for combinations of others with 
 * simple bit tests. 
 */ 
static const char * const task_state_array[] = { 
    "R (running)", /* 0 */ 
    "S (sleeping)", /* 1 */ 
    "D (disk sleep)", /* 2 */ 
    "T (stopped)", /* 4 */ 
    "t (tracing stop)", /* 8 */ 
    "X (dead)", /* 16 */ 
    "Z (zombie)", /* 32 */ 
};
登录后复制
  • R 运行状态(running):并不意味着进程一定在运行,它表明进程要么在运行中要么在运行队列里。对于了就绪状态和运行状态。
  • S 睡眠状态(sleeping):意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠)。对于了堵塞状态。
  • D 磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
  • T 停止状态(stopped):可以通过发送SIGSTOP信号给进程来停止进程,这个被暂停的进程可以发送SIGCONT信号让进程继续运行。
  • X 死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。

说了这么多,有没有可信度呢?当然了,下面就是查看进程状态。

2.3 进程状态的查看

ps aux / ps axj 命令
登录后复制

有什么区别呢?auxaxj其实是分开看的:a-u-x-j。

  • ps a 显示现行终端机下的所有程序,包括其他用户的程序。
  • ps u 以用户为主的格式来显示程序状态。
  • ps x 显示所有程序,不以终端机来区分。
  • ps j 显示进程归属的进程组ID、会话ID、父进程ID。

也就是说,ps aux就是以用户为主打印所有进程。ps axj打印所有进程并显示PID、PPID。

先写一个程序让它跑起来,然后观察它的状态。

#include <stdio.h>
#include <unistd.h>
#include <sys>
int main(){
    while(1){
        printf("I am a process!\n");
        sleep(1); // 休眠一秒
    }
    return 0;
}
</sys></unistd.h></stdio.h>
登录后复制

Makefile的配置:

mybin:test1.c
    gcc -o mybin test1.c
.PHONY:clean
clean:
    rm -f mybin
登录后复制

把Xshell开个双窗口,来观察进程的状态,一个用来运行程序,一个用来观察状态。

了解Linux环境下的进程

证件照制作小程序免费版
证件照制作小程序免费版

在线证件照系统是一套完善的冲印行业解决方案,致力于解决用户线上拍摄证件照,拍摄最美最标准证件照的使命。证件照免费版功能:后台统计:当天制作、当天新增、支持规格、近7日统计规格列表:筛选查看、编辑用户列表:筛选查看常见问题:筛选查看、新增、编辑、删除小程序设置:应用设置、流量主设置小程序跳转:筛选查看、新增、编辑、删除关注公众号:引导设置系统要求:系统:Linux系统(centos x64)运行环境

证件照制作小程序免费版 1
查看详情 证件照制作小程序免费版

在此之前,我们还要写一个循环来查看进程状态的指令。

了解Linux环境下的进程

while :;do ps ajx|head -1 && ps ajx|grep mybin|grep -v grep;sleep 1; done
登录后复制

每秒打印一次mybin进程的状态。下面让我们运行一下程序来看看吧。

了解Linux环境下的进程

从该程序我们可以看出该进程的各个信息,其中有个信息是有关进程状态的,就是STAT。

可是在上面的图片中为什么我们进程显示的状态是S呢?S可是睡眠的意思啊。令人费解,程序不是正在运行吗?其实图片没有错的,在我们的程序中存在一个sleep函数会让程序休眠一秒钟,进程不能在它睡眠期间还把它放在运行状态,这也就是导致了,mybin的运行状态只有一瞬间,运气好的话可能能捕捉到这一瞬间。

那么思考一下,如果把sleep去掉,STAT会是什么状态呢?答案还是S,这是因为cout的缘故,printf也会存在休眠时间的,CPU的速度是非常快的,printf的休眠时间对于CPU的速度来说就显得非常漫长了,所以STAT依旧会显示S

那么如何才能显示R呢?把printf去掉就可以,直接让程序执行死循环。

了解Linux环境下的进程

例子就是如此。

#include <stdio.h>
#include <unistd.h>
#include <sys>
int main(){ 
    int a = 0;
    while(1){ 
        scanf("%d",&a);
    }
    return 0;
}
</sys></unistd.h></stdio.h>
登录后复制

2.4 介绍僵尸进程与孤儿进程

Z(zombie)-僵尸进程

僵尸状态(Zombies)是一个比较特殊的状态,当进程退出并且父进程没有读取到子进程退出的返回代码就会产生僵尸进程。僵尸进程会将终止状态保持在进程表中,并且会一直等待父进程读取退出状态代码。所以,只要子进程退出,父进程还在运行,但是父进程没有读取子进程的状态,子进程就会进入僵尸状态。

创建一个维持30秒的僵尸进程:

#include <stdio.h>
#include <stdlib.h>
int main(){
    pid_t id = fork();
    if(id > 0){
        printf("parent[%d] is sleeping...\n",getpid());
        sleep(30);
    }else{
        printf("child[%d] is begin Z...\n",getpid());
        sleep(5);
        exit(-1);
    }
    return 0;
}
</stdlib.h></stdio.h>
登录后复制

了解Linux环境下的进程

僵尸进程的危害:

进程的退出状态必须维持下去,因为它要告诉关心它的进程(父进程),父进程的任务,我完成的如何。可是父进程如果一直不读取,那子进程就一直处于Z状态。维护退出状态本身就是要用数据维护,也属于进程的基本信息,所以保存task_struct(PCB)中,Z状态一直不退出,PCB将会一直维护它,不退出。那父进程创建了很多子进程,就是不回收,就会导致内存的资源的浪费。因为数据结构对象就要占用内存。僵尸进程会导致内存泄漏!

孤儿进程:

父进程如果提前退出,那么子进程后退出,进入Z之后如何处理。父进程先退出,子进程就称为“孤儿进程”。孤儿进程会被1号进程收养。

#include <stdio.h>
#include <stdlib.h>
int main(){
    pid_t id = fork();
    if(id > 0){
        printf("parent[%d] is sleeping...\n",getpid());
        sleep(3);
        exit(-1);
    }else{
        printf("child[%d] is begin Z...\n",getpid());
        sleep(10);
    }
    return 0;
}
</stdlib.h></stdio.h>
登录后复制

了解Linux环境下的进程

可以看到,子进程被1号进程接管了。

  1. 进程优先级

3.1 为什么要有优先级的概念

大多数人的电脑都是一个CPU,一次只能处理一个进程任务,但是进程又有很多个。这也就导致了CPU的资源不足,为了更合理的利用CPU资源,就存在进程优先级来确定进程获取CPU资源的顺序。

就像生活中的排队,进程在CPU中也是需要排队的,除了遵循先来后到的排队原理,还存在优先级更高的进程是可以进行插队的,这也可以理解,在医院排队时,如果碰到急诊病人是可以优先挂号的。

提问:有没有可能因为大量的优先级更高的进程插队导致低优先级的进程迟迟得不到执行。

回答:这就涉及到了进程饥饿的问题了,在Linux下是有相关解决方法的,Linux会维护两个队列,一个为活跃队列,另一个为过期队列,这里就不细讲了。

3.2 进程优先级的基本概念

CPU资源分配的先后顺序,就是指进程的优先级(priority)。优先权高的进程有优先执行权力。配置进程优先级对多任务环境的Linux很有用,可以改善系统的性能。把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能。

进程要访问某种资源,就必须通过一定的方式,确定享受资源的先后顺序。可能有人会把进程的优先级和权限进行类比,其实两者还是很不同的:

3.3 查看系统进程

在Linux或者Unix系统中,输入ps -l 会出现以下内容:

了解Linux环境下的进程

下面我们来介绍各个符号的意义:

  • UID:代表执行者的身份。
  • PID:代表这个进程的代号。
  • PPID:代表这个进程是由哪个进程发展衍生而来的,也就是父进程的代号。
  • PRI:代表这个进程可被执行的优先级,其值越小优先级越高。
  • NI:代表这个进程的nice值。

3.3.1 介绍PRI和NI

PRI就是进程的优先级,通俗点就是程序被CPU执行的先后顺序,此值越小进程的优先级越高。优先值范围[60,99],一般默认的优先级为80。

那NI就是我们要说明的nice值,表示进程可被执行的优先级的修正数值。PRI越小越快被执行,那么加入nice值后,就会得到新的PRI:PRI(NEW) = PRI(OLD)+ nice。如此一来的话,当nice值为负值的时候,那么该程序就会优先值变小,其优先级会变高,则越快被执行。

nice的取值范围为[-20,19],一共40个级别,这也就对应了第一点。

3.1.2 调整进程的优先级

通过公式PRI(NEW) = PRI(OLD)+ nice。我们知道,调整进程的优先级的本质就是调整nice的值。

指令:top

进入top后按“r”->输入进程PID->输入nice值
登录后复制

演示:修改nice值为100。

了解Linux环境下的进程

通过演示我们可以发现,尽管nice的值被修改为100,PRI的值就值变成了99。由此也可以证明PRI的范围。

值得注意的是,每次都是直接给nice赋值,而不是在原有nice的基础上加减。

提问:为什么调整优先级是要受限制的?

回答:如果不加限制,将自己进程的优先级调整的非常高,别人的优先级调整的非常低,优先级较高的进程,优先得到资源,后续源源不断的进程产生。常规进程很难享受到CPU的资源,也就造成了进程饥饿问题。

3.4 Linux的调度与切换

提问:在进程运行时,CPU会直接把进程代码跑完吗?

答案:是不会的,现代操作系统就是基于时间片进行轮流执行的,假设每个进程执行1ms,那么CPU在每1ms都会切换进程来执行。

一些其他概念:

  • 竞争性:系统进程数目众多而CPU资源只有少量,甚至一个,所以进程间是具有竞争属性的,为了高效完成任务,更合理竞争相关资源,便有了优先级。
  • 独立性:多进程运行,需要独享各种资源,多进程运行期间,互不干扰。
  • 并行:多个进程在多个CPU下分别,同时进行运行,这称之为并行。
  • 并发:多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程得以推进,称之为并发。

以上就是了解Linux环境下的进程的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号