文章目录
前言
对进程概念的理解为进程控制、多线程等方面的学习具有较大的程度的影响,在Linux操作系统学习中具有举足轻重的地位!!!
1、认识冯诺依曼体系结构
冯诺依曼体系结构:现代计算机硬件体系结构-----五大硬件单元
1.1 、五大硬件单元
输入设备: 进行数据采集的设备,获取数据的设备-----典型设备:键盘
输出设备: 进行数据输出的设备 -----典型设备:显示器
存储器: 典型设备:内存(本质上来说内存也不是用来存储数据的,而是用来交换数据的,缓冲数据的)
运算器: 典型设备:CPU
控制器: 典型设备:CPU(CPU----中央处理器----包含控制器和运算器)
注:
1. 内存的存储介质是一种易失性介质,断电数据则丢失,但是内存的吞吐率非常高,因此适宜做中间缓冲区带,并非存储数据,则是使用磁盘使用持久性存储
2.这里的存储器指的是内存。所有设备都只能直接和内存打交道,即所有设备都围绕这存储器工作
那么该如何去理解冯诺依曼体系呢?
1.2、理解冯诺依曼体系结构
对于我们写程序,实则就是告诉计算器什么时候控制什么设备进行怎样的操作
那么对于冯诺依曼体系结构的理解,可从现实生活中的实际行为,感受其中的数据流向!!!
比如: 我们给对方传输文件,计算机先有硬盘(输入设备)进行输送,首先文件会被放到存储器中进行缓存,然后从存储器中取出相应的数据加载到CPU(控制器&运算器)上,待处理完毕后,把处理后的数据又放回到存储器中,控制存储器,最终将相应位置的数据传输给制定的输出设备–网卡,网卡拿到数据然后将数据发送到网络上,进行通信
2、操作系统
2.1、概念
定位:让计算器更加好用!!!
任何计算机系统都包含一个基本的程序集合,成为操作系统------管理计算机软硬件资源
操作系统包括:
- 内核(进程管理,内存管理,文件管理,驱动管理)
其他程序(如库函数,shell程序等等)
2.2、目的
设计OS的目的:
- 与硬件交互,管理所有的软硬件资源
为用户程序(应用程序)提供一个良好的执行环境
2.3、如何理解管理
- 描述被管理对象
组织被管理对象
易知:用户是无法直接操作内核的,实际流程如下图
- 系统调用接口:操作系统向上提供用于访问系统内核的接口
库函数:针对某个功能,使用大量的系统调用接口进行封装而成的函数借口
库函数和系统调用接口的联系:库函数实则为多个系统调用接口封装组合的结果
用户则是通过自顶向下逐步进行,实现对内核操作
3.进程概念
3.1、概念
进程就是运行中的程序。
应用程序:就是程序员写的代码程序
代码程序:一堆硬盘上的固定指令,运行程序就是让cpu去处理这些指令
运行程序:首先要将程序从硬盘加载到内存上,cpu从内存的指定位置开始处理(冯诺依曼体系结构)
CPU分时机制
:cpu对多任务的处理进行划分,让每个程序的指令和数据在cpu上运行一个时间段很短的时间片,待时间片结束后,则将切换到下一程序,由于时间很短,就给人一种同时运行的感觉(并行);
疑惑:当操作者运行切换回来的时候,那么是如何知道上一次是运行到哪里的???
因为程序是切换调度运行的,所以系统必须知道每个程序要运行的代码和数据在内存中的具体信息(包括运行到哪里,保存在内存哪个位置等),故操作系统对于每一个程序的运行都进行了描述(存于pcb-进程控制块)。
- 故从操作系统的角度,进程是对运行程序的一种动态描述,通过这个描述操作系统才能实现对程序运行管理和资源调度。
3.1.1、描述进程-PCB
进程信息被放在一个叫做进程控制块的数据结构中(即pcb),可以理解为进程属性的集合。
其在Linux下,是一个task_struct结构体
其中的描述信息包括:
- 标识符:描述本进程的唯一标识符,用于区别其他进程
状态:任务状态,退出代码,退出信号等。
优先级:相对于其他进程的优先级
程序计数器:进程中,即将被执行的下一条指令的地址
内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
上下文数据:进行执行时处理器的寄存器中的数据
I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和进程使用的文件列表
记账信息:可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等
其他信息
3.2、查看进程
3.2.1、进程查看进程指令
查看所有进程(包括相关进程的父进程)的信息 : ps -ef
查看更为详细进程的信息 : ps -aux
USER : 进程的属主;
PID : 进程的ID;
PPID : 父进程;
%CPU :进程占用的CPU百分比;
%MEM :占用内存的百分比;
NI :进程的NICE值,数值大,表示较少占用CPU时间;
VSZ : 进程虚拟大小;
RSS :驻留中页的数量;
TTY :终端ID WCHAN 正在等待的进程资源;
START :启动进程的时间;
TIME :进程消耗CPU的时间;
COMMAND : 命令的名称和参数;
3.2.2、杀死进程指令
cpu使用率查看: top
杀死进程指令: kill + pid kill -9 + pid(强制杀死)
注 : kill不能消灭僵尸状态
4.进程状态
进程状态:阻塞,就绪,运行
在Linux下:
R运行态:进程要是在运行中(拿到时间片,正在运行的)要么在运行队列里
S睡眠状态:进程等待事件完成(可中断睡眠)
D磁盘休眠状态:又时候也叫不可中断休眠状态,通常会等待IO的结束
T停止状态:一种能被调度,但是什么都不做的一种状态
X死亡状态:只是一种返回状态,不会显示在任务列表中
4.1、进程状态的查看
例如查看orphan进程
(ps -aux|head -n 1)&&(ps -aux|grep orphan)
4.2、特殊进程
4.2.0、创建子进程
创建子进程指令 : pid_t fork(viod);
返回值(区分父子进程的方式,进而实现分流操作):
返回-1,表示创建子进程失败;
返回 0,表示为创建的子进程;
返回值大于0,表示父进程(父进程返回的是子进程的pid(大于0));
创建子进程,通过复制调用进程,即创建一个pcb(在Linux中,即task-struct结构体),其会复制父进程pcb的大量信息
例如:
- 内存指针,其意味着父子进程运行的是同一段代码
- 上下文数据,其意味着运行的当前位置也一样
创建子进程的目的: - “分摊压力”,大两任务处理时,父子进程协作,提高处理效率;
- “保护父进程”,子进程处理其他任务,运行部分代码,父子进程功能不同,实则就是找个替罪羊(大冤种~~~~)
4.2.1、僵尸进程
僵死态: 进程退出了,但是资源没有完全释放
僵尸进程: 僵尸态的进程,但是资源未被完全释放
- 那么是什么资源未被释放呢?
又是为什么会发生僵尸进程呢?
僵尸进程的危害:造成资源泄露
(1.pcb占据内容,内存资源;2.一个用户能够创建的进程是有限)
include <stdio.h>
2 #include <unistd.h>//fork函数头文件
3 #include <sys/uio.h>
4 #include <stdlib.h>//exit函数头文件
5 int main()
6 {
7 int pid=fork();//创建子进程
8 if(pid<0)//出错返回
9 {
10 printf("fork error\n");
11 return 1;
12 }
13 else if(pid>0)//父进程
14 {
15 printf("parents fork\n");
16 }
17 else{//子进程
18 sleep(5);
19 printf("child fork\n");
20 exit(0);
21 }
22 while(1)//死循环
23 {
24 sleep(1);
25 }
26 return 0;
27 }
sleep(5);睡眠五秒后,子进程退出,状态有S+的可中断睡眠态,到Z+的僵死态
但,我们可发现子进程的资源并未释放,即其pcb资源未被释放;
待Ctr+C进行进程退出
子进程被销毁
4.2.2、孤儿进程
孤儿进程:父进程先退出,子进程就成为孤儿进程
孤儿进程最终会被1号Init进程领养,即也有1号进程回收
1 #include <stdio.h>
2 #include <unistd.h>//fork函数的头文件
3 #include <stdlib.h>//exit函数的头文件
4 #include <sys/uio.h>//sleep函数的头文件
5 int main()
6 {
7 int pid=fork();
8 if(pid<0)
9 {
10 printf("fork error\n");
11 }
12 else if(pid==0)
13 {
14 sleep(5);//使父进程先退出
15 printf("child fork\n");
16 }
17 else
18 {
19 printf("parent fork\n");
20 exit(0);//退出父进程
21 }
22 while(1)//死循环
23 {
24 sleep(1);
25 }
26 return 0;
27 }
注:一号进程很“负责”,子进程退出,会立刻释放资源,不会导致为僵尸进程
特殊的孤儿进程:守护/(精灵)进程
1.运行在后台
2.自行新建会话,脱离了登录终端时所建立的登录会话(即不受终端印象,默默进行任务处理)
5.环境变量
环境变量: 环境遍历实则就是保存当前运行环境参数的一些变量,可以让环境配置更为简单
PATH环境变量:保存系统程序的默认搜索路径,即将程序所在路径加入其中,则运行程序就可不用路径了
若无环境变量,则所有的配置,都在配置文件中,那么配置文件若被修改,就需要重新加载配置
相关指令:
env 查看所有环境变量
echo $(环境变量) 打印制定的环境变量
set 查看当前环境的所有变量(包括普通变量和环境变量)
export 变量 声明某个变量为环境变量
unset 环境变量 移除制定的环境变量
相关函数:
const char* getenv(const char* name); //查看制定环境变量数据的值
extern const char** environ; //全局变量,保存了所有环境变量
6.程序地址空间
- 地址空间,即内存地址空间
程序本身不占据内存,只有运行起来后,被加载到内存,才占据空间,因此程序地址空间,更应该被称为进程地址空间
6.1、虚拟地址空间
其实进程内部即用户所访问到的空间地址实则是一个假地址
进程地址空间,其实是一个虚拟地址空间,进程中所用的地址都是虚拟地址空间的虚拟地址,
物理地址由操作系统(OS)统一管理;
虚拟地址空间,就是系统为进程进行的虚拟空间维度进行划分的描述,这个描述是一个mm_struct结构体
若物理地址空间为8 G,操作系统会通过虚拟地址空间对每一个进程进行按需分配(即用多少就分配多少)
- 1.用户访问的是虚拟地址空间,其物理地址空间存在一定的映射关系(使得虚拟地址空间和物理地址空间不一致),进而可实现离散存储数据(充分利用物理内存中的碎片化空间)
- 2.**每个进程都有自己独立的虚拟地址空间,且无需担心与其他进程地址冲突,同时还可以在虚拟和物理空间映射关系之间做出内存访问控制**
当创建一个子进程时,其回复制调用进程(父进程)中pcb的大量信息,其中也包括虚拟地址空间,mm_struct结构体,但其中父子进程中的相同变量值可能不同(写时拷贝技术)。
写时拷贝技术:空间原本访问的是同一块,但当空间中的数据发生改变时,就要为子进程重新开辟空间,拷贝数据,修改映射关系
1 #include <stdio.h>
2 #include <sys/types.h>
3 #include <unistd.h>
4 int val=100;
5 int main()
6 {
7 int pid=fork();
8 if(pid<0)
9 {
10 printf(" fork error\n");
11 }
12 else if(pid==0)
13 {
14 val=200;
15 printf("child fork:%d\n",val);
16 }
17 else
18 {
19 printf("parent fork:%d\n",val);
20 }
21 return 0;
22 }
6.2、映射方式
虚拟地址到物理地址的映射方式:内存管理方式
- 1.分段式
将虚拟地址空间划分为,数据段,代码段…
作用:便于编译器进行地址管理
实现:将虚拟地址空间进分段,而虚拟地址空间包含两个信息----段号&段内地址
- 2.分页式
将虚拟地址进行更加细致的分页管理,一个内存页默认4096个字节
实现:虚拟地址空间包含两个信息----页号&页内偏移量
3.段页式
将地址空间进行分段,每个分段内采用进行分页管理
地址组成:段号+段内页号+页内偏移量
通过段号,从段表中获得该段的页表位置,找到页表,在对应段内页号,获得相应页号的物理段起始地址,再加上页内偏移量获得映射后的物理地址
缺页中断:发现当前要访问的数据不在物理内存中,触发缺页中断,将需要的数据从交换分区中重新加载进来;
- 导致缺页中断的:
内存置换:当运行的程序多了,加载到内存条上的数据也就多了,当内存不够用时,会将物理内存中(不常用)的数据加载到磁盘的交换分区中(该交换分区作为交换内存使用),进而腾出内存供新数据使用
那么操作系统是判断数据为不常用的呢???
1.最久未使用LRU算法
2.最少未使用LFU算法
有兴趣的小伙伴可以研究一下这篇博客:
https://blog.csdn.net/weixin_44588495/article/details/106743802