1. 冯诺依曼体系布局
外设:
- 输入装备:鼠标,键盘,摄像头,话筒,磁盘,网卡
- 输出装备:体现器,播放器硬件,磁盘,网卡
有的装备是纯的输入,输出,也有即使输入,又是输出装备
cpu/中央处理器:
- 运算器:对我们的数据进行盘算任务(算数运算,逻辑运算)
- 控制器:对我们的盘算硬件流程进行一定的控制
输入装备、输出装备、运算器、控制器、内存都是独立的个体。各个硬件单位必须用“线”链接起来(“线”即总线,包罗体系总线和I0总线)
盘算机存储金字塔:
2. 操纵体系
2.1 概念
任何盘算机体系都包罗一个基本的程序聚集,称为操纵体系(OS)。笼统的理解,操纵体系包罗:
- 内核(历程管理,内存管理,文件管理,驱动管理)
- 其他程序(比方函数库,shell程序等等)
2.2 设计OS的目标
- 与硬件交互,管理所有的软硬件资源
- 为用户程序(应用程序)提供一个良好的实行环境
2.3 定位
在整个盘算机软硬件架构中,操纵体系的定位是:一款纯正的“搞管理”的软件
2.4 如何进行 “管理”
2.5 操纵体系管理硬件
- 形貌起来(指形貌底层硬件),用struct布局体
- 组织起来(将形貌出来的底层硬件组织起来),用链表或其他高效的数据布局
2.6 体系调用和库函数概念
- 在开辟角度,操纵体系对外会体现为一个整体,但是会暴露自己的部分接口,供上层开辟使用,这部分由操纵体系提供的接口,叫做体系调用。
- 体系调用在使用上,功能比较基础,对用户的要求相对也比较高,以是,有心的开辟者可以对部分体系调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开辟者进行二次开辟
3. 历程
3.1 基本概念
- 课本概念:程序的一个实行实例,正在实行的程序等
- 内核观点:担当分配体系资源(CPU时间,内存)的实体。
- 历程:内核PCB数据布局对象+你自己的代码和数据
3.2 PCB
- 历程信息被放在一个叫做历程控制块的数据布局中,可以理解为历程属性的聚集。
- 课本上称之为PCB(process control block),Linux操纵体系下的PCB是:** task_struct**
- PCB是一个struct布局体
3.3 task_ struct内容分类
- 标示符: 形貌本历程的唯一标示符,用来区别其他历程。
- 状态: 任务状态,退出代码,退出信号等。
- 优先级: 相对于其他历程的优先级。
- 程序计数器: 程序中即将被实行的下一条指令的地址。
- 内存指针: 包罗程序代码和历程相关数据的指针,还有和其他历程共享的内存块的指针
- 上下文数据: 历程实行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
- I/O状态信息: 包罗体现的I/O请求,分配给历程的I/O装备和被历程使用的文件列表。
- 记账信息: 大概包罗处理器时间总和,使用的时钟数总和,时间限制,记账号等。
- 其他信息
3.4 操纵体系对历程的管理
- 一个操纵体系,不仅仅只能运行一个历程,可以同运行多个历程
- 操纵体系必须的将历程管理起来! 如何管理历程呢?-------->先形貌,在组织
- 先形貌:任何一个历程,在加载到内存的时候,形成真正的历程时,操纵体系都要先创建形貌历程属性的布局体对象— PCB、process ctrlblock、历程控制块
- 后组织:在操纵体系中,对历程进行管理,变成了对单链表进行增删改查!
3.5 组织历程
可以在内核源代码里找到它。所有运行在体系里的历程都以task_struct链表的情势存在内核里
3.6 检察历程
- 历程的信息可以通过 /proc 体系文件夹检察
如:要获取PID为1的历程信息,你需要检察 /proc/1 这个文件夹
- 大多数历程信息同样可以使用top和ps这些用户级工具来获取
竣事历程:
- # kill -9 XXX // XXX:进程对应的PID
复制代码 3.7 通过体系调用获取历程标示符
- #include <stdio.h>
- #include <sys/types.h>
- #include <unistd.h>
- int main()
- {
- printf("pid: %d\n", getpid());
- printf("ppid: %d\n", getppid());
- return 0;
- }
复制代码 3.8通过体系调用创建历程-fork初识
fork:通过体系调用函数,让父历程创建子历程
fork函数返回值:
如果乐成,给父历程返回子历程的PID,0返回给子历程,此时返回值有两个
如果失败,给父历程返回-1,不返回给子历程
fork 之后通常要用 if 进行分流:
- #include <stdio.h>
- #include <sys/types.h>
- #include <unistd.h>
- int main()
- {
- printf("begin: 我是一个进程,pid: %d!, ppid: %d\n", getpid(), getppid());
-
- int ret = fork();
- if(ret < 0)
- {
- perror("fork");
- return 1;
- }
- else if(ret == 0) //child
- {
- printf("我是子进程,pid: %d!, ppid: %d\n", getpid(), getppid());
- }
- else //father
- {
- printf("我是父进程 : %d!, ret: %d\n", getpid(), getppid());
- }
- sleep(1);
- return 0;
- }
复制代码 代码实行结果:
- 为什么fork要给子历程返回0,给父历程返回子历程pid?
返回不同的返回值,是为了区分让不同的实行流,实行不同的代码块! 一样平常而言fork代码及fork之后的代码父子共享。给父历程返回子历程pid是为了让父历程区别子历程。
为了让父和子实行不同的事情!------需要想办法让父和子实行不同的代码块
让fork具有了不同的返回值!-------区别父子历程
- 如果父子历程被创建好,fork0,往后,谁先运行呢??
谁先运行,由调治器决定,不确定的
因为fork代码及fork之后的代码父子共享,以是 fork 函数被父子历程实行了两次,以是有两个返回值。而且ret是父历程的数据,子历程无法修改,当子历程调用函数后,再赋值给 ret 时,发生了深拷贝,新空间也有一个变量,叫做 ret,以是是同一变量名,不同空间。
4. 历程状态
- /*
- * The task state array is a strange "bitmap" of
- * reasons to sleep. Thus "running" is zero, and
- * you can test for combinations of others with
- * simple bit tests.
- */
- 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 */
- };
复制代码
- R运行状态(running): 并不意味着历程一定在运行中,它表明历程要么是在运行中要么在运行队列里
- S就寝状态(阻塞状态): 意味着历程在期待变乱完成(这里的就寝有时候也叫做可中断就寝
(interruptible sleep))。
- 挂起状态:或简称“挂起态”,是一个表示历程被“冻结”或“停滞”的特殊状态。在此状态下,历程不会在主存中活跃,而是被转移到辅助存储器(如硬盘)中。这意味着历程在此状态下不会得到CPU的实行时间,并从活跃队列中移除
- D磁盘休眠状态(Disk sleep)有时候也叫不可中断就寝状态(uninterruptible sleep),在这个状态的历程通常会期待IO的竣事。
休眠状态就是阻塞状态,我们将这种休眠状态称之为阻塞状态,于此对应有深度休眠状态,浅度就寝的历程的会对外部的信号做出响应(比如可以直接ctrl c被终止掉)。
而深度就寝(disk sleep)状态也是阻塞状态,这种状态的程序是针对磁盘设计的,这种状态的 历程,操纵体系是不能杀掉的。原因如下:
操纵体系在没有空间的时候,会通过杀掉历程节流资源,但当一个历程正在向磁盘中写入关键数据 时,如果杀掉该历程,那么写入磁盘就会失败导致数据丢失,为了应对这种环境设置了深度就寝的状态,操纵体系遇到这种状态的历程,就不会去杀掉他。直到历程向磁盘中写完关键数据后,深度就寝才竣事
- T停止状态(stopped): 可以通过发送 SIGSTOP 信号给历程来停止(T)历程。这个被停息的历程可以通过发送 SIGCONT 信号让历程继承运行。
- X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态
4.1 Z(zombie)-僵尸历程
- 僵死状态(Zombies)是一个比较特殊的状态。当历程退出而且父历程(使用wait()体系调用,反面讲)没有读取到子历程退出的返回代码时就会产生僵死(尸)历程
- 僵死历程会以终止状态保持在历程表中,而且会一直在期待父历程读取退出状态代码。
- 以是,只要子历程退出,父历程还在运行,但父历程没有读取子历程状态,子历程进入Z状态,历程的相关资源尤其是task struct 布局体不能被释放!
4.2 僵尸历程的危害
- 历程的退出状态必须被维持下去,因为他要告诉关心它的历程(父历程),你交给我的任务,我办的怎么样了。可父历程如果一直不读取,那子历程就一直处于Z状态。
- 维护退出状态自己就是要用数据维护,也属于历程基本信息,以是生存在task_struct(PCB)中,* * 换句话说,Z状态一直不退出,PCB一直都要维护。
那一个父历程创建了很多子历程,就是不回收,就会造成内存资源的浪费。是的!因为数据布局对象自己就要占用内存,C中界说一个布局体变量(对象),是要在内存的某个位置进行开辟空间!
- 内存泄漏。
4.3 孤儿历程
- 父历程如果提前退出,那么子历程后退出,进入Z之后,那该如那里理呢?
- 父历程先退出,子历程就称之为“孤儿历程”
- 孤儿历程被1号init历程(体系)领养,当然要有init历程回收喽
- 为什么孤儿历程要被领养? 因为孤儿历程未来也会退出,也要被释放
5. 历程优先级
基础概念:
- cpu资源分配的先后顺序,就是指历程的优先权(priority)。
- 优先权高的历程有优先实行权利。配置历程优先权对多任务环境的linux很有用,可以改善体系性能。
- 还可以把历程运行到指定的CPU上,如许一来,把不重要的历程安排到某个CPU,可以大大改善体系整体性能
- 如果历程长时间得不到CPU资源,该历程的代码长时间无法得到推进 — 该历程的饥饿题目
5.1 检察体系历程
在linux或者unix体系中,用ps –l命令则会类似输出以下几个内容:
我们很容易注意到其中的几个重要信息,有下:
- UID : 代表实行者的身份
- PID : 代表这个历程的代号
- PPID :代表这个历程是由哪个历程发展衍生而来的,亦即父历程的代号
- PRI :代表这个历程可被实行的优先级,其值越小越早被实行
- NI :代表这个历程的nice值,表示历程可被实行的优先级的修正数值
5.2 PRI and NI
- PRI也还是比较好理解的,即历程的优先级,或者通俗点说就是程序被CPU实行的先后顺序,此值越小历程的优先级别越高
- PRI值越小越快被实行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice
- 如许,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被实行
- 以是,调解历程优先级,在Linux下,就是调解历程nice值
- nice其取值范围是-20至19,一共40个级别
5.3 其他概念
- 竞争性: 体系历程数目浩繁,而CPU资源只有少量,甚至1个,以是历程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
- 独立性: 多历程运行,需要独享各种资源,多历程运行期间互不干扰
- 并行: 多个历程在多个CPU下分别,同时进行运行,这称之为并行
- 并发: 多个历程在一个CPU下采用历程切换的方式,在一段时间之内,让多个历程都得以推进,称之为并发
5.3.1 历程切换------>并发
历程切换就是从正在运行的历程中,收回CPU的使用权利,交给下一个要运行的历程。
通过历程切换,从而实现并发。
我们先相识一下寄存器:
- 为什么函数返回值,会被外部拿到呢?-------->通过CPU寄存器
return a -> mov eax 10
- 体系如何得知我们的历程当前实行到哪行代码了?
程序计数器pc, eip(寄存器):纪录当进步程正在实行指令的下一行指令的地址!
- 寄存器作用:提高历程服从,高频数据放入寄存器中
- CPU寄存器内里生存的是历程的临时数据-----历程的上下文!
历程切换:
- 生存上下文----->下一个历程------>又到了这一个历程------>规复上下文
- 历程在从CPU上离开的时候,要将自己的上下文数据生存好,甚至带走
- 生存的目标,是为了未来的规复
6. 环境变量
6.1 基本概念
- 环境变量一样平常是指在操纵体系中用来指定操纵体系运行环境的一些参数
- 环境变量通常具有某些特殊用途,还有在体系当中通常具有全局特性
- 全局特性的原因:我们所运行的历程,都是子历程,bash自己在启动的时候,会从操纵体系的配置文件中读取环境变量信息,子历程会继承父历程交给它的环境变量!
6.2 常见环境变量
- PATH : 指定命令的搜索路径
- HOME : 指定用户的主工作目次(即用户登陆到Linux体系中时,默认的目次)
- SHELL : 当前Shell,它的值通常是/bin/bash。
6.3 和环境变量相关的命令
- echo $NAME //NAME:你的环境变量名称 不加$的话,echo把NAME当作字符串,直接打印NAME
复制代码- //直接设置环境变量
- export XL=123456 //设置的环境变量名称为XL,环境变量值为:123456
- //将本地变量转化为环境变量
- a=3 //设置本地变量
- export a
复制代码- unset NAME //NAME:你的环境变量名称
复制代码
- env: 体现所有环境变量
- set: 体现当地界说的shell变量和环境变量
6.4 环境表
每个程序都会收到一张环境表,环境表是一个字符指针数组,每个指针指向一个以’\0’结尾的环境字符串
6.5 通过代码如何获取环境变量
- #include <stdio.h>
- int main(int argc, char *argv[], char *env[])
- {
- int i = 0;
- for(; env[i]; i++){
- printf("%s\n", env[i]);
- }
- return 0;
- }
复制代码
- #include <stdio.h>
- int main(int argc, char *argv[])
- {
- extern char **environ;
- int i = 0;
- for(; environ[i]; i++){
- printf("%s\n", environ[i]);
- }
- return 0;
- }
复制代码 libc中界说的全局变量environ指向环境变量表,environ没有包罗在任何头文件中,以是在使用时 要用extern声明。
6.6 通过体系调用获取或设置环境变量
- getenv("NAME"); //NAME:环境变量名称 返回值为环境变量的值
复制代码
6.7 当地变量 && 内建命令
- 当地变量,只会在本BASH内部有效,不会被继承
- 创建当地历程
- 两批命令
- 常规命令 – 通过创建子历程完成的
- 内建命令 – bash不创建子历程,而是由自己亲自实行,类似于bash调用了自己写的,或者体系提供的函数
7. 程序地址空间
7.1 内存管理和历程管理
我们可以看到的所有地址都是虚拟地址,平时写的C/C++,用的指针,指针内里的地址,全部都不是物理地址。
7.2 子历程的历程管理与内存管理
- 写时拷贝是由操纵体系自动完成的,操纵体系重新开辟空间,但是在这个过程中,页表左侧的虚拟地址是0感知的,一直都是稳定的。
- 页表的 rw 表示该数据的读写属性,即确定了数据是否能修改。
- 子历程的页表是由父历程拷贝而来,但不同的是,不管父历程 rw 那一栏是什么,子历程页表的 rw 那一栏都是 r 。当子历程要修改数据时,操纵体系会辨别数据是否能修改,若能修改,那操纵体系会重新开辟空间,进行写时拷贝,并修改 rw 属性
7.3 页表
什么是页表?
页表是一种数据布局,用于管理虚拟内存和物理内存之间的映射关系。在操纵体系中,当一个程序需要访问内存时,它会先访问虚拟内存,然后再通过页表将虚拟地址映射到物理内存中的实际地址。
页表的作用是什么?
- 让历程以统一的视角对待内存。
- 增加历程虚拟地址空间可以让我们访问内存的时候,增加一个转换的过程,在这个转化的过程中,可以对我们的寻址请求进行审查,以是一旦异常访问,直接拦截,该请求不会到达物理内存,保护物理内存
- 因为有地址空间和页表的存在,将历程管理模块,和内存管理模块进行解耦合!
页表地址:
页表地址也属于历程上下文,历程运行时,被放在了寄存器了。
7.4 大文件的惰性加载
- 概况程序在盘算机如何运行:自己程序是在硬盘上,需要把程序加载进内存,然后由CPU去实行。
- 操纵体系对大文件可以实现分批加载,意思就是说,先将大文件放在硬盘里,cpu运行时,需要哪部分数据或代码再把哪部分数据或代码加载到内存里,cpu用完后,再把在内存中的这部分数据或代码释放在内存中释放掉。
- 缺页中断:当cpu需要某数据或代码时,会去历程地址空间查找虚拟地址,再通过页表检察对应代码或数据有没有加载到内存,如果有,就通过物理地址在内存中查找,如果没有,就在将数据或代码从硬盘加载到内存。当没有被加载的环境,就叫做缺页中断
- 那么历程在被创建的时候,是先创建内核数据布局呢? 先加载对应的可实行程序呢?---------先创建内核数据布局。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |