冯诺依曼体系结构

冯诺依曼体系结构:
输入设备,输出设备,存储器,中央处理器(CPU)
存储器指的是内存
CPU包括运算器和控制器
CPU在数据层面,不会和外设直接打交道,只会和内存进行交互
任何程序在运行时,都必须先从磁盘中加载到内存
因为代码和数据必须被CPU访问,但是CPU指挥和内存进行交互
这是体系结构决定的
数据流动eg.

为什么要有内存?
内存时外设和CPU之间的一个巨大的缓存
操作系统(OS)
任何计算机系统都包含一个基本的程序集合,称为操作系统
操作系统包括:内核,其他程序
OS的本质时一种进行软硬件资源管理的软件
设计OS的目的
每一种硬件都要有自己对应的驱动程序
与硬件交互,管理所有的软硬件资源
为用户程序提供一个良好的运行环境
管理是先描述再组织





进程
什么是进程
进程是程序的执行实例,是正在执行的程序
进程是担当分配系统资源的实体
进程 = 内核数据结构(task_struct) + 程序的代码和数据

在调度进程时,会让task_struct进去排队
进程会被根据task_struct属性,被os调度器调度运行
task_struct内部有哪些属性:

标示符
ps ajx | head -1 && ps ajx | grep 执行文件名可以查看运行中程序的属性
将程序运行起来(./或双击)的本质,就是启动一个进程:
执行完就退出(ls, pwd指令)
一直不退,直到用户退出(常驻进程)
pid
怎么显示进程的标示符(PID)
同一个进程再不同时间启动,其PID是不一样的,因为PID是累加的
PID是为了区别进程
在进程中使用 getpid 就可以获取pid


在系统中可以使用ctrl c来终止运行中的进程
也可以使用kill,kill可以向指定的进程发送信号
kill -9 pid编号 可以杀掉一个进程
在linux系统中可以使用ps来查看文件的属性
根目录下的**/proc**中的每一个目录即代表一个进程
目录的名称就是pid
目录中存储的是该进程的属性
当启动进程时,系统会紧接着在/proc中新建一个相应的文件夹
ps的本质就是打开/proc
cwd:current work dir(当前工作目录)
当前路径就是进程的cwd
当一个进程实际启动的时候,该进程会记住当前的路径(cwd)
启动进程时,会在当前路径生成文件,这个路径就是文件的cwd
chdir可以改变进程启动时的路径
这样就可以让其他的文件在别的路径生成

ppid
ppid是父进程id
在Linux系统中启动后,新建任何进程时,都是自己的父进程创建的
使用getppid()可以获取父进程id

命令行中,执行命令/程序,本质是bash进程创建的子进程,由子进程执行我们的代码
子进程
fork()函数可以创建子进程,创建成功会给父进程返回子进程的pid



可以看到这里打印了两个分支printf
因为经过fork分支后,会执行两个分支
fork()函数由两个返回值
因为fork后变成了两个进程
这两个进程是父子关系
一般而言,这两个进程的代码是共享的(子进程的代码是从父进程继承来的)
但是数据是各自私有一份
fork()返回的id对于父进程是subpid
对于子进程是0
为什么父子进程的数据各自私有?
进程具有很强的独立性,多个进程之间,运行时,互不影响,即便是父子
一个进程在运行时,代码是只读的
但是数据不允许两个进程互相干扰,因此要保证数据各自私有一份

返回的本质就是对指定的变量写入数据
而打印的数据就是读取数据
创建多进程

创建子进程时,子进程的task_struct是拷贝自父进程的
然后再调整新的task_struct部分属性
task_struct连入到进程列表中
此时子进程已经创建了

父进程执行一次return,子进程执行一次ruturn
所以fork()会存在两个返回值
fork后,谁先运行是由OS的调度器来自主决定的
状态
进程的状态

CPU执行代码,不是把进程执行完毕才开始执行下一个
而是给每一个进程预分配一个时间片
基于时间片进行调度轮转(单CPU下)
这种行为是并发
多个进程再多个CPU下同时运行
这种行为是并行

Linux/windows民用级别的操作系统
分时操作系统,调度任务追求公平
实时操作系统,从开始到结束,必须尽快处理,且一但运行就尽量跑完
一个进程位于运行队列当中,该进程就叫做运行状态
本质是准备好了,随时可以运行
数据没有准备好,导致进程无法运行,则是阻塞状态
运行和阻塞的本质是让不同的进程处在不同的对列中
当内存严重不足时,系统会将进程挂起到外设
在磁盘中单独设有一个swap分区用于挂起
挂起的本质是用时间换空间
Linux进程的状态

R(running):运行状态
S(sleeping):休眠状态,阻塞等待的状态,可中断睡眠,浅睡眠
D(disk sleep):磁盘休眠状态,阻塞等待状态,不可中断睡眠,深度睡眠
该进程在睡眠期间,不可被杀掉
T(stopped):停止状态
t(tracing stop):追踪停止
进入停止状态的原因:
1.进程做了非法但是不致命的操作,被OS停止了
2.当进程被追踪断点停下,进程状态是t
使用 kill -19 可以让进程停止
使用 kill -18 可以结束停止状态
X(dead):终止状态
进程创建出来,是为了完成用户的任务
通过进程执行的结果来告知父进程/操作系统,任务是否完成
Z(zombie):僵尸状态
维持退出信息,方便父进程和操作系统来进行查询
进程退出时
1.代码不会执行了,首先可以立即释放的就是对应的程序信息结构
2.进程退出,要有退出信息(进程的退出码),保存在自己的task_struct内部
3.管理结构task_struct必须被OS维护起来,方便用户未来获取进程的退出信息
进程创建时,先创建task_struct,再有代码和数据
进程退出时,先释放代码和数据,再删除task_struct,还没有删除task_struct时,进程时僵尸状态
僵尸进程如果没有人管,则会一直僵尸,僵尸进程默认没人管

如果一个进程僵尸,父进程不进行回收,就是内存泄漏
语言层面的内存泄漏问问题,如果在常驻内存的进程中出现,影响比较大
父进程还在,子进程退出,则正常
父进程退出,子进程还在,子进程会被系统领养
这种子进程是孤儿进程

进程优先级
优先级是获得某种资源的先后顺序
排队的本质就是确认优先级
为什么要有优先级:
因为目标资源较少
优先级就是task_struct中维护的int类型
优先级数字越小,代表优先级越高
优先级受PRI和NI影响
PRI(priority):Linux中的进程的优先级
NI(nice):优先级的nice数据(-20 <= nice <= 19)
最终优先级 = pri(默认/老的) + nice
uid是用户id,决定了进程是谁启动的

为什么nice存在范围?
为了保证进程调度的尽量公平
为什么是[-20,19]?
真实进程的优先级范围是[60,99]
相同优先级的进程会被挂接在一起
修改优先级不是高频操作,而且不建议修改,可以使用指令或代码进行修改
进程队列
CPU里面维护了有两张相同数据的哈希表
active(活跃)和expired(过期)

CPU调度只会从active队列中选择进程进行调度
通常哈希表中纵向根据优先级排队,横向根据到来的先后顺序排队


为了防止进程饥饿问题
创建的新进程和时间片结束的进程会被放到expired对列中
只有当active对列为空,才会将expired中的进程放入active对列中(直接将两个对列的指针交换)
同时又维护了两个bit_map[5](位图)来决定对应为止是否存在进程
这是LInux内核O(1)调度算法

所有进程都要链表链接
但是linux中的链表结构与一般结构不同
只有连接字段,没有属性字段


为什么要设计成这样?
一个进程,既可以在全局链表中,又可以在不脱离全局链表的同时,加入别的链表

原理:
同一个结构体中定义的对象,其地址时相邻的,并逐渐递增
这样只要知道其中一个对象的地址,并知道该对象的偏移量,就可以知道整个元素的地址了

进程切换
每个进程都有一个时间片,时间片到了,就要切换进程
Linux是基于时间片进行调度轮转的
一个进程在时间片结束时,不一定跑完,可以在任何地方被重新调度切换
CPU
eip(pc):当前正在执行指令的下一条指令的地址
ir:指令寄存器,就是正在执行的指令
CPU内部的寄存器的数据,是进程执行时的瞬时状态信息数据
CPU内有很多个寄存器,整体称为一套寄存器,寄存器不等于寄存器中的数据
进程切换的核心是进程上下文数据的保存和恢复
其本质就是
切走:将相关寄存器的内容,保存起来
切回:将历史保存的寄存器的数据,恢复到寄存器中
每次切换时,每次保存完上下文的时候,CPU都是全新的
一套寄存器是被多个进程共享使用的
进程的上下文寄存器数据,被保存到当前进程的PCB中就可以了
进程运行就是重复"取指令,更新pc,分析执行指令"的过程
