目录
1.冯诺伊曼体系
2.操作系统
3.进程
4.进程的三种状态
4.1概念
4.2Linux中不同进程状态
5.进程的优先级
6.进程的几个其它概念
7.进程的切换
8.环境变量
8.1环境变量的概念
8.2常见的环境变量
8.3和环境变量相关的命令
8.4查看环境变量的方法
8.5通过代码获取环境变量的方式
8.6环境变量具有全局属性
9.程序地址空间
1.冯诺伊曼体系
1.计算机组成基本概念:
CPU:称为中央处理器,是由运算器,控制器和其它一些设备组成,是用来进行数据处理的
内存:具有掉电易失的特性,因此用来进行临时存储
外设:磁盘,输入和输出设备,具有永久性存储的特点
注:处理数据的速度,依次递减
2.cpu 内存 外设之间的关系
cpu在处理数据之前首先需要获取数据,由于处理数据速度关系,为了提高计算机整机效率,cpu会从内存中获取数据,因此cpu依赖与内存
内存中数据具有临时存储的特性,因此内存中的数据是从磁盘中加载进来的,所以内存依赖与外设
注:内存的存在解决了cpu与外设之间速度不匹配的问题!
3.操作系统
内存中数据的加载和清理是由操作系统完成的
4.图解:
注:上面所说的CPU,内存,外设之间的关系是建立在数据层面上的
2.操作系统
1.什么是操作系统?
是一款进行软硬件资源管理的软件
2.为什么要进行软硬件资源管理?
为用户提供,稳定的,高效的,安全的执行环境
3.怎么进行软硬件资源管理?
管理的本质:
举一个例子:作为一名学生,在大学中我们被校长管理,但是我们却很少见过校长的面,甚至一次都没有见过,但是为什么我们依旧能够被很好的管理呢?这是因为我们自己本身的所有资料都被校长所掌握,其中校长获取数据是通过执行者去获取数据然后交给校长,例如学校的辅导员,然后校长通过我们的数据,做出相应的决策,让辅导员执行校长的决策,因此通过这种方式管理方式实现了对我们的管理!
得出结论:管理的本质是对数据的管理!
所以操作系统对硬件的管理中间还包含一层驱动层,为了获取数据和执行决策:
管理过程中会存在的问题:
当学生的数据量比较大的时候,校长管理成本就会非常高,效率会非常差,为了降低管理成本,提高效率,可以将每个学生的信息类型实例化出一个类,然后再通过相应的数据结构,如:链表,进行管理,这样的方式大大提高了管理效率!
实例化类:称为描述
对应的数据结构管理:称为组织
4.如何为用户提供良好的环境?
举个例子:
用户到银行存钱和取钱的过程,用户到银行存钱的时候不能直接到银行的保险柜放入钱,而是与银行的工作人员接触,在对应办理业务的地方进行办理,在柜台办理业务的地方类比到操作系统中就是一个个相应的函数调用接口,通过这种接口来实现与操作系统交互,完成用户的需求!
注:提供接口的本质是为了保护操作系统
上面银行的服务解决了大部分人的问题,但是此时有一个大妈,不认识字,到柜台办理业务不能填表等操作,此时银行为了解决这个问题,在大厅中有专门负责这种问题的经理,下次大妈办理业务的时候只需要将自己的需求告诉经理,然后经理办理相应的业务,类比到操作系统中,在系统调用接口之上还提供了一些接口,比如shell完成指令操作,C/C++lib库完成编程操作以及界面等!
此时一套完整的体系结构就出来了:
3.进程
1.概念:加载内存的程序叫做进程!
图例解释:
进程 = 内核数据结构 + 对应的磁盘代码
2.在Linux操作系统下查看进程:
1.写好代码
2.将写好的代码进行编译
3.加载到内存运行程序
4. 查看进程:
指令:ps ajx
3.标识进程的两种ID: 进程和父进程
子进程:PID
父进程:PPID
获取进程和父进程的的两个系统调用接口:
getpid , getppid
父进程和子进程的区别:
父进程:是登录Linux操作系统就创建的,不会改变:
子进程:是每次运行程序就会创建一个新的子进程
4.终止进程:
1.使用命令进行终止:
kill -l:查看系统信号
2.CTRL c:终止进程:
5.创建进程:
使用系统调用接口fork()
fork()使用说明:
返回值:pid_t(是一个整数)
如果创建成功:子进程的pid返回给父进程,0返回给这个子进程
创建失败:-1返回给父进程,没有进程被创建
4.进程的三种状态
4.1概念
1.进程的运行状态:
2.进程的阻塞状态:
3.进程的挂起状态:
注:所谓进程的不同状态,本质是进程在不同的队列中,等待某种资源!
4.2Linux中不同进程状态
Linux内核源代码中对不同进程的描述:
/* * 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 */ };
1.R:运行状态:
当前程序:
编译运行:
2.S:睡眠状态:阻塞状态的一种!
当前程序:
是因为printf像显示器输出的时候,速度比较慢,大多数的时候进程处于等待状态!
3.T:暂停状态:
如何继续呢?
注:第一次运行的时候R后面带有+,第二次暂停后运行的时候R后面的+不见了有什么区别吗?
带有+号的称为是前台程序,不带+号的称为后台程序
前台程序:运行的时候命令行解释器不能够再被使用,可以使用CTRL + C 终止掉程序
后台程序:运行的时候命令行解释器还能够再使用,但是不能通过ctrl + C 终止程序,只能够使用kill -9 终止程序
4.D状态:深度睡眠状态
内存中有一个进程A,现在需要像磁盘中写入数据,但是此时内存中内存不足了,操作系统就会将进程A结束掉,然后当需要磁盘中的数据的时候,无法再找到,因此,为了防止这种情况的产生,可以将进程A设置为D状态,此时这个进程就无法通过操作系统结束掉,只能通过断电或者是进程自己醒来
5.t状态:
在调试的过程中的一种暂停状态
6.Z状态:僵尸状态
解释:进程已经结束了,但是资源没有被立即释放
子进程的释放是由父进程和操作系统回收的
7.孤儿进程:
解释:当父进程被杀掉的时候被操作系统回收,此时这个被操作系统回收的这个进程被称为孤儿进程
5.进程的优先级
为什么会存在优先级?
在操作系统中进程数量远大于CPU的数量,所以CPU的资源是有限的,因此存在优先级的问题!
Linux操作系统中的优先级:
最终优先级 = 老的优先级(80) + NI
NI的取值范围:[-20,19]
数值越小表示优先级越高
NI的值可以通过top这个工具进行修改:
修改之前的:
使用top修改:
修改之后的:
6.进程的几个其它概念
竞争性:进程的数量很多,而CPU的数量很少,所以资源有限,导致进程之间是有竞争性的
独立性:每个进程之间都是互相独立运行的,并不存在互相影响的问题
并行:多个进程在多个CPU上同时,分别进行运行,这种称之为并行
并发:多个进程在同一个CPU中采用进程切换的方式,在一段时间内,多个进程都得以推进,这种方式就被称为是并发的!
7.进程的切换
1.什么是进程切换?
进程在运行的时候,占用CPU资源,但不是一直占有直到进程结束,而是进程运行的时候,都有自己的时间片,时间一到,CPU就会运行下一个进程,这种方式就叫做进程的切换
2.如何做到进程切换?
举个例子:假设你上大学时候在校学习一年,然后报名参军,当你离开学校的时候就会到教务处告知你参军的信息,目的是为了保留你的学籍,参军结束后继续恢复学籍。继续上学,而这种保留学籍和恢复学籍的过程就如同是进程在运行时间片过去后用寄存器保留信息,下一次到该进程时用寄存器保留的信息恢复上一次进程结束的时候,继续运行!
8.环境变量
8.1环境变量的概念
环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数
8.2常见的环境变量
PATH
1.为什么在命令行解释器上执行一些系统的命令如ls,pwd,which这些命令可以直接执行,而不需要带上相应的路径,而自己写的可执行程序需要带上路径呢?本质都是.exe的可执行程序,为什么自己写的不行呢?
答案是:有一个环境变量叫做PATH
什么是PATH?
PATH:是用来指定命令的搜索路径
系统的指令路径已经添加到对应的PATH这个环境变量中了,所以在执行命令的时候,会默认到指定路径下查找
如何将自己写的.exe程序放到PATH这个环境变量呢?
PWD:
记录当前所处的路径
HOSTNAME:
获取主机名
8.3和环境变量相关的命令
env:显示所有环境变量
export:设置一个新的环境变量
1.在命令行上定义一个本地变量
2.使用export将myval设置为全局的环境变量
set显示本地定义的shell变量和环境变量
unset清除环境变量
8.4查看环境变量的方法
echo $NAME //要查看环境变量的名称
8.5通过代码获取环境变量的方式
getenv:获取环境变量的值:
例:获取用户名
通过getenv我们能够推导出为什么普通用户在执行一些指令的时候会出现权限不足的问题:
是通过判断当前用户名与该权限的用户是否一致
命令行参数:
什么是命令行参数
argc:表示选项的个数
argv:是一个指针数组,指向每一个命令选项的起始地址
如何使用命令行参数?
可以让用户通过不同的命令选项执行不同的功能!
第三个命令行参数:
env:是一个指针数组,指向每个环境变量的首字符,最后一个指向NULL
不传命令行参数如何获取环境变量的值:使用environ
environ是一个二级指针,指向指针数组env(命令行第三个参数)的第一个指针
8.6环境变量具有全局属性
bash是一个系统进程,mycmd是bash的子进程,当前进程能够获取环境变量的值,说明环境变量是具有全局属性的,这样做的目的也是为了让子进程能够使用环境变量的值
9.程序地址空间
C/C++程序地址划分:
这里的地址空间划分是地址吗?
答案:不是,这里的空间划分是虚拟的地址空间
如何证明呢?
通过下面的一段代码来证明:
#include <stdio.h> #include <unistd.h> int global_value = 100; int main() { pid_t id = fork(); if(id < 0) { printf("fork error\n"); return 1; } else if(id == 0) { int cnt = 0; while(1) { printf("我是子进程, pid: %d, ppid: %d | global_value: %d, &global_value: %p\n", getpid(), getppid(), global_value, &global_value); sleep(1); cnt++; if(cnt == 10) { global_value = 300; printf("子进程已经更改了全局的变量啦..........\n"); } } } else { while(1) { printf("我是父进程, pid: %d, ppid: %d | global_value: %d, &global_value: %p\n", getpid(), getppid(), global_value, &global_value); sleep(2); } } sleep(1); }
通过运行之后发现,当global_val的值被改变之后,子进程和父进程互相独立运行,输出各自的值,但是global_val的地址都是相同的,从这里可以看出global_val存放的地址并不是实际的物理地址而是虚拟地址
感性理解虚拟地址空间:
举个例子:
假设有一个大富翁,这个大富翁有三个私生子,他们互相不认识,大富翁让三个儿子分别掌管自己的三个不同的企业,并告诉自己的三个儿子,好好干,将来将自己的所有资产都让你来继承,这里的三个儿子就相当于是三个进程,大富翁是操作系统,大富翁的资产是内存,而富翁分别给自己的三个儿子继承资产画的饼就相当于是虚拟地址空间;
在计算机中如何画饼呢?
是通过先描述在组织的方式,在语言层面就是通过结构体来描述,然后通过该结构体定义出不同的对象进行组织
如何管理虚拟地址空间呢?
在操作系统中有一种内核数据结构:mm_struct,是通过该结构体进行管理虚拟地址空间
1.虚拟地址空间划分:
下面以32位操作系统来举例:
地址空间描述的基本大小是字节
每一个字节都要有自己的唯一地址,所以在32位操作系统下有2^32个地址,而这2^32个地址就是虚拟地址
从0x0000……0000一直编址到0xffff ffff
语言层面进行虚拟地址空间划分:
是通过在结构体中定义每个区域的起始位置和终止位置进行区域的划分
struct mm_struct { uint32_t code_start,code_end; //代码区 uint32_t data_start,data_end; //数据区 uint32_t heap_start,heap_end;//堆区 uint32_t stack_start,stack_end;//栈区 };
所以进程地址空间的管理是通过定义一个结构体指针,通过该结构体指针完成对进程中每个区域的划分,需要调整区域划分时只需要通过该结构体指针找到数据的起始位置和终止位置进行修改就可以完成进程地址空间的管理了
所以在C/C++中申请和释放空间的本质是修改各个区域的start和end
进程地址空间和内存是如何建立关系的呢?
首先介绍一个新的东西页表,什么是页表?
简单来说页表是将进程地址空间和内存之间建立联系的,页表的左侧存放虚拟地址,页表的右侧存放物理地址,当需要改a的值时通过虚拟地址找到页表,再通过页表找到物理地址,然后修改a的值
为什么存在进程地址空间呢?
1.如果让进程直接访问物理内存,如果存在越界,就会造成进程间的影响,所以存在虚拟地址空间,然后通过页表映射物理内存的方式可以避免这种问题,因为页表对于虚拟地址的映射会进行判断是否合理,如果不合理就会拒绝映射
2.解释该代码中的现象:
父子进程创建完成之后,global_val是一个共享数据,共同指向同一块物理内存,当子进程需要修改global_val的值的时候,为了保证进程之间的相互独立性,所以操作系统会将物理内存的值重新拷贝一份放到一块区域,然后将新拷贝的数据的地址放入到页表中,然后子进程再通过页表修改重新拷贝数据区域的值,此时就保证了不同进程之间的相互独立性,所以上图中父进程和子进程global_val的虚拟地址都相同,但是值不相同的原因就在这里
所以进程地址空间存在的第二个原因是可以更加方便进程和进程之间数据代码的解耦,保证进程相互独立性的特征
3.进程内部的执行方式:
首先编译器编译代码也是通过进程虚拟地址空间的方式对代码和数据进行编址的,当程序加载到内存的时候就天然具有了一个外部物理地址, 因为编译器的编址方式和mm_struct编址方式是相同的,所以在进程地址空间代码区中将代码的地址放到start和end中,然后通过页表映射到内存中,将进程地址空间和内存建立起联系,然后当进程调度CPU执行指令的时候,通过运行队列找到task_struct,再通过task_struct找到code_start,执行指令,执行完一条指令后继续通过地址找到代码,继续执行下一条代码,通过这种方式,最终完成程序的编译!
注:CPU在执行代码的时候也就是虚拟地址
所以进程地址空间存在的第三个原因是因为让进程以统一的视角,来看待程序对应代码和各个数据区域,方便使用地址让CPU编译代码
注:编译器划分代码区域的地址称为逻辑地址,划分方式和进程地址空间划分方式相同,所以逻辑地址也称作虚拟地址