冯·诺依曼结构,又称为普林斯顿结构,是一种将程序指令存储器和数据存储器合并在一起的存储器结构。程序指令存储地址和数据存储地址指向同一个存储器的不同物理位置,因此程序指令和数据的宽度相同,比如英特尔公司的8086中央处理器的程序指令和数据都是16位宽。
数学家冯·诺依曼提出了计算机制造的三个基本原则,即采用二进制逻辑、程序存储执行以及计算机由五个部分组成(运算器、控制器、存储器、输入设备、输出设备),这套理论被称为冯·诺依曼体系结构。
我们常见的计算机,如笔记本。我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系
截至目前,我们所认识的计算机,都是有一个个的硬件组件组成
输入单元:包括键盘, 鼠标,扫描仪, 写板等
中央处理器(CPU):含有运算器和控制器等
输出单元:显示器,打印机等
输入设备和输出设备处理数据的速度相较于CPU是很慢的,如果数据直接从输入输出设备中发送或者接收就会形成一个很大的速度差,这样就会使得整个计算机运行起来的效率还是很低。
因为有IO设备和CPU的效率不均衡,因此引进了存储器(也就是内存),快于IO设备又慢于CPU。
冯诺依曼体系中数据的输入输出就不是直接传到CPU。数据由输入设备先传递到内存,然后CPU再从内存中读取数据进行处理,处理完CPU再写入内存,最后由内存传给输出设备。
总结:
不考虑缓存情况,这里的CPU能且只能对内存进行读写,不能访问外设(输入或输出设备)
外设(输入或输出设备)要输入或者输出数据,也只能写入内存或者从内存中读取。 一切设备都与内存打交道
操作系统是一个进行软硬件资源管理的软件
底层硬件是以冯诺依曼结构组织的
操作系统和底层硬件之间还有个驱动程序,那么这个驱动程序呢就是让操作系统通过调用不同的驱动程序去管理不同的硬件
操作系统包括:
内核(进程管理,内存管理,文件管理,驱动管理)其他程序(例如函数库,shell 程序等)
定位:在整个计算机软硬件架构中,操作系统的定位是一款纯正的 "搞管理" 的软件。
管理的目的:为用户程序提供一个良好稳定的运行环境
与硬件交互,管理所有的软硬件资源。
那么什么叫做管理?如何理解?
管理的本质就是对数据或者说信息进行管理
而管理者(OS)的工作是做决策,根据数据做决策
那么数据量大时管理者该怎么做管理呢?
可以将事物抽象成为一个对象,这个对象中保存着一个事物的信息(属性),也就是抽取事物的属性来描述这个事物
比如说学生:可以抽取姓名、性别、年龄、学号等
看着是不是很眼熟,这就是先描述,用一个数据类型(struct)以及学生的属性来描述学生
然后通过某种数据结构(容器)对大量的学生(数据/对象)进行管理,链表就很合适(可以通过学号或成绩等来做管理)
决策者(OS)所有的决策工作就变成了对链表的增删查改
对学生(数据)的管理也变成了对链表的增删查改
这也就是再组织的过程
总结:
先描述:将事物抽离出属性,通过大量属性定义出对象(通过结构体或类定义出对象(变量))
再组织:通过容器(数据结构)对大量的对象进行管理
在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口,叫做系统调用。
系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有心的开发者可以对部分系统调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行二次开发。
进程是一个运行起来的程序,在具体点呢?
我们先想想下面几个问题
啥是程序呢?程序在哪呢?
程序在电脑中不就是二进制文件嘛,既然是文件那它就存在磁盘里
就是磁盘里的那个.exe(可执行程序)文件,将这玩意加载到内存里就是进程了(暂时先这么理解)
可以同时运行多个程序吗?
可以同时运行,一定要将多个.exe(可执行程序)加载到内存
操作系统要不要管理多个加载到内存的程序呢?
要
操作系统如何管理多个加载到内存的程序呢?
先描述,再组织
先描述:
进程加载到内存里,操作系统咋知道谁是谁啊
所以对进程的属性进行抽离,根据属性创建出对象来描述每个进程"长啥样"
由此就产生了processes control block(PCB)也叫进程控制块
那么为什么程序加载到内存,变成进程之后我们要给每个进程创建一个PCB对象呢?
因为操作系统要对进程进行管理
PCB中有一个属性(指针)指向对应的进程,那么对进程的管理变成对PCB对象的管理
然后PCB里面又有指向下个PCB对象的指针,那么就变成对链表的增删查改(再组织)
所以进程的本质是
内核数据结构 + 可执行程序 = 进程
未来,所有对进程的控制和操作只和进程的PCB有关,和进程的可执行程序无关
Linux操作系统下PCB是task_struct
task_struct描述内容分类
标示符: 描述本进程的唯一标示符,用来区别其他进程。
状态: 任务状态,退出代码,退出信号等。
优先级: 相对于其他进程的优先级。
程序计数器: 程序中即将被执行的下一条指令的地址。
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等
进程标示符: 描述本进程的唯一标示符,用来区别其他进程
也就是进程的PID,PID是操作系统中唯一标识的进程号
通过系统调用获取标识符
#include
#include
#include
#include
int main()
{
printf("before fork: I am a prcess, pid: %d, ppid: %d\n", getpid(), getppid());
printf("创建进程\n");
sleep(5);
pid_t id = fork();
if(id<0)
{
return 0;
}
else if(id == 0)
{
//子进程
while(1)
{
printf("after fork, 我是子进程: I am a prcess, pid: %d, ppid: %d,returnid:%d\n", getpid(), getppid(), id);
sleep(1);
}
}
else
{
while(1)
{
printf("after fork,i am a fatherprcess,pid:%d,return id:%d\n",getpid(),getppid(),id);
sleep(1); }
return 0;
}
可以在内核源代码里找到它。所有运行在系统里的进程都以task_struct链表的形式存在内核里
进程的信息可以通过 /proc 系统文件夹查看
1.通过ps aux 可以查看系统中所有进程的信息
fork成功返回子进程的pid给父进程,返回0给子进程
失败返回-1给父进程,子进程不会创建
#include
#include
#include
int main()
{
int id = fork();
if(id < 0)
{
perror("fork");
return 1;
}
else if(id == 0)
{
//child 可以执行你所需要的子进程内容
printf("I am child : %d!, ret: %d\n", getpid(), id);
}
else
{ //father 执行所需要的父进程内容
printf("I am father : %d!, ret: %d\n", getpid(), id);
}
sleep(1);
return 0;
}
父子非同一个进程,可以同时跑(两个执行流)
进程 = 内核数据结构 + 可执行程序的代码和数据
子进程又没有自身的代码和数据,子进程会指向父进程的代码和数据
子进程被创建是以父进程为模板的
为什么给父进程返回子进程的pid,而给子进程返回0?
我们要明白,一个父亲可以有多个孩子,而一个孩子只能有一个父亲
父亲:孩子 = 1:n 的关系 当然 孩子也可以有一个
但重要的是父进程是唯一的,子进程可以很容易的找到父进程,而父进程需要通过子进程的pid去找到子进程
为什么fork函数会返回两次?
还记得我们上面写的代码吗?
if(id < 0)
{
//...
}
else if(if == 0)
{
//...
}
else
{
//...
}
id如果是同一个变量怎么可能既大于0又等于0
一个进程挂掉是不会影响另一个进程的
(任意)进程之间是具有独立性的,互相不能影响
前面我们提到子进程没有自身的代码和数据,子进程会指向父进程的代码和数据。那么当子进程要改变(写入)变量时会出现写时拷贝,可以用同一个变量名来表示不同的内存
在return子进程之前已经有父子进程分流了
返回的本质就是写入