Linux——进程基本概念下篇

Linux——进程基本概念下篇



一、环境变量

要执行一个命令,必须先要找到对应的可执行程序
为什么我们运行自己的程序的时候要加上././就是指明当前路径,要找到程序才能运行,而我们运行指令的时候却不用加呢?

因为环境变量

我们通过Xshell输入账号密码登录云服务器
在这里插入图片描述
在这里插入图片描述
为什么命令行中会自动进入我所在的家目录/home/hs 而root进入的是/root

登陆的时候,系统会进行以下步骤
1.输入用户名&&密码
2.认证
3.形成环境变量(肯定不止一个,PATH,PWD,HOME) 并且根据用户名,初始化HOME=/root,HOME=/home/XXX
4.cd $HOME

1.1 环境变量的定义

环境变量一般是指在操作系统中用来指定操作系统运行环境的一些参数

环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性(这就是我们使用某些程序不需要./的原因)

系统中会存在大量的环境变量,每一个环境变量都有它自己的特殊用途,用来完成特定的系统功能
注意环境变量也是OS在内存或磁盘中开辟的空间用来保存系统相关数据

常见的环境变量

PATH : 指定命令的搜索路径
HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)
SHELL : 当前Shell,它的值通常是/bin/bash

1.2 环境变量的相关命令

echo $NAME // NAME:你的环境变量名称

在这里插入图片描述

env命令可以查看系统所有变量

在这里插入图片描述
我们也可以把我们自己的程序路径加到环境变量中

使用export指令

export PATH=

注意如果直接export PATH=我们自己的程序路径的话,会导致path环境变量本身具有的环境变量被覆盖
如果之前的path环境变量被覆盖,那么系统中的很多指令就会执行不了,因为没有默认寻找路径

所以不改变原有环境变量,并且添加自己的环境变量推荐以下写法

export PATH=$PATH:xxx程序所在路径 向环境变量PATH中添加xxx程序

默认更改环境变量,只限于本次登陆,重新登陆,环境变量自动被恢复 为什么?

我们直接更改的是bash进程内部的环境变量信息,每一次重新登陆,都会给我们形成新的bash解释器并且新的bash解释器自动从读取形成自己的环境变量表信息

1.3 命令行参数

main函数可以携带两个参数

int main(int argc, char* argv[])

int argc是参数的个数
char* argv[]是一个数组指针,数组的每个元素是char* 类型的指针,在linux中,通常以每个程序后面的附加选项实现,例如ls -a, ls -l,会被系统以空格为分隔符,划分成一个个字符串,依次放入argv[]中

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

shell or os自动帮我们做的,将一个大的字符串以空格作为分隔符,被分割成了若干个字串

为什么要这么干? ? ?

命令行参数,可以支持各种指令级别的命令行选项的设置,理解历史学的指令与选项之间的关系

其实main函数还有第三个参数——如何证明?

在这里插入图片描述
在这里插入图片描述
再执行env指令
在这里插入图片描述
通过对比,我们发现第三个参数是系统中的环境变量,那么这是谁传给main函数的呢?

系统启动我们的程序的时候,可以选择给我们的进程(main)提供两张表:
1.命令行参数表
⒉环境变量表

命令行启动的进程都是shell/bash的子进程,子进程的命令行参数和环境变量是父进程bash给我们传递的

父进程的环境变量信息又从哪里来?

环境变量信息是以脚本配置文件的形式存在的,每一次登陆的时候,你的bash进程都会读取vim.bash_profile配置文件中的内容,为我们bash进程形成一张环境变量表信息

如何证明?

当操作系统运行起来后,bash进程也会运行起来,同时读取配置环境变量的本地脚本,形成环境变量表,而我们的进程又大多都是bash的子进程,都会被传这张相同的环境变量表,系统环境变量具有全局属性,可以被子进程继承下去

即使不在main函数中读取第三个参数,main函数中也会有一个全局变量char** environ指向这张环境变量表
在这里插入图片描述

1.4 本地变量和环境变量

本地变量只在bash进程内部有效,不会被子进程继承下去
环境变量通过让所有的子进程继承的方式,实现自身的全局性

shell本身就是一个c程序,本地变量相当于在main中定义的一些局部变量,用set查看shell中的环境变量和本地变量,unset取消环境变量和本地变量

举个例子
在这里插入图片描述

1.5 常规命令和内建命令

Linux的命令分类

常规命令:shell调用fork函数,创建子进程,让子进程成执行的
内建命令:shell命令行的一个函数,可以直接读取shell内部定义的本地变量

echo,set,unset,pwd,umask,export等等都是内建命令,内建命令速度更快,不受环境变量影响

二、进程地址空间

2.1 地址空间的概念

在我们之前学习的C语言或者C++中,相信大家对内存应该有一定的了解,对于内存中的分布,也应该有一定的概念

在我们之前的学习中,内存中的空间分布应该是如下图中所示
在这里插入图片描述
在这里插入图片描述
但实际在内存中真的是这样分布的吗?

我们用代码进行验证
在这里插入图片描述
输出结果
在这里插入图片描述

此时能得到在父进程和子进程中g_val的地址和值是相同的,没有问题!
因为根据我们之前学的,子进程创建的时候,代码和数据是以父进程为模板创建的

我们将代码稍稍修改一下
在这里插入图片描述
输出结果
在这里插入图片描述

我们发现,父子进程,输出地址是一致的,但是变量内容不一样,这就与我们知道的知识相悖了,为什么同一个变量,同一个地址,会出现两个不同的值?

前面我们讲过如果父子进程之间的任何一方将值修改了后,OS就会进行写时拷贝,但如果发生写实拷贝的话,地址为什么还一样呢?

所以我们可以得出,此处的地址绝对不是真实的物理地址,我们平时用到的地址,都是虚拟地址/线性地址

结论

变量内容不一样,所以父子进程输出的变量绝对不是同一个变量
但地址值是一样的,说明该地址绝对不是物理地址
在Linux地址下,这种地址叫做虚拟地址
我们在用C/C++语言所看到的地址,全部都是虚拟地址!物理地址,用户一概看不到,由OS统一管理

OS必须负责将 虚拟地址 转化成 物理地址

什么是地址空间? 什么是区域划分?

OS中有物理地址,而每个进程都有一份自己的虚拟地址
每个进程都会认为自己是独享整个物理地址,它们都会以统一的方式划分自己的内存

每个进程都有自己的地址空间,而操作系统需要管理这些地址空间,如何管理?

答案很简单,先描述,再组织

所以进程地址空间本质是内核的数据类型(struct mm_struct)

struct mm_struct
{
	long code start;
	long code_end;
	long data_start;
	long data_end;
	long heap_start;
	long heap_end; //brk
	long stack_start;
	long stack_end;
	................
	//其他区域也类似
}

在这个结构体中划分好了各个区域的起始和终止位置,这就是区域划分
通过这些区域划分出不同的界限,每个进程都认为自己的mm_struct代表整个内存(地址从0x000000…000 ~ 0xFFFFFF…FFF),也就是每个进程都认为自己拥有4GB的空间

2.2 页表和MMU

OS通过页表+MMU(内置在CPU中的一个硬件)来对齐进行映射
页表负责把虚拟地址映射到物理地址

那么为什么需要页表映射,不能直接让进程地址空间访问物理内存吗?

如果我们允许进程直接访问物理内存,就很有可能访问到不属于自己的空间,所以我们需要页表来管理,如果是非法的OS就直接可以拒绝访问,所以页表中除了虚拟地址和物理地址之间的映射,还有对应的访问权限字段和是否分配&&是否有内容的字段

在这里插入图片描述

在这里插入图片描述

举个例子:

const char* p = "abcd",我们不能通过指针p修改"abcd",因为它在字符常量区,本质是OS给我们的权限只有读权限(通过页表的权限管理)

值得注意的是,当进程创建子进程前,父子进程在页表中的数据段和代码段的权限都会被OS修改成只读的,代码段只读是正常的,因为代码本身就不能被更改,但为什么数据段也要修改成只读呢?
父进程创建子进程的时候首先将自己的读写权限,改成只读,然后再创建子进程

当父进程创建子进程之后,子进程写入,此时发生写时拷贝,重新申请空间,进行拷贝,但由于权限是只读的,OS就会进行介入,此时OS发现是因为需要进行写实拷贝的情况,会进行放行,OS会修改页表,父子进程指向不同的物理内存,这是写实拷贝的策略机制

但当不是这种情况的时候,就比如上述例子中,本身字符常量区是不能修改的,此时程序出错,OS就会将请求驳回

在这里插入图片描述

2.3 地址空间的作用

为什么要有地址空间?

1. 让进程以统一的视角看待内存,所以任意一个进程,可以通过地址空间+页表可以将乱序的内存数据,变成有序,分门别类的规划好
2. 存在虚拟地址空间,可以有效的进行进程访问内存的安全检查
3. 将进程管理和内存管理进行解耦,通过页表让进程映射到不同的物理内存处,从而实现进程的独立性

2.4 地址空间的好处

我们想象一种场景:我们直接申请一块大空间,操作系统会直接全部给我了吗?如果我们没有用那么多,其他的空间不是被浪费了吗?

所以当我们在堆区申请一块空间的时候,OS会把地址空间中的heap_end加上我们申请的大小,但是却没给我们物理内存,当我们需要读取的时候,才会使用页表进行映射,得到物理空间

假设没有地址空间,那么CPU是不是只能遍历一遍物理空间才能找到该进程的起始位置?

我们可以把main函数的地址放入每个进程的地址空间(同一位置),然后CPU就访问地址空间的固定位置然后通过页表映射找到物理内存存放代码数据的位置,这样效率可以得到提升

总结
为什么要有地址空间?

1️⃣ 通过添加一层软件层,完成对进程操作的风险管理(权限管理),保护了物理内存以及各个进程的数据安全
2️⃣ 将内存申请和内存使用的概念在时间层面上划分清楚,通过地址空间完成屏蔽底层申请物理空间的过程,达到进程读写内存和OS进行内存管理操作,在软件层面上分离(你不知OS给你的内存是富裕或者是快满状态的空间)
3️⃣ 站在CPU和应用层的角度,每个进程可以看作统一使用4GB空间,而且每个空间区域的相对位置是比较确定的

  • 15
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值