文章目录
1.冯诺依曼体系结构
我们常见的计算机,如笔记本。我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系。
截至目前,我们所认识的计算机,都是由一个个的硬件组件组成
- 输入单元:包括键盘,鼠标,扫描仪,写板等
- 中央处理器(CPU):含有运算器和控制器等
- 输出单元:显示器,打印机等
再次强调:
程序运行时必须先从磁盘加载到内存,CPU获取写入只能从内存中拿数据,CPU执行我们的代码,访问我们的数据,数据从一个设备“拷贝”到另一个设备
体系结构的效率:由设备的拷贝效率决定
结论: CPU在数据层面只和内存打交道,外设只和内存打交道
存储分级
由于外设的速度比较慢,CPU的速度较快,所以外设和CPU之间就存在木桶原理,内存的出现让CPU和外设的速度更匹配。
冯诺依曼体系结构的存在使得电脑变便宜了。
理解数据流动
两个人如果在微信上面聊天,本质上是两个冯诺依曼体系之间的信息流通,发信息的人通过输入设备“键盘”进行输入。我们发微信首先得打开登陆微信,即把微信的可执行程序加载到内存。所以在键盘里输入根据冯诺依曼体系是把键盘数据搬到内存(硬件层面),软件层面是把数据交给微信 ,所以数据流动就从输入数据流到存储器。微信信息比如说要加密,把数据“你好”(发送的数据)经过运算器控制,运算器运算完毕之后把“你好”转化成了一个乱码的结构,然后再有CPU
写入内存,再由微信把你自己的数据传输到输出设备 ,用户a
的输出设备是网卡,网卡把数据交到网络里,通过网络再把数据交给用户b
。用户b
的输入设备拿到数据,而用户b的输入设备只能是网卡,输入设备拿到数据放到内存里,用户b
也必须启动微信,启动的微信也在内存里,在微信里读到的代码再交给CPU
,将乱码数据解密成“你好”,再通过存储器刷新到输出设备上,这个输出设备就是显示器。
那如果是发送文件呢???
其实文件的本质也是数据,当我们把文件拖到微信程序时,在没拖之前文件是在磁盘上,本质上是把磁盘文件拷贝到微信里面,在经过运算器加密包装回到存储器,在经过网卡发送,对方通过网卡接受,接收后首先要把文件数据读到内存,通过运算器解包解密写回内存,然后把数据写到输出设备。
总结:
- 聊天: 是把用户数据从键盘经过体系结构转发到对方显示器的工作。
- 发送文件: 本质是把文件从本地磁盘经过体系结构拷贝至对方磁盘的过程。
2. 操作系统(Operator System)
2.1 概念
任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)。笼统的理解,操作系统包括:
- 内核(进程管理,内存管理,文件管理,驱动管理)
- 其他程序(例如函数库,shell程序等等)
操作系统本质是一款进行软硬件管理的软件
2.2 设计OS的目的
操作系统上面不仅有硬件,而且还有软件
- 对下,与硬件交互,管理所有的软硬件资源
- 对上,为用户程序(应用程序)提供一个良好的执行环境
- 软硬件体系结构是一个层状的结构
- 访问操作系统,必须用系统调用–其实就是函数,只不过是系统提供的
- 我们的程序,只要判断出访问了硬件,那么它必须贯穿整个软硬件体系结构
- 库可能在底层封装了系统调用
2.3 核心功能
- 在整个计算机软硬件架构中,操作系统的定位是:一款纯正的“搞管理”的软件
2.3.1 如何理解"管理"
- 管理的例子 - 学生(被管理者),辅导员(执行权),校长(决策权)
操作系统就如同校长一样具有决策权
,驱动程序就如同辅导员具有执行权
,底层硬件就如同学生被管理
- 要管理,管理者和被管理者不需要见面
- 管理者和被管理者,根据“数据”进行管理
- 不需要见面如何得到数据呢?由中间层(辅导员)获取
📌:操作系统通过数据对底层硬件进行管理,而数据是通过驱动程序获取的。
总结:
计算机管理硬件
- 描述起来,用struct结构体
- 组织起来,用链表或其他高效的数据结构
先描述,后组织
- 操作系统对硬件的管理:
操作系统在自己的内部先描述,再组织,把网卡,硬盘,显卡,键盘,显示器等,统一可以定义一个类,类里面可以包含每一个硬件对应的属性,属性包括硬件的名称,硬件的状态,硬件相关的信息。所以操作系统管理硬件转变成立对硬件的增删查改。 - 操作系统对进程的管理:
操作系统对进程的管理也类似,操作系统首先要对每个进程建立对应的struct
类对象,然后把进程相关的属性放在结构体里,用链接节点连起来,对进程管理转化成对链表的增删查改。
2.4 系统调用和库函数概念
操作系统不相信任何用户或者人,操作系统要向上提供对应的服务,就好比“银行”
操作系统会把自己封装起来,所以系统调用本质上是操作系统提供给我们的系统调用。我们未来在操作系统获取信息等都是通过系统调用完成的。
系统调用的本质是用户和操作系统之间进行某种数据的交互。
结论:
- 在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口,叫做系统调用。
- 系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有心的开发者可以对部分系统调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行二次开发。
- 库函数和系统调用是上下层的关系
3. 进程
3.1 基本概念与基本操作
- 课本概念:程序的一个执行实例,正在执行的程序等
- 内核观点:担当分配系统资源(CPU时间,内存)的实体。
因为操作系统内同时可能有许多的可执行程序加载到内存里,所以必然要对加载到内存中的程序做管理,答案是先描述(给每个可执行程序定义结构体(PCB)Liunx下叫struct task_struct
进程控制块),与下一个可执行程序的结构体形成链表,这个链表就叫做进程列表
进程 = 内核数据结构对象 + 自己的代码和数据
进程 = PCB(task_strcut)+ 自己的代码和数据
✏️对进程的管理就变成了对链表的增删查改
✏️进程的所有属性都能直接或者间接在task_struct
找到
3.2 描述进程-PCB
基本概念
- 进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。
- 课本上称之为
PCB(process control block)
,Linux操作系统下的PCB是:task_struct
task_struct-PCB的⼀种
- 在Linux中描述进程的结构体叫做
task_struct
。task_struct
是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息。
3.2.1 task_struct
内容分类
- 标识符(PID)
描述本进程的唯一标识符,用来区别其他进程。- 状态
任务状态,退出代码,退出信号等。- 优先级
相对于其他进程的优先级。- 程序计数器(PC)
程序中即将被执行的下一条指令的内存地址。- 内存指针
包括程序代码和进程相关数据的指针,以及与其他进程共享的内存块的指针。- 上下文数据
进程执行时处理器的寄存器中的数据(需补充示意图:CPU 寄存器状态)。- I/O 状态信息
包括显式的 I/O 请求、分配给进程的 I/O 设备和被进程使用的文件列表。- 记账信息
可能包含:处理器时间总和、使用的时钟数总和、时间限制、记账号等。- 其他信息
未明确分类的附加属性。
组织进程
可以在内核源代码里找到它。所有运行在系统里的进程都以task_struct
链表的形式存在内核里。
3.3 查看进程
1.首先我们来自己创建一个进程,我们首先来认识一个进程指令getpid
我们历史上执行的所有指令,工具,自己的程序,运行起来全部都是进程!!
2. 写一个可以查看进程的程序
3.运行程序
4.只要是一个程序我们就可以查看它的进程,但是还不够,我们再打开一个Xshell
。在系统上我们有对应的指令让我们去查看当前的进程有哪些。这个指令就叫做ps
,我们可以查所有的以特定格式显示的进程,比如ps axj
其中的a
表示所有进程的意思。
这展现的就是系统中所有的进程。
5. 我们若只想查看我们自己刚刚的进程呢?ps axj | grep myprocess
Linux中我们也可以两条指令同时输入,用;
隔开,表示先执行前面的,再执行;后面的。出了这种写法我们也可以ps ajx | head -1 && ps ajx | grep myprocess
,效果和上面一模一样
6. 当我们查进程时会发现这个grep
一直存在,为什么呢?我们在查进程的时候grep
也是个命令,当它把显示出来的结果做过滤的时候,grep
命令一旦跑起来,它自己也是个进程,而它自己过滤关键字里本来就包含myprocess,所以它也会自己把自己查出来,我们如果不想看到grep
呢,我们可以ps ajx | head -1 && ps ajx | grep myprocess | grep -v grep
,grep -v
表示反向匹配,包含grep
的不要,不包含的留下来了
7. 只要是个进程,我们就可以杀掉它,如何杀呢?ctrl + c
/kill -9 + pid
,杀掉进程。(-9
表示一个信号编号)
第二种查看进程的方式:
- 进程的信息可以通过
/proc
系统文件夹查看(以文件的形式查看进程)
每一个数字目录代表特定进程的pid
,每个目录包含的内容是这个进程运行时的动态属性
如:要获取PID为3908132的进程信息,你需要查看
/proc/3908132
这个文件夹。
3908132
文件夹中的内容
在重重之中,我们只需要了解两个
大多数进程信息同样可以使用
top
和ps
这些用户级工具来获取
上面中我们还遗留了一个信息getppid
,表示获取父进程Pid
下面我将讲如何不用指令而用代码创建进程,Linux中所有的进程都是被它的父进程创建的,Linux是一个单亲繁殖系统,只有父进程,没有母进程,所以Linux中所有的进程是一个进程树
获取父进程
运行
重新执行多次
我们看到的现象是他们的父进程都是一样的,那父进程是谁呢?
我们查到的父进程是一个bash
,叫做命令行解释器,本质是一个进程,我们每次登陆我们的云服务器,操作系统会给我们每一个用户分配一个bash
,-
表示远程登陆。
它先启动“printf”
打印命令行,然后再“scanf”
我们历史上执行的命令,比如ls
,pwd
,mkdir
都是进程,他们的父进程全都是bash
父进程如何创建子进程的呢?代码创建子进程的方式,
fork
是一个系统调用
代码演示
正常情况下代码刚开始运行都是一个执行流,执行完fork
就会变成两个执行流,这两个执行流都会执行后续代码,这就是为什么进程开始运行
会出现两次,原因是进程 = PCB + 自己的代码和数据,父进程再创建子进程的时候会把它的PCB拷贝一份给子进程(但子进程pid会被修改),所以子进程的代码和数据和父进程的一样,所以会运行两次,子进程没有自己的代码和数据,因为目前没有新的程序加载,所以会共享父进程的代码和数据。让父子进程执行不一样的逻辑
运行结果
fork
的返回值
这里提出几个问题:
- 为什么
fork
给父子返回各自夫人不同返回值?
父:子 = 1:n,因为父进程有多个孩子,一定要把子进程的pid
返回给父进程,父进程才好区分子进程 - 为什么一个函数有两个返回值?
一个函数执行到return
,这个函数的核心功能已经做完了
写时拷贝:
独立性是如何做到的?数据结构独立,代码共享(代码是只读的,不影响),数据以写时拷贝的方式各自私有一份
- 为什么一个变量即==0,又大于0,导致if else同时成立?
返回的本质时写入变量,父和子谁先return谁就先会修改pid_t id
这个变量
👍 如果对你有帮助,欢迎:
- 点赞 ⭐️
- 收藏 📌
- 关注 🔔