目标:
-
冯诺依曼系统
-
系统的概念
-
进程的概念
1 . 冯诺依曼体系结构
冯诺依曼提出了一个计算机组成模型
a. 存储器:内存,并不是磁盘
b. 输入设备:键盘,摄像头,磁盘(从磁盘中读取数据到内存中)
c. 输出设备:显示器,磁盘等
d: CPU 由运算器和控制器组成,运算器负责
算术运算和逻辑运算,控制器用来响应外部事件。
为什么要存在存储器,直接接cpu不好吗?
因为cpu的运行速度远大于内存或者磁盘的运行速度,如果没有存储器,那么cpu每次读取数据很可能都要等待,会大大拖慢cpu的速率,但是如果有存储器的话,可以用软件提前加载数据到内存中,能大大提高速率。
要知道的是,内存的价格远高于硬盘,冯诺依曼模型的牛逼之处就在于用了较少的内存而达到了较高的效率。
站在数据的角度,我们认为cpu只从内存中读取数据,而外设必须先将外设中的数据加载到内存中才能被cpu读取。
冯诺依曼模型规定了底层硬件的设计模式,同时也规定了数据在计算机中的走向。
比如说 yyj准备把他的工作结果文档发给他的领导,那么文件首先从磁盘中被读入内存,进入cpu处理后返回内存,再发送到网卡进而输出。
任何数据在计算机中的流程大致都是这个样子。
2 . 操作系统
操作系统是用来帮助用户管理硬件的,要理解操作系统的管理,就得区分决策和执行。
我们每时每刻都在做决策或者执行,所谓决策是指对面对的情况做出的决定,而执行则是对决策的行动。操作系统的概念,可以用学校和银行的例子来理解
e.g 1学校
小明高高兴兴地进入了大学,但有一点令他郁闷的是他只在开学典礼上见过一次校长,其余时候连屁都闻不到。每天都是辅导员在追着他问为什么不上课,为什么不写作业 ....
那么请问在上述一个只有校长-辅导员-学生的模型中,谁是管理者,谁是被管理者?
答案是校长是管理者,学生是被管理者? 诶,那么辅导员是个啥东西? 先别急,听我细细道来。校长的职责是管理一个学校的学生对不对? 那么校长连学生面都见不了怎么管理呢? 这个我们应该都知道,校长是通过学生的各项数据进行管理的,这个管理也就是决策,比如说某个学生的成绩太差了那么校长可能就会做出开除的决策。欸嘿,也就是说,管理本质上是对数据的管理,并不需要管理者和被管理者直接沟通。那么管理者也就是校长是如何获得数据的呢?这里就用到我们的辅导员了,辅导员的工作就是将学生的各项数据向上传给校长,并且执行校长的决策,那么辅导员也就是我们的执行者了。这个概念和我们的操作系统层次图是对应的上的。
这里我想讲的就是,操作系统的管理实质上是对数据的管理,并不需要其和硬件面对面沟通,驱动程序就是那个辅导员
还是拿学校的例子来说,如果一个学校的学生太多,各种各样的信息铺天盖地奔向校长,校长被折磨得想跑路了,这个时候呢,一个长得英俊潇洒且头发旺盛的程序员给他想了个办法:咱定义一个学生结构体,把学生的各项信息存储在内,然后用它定义一个学生数组不就成了吗?这不就是老师讲的数据结构嘛,如果校长想要开除成绩最差的那个学生,那么就可以对这个数组按照成绩进行排序,找到最差的那个学生删除学籍就ok了嘛,这不就是我们学的算法吗?
//样例 struct stu { //各种各样的信息 }; stu students[n]//定义具有n个学生的数组用来管理这里就是我想说的第二个点:任何数据的管理最终都要转化成数据结构和算法。即先描述,再组织。
下面呢,通过一个银行的例子进一步理解操作系统。
e.g2. 银行
银行是用来干啥的?先不谈其他啥理财业务,最基本的业务是不是存钱和取钱? 好,那么我们在哪里取钱呢?是不是柜台,那么柜台是不是封得严严实实的,生怕你闯进去,是不是呢?那么为什么柜台要这么搞呢?
因为银行它怕我们乱搞,它不相信任何人,如果遇上了 “懂哥”,那可能就造成不小的损失了,而我们的操作系统也是一样的:操作系统不相信任何人,你想访问操作系统其实是在访问操作系统提供的接口
第二点是:银行是要为所有人提供服务的,不管客人理不理解银行的工作原理,不管他是对银行工作原理有着深刻理解的精英大佬抑或是对此一窍不通的王大妈,银行都要提供服务。所以银行在大堂设立了一个辅助人员,用来帮助用户进行服务,通俗点说就是:你把资料一股脑给我,剩下啥都不用管了。同样的,由于操作系统的理解成本过高,为了方便使用,人们在操作系统之上加上了一层 "服务层"。
所以其实平时我们使用的编程语言实际上是在调用系统接口,只是换了一种方式而已。
3 . 进程
3.1 进程概念
进程这个概念对我来讲其实有点既陌生又熟悉,为啥呢? 因为我以前用老式台式机打游戏老卡,所以老得打开任务管理器关掉游戏再重进,而在任务管理器中,有个这玩意
这不就是进程嘛,那这玩意和Linux下的进程是一个东西吗?
本质上是的,这都是一个进程,前面说过想打开文件是要加载到内存中的,到内存中这实际上就是一个进程。
那么进程是如何管理的呢?
在前面讲过,任何数据都是先描述再组织,进程也不例外。
以一个简单的例子说明:当我们打开一个文件时,这个文件会被读入到内存中,到了内存,内存是如何进行管理的呢?
在内存中,有一个PCB(process control block)用来管理进程,文件载入内存后就分为了两部分进行管理,第一部分是文件的内容,而第二部分是一个结构体,结构体中存储了文件所有的属性,并且指向文件内容,如图示
每一个指令都是一个进程,都有一个结构体指向它,内容 + 结构体 = PCB
上面的struct_task存放着内容的所有属性,许多的结构体连接起来形成链表即可进行管理。
3.2 进程的查看
进程的查看有多种方式
-
通过系统文件夹proc查看,例如如果你想查看pid为1的进程就执行/proc/1命令
-
-
通过top命令查看,使用top可以获得进程列表
-
-
通过ps命令进行查看
ps 命令的基本格式如下:
[root@localhost ~]# ps aux #查看系统中所有的进程,使用 BS 操作系统格式 [root@localhost ~]# ps -le #查看系统中所有的进程,使用 Linux 标准命令格式
选项:
-
a:显示一个终端的所有进程,除会话引线外;
-
u:显示进程的归属用户及内存的使用情况;
-
x:显示没有控制终端的进程;
-
-l:长格式显示更加详细的信息;
-
-e:显示所有进程;
-
-
3.3 进程初步测试
getpid: 获取当前进程编号
getppid: 获取父进程编号
fork: 创建子进程。
fork的基本用法:
-
fork之后,代码是父子共享的
-
fork有俩返回值,如果创建子进程成功则会返回给父进程子进程的id,而返回给子进程0。
注意这两个特性,fork后有两个进程,对于这两个进程来讲,它们都能够看见接下来的代码,相当于现在有两个人同时执行一份代码,以下面这份代码为例。
#include <stdio.h>
#include <unistd.h>
int main()
{
pid_t id = fork();
//返回-1代表创建失败,直接返回
if (id < 0)
{
perror("fork");//这里的perror是打印fork出错的原因
return 1;
}
else if (id == 0)
{
//这里执行子进程
while(1)
{
printf("这里是子进程 pid: %d, ppid: %d\n", getpid(), getppid());
}
}
else
{
while (1)
{
printf("这里是父进程 pid: %d, ppid: %d\n", getpid(), getppid());
}
}
printf("这是进程的测试\n");
sleep(2);
//printf("I am parent process %d\n", getpid());
//int ret = fork();
return 0;
}
结果:
注意到了吗,这份代码的结果是:走完一次代码,父进程执行一次代码,子进程执行一次代码,所以最后既会输出父进程,也会输出子进程,
Tip:
-
为什么要给父进程返回子进程的pid,而给子进程返回0?
日常生活中每个父亲肯定都会给孩子取小名,没见过哪个孩子给父亲取小名的把。所以呢这里肯定要告知父进程孩子的名字,而只需要返回给孩子0就好。
-
操作系统和cpu运行某一个进程,本质上是从task_struct中挑选一个task_struct运行其代码。只要想到进程,优先要想到进程对应的tast_struct.