• 【Linux】进程概念(万字详解)—— 冯诺依曼体系结构 | 操作系统 | 进程


    ⚡欢迎来到Linux专栏 ~~进程概念


    • (꒪ꇴ꒪(꒪ꇴ꒪ )🐣,我是Scort🎓
    • 🌍博客主页:张小姐的猫~江湖背景🌍
    • 快上车🚘,握好方向盘跟我有一起打天下嘞!
    • 送给自己的一句鸡汤🤔:
    • 🔥集中起来的意志可以击穿顽石🔥
    • 🙏作者水平很有限,如果发现错误,可在评论区指正,感谢🙏
    • 🎉🎉欢迎持续关注!🎉🎉
      在这里插入图片描述

    请添加图片描述

    请添加图片描述

    一、冯洛伊曼体系结构

    🌌体系结构

    在这里插入图片描述
    说明:

    1. 输入设备:键盘,摄像头,话筒,磁盘,网卡
    2. 输出设备:显示器、磁盘、网卡、显卡、音响
    3. 存储器:指的是内存!不是磁盘
    4. 中央处理器:其中运算器进行算术运算逻辑运算

    🌌数据流向

    冯 • 诺依曼体系结构规定了硬件层面上的数据流向,所有的输入单元的数据必须先写到存储器中 (这里只是针对数据,不包含信号(通过外设直接对 CPU 交互)),然后 CPU 通过某种方式访问存储器,将数据读取到 CPU 内部,运算器进行运算,控制器进行控制,然后将结果写回到内存,最后将结果传输到输出设备中。

    在这里插入图片描述
    由上图我们知道

    cpu &&寄存器 > 内存 > 磁盘/SSD > 光盘 > 磁带

    这里有一个不太严谨的运算速度的数据,CPU 是纳秒级别的;内存是微秒级别的;磁盘是毫秒级别的。当一个快的设备和一个慢的设备一起协同时,最终的运算效率肯定是以慢的设备为主,就如 “ 木桶原理 ”:也就是说一般 CPU 去计算时,它的短板就在磁盘上,所以整个计算机体系的效率就一定会被磁盘拖累,如果把软件数据放在内存里,cpu直接和内存交互,如此一来效率大大提升。

    所以本质上可以把内存看作 CPU 和所有外设之间的缓存,也可以理解成这是内存的价值。

    在这里插入图片描述

    🔥总结CPU不直接和外设打交道,因为CPU很快,外设很慢。因此有存储器在二者间起缓冲作用。在数据层面,任何外设,基本优先对内存读写;CPU也是直接对内存读写,内存是体系结构的核心设备IO = input + output。

    🌌实例

    对冯诺依曼的理解,不能只停留在概念上,要深入到对软件数据流理解上,请解释,从你登录上qq开始和某位朋友聊天开始,数据的流动过程。从你打开窗口,开始给他发消息,到他的到消息之后的数据流动过程。如果是在qq上发送文件呢?

    在这里插入图片描述
    同理文件也是这样子接收的

    二、操作系统 (Operator System)

    🌈是什么what?

    操作系统,是一款专门针对软硬件进行管理软件

    🌈为什么why?

    在整个计算机软硬件架构中,操作系统的定位是:一款纯正的“搞管理”的软件

    • 对上:给用户提供稳定、高效、安全的运行环境 —— 目的
    • 对下:管理好软硬件资源 —— 方式

    🌈怎么样管理how?

    🌊以学校中的管理类比,操作系统中——在学校里大概有这三种角色:

    1. 管理者和被管理者并不会直接打交道(就好像你在学校见过校长吗❓)
      学生 (被管理者) —— 软硬件
      辅导员 (执行者) —— 驱动
      校长 (管理者) —— 操作系统
    2. 如何管理我们?
      对我们做出各种决策,依据就是你的核心数据
    3. 校长是如何做执行的?你的数据如何被校长拿到?
      通过辅导员

    🚩站在校长的角度———

    • 用结构体来描述一个学生的数据
    • 来用特定的数据结构来组织,于是对学生的管理工作,变成了对数据结构的增删查改———对多个学生进行管理!

    ⚡管理的理念—— 先描述,再组织

    • 先描述:被管理的对象
    • 再组织:将被管理的对象用特定的数据结构组织起来

    🌏对应到操作系统——

    在这里插入图片描述
    ⚡系统调用和库函数概念 ❗

    1. 在开发角度,操作系统对外会表现为一个整体,它不相信任何用户,但是会暴露自己的部分接口供上层开发者使用,这部分由操作系统提供的接口,叫做系统调用。
    2. 系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有心的开发者就对部分系统调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行二次开发。

    类似于银行取钱时,一般都会雇佣服务人员 (库),王大爷不会取钱,就叫指导人员来指导 (调用库)。其实对于库函数的使用要么使用了 SystemCall,如 printf 函数;要么没使用 SystemCall,如 sqrt 函数。

    操作系统是怎么管理进行进程管理的呢?很简单,先把进程描述起来,再把进程组织起来!

    三、进程 (process)

    🌊基本概念

    • 课本概念:程序的一个执行实例,正在执行的程序等
    • 内核观点:担当分配系统资源(CPU时间,内存)的实体
      在这里插入图片描述

    当我们启动一个软件的时候,本质上就是启动了一个进程
    在linux中运行一条命令./xxx运行的时候,其实就是在系统层面创建了一个进程

    Linux是可以同时加载多个程序的,Linux是可能同时存在大量的进程在系统的OS、内存

    🌊描述进程-PCB

    为什么要有PCB?因为我们要先描述进程,后管理

    在任何进程形成的时候,操作系统要为进程创建PCB(process control block),进程控制块 —— 就是描述进程的结构体

    我们知道:文件 = 内容 + 属性
    把mytest.exe加载到内存里,本质上只是把内容加载到内存里,可是我们要管理进程,这里就需要大量的PCB结构来描述这里的进程,其中PCB包含了进程所有的属性:(包括了代码在哪、数据在哪、谁启动、什么时间启动的)

    在这里插入图片描述

    对进程的管理,变成了对进程PCB结构体链表的增删查改❗
    在这里插入图片描述

    🔥🔥什么是进程?

    进程 = 对应的代码和数据 + 进程对应的PCB结构体

    struct PCB
    {
    	//属性数据,进程全部的属性数据 
    }
    
    • 1
    • 2
    • 3
    • 4

    Linux 操作系统下的 PCBtask_struct,相当于是媒婆和王婆之间的关系。它会被装载到 RAM(内存) 里并且包含着进程的信息。

    struct task_struct
    {
    	//进程全部属性数据 
    }
    
    • 1
    • 2
    • 3
    • 4

    🌌 task_struct中有什么属性字段?

    1. 标示符: 描述本进程的唯一标示符,用来区别其他进程。

    2. 状态: 任务状态,退出代码,退出信号等。

    3. 优先级: 相对于其他进程的优先级。

    4. 程序计数器: 程序中即将被执行的下一条指令的地址

    5. 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针

    6. 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。

    7. I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表

    8. 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。

    9. 其他信息

    🌊组织进程

    可以在内核源代码里找到它。所有运行在系统里的进程都以task_struct链表的形式存在内核里。

    四、查看进程

    我写了一段程序myproc.c就是隔1s打印,./运行,同时复制SSH渠道再打开一个窗口,便于监视进程。

    💦查看进程

    ps axj | grep "myproc" 
    
    • 1

    🌈关闭进程 ——

    [Ctrl + C]
    kill -9 [pid] 向目标进程发送9号信号 -- 同时也证明pid能标识系统上的唯一进程
    
    • 1
    • 2

    在这里插入图片描述

    其中下面的是grep进程,我们不用管

    在这里插入图片描述

    🌈以文件形式查看进程 ——/proc是Linux系统下查看进程的目录

    ls /proc
    top  //不常用 相当于任务管理器
    
    • 1
    • 2

    进程启动后,会在/proc下形成目录,以自身PID的编号作为目录文件名 ——

    在这里插入图片描述

    🌈查看该进程的属性数据

    在这里插入图片描述

    每个进程都会有一个属性,来保存自己所在的工作路径

    ls / proc 目录中,当我们停止掉某个进程,此进程目录就会消失所以proc目录是动态

    五、通过系统调用获取进程标示符

    我们可以使用 man 2 getpid/getppid 命令来查看人生中第一个系统调用接口

    💛 查看进程PID

    在这里插入图片描述

    执行以下代码———

     #include
     #include
       
     int main()
     {
         while(1)
         {
           pid_t id = getpid();//获取的是自己进程的PID
          printf("hello world! pid: %d\n",id);                                               
          sleep(1);
         }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述

    父进程

    执行以下代码———

     #include
     #include
      
     int main()
     {
        while(1)
        {
          pid_t pid = getppid();//父进程
          pid_t id = getpid();//获取的是自己进程的PID
          printf("hello world! pid: %d, ppid: %d\n",id,pid);                                            
          sleep(1);
        }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述
    这里我们发现父进程居然是bash,我们回想一下shell外壳 ❗❗可以看看这篇博客🚩shell外壳详解🚩
    我们可以假设,这里的bash是王婆,为了完成任务,但又不想砸了自己的招牌,所以招了个实习生(可以理解成子进程

    我们在操作命令行的时候,父进程永远是bash外壳,其原理:shell外壳通过创建子进程的方式,以bash的子进程去执行

    每次我们登录成功的时候,系统就会指派一个王婆跟着你,当你输入命令行的时候,王婆会说她帮你创建子进程去执行。卖个关子🚩王婆bash的父进程是谁??

    六、通过系统调用创建进程-fork初识

    💛 创建子进程

    在这里插入图片描述

    #include
    #include
    int main()
    {
       printf("I am parent process!\n");
       fork();
       
       printf("you can see me ?\n");
       sleep(1);
       return 0;
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述调用了fork之后,我们发现打印了两次,其实就是又两个进程在实行来执行代码,💡下面我来深入探究

    1️⃣如何理解fork创建子进程

    ✨fork本质是创建进程,系统中多了一个进程,就多了一份与进程相关的内核数据结构PCB + 进程的代码和数据 。 我们fork只是创建了子进程,但是子进程对应的代码和数据呢?

    1. 默认情况下,子进程会继承父进程的代码和数据
      💖代码:父子进程代码共享,但是父子进程对应的id值不同,所以会执行不同的代码
      💖数据:默认情况下,数据也是“共享的”,不过修改时会发生写时拷贝来维护数据的独立性
    2. 子进程内核的数据结构task_struct,也会以父进程的为模板初始化自身

    注:if 和 else if 有没有可能是同时执行的呢?

    • 没有!因为id在父进程里面是子进程的pid,在子进程里面是0,所以一般else运行父进程

    在这里插入图片描述

    2️⃣fork有两个返回值

    1. 如何理解一个函数有两个返回值

      1️⃣因为在fork内部,return时子进程已被创建,甚至可以被调度了,父子进程各自会执行return语句。
      2️⃣返回两次并不意味着会保存两次(买个关子后面讲👍)

    2. 我们创建的子进程和父进程是做相同的事情吗?岂不是没有意义
      答:是通过if-else分流,让父子进程各自执行不同的代码段,而这就是通过fork的返回值来完成的。
      创建失败<0
      创建成功:给父进程返回子进程的PID;给子进程返回0,表示成功创建

    3. 为什么给子进程返回0,给父进程返回子进程的pid?
      💡首先我们知道:父进程:子进程 = 1:n
      💡第二:因为父进程可能会创建多个子进程,这为了保证父进程能拿到想拿到的子进程(你爸给你起名字),而子进程返回 0 的原因是父进程对于子进程是唯一的(好比你不可能给你爸起名字)
      在这里插入图片描述

    4. 父子进程被创建出来,哪一个进程先运行呢?
      答:不一定!!这个是由操作系统的调度器决定

    ⚡多进程代码,让父子执行不同的事情:if else 分流

     #include
     #include
     int main()
     {
         pid_t id = fork();
         if(id < 0)
         {
           perror("fork");
           return 1;
         }
         else if(id == 0)                                                                                                             
         {
            //子进程
            while(1)
            {
              printf("I am child, pid :%d, ppid: %d\n", getpid(),getppid());
              sleep(1);
            }
         }
         else
         {
            //父进程
            while(1)
            {
              printf("I am father, pid :%d, ppid: %d\n", getpid(),getppid());
              sleep(1);
            }
          }
      return 0}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    七、进程状态

    进程的状态信息也是在task_struct(PCB)中。进程状态的意义在于,方便OS快速判断进程,并完成特定的功能,比如调度。本质上是一种分类。

    💦进程状态

    下面的状态在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 */
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    一个完整的进程状态的变迁如下图————
    在这里插入图片描述

    ⭐️ R 运行状态(running): 并不意味着进程一定在运行中❓ 进程可能在运行队列中等或者正在被执行,就叫做 运行态,随时可以被CPU调度

    在这里插入图片描述

    ⭐️ S 浅度睡眠状态(sleeping) ,也叫做可中断睡眠(interruptible sleep)

    • 等待非CPU资源就绪。这种休眠是可被换醒的,我们可以 Ctrl + C 退出循环,而此时的进程就没了,也就是说它虽然是一种休眠状态,但是它随时可以接收外部的信号,处理外部的请求。

    在这里插入图片描述在这里插入图片描述

    • ⭐️ 挂起状态(也属于S 状态)

      内存不足的时候,OS提供适当的置换进程的代码和数据到磁盘中,PCB不换(好比你学籍还在,人把你赶走了)进程的状态就叫做挂起 在这里插入图片描述
      📌你现在正在等待某种资源的时候,正巧内存不足了,内存不够是你正在阻塞状态,所以把你的代码数据置换到磁盘里,所以叫做“挂起阻塞

    ⭐️ D 深度睡眠状态(Disk sleep),也叫不可中断睡眠状态(uninterruptible sleep)
    进程处于D状态,不可被杀掉,耶稣来了都没用,只能等这个进程自动醒来,kill -9 都杀不掉它,也得等它醒来 (关机除外,有可能关机都要被磁盘写入卡住,只能拔电源)
    在这里插入图片描述
    dd命令能够演示D状态进程(想知道的同学可以自行百度)

    ⭐️ T暂停状态(stopped)
    可以通过发送 SIGSTOP(kill -19) 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。

    ⭐️ X死亡状态(dead)
    随时准备被OS回收。此状态只是一个返回状态,无法在任务列表中看到这个状态。因为回收进程是一瞬间发生的事情,我们很难直接捕捉到。

    ⭐️ Z僵尸状态(Zombie)
    💦是什么:一个进程已经退出,但还不允许被OS释放,处于一个被检测的状态(好比出事了,警察要拉警戒线去调查原因),一般是父进程或者OS,想要得知该进程的结果,如何检测呢?这个我们后文再细说

    💦为什么? 维持该状态是为了让父进程和OS来回收,从Z状态变成X

    在这里插入图片描述

    演示R/S/T状态:同样的复制SSH渠道,监视

    1. 运行状态 R ——— 死循环
    #include      
    int main()      
    {      
      while(1);    
      return 0;    
    }  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    1. 睡眠状态S —— sleep
    #include 
    #include     
    int main()      
    {      
      sleep(100) ; //睡眠100秒
      return 0;    
    }  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

    1. 暂停状态 T
      我们先看看kill指令
      在这里插入图片描述
      暂停进程——
      在这里插入图片描述这时,发送信号恢复状态,会发现S后面没有+号,[ctrl + C] 也没法终止程序,这是因为你的暂停和继续让进程变成了后台运行。
      在这里插入图片描述
      这时候直接
    kill -9  pid //可直接删除
    
    • 1

    ⚡ 前台进程:S+ 和后台进程:S 的区别 ——

    • 前台进程:./myproc,输入指令无效bash的命令行解释器就停止工作了,可以被【Ctrl +C】终止
    • 后台进程:./myproc &,可以执行指令,【Ctrl +C】 不能终止进程,退出进程要用kill

    在这里插入图片描述

    💦僵尸进程

    • 子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程就进入Z状态
    • 僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码

    监控命令行脚本(常用):

    while :; do ps axj | head -1 && ps axj | grep myproc | grep -v grep; sleep 1; echo "########################"; done
    
    • 1

    下面这段代码:我把子进程杀掉,父进程也不回收,看看子进程变啥样❓

    #include    
    #include    
        
    using namespace std;    
        
    int main()    
    {    
      pid_t id = fork();    
      if(id == 0)    
      {    
        //child    
        while(true)    
        {    
          cout << "I am a child, running!" << endl;    
          sleep(2);    
        }    
      }    
      else    
      {    
        //parent    
        cout << "father do nothing!" << endl;    
        sleep(50);                                                     
      }    
      return 0;    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    如果没有人检测和回收(由父进程来做),该进程退出就进入Z状态 ——

    在这里插入图片描述

    ⚡僵尸进程的危害:进程的退出状态被一直维持,本身就需要数据维护,占用了内存空间,长时间的占用就会导致内存泄漏!!如何避免呢?我们后面讲

    💦孤儿进程

    • 父进程如果提前退出,那么子进程后退出,进入Z之后,那该如何处理呢?
    • 父进程先退出,子进程就称之为“孤儿进程”
    • 孤儿进程被1号init进程(系统本身领养,当然要有init进程回收喽
      在这里插入图片描述此时进程变成了后台进程,【ctrl+c】停止不了,我们直接kill -9就好,乱了也没事

    八. 进程优先级

    什么是优先级?
    cpu资源分配的先后顺序,就是指进程的优先权(priority)

    为什么要有优先级?
    因为CPU是有限的!进程太多,需要通过某种方式(优先级)竞争资源()

    ✨查看优先级

    ps -l
    
    • 1

    在这里插入图片描述
    我们很容易注意到其中的几个重要信息,有下:

    • PRI :代表这个进程可被执行的优先级,其值越小越早被执行
    • NI :代表这个进程的nice值,优先值的修正数据;调整进程优先级,在Linux下,就是调整进程nice值
    • UID : 代表执行者的身份

    在这里插入图片描述

    注:

    • 需要强调一点的是,进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进程的优先级变化
    • nice值是进程优先级的修正修正数据

    ✨调整优先级

    Linux具体优先级做法

    • 优先级 = 老的优先级 + nice

    ⚡调整优先级:用top命令更改已存在进程的nice值(频繁操作可能需要sudo

    top
    进入top后按"r" → 输入进程PID → 输入nice值
    
    • 1
    • 2

    我们发现PRI默认是80
    在这里插入图片描述
    修改nice后:老的优先级都是80,也就是每次设置优先值都是在80上加减
    在这里插入图片描述nice其取值范围是-20至19,一共40个级别
    在这里插入图片描述

    为什么nice值处在一个相对较小的范围内呢?

    因为优先级再怎么设置,也只能是一种相对的优先级,不能出现绝对的优先级,否则会出现严重的进程饥饿的问题。

    其他概念:

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

    在这里插入图片描述

    并发的切换
    在这里插入图片描述

    📢写在最后

    • 能看到这里的都是棒棒哒🙌!
    • 想必权限也算是Linux中重要🔥的部分了,如果认真看完以上部分,肯定有所收获。
    • 接下来我还会继续写关于📚《环境变量》等…
    • 💯如有错误可以尽管指出💯
    • 🥇想学吗?我教你啊🥇
    • 🎉🎉觉得博主写的还不错的可以`一键三连撒🎉🎉请添加图片描述
  • 相关阅读:
    【电力系统】基于Matlab实现风电光伏概率潮流计算
    内存与IO访问函数实例
    十大排序算法(C语言)
    【每日一好题】这么经典的题你不能不会:矩阵置零
    Dubbo 可观测性实践之 Metrics 功能解析
    51单片机应用从零开始(二)
    Apache DolphinScheduler版本2.0.5分布式集群的安装
    深度学习复盘与论文复现B
    思科设备配置路由重发布
    ACM练习——第五天
  • 原文地址:https://blog.csdn.net/qq_42996461/article/details/126501930