目录
1. 冯诺依曼体系结构
现代计算机的硬件体系结构
-
1.1 五大硬件单元
- 输入设备:键盘
- 输出设备:显示器
- 存储器:内存
- 运算器
- 控制器
- 内存 -> 大脑正在思考的数据的区域(30Gbps,每秒30Gbit)
- 硬盘 -> 大脑中存放记忆的区域(机械200MB/s,固态3-400MB/s 7-800MB/s)
- CPU主频 -> 时钟震荡周期 -> 机器指令周期。主频越高,CPU单位时间内能处理的指令越多
-
硬件结构决定软件行为
- 所有设备都是围绕存储器(内存)工作的,所有数据都是围绕内存流动的。
- 输入设备获取数据存储到内存中,CPU从内存中获取数据,处理数据,运算完毕放入内存,输出设备从内存中获取数据。
-
两种主要CPU架构:RISC(精简指令集)、CISC(复杂指令集)
-
通常说的32位、64位,值得是CPU一次可以读取32/64bits的数据
-
1.2 操作系统
操作系统=内核+应用
- 操作系统功能:管理计算机中的软硬件资源,为上层应用程序提供良好的执行环境。让计算机使用更加方便,用户体验更好。
- 操作系统 -> 负责管理的软件 ,对下管理软硬件资源,对上提供良好的执行环境。
- 管理:
- 描述+高效的组织,先描述被管理者,再组织起来进行管理
- 描述——结构体,组织——链表
- 硬件驱动将硬件信息收集起来,交给操作系统。操作系统对这些信息进行管理。
- 系统调用接口:操作系统向上层用户提供的操作接口,提供良好的执行环境。
- 库函数(lib)和系统调用接口的关系:
- 上下级的调用关系,库函数就是系统调用接口的一层封装。
- 库函数(lib)和系统调用接口的关系:
- 管理:
2. 进程概念
- 从用户角度来说,进程就是进行中的程序(抽象化概念),程序运行起来需要被加载到内存。
- 从操作系统角度来说,进程就是操作系统为了更好的管理程序,对运行中的程序进行信息描述(具象化描述)- 进程描述符PCB。
- 程序:存储在硬盘中的可执行程序文件
-
2.1 操作系统如何管理进程
先描述后组织
- 描述信息称为进程控制块PCB ,Linux下的PCB是结构体struct,称为task_struct。
- 使用task_struct结构体描述进程,使用双向链表将这些结构体组织起来进行管理
- task_struct包含着进程的信息,被装载到内存中。
- 操作系统通过task_struct来运行程序。
-
2.2 操作系统如何描述进程
操作系统去双向链表中通过PID找PCB。
- 2.2.1 task_struct内容分类:
- 标识符pid:用于区别其他进程的唯一标识符
- 进程状态
- 上下文数据:保存程序当前正在处理的数据(学生休学要保留学籍,而上下文数据就是保留的学籍)
- 程序计数器:保存即将被执行的下一条指令的地址(下一个需要处理的学生)
- 进程优先级:cpu优先分配权,让操作系统运行更加合理
- 内存指针:指向该进程运行起来后,程序以及数据被加载到内存中的位置
- 文件状态信息:I/O请求、被进程使用的文件列表
- 记账信息:进程在cpu上的运行时间
- 进程按功能分类
交互式进程:与用户进行交互的进程,优先级较高
批处理进程:在后台一直运行的进程,优先级较低
- 进程的切换调度:
- cpu的分时机制(并发):cpu只是在每个进程上运行很短的一段时间(时间片),如果cpu处理某个进程的时候,时间片用尽,namecpu就要去调度运行个下一个进程了
- 一个进程并不是一直在被cpu处理的,而是不断地轮询切换被cpu调度运行
- 一个进程运行时,就会创建一个目录,保存进程运行信息,当进程退出时,其目录就会被删除掉。
- /dev/pts/0 终端文件
-
2.3 查看进程:
-
/proc 目录保存当前运行的进程信息
-
获取PID为1的进程信息 -> /proc/1
-
- ps:查看进程信息
- -ef
- 用标准的格式显示进程
- UID:用户id
- PID :进程id
- PPID :父进程id
- C:进程占用CPU的百分比
- STIME:进程启动到现在的时间
- TTY:该进程在那个终端上运行 -> 若与终端无关,则显示? 若为pts/0等,则表示由网络连接主机进程
- TIME:该进程实际使用CPU运行的时间
- CMD:命令的名称和参数
- 用标准的格式显示进程
- aux
- 用BSD的格式来显示进程
- USER
- PID
- %CPU:进程占用的CPU百分比
- %MEM :占用内存的百分比
- VSZ :该进程使用的虚拟內存量(KB)
- RSS:该进程占用的固定內存量(KB)
- TTY
- STAT:进程的状态
- START:该进程被触发启动时间
- TIME
- COMMAND
- 用BSD的格式来显示进程
- -ef
- top:查看服务器负载
- getpid():获取进程pid
- getppid():获取父进程pid
-
#include<stdio.h>
#include<unistd.h>
int main(){
//pid_t getpid(void);
//获取调用进程的pid,谁调用返回谁的pid
pid_t pid=getpid();
printf("pid:%d\n",pid);
printf("ppid:%d\n",getppid());
while(1){
sleep(1);
}
return 0;
}
-
2.4 创建进程(创建PCB):
fork():操作系统提供的创建进程的接口。
- 通过复制调用进程创建一个新的PCB(子进程),复制的是父进程的PCB (PID不同)
- -> 子进程与父进程代码和数据指向同一块内存区域(数据和代码运行的完全一样),并且子进程复制了程序计数器、上下文数据。
- -> 当前的子进程不是从代码的起始位置开始运行,而是从子进程创建成功(fork())的下一步指令开始运行的。
- -> 子进程与父进程代码和数据指向同一块内存区域(数据和代码运行的完全一样),并且子进程复制了程序计数器、上下文数据。
- 总结上述内容:父子进程代码共享,数据独有
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
int main(){
//pid_t fork(void);
//通过复制调用进程,创建一个新的子进程
pid_t pid=fork();
//因为父子进程返回值不同,虽然代码相同,父进程因为返回值会进入不同的判断执行体
if(pid==0){
printf("i am child! pid:%d\n",getpid());
}else if(pid>0){
printf("i am parent! pid:%d\n",getpid());
}else{ //<0,出错
//perror:打印上一个系统调用的错误信息
perror("fork error");
return -1;
}
printf("pid:%d\n",getpid());
while (1){
printf("this is public pid:%d !\n", getpid());
Sleep(1);
//每间隔1s,打印两条,说明父子进程同时运行
//printf("----\n");
}
return 0;
}
-
2.5 进程状态
阻塞态、运行态、就绪态
- linux下的进程状态:
- 运行 R -> Running (上述的运行+就绪)
- 可中断睡眠 S -> sleeping (随时可被唤醒)
- 不可中断睡眠 D -> disk sleep (特殊手段才可被唤醒,操作完毕后自动唤醒)
- 停止 T -> stopped (被暂停的进程可 以通过发送 SIGCONT 信号让进程继续运行 )
- 僵尸 Z -> zombie
- 追踪状态 t -> tracing stop
- 死亡 X -> dead
- 僵尸进程:处于僵尸状态的进程(还未完全退出,仍占用系统资源)
- 危害:资源泄露,僵尸进程过多导致正常进程可能无法创建
- 产生原因:子进程先于父进程退出,操作系统检测到子进程退出后通知父进程,但没有联系到父进程,操作系统不敢擅自释放子进程的资源(子进程的pcb中包含有退出原因,一旦释放就没地方保存退出原因)。这时,子进程处于退出但是资源没有完全释放的状态(既没有运行又没有退出),子进程就成了僵尸进程。
- 父进程需要根据子进程的退出原因判断子进程是否成功完成任务
- 解决:杀死父进程(退出原因的保存已经毫无意义)
- 预防:进程等待
- 一个进程无法杀死,该进程可能就是僵尸进程
- 孤儿进程:
- 产生原因:父进程先于子进程退出,子进程成为了孤儿进程,该孤儿进程将被“孤儿院” init 进程领养,子进程退出后将由init进程来回收释放资源,因此孤儿进程不会产生僵尸进程。
- 守护进程/精灵进程:特殊的孤儿进程 -> 在孤儿进程的基础上进行一定的操作。
-
2.6 进程优先级 PRI
- 数字,决定进程CPU资源的优先分配权
- PRI值越小,优先级越高
- 默认优先级80,优先级无法直接修改,通过设置NI值进而对优先级做出设置
- PRI=PRI+NI
- 查看: ps -l
- 交互式进程优先级较高,批处理进程优先级较低
- 设置:
- renice
- 程序运行后设置
- renice -n ni_val -p pid 将标识符为PID的进程的NI值修改为ni_val
- 程序运行后设置
- nice(NI)
- 程序运行时设置
- nice取值范围:-20~19
- nice -n ni_val 程序 将该程序的进程的NI值修改为ni_val
- 程序运行时设置
- renice
- 进程相关概念:
- 竞争性:操作系统有多个进程,而CPU只有一个进程,进程会抢占CPU资源。
- 独立性:进程之间相互独立,每个进程都不受到其他进程的影响,保证进程稳定运行。
- 并发:CPU资源不够时,切换调度运行。
- 并行:CPU资源足够的情况下,多个进程同时运行。
3. 环境变量
操作系统中用来设置操作系统运行环境参数的变量,具有全局性
- 作用
- 让系统设置更加方便,程序运行更加高效(因为环境变量具有全局特性)、规范
- 指令
- env 查看所有环境变量
- echo $变量 查看指定环境变量
- set 查看所有变量(包含环境变量)
- unset 变量 删除一个变量 / 环境变量
- export 变量=值 (定义)声明一个环境变量,不加export则声明的是局部变量
//环境变量的全局特性演示
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(){
//char *getenv(const char *name)
//通过环境变量名称来获取环境变量值
char *ptr=getenv("MYVAL");
printf("MYVAL:%s\n",ptr);
return 0;
}
- 环境变量在代码中的获取使用及全局特性:
- 接口 getenv():char *getenv(char *env_name)
- main第三个参数:main(int argc,char *argv[],char *env[])
- 声明全局变量char **environ:extern char **environ
- 常见环境变量:PATH HOME SHELL
4. 程序地址空间
- 每个程序都有自己的程序地址空间,程序地址空间实际上是一个虚拟地址空间。
- 虚拟地址空间:假的内存地址,是一个结构体(进程的内存描述符):mm_struct
- 结构体包含
- 代码的起始与结束地址(code_start、code_end)
- 数据的起始与结束地址(data_start、data_end)
- 总的大小(size)
- 内存地址
- 内存区域的一个编号,地址指向内存的某个位置
- 虚拟地址空间使用的就是虚假的内存地址编号
- 结构体包含
- 虚拟地址空间:假的内存地址,是一个结构体(进程的内存描述符):mm_struct
- 为什么使用结构体描述一段内存,而不是直接使用物理内存?
- 缺乏内存访问控制、内存利用率低
- 采用分页式内存管理(虚拟地址空间+页表)
- 页表
- 记录虚拟地址与物理地址之间的映射关系
- 对内存的访问控制 -> 在页表中,可以将一个虚拟内存地址标为只读的
- 页表
5. 进程调度
操作系统切换调度进程(PCB)运行在cpu上,若存在很多进程,则依次运行,但是进程具有优先级。
- CPU O(1)调度算法 (空间换时间)
- 若使用双向链表来进行管理,需要将进程遍历一遍,找出优先级最高的进行调度,效率过低。
- 空间换时间,有n个优先级,创建n个队列,组成队列数组。若优先级为n,加入到第n个队列。之后找优先级为0的队列,下标越小,越优先被调度。
- 由于仍然需要判断每个队列是否为空,为了提高效率,使用位图。再设置n个比特位,0代表队列为空;1代表队列非空,表示有进程需要被调度。
- 进程调度完成后,需要出队。再创建n个队列,出队后放入新队列。队列全部出完后,可调度队列不存在。新的队列可认为是一个过期队列。