冯诺依曼体系和操作系统和进程

Posted 为世界献上祝福

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了冯诺依曼体系和操作系统和进程相关的知识,希望对你有一定的参考价值。

冯诺依曼体系和操作系统和进程初识

文章目录

冯诺依曼体系

我们常见的计算机,如笔记本。我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系。

输入设备:就是键盘,鼠标,扫描仪,摄像头…

输出深:显示器,打印机,音响…

中央处理器(CPU):内含运算器和控制器!

存储器:这里指的是内存!

内存具有掉电易失的特性,就是断点后就会失去数据!

内存想对的是具有永久性存储能力的设备那就叫外存!一般我们普遍认识的就是磁盘!

对于存储器我们可能会认为是外存 + 内存!但是存储器这个概念在这里是指内存!

外存——外部存储器既是输入设备也是输出设备!磁盘是外部设备的一种!**磁盘不属于存储器!**磁盘和网卡都是典型的即使输入也是输出!

下面我们将对 存储器和CPU这两个部分的功能进行详细的解释!

>> 在整个冯诺依曼体系中个部分的速度也是有快有慢!

可以看出来相比CPU和寄存器,外设的速度其实是很慢的!

CPU比外设的速度快了几百万倍!

接下来我们来简单的看一下cpu功能和内部

就像是我们在读书前还得学会识字,cpu也是一样执行代码前也必须先认识代码!我们学习的识字的过程中,也相当于在大脑里面写入指令集!我们是通过这些指令集来读书的!

cpu的数据来源!

可以将内存看成是CPU和磁盘之间的一个巨大的缓存,用来适配CPU和磁盘之间巨大的速度差!

所以这样子其实就变成了CPU在读取数据和写入数据只和内存(存储器)进行数据交流!!而不会直接和外设进行数据交流!——这样子可以更好的提高整机的效率!

计算机本质是一个硬件和软件的完美结合!

总结

  1. CPU不和外设直接进行数据交流!只和内存进行数据交流!
  2. 所有的外设,有数据要进行载入,只能载入到内存中!内存写出,也一定是写到外设中去!

有了以上的结论我们就可以对平常的一些现象进行解释了!

我们都知道程序运行必须加载到内存!那为什么一定要加载到内存中呢?——因为CPU运行程序需要代码!但是CPU不能直接的去读取磁盘中的数据!CPU只会从内存中拿数据!所以我们运行的时候就一定要把数据加载到内存中!

我们接下来看一下数据是如何通过冯诺依曼体系来在两个设备之间进行传输的!

我与我的朋友在qq上进行聊天,我发了一句“你好!”,请问这一句”你好!“在电脑中是如何流动的!?(这里我们不考虑网络的传输,只考虑在冯诺依曼体系中的数据流通!)

操作系统

操作系统是一个软硬件资源管理软件

为什么要有操作系统?

这就是为什么要存在操作系统!操作系统的存在可以为我们管理好软硬件资源!为我们(上层用户)提供一个良好的执行环境!

什么叫管理?

操作系统是如何对资源进行管理的?这是一个看上去很抽象的概念!

我们可以举一个例子来帮助理解这个抽象的概念!

当我们在上学的时候,我们也是受到管理的一方!而学校最后都是由校长来进行管理的!所以其实我们也是受到校长的管理的!可是其实我们其实很少或者说根本没有跟校长直接的进行接触!校长也不会真的来我们身边详细的询问我们的成绩,指导我们的学习,跟我们说我们该怎么做!

所以其实管理者根本不需要和被管理者直接的进行交互!也是可以管理被管理者的!

我们也要明白和我们直接进行交互的人不一定是我们的管理者!像是我们上学时候的班长!班长是我们的管理者吗?不是!班长是监管我们的人!但是班长不是管理我们的人!班长负责将管理的信息向下进行传递!

管理者必须具备对重大的事宜具有决策权!

所以老师也不是一个管理者!因为老师不具备重大事情的决策权!老师不能随意的想要将我们开除学籍,更换班级!老师和班长都是做执行的人!

那么校长是如何做出决策的?

那校长是如何对我们进行做出决策进行管理的?——答案是数据!**校长根据我们的数据来管理我们!**我们的数据上有我们的学习成绩,有我们的个人信息,有我们的实践经历,获奖经历…有我们的各种信息!像是我们迟到,我们早退,我们作弊这些都会化作数据记录起来!这样子校长就可以通过这些数据来进行管理!进行给学生颁奖!通报学生!劝退学生…各种操作!

所以虽然我们不会直接和校长打交道!但是我们的数据早就被校方拿走了!而且一直在更新

所以管理的本质就是对数据进行管理!

数据哪里来?

如果校长要对数据进行管理!那么么数据是哪里来的?管理者不会与被管理者直接的进行接触!**所以必须存在第三者用来获取数据!那就是执行者!**从老师哪里来的!所以和我们直接进行交互的老师其目的不是为了管理我们而是为了从身上获得必要的数据!且及时的对数据进行更新!初次之外执行者还有执行管理者下达命令的职能

这样子流程就变成了 老师收集学生的数据——学习成绩,课堂表现… 然后老师将数据给校长,校长做决策来决定给学生的待遇!——发奖学金,处分…然后老师在根据下达的命令来执行相应的行为!

例如校长看到一班的成绩不错,但是出勤率很差!于是告诉老师要提高出勤率!于是老师根据这个决策来开始天天到班级点名!提高学生的出勤率!

接下里我们将上面所说的角色和操作系统进行一一的对应!

我们可以认为操作系统就是校长是管理者!硬件就是学生是被管理者!那么他们中间一定存在一个执行者!那就是驱动层!每一个硬件都存在驱动!操作系统就是通过管理驱动来管理硬件的!也是通过驱动来控制硬件的!

但是这还衍生出来一个问题!学生少的时候还好!一旦学生多了数据量大了!我们应该 怎么处理?数据量一旦大起来!数据的管理也是一种巨大的负担!

所以如何更好的去管理数据呢?校长总不能天天盯着这些excel的数据天天吧?恰好校长是一名程序员!校长发现学生信息虽然很多!但是种类都是一样的!可以按种类进行管理,所以校长就通过对学生的信息进行抽象和提炼最后定义了一个结构体!用来专门管理这些信息!

**最后对于学生的管理便转化成了对这一个个的变量进行管理!**但是万一数据量还是太多了!变量管理起来也不方便!要找也很麻烦!校长作为一个优秀的程序员于是他在结构体里面又加了一个指针!

==这样子对于学生信息的管理就变成了对于链表的管理和维护!==对于学生的管理就变成了对于链表的增删查改!

有新的学生!那就添加一个节点!学生被退学了那就是删除一个节点!学生的信息更新了那就对学生对应的节点进行修改!校长想要找到成绩最好的学生!也只要设计一个遍历的算法对整个链表进行遍历就可以找到了!

这就是对被管理的对象进行建模的过程!

管理的两个重要的阶段 ——先描述,在组织!

描述就是将要管理的对象进行抽象变成一个结构体!

组织就是根据结构体设计出来一个特定的数据结构!

这样就将对对象的管理转换为了对某种结构的管理!

==这也是我们操作系统的管理思想!==所有管理的本质逻辑也都是:先描述,在组织!

这也是所有软件的设计思路!

像是我们写一个通讯录!

我们首先就要先构建对应的结构体!——struct person——描述

然后以链表或者数组的形式进行增删查改!——组织

所以我们就可以进一步的去了解操作系统究竟是如何的去管理硬件的了!

其实也是通过描述后组织的方式来管理硬件的!

操作系统就是通过这个链表来管理硬件的!当操作系统遍历链表的时候发现有个设备的status是error 那么就将其从链表移除!

对于软件做管理!

操作系统对于硬件做管理是先描述再组织!那么对于软件呢?自然也是一样的!软件不仅能管理硬件也管理软件!——这也是很好理解的,人不仅可以管理物品,人也能管理人!是一样的!所以接下来我们再举个例子用来说明这一点!也用这个例子来进一步的完善整个计算机体系!

下面这是一个银行!

从上面的例子我们可以显然的看出来!这个银行的内部结构和操作系统也具有相似性!而银行还具有承接用户存钱的业务!

那么问题来了!在传统的银行中是否允许用户直接的操作银行的电脑!直接自己存钱?——答案是否定的!一般我们都是将钱给业务人物让他们来替我们存钱!而柜台的前面往往还有很厚的玻璃那么为什么要怎么做?——答案自然是这样子能保护银行!

**银行系统不相信任何人!而且自己很容易受到伤害!银行必须保护自己!**但是银行也承担着为客户服务的责任! ——所以银行既要保护自己,又要对外提供服务!

而操作系统也是一样!操作系统相信任何人和任何程序!但是也必须给上层的用户提供各种服务!

**为了保护操作系统不允许任意用户对操作系统内做任意的修改!所以操作系统不允许用户直接访问操作系统里面的各种程序!但是又必须提供服务,所以操作系统提供了一个接口!(就像银行不允许用户直接往仓库存钱一样!操作系统也不允许用户直接往磁盘写入数据!) **

**操作系统接口既可以用来满足用户的请求,又可以拒绝用户的非法请求!**这样子就可以最大程度的保护操作系统!

**系统调用接口是由操作系统来提供的!**在linux下因为linux都是有c语言写成的!所以linux的系统调用接口其实都是c式的接口!

系统调用接口是很重要的接口!从编程的角度上来看实际上我们本质都是在调用系统接口!

所以我们以后对于操作系统的任意访问都必须通过系统调用接口来进行访问!

随着银行的发展,银行来的各种各样的人越来越多!有的人还不识字!来了柜台还没法办事!那么怎么呢?答案是银行也有了一个大厅经理!专门来帮组人来办事!就是说来银行的人甚至都不用去柜台了**!只要把需求告诉大厅经理!大厅经理就拿着你的材料和带着你的需求帮你去柜台办事!**

用系统调用接口要求我们用户对于操作系统要有一定的了解!但是这样也太麻烦了!所以在操作系统之上还多了一个软件层!用来解决这个问题!

例如shell/各种各样的库…这些都是软件层甚至界面也是属于软件层!我们只需要输入各种的指令!软件层就会根据这些指令去调用各种各样的系统接口!

当我们开始编程的第一行代码就是“hello world” ,这个代码的执行就是调用c标准库里面的函数接口!然后c标准库再去调用系统接口!最后由操作系统来完成输出到显示器上的!

上面的所有结合起来就是计算机的软硬件体系结构!就是下面的这张图!

总结

  • 管理的本质:对于数据做管理!
  • 管理的方法:先描述,再组织!
  • 我们对于系统的任意访问都必须经过系统接口!
  • 我们的指令和编程的本质也是调用系统接口!

进程初识

基本概念

  • 课本概念:程序的一个执行实例,正在执行的程序等

什么叫做运行起来呢?——答案是加载到内存中

但是其实很多人看到这个定义其实很难理解这到底是什么意思!为什么程序加载道内存里面就变成了进程?把程序换个位置就变成了进程?

或许有人看听过另一句话进程和程序相比就有动态的属性!

那这句话就又要么理解呢?

我们用c/c++/java…各种语言编写出来的程序本质上是什么呢?

程序的本质——就是文件!而文件放在磁盘上!

然后呢如果我们要执行这个软件根据冯诺依曼体系我们可以知道文件要放在内存中

然后CPU就去内存中直接拿到这个文件然后执行文件里面的代码吗?不是的!

因为不止一个程序在内存中跑!一般我们内存中至少有几十个程序在执行!这就衍生出了很多的问题!每一个程序内存该分配多少?在哪里分配内存?程序的代码执行到了哪里?有没有被调度过?CPU应该先执行哪一个程序,后执行哪一个程序?程序执行完毕了又该怎么办?…

这些问题的本质就是操作系统该如何去管理这些加载进内存的程序呢?因为操作系统本身就是用来管理软硬件资源的软件!所以进程的问题本质也属于操作系统的管理问题!

那操作系统肯定要对这些加载进内存的程序进行管理!我们上面说了管理的本质是对数据进行管理!管理的两个步骤就是先描述后组织!——描述就是用结构体进行描述,组织就是用数据结构进行组织!

所以操作系统为了对进程进行描述!就有了一个叫PCB的概念!

task_struct内容

  • 标示符: 描述本进程的唯一标示符,用来区别其他进程。
  • 状态: 任务状态,退出代码,退出信号等。
  • 优先级: 相对于其他进程的优先级。
  • 程序计数器: 程序中即将被执行的下一条指令的地址。
  • 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
  • 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
  • I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
  • 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等
  • 其他信息

这些进程属性在当初的可执行程序里面有吗?——没有!

可执行程序里面只有代码和执行逻辑!其他什么都没有!

每一个可执行程序进入内存里面,操作系统都会自动的生成一个相对应的pcb!

然后这些pcb以链表的方式链接起来!完成再组织!

这样子如果我们要CPU去调度一个优先级最高的进程那么流程是怎么样的呢?

首先操作系统会去遍历这个pcb链表!(不是遍历可执行程序!没有任何的意义!)因为pcb有进程的所有信息!所以操作系统可以找到优先级最高的进程!然后把这个进程对应的代码交到CPU里面!CPU就可以执行这个进程的代码了!

进程退出也是同理——操作系统遍历链表,发现有一个pcb里面的状态被标记为死亡,那么操作系统就将这个pcb从链表删除,同时释放进程在内存中对应的代码和数据那么进程就被退出了!

这样对所谓的对进程进行管理就变成了对进程对应的PCB进行管理!

对进程的管理 ——> 转换为了对链表的增删查改!

这就是PCB存在的意义!所以PCB存在也是一个必然的!

linux内核的源码下的PCB

所以我们终于可以对进程下达一个准确的定义!

进程 = 内核数据结构(task_struct) + 进程对应的磁盘代码!

进程在linux与Windows下的表现!

Windows下的进程

linux下的进程

这就是linux下面的进程(右边)首行有这个进程的各种属性!

PPID 该进程的父进程id

PID 该进程自己的id

PGID 该进程的主id

SID 该进程的会话id

TTY 该进程对应的终端!

STAT 该进程的状态!

UID 用户的id

COMMAND 对应的是哪一个进程

有两个进程一个是我们左边执行的mypro,一个是我们用来过滤的grep!

进程在调度运行的时候!进程就具有了其动态属性!

linux下面的进程查看指令

ps aux / ps axj 命令

可以配合管道与其他指令一起使用!上面的右图便是使用ajx来查看系统进程!

进程有关的系统调用!

我们上面的冯诺依曼体系已经说过了!我们编程的本质就是在调用系统调用接口!然后让系统帮我们完成剩下工作!

我们一般同软件层来使用系统调用!但是我们也可以自己去调用系统调用!

getpid/getppid

(2)意味着就是系统调用接口!

我们可以看出getpid的功能就是返回调用它的进程的id

getppid的功能就是返回调用它的进程的ppid

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
 
    while(1)
                                                                        
        printf("这是一个进程! pid :%d ,ppid:%d\\n",getpid(),getppid());
        sleep(1);
    
    return 0;


接下来我们可以使用这个接口看到一种现象

那就是每次重新将程序加载到内存的时候!该进程的pid都会改变!这是很正常的一个现象!

因为每次加载到内存中,操作系统都要为这个进程重新创建相应的pcb! 重新分配pid!

而且我们也可以发现另一个现象那就是ppid都不会改变!这是因为我们现在所有的子进程都是在bash这个父进程下面运行!所以无论子进程如何改变都不会影响到父进程的!

而且bash这个进程也是shell外壳(父bash)下面的一个子进程!这是因为shell是内核和用户进行交互的唯一窗口如果这个窗口坏了那就无法进行交互了!所以shell外壳都会以子进程的方式运行而不会本身自己去运行!这样子无论子进程如何,都不会影响到shell外壳本身!

如果我们把bash杀掉了!也会shell外壳重新生成一个新的子进程!

我们可以看到此时ppid就发生了改变!

一般来说命令行上启动的进程它的父进程的都是bash!

无论子进程如何错误!顶多是子进程崩溃!父进程不会有任何影响!

我们可以看到我们在进程里面写入一个 1/0最后进程是崩溃了!

但是父进程是没有任何的影响

另一种查看进程的方式

ls /proc/

/proc 是一个系统文件!

我们可以发现其实linux下面的进程也是以文件的形式存在的!

所以我们可以用我们pid 来查找该文件下我们的进程

我们可以从下面的这个文件里面看到这个进程对应的可执行程序!

当我们使用kill -9 杀死该进程的时候!下面的文件就消失了!

接下来我们做个实验?那就是当我们在进程执行的时候删掉这个进程指向的二进制文件!这个进程会怎么样?因为我们上面可以看到这个进程的文件是会指向这个进程的二进制文件的!

我们可以发现进程仍然在执行!虽然进程下面的文件警告我们我们的可执行文件已经被删除了!

从这里我们也可以看出来!一个进程对应的程序加载到内存中之后!理论上来说已经和这个进程对应的可执行程序没有任何关系了!

进程的常见调用——fork

用于创建子进程的系统调用接口!

这是一个函数函数执行前只有一个父进程,函数执行后有一个父进程一个子进程

我们可以看到一个行代码打印了两次!

30047就是子进程30038的父进程!

那fork的实际用途是什么呢?

我们可以看看fork的返回值!

会返回给父进程子进程的pid

会返回给子进程一个0!

这个意思是不是说fork有两个返回值呢?——不是的!但是现在因为牵扯到内存地址空间!所以无法进行解释!但是fork并不是有两个返回值!

我们先来了解fork的用法!

#include<stdio.h>
#include <unistd.h>
int main()

    pid_t id = fork();
    if(id < 0 )
    
        perror("fork");
        return 1;
    
    else if(id == 0)
    
        while(1)
        
            printf("我是子进程 ,pid = %d ,ppid = %d\\n",getpid(),getppid());
            sleep(1);
        
        //child
                                                                                                         
    else
    
        //parent
        while(1)
        
         	printf("我是父进程 ,pid = %d ,ppid = %d\\n",getpid(),getppid());
            sleep(3);
        
    
    return 0;



我们神奇的发现本应该只执行一个的if else语句竟然两个都开始执行了!

这是因为fork之后,会有连哥哥进程在执行代码!

fork后续的代码!会被父子进程共享!

通过返回值的不同来让父子进程执行后续共享代码的一部分!

从上面的代码中我们可以看出操作系统不仅对硬件做管理!

操作系统也对进程做管理!而进程在没有进入内存之前就是磁盘上的一个个的二进制程序

然后我们把它加载到内存中 就要先描述在组织,对应的设计出进程的管理结构(就是PCB)

PCB里面就含有各种信息例如(pid,ppid这些都是进程里面的信息)

而对进程做管理就是对软件做管理!

而无论对硬件还是软件做管理里面的数据是要被用户给拿到的!
);


return 0;


[外链图片转存中...(img-zyKZf7pF-1680415033352)]

**我们神奇的发现本应该只执行一个的if else语句竟然两个都开始执行了!**

**这是因为fork之后,会有连哥哥进程在执行代码!**

**fork后续的代码!会被父子进程共享!**

**通过返回值的不同来让父子进程执行后续共享代码的一部分!**

**从上面的代码中我们可以看出操作系统不仅对硬件做管理!**

**操作系统也对进程做管理!而进程在没有进入内存之前就是磁盘上的一个个的二进制程序**

**然后我们把它加载到内存中 就要先描述在组织,对应的设计出进程的管理结构(就是PCB)**

**PCB里面就含有各种信息例如(pid,ppid这些都是进程里面的信息)**

**而对进程做管理就是对软件做管理!**

**而无论对硬件还是软件做管理里面的数据是要被用户给拿到的!**

初识多线程


前言

我们先简单介绍操作系统来进一步了解多线程

一、操作系统

1.冯诺依曼体系结构

冯诺依曼体系结构是由CPU(运算器,控制器) 存储器 输入设备 和输出设备组成
CPU(运算器,控制器): 算术运算和逻辑判断
存储器:主要功能是存储设备 分为内存和外存 内存一般空间比较小,但是访问速度快,内存空间大,但是访问速度慢
输入输出设备输入设备 键盘鼠标 输出设备 显示器,打印机等

简单描述CPU是如何工作的:

CPU的核心功能就是执行一些指令,指令就是一些二进制的数据,用来表示一些特定的含义,例如我们写的java代码先被编译器编译为字节码之后被JVM翻译为成一条条的CPU指令,最终在CPU上执行,而 C++代码直接把源代码就编译成二进制的机器指令了,Java/C++ 代码在编译好之后就得到了一些二进制的机器指令,这些机器指令是保存在硬盘上的,当我们进行运行的时候,这时候操作系统先把这些指令从磁盘加载到内存中,再由CPU从内存中读取指令进行执行

2.操作系统

操作系统是一个软件,一个管理的软件,管理硬件设备(CPU,内存,磁盘,鼠标等)和软件资源(进程,线程,内核中特殊的资源),操作系统是计算机最重要的软件。

操作系统在其中运筹帷握,才能让这些硬件和软件能够很好的搭配工作
当前主流的操作系统 PC端 Windows Linux Mac 移动端 IOS Android 鸿蒙

二、进程

进程(Process)操作系统中的核心概念,也和平时运行程序密切相关,进程是按照一定的流程(代码中所编写的CPU指令)来具体执行一些计算工作,通俗来说:当运行某个程序的时候,操作系统就创建了一个对应的进程

进程的定义:
1)进程是程序的动态执行
2)进程是一个程序及其数据在处理机上顺序执行时所发生的活动
3)进程是具有独立功能的程序在其数据集合上运行的过程,他是系统调度和资源分配的一个独立单位
我们可以通过任务管理器来查看当前电脑上由多少进程,

为什么引入进程

因为现代操作系统,都需要支持“多任务”“并发” 执行,多任务并发执行能更充分利用多核资源,还有更充分利用CPU资源
1)系统支持多任务
2)更充分利用CPU资源

2.1操作系统管理进程

描述:使用task struct 结构
进程的组成方式:使用双向链表把很多的task struct 变量给串起来(例如,当我们看到任务管理器可以认为,是操作系统内核遍历了双向链表,然后把每个节点的信息获取出来,并展示)
当我们创建一个进程时,实际上就是创建一个task struct 放到双向链表中,当进程结束了就是从双向链表中删除该节点。

2.2进程的组成

一个进程的task struct 里面大概都有啥样的信息
1)pid 进程ID 在任务管理器中也能看到

2)进程的内存指针:描述了进程持有的内存资源是哪些范围(进程依赖的代码和数据在哪里)
下面的信息进程的组成为了辅助进行进程的“调度”
调度 如果在系统某一刻可能有100个进程,我们应该如何执行,
并行从微观角度,每个进程和进程之间,是同时进行的,比如 8个CPU核心,可以同时进行8个进程
并发从微观角度讲,进程是串行执行的,从宏观角度讲,进程是“同时”进行的,
例如一个CPU负责执行3个进程,CPU先执行进程一,之后进行进程二,最后执行进程三,执行依次来回切换,由于CPU切换速度极快,从宏观角度来看好像是三个进程同时执行,但是微观角度依然是串行执行的。
3)进程的优先级多个进程存在,哪个进程要先执行
4)进程的上下文记录一次活动的相关状态(某个进程在CPU上执行一段时间之后,就需要调度下去了,这时进程就需要把上次执行的状态给记录下来)记录的内容:当时CPU的各个寄存器的值,记录到内存中 记录的目的,下次运行的时候,就能够恢复上次的信息,继续向下执行
5)进程的记账信息:统计工具(每个进程在CPU上执行多久了,调度多少次了)
6)进程的状态当前进程的状态,影响调度

进程的调度
所谓的进程调度,其实就是一个抢占式执行的过程,这里的调度工作完全是由操作系统内核来实现,每个进程自身,不知道自己啥时候能获取到CPU的执行,也不知道自己啥时候失去CPU,更不知道下一次获取到CPU是啥时候,一切都是听操作系统的,一切对于应用程序来说都是透明的。

2.3时间片

现代操作系统都支持多任务,就是操作系统可以同时运行多个任务
操作系统的任务调度是采用时间片轮转的抢占式调度方式,也就是说任务执行一小段时间后强制暂停去执行下一个任务,每个任务轮流执行。
由于CPU执行的效率非常高,时间片非常短,在各个任务之间快速切换,给人一种多个任务同时执行的感觉,这就是我们所说的并发

2.4并行和并发

并发:多个进程在一个CPU下采用时间片轮转的方式,在一段时间内,让多个进程都得以推进,称为并发
并行:多个进程在多个CPU下分别,同时进行运行,这称为并行。

2.5内核态和用户态

内核态,操作系统内核来执行任务
用户态,应用程序来执行任务
完整的操作系统=操作系统内核+配套的应用程序
如果某个操作系统进入内核操作,就会变得不可控

2.6进程状态

三种基本状态

就绪已经具备运行条件,但是由于没有空闲CPU,而暂时不能运行
运行占有CPU,正在CPU上运行
堵塞等待某一事件的发生,暂时不能运行

另外两种状态

创建进程正在被创建,操作系统为进程分配资源,初始化PCB
结束进程正在从系统中撤销,操作系统会回收进程拥有的资源,撤销PCB

三、多线程

3.1线程是什么?

进程是为了实现并发编程的效果,但是为了追求效率就引入了线程(创建一个进程/销毁一个进程,开销比较大)因此就希望能够更高效,更轻量的完成并发编程。通过线程来完成,线程也被称为“轻量级进程”
每个线程就对应到一个“独立的执行流”在这个执行流里就能够完成一系列的指令,多个线程,就有了多个执行流,就可以并发的完成多个系列的指令了。

进程和线程的关系:
一个进程包含了多个线程
一个进程其实从系统这里申请了很多的系统资源~~,进程同一对这些资源进行管理,这个进程内的多个线程,共享了这些资源
进程具有独立性:一个进程掉了,不会影响到其他进程
线程则不是这样,如果一个线程坏了,其他的线程可能受到影响。

举个例子来说明线程和进程
如果我们要吃100个西瓜

如果是引入多进程的方式就是

但是当我们创建新的进程时利用系统大量的开销,也不太高效

这样我们就引入多线程,把单线程变为多线程

在这种情况下,同样可以达到并发编程,调度器也是可能会把两个线程安排到不同的CPU上,开销比多进程更低

但是线程越多越好吗? 我们引入100个人吃西瓜,每个人只吃一个,岂不是更快?
所以增加线程可能会提高效率,但是一旦线程数量太多,就会拥挤不堪,这个时候,多个线程为了竞争CPU资源就会占更多的开销(线程多了,调度的开销也变大了)

3.2进程和线程的区别

1)进程时包含线程的~一个进程可以包含一个线程,也可以包含多个线程。
2)进程是资源分配的基本单位,线程是系统调度执行的基本单位
3)进程和进程之间是相互独立的,进程A挂了,不会影响进程B,同一个进程的若干线程之间,共享这一资源(内存资源),如果某个线程出现异常,可能会导致整个进程终止,因此其他线程也就无法工作了。

3.3Java实现多线程

继承Thread 重写run 方法

public class Demo3 {
    public static void main(String[] args) {
        MyThread1 myThread1=new MyThread1();
        myThread1.start();
        while (true){
            System.out.println("这是主线程!!!");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class MyThread1 extends Thread{
    @Override
    public void run() {
    //线程要执行的任意方法
        while(true){
            System.out.println("这是新线程!!!");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

结果:

多线程创建:
1)创建一个类继承Thread
2)重写Thread 的run方法 ,在新的run里面编写线程要执行的执行流,
3)创建子类实例
4)调用子类的start 方法.

3.4通过代码演示多线程提高效率

1.先不使用多线程,直接串行执行

public class Demo4 {
    //演绎多线程来提高程序的运行效率
    //假设把两个long类型的整数,给自增100亿次
    //1.先不使用多线程,直接串行执行
    private static long count=100_0000_0000L;
    public static void serial(){
        long start=System.currentTimeMillis();//毫秒级时间戳
        long a=0;
        for(int i=0;i<count;i++){
            a++;
        }
        long b=0;
        for(int i=0;i<count;i++){
            b++;
        }
        long end=System.currentTimeMillis();
        System.out.println("总执行时间"+(end-start)+"ms");
    }
    public static void main(String[] args) {
        serial();
    }
}

执行时间是10S左右:


2.使用多线程

public class Demo4 {
    //演绎多线程来提高程序的运行效率
    //假设把两个long类型的整数,给自增100亿次
    //1.先不使用多线程,直接串行执行
    private static long count=100_0000_0000L;
    public static void serial(){
        long start=System.currentTimeMillis();//毫秒级时间戳
        long a=0;
        for(long i=0;i<count;i++){
            a++;
        }
        long b=0;
        for(long i=0;i<count;i++){
            b++;
        }
        long end=System.currentTimeMillis();
        System.out.println("总执行时间"+(end-start)+"ms");
    }
    public static void concurrency(){
        //2.并发执行
        long beg=System.currentTimeMillis();
        //创建两个线程
        Thread t1=new Thread(){
            @Override
            public void run() {
                long a=0;
                for(long i=0;i<count;i++){
                    a++;
                }
            }
        };
        t1.start();

        Thread t2=new Thread(){
            @Override
            public void run() {
                long b=0;
                for(long i=0;i<count;i++){
                    b++;
                }
            }
        };
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long end=System.currentTimeMillis();
        System.out.println("执行时间"+(end-beg)+"ms");
    }
    public static void main(String[] args) {
        //serial();
        concurrency();
    }
}


时间缩短了4S,为什么创建两个线程时间不是缩短5S,因为创建出来的两个线程不能保证完全是并行执行的~

以上是关于冯诺依曼体系和操作系统和进程的主要内容,如果未能解决你的问题,请参考以下文章

初识多线程

Linux系统进程概念

Linux进程

Linux进程

Linux进程

Linux进程管理