文章目录
冯诺依曼体系结构
冯诺依曼体系结构
1.输入设备:键盘,磁盘(外设,读取文件(没读的时候就在磁盘)),网卡,显卡,话筒,摄像头(效率是s,ms级别)
2.输出设备:显示器,打印机,磁盘(写文件,就是把数据写到磁盘上),网卡,显卡,音箱等
3.存储器(内存):离cpu越近的设备存储效率越高也越贵,有了内存,cpu就不用直接和外设打交道,存储器的快慢决定快慢(内存是体系结构的核心设备)
4.运算器&&控制器(CPU):运算器(算术运算与逻辑运算)
控制器(读取指令,分析指令,执行指令)(效率ns级别)
如
a和b通过qq发消息
任何的外设,在数据层面,基本都是有限和内存打交道!,
cpu,在数据层面上,也直接和内存打交道
操作系统
启动的(将软件数据与代码,加载到内存中)操作系统,才有意义
是什么
OS 是一款软件,专门针对软硬件资源进行管理工作的软件
(核心工作就是做管理的)
为什么
对下:管理好软硬件资源。对上:给用户提供稳定高效安全的运行环境
方式 目的
怎么办
核心在管理,管理是对人的属性数据进行管理,描述属性结合的就是结构体,里面就有被管理者的全部数据,再加个联系指针连接起来(链表,哈希,树等),对学生的管理就变成了对链表的管理,对学生的褒奖工作就变成了对链表的增删查改
本质(先描述再组织)
管理理念:先描述,再组织,
可以转化成对目标的管理,
转化成对数据的管理
进程(软件)
加载到内存的程序就叫做进程
系统中可能存在大量的进程,操作系统要管理进程
如何管理进程?
先描述,再组织
任何进程在形成的时候,操作系统要为该进程创建PCB,进程控制块( 先描述,struct PCB 里面就是进程的所有属性,结构体描述),
PCB
OS 上面,PCB 进程控制块,就是一个结构体类型
在Linux系统中,PCB ->struct task_struct{ //进程的所有属性 }
类比shell和bash的关系
类比媒婆和王婆的关系
如
我们所有启动的程序的过程都是在系统上面创建进程
把程序运行起来,就是把程序加载到内存,就是把程序由磁盘加载到内存中变成一个进程
进程的属性
程序运行结束进程就结束了
进程vs程序
有了进程控制块,所有进程管理任务与进程对于的程序毫无关系!!!
与进程对应的内核创建的进程的 PCB 强相关
进程=程序+操作系统维护进程的相关数据结构(更多的数据结构)(进程控制块)
PCB的内部构成
- pid:描述本进程的唯一标识符,原来区别其他进程
结束进程
kill -9 pid
- ppid:获得其父的pid(在命令行上运行的命令,基本上其父进程都是-bash)
-
状态:任务状态,退出代码,退出信号等。
0
输出离他最近执行命令的退出码
-
优先级:相对于其他进程的优先级,CPU 只有1个,进程有非常多个,确认先后的问题,
-
程序计数器:程序中即将被执行的下一条指令的地址
-
内存指针:可以通过PCB 找到对应的代码和数据
-
IO状态信息:进程可能要进行的IO,(从输入设备读入)(输出设备读出),进程在进行IO,
-
记账信息:进程被os调度器调度的信息(os为了较为均衡的调用每个进程(获得cpu的资源(进程被执行))),有处理器时间总和,使用的时间限制,记帐号
-
上下文数据:(进程执行时处理器的寄存器中的数据)寄存器(当前正在运行的进程的临时数据)
时间片(单次运行每个进程的最大时间10ms,没跑完,排到最后等待继续运行),在cpu情况下,用户感受到多个进程都在运行(靠cpu快速切换完成的),进程在运行期间是由切换的,进程可能存在大量的临时数据----》暂时在cpu的寄存器中保存,
保护上下文
恢复上下文
虽然寄存器硬件只有一份,但是寄存器里面的数据是你这个进程的,走的时候,把寄存器里面的数据返回pcb里面,进入下一个进程,等待下一次使用,再进入时,再把之前的临时数据放回cpu
通过上下文的保护和恢复,我们可以感受到进程被切换的
查看进程的方案
ls /proc
exe就是当前执行的文件路径
cwd就是当前工作路径
fork
fork就是用来创建子进程的
演示
1 #include <iostream>
2 using namespace std;
3 #include<unistd.h>
4
5 int main()
6 {
7 fork();//创建子进程
8 cout<<"hello proc "<<getpid()<<"hello parent "<<getppid()<<endl;
9 sleep(1);
10 return 0;
11 }
上面的代码我们fork后,只写了一行的打印,但是运行结果是有两条的内容
如何理解fork创建子进程
有我们在命令行里面./cmd or run command(执行指令) ,与fork相比:在操作系统角度上,创建进程的方式都是没有差别的,只不过fork创建的进程间有父子关系
fork本质是创建进程 ----> 系统里面多了一个进程 —>在系统里面多了一份与进程相关的内核数据结构 (task_struct) + 进程的代码和数据(我们只是fork了,创建了子进程,但是子进程对应的代码和数据)---------》
- 默认情况下,会继承父进程的代码和数据
- 内核数据结构task_struct 也会以父进程为模板,初始化子进程的task_struct
例如父亲是做鞋的工厂老板,那么你就会继承父亲的基因(数据结构),同时你也会子承父业继续做鞋(继承代码和数据)
进程具有独立性
- 代码
- fork之后子进程和父进程代码是共享的
- 代码是不可以被修改的
- 父子代码只有一份
- 数据
- 默认情况下“数据也是共享的”,不过因为进程具有独立性,所以也要考虑修改的情况
- 可以通过“写时拷贝”来完成进程数据的独立性
fork的返回值
我们创建的子进程,就是为了和父进程干一样的事情???
一般是没有意义的,我们一般还是希望要让父进程和子进程做不一样的事
我们通常是用fork的返回值来完成
- 失败: <0
- 成功:
- 给父进程返回子进程的pid
- 给子进程返回0
1 #include <iostream>
2 using namespace std;
3 #include<unistd.h>
4 #include <sys/types.h>
5 int main()
6 {
7 pid_t id=fork();//获得其返回值
8 cout<<"hello proc "<<getpid()<< " hello parent "<<getppid()<<" ret: "<< id <<endl;
9 sleep(1);
10 return 0;
11 }
-
如何理解有两个返回值?
如果一行函数已经执行return,那么函数的核心功能执行完了
pid_t fork() { //创建子进程的逻辑 return XXX;//也是语句,父子共享return }
到return的时候,他创建子进程的逻辑也完了,所以子进程已经有了,父进程要return,子进程也要return
-
返回的是数据吗?return的时候也会写入吗?
返回的也是数据,会,发生了写时拷贝
-
-
如何理解两个返回值的设置
父:子=1:n,
子进程的父进程只有1个,而父进程可以有很多的子进程,所以要得到其子进程的pid来控制子进程,而子进程通过ppid就可以找到父进程
对多进程的控制
#include<iostream>
#include<unistd.h>
using namespace std;
int main()
{
pid_t id=fork();
if(id==0)//利用if else,进行分流,实现父子进程完成不同的事
{
//child
while(true)
{
cout<<"I am 子进程 pid="<<getpid()<<" ppid="<<getppid()<<endl;
sleep(1);
}
}
else if(id>0)
{
while(true)
{
cout<<"I am 父进程 pid="<<getpid()<<" ppid="<<getppid()<<endl;
sleep(2);
}
}
else
{
//todo
}
sleep(1);
return 0;
}
fork之后谁先跑呢??
不确定
进程状态
进程的状态信息在哪里呢??
在 task_struct(PCB)
进程状态的意义:
方便OS 快速判断进程,完成特定的功能,比如调度,本质是一种分类
具体的状态
-
R:运行态,不一定正在CPU上面正在运行,但是如果处于运行的队列(等待CPU)中,那么这些状态都可以称为R状态,随时都可以被CPU进行调度
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p20zfQoB-1647504125418)(picture/image-20220310140329017.png)]
-
S/D:睡眠状态,当我们想要完成某种任务的时候,任务条件不具备,需要进程进行某种等待时,就S,D,等待资源就绪
如:今天想打游戏,但是电脑没开,那么我们就要开机等待电脑开机,那么此时我们就是S,D
所谓的进程,在运行的时候可能会因为运行的需要,可以会在不同的队列里,
在不同的队列里面,所处的状态是不一样的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3K13Q8eR-1647504125419)(picture/image-20220310141732027.png)]
如果资源就绪了那么S状态就变成了R状态,放到运行队列里,
如果这个进程突然卡死了,那OS就会把他从运行队列放到等待队列里面去
我们把,从运行状态的task_struct(run_queue),放到等待队列中,就叫做挂起等待(阻塞)
从等待队列,放到运行队列,被CPU调度,就叫做唤醒进程
#include<iostream>
#include<unist.h>
int main()
{
sleep(10);
cout<<"hello "<<endl;
return 0;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0vxSbIJj-1647504125420)(picture/image-20220310144349978.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uAi5lsGD-1647504125420)(picture/image-20220310143310732.png)]
等待我们设定的10s就绪,那么他就处于S状态,
可以直接立即终止(可中断睡眠)
-
深度睡眠 ;不可中断睡眠,
假设进程发出了一个命令,要将1t数据(数据量非常大)都放到磁盘里面,让磁盘去读写,之后磁盘就开始工作了,这个时候进程就开始等待
等待磁盘把任务完成,这个时候,OS来了发现进程在休息,就把他给杀掉了,
磁盘读写完了,要把失败与否的结论告诉进程,但是发现进程不见了,
进程:是OS把我杀掉的
磁盘:是进程不见的
没有办法解决问题
本质上是因为操作系统把正在等待的进程给杀掉的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JH3pVRNg-1647504125421)(picture/image-20220310153558797.png)]
所以为了解决这个问题,就有了深度睡眠的D状态,
进程进入了D状态,不可被杀掉
-
T :暂停状态,完全暂停不会因为某种条件达成变成R,做某些任务的时候,进程需要暂停
-
t(trace stop):经常调试程序的时候,在调试的时候,进程所处的状态,打个断点,就停下来了,临时查看很多资源数据
-
x(dead):死亡状态,回收进程资源=进程相关的数据结构+你的代码和数据(和创建的操作是一样的),死亡状态无法查到
-
z(zombie):僵尸状态,先进入僵尸状态再进入死亡状态
-
为什么要有僵尸状态
为了辨别退出死亡原因,进程退出时要暂时保存退出的信息
在task_struct存了进程退出的所有信息
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VWpdwoVG-1647504125421)(https://raw.githubusercontent.com/zevin02/image/master/%20202203121022560.png)]
状态代码验证
R
:没有IO ,所以就不用等待
1 #include<iostream>
2 using namespace std;
3 int main()
4 {
5 while(true);
6 return 0;
7 }
S
1 #include<iostream>
2 using namespace std;
3 int main()
4 {
5 while(true)
6 {
7 cout<<"hello"<<endl;
8 }
9 return 0;
10 }
~
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RYSD1QuG-1647504125422)(https://raw.githubusercontent.com/zevin02/image/master/%20202203111201188.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w6blxo0h-1647504125423)(https://raw.githubusercontent.com/zevin02/image/master/%20202203121024675.png)]
有可能还能检测到r状态
打印,是往显示器打印,外设慢,IO,实际上等待外设就绪是很慢的,所以大部分状态都是S状态,CPU太快了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NWVrfyha-1647504125423)(https://raw.githubusercontent.com/zevin02/image/master/%20202203121028603.png)]
暂停进程
继续进程
后面没有+号,在后台运行
但是按ctrl c无法结束进程
结束进程
+
:带了+,说明是在前台运行,在命令行输入任何内容都是没有用的,但是ctrl c来干掉进程
后台进程./myproc &,就变成后台进程,可以在命令行中输入命令,无法用ctrl c来结束进程,只能用kill -9 pid来结束进程
z
如果没有人检测回收进程(父进程),该进程退出或进入z
1 #include <iostream>
2 #include<unistd.h>
3 using namespace std;
4 int main()
5 {
6 pid_t id=fork();
7 if(id==0)
8 {
9 //child
10 while(true)
11 {
12 cout<<"I am child,running"<<endl;
13 sleep(2);
14 }
15 }
16 else
17 {
18 //father
19 cout<<"I am father ,doing nothing"<<endl;
20 sleep(50);
21 }
return 0;
22 }
把子进程给终止掉,但是父进程没有回收,就变成了僵尸进程
(子进程先死了,父进程还在运行)
孤儿进程
被1号进程领养,1号进程也叫做操作系统OS
父进程终止了,子进程还在运行
1 #include <iostream>
2 #include<unistd.h>
3 using namespace std;
4 int main()
5 {
6 pid_t id=fork();
7 if(id==0)
8 {
9 //child
10 while(true)
11 {
12 cout<<"I am child,running"<<endl;
13 sleep(2);
14 }
15 }
16 else
17 {
18 //father
19 cout<<"I am father ,doing nothing"<<endl;
20 sleep(10);
exit(-1);
21 }
return 0;
22 }
进程优先级
为什么会有优先级?
本质上是资源太少的问题,优先级本质是获得资源,分配资源的一种方式
查看进程的方案
1 #include<stdio.h>
2 #include<unistd.h>
3 int main()
4 {
5 while(1)
6 {
7 ¦ printf("I am a process ,pid %d,ppid %d\n",getpid(),getppid());
8 ¦ sleep(1);
9 }
10 return 0;
11 }
~
UID 相当于我的名字,不过他是以数字来标识(类似身份证号码)
PRI就是优先级数据
NI优先级的修正数据,
linux中的优先级数据,值越小,优先级越高,
就尽快的享受到某种资源
PRI vs NI
加入nice值,PRI(new)=PRI(old)+nice
所以nice为正,优先级变满,
反之亦然
特别不建议自己修改优先级
优先级设置
nice值(-20~19)
old不会因为之前的改变而改变,还是一开始的值
nice值为什么是一个相对小的范围
优先级再怎么设置,也只能是一种相对的优先级,不能出现绝对的优先级,避免有的进程出现饥饿问题,而我们的调度器
就是为了较为均衡的让每个进程享受到CPU资源,