爆款云主机2核4G限时秒杀,88元/年起!
查看详情

活动

天翼云最新优惠活动,涵盖免费试用,产品折扣等,助您降本增效!
热门活动
  • 618智算钜惠季 爆款云主机2核4G限时秒杀,88元/年起!
  • 免费体验DeepSeek,上天翼云息壤 NEW 新老用户均可免费体验2500万Tokens,限时两周
  • 云上钜惠 HOT 爆款云主机全场特惠,更有万元锦鲤券等你来领!
  • 算力套餐 HOT 让算力触手可及
  • 天翼云脑AOne NEW 连接、保护、办公,All-in-One!
  • 中小企业应用上云专场 产品组合下单即享折上9折起,助力企业快速上云
  • 息壤高校钜惠活动 NEW 天翼云息壤杯高校AI大赛,数款产品享受线上订购超值特惠
  • 天翼云电脑专场 HOT 移动办公新选择,爆款4核8G畅享1年3.5折起,快来抢购!
  • 天翼云奖励推广计划 加入成为云推官,推荐新用户注册下单得现金奖励
免费活动
  • 免费试用中心 HOT 多款云产品免费试用,快来开启云上之旅
  • 天翼云用户体验官 NEW 您的洞察,重塑科技边界

智算服务

打造统一的产品能力,实现算网调度、训练推理、技术架构、资源管理一体化智算服务
智算云(DeepSeek专区)
科研助手
  • 算力商城
  • 应用商城
  • 开发机
  • 并行计算
算力互联调度平台
  • 应用市场
  • 算力市场
  • 算力调度推荐
一站式智算服务平台
  • 模型广场
  • 体验中心
  • 服务接入
智算一体机
  • 智算一体机
大模型
  • DeepSeek-R1-昇腾版(671B)
  • DeepSeek-R1-英伟达版(671B)
  • DeepSeek-V3-昇腾版(671B)
  • DeepSeek-R1-Distill-Llama-70B
  • DeepSeek-R1-Distill-Qwen-32B
  • Qwen2-72B-Instruct
  • StableDiffusion-V2.1
  • TeleChat-12B

应用商城

天翼云精选行业优秀合作伙伴及千余款商品,提供一站式云上应用服务
进入甄选商城进入云市场创新解决方案
办公协同
  • WPS云文档
  • 安全邮箱
  • EMM手机管家
  • 智能商业平台
财务管理
  • 工资条
  • 税务风控云
企业应用
  • 翼信息化运维服务
  • 翼视频云归档解决方案
工业能源
  • 智慧工厂_生产流程管理解决方案
  • 智慧工地
建站工具
  • SSL证书
  • 新域名服务
网络工具
  • 翼云加速
灾备迁移
  • 云管家2.0
  • 翼备份
资源管理
  • 全栈混合云敏捷版(软件)
  • 全栈混合云敏捷版(一体机)
行业应用
  • 翼电子教室
  • 翼智慧显示一体化解决方案

合作伙伴

天翼云携手合作伙伴,共创云上生态,合作共赢
天翼云生态合作中心
  • 天翼云生态合作中心
天翼云渠道合作伙伴
  • 天翼云代理渠道合作伙伴
天翼云服务合作伙伴
  • 天翼云集成商交付能力认证
天翼云应用合作伙伴
  • 天翼云云市场合作伙伴
  • 天翼云甄选商城合作伙伴
天翼云技术合作伙伴
  • 天翼云OpenAPI中心
  • 天翼云EasyCoding平台
天翼云培训认证
  • 天翼云学堂
  • 天翼云市场商学院
天翼云合作计划
  • 云汇计划
天翼云东升计划
  • 适配中心
  • 东升计划
  • 适配互认证

开发者

开发者相关功能入口汇聚
技术社区
  • 专栏文章
  • 互动问答
  • 技术视频
资源与工具
  • OpenAPI中心
开放能力
  • EasyCoding敏捷开发平台
培训与认证
  • 天翼云学堂
  • 天翼云认证
魔乐社区
  • 魔乐社区

支持与服务

为您提供全方位支持与服务,全流程技术保障,助您轻松上云,安全无忧
文档与工具
  • 文档中心
  • 新手上云
  • 自助服务
  • OpenAPI中心
定价
  • 价格计算器
  • 定价策略
基础服务
  • 售前咨询
  • 在线支持
  • 在线支持
  • 工单服务
  • 建议与反馈
  • 用户体验官
  • 服务保障
  • 客户公告
  • 会员中心
增值服务
  • 红心服务
  • 首保服务
  • 客户支持计划
  • 专家技术服务
  • 备案管家

了解天翼云

天翼云秉承央企使命,致力于成为数字经济主力军,投身科技强国伟大事业,为用户提供安全、普惠云服务
品牌介绍
  • 关于天翼云
  • 智算云
  • 天翼云4.0
  • 新闻资讯
  • 天翼云APP
基础设施
  • 全球基础设施
  • 信任中心
最佳实践
  • 精选案例
  • 超级探访
  • 云杂志
  • 分析师和白皮书
  • 天翼云·创新直播间
市场活动
  • 2025智能云生态大会
  • 2024智算云生态大会
  • 2023云生态大会
  • 2022云生态大会
  • 天翼云中国行
天翼云
  • 活动
  • 智算服务
  • 产品
  • 解决方案
  • 应用商城
  • 合作伙伴
  • 开发者
  • 支持与服务
  • 了解天翼云
      • 文档
      • 控制中心
      • 备案
      • 管理中心

      Linux进程概念(精讲)

      首页 知识中心 其他 文章详情页

      Linux进程概念(精讲)

      2023-05-22 06:27:47 阅读次数:121

      linux,进程

       

      基本概念

      课本概念: 程序的一个执行实例,正在执行的程序等。
      内核观点: 担当分配系统资源(CPU时间,内存)的实体。

      只要写过代码的都知道,当你的代码进行编译链接后便会生成一个可执行程序,这个可执行程序本质上是一个文件,是放在磁盘上的。当我们双击这个可执行程序将其运行起来时,本质上是将这个程序加载到内存当中了,因为只有加载到内存后,CPU才能对其进行逐行的语句执行,而一旦将这个程序加载到内存后,我们就不应该将这个程序再叫做程序了,严格意义上将应该将其称之为进程。
      Linux进程概念(精讲)

      描述进程-PCB

      系统当中可以同时存在大量进程,使用命令ps aux便可以显示系统当中存在的进程。
      Linux进程概念(精讲)
      而当你开机的时候启动的第一个程序就是我们的操作系统(即操作系统是第一个加载到内存的),我们都知道操作系统是做管理工作的,而其中就包括了进程管理。而系统内是存在大量进程的,那么操作系统是如何对进程进行管理的呢?
      这时我们就应该想到管理的六字真言:先描述,再组织。操作系统管理进程也是一样的,操作系统作为管理者是不需要直接和被管理者(进程)直接进行沟通的,当一个进程出现时,操作系统就立马对其进行描述,之后对该进程的管理实际上就是对其描述信息的管理。
      进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合,课本上称之为PCB(process control block)。

      操作系统将每一个进程都进行描述,形成了一个个的进程控制块(PCB),并将这些PCB以双链表的形式组织起来。
      Linux进程概念(精讲)
      这样一来,操作系统只要拿到这个双链表的头指针,便可以访问到所有的PCB。此后,操作系统对各个进程的管理就变成了对这条双链表的一系列操作。
      例如创建一个进程实际上就是先将该进程的代码和数据加载到内存,紧接着操作系统对该进程进行描述形成对应的PCB,并将这个PCB插入到该双链表当中。而退出一个进程实际上就是先将该进程的PCB从该双链表当中删除,然后操作系统再将内存当中属于该进程的代码和数据进行释放或是置为无效。
      总的来说,操作系统对进程的管理实际上就变成了对该双链表的增、删、查、改等操作。

      task_struct-PCB的一种

      进程控制块(PCB)是描述进程的,在C++当中我们称之为面向对象,而在C语言当中我们称之为结构体,既然Linux操作系统是用C语言进行编写的,那么Linux当中的进程控制块必定是用结构体来实现的。

      • PCB实际上是对进程控制块的统称,在Linux中描述进程的结构体叫做task_struct。
      • task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含进程的信息。

      task_struct内容分类

      task_struct就是Linux当中的进程控制块,task_struct当中主要包含以下信息:

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

      查看进程

      通过系统目录查看

      在根目录下有一个名为proc的系统文件夹。
      Linux进程概念(精讲)
      文件夹当中包含大量进程信息,其中有些子目录的目录名为数字。
      Linux进程概念(精讲)
      这些数字其实是某一进程的PID,对应文件夹当中记录着对应进程的各种信息。我们若想查看PID为1的进程的进程信息,则查看名字为1的文件夹即可。
      Linux进程概念(精讲)

      通过ps命令查看

      单独使用ps命令,会显示所有进程信息。

      [cl@VM-0-15-centos dir2]$ ps aux
      

      Linux进程概念(精讲)
      ps命令与grep命令搭配使用,即可只显示某一进程的信息。

      [cl@VM-0-15-centos dir2]$ ps aux | head -1 && ps aux | grep proc | grep -v grep
      

      Linux进程概念(精讲)

      通过系统调用获取进程的PID和PPID

      通过使用系统调用函数,getpid和getppid即可分别获取进程的PID和PPID。
      我们可以通过一段代码来进行测试。
      Linux进程概念(精讲)
      当运行该代码生成的可执行程序后,便可循环打印该进程的PID和PPID。
      Linux进程概念(精讲)
      我们可以通过ps命令查看该进程的信息,即可发现通过ps命令得到的进程的PID和PPID与使用系统调用函数getpid和getppid所获取的值相同。
      Linux进程概念(精讲)

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

      fork函数创建子进程

      fork是一个系统调用级别的函数,其功能就是创建一个子进程。
      例如,运行以下代码:
      Linux进程概念(精讲)
      若是代码当中没有fork函数,我们都知道代码的运行结果就是循环打印该进程的PID和PPID。而加入了fork函数后,代码运行结果如下:
      Linux进程概念(精讲)
      运行结果是循环打印两行数据,第一行数据是该进程的PID和PPID,第二行数据是代码中fork函数创建的子进程的PID和PPID。我们可以发现fork函数创建的进程的PPID就是proc进程的PID,也就是说proc进程与fork函数创建的进程之间是父子关系。

      每出现一个进程,操作系统就会为其创建PCB,fork函数创建的进程也不例外。
      Linux进程概念(精讲)
      我们知道加载到内存当中的代码和数据是属于父进程的,那么fork函数创建的子进程的代码和数据又从何而来呢?
      我们看看以下代码的运行结果:
      Linux进程概念(精讲)
      运行结果:
      Linux进程概念(精讲)
      实际上,使用fork函数创建子进程,在fork函数被调用之前的代码被父进程执行,而fork函数之后的代码,则默认情况下父子进程都可以执行。需要注意的是,父子进程虽然代码共享,但是父子进程的数据各自开辟空间(采用写时拷贝)。

      小贴士: 使用fork函数创建子进程后就有了两个进程,这两个进程被操作系统调度的顺序是不确定的,这取决于操作系统调度算法的具体实现。

      使用if进行分流

      上面说到,fork函数创建出来的子进程与其父进程共同使用一份代码,但我们如果真的让父子进程做相同的事情,那么创建子进程就没有什么意义了。
      实际上,在fork之后我们通常使用if语句进行分流,即让父进程和子进程做不同的事。

      fork函数的返回值:
      1、如果子进程创建成功,在父进程中返回子进程的PID,而在子进程中返回0。
      2、如果子进程创建失败,则在父进程中返回 -1。

      既然父进程和子进程获取到fork函数的返回值不同,那么我们就可以据此来让父子进程执行不同的代码,从而做不同的事。
      例如,以下代码:
      Linux进程概念(精讲)
      fork创建出子进程后,子进程会进入到 if 语句的循环打印当中,而父进程会进入到 else if 语句的循环打印当中。
      Linux进程概念(精讲)

      Linux进程状态

      一个进程从创建而产生至撤销而消亡的整个生命期间,有时占有处理器执行,有时虽可运行但分不到处理器,有时虽有空闲处理器但因等待某个时间的发生而无法执行,这一切都说明进程和程序不相同,进程是活动的且有状态变化的,于是就有了进程状态这一概念。
      Linux进程概念(精讲)
      这里我们具体谈一下Linux操作系统中的进程状态,Linux操作系统的源代码当中对于进程状态有如下定义:

      /*
      * 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 *task_state_array[] = {
      	"R (running)",       /*  0*/
          "S (sleeping)",      /*  1*/
          "D (disk sleep)",    /*  2*/
          "T (stopped)",       /*  4*/
          "T (tracing stop)",  /*  8*/
          "Z (zombie)",        /* 16*/
          "X (dead)"           /* 32*/
      };
      

      小贴士: 进程的当前状态是保存到自己的进程控制块(PCB)当中的,在Linux操作系统当中也就是保存在task_struct当中的。

      在Linux操作系统当中我们可以通过 ps aux 或 ps axj 命令查看进程的状态。

      [cl@VM-0-15-centos ~]$ ps aux
      

      Linux进程概念(精讲)

      [cl@VM-0-15-centos ~]$ ps axj
      

      Linux进程概念(精讲)

      运行状态-R

      一个进程处于运行状态(running),并不意味着进程一定处于运行当中,运行状态表明一个进程要么在运行中,要么在运行队列里。也就是说,可以同时存在多个R状态的进程。

      小贴士: 所有处于运行状态,即可被调度的进程,都被放到运行队列当中,当操作系统需要切换进程运行时,就直接在运行队列中选取进程运行。

      浅度睡眠状态-S

      一个进程处于浅度睡眠状态(sleeping),意味着该进程正在等待某件事情的完成,处于浅度睡眠状态的进程随时可以被唤醒,也可以被杀掉(这里的睡眠有时候也可叫做可中断睡眠(interruptible sleep))。

      例如执行以下代码:
      Linux进程概念(精讲)
      代码当中调用sleep函数进行休眠100秒,在这期间我们若是查看该进程的状态,则会看到该进程处于浅度睡眠状态。

      [cl@VM-0-15-centos stat]$ ps aux | head -1 && ps aux | grep proc | grep -v grep
      

      Linux进程概念(精讲)
      而处于浅度睡眠状态的进程是可以被杀掉的,我们可以使用kill命令将该进程杀掉。
      Linux进程概念(精讲)

      深度睡眠状态-D

      一个进程处于深度睡眠状态(disk sleep),表示该进程不会被杀掉,即便是操作系统也不行,只有该进程自动唤醒才可以恢复。该状态有时候也叫不可中断睡眠状态(uninterruptible sleep),处于这个状态的进程通常会等待IO的结束。

      例如,某一进程要求对磁盘进行写入操作,那么在磁盘进行写入期间,该进程就处于深度睡眠状态,是不会被杀掉的,因为该进程需要等待磁盘的回复(是否写入成功)以做出相应的应答。(磁盘休眠状态)

      暂停状态-T

      在Linux当中,我们可以通过发送SIGSTOP信号使进程进入暂停状态(stopped),发送SIGCONT信号可以让处于暂停状态的进程继续运行。

      例如,我们对一个进程发送SIGSTOP信号,该进程就进入到了暂停状态。
      Linux进程概念(精讲)
      我们再对该进程发送SIGCONT信号,该进程就继续运行了。
      Linux进程概念(精讲)
      小贴士: 使用kill命令可以列出当前系统所支持的信号集。

      [cl@VM-0-15-centos stat]$ kill -l
      

      Linux进程概念(精讲)

      僵尸状态-Z

      当一个进程将要退出的时候,在系统层面,该进程曾经申请的资源并不是立即被释放,而是要暂时存储一段时间,以供操作系统或是其父进程进行读取,如果退出信息一直未被读取,则相关数据是不会被释放掉的,一个进程若是正在等待其退出信息被读取,那么我们称该进程处于僵尸状态(zombie)。

      首先,僵尸状态的存在是必要的,因为进程被创建的目的就是完成某项任务,那么当任务完成的时候,调用方是应该知道任务的完成情况的,所以必须存在僵尸状态,使得调用方得知任务的完成情况,以便进行相应的后续操作。
      例如,我们写代码时都在主函数最后返回0。
      Linux进程概念(精讲)
      实际上这个0就是返回给操作系统的,告诉操作系统代码顺利执行结束。在Linux操作系统当中,我们可以通过使用echo $?命令获取最近一次进程退出时的退出码。

      [cl@VM-0-15-centos exitcode]$ echo $?
      

      Linux进程概念(精讲)

      小贴士: 进程退出的信息(例如退出码),是暂时被保存在其进程控制块当中的,在Linux操作系统中也就是保存在该进程的task_struct当中。

      死亡状态-X

      死亡状态只是一个返回状态,当一个进程的退出信息被读取后,该进程所申请的资源就会立即被释放,该进程也就不存在了,所以你不会在任务列表当中看到死亡状态(dead)。

      僵尸进程

      前面说到,一个进程若是正在等待其退出信息被读取,那么我们称该进程处于僵尸状态。而处于僵尸状态的进程,我们就称之为僵尸进程。

      例如,对于以下代码,fork函数创建的子进程在打印5次信息后会退出,而父进程会一直打印信息。也就是说,子进程退出了,父进程还在运行,但父进程没有读取子进程的退出信息,那么此时子进程就进入了僵尸状态。

      #include <stdio.h>
      #include <stdlib.h>
      #include <unistd.h>
      int main()
      {
      	printf("I am running...\n");
      	pid_t id = fork();
      	if(id == 0){ //child
      		int count = 5;
      		while(count){
      			printf("I am child...PID:%d, PPID:%d, count:%d\n", getpid(), getppid(), count);
      			sleep(1);
      			count--;
      		}
      		printf("child quit...\n");
      		exit(1);
      	}
      	else if(id > 0){ //father
      		while(1){
      			printf("I am father...PID:%d, PPID:%d\n", getpid(), getppid());
      			sleep(1);
      		}
      	}
      	else{ //fork error
      	}
      	return 0;
      } 
      

      运行该代码后,我们可以通过以下监控脚本,每隔一秒对该进程的信息进行检测。

      [cl@VM-0-15-centos zombie]$ while :; do ps axj | head -1 && ps axj | grep proc | grep -v grep;echo "######################";sleep 1;done
      

      检测后即可发现,当子进程退出后,子进程的状态就变成了僵尸状态。
      Linux进程概念(精讲)

      僵尸进程的危害

      1. 僵尸进程的退出状态必须一直维持下去,因为它要告诉其父进程相应的退出信息。可是父进程一直不读取,那么子进程也就一直处于僵尸状态。
      2. 僵尸进程的退出信息被保存在task_struct(PCB)中,僵尸状态一直不退出,那么PCB就一直需要进行维护。
      3. 若是一个父进程创建了很多子进程,但都不进行回收,那么就会造成资源浪费,因为数据结构对象本身就要占用内存。
      4. 僵尸进程申请的资源无法进行回收,那么僵尸进程越多,实际可用的资源就越少,也就是说,僵尸进程会导致内存泄漏。

      孤儿进程

      在Linux当中的进程关系大多数是父子关系,若子进程先退出而父进程没有对子进程的退出信息进行读取,那么我们称该进程为僵尸进程。但若是父进程先退出,那么将来子进程进入僵尸状态时就没有父进程对其进行处理,此时该子进程就称之为孤儿进程。
      若是一直不处理孤儿进程的退出信息,那么孤儿进程就会一直占用资源,此时就会造成内存泄漏。因此,当出现孤儿进程的时候,孤儿进程会被1号init进程领养,此后当孤儿进程进入僵尸状态时就由int进程进行处理回收。

      例如,对于以下代码,fork函数创建的子进程会一直打印信息,而父进程在打印5次信息后会退出,此时该子进程就变成了孤儿进程。

      #include <stdio.h>
      #include <stdlib.h>
      #include <unistd.h>
      int main()
      {
      	printf("I am running...\n");
      	pid_t id = fork();
      	if(id == 0){ //child
      		int count = 5;
      		while(1){
      			printf("I am child...PID:%d, PPID:%d\n", getpid(), getppid(), count);
      			sleep(1);
      		}
      	}
      	else if(id > 0){ //father
      		int count = 5;
      		while(count){
      			printf("I am father...PID:%d, PPID:%d, count:%d\n", getpid(), getppid(), count);
      			sleep(1);
      			count--;
      		}
      		printf("father quit...\n");
      		exit(0);
      	}
      	else{ //fork error
      	}
      	return 0;
      } 
      

      观察代码运行结果,在父进程未退出时,子进程的PPID就是父进程的PID,而当父进程退出后,子进程的PPID就变成了1,即子进程被1号进程领养了。
      Linux进程概念(精讲)

      进程优先级

      基本概念

      什么是优先级?
      优先级实际上就是获取某种资源的先后顺序,而进程优先级实际上就是进程获取CPU资源分配的先后顺序,就是指进程的优先权(priority),优先权高的进程有优先执行的权力。

      优先级存在的原因?
      优先级存在的主要原因就是资源是有限的,而存在进程优先级的主要原因就是CPU资源是有限的,一个CPU一次只能跑一个进程,而进程是可以有多个的,所以需要存在进程优先级,来确定进程获取CPU资源的先后顺序。

      查看系统进程

      在Linux或者Unix操作系统中,用ps -l命令会类似输出以下几个内容:

      [cl@VM-0-15-centos pri]$ ps -l
      

      Linux进程概念(精讲)
      列出的信息当中有几个重要的信息,如下:

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

      PRI与NI

      • PRI代表进程的优先级(priority),通俗点说就是进程被CPU执行的先后顺序,该值越小进程的优先级别越高。
      • NI代表的是nice值,其表示进程可被执行的优先级的修正数值。
      • PRI值越小越快被执行,当加入nice值后,将会使得PRI变为:PRI(new) = PRI(old) + NI。
      • 若NI值为负值,那么该进程的PRI将变小,即其优先级会变高。
      • 调整进程优先级,在Linux下,就是调整进程的nice值。
      • NI的取值范围是-20至19,一共40个级别。

      注意: 在Linux操作系统当中,PRI(old)默认为80,即PRI = 80 + NI。

      查看进程优先级信息

      当我们创建一个进程后,我们可以使用ps -al命令查看该进程优先级的信息。

      [cl@VM-0-15-centos pri]$ ps -al
      

      Linux进程概念(精讲)
      注意: 在Linux操作系统中,初始进程一般优先级PRI默认为80,NI默认为0。

      通过top命令更改进程的nice值

      top命令就相当于Windows操作系统中的任务管理器,它能够动态实时的显示系统当中进程的资源占用情况。
      Linux进程概念(精讲)
      使用top命令后按“r”键,会要求你输入待调整nice值的进程的PID。
      Linux进程概念(精讲)
      输入进程PID并回车后,会要求你输入调整后的nice值。
      Linux进程概念(精讲)
      输入nice值后按“q”即可退出,如果我们这里输入的nice值为10,那么此时我们再用ps命令查看进程的优先级信息,即可发现进程的NI变成了10,PRI变成了90(80+NI)。
      Linux进程概念(精讲)
      注意: 若是想将NI值调为负值,也就是将进程的优先级调高,需要使用sudo命令提升权限。

      通过renice命令更改进程的nice值

      使用renice命令,后面跟上更改后的nice值和进程的PID即可。
      Linux进程概念(精讲)
      之后我们再用ps命令查看进程的优先级信息,也可以发现进程的NI变成了10,PRI变成了90(80+NI)。
      Linux进程概念(精讲)
      注意: 若是想使用renice命令将NI值调为负值,也需要使用sudo命令提升权限。

      四个重要概念

      竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便有了优先级。

      独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰。

      并行: 多个进程在多个CPU下分别同时进行运行,这称之为并行。

      并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发。

      环境变量

      基本概念

      环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数。

      例如,我们编写的C/C++代码,在各个目标文件进行链接的时候,从来不知道我们所链接的动静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。

      环境变量通常具有某些特殊用途,并且在系统当中通常具有全局特性。

      常见环境变量

      • PATH: 指定命令的搜索路径。
      • HOME: 指定用户的主工作目录(即用户登录到Linux系统中的默认所处目录)。
      • SHELL: 当前Shell,它的值通常是/bin/bash。

      查看环境变量的方法

      我们可以通过echo命令来查看环境变量,方式如下:

      echo $NAME //NAME为待查看的环境变量名称

      例如,查看环境变量PATH。

      [cl@VM-0-15-centos ENV]$ echo $PATH
      

      Linux进程概念(精讲)

      测试PATH

      大家有没有想过这样一个问题:为什么执行ls命令的时候不用带./就可以执行,而我们自己生成的可执行程序必须要在前面带上./才可以执行?
      Linux进程概念(精讲)
      容易理解的是,要执行一个可执行程序必须要先找到它在哪里,既然不带./就可以执行ls命令,说明系统能够通过ls名称找到ls的位置,而系统是无法找到我们自己的可执行程序的,所以我们必须带上./,以此告诉系统该可执行程序位于当前目录下。

      而系统就是通过环境变量PATH来找到ls命令的,查看环境变量PATH我们可以看到如下内容:
      Linux进程概念(精讲)
      可以看到环境变量PATH当中有多条路径,这些路径由冒号隔开,当你使用ls命令时,系统就会查看环境变量PATH,然后默认从左到右依次在各个路径当中进行查找。
      而ls命令实际就位于PATH当中的某一个路径下,所以就算ls命令不带路径执行,系统也是能够找到的。
      Linux进程概念(精讲)

      那可不可以让我们自己的可执行程序也不用带路径就可以执行呢?

      当然可以,下面给出两种方式:

      方式一:将可执行程序拷贝到环境变量PATH的某一路径下。
      既然在未指定路径的情况下系统会根据环境变量PATH当中的路径进行查找,那我们就可以将我们的可执行程序拷贝到PATH的某一路径下,此后我们的可执行程序不带路径系统也可以找到了。

      [cl@VM-0-15-centos ENV]$ sudo cp proc /usr/bin
      

      Linux进程概念(精讲)
      方式二:将可执行程序所在的目录导入到环境变量PATH当中。
      将可执行程序所在的目录导入到环境变量PATH当中,这样一来,没有指定路径时系统就会来到该目录下进行查找了。

      [cl@VM-0-15-centos ENV]$ export PATH=$PATH:/home/cl/dirforproc/ENV
      

      Linux进程概念(精讲)
      将可执行程序所在的目录导入到环境变量PATH当中后,位于该目录下的可执行程序也就可以在不带路径的情况下执行了。
      Linux进程概念(精讲)

      测试HOME

      任何一个用户在运行系统登录时都有自己的主工作目录(家目录),环境变量HOME当中即保存的该用户的主工作目录。

      普通用户示例:
      Linux进程概念(精讲)
      超级用户示例:
      Linux进程概念(精讲)

      测试SHELL

      我们在Linux操作系统当中所敲的各种命令,实际上需要由命令行解释器进行解释,而在Linux当中有许多种命令行解释器(例如bash、sh),我们可以通过查看环境变量SHELL来知道自己当前所用的命令行解释器的种类。
      Linux进程概念(精讲)
      而该命令行解释器实际上是系统当中的一条命令,当这个命令运行起来变成进程后就可以为我们进行命令行解释。
      Linux进程概念(精讲)

      和环境变量相关的命令

      1、echo:显示某个环境变量的值。
      Linux进程概念(精讲)
      2、export:设置一个新的环境变量。
      Linux进程概念(精讲)
      3、env:显示所有的环境变量。
      Linux进程概念(精讲)
      部分环境变量说明:

      环境变量名称 表示内容
      PATH 命令的搜索路径
      HOME 用户的主工作目录
      SHELL 当前Shell
      HOSTNAME 主机名
      TERM 终端类型
      HISTSIZE 记录历史命令的条数
      SSH_TTY 当前终端文件
      USER 当前用户
      MAIL 邮箱
      PWD 当前所处路径
      LANG 编码格式
      LOGNAME 登录用户名

      4、set:显示本地定义的shell变量和环境变量。Linux进程概念(精讲)
      5、unset:清除环境变量。
      Linux进程概念(精讲)

      环境变量的组织方式

      在系统当中,环境变量的组织方式如下:
      Linux进程概念(精讲)
      每个程序都会收到一张环境变量表,环境表是一个字符指针数组,每个指针指向一个以’\0’结尾的环境字符串,最后一个字符指针为空。

      通过代码获取环境变量

      你知道main函数其实是有参数的吗?
      main函数其实有三个参数,只是我们平时基本不用它们,所以一般情况下都没有写出来。
      我们可以在Windows下的编译器进行验证,当我们调试代码的时候,若是一直使用逐步调试,那么最终会来到调用main函数的地方。
      Linux进程概念(精讲)
      在这里我们可以看到,调用main函数时给main函数传递了三个参数。

      我们先来说说main函数的前两个参数。

      在Linux操作系统下,编写以下代码,生成可执行程序并运行。
      Linux进程概念(精讲)
      运行结果如下:
      Linux进程概念(精讲)
      现在我们来说说main函数的前两个参数,main函数的第二个参数是一个字符指针数组,数组当中的第一个字符指针存储的是可执行程序的位置,其余字符指针存储的是所给的若干选项,最后一个字符指针为空,而main函数的第一个参数代表的就是字符指针数组当中的有效元素个数。
      Linux进程概念(精讲)
      下面我们可以尝试编写一个简单的代码,该代码运行起来后会根据你所给选项给出不同的提示语句。

      #include <stdio.h>                                                                                                                         
      #include <string.h>
      int main(int argc, char *argv[], char* envp[])
      {
      	if(argc > 1)
      	{
      		if(strcmp(argv[1], "-a") == 0)
      		{
      			 printf("you used -a option...\n");
      		}
      		else if(strcmp(argv[1], "-b") == 0)
      		{
      			printf("you used -b option...\n");
      		}
      		else
      		{
      			printf("you used unrecognizable option...\n");
      		}
      	}
      	else
      	{
      		printf("you did not use any option...\n");
      	}
      	return 0;
      }
      

      代码运行结果如下:
      Linux进程概念(精讲)

      现在我们来说说main函数的第三个参数。

      main函数的第三个参数接收的实际上就是环境变量表,我们可以通过main函数的第三个参数来获取系统的环境变量。
      例如,编写以下代码,生成可执行程序并运行。
      Linux进程概念(精讲)
      运行结果就是各个环境变量的值:
      Linux进程概念(精讲)
      除了使用main函数的第三个参数来获取环境变量以外,我们还可以通过第三方变量environ来获取。
      Linux进程概念(精讲)
      运行该代码生成的可执行程序,我们同样可以获得环境变量的值:
      Linux进程概念(精讲)
      注意: libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用时要用extern进行声明。

      通过系统调用获取环境变量

      除了通过main函数的第三个参数和第三方变量environ来获取环境变量外,我们还可以通过系统调用getenv函数来获取环境变量。
      getenv函数可以根据所给环境变量名,在环境变量表当中进行搜索,并返回一个指向相应值的字符串指针。

      例如,使用getenv函数获取环境变量PATH的值。
      Linux进程概念(精讲)
      运行结果:
      Linux进程概念(精讲)

      程序地址空间

      下面这张空间布局图相信大家都见过:
      Linux进程概念(精讲)
      在Linux操作系统中,我们可以通过以下代码对该布局图进行验证:
      Linux进程概念(精讲)
      运行结果如下,与布局图所示是吻合的:
      Linux进程概念(精讲)

      下面我们来看一段奇怪的代码:

      Linux进程概念(精讲)
      代码当中用fork函数创建了一个子进程,其中让子进程相将全局变量g_val该从100改为200后打印,而父进程先休眠3秒钟,然后再打印全局变量的值。
      按道理来说子进程打印的全局变量的值为200,而父进程是在子进程将全局变量改后再打印的全局变量,那么也应该是200,但是代码运行结果如下:
      Linux进程概念(精讲)
      可以看到父进程打印的全局变量g_val的值仍为之前的100,更奇怪的是在父子进程中打印的全局变量g_val的地址是一样的,也就是说父子进程在同一个地址处读出的值不同。

      如果说我们是在同一个物理地址处获取的值,那必定是相同的,而现在在同一个地址处获取到的值却不同,这只能说明我们打印出来的地址绝对不是物理地址!!!

      实际上,我们在语言层面上打印出来的地址都不是物理地址,而是虚拟地址。物理地址用户一概是看不到的,是由操作系统统一进行管理的。

      所以就算父子进程当中打印出来的全局变量的地址(虚拟地址)相同,但是两个进程当中全局变量的值却是不同的。
      Linux进程概念(精讲)
      注意: 虚拟地址和物理地址之间的转化由操作系统完成。

      进程地址空间

      我们之前将那张布局图称为程序地址空间实际上是不准确的,那张布局图实际上应该叫做进程地址空间,进程地址空间本质上是内存中的一种内核数据结构,在Linux当中进程地址空间具体由结构体mm_struct实现。

      进程地址空间就类似于一把尺子,尺子的刻度由0x00000000到0xffffffff,尺子按照刻度被划分为各个区域,例如代码区、堆区、栈区等。而在结构体mm_struct当中,便记录了各个边界刻度,例如代码区的开始刻度与结束刻度,如下图所示:
      Linux进程概念(精讲)
      在结构体mm_struct当中,各个边界刻度之间的每一个刻度都代表一个虚拟地址,这些虚拟地址通过页表映射与物理内存建立联系。由于虚拟地址是由0x00000000到0xffffffff线性增长的,所以虚拟地址又叫做线性地址。

      扩展知识:
      1、堆向上增长以及栈向下增长实际就是改变mm_struct当中堆和栈的边界刻度。
      2、我们生成的可执行程序实际上也被分为了各个区域,例如初始化区、未初始化区等。当该可执行程序运行起来时,操作系统则将对应的数据加载到对应内存当中即可,大大提高了操作系统的工作效率。而进行可执行程序的“分区”操作的实际上就算编译器,所以说代码的优化级别实际上是编译器说了算。

      每个进程被创建时,其对应的进程控制块(task_struct)和进程地址空间(mm_struct)也会随之被创建。而操作系统可以通过进程的task_struct找到其mm_struct,因为task_struct当中有一个结构体指针存储的是mm_struct的地址。
      例如,父进程有自己的task_struct和mm_struct,该父进程创建的子进程也有属于其自己的task_struct和mm_struct,父子进程的进程地址空间当中的各个虚拟地址分别通过页表映射到物理内存的某个位置,如下图:
      Linux进程概念(精讲)
      而当子进程刚刚被创建时,子进程和父进程的数据和代码是共享的,即父子进程的代码和数据通过页表映射到物理内存的同一块空间。只有当父进程或子进程需要修改数据时,才将父进程的数据在内存当中拷贝一份,然后再进行修改。

      例如,子进程需要将全局变量g_val改为200,那么此时就在内存的某处存储g_val的新值,并且改变子进程当中g_val的虚拟地址通过页表映射后得到的物理地址即可。
      Linux进程概念(精讲)
      这种在需要进行数据修改时再进行拷贝的技术,称为写时拷贝技术。

      1、为什么数据要进行写时拷贝?

      进程具有独立性。多进程运行,需要独享各种资源,多进程运行期间互不干扰,不能让子进程的修改影响到父进程。

      2、为什么不在创建子进程的时候就进行数据的拷贝?

      子进程不一定会使用父进程的所有数据,并且在子进程不对数据进行写入的情况下,没有必要对数据进行拷贝,我们应该按需分配,在需要修改数据的时候再分配(延时分配),这样可以高效的使用内存空间。

      3、代码会不会进行写时拷贝?

      90%的情况下是不会的,但这并不代表代码不能进行写时拷贝,例如在进行进程替换的时候,则需要进行代码的写时拷贝。

      为什么要有进程地址空间?

      1、有了进程地址空间后,就不会有任何系统级别的越界问题存在了。例如进程1不会错误的访问到进程2的物理地址空间,因为你对某一地址空间进行操作之前需要先通过页表映射到物理内存,而页表只会映射属于你的物理内存。总的来说,虚拟地址和页表的配合使用,本质功能就是包含内存。
      2、有了进程地址空间后,每个进程都认为看得到都是相同的空间范围,包括进程地址空间的构成和内部区域的划分顺序等都是相同的,这样一来我们在编写程序的时候就只需关注虚拟地址,而无需关注数据在物理内存当中实际的存储位置。
      3、有了进程地址空间后,每个进程都认为自己在独占内存,这样能更好的完成进程的独立性以及合理使用内存空间(当实际需要使用内存空间的时候再在内存进行开辟),并能将进程调度与内存管理进行解耦或分离。

      对于创建进程的现阶段理解:

      一个进程的创建实际上伴随着其进程控制块(task_struct)、进程地址空间(mm_struct)以及页表的创建。

      Linux2.6内核进程调度队列

      Linux进程概念(精讲)

      一个CPU拥有一个runqueue

      如果有多个CPU就要考虑进程个数的父子均衡问题。

      优先级

      queue下标说明:

      • 普通优先级:100~139。
      • 实时优先级:0~99。

      我们进程的都是普通的优先级,前面说到nice值的取值范围是-20~19,共40个级别,依次对应queue当中普通优先级的下标100~139。

      注意: 实时优先级对应实时进程,实时进程是指先将一个进程执行完毕再执行下一个进程,现在基本不存在这种机器了,所以对于queue当中下标为0~99的元素我们不关心。

      活动队列

      时间片还没有结束的所有进程都按照优先级放在活动队列当中,其中nr_active代表总共有多少个运行状态的进程,而queue[140]数组当中的一个元素就是一个进程队列,相同优先级的进程按照FIFO规则进程排队调度。

      调度过程如下:

      1. 从0下标开始遍历queue[140]。
      2. 找到第一个非空队列,该队列必定为优先级最高的队列。
      3. 拿到选中队列的第一个进程,开始运行,调度完成。
      4. 接着拿到选中队列的第二个进程进行调度,直到选中进程队列当中的所有进程都被调度。
      5. 继续向后遍历queue[140],寻找下一个非空队列。

      bitmap[5]:queue数组当中一共有140个元素,即140个优先级,一共140个进程队列,为了提高查找非空队列的效率,就可以用5 × \times × 32个比特位表示队列是否为空,这样一来便可以大大提高查找效率。

      总结: 在系统当中查找一个最合适调度的进程的时间复杂度是一个常数,不会随着进程增多而导致时间成本增加,我们称之为进程调度的O(1)算法。

      过期队列

      • 过期队列和活动队列的结构相同。
      • 过期队列上放置的进程都是时间片耗尽的进程。
      • 当活动队列上的进程被处理完毕之后,对过期队列的进程进行时间片重新计算。

      active指针和expired指针

      • active指针永远指向活动队列。
      • expired指针永远指向过期队列。

      由于活动队列上时间片未到期的进程会越来越少,而过期队列上的进程数量会越来越多(新创建的进程都会被放到过期队列上),那么总会出现活动队列上的全部进程的时间片都到期的情况,这时将active指针和expired指针的内容交换,就相当于让过期队列变成活动队列,活动队列变成过期队列,就相当于又具有了一批新的活动进程,如此循环进行即可。

      版权声明:本文内容来自第三方投稿或授权转载,原文地址:https://blog.csdn.net/chenlong_cxy/article/details/120193456,作者:2021dragon,版权归原作者所有。本网站转在其作品的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如因作品内容、版权等问题需要同本网站联系,请发邮件至ctyunbbs@chinatelecom.cn沟通。

      上一篇:CDH6.3.2 集成 Freeipa 的kerberos

      下一篇:Linux命令之修改主机名hostnamectl

      相关文章

      2025-05-19 09:04:53

      查看RISC-V版本的gcc中默认定义的宏

      查看RISC-V版本的gcc中默认定义的宏

      2025-05-19 09:04:53
      c++ , linux
      2025-05-16 09:15:10

      Linux系统基础-进程信号超详细讲解

      Linux系统基础-进程信号超详细讲解

      2025-05-16 09:15:10
      kill , 信号 , 命令 , 进程
      2025-05-14 10:02:58

      Linux top 命令使用教程

      Linux top 是一个在Linux和其他类Unix 系统上常用的实时系统监控工具。它提供了一个动态的、交互式的实时视图,显示系统的整体性能信息以及正在运行的进程的相关信息。

      2025-05-14 10:02:58
      CPU , 信息 , 内存 , 占用 , 备注 , 进程
      2025-05-13 09:51:29

      ogg在启动应用进程时报错OGG-00412

      ogg在启动应用进程时报错OGG-00412

      2025-05-13 09:51:29
      ogg , OGG , 时报 , 进程
      2025-05-13 09:51:17

      无法启动ogg配置好的抓取以及投递进程

      无法启动ogg配置好的抓取以及投递进程

      2025-05-13 09:51:17
      ogg , 启动 , 抓取 , 进程
      2025-05-13 09:49:19

      内置变量_其他相关

      内置变量_其他相关

      2025-05-13 09:49:19
      参数 , 基础知识 , 实践 , 小结 , 脚本 , 进程
      2025-05-09 08:51:09

      【Linux 从基础到进阶】进程管理与性能调优

      在 Linux 系统中,进程是执行中的程序实例。有效的进程管理和性能调优可以提升系统的响应速度和资源利用率。本文将介绍进程管理的基本概念、常用命令,以及性能调优的最佳实践,适用于 CentOS 和 Ubuntu 系统。

      2025-05-09 08:51:09
      CPU , 性能 , 管理 , 调优 , 进程
      2025-05-07 09:08:16

      MFC如何打开和关闭进程、动态获取进程号、关闭自身进程

      进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,每一个进程都是一个实体有属于自己的地址控件,进程也是一个执行的程序

      2025-05-07 09:08:16
      函数 , 句柄 , 返回值 , 进程
      2025-04-22 09:40:08

      【Linux】Java进程CPU 使用率过高问题排查

      【Linux】Java进程CPU 使用率过高问题排查

      2025-04-22 09:40:08
      CPU , 打印 , 进程
      2025-04-22 09:27:28

      Linux进程调度与等待:背后的机制与实现

      Linux进程调度与等待:背后的机制与实现

      2025-04-22 09:27:28
      wait , 状态 , 进程
      查看更多
      推荐标签

      作者介绍

      天翼云小翼
      天翼云用户

      文章

      33561

      阅读量

      5243147

      查看更多

      最新文章

      查看RISC-V版本的gcc中默认定义的宏

      2025-05-19 09:04:53

      ogg在启动应用进程时报错OGG-00412

      2025-05-13 09:51:29

      【Linux 从基础到进阶】进程管理与性能调优

      2025-05-09 08:51:09

      MFC如何打开和关闭进程、动态获取进程号、关闭自身进程

      2025-05-07 09:08:16

      Linux进程调度与等待:背后的机制与实现

      2025-04-22 09:27:28

      Linux中ps命令使用指南

      2025-04-16 09:26:45

      查看更多

      热门文章

      Linux crontab 任务误删恢复及备份步骤

      2023-03-20 08:19:07

      Linux 趣味小知识--软硬连接以及应用

      2023-04-23 09:32:49

      Linux常用命令总结

      2023-05-12 07:20:42

      linux-压缩与解压缩

      2023-05-15 10:03:24

      linux基本命令(47)——iostat命令

      2023-05-12 07:21:43

      Linux中文本搜索命令grep用法详解

      2023-06-07 07:36:41

      查看更多

      热门标签

      linux java python javascript 数组 前端 docker Linux vue 函数 shell git 节点 容器 示例
      查看更多

      相关产品

      弹性云主机

      随时自助获取、弹性伸缩的云服务器资源

      天翼云电脑(公众版)

      便捷、安全、高效的云电脑服务

      对象存储

      高品质、低成本的云上存储服务

      云硬盘

      为云上计算资源提供持久性块存储

      查看更多

      随机文章

      Linux压缩和打包操作

      linux基本命令(56)——netstat命令

      Linux解压rar文件

      tar 解压缩命令

      linux基本命令(6)——rmdir命令

      linux基本命令(41)——ps命令

      • 7*24小时售后
      • 无忧退款
      • 免费备案
      • 专家服务
      售前咨询热线
      400-810-9889转1
      关注天翼云
      • 旗舰店
      • 天翼云APP
      • 天翼云微信公众号
      服务与支持
      • 备案中心
      • 售前咨询
      • 智能客服
      • 自助服务
      • 工单管理
      • 客户公告
      • 涉诈举报
      账户管理
      • 管理中心
      • 订单管理
      • 余额管理
      • 发票管理
      • 充值汇款
      • 续费管理
      快速入口
      • 天翼云旗舰店
      • 文档中心
      • 最新活动
      • 免费试用
      • 信任中心
      • 天翼云学堂
      云网生态
      • 甄选商城
      • 渠道合作
      • 云市场合作
      了解天翼云
      • 关于天翼云
      • 天翼云APP
      • 服务案例
      • 新闻资讯
      • 联系我们
      热门产品
      • 云电脑
      • 弹性云主机
      • 云电脑政企版
      • 天翼云手机
      • 云数据库
      • 对象存储
      • 云硬盘
      • Web应用防火墙
      • 服务器安全卫士
      • CDN加速
      热门推荐
      • 云服务备份
      • 边缘安全加速平台
      • 全站加速
      • 安全加速
      • 云服务器
      • 云主机
      • 智能边缘云
      • 应用编排服务
      • 微服务引擎
      • 共享流量包
      更多推荐
      • web应用防火墙
      • 密钥管理
      • 等保咨询
      • 安全专区
      • 应用运维管理
      • 云日志服务
      • 文档数据库服务
      • 云搜索服务
      • 数据湖探索
      • 数据仓库服务
      友情链接
      • 中国电信集团
      • 189邮箱
      • 天翼企业云盘
      • 天翼云盘
      ©2025 天翼云科技有限公司版权所有 增值电信业务经营许可证A2.B1.B2-20090001
      公司地址:北京市东城区青龙胡同甲1号、3号2幢2层205-32室
      • 用户协议
      • 隐私政策
      • 个人信息保护
      • 法律声明
      备案 京公网安备11010802043424号 京ICP备 2021034386号