当前位置: 首页 > news >正文

【Linux】进程概念(上):从冯诺依曼到进程入门

前言:进程是操作系统的核心概念之一,是我们必学的内容之一。我们的日常生活中使用的app,或是后台运行的程序,其本质都是一个个被OS管理的进程。本篇从冯诺依曼体系结构出发,一步步揭开进程的神秘面纱,进程到底是何方神圣?

一、冯诺依曼体系结构

要说进程,首先得知道计算机的基础架构。我们日常使用的笔记本、服务器等设备,大多遵循冯诺依曼体系,其核心思想可以概括为:所有的设备都只能直接和内存打交道,计算机硬件间数据交互的媒介是内存。

冯诺依曼体系的硬件组成包括:

  • 输入设备:键盘、鼠标、扫描仪等,负责将数据传入系统
  • 中央处理器(CPU):包括运算器和控制器,是执行指令的核心;
  • 存储器:此处特指内存,用于临时存储数据和指令;
  • 输出设备:显示器、打印机等,负责将处理结果输出。
    在这里插入图片描述

图片解释案例–以QQ聊天为例:
当我们发送一条信息时,信息的流动严格遵循冯诺依曼规则:

  1. 你在键盘输入消息(输入设备),数据先被写入保存;
  2. CPU从内存中读取消息数据,进行处理(如封装、加密等)
  3. 处理后的消息再次被写入内存,再通过网卡(输出设备)发送给对方;
  4. 对方的QQ接收信息后,先存入其设备内存,再由CPU处理并通过显示器(输出设备)展示。
    简洁一点来说就是,信息(数据)从本地磁盘读入内存,经CPU处理后通过网络发送,接收方则将数据从内存写入其磁盘。

二、操作系统:进程的“老大”(1号进程)

2.1概念:

OS:任何计算机系统都包含一个基本的程序集合,成为操作系统。操作系统的定位是一款“搞管理”的软件!
可以笼统的理解,OS包括:

  • 内核:进程管理、内存管理、文件管理、驱动管理
  • 其他程序:如函数库,shell程序等
    在这里插入图片描述

2.2操作系统的两层定位

  • 对下,与硬件交互,管理CPU、内存、磁盘等资源
  • 对上,为用户程序(应用程序)提供一个良好的执行环境
    在这里插入图片描述

2.4如何理解“管理”?

操作系统的“管理”,本质是对被管理对象的组织与描述:

  • 组织:用链表、树等结构将对象有序排列,方便高效管理
  • 描述:用数据结构(如struct)记录对象属性,例如用task_struct描述进程
    如以下的一个案例:大学校长–大学辅导员–学生:辅导员用表格(描述)记录学生,再按班级(组织)分类管理
    在这里插入图片描述

三、进程:程序的“执行实例”

3.1什么是进程?

  • 课本概念:进程是程序的一个执行实例,正在执行的程序等;
  • 内核观点:担当分配系统资源(CPU时间、内存等)的实体
  • 这里要说的:进程=内核数据结构(task_struct)+程序代码+数据

3.2进程的唯一标识:PCB

进程控制块(PCB):PCB是描述进程的核心数据结构,在Linux中成为task_struct。它包含进程的关键信息:

  • 标识符(PID):唯一区分进程的ID;
  • 状态:任务状态、推出代码、退出信号等
  • 优先级:相对于其他进程的优先级
  • 程序计数器(PC):下一条要执行的指令地址
  • 内存指针:指向程序代码和数据的位置
  • 上下文数据:CPU寄存器中的数据,用于进程切换后恢复进程执行
    所有进程的task_struct以双链表形式组织,方便操作系统遍历和管理
3.2.1查看进程信息:
  • 通过/proc系统文件夹查看:(查看一号进程)
    在这里插入图片描述

  • 大多数进程信息同样可以使用top和ps这些用户级工具获取
    代码:在终端输入:vim test.c

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>int main()
{while(1){printf("这是一个进程")sleep(1);}return 0;
}

指令:

//通用命令
ps aux | ps axj 命令
  • a:显示一个终端所有的进程,包括其他用户的进程
  • x:显示没有控制终端的进程,例如后台运行的进程
  • j:显示进程归属的进程组ID、会话ID、父进程ID,以及与作业控制相关的信息
  • u:以用户为中心的格式显示进程信息,提供进程的详细信息,如用户、CPU和内存使用情况
//这里是查看一号进程:
ps aux | grep test | grep -v grep

在这里插入图片描述

3.2.3通过系统调用获取进程标识符
  • 父进程ID:PPID;
  • 进程ID:PPID
    在此之前,我们先认识一个函数getpid,在命令行输入man getpid可以看到这个函数的详情。
    在这里插入图片描述

了解以后,我们就可以初步看看,怎么获取进程ID了:

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>int main()
{while(1){printf("我的进程ID是:%d\n",getpid());printf("我的父进程ID是:%d\n",getppid());sleep(10);}return 0;
}

在这里插入图片描述

3.2.4通过系统调用创建进程:初识fork

fork是什么?fork的作用是创建一个新进程,新进程称为子进程,原来那个进程称为父进程。在命令行输入man fork,可以看到fork的用法。
man fork以后可以看到:

  • fork有两个返回值,从父进程的角度看,fork 会返回子进程的进程 ID(PID,一个正整数);而从子进程的角度看,fork 会返回 0
  • 父子进程代码共享,数据各自开辟空间,私有一份(采用写时拷贝)
    在这里插入图片描述

接下来简单实现一下fork的功能:
代码:

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>int main()
{int ret=fork();//创建失败if(ret<0){perror("fork");return 1;}//子进程else if(ret==0){printf("I am child:%d!,ret:%d\,",getpid(),ret);}//父进程else{printf("I am father:%d!,ret:%d\n",getppid(),ret);}sleep(5);return 0;}

在这里插入图片描述

3.3进程状态:进程的”一生之旅“

在这里插入图片描述

3.3.1进程状态名称:

一个进程能有几个状态(在Linux内核里,进程有时候也叫做任务),这些状态在kernel源代码中这么定义:

  • R运行状态(running):要么正在执行,要么在运行队列中等待
  • S睡眠状态(sleeping):等待事件完成(可中断睡眠,如等待用户输入)
  • D磁盘休眠状态(Disk sleep):有时候也叫不可中断睡眠,即等待IO完成,此时进程不能被杀死
  • T停止状态(stopped):收到SIGSTOP信号后暂停,可通过SIGCONT恢复
  • X死亡状态(dead):进程彻底结束,无法在任务列表中看到
3.3.2 Z(zombie)–僵尸进程
  • 僵尸进程:当子进程退出并且父进程没有读取子进程退出的返回代码时,就会产生僵尸进程。
  • 僵尸进程会以终止状态保持在进程表中,并且会一直等待父进程读取退出状态码
  • 简单来说就是,只要子进程退出,父进程在运行,并且没有读取子进程的状态,子进程就会进入Z状态
    简单举个例子:
#include<stdio.h>
#include<stdlib.h>int main()
{       pid_t ret = fork();if(ret<0){printf("fork error\n");return 1;}else if(ret > 0){printf("parent[%d] is sleeping...\n",getpid());sleep(50);}else{printf("child:[%d],ready to zombie...\n",getpid());sleep(5);exit(EXIT_SUCCESS);}return 0;
}       

在这里插入图片描述

测试语句:

while :; do ps aux | grep zombie | grep -v grep; sleep 1; echo "------------------------"; done

在这里插入图片描述

如上图我们可以看到,父进程不读取,子进程确实会变成僵尸进程,那僵尸进程会导致什么后果呢?

  • 父进程不读取,子进程会一直处于Z状态
  • Z状态一直不退出,PCB一直都要维护
  • 父进程一直不回收子进程,同时创建很多个子进程,会导致资源的浪费,且会导致资源泄露
3.2.3 孤儿进程

孤儿进程:父进程提前退出,子进程被1号进程init收养,由系统负责回收,避免成为僵尸进程
举个例子:

#include <stdio.h>   
#include <unistd.h>  
#include <stdlib.h>  
#include <sys/types.h>  int main() {// 创建子进程pid_t pid = fork();// 判断fork是否成功if (pid < 0) {perror("fork failed");  return 1;  }// 子进程else if (pid == 0) {printf("子进程: PID=%d, 父进程PID=%d\n", getpid(), getppid());sleep(10);printf("子进程: 10秒后,父进程PID=%d (已被领养)\n", getppid());}// 父进程else {printf("父进程: PID=%d, 子进程PID=%d\n", getpid(), pid);sleep(3);printf("父进程: 即将退出\n");// 父进程主动退出exit(0);}return 0;
}

显而易见:父进程提前退出的子进程确实被1号进程收养了,避免了成为僵尸进程
在这里插入图片描述

3.3 进程优先级

进程的的优先权即cpu资源分配的先后顺序,优先权高的进程有优先执行的权力。

3.3.1查看系统进程

在Linux或Unix系统中,用ps -l命令输出内容:
在这里插入图片描述

图中的被圈起来的代表什么呢?

  • UID:代表执行者的身份(root用户为0,普通用户从1000开始)
  • PID:代表这个进程的代号
  • PPID:父进程代号
  • PRI:代表这个进程可被执行的优先级,其值越小越早执行
  • NI:代表这个进程的nice值(nice值即进程的优先级数值,数值范围是-20~+19
    【注】nice值不是进程优先级,它代表的是进程优先级的数值,可以理解为nice值是进程优先级的修正数据
3.3.2 查看进程优先级的命令

用top命令更改已存在进程的nice:

  • 在终端输入:top回车->r回车->输入PID->输入nice值
    把nice值改成10,改前:
    在这里插入图片描述

改后:
在这里插入图片描述

【注】nice值的取值范围是:-20~+19即无论你设置到大于19,小于-20,都会是在这个范围,就比如说,你设置的是+30,那么它最大不能超过19,他就只会到19了。

3.3.3 竞争、独立、并行、并发
  • 竞争性:系统进程数目众多,而CPU资源只有少量,所以进程之间是具有竞争属性的
  • 独立性:多进程运行,需独立享有各种资源,多进程间互不干扰
  • 并行:多个进程在多个CPU下分别,同时进行运行,称之为并行
  • 并发:多个进程在一个CPU下采用进程切换的方式,在一段时间内,让多个进程都得以推进,称之为并发。
3.3.4 进程切换(process switching)

基本概念:是指操作系统从一个正在运行的进程中暂停,保存其运行状态,并恢复另一个进程的运行状态,使其开始执行的过程

进程切换的原因:
  • 时间片耗尽:在采用时间片轮转调度算法的系统中,每个进程被分配一个固定的时间片来使用 CPU
  • 更高优先级进程就绪:如果系统采用优先级调度算法,当有更高优先级的进程进入就绪队列时,正在运行的低优先级进程会被暂停,操作系统切换到高优先级进程执行,确保重要或紧急的任务能够优先得到处理
  • 等待资源:当进程需要等待某些资源(如 I/O 操作完成、等待信号量等)而无法继续执行时,它会主动放弃 CPU,进入等待状态
进程切换的过程:
  • 保存当前进程状态:操作系统会将当前正在运行进程的相关信息保存起来,主要包括程序计数器(PC)的值,它记录了进程下一条要执行的指令地址;CPU 寄存器中的数据,如通用寄存器、状态寄存器等,这些数据反映了进程当前的运算状态和控制信息;进程的上下文环境,包括栈指针等信息。这些信息会被存储在进程的进程控制块(PCB)中。
  • 更新进程状态:将当前进程的状态从 “运行” 状态改为 “就绪” 状态或 “等待” 状态,具体取决于进程切换的原因。同时,从就绪队列中选择一个合适的进程,将其状态从 “就绪” 状态改为 “运行” 状态。
  • 恢复新进程状态:从即将运行进程的 PCB 中读取之前保存的状态信息,恢复该进程的程序计数器、CPU 寄存器等数据,让 CPU 能够从上次中断的地方继续执行该进程的指令。
    图示:
    在这里插入图片描述
3.3.5 进程调度:内核进程O(1)调度队列

在这里插入图片描述

【注】活跃进程是时间片还没用完的进程,过期进程是时间片耗尽的进程,还在等待时间片的进程

优先级
  • 普通优先级:100~139(我们所说的进程都是普通优先级)
  • 实时优先级:0~99(系统管)
活动队列(active)
  • 时间片还没有结束的所有进程都按照优先级放在该队列
  • nr_active:总共有多少个运行状态的进程
  • queue(140):一个元素就是一个进程队列,相同优先级的进程按照FIFO规则进行排队调度,所以, 数组下标就是优先级!
    那么选择一个进程,过程应该是怎么养的呢?
  1. 从0下标开始遍历queue(140)
  2. 找到第一个非空队列,该队列必定是优先级最高的队列
  3. 拿到选中队的第一个进程,开始运行,调度完成
  4. 遍历queue(140)时间复杂度是常数(低效!)

则有:bitmap(5):一共140个优先级,一共140个进程队列,为了提⾼查找非空队列的效率,就可以用5 * 32个比特位表示队列是否为空,这样,便可以大大提⾼查找效率!
在这里插入图片描述

过期队列
  • 结构和活动队列一模一样
  • 过期队列上方的都是时间片耗尽的进程
  • 当活动队列上的进程都被处理完毕后,对过期队列的进程进行时间片重新计算
active指针和expired指针
  • active指针永远指向活动队列
  • expired指针永远指向过期队列
  • 活动队列上的进程会越来越少,过期队列上的进程会越来越多,因为进程时间片到期时一直都存在的;
  • 在合适的时候,只要交换active指针和expired指针的内容,就相当于又具有了一批新的活跃进程!
http://www.dtcms.com/a/392557.html

相关文章:

  • 计算机视觉(opencv)实战二十八——基于 OpenCV CSRT 跟踪器的实时目标
  • 【Mysql】深分页问题、页分裂问题、加密/解密、执行计划
  • 【名人简历】牛顿
  • coze开发的牙科AI智能体助手web页面
  • JavaEE初阶——从入门到掌握线程安全
  • GitHub热门大数据项目:基于人体生理指标管理的可视化分析系统技术解析
  • 零基础学Docker(2)--基本命令
  • 华为FusionCloud私有云:企业数字化转型的智能底座
  • 【LVS入门宝典】LVS NAT模式深度解析:从原理到实战配置指南
  • MQ 项目(实习项目,初步更新)
  • Redis中Lua脚本的应用场景分析
  • phpkg 让 PHP 摆脱 Composer 依赖地狱
  • Python -- 人生重开模拟器(简易版)
  • CSS基础查缺补漏(持续更新补充)
  • 用户生命周期价值(CLV)目标变量系统性设计与实践(二)
  • TDengine 与工业应用平台 Ignition 集成
  • JVM垃圾收集中判断对象存活相关问题
  • 【C++】告别“类型转换”踩坑,从基础到四种核心强制转换方式
  • WinDivert学习文档之五-————编程API(八)
  • 【LVS入门宝典】LVS NAT模式深度解析:流量走向与IP包头修改机制
  • 第二章 微调:定制专属模型——从通用能力到场景适配
  • 为统信UOS2.0离线安装python3.11.9开发环境
  • Maven 进阶:依赖管理的 “坑” 与解决方案
  • 2.15Vue全家桶-VueRouter
  • 五、Maven引入
  • 通过 TypeScript 在 Vue 3 中利用类型系统优化响应式变量的性能
  • Maven 入门:从 “手动导包” 到 “自动化构建” 的第一步
  • 【Python】数组
  • AI任务相关解决方案18-基于大模型、MCP、Agent与RAG技术的数据分析系统研究报告
  • 飞牛NAS系统版本重大更新:支持挂载115网盘!挂载教程来袭!