自由的羽毛 发表于 2024-8-18 03:19:18

<Linux>历程概念-下

文章目录

   目录
前言
一、环境变量
1. PATH
2. HOME
3. 其他环境变量
系统调用接口--getenv
 
4. 命令行参数
4.1 双参数main
4.2 三参数main
5. 设置环境变量
5.1 本地环境变量
 5.1.1 内建命令
5.2 固定环境变量
6. 取消环境变量 
7. 小总结
二、步伐地址空间 
1. 空间划分
2. 历程地址空间
3. 缺页制止
4. 历程地址空间的意义

前言

   历程切换

为什么函数返回值会被外部拿到?
        return a -->  mov eax 10,是通过CPU寄存器实现的
系统如何得知我们的历程当前实行到哪行代码了?
        步伐计数器pc,eip:记录当前历程正在实行指令的下一行指令的地址

寄存器大抵分类:
        通用寄存器:eax,ebx,ecx,edx
        栈帧:ebp,esp,eip
        状态寄存器:status

寄存器有很多,那么寄存器扮演着什么角色?
        1. 进步效率,有关历程的高频访问数据放入寄存器中
        2. CPU中寄存器生存的是历程相关的数据,即历程的临时数据——历程的上下文数据,CPU中有大量的历程上下文数据

        由于历程切换与时间片概念,是历程切换基于时间片轮转的调度算法,可以在同一时间,让多个历程得以推进,称之为并发。
        所以历程在从CPU上离开的时候,要将自己历程的上下文数据生存好带走(一些临时数据,ebp、esp、eip等),如果不生存这些临时数据,历程轮转之后新的历程数据会覆盖原先的历程上下文数据,所以生存的目标是为了下次轮转到自己时快速规复历程上下文数据,这些数据生存到PCB中(真正的是软硬件互助生存)

历程切换时:
        1. 生存上下文       
        2. 回复上下文
一、环境变量

基本概念:
        环境变量(env
ironment variables)一样平常是指在操纵系统中用来指定操纵系统运行环境的一些参数。
        例如,我们编写的C/C++代码,在各个目标文件进行链接的时候,从来不知道我们所链接的动静态库在哪里,但是照样可以链接成功,天生可实行步伐,原因就是有相关环境变量帮助编译器进行查找。
        环境变量通常具有某些特别用途,而且在系统当中通常具有全局特性。
   常见环境变量:
           PATH : 指定命令的搜索路径              HOME : 指定用户的主工作目录   (   即用户登陆到   Linux   系统中时   ,   默认的目录   )              SHELL : 当前   Shell,   它的值通常是   /bin/bash   。       查看环境变量方法:               echo $name     //name:环境变量名称               env
         //显示全部的环境变量    1. PATH

echo $PATH
必须要$,不然会被echo识别为字符串 https://i-blog.csdnimg.cn/direct/12b3cf12acf94fbb97d24aa55d23ac3e.png
        这些路径用冒号进行毗连,在调用指令时,系统默认从左到右,按指定路径进行搜索。如果不在指定的路径中,例如我们写的./mycmd,如果不写 ./ 只是简单的输入mycmd,会报错,指令找不到(找的操纵是shell进行的)
        如果将mycmd所在路径添加到PATH中,那么我们直接输入mycmd不加./,也可以直接实行,所以输入 ./ 是为了确定路径
PATH=$PATH:/home/ljs/XXXXX
#等号左右两侧不能有空格
//如果不写$PATH:那么PATH会被覆写,如果被覆写后,可以重启xshell
//因为环境变量是被存储在Linux配置文件中,每次启动后会加载到内存中         指令也是可实行步伐,实行前shell要先找到路径,所以shell会维护PATH环境变量,来生存指令搜索路径。
        可以知道,which指令就是从PATH环境变量中搜索路径。 
2. HOME

        不同的用户,$HOME不同。
        在xshell中,不同用户登陆后,由xshell分配bash,命令行解释器历程,此时会默认的cd到自己的家目录路径下,而HOME环境变量就是 shell 用来生存各个用户的家目录路径。
3. 其他环境变量

      和环境变量相关的命令    

[*]echo: 显示某个环境变量值
[*]export: 设置一个新的环境变量
[*]env
: 显示全部环境变量
[*]unset: 清除环境变量
[*]set: 显示本地定义的shell变量和环境变量
    env
https://i-blog.csdnimg.cn/direct/15cf5afd26be466b954b7723036b0b9b.png



[*]HOSTNAME:主机名
[*]HISTSIZE:生存到是历史命令被生存下来的条数(history指令可以查历史指令,由histsize变量控制条数)
[*]SSH_TTY:终端名称,我们在xshell中可以打开多个终端,每个终端互不干扰,只在指定的终端输出就是依靠终端名分别终端
[*]USER:用户名
[*]LS_COLORS:配色方案
[*]PATH:可实行步伐的路径
[*]PWD:当前历程所在路径
[*]LOGNAME:当前登任命户
[*]OLD_PWD:当前路径的上一个路径(cd - 等同于 cd $OLDPWD)
系统调用接口--getenv
 

        系统调用接口,getenv
可以根据传入的实参字符串,返回对应的环境变量
https://i-blog.csdnimg.cn/direct/5c6fd89c9dba42eaa42c24569672695e.png
printf("PATH: %s\n", getenv
("PATH");            环境变量:是系统提供的一组形如 name = value形式的变量,不同的环境变量由不同的用户,通常具有全局属性
4. 命令行参数

        我们初学c语言时,可能看到过main函数有参数
int main(int argc, char* argv[])
{
   

    return 0;
}          其中,argv是指针数组,argc是数组内元素个数。main函数并不是第一个函数,它是被CRTStartup()调用的,那么这两个参数是干什么用的呢?
        它是为指令、工具、软件等提供命令行选项的支持!
4.1 双参数main

           我们在输入 ./mycmd 时,如果背面追加 -a -b -c -d这一串字符串,那么https://i-blog.csdnimg.cn/direct/5167159aabff4ce684f879e685c954de.pnghttps://i-blog.csdnimg.cn/direct/95ae0b33c47240f1a5add1249fb1b886.png
        由于argv指针数组的末了一个数据的下一个位置默认设置为nullptr,所以我们也可以改变for循环内的条件。
https://i-blog.csdnimg.cn/direct/802553509b6c4dde831ec5f3adbb985d.png
        我们输入的 ./mycmd 在bash看来就是一个字符串,bash将空格作为分隔符,第一个字符串为指令,剩下的字符串是指令选项,bash可以得到小的字符串,根据小字符串的数目赋予argc变量值,argv数组中每个数据存放的是这些字符串首字符的地址。 
        那么我们可以根据这个原理,来操持带选项的指令,根据argv中指向的字符串来判定携带的是什么选项
https://i-blog.csdnimg.cn/direct/0346c081180046ceb9d9d0f3b638961a.png
https://i-blog.csdnimg.cn/direct/88778dca59a744948067e672a7791ce4.png
        这就是指令为什么可以有选项的原因,例如 ls -l 、ls -a等等。所以命令行参数它是为指令、工具、软件等提供命令行选项的支持!根据不同的选项,表现不同的功能
        所以在学c语言时,没有利用该知识点是因为当时我们不利用命令行,所以不需要。
4.2 三参数main

   main函数除了这两个参数范破例,还重载的有三参数范例
int main(int argc, char* argv[], char* env
[]){    return 0;} https://i-blog.csdnimg.cn/direct/81e5459210734adfb5936eb52724a0be.pnghttps://i-blog.csdnimg.cn/direct/51b75886a6734ff8bfd76bd01a615189.png
        我们利用env
指针数组,也实现了输出全部环境变量的结果
   综上,main函数有两张核心的向量表
        1. 命令行参数表
        2. 环境变量表
        即一个历程的创建不仅仅只是将我们的步伐加载到内存就竣事了,而是需要被调用main函数并完成两张核心表的建立
        我们所运行的历程,都是子历程,bash本身在启动时,会从操纵系统的配置文件中读取环境变量信息,而子历程会继承父历程的环境变量,所以我们全部的历程环境变量几乎相同,这就体现了环境变量的全局性
        历程之间具有独立性,如果子历程对环境变量有修改,那么就会发生写时拷贝
           除了env
、和三参数的main函数可以获取环境变量,我们还可以利用第三方变量env
iron来获取环境变量,可以利用man命令查看用法
           libc中定义的全局变量env
iron指向环境变量表,env
iron没有包罗在任何头文件中,所以在利用时 要用extern声明    #include <stdio.h>int main(int argc, char *argv[]){   extern char **env
iron;   int i = 0;   for(; env
iron; i++)   {         printf("%s\n", env
iron);   }   return 0;}5. 设置环境变量

5.1 本地环境变量

MY_VALUE=12345678          如果再命令行处直接定义,它会成为本地环境变量,在env
中查找不到
https://i-blog.csdnimg.cn/direct/6f1c6ce566d944ae84ad197d61ca44ba.png
        本地环境变量不会被子历程继承,它只在本bash处有用
set
查看本地环境变量  https://i-blog.csdnimg.cn/direct/0eb4307dcab74c7e843bf770cce28e92.png
           其中的ps1环境变量表示对是命令行提示符的格式,\u为用户名,\h为主机名、\w为当前工作目录,$为提示符。如果是root用户,那么提示符为#,这就表明白本地环境变量只在本bash内有用
        ps2是命令行次提示符,当一行指令还没输完,可以用 \ 续行
ls \
> -a 5.1.1 内建命令

问题:既然本地变量只在本bash内部有用,那么为什么echo能够输出本地变量的值呢?(echo是指令,我们之前认为任何指令都会创建历程,该历程是bash的子历程),也就是说bash的子历程为什么能输出bash的本地变量?
   答:指令分为两类


[*]常规命令 -- 通过创建子历程完成
[*]内建命令 -- bash不创建子历程,而是由自己完成,类似于bash调用了自己写的或是系统提供的函数,即echo在bash内部有代码实现,所以不创建子历程
那么类似的内建命令尚有export、cd等命令,cd并不是一个新的bash的子历程,而是直接由bash操纵,因为如果是bash子历程,那么它修改的就是子历程的当前工作目录,修改不了父历程工作目录,我们可以简单的实现以下cd功能,cd命令依靠的是chdir系统调用接口。
终端1:
./mycmd /

终端2:
ps axj | head -1 && ps axj | grep mycmd    从而找到pid
ls /proc/pid/cwd -l    显示当前工作目录#include<stdio.h>#include<stdlib.h>#include<string.h>#include<unistd.h> int main(int argc, char* argv[], char* env
[]) {      sleep(20);   printf("change beign\n");   //判定argv的值,就是命令行参数是否为两个字符串   if (argc == 2)   {         chdir(argv);   }   printf("change end\n");   sleep(20);    return 0;}        如许就可以观察到mycmd历程确实更改了路径,将mycmd名称更改为bash,那么这就是cd的简答代码

5.2 固定环境变量

export MY_VALUE=12345678         再进行grep查找
https://i-blog.csdnimg.cn/direct/fb92b0130c854ae3b0b70058858170fe.png
        此时我们再次实行我们的步伐,即可证明环境变量是会被子历程继承的,因为有我们新设置的MY_VALUE
https://i-blog.csdnimg.cn/direct/2f7ad3470cd844b19cc06206c018df94.png

6. 取消环境变量 

unset MY_VALUE         再查找就查找不到了,即一旦bash的环境变量改变,子历程继承的环境变量也会改变

7. 小总结

   
[*]步伐运行需要命令行参数表、环境变量表,这两张表是系统维护的,可以简单理解为bash(因为bash广义上也是操纵系统的一部分)
[*]main函数不要简单的被语言方面局限了,main也是函数,它也需要被传参调用(被系统调用),传入两张表
二、步伐地址空间 

1. 空间划分

https://i-blog.csdnimg.cn/direct/ab8b599c50a54228b705e9d3edd9b36e.png
1. 首先,我们来验证一下各区地址的大小环境
https://i-blog.csdnimg.cn/direct/f5123582c6c04ce7944e139e662da941.png
https://i-blog.csdnimg.cn/direct/a961fb194f1543a9be86f248fc76efee.png
        可以看到代码区、字符常量区、已初始化全局变量、为初始化全局变量、堆区、栈区地址依次从低到高 
2. 我们再来验证堆、栈各自地址的生长环境
https://i-blog.csdnimg.cn/direct/1ddda72f04c742368764d64fcfe9ae24.png
https://i-blog.csdnimg.cn/direct/8c7fa3c8abd64c99b7d32f05f4a4fce6.png
        可以看到,堆区向上生长,地址数为七位,栈区向下生长,地址数位12位,两者相对,中心空缺的部分为动静态库
           如果将变量a前加上static修饰,那么a的地址数将由12位更改为7位,故static修饰的局部变量编译的时候已经被编译到全局数据区,只是作用域只在该函数内部,而生命周期是全局的
3. 再来看命令行参数环境变量所在地区 
https://i-blog.csdnimg.cn/direct/e6ea73e7bb2f4a0c9336522380b53154.png
https://i-blog.csdnimg.cn/direct/1c9da6b96ddb4fd3b73101abd3692d71.png
        可以看到地址是递增的,且大于堆的地址,所以命令行参数和环境变量在堆的上层空间
           在学完历程地址空间后,我们再来看这里的环境变量,可以理解为什么环境变量具有全局属性,因为命令行参数和环境变量也在假造地址空间上,在页表中也有对应的映射关系,所以在创建子历程时,子历程以父历程为模板,拷贝了父历程的假造地址空间和页表,此时即使我们不向子历程的main函数传入环境变量表,子历程也能获取环境变量
2. 历程地址空间

我们再来看历程方面
https://i-blog.csdnimg.cn/direct/dea8fd525489497a970d9d764931d418.png
https://i-blog.csdnimg.cn/direct/b38b81f94f264da1acf4bc1e9ec3ef76.png
   问:为什么同一个变量,同一个地址,同时读取,却读到了不同的内容?
        结论:该地址肯定不是物理地址,如果是物理地址,那么肯定不会出现上面的征象,这种地址被称为线性地址或假造地址
        任何一个历程都需要PCB内核数据结构和历程假造地址表,历程假造地址表的地址在PCB结构体中生存,而且有一张页表来生存映射假造地址到物理地址
        父历程创建子历程后,子历程继承父历程的大部分PCB数据并进行修改,同时也完全拷贝父历程的假造地址表和页表,此时因为页表相同,所以父子历程同时指向同一块的物理地址(数据和代码都相同),当子历程检测到g_val被修改时,操纵系统会先创建一个新的g_val空间,并拷贝父历程的数据,在根据子历程修改的数据进行修改,再修改页表的映射关系,原先的假造地址不变,修改映射的物理地址
        前提是该历程正在被CPU实行
https://i-blog.csdnimg.cn/direct/6f26198071a648a78af19d37f3c28ffa.png
https://i-blog.csdnimg.cn/direct/46950e1934534d05995ebaa33b070317.png
地址空间:我们的盘算机地址总线分列组合形成地址范围【0,2^32】(在32位的盘算机中,有32位的地址和数据总线)
           所谓历程地址空间,本质是一个描述历程可视范围的大小。地址空间内肯定要存在各种地区划分,对线性地址进行start,end描述。
        任何一个历程都要有PCB和历程地址空间
        地址空间本质是内核的一个数据结构对象,和PCB一样,也是要被操纵系统管理,进行先描述,再构造
struct mm_struct
{
    long code_start, code_end;

    long readonly_start, readonly_end;
   
    long init_start, init_end;

    long uninit_start, uninit_end;

    long heap_start, heap_end;

    long stack_start, stack_end;
}         每一个task_struct都要指向自己的 mm_struct结构体,32位系统默认内存为4GB,那么每个历程都在该物理内存中规划自己的历程地址
        历程地址空间就类似于一把尺子,尺子的刻度由0x00000000到0xffffffff,尺子按照刻度被划分为各个地区,例如代码区、堆区、栈区等。而在结构体mm_struct当中,便记录了各个边界刻度,例如代码区的开始刻度与竣事刻度,
        在结构体mm_struct当中,各个边界刻度之间的每一个刻度都代表一个假造地址,这些假造地址通过页表映射与物理内存建立接洽。由于假造地址是由0x00000000到0xffffffff线性增长的,所以假造地址又叫做线性地址。
           32位操纵系统有4GB内存,每个历程的历程地址空间可分得一部分内存大小,但是不能全部获取4GB大小内存
    页表:每一个历程都要在CPU上被调度,在CPU上有一个cr3寄存器,存放的是页表的起始地址(物理地址),本质上是历程的硬件上下文,所以在历程轮转时,该数据会被生存,下一次被调度时再次加载 。当CPU需要访问物理地址的数据时,会根据cr3寄存器找到页表,从而找到物理地址
        此时我们页表有三个字段,分别为历程假造地址、物理地址、标志位,标志位的意义在于我们查找到数据所在的物理地址后,我们应该区分该地址的数据是可读写照旧只可读,这就是标志位字段的意义,标志该地址处的数据可读写环境
        所以为什么代码、字符常量是只读的?因为代码加载到磁盘就是写入,而你说了磁盘的代码处是只读的,这里的关键就是页表的第三个字段——标志位,因为标志位是r,所以是只读!!
3. 缺页制止

        我们也玩过大几十G的游戏,我们知道历程是要将自己的代码和数据加载到内存中的,但是内存大多是都是16或32GB,怎么能容纳几百G的游戏呢?
        此时操纵系统的解决方案就是对大文件实现分批加载,先加载并运行一小部分,之后再加载剩余的代码和数据,这个举动就是惰性加载。此时页表的映射关系中,假造地址是全填的,但是会缺少一部分的物理地址,因为此时背面的代码和数据还没有加载到内存,当CPU根据假造地址找到内存的物理地址,发现该物理地址处还没有加载,那么就会申请空间,将对应剩余的代码和数据加载到内存中,该机制被称为缺页制止
        页表的第四个字段也是标志位,表示物理地址此处对应的代码和数据是否已经加载到内存,从而使CPU根据这一标志来判定是否触发缺页制止机制,将在磁盘中剩余的代码和数据自动加载到内存中,并构建号页表内剩余的映射关系。
        所以,历程在被创建的时候是先构建内核数据结构(PCB、假造地址空间、页表),再渐渐加载对应的可实行步伐,从而填充页表。
        写时拷贝就是根据缺页制止实现的
   那么历程之间具有独立性更具体的理解:
        首先,每一个历程都已各自的PCB、历程地址空间、页表,这些都是互相独立的
        其次是步伐的代码和数据层面,因为页表的存在,即使是父子历程,代码指向相同,即使假造地址相同,但是它们所映射的物理地址不同,这就使得它们的数据层面解耦合,互相独立,这就是历程之间的独立性根源
4. 历程地址空间的意义

综上所述,为什么要操持历程地址空间?


[*]让历程以同一的视角看待内存。如果没有历程地址空间,那么历程PCB需要记录代码和数据的物理地址,一旦历程换出再换入时,PCB还要修改内容,而且尚有越界访问的风险,所以我们需要中转站,即历程地址空间这一结构,此时历程只需要根据相应的映射即可找到代码和数据
[*]增长转换过程(页表),在转换过程中可以对我们的寻址哀求进行检察。一旦非常访问,直接拦截,该哀求不会到达物理内存,从而掩护了物理内存
[*]因为有地址空间和页表的存在,将历程管理模块(PCB、历程地址空间、页表)和内存管理模块(页表和物理内存)进行解耦合,只负责各自的工作
    历程 = 内核数据结构(task_struct && mm_struct && 页表)+ 步伐的代码和数据
所以,历程切换的时候,只用切换历程上下文数据即可,因为task_struct中有指针指向历程地址空间,而cr3寄存器属于历程上下文数据,cr3寄存器存放的是页表的起始地址。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: <Linux>历程概念-下