一、冯诺依曼体系结构
我们常见的计算机如:笔记本,不常见的计算机如:服务器,大部分都遵循冯诺依曼体系结构
目前我们熟知的计算机都是由一个一个的硬件组件组成的
如下:
**输入单元:**包括键盘、鼠标、扫描仪、写板等等
**中央处理器(CPU):**含有运算器和控制器
**输出单元:**显示器打印机等
冯诺依曼体系结构的特点就是:
从输入到输出,数据都必须经过存储器(存储器指的是内存)
那么为啥要设计内存这个东西呢?
我们发现,我们的输入输出设备是很多的,但是运算器只有一个,那么我们的计算机对数据进行处理的时候,是有先后顺序的,那么CPU在处理数据的时候,其他待运行的指令就要等待,那么就导致我们计算机的运行效率很低。CPU的利用率很低。
CPU的特点:运算的速度快,内存小,而且价格高
硬盘的特点:空间大、运算运行速度慢,价格便宜
然后内存的话,其运算效率较CPU低一点,但是其空间很多,而且价格相对也没有这么高,所以内存,可以平衡二者之间的不足。
总结如下:
不考虑缓存,这里的CPU能且只对内存进行读写,不能访问外设(输入或输出设备)
外设(输入或输出设备)要输入或者输出数据,也只能写入内存或者从内存中读取
所以所有设备只能和内存打交道。

二、操作系统
1、概念
任何计算机都包含一个基本的程序集合,其称为操作系统。
操作系统包括:
内核(进程管理、内存管理、文件管理、驱动管理)
其他程序:函数库,shell程序等
简单来说操作系统是一个进行管理计算机软件硬件资源的,比如管理底层的驱动程序的硬件设备。

那么为啥要操作系统来进行管理呢?我们要操作那个硬件,其无法自己完成么?
这是因为大部分硬件我们用户是无法直接去进行操作的,所以通过操作系统来帮助我们对硬件进行使用。
操作系统可以给我们提供一个更加接近人类的操作逻辑,使我们使用计算机的时候,得到一个稳定
可靠、方便、安全的操作环境。
2、进一步理解操作系统
操作系统就好比我们学校的辅导员这样,我们的学校就是我们的硬件,那么学生就是用户,那么我们要使用学校的各个服务,那么就需要通过辅导员呀,宿管阿姨的帮助呀.
还有,好比如银行,其有前台、窗口、钱库等硬件。
然后还有大堂经理、业务员等
那么我们第一次去,存款,那么我们对于这个硬件的操作肯定不熟悉,那么就需要工作人员的帮忙。
然后我们取钱,不可能直接进入到钱库拿吧,也需要经过业务员的操作。
所以我们的操作系统其给了我们用户一个"系统调用接口",允许我们通过接口间接和计算机的软件硬件交互。
3、系统调用
操作系统对外会提供一些接口,给上层开发进行使用,这部分由操作系统提供的接口就称为系统调用
然后操作系统提供的系统调用接口,其对于用户的要求其实挺高的,所以后续有的开发者,对于部分系统调用的接口进行了封装,然后这些被封装的系统调用形成一个一个的库。
那么我们的用户要如何去使用系统调用呢?
我们日常使用电脑的时候,其实并没有感觉到这些,我们只需要在鼠标键盘上操作即可。
我们这个操作就是对操作系统发起操作,然后操作系统根据我们的操作,然后去进行系统调用的转换。
三、进程理解
课本的概念:程序的一个执行实例,正在执行的程序等
内核观点:担当分配系统资源(CPU实体,内存的实体)
下面我们来进行讲解:
我们先来了解一下操作系统是如何进行管理的:
还是前面下学校例子:
我们很多同学见过校长,可能就是新生开学典礼和毕业典礼上会见到,所以其对于学生的管理如下:
首先找到对应的部门,然后由这个部分作为执行者去找学生,然后完成对应的工作。
就比如校长需要开除这个学年绩点不及格的最后五个学生,那么不需要去当面找学生,只需要在学生的管理系统中找绩点最低的五个学生,然后和辅导员说,让辅导员去执行这个开除的操作即可。
所以其角色如下:
操作系统->校长
底层驱动->辅导员
底层硬件->学生
操作系统管理的不是每个硬件的具体数据,而是其属性。
就比如我们的校长,其管理的是我们在学校是各种属性,成绩呀,奖学金评选呀,保研等。
我们的操作系统是可以同时运行很多程序的,那么每一个程序都需要加载到内存中吧,那么我们的内存中就一定会存在很多的进程,那么这些进程也需要被操作系统进行管理。
就好比我们的学校,我们要对学生进行管理,那么我们可以将学生的一些数据使用一个学生管理系统进行记录保存管理,那么我们要先对学生这个进行描述,然后将学生的信息进行组织。
所以我们的操作系统对于进程,先描述一个进程
在每个执行任务被加载到内存中的时候,会提前被操作系统建一个结构体:struct来存储这些属性。
描述进程:PCB
进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合
在Linux操作系统下PCB叫:task_struct。
所以总的来说进程就是:
进程=内核数据结构PCB+自己的程序的代码和数据
所以操作系统对于进程的管理就变成了对于PCB的管理。

PCB中一般就存储以下的信息:
**标示符:**描述本进程的唯一标示符,用来区别其他进程
**状态:**任务状态、退出代码、退出信号等等
优先级:相对于其他进程的优先级
程序计数器:程序中即将被执行的下一条指令的地址
内存指针:包括程序代码和进程相关数据的指针,还有其他进程共享的内存块指针
上下文数据:进程执行的时候处理器寄存器中的数据
I/O状态信息:包括显示的I/O请求,分配给进程I/O设备和被进程使用的文件列表
记账信息:可能包括处理器时间总和,使用的时钟总数和、时间限制、记帐号等
四、查看进程
首先我们要注意的是,我们要查看一个进程,那么我们要先保证其在我们的内存中运行才行。
指令:
ps命令
ps查看进程是属于静态查看,其有如下选项:
a :显示所有用户的进程
u:显示用户信息
x:显示没有控制终端的进程
-e:显示所有进程
-f:显示完整的进程的信息
top命令
按q退出
P表示按照CPU的使用率进行排序
M表示按照内存的使用率排序
基本使用如下:
ps -f |head -1;ps aux| grew -w "具体的程序的名字"
ps -p PID
1、标识符
标识符就类似我们的身份证,学号等,可以表示我们的身份的东西,其表示了每一个进程,在task_struct结构体中存放了两个成员变量:PID和PPID
PID:进程的唯一标识
PPID: 进程的父进程的PID
我们可以通过头文件<sys/types.h>中的两个库函数来查看父子进程的PID
getpid();
getppid();
如下:



2、fork函数
fork函数是一个系统调用,其作用是在当前的进程下创建一个子进程,其需要包含头文件<unistd.h>
这个函数的返回值为一个pid_t变量,pid_t实际上是一个整型变量,如果pid_t==0,那么就说明进程创建成功
下面我们来看看fork函数是如何进行进程的创建的。
其是由当前正在运行的进程调用,这个正在运行的进程就是后面创建的进程的父进程。
然后子进程PCB结构成员的参数基本都是按照父进程,然后生成唯一PID的标识符。
然后子进程和父进程共享代码,数据则是由子进程在访问的时候直接可以访问父进程,然后子进程要对数据进行修改的话,那么其会在内存中申请一块和父进程一样的空间,然后对其数据进行修改。


我们看上面代码的运行结果,我们发现其printf在一个if语句里竟然被执行了两次,这就是我们上面说到的,子进程被创建后会拷贝父进程的代码,然后其会在内存中申请自己的一块空间,然后进程创建成功对于父进程的fork返回值是大于0的,然后对于fork的话就是为0。
fork函数的具体规则:
返回值类型是pid_t,实际上是一个整型类型,一般为int和long,用来表示ID
fork调用一次,会有两次返回,一次父进程一次子进程,然后还有一个情况就是进程创建失败返回0
其在父进程中返回的是子进程的PID,所以大于0的部分执行的是实际上是父进程中的代码
等于0是子进程中的返回。
-1就表示创建失败
然后并不是父进程先执行,这个要看谁的优先级高,执行的先后由操作系统来决定。
为啥要父进程返回子进程的PID呢?
这是因为一个父进程,其可以有多个子进程,那么就要有标识符来让父进程来区分多个子进程。