冯诺依玛体系结构

输入设备 包括键盘、鼠标、话筒、摄像头等。
输出设备 包括显示器、磁盘(外存)、网卡、打印机等。
输入输出设备都是外设。
CPU = 运算器 + 控制器
存储器简单来讲即使内存。
为了理解冯诺依曼体系结构,我们还需要先理解一下两个问题:
- 软件运行,是否必须先加载到内存?程序运行之前在哪?为什么?
CPU执行代码访问数据,磁盘 -> 文件。CPU只能从内存中进行获取写入 !体系结构规定。数据流转的本质是从一个设备到另一个设备,整个体系结构的效率高低由设备的拷贝效率的高低决定的。
CPU在数据层面只和内存打交道。外设只和内存打交道! - 数据流动是什么?
聊天从硬件上讲是用户文件通过体系结构发送到对方内存中的操作。当两台计算机通信时本质上即使两个冯诺依曼体系结构在通信。
操作系统
概念
任何计算机操作系统都包含一个基本的程序集合,称为操作系统OS。
笼统的理解,一个操作系统包括:
1.内核(进程管理,内存管理,文件管理,驱动管理)
2.其他程序(例如函数库,shell程序等等)

上图展现的就是一个操作系统。
我们平常使用的安卓等操作系统就是在kernel内核中再加上外壳shell、glibc、原生库、预装系统级软件组成的。
设计OS的目的
对上,为用户程序(应用程序)提供一个良好的执行环境
对下,与硬件交互,管理所有的软硬件资源

其中,硬件部分就是我们上面说的冯诺依曼体系结构。
操作系统管理所有软硬件不是目的,是手段。
我们为什么要使用操作系统?
- 软硬件体系结构是层状结构的,也就是高内聚低耦合的结构,需要有人能够统一管理
- 访问操作系统,必须使用系统调用 ------ 函数只不过是系统提供的接口。
printf/cout的本质:是你把你的数据写到了像显示器这样的硬件 - 我们的程序,只要你判断它访问了硬件,那么它就必须贯穿整个软硬件体系结构
- 库函数就是在底层封装了系统调用形成的
理解操作系统
核心功能:在整个计算机软硬件架构中,操作系统的定位是:一款纯正的"搞管理"的软件
那我们应该如何理解管理呢?
首先我们需要描述被管理对象:例如在学校,学生就是辅导员管理的对象,辅导员是校长主任管理的对象
其次组织被管理对象:我们将上面被管理的对象组织起来,一个完整的管理体系就出来了。管理者负责决策,而被管理者负责执行。
- 要管理,管理者和被管理者,可以不需要见面
- 管理者和被管理者,怎么管理呢?根据"数据"进行管理!
- 不需要见面,如何得到数据?由中间层获取!
操作系统在这其中就是起到了管理者的作用。
这个世界的特点就是先描述再组织。在后续的文章中,笔者会经常使用这个原理。
理解操作系统调用:
操作系统要向上提供对应服务
操作系统不相信任何用户或人
===> 系统调用 --> Linux/Windows/macos --> C语言 --> C函数
通过这样一系列的操作,最终实现了系统调用。
库函数和系统调用属于上下层的关系。
进程

cpp
struct XXX
{
代码地址
数据地址
id
优先级
状态
......
struct XXX* next
};
在所有操作系统中,都存在这个XXX,它叫做PCB也可以称为进程控制块。进程的所有属性,都可以直接或者间接通过进程控制块找到 。这样一来,我们就可以将对进程的管理变成对链表的增删查改。
进程 = 内核数据结构对象 + 自己的代码和数据
进程 = PCB(task_struct) + 自己的代码和数据
有了这个看法,我们再看上面那张图就可以更好的理解了。
我们历史上执行的所有的命令、工具、自己的程序,运行起来,全部都是进程。
getpid:系统调用,获取进程ID
ctrl+c:杀掉进程
kill -9 进程ID: 杀掉对应的进程
proc目录:可以通过文件的方式查看进程,proc目录里记录的是当前系统所有进程的信息,进程退出文件自动移除

补充两个小知识:
cwd:进程会记录下来自己的当前路径
exe:进程对应的可执行文件
getppid:获得父进程ID
Linux中所有进程都是被他的父进程创建的 ,多叉树结构父进程都是bash
命令行解释器本质上就是一个由bash创建的进程。
重点:OS会给每一个登录用户分配一个bash
代码创建子进程
fork:创建子进程
创建出的子进程没有自己的代码和数据 ,因为目前没有程序新加载。子进程默认共享父进程的代码和数据(创建子进程之后的代码)
我们先来看一段代码:
cpp
int id = fork();
if(id < 0)
{
perror("fork");
return 1;
}
else if(id == 0)
{
// child
while(1)
{
sleep(1);
printf("我是一个子进程,我的pid: %d,我的父进程id: %d\n", getpid(), getppid());
}
}
else if(id == 0)
{
// father
while(1)
{
sleep(1);
printf("我是一个父进程,我的pid: %d,我的父进程id: %d\n", getpid(), getppid());
}
}
有些读者可能会疑惑?这个程序可以运行吗?答案是可以,而且还会打出父子进程的pid。
下面笔者将通过解答几个问题来解答这个疑惑:
-
为什么fork给父子返回各自不同的返回值?
父:子 = 1 : n,一个父进程可能有多个子进程,一个子进程只能有一个父进程。父进程会获得子进程的pid用以管理 -
为什么一个函数会返回两次?
fork函数本质上是系统调用

-
为什么一个变量,即
= 0同时又> 0?导致if else居然同时成立?父子进程共享return,都会有一个返回值
结论:进程具有独立性!
把父子任何一方,进行修改数据,OS把被修改的数据再底层拷贝一份,让目标进程修改整个拷贝。这个操作叫做写时拷贝
进程状态 / 僵尸进程
进程状态决定在系统层面应该如何处理当前进程。
进程状态就是task_struct内的一个整数。

运行状态是在CPU中的进行的。
一个CPU,一个调度队列
调度算法之一:FIFO,先进先出
进程状态的变化,表现之一就是要在不同的队列中进行流动。本质上都是数据结构的增删查改。
运行状态 :只要进程在调度队列中 ,进度的运行状态都是running
阻塞状态 :等待某种设备或资源就绪(键盘,显示器,网卡,磁盘......)OS要管理系统中的各种硬件资源,先描述再组织。
挂起状态 :将不会被调度的进程(代码和数据)转移到磁盘上 。从运行状态转移到磁盘和从阻塞状态转移分别称为运行挂起和阻塞挂起
cpp
struct device
{
int id;
int vender;
int status;
void *data;
struct device *next;//调度队列
int type;
struct task_struct *wait queue;//等待队列
};
理解内核链表:
cpp
struct list_head
{
struct list_head *next *prev;
};
struct XXX
{
int x, y, z;
list_head links;
};

内核中next只指向目标结构体内部的next (可以实现查询其他结构体中的成员)
一个结构体内构建多个next/prev对象即可实现多个数据结构
Linux的进程状态:
cpp
static const char *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 */
};
X死亡状态 Z僵尸状态
我们创建子进程的目的,是为了让子进程完成某种事情的!结果相关的信息,需要让父进程知道。代码和数据可以清空,但PCB需要先保留,PCB保留但代码和数据清空 的这个期间叫做Z僵尸状态。
- 哪些信息需要保留?退出的信息,信号,数字
- 信息保存在哪里?task_struct
如果父进程一直不管,不回收,不获取子进程的退出信息,那Z会一直存在,造成内存泄漏问题。
tips:
进程退出了,内存泄漏问题就不存在了。
什么样的进程具有内存泄漏问题是比较麻烦的?常驻内存的进程
孤儿进程
父子进程关系中,如果父进程先退出,子进程要被 1号进程 领养,这个被领养的进程(子进程),叫做孤儿进程。自动变成后台进程,不能用ctrl+c停止,需要用kill -9杀死
为什么要领养?
如果不领养,子进程会进入僵尸进程,造成内存泄漏。
进程优先级
进程优先级是什么?
进程得到CPU资源的先后顺序
为什么要有进程优先级?
目标资源稀缺,导致要通过优先级确认谁先谁后的问题!
怎么处理进程优先级?
进程优先级也是一种数字,int类型,位于task_struct中
数字值越低,优先级越高,反之,优先级越低
基于时间片的时分操作系统,考虑公平性,优先级可能变化,但是变化幅度不能太大
小知识:
系统怎么知道我访问文件的时候,是拥有者,所属组还other?
Linux系统中,访问任何资源,都是进程访问,进程就代表用户

UID:user id
PRI:进程的优先级,默认:80
NI:进程优先级的修正数据,nice值,默认值 = 0
进程真实的优先级 = PRI(默认)+ NI
调整优先级方法:nice,renice等
优先级的极值问题:nice值的最小值是-20 最大值是19(Linux进程的优先级范围 [60, 99])
优先级设立不合理,会导致优先级低的进程,长时间得不到CPU资源,进而导致进程饥饿
优先级 VS 权限
优先级:确认能得到资源,先后的问题
权限:是否能得到资源的问题
补充概念:竞争、独立、并行、并发:
竞争性 :系统进程数目众多,二CPU资源只有少量,所以进程之间是具有竞争属性的。为了高效完成任务,更合理的竞争相关资源,便有了优先级。
独立性 :多进程运行,需要独享各种资源,多进程运行期间互不干扰。
并行 :多个进程在多个CPU下分别,同行进行运行,这称之为并行。
并发:多个进程在一个CPU下采用进程切换的方式,在一段时间内,让多个进程都得以推进,称之为并发。