0


【Linux】进程概念 —— 冯诺依曼体系结构 | 操作系统 | 进程

进程概念

1. 冯诺依曼体系结构

说明:

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

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

IO = input + output

2. 操作系统 operator system

2.1 是什么what?

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

2.2 为什么why?

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

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

💛 以学校中的管理类比,操作系统中——

  1. 管理者和被管理者并不会直接打交道(就像我从来没见过校长一样,真没见过,就在百米开外见过书记哈哈) 决策 —— 管理者 eg.校长 执行 —— 执行者 eg.执行者
  2. 如何管理你? 对你做出各种决策,决策依据是你的属性数据
  3. 你的数据如何被校长知道?校长的决策又是如何执行? 通过辅导员。

2.3 怎么管理how?

站在校长角度 ——

  • 如何聚合一个学生的数据?用类/结构体描述
  • 如何将多个学生的聚合数据产生关联?用特定的数据结构来组织,于是对学生的管理工作,变成了对数据结构的增删查改。

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

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

对应到操作系统,它承担着承上启下的角色 ——

3. 进程

系统中存在大量的进程,操作系统是如何进行管理的?先描述再组织

3.1 描述进程 - PCB

为什么要有PCB?因为要管理进程,就要先描述进程。

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

struct PCB
{//进程的所有属性!}

在Linux系统中,PCB 是

task_struct

,相当于媒婆和王婆的关系 ——

struct task_struct
{//进程的所有属性!}

❤️

task_struct

中有什么属性字段

  1. 标示符:描述本进程的唯一标示符,用来区别其他进程。
  2. 状态:任务状态,退出代码,退出信号等。(进程控制、信号详谈)
  3. 优先级:相对于其他进程的优先级,先后问题。
  4. 程序计数器:程序中正在被执行的下一条指令的地址
  5. 内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
  6. 上下文数据:进程执行时处理器的寄存器中与进程强相关的的临时数据。
  7. I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表
  8. 记账信息:可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。进程创建出来,CPU要执行它对应的代码,然而CPU很少,进程很多。因此OS内有一个调度模块,负责较为均衡的调度每一个进程,较为公平的获得CPU资源。
  9. 其他信息

上下文数据,后文马上详谈。

3.2 组织进程

💛 进程 vs 程序

❤️ 结论:曾经所有的程序启动,本质上都是在系统上面创建进程

有了进程控制快,所有的进程管理任务与进程对应的代码和数据毫无关系,与内核创建的该进程的

PCB

强相关。

把进程控制块

PCB

用双向链表组织在一起,于是操作系统对进程的管理,变为对数据的管理,本质上就是对双链表的增删查改。

下面详谈一下进程控制块中的上下文数据 ——

进程的代码是不可能在很短时间运行完的,规定每个进程的时间片(单次运行的最长时间),用户感受到的多个进程同时运行,本质上是CPU的快速切换。CPU只有一套寄存器,为了保护上下文,进程的这些临时数据被写入在PCB中,再来执行时,恢复上下文

4. 查看进程

我写了一段程序

Myproc.c

就是隔1s打印"Always",

./

运行,同时复制SSH渠道再打开一个窗口,便于监视进程。

💛 查看进程

ps axj | grep "proc" 

关闭进程 ——

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

💛 以文件形式查看进程 ——

/proc

是Linux系统下查看进程的目录

ls /proc

进程启动后,会在

/proc

下形成目录,以自身

PID

的编号作为目录文件名 ——

💛 查看该进程属性信息,注意其中的 ——


cwd:这就是为什么文件操作时,不指定路径,会默认在当前目录下创建文件。

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

💛 查看进程PID

💛 创建子进程

执行如下代码 ——

#include<iostream>#include<sys/types.h>#include<unistd.h>intmain(){fork();    
  std::cout <<"hello proc:"<<getpid()<<" hello parent:"<<getppid()<< std::endl;sleep(1);return0;}

发现调用了fork后,打印了两次,并且这两个进程是有父子关系的,且普通进程的父进程基本是

bash

这令人感到奇怪,怎么会打印两次呢?但其实就是有两个进程在执行代码段,我们来详谈。

5.1 如何理解fork创建子进程

目前创建进程主要有两种方式,

./cmd

run command

fork在操作系统角度,和它们没有差别。

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

  1. 默认情况下,子进程会“继承”父进程的代码和数据 ⭐️代码fork之后,产生的子进程和父进程代码是共享的。代码是不可被修改的,这意味着父子代码只有一份完全共享。 ⭐️数据:默认情况下,数据也是“共享的”,不过修改时会发生写时拷贝来维护数据的独立性
  2. 子进程内核的数据结构task_struct,也会以父进程的为模板初始化自身

5.2 返回值

我们把代码稍作修改,打印一下返回值 ——

#include<iostream>#include<sys/types.h>#include<unistd.h>intmain(){    
  pid_t id =fork();    

  std::cout <<"hello proc:"<<getpid()<<" hello parent:"<<getppid()<<"ret:"<< id << std::endl;sleep(1);return0;}

发现一个函数居然有两个返回值 ——

  1. 如何理解一个函数有两个返回值?return时子进程已被创建,return也是语句,父子都会执行。
  2. 我们创建的子进程和父进程干一样的事情吗?这是没有意义的。 一般是通过if-else分流,让父子进程各自执行不同的代码段,而这就是通过fork的返回值来完成的。 ⭐️创建失败:<0 ⭐️创建成功:给父进程返回子进程的PID;给子进程返回0,表示成功创建。
  3. 返回值是数据,return时需要写入。谁先返回,就会发生写时拷贝,可以看到两个返回值的确不同。 注:fork之后,父子谁会先运行?这是不确定的,是由调度器来确定的。

多进程代码,让父子执行不同的事情 ——

#include<iostream>#include<sys/types.h>#include<unistd.h>intmain(){    
  pid_t id =fork();if(id ==0){//child    
   std::cout <<"I am child,pid: "<<getpid()<<", ppid:"<<getppid()<< std::endl;sleep(1);}elseif(id >0){//parent    
   std::cout <<"I am parent,pid: "<<getpid()<<", ppid:"<<getppid()<< std::endl;sleep(1);}else{//TODU    }return0;}

实现了分流 ——

6. 进程状态

进程的状态信息也是在

task_struct(PCB)

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

6.0 进程状态

下面的状态在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.
*/staticconstchar*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)

运行状态不一定在占用CPU哦,只是表示当前进程在运行队列中,随时可以被CPU调度。

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

当完成某种任务是,任务条件不具备,需要进程进行某种等待(S/D)。可以随时接收信号[Ctrl + c]掉

⭐️ 我们把运行状态的

task_struct

从运行队列(run_queue)放到等待队列(wait_queue)中,叫做挂起等待阻塞

⭐️ 把从等待队列放到运行队列中,被CPU调度,叫做唤醒进程

注:千万不要认为,进程只会等待CPU资源。进程可能会因为运行需要,在不同的队列里,所处状态就不同,本质上进程状态就是一种分类。

❤️ D深度睡眠状态(Disk sleep),也叫不可中断睡眠状态(uninterruptible sleep),

进程处于D状态,不可以被杀掉!(很难演示) ,在这个状态的进程通常会等待IO的结束。

❤️ T暂停状态(stopped)

可以通过发送 SIGSTOP 信号给进程来停止进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。

❤️ X死亡状态(dead)

回收进程资源。进程相关的内核数据结构&代码和数据。

❤️ Z僵尸状态(Zombie)

为什么要有僵尸状态?因为需要辨别退出/死亡原因,把进程退出的信息(数据)写入到

task_struct

中,供系统/父进程读取。

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

  1. 运行状态R:写一个死循环,空语句
#include<iostream>#include<sys/types.h>#include<unistd.h>intmain(){while(true);return0;}

  1. 睡眠状态S:循环打印
#include<iostream>#include<sys/types.h>#include<unistd.h>intmain(){while(true){    
    std::cout <<"Always"<< std::endl;}return0;}

可以看到大多数处于睡眠状态,还有少部分在运行状态 ——

这是因为,打印到显示器上,显示器是外设,很慢,IO等待外设就绪是要花时间的。而CPU太快了,挂起运行挂起运行特别快,虽然给人感受一直在运行,实际上相当长的时间都在休眠。

(这也是为什么刚才要看到R状态时,只写了一个空语句,因为这样没有IO,不用等待,排队CPU资源即可)

  1. 暂停状态T

发送信号。

暂停进程 ——


此时,发送信号恢复状态,会发现

S

后面没有+号,[ctrl + C] 也没法终止程序,这是因为你的暂停和继续让进程变成了后台运行。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-brxKkfEG-1647243295338)(C:\Users\13136\AppData\Roaming\Typora\typora-user-images\image-20220311204814783.png)]
那怎么干掉呢?

$ kill -9 3061

即可

💛 前台进程和后台进程的区别 ——

  • 前台进程:./myproc,输入指令无效,[ctrl + c] 可终止进程
  • 后台进程:./myproc &,可以执行指令,[ctrl + c] 不能终止进程,退出进程要用kill

6.1 僵尸进程

写一个监控命令行脚本,语法类似C语言 ——

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

子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程就进入Z状态。

下面一段代码,在50秒内,我把子进程杀掉,父进程不退出休眠啥也不干,此时子进程成为僵尸进程。

#include<iostream>#include<unistd.h>
    
using namespace std;intmain(){    
  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);}return0;}

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z9XAQVOU-1647243295345)(C:\Users\13136\AppData\Roaming\Typora\typora-user-images\image-20220311213037066.png)]

僵尸进程会以终止状态保持在进程表中,等待父进程读取退出状态代码。会造成内存泄漏,如何避免,下下下篇文章详谈。

6.2 孤儿进程

父进程先退出,子进程就称之为“孤儿进程”。把代码做一点点改动 ——

#include<iostream>#include<unistd.h>#include<stdlib.h>
                                                                           
using namespace std;intmain(){                                        
  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(10);exit(1);//终止程序                  }return0;}

孤儿进程被1号进程init领养,资源由init进程回收。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iZWdInY6-1647243295347)(C:\Users\13136\AppData\Roaming\Typora\typora-user-images\image-20220311214706614.png)]

7. 进程优先级

CPU资源分配的先后顺序,就是指进程的优先权(priority)。为什么会有优先级?因为资源太少,本质上是分配资源的一种方式。类似食堂排队抢饭。

7.1 查看优先级

ps -l

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NVG1c09y-1647243295349)(C:\Users\13136\AppData\Roaming\Typora\typora-user-images\image-20220314084521025.png)]

注 ——

  • PID:进程的优先级,值越小优先级越高
  • NI:进程的nice值,优先级的修正数据
  • UID:用户ID。相当于你身份证号一样的东西 在这里插入图片描述

7.2 调整优先级

调整优先级,并不不直接改PRI(你自己知道能设置就得了,不建议自己设置)

     P
    
    
     R
    
    
     I
    
    
     (
    
    
     n
    
    
     e
    
    
     w
    
    
     )
    
    
     =
    
    
     P
    
    
     R
    
    
     I
    
    
     (
    
    
     o
    
    
     l
    
    
     d
    
    
     )
    
    
     +
    
    
     n
    
    
     i
    
    
     c
    
    
     e
    
   
   
     PRI(new) = PRI(old) + nice 
   
  
 PRI(new)=PRI(old)+nice

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

sudo

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

可以看到,PRI通常都是80 ——


nice其取值范围是-20至19,一共40个级别 ——

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

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

其他概念 ——

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

持续更新@边通书

标签: linux 运维 服务器

本文转载自: https://blog.csdn.net/qq_54851255/article/details/123480069
版权归原作者 呀小边同学 所有, 如有侵权,请联系我们删除。

“【Linux】进程概念 &mdash;&mdash; 冯诺依曼体系结构 | 操作系统 | 进程”的评论:

还没有评论