【Linux】冯诺依曼体系结构与进程的基础知识点

1.冯诺依曼体系结构

计算器、笔记本、服务器大都遵循冯诺依曼体系结构。
结构如下图所示:
在这里插入图片描述

硬件[2~5]

2.为什么要有内存?

  • 外设:速度相对慢,价格相对较低(输入输出设备都属于外设)
  • 内存:速度相对快,价格相对较高,数据掉电易失
  • CPU:速度最快,价格高

1.那么是否可以不通过存储器,直接使用输入设备将数据传给CPU,再由CPU处理后传给输出设备呢?
——可以。
2.那么为什么不这么做呢?
——因为外设的速度相对于CPU太慢了,整体的效率就会以外设为主,所以引入了内存来缓解效率下降的问题。
3.内存是如何缓解效率下降问题的?
——运行速度:外设<内存<CPU;而内存可以临时存储数据,当CPU处理一项任务A的时候,可以预先将另一项任务B的数据存储在内存中,等待CPU处理完任务A以后,直接从内存读取任务B的数据来执行,即预加载。所以整体的效率就以内存为主,从而提高了运行效率。

总结:
内存是为了适配外设与CPU的速度。
在数据层面:CPU一般不与外设直接沟通,而是直接与内存沟通。

3.为什么不用CPU中的寄存器做存储单元?

虽然速度快,但是价格过高。

4.为什么我们的程序必须先被加载到内存中?

可执行程序是一个文件,存储在磁盘上,而磁盘属于外设。
冯诺依曼体系结构规定:CPU只直接与内存沟通,所以我们的程序需要预先加载到内存中,再传递给CPU计算。

5.在硬件层面数据流是如何流向的?

都遵循冯诺依曼体系结构!
1.单机:
在这里插入图片描述
2.跨主机:(不考虑网络
在这里插入图片描述

总结:
在数据层面,数据要传递给CPU,必须要先从外设到达内存,然后再给CPU。


软件[6~10]

6.操作系统Operator System

操作系统是计算机基本的程序集合,操作系统包括:

  • 内核(进程管理,内存管理,文件管理,驱动管理)
  • 其他程序(例如函数库, shell程序…)

操作系统是软件,而软件需要预先加载到内存中再运行,那么操作系统是什么时候加载到内存中的呢?
——开机加载的时候。

7.操作系统的作用

操作系统是一款进行软硬件管理的软件

它的作用是:

  • 与硬件交互,管理所有的软硬件资源
  • 为用户程序提供一个良好的执行环境

8.操作系统是如何“管理”软硬件的?

——把被管理的对象转换成对某种数据结构的增删查改。

管理的本质:先描述,再组织

  1. 描述:用struct结构体描述硬件信息
  2. 组织:用链表或其他高效的数据结构链接整合描述的信息

9.操作系统为什么要对软硬件资源进行管理?

对下:管理好软硬件资源
对上:为用户提供安全、稳定、高效的执行环境

10.系统调用

在这里插入图片描述
系统调用其实就相当于OS提供的C函数。
用户可以通过调用这些函数来使用操作系统的功能。
我们在使用printf打印数据时,底层其实就调用了系统调用来输出到显示器这个硬件。
库函数其实就是对系统调用的封装(不是所有的库函数都会调用系统调用)。

操作系统提供系统调用接口是为了保证自身的封装性,所以通过接口的形式给用户使用,而不是直接将操作系统暴露给用户。

而系统调用的使用成本较高,所以就有人基于系统调用为其他用户完成了:
图形化界面、shell和工具集、开发工具
又有人基于上述完成的功能来实现了上层应用(应用层)

在这里插入图片描述

进程[11~20]

任何启动并运行程序的行为,都由操作系统帮助我们把程序转换成进程来完成特定的任务。

11.当有多个进程从磁盘加载到内存中时,OS如何管理这些进程?

如果不进行管理,那么无法区分哪部分内存是属于哪个进程的。

那么操作系统如何管理进程呢?
——先描述,再组织:

  1. 描述:当进程加载到内存时,操作系统会在内核中创建一个数据结构对象(pcb / task_struct是Linux下的pcb),pcb提取了进程的大部分属性,其中有一个指针指向自己进程所属的内存空间。
  2. 组织:每个pcb中又存有一个指向下个pcb的指针,将所有pcb链接起来。(Linux中用双链表链接)
    在这里插入图片描述

总结:
对进程的管理,通过PCB就转化成了对链表的增删查改。

进程 = 内核中进程的属性数据结构 + 内存中加载的代码和数据
二者相加才是进程,只有内存中的代码和数据不能算是一个完整的进程。

注意区分:
1.文件 = 内容 + 属性
2.进程 = 内核中进程的属性数据结构 + 内存中加载的代码和数据
这里文件中的属性和内核数据结构中进程的属性不是一个属性,没有关系

12.查看进程常用命令

ps axj查看所有进程
——————————
ps axj | head -1:只查看ps axj输出结果的第一行,也就是查看属性列表
——————————
ps axj | grep 进程名/pid :查看指定的进程
——————————
ps axj | head -1 && ps axj | grep 进程名/pid:查看进程属性名+指定的进程(会多打印一行grep进程,因为grep自己也是进程也会打印出来)
——————————
ps axj | head -1 && ps axj | grep 进程名 | grep -v grep:查看进程属性名+指定的进程(不显示grep进程)

查看进程还有另一个方法:
在根目录下有一个/proc目录,就是保存进程相关属性的目录,proc是内存级的文件目录,只有当操作系统启动时它才会存在。
当相应的进程终止时,proc中相应进程的目录也会消失。
可以直接cd进入proc目录查看进程属性。

13.进程属性

在这里插入图片描述


0.UID

用户标识符,操作系统通过UID来标识区分用户。

1.PID

PID:当前进程的编号
getpid:获取进程的pid,谁运行的时候调用这个函数就获取谁的pid
——头文件:<sys/types.h>、<unistd.h>
——返回值:pid_t getpid(void);其实就是有符号整数

每次调用同一个进程都会给新的pid值,不是一样的。


2.PPID

PPID:父进程的编号
getppid:获取当前进程的父进程的pid
——头文件:<sys/types.h>、<unistd.h>
——返回值:pid_t getpid(void);

bash(命令行解释器)也是一个进程,有独立的pid。
命令行启动的所有程序都会变成进程,该进程的父进程都是bash。
也就是执行我们自己的程序都是通过bash创建子进程来执行的。
我们所使用的指令,比如ls、touch等也是bash的子进程。

3.STAT

当前进程所处的状态。(具体细节下面部分解析)

linux进程状态有:

  • R (running)
  • S (sleeping)
  • D (disk sleep)
  • T (stopped)
  • t (tracing stop)
  • X (dead)
  • Z (zombie)

4.PRI

进程的优先级。

5.NI

进程优先级的修正数据。

14.如何创建子进程

fork:创建子进程
函数:pid_t fork(void);
头文件:#include <unistd.h>

创建子进程,直接调用fork函数即可:
fork();或者pid_t ret = fork();创建并接收返回值。

  1. fork之后,执行流会变成两个
  2. fork之后,谁先运行由调度器决定
  3. fork之后吗,fork后面的代码共享,通常以if和else if来进行执行流分流。

15.fork函数的返回值

返回值:pid_t,也是整形。
给父进程返回子进程pid,给子进程返回0,创建子进程失败返回-1。
可以在调用时创建pid_t类型的变量获取pid_t ret = fork();
注意这个返回值,在父子进程中的值不一样,“看似”有两个返回值

  • 在父子进程种两个ret的值不一样
  • 在父子进程中两个ret的地址一样
    如下例所示:
    在这里插入图片描述

可以通过fork的返回值来进行执行流分流父子进程:

pid_t ret = fork();
if(ret == 0)
{
	//子进程
}
else if(ret > 0)
{
	//父进程
}

16.fork创建子进程的原理

1.fork做了什么

进程 = 内核数据结构 + 进程的代码和数据
fork相当于在内核中创建新的子进程数据结构pcb,其内容与父进程数据结构基本一致,pid和ppid不同。
其进程的代码和数据不会拷贝一份新的,父子进程共享
父子进程pcb与子进程pcb都指向相同的代码和数据。
在这里插入图片描述

2.fork如何看待代码和数据

进程运行时,具有独立性:一个进程终止与否不影响其他进程。
父子进程也一样具有独立性。
但是父子进程共享代码和数据,独立性要如何保证?

1.代码:代码是只读的,不会变化。
2.数据:当一个执行流尝试修改数据的时候,OS会给当前的进程触发一种机制:写时拷贝。所以写的进程写入位置发生变化,不会影响读的进程读取原始数据。

写时拷贝
字面理解,当想要写入修改数据时,将原始数据拷贝一份到另一个地方,修改拷贝的数据而不是原始数据。写时拷贝时原始数据与拷贝的数据地址相同,是因为存在虚拟地址空间(后续解释,c/c++中的所有地址都不是物理地址,是虚拟地址)。

3.fork如何理解两个返回值

fork其实就是创建了一个子进程pcb并进行了初始化。

fork的本质就是OS提供的一个函数;
函数在return的时候,其主体功能就已经完成了;
fork作为一个函数,当返回返回值的时候,其创建子进程并初始化的工作已经完成了,只差return没有进行;
return也是语句,因为子进程已经创建完成了,所以这条return语句会被父子进程分别执行,一共执行两次,就会看似有两个返回值。
当使用变量接收返回值时,会发生写时拷贝pid_t ret = fork;,实际上存在两个ret数据,存在两个物理空间中,但是其虚拟地址空间相同。

17.进程状态

进程状态有:

  • R (running)
  • S (sleeping)
  • D (disk sleep)
  • T (stopped)
  • t (tracing stop)
  • X (dead)
  • Z (zombie)

进程的运行模式:
一个进程并不是一直在CPU中运行的,而是运行一段时间然后再换另一个进程到CPU中运行,让所有的进程代码在同一时间段内都有所推进。而我们作为用户作为人是感受不到进程的切换的,因为切换的速度很快,所以我们看到的就好像是多个进程在同一个时间段内同时运行。

18.阻塞与挂起

  • 阻塞:
    进程因为等待某种条件就绪,而导致的一种不推进的状态。(其实就是进程卡住了)
    阻塞一定是在等待某种资源——比如:
    进程在等待CPU调度,就是在等待占用CPU资源;
    下载时卡住了,就是在进程在等待占用网络资源;

  • 挂起:
    当内存中的空间很紧张的时候,OS会将内存中不被调度的、闲置的一些进程的数据和代码交换到磁盘当中,这部分代码和数据就相当于被释放了,当该进程需要再次被调度的时候,重新将这部分代码和数据交换到内存中,给CPU来运行。将代码和数据暂时由OS交换到磁盘的这段时间,该进程就处于挂起状态。
    在这里插入图片描述

阻塞:进程在等待某种资源就绪的过程。
这里的资源指的是:磁盘、网卡、显卡等各种硬件外设。
那么操作系统是如何管理硬件外设的呢?
——先描述,再组织:通过结构体来描述硬件、然后用链表的形式链接,将对硬件的管理转换成对链表的增删查改。(比如当电脑插入一个键盘的时候,OS就要创建一个键盘对应的数据结构来对其进行管理)
同时OS中也存在着大量的进程,如何管理进程?
——先描述,再组织:每个进程都是tesk_struct定义的一个对象。


阻塞的原理

当CPU调用某个进程时,拿到它对应的tesk_struct数据结构,发现其中有某项资源没有就绪,就让它进入阻塞状态,将其从CPU特定的队列中移动到所需要等待的资源处排队,就是将该进程对应的的tesk_struct链入到所需的外设资源的结构体中等待资源。(在硬件对应的struct中有一个结构体指针可以接收tesk_struct
而当进程在硬件的队列中阻塞时,从硬件获得数据后就会链回到CPU的特定队列来运行。
所以,PCB时可以被维护在不同的队列当中的!
在这里插入图片描述

在这里插入图片描述

19.Linux下的进程状态

tesk_struct是一个结构体,其中包含各种属性,其中就有进程状态。

struct tesk_struct
{
//...
int status;//进程状态
}

linux下的进程状态有:

  • R (running)
  • S (sleeping)
  • D (disk sleep)
  • T (stopped)
  • t (tracing stop)
  • X (dead)
  • Z (zombie)

一个进程被新建,默认是R状态;就绪也相当于是R状态;运行状态也是R状态;阻塞为S状态;终止为X或Z状态。(如下图)
在这里插入图片描述

  1. R状态:运行
    进程处于R状态,不一定在运行,比如有很多进程处于R状态,其中只有几个在CPU上运行。
    在进程运行时要维护一个运行队列,处于R状态的进程结构体都会被链接在上面,CPU就会在其中挑选指定的进程来运行。
    也就是说在CPU上运行的进程+在运行队列中排队的进程都处于R状态。
    这个运行队列不在CPU中,由OS自己维护。
    (队列中不是进程的数据和代码,是进程结构体对象tesk_struct)
    补充:
    但是当循环打印的时候,查看进程的状态会发现处于S(休眠)状态,而不是R状态,这是因为当往显示器打印字符的时候,有可能进程需要等待显示器的资源,所以CPU会将进程放到显示器的结构体中去等待资源,此时处于阻塞状态(S状态也是一种阻塞状态),当获得显示器资源的时候回到CPU变成R状态,但是因为大部分时间都在等待,运行占小部分时间,二者切换速度过快,所以我们查看进程状态的时候很难看到处于R状态。

  2. S状态:可中断休眠
    休眠状态本质就是一种阻塞状态。
    可中断指的是:处于S状态的进程可以被直接中断,或者是在等待获取资源,取得资源后会变成R状态运行。

  3. D状态:不可中断休眠
    当CPU调用某个存储数据时,进程等待磁盘存储,自身处于S状态阻塞,CPU去调用其他程序,在此时,如果内存的资源极其紧张,进程再多OS就要挂了。OS就可能会将这个处于阻塞状态的进程结束(杀进程),而此时,磁盘存储数据如果失败了,会返回进程,但是此时进程已经被OS杀死不在内存中了。
    ———为了防止出现这种问题,就有了D状态,D状态的进程不可以被中断。(甚至OS也不能将其中断)
    当进程进入这种状态,只有等待其自己醒来,或者关机重启(有些情况甚至无法关机),没有别的办法可以将其结束。如果发现出现D状态,那么计算机也基本快要宕机了。如果直接拔电源,那么之前进程在往磁盘写入的数据就会丢失。

  4. T状态:暂停
    也是一种阻塞状态,暂停≠终止
    暂停进程的方法:
    kill -19 pid :暂停信号——暂停编号为pid的进程;
    kill -18 pid :继续信号——让被暂停的信号继续运行;
    在进程运行时,输入kill -19 pid命令会将进程暂停,此时查看进程状态变为T状态。
    注意:
    在进入T状态之前的进程状态的符号后面都会有一个“+”,但是在进入一次T状态然后再解除后会发现进程状态后面没有“+”了,并且按下ctrl+c无法终止进程。
    因为带“+”的进程,表示该进程在前台运行;不带“+”的进程,表示该进程在后台运行;
    此时你可以处理其他的shell指令,比如ls,cd等,但是后台进程会一直自动执行。
    此时输入 kill -9 pid或者killall 进程名称可以结束该进程。
    所以,前台进程可以用ctrl+c或kill -9 pid结束,后台进程只能用kill -9 pid结束。

  5. t状态:追踪暂停
    我们在写程序的时候,加入断点的本质就是让进程暂停!
    此时该进程暂停等待跟踪他的进程进行操作,比如写程序加断点。

  6. X状态:终止
    X时一个瞬时状态,很难查看到,在进程终止的瞬间为X状态。


Z状态:僵尸状态

(重点状态,单独记忆)
1.知识补充:
在写c/c++时,main函数结束会有一个返回值return 0;
return 0是进程的退出码, 这个退出码可以检查程序退出的结果是否正确。

查看进程退出码的方法:
进程运行结束后,命令行输入:echo $?

比如,在ls命令后如果输入例如-asdaslf这种不存在的选项,则用echo $?会显示退出码为2.


2.为什么需要僵尸状态?
如果一个进程退出了,马上进入X状态退出,父进程是否有机会获取退出码呢?
——没有。
那么在linux中当进程退出时,进程一般不会立马退出,而是会维持一个Z状态,便于后续父进程(OS)获取子进程的退出结果。


3.观察僵尸进程的方法
同时运行一个父进程和子进程,此时查看父子进程都为S+状态;
kill子进程,此时查看父进程为S+状态,子进程为Z+状态,并且在子进程的属性列表末尾还多了一个,此时子进程就是僵尸状态,是一个僵尸进程,因为它还没有被回收。


4.僵尸进程会导致内存泄漏问题
处于Z状态的进程称之为僵尸进程,并且僵尸进程的数据没有被完全释放,会占一部分资源,如果我们不回收,OS就要消耗一部分资源来维护僵尸进程的数据结构pcb,这部分内存就会一直被占用,就会造成内存的泄漏。
所以僵尸进程必须要回收。


5.为什么我们退出父进程,查看进程属性时发现父进程不会进入僵尸状态而是会直接退出?
因为父进程是bash的子进程,bash会自动回收父进程的退出结果。


20.孤儿进程

如果父进程退出,而它的子进程没有退出:
此时子进程会被1号进程领养,1号作为新的父进程,1号进程其实就是OS。
对于这种被领养的进程,我们称之为孤儿进程
孤儿进程被OS领养后,会自动变为后台进程(没有+)。

为什么要存在孤儿进程?

因为如果没有孤儿进程这个概念,当后续子进程退出的时候,没有父进程接收其退出结果,子进程就会一直保持僵尸状态,造成内存泄漏。

学习的指令

1.杀进程

kill -9 pid:结束编号为pid的进程
killall [进程名称] :结束指定名称的进程

2.暂停进程

kill -19 pid :暂停信号——暂停编号为pid的进程
kill -18 pid :继续信号——让被暂停的信号继续运行

3.查看进程退出码

查看进程退出码的方法:
进程运行结束后,命令行输入:echo $?

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值