Linux进程概念——其二

news2024/9/21 0:29:22

目录

环境变量

基本概念

常见环境变量

查看环境变量方法

测试PATH[重点]

测试HOME

和环境变量相关的命令

环境变量的组织方式

通过代码获取环境变量

通过系统调用获取或设置环境变量

环境变量通常是具有全局属性的[重点]

程序地址空间

研究背景

程序地址空间回顾

程序地址空间新认识

进程地址空间

程序——>进程

g_val的地址相同值不同解释

fork为什么两个返回值

虚拟空间存在的意义


环境变量

基本概念

  • 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数
  • 如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
  • 环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性

常见环境变量

  • PATH : 指定命令的搜索路径 [重点]
  • HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)[重点] 

  • SHELL : 当前Shell,它的值通常是/bin/bash

  • HOSTNAME:当前主机的机器名

  •  HISTSIZE:系统一次允许记录使用过的命令的最多条数

查看环境变量方法

  • echo $NAME    //NAME:你的环境变量名称[重点]

比如查看PATH环境变量,使用以上命令:

测试PATH[重点]

  • 创建myprocess.c文件
    #include <stdio.h>
    int main()
    {
         printf("hello world!\n");
         return 0;
    }
    
  • 对比./myprocess执行和之间myprocess执行

发现后者报错:

  • 为什么有些指令可以直接执行,不需要带路径,而我们的二进制程序需要带路径才能执行?

因为其他的指令的二进制文件路径被添加到了PATH环境变量中,而我们的没有

  • 将我们的程序所在路径加入环境变量PATH当中, export PATH=$PATH:hello程序所在路径

  • 还有什么方法可以不用带路径,直接就可以运行呢?

直接将我们的文件拷贝到指令集的二进制文件目录(/usr/bin)下

测试HOME

  • 用root和普通用户,分别执行 echo $HOME,对比差异

  • 执行 cd ~; pwd,对应 ~ 和 HOME 的关系

 cd ~即访问的是HOME的变量值

和环境变量相关的命令

  • echo: 显示某个环境变量值[重点]

比如我们想查看PATH环境变量,如下图:

  • export: 设置一个新的环境变量[重点]

 通过上述实验发现确实可以,当如果想要把我们自己的路径添加到PATH中时,如下图:

发现我们是修改了PATH环境变量,并不是添加,此时PATH中环境变量全没了

由于没有修改系统中的配置文件只命令行上修改是临时性的,只要重新登录终端就恢复了,如下图:

 增加环境变量的正确操作应该如下图:

我们发现在原来的基础增加了新的PATH环境变量,而我们使用的命令是export PATH=$PATH:环境变量值这个命令的意思是$PATH是把PATH中的所有环境变量提取出来放到PATH中,:后面是新增加一个环境变量

  • env: 显示所有环境变量[重点]
  • unset: 清除环境变量

清除环境变量方式如下图:

  • set: 显示本地定义的shell变量和环境变量

使用set | less命令能看到这些全是环境变量和shell本地变量

可以看到我们定义的本地shell变量和环境变量都可以查看到 

环境变量的组织方式

每个程序都会收到一张环境表,环境表是一个字符指针数组,每个指针指向一个以’\0’结尾的环境字符串

通过代码获取环境变量

  • 命令行第三个参数[重点]
#include <stdio.h>
int main(int argc, char *argv[], char *env[])
{
     for(int i = 0; env[i]; i++){
         printf("%s\n", env[i]);
     }
     return 0;
}

main函数的第三个参数接受的就是环境变量,打印env即打印的是环境变量

  • 通过第三方变量environ获取[重点]
#include <stdio.h>
int main(int argc, char *argv[])
{
     extern char **environ;
     for(int i = 0; environ[i]; i++){
         printf("%s\n", environ[i]);
     }
     return 0;
}

libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用时要用extern声明。

通过系统调用获取或设置环境变量

  • putenv
  • getenv
#include <stdio.h>
#include <stdlib.h>
int main()
{
 printf("%s\n", getenv("PATH"));
 return 0;
}

常用getenv和putenv函数来访问特定的环境变量。

例如使用getenv获取PATH环境变量:

环境变量通常是具有全局属性的[重点]

  • 环境变量通常具有全局属性,可以被子进程继承下去
#include <stdio.h>
#include <stdlib.h>
int main()
{
     char * env = getenv("MYENV");
     if(env){
         printf("%s\n", env);
     }
     return 0;
}

首先定义一个bash本地变量MYENV,发现没有结果,说明该环境变量根本不存在,导出环境变量 export MYENV="hello world",再次运行程序后会发现就会有hello world。

解释:MYENV被定义且没有使用export时,此时MYENV是bash本地变量,我们使用getenv时返回的是null,而使用export导出MYENV后就返回的是hello world,说明MYENV变成了环境变量。

由于我们能访问到本来是在bash中被定义的本地变量MYENV,而MYENV变成了环境变量后我们作为bash的子进程的程序也能访问到MYENV,说明了MYENV具有了全局属性,可以被子进程继承下去

额外的:既然子进程无法访问作为父进程bash中的本地变量,但是却可以使用echo输出bash中的本地变量。虽然Linux大部分命令都是通过子进程的方式执行的,但是还有一部分命令不通过子进程的方式执行,而是由bash自己执行(调用自己对应的函数来完成特定的功能,我们把这种命令叫做自建命令)

程序地址空间

研究背景

  • kernel 2.6.32
  • 32位平台

程序地址空间回顾

学习C语言的时候,接触过如下空间布局图:

 使用下面的代码验证一下:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int ug_val;
int g_val = 100;

int main(int argc, char* argv[], char* env[])
{
    printf("main addr:%p\n", main);
    printf("init addr:%p\n", &g_val);
    printf("uninit addr:%p\n", &ug_val);
  
    char* m1 = (char*)malloc(100);
    char* m2 = (char*)malloc(100);
    char* m3 = (char*)malloc(100);
    char* m4 = (char*)malloc(100);
    static int n = 100;//全局数据区
 
    printf("static addr:%p\n", &n);                                                                                                                     
 
    printf("heap addr:%p\n", m1);
    printf("heap addr:%p\n", m2);
    printf("heap addr:%p\n", m3);
    printf("heap addr:%p\n", m4);

    printf("stack addr:%p\n", &m1);
    printf("stack addr:%p\n", &m2);
    printf("stack addr:%p\n", &m3);
    printf("stack addr:%p\n", &m4);
 
    for(int i = 0; i < argc; ++i)
        printf("argv addr:%p\n", argv[i]);
    for(int i = 0; env[i]; ++i)
        printf("env addr:%p\n", env[i]);
    return 0;
 }

然后运行该程序:

根据输出结果仔细对比上面的图发现确实如此,栈是向下增长的,堆是向上增长的,相对而生

程序地址空间新认识

根据程序地址空间,认识地址空间的存在,有如下代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
 
int g_val = 100;

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
         int flag = 0;
         while(1)
         {
             printf("I am son: pid:%d, ppid:%d, g_val=%d, g_val's address:%p\n", getpid(), getppid(), g_val, &g_val);
             flag++;
             sleep(1);
             if(flag == 5)
             {
                 g_val = 200;
                 printf("warning:the number has been changed!\n");
             }
        }
   }
   else
   {
     while(1) 
     {
         printf("I am father: pid:%d, ppid:%d, g_val=%d, g_val's address:%p\n", getpid(), getppid(), g_val, &g_val);                                     
         sleep(1);
     }
   }
   return 0;
 }

运行该程序,在flag等于5之前:

父进程和子进程的g_val值一样,地址也一样,这里很好理解,因为子进程是以父进程为模板创建的。

当flag等于5以后,出现了一个无法理解的现象:

 父进程和子进程中的g_val地址一样,但是值却不一样! 能得出如下结论:

  • 变量内容不一样,所以父子进程输出的变量绝对不是同一个变量!
  • 地址值是一样的,说明该地址绝对不是物理地址!
  • 在Linux地址下,这种地址叫做虚拟地址(也叫逻辑地址或者线性地址)
  • 我们在用C/C++语言所看到的地址,全部都是虚拟地址!物理地址,用户一概看不到,由操作系统统一管理!

操作系统必须负责将虚拟地址转化成物理地址 。

所以说“程序地址空间”的说法是不准确的,准确的应该说成进程地址空间

进程地址空间

每一个进程都会有一个自己的进程地址空间。

地址空间:操作系统通过软件的方式,给进程提供一个软件视角,认为自己会独占系统的所有资源(主要体现在内存上)。

操作系统通过先描述再组织的方式管理进程地址空间,也就是说进程地址空间其实是内核的一个数据结构(struct mm_struct)

我们发现在内核源码确实存在struct mm_struct,操作系统将虚拟地址分为了很多区域,如栈区,堆区等,每个区域都有一个start和end来划分区域,只不过是使用链表的方式划分的,而最后会经过页表的方式映射到物理内存上

程序——>进程

当一个程序写完并编译后未被加载,程序内部有无地址?

有,从链接中可以体现出来,链接会将我们的程序和库文件产生联系,而产生联系的方式就是需要使用地址

当一个程序写完并编译后未被加载,程序内部有无区域?

有,使用readelf -S命令查看编译好的二进制文件:

发现文件中确实有address(地址)和offset(偏移量) 

我们所熟知的是只有当程序加载到内存中才有地址,但是这里并未加载到内存中怎么会有地址?

说明编译好文件中的地址并非物理地址,而是虚拟地址,当加载到内存中的时候经过相关转化的处理即可,所以在编译程序的时候就已经编址好了,等加载到内存中经过页表的处理映射到物理内存就行了

注:虚拟地址空间不仅操作系统会考虑,编译器也会考虑!

g_val的地址相同值不同解释

这里就能解释上面的父进程和子进程中的g_val地址一样,但是值却不一样,如下图(只划分出了数据,未划分出代码的存储,和数据存储的情况一致):

当数据未被修改时,由于子进程是以父进程为模板创建的,因此两个进程虚拟地址一致,并且映射到了同一个物理地址上,也就是flag=5之前的现象

但是,当我们进行写入时,无论是父进程还是子进程,由于进程具有独立性,进程之间互不影响,因此就会发生写时拷贝,操作系统重新给子进程开辟新的空间并将g_val的值拷贝到开辟的新空间中,两个进程不在使用同一块物理空间,如下图:

写时拷贝:父进程/子进程尝试修改对应的数据时,操作系统会给修改的一方重新开辟一块空间并将原始数据拷贝到新空间当中

通过页表,将父子进程的数据就可以通过写时拷贝的方式进行分离,做到父子进程具有独立性!

所以就出现了地址一样但却值不一样的现象,本质上就是虚拟地址一样但是物理地址不一样

fork为什么两个返回值

使用fork时我们知道它有两个返回值,而我们还是使用同一个变量(pid_t id)去接收的,怎么做到的?

pid_t id是属于父进程栈空间定义的变量,fork内部的return被执行两次,而return是通过寄存器将返回值写入到接收返回值的变量中

当id=fork()时,先返回的进程就要先发送写时拷贝,所以同一个变量有不同的返回值,本质上就虚拟地址一致但是物理地址不一样

虚拟空间存在的意义

为什么存在虚拟地址?

  • 保护内存

直接让进程访问物理内存是不安全的,虚拟地址的存在让访问内存添加了一层软硬件层,能对转化过程进行审核,拦截非法访问。以及防止越界后进程之间互相影响或者访问到操作系统的进程导致进程崩溃

  • Linux内存管理

按需提供物理内存,并非一次性就给申请的全部空间,使用到多少空间给多少空间,通过地址空间进行功能模块的解耦,提高Linux操作系统的运行效率

  • 让进程或者程序可以以统一的视角看待内存

方便以统一的方式来编译和加载所有的可执行程序,简化进程本身的设计与实现

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/467676.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

学会笔记本电脑录屏快捷键,轻松实现录屏!

案例&#xff1a;笔记本电脑录屏有快捷键吗&#xff1f; 【我每次打开笔记本电脑录屏都要耗费比较长的时间&#xff0c;这样会影响到我录屏的效率。在这里想问一下&#xff0c;有没有快速打开电脑录屏的方法&#xff1f;】 在日常的工作、学习、娱乐中&#xff0c;我们经常需…

算法训练第一周题解汇总

A - Sort the Subarray 大意&#xff1a;在s1找一个最大的 [l&#xff0c;r] 子区间&#xff0c;使其经过从小到大的排序后 能够变成 s2 题解&#xff1a;先确定最小的区间&#xff0c;然后慢慢扩大。 最小区间的确定&#xff1a;s1和s2第一个不相等的数开始&#xff0c;到最后…

浅谈测试用例设计 | 京东云技术团队

作者&#xff1a;京东物流 王莹莹 一、测试用例为什么存在 1.1 定义 测试用例(Test Case)是指对特定的软件产品进行测试任务的描述&#xff0c;体现测试方案、方法、技术和策略。测试用例内容包括测试目标、测试环境、输入数据、测试步骤、预期结果、测试脚本等&#xff0c;…

pytorch 计算网络模型的计算量FLOPs和参数量parameter之殊途同归

计算网络模型的计算量FLOPs和参数量parameter之殊途同归 参数量方法一&#xff1a;pytorch自带方法&#xff0c;计算模型参数总量参数量方法二&#xff1a; summary的使用&#xff1a;来自于torchinfo第三方库参数量方法三&#xff1a; summary的使用&#xff1a;来自于torchsu…

controlnet1.1预处理器功能详解

ControlNet 1.1 与 ControlNet 1.0 具有完全相同的体系结构,ControlNet 1.1 包括所有以前的模型&#xff0c;具有改进的稳健性和结果质量,但增加并细化了多个模型。 今天太忙了&#xff0c;有时间就把每个模型的测试样稿发出来 2023.4.27 分类预处理器备注模型黑白倒转invert边…

数据库物理存储结构

目录 一、数据库文件和文件组 1、数据库文件 &#xff08;1&#xff09; 主数据库文件&#xff08;Primary Database File&#xff09; &#xff08;2&#xff09; 次数据库文件&#xff08;Secondary Database File&#xff09; &#xff08;3&#xff09; 事务日志文件 …

[Linux]文档搜索和归档备份

​⭐作者介绍&#xff1a;大二本科网络工程专业在读&#xff0c;持续学习Java&#xff0c;输出优质文章 ⭐作者主页&#xff1a;逐梦苍穹 ⭐所属专栏&#xff1a;Linux基础操作。本文主要是分享一些Linux系统常用操作&#xff0c;内容主要来源是学校作业&#xff0c;分享出来的…

配置zabbix自定义监控项

1.需要安装zabbix-agent服务&#xff0c;使用的zabbix版本为5.0版本 参考&#xff1a;zabbix监控linux主机_Apex Predator的博客-CSDN博客 2.创建存放脚本目录并编辑监控服务的脚本(此处监控一下服务是否存活) mkdir /opt/zabbix_jb vi /opt/zabbix_jb/service_status.sh …

【容器化应用程序设计和开发】2.2 Dockerfile 的编写和最佳实践

往期回顾&#xff1a; 第一章&#xff1a;【云原生概念和技术】 第二章&#xff1a;2.1 容器化基础知识和Docker容器 容器化应用程序设计和开发 2.2 Dockerfile 的编写和最佳实践2.2.1 Dockerfile 包含哪些指令2.2.2 Dockerfile 注意事项 在上篇章节中&#xff0c;我们介绍了…

什么是索引?MySQL索引的底层数据结构

索引(index)是帮助MySQL高效获取数据的数据结构(有序)。在数据之外&#xff0c;数据库系统还维护着满足特定查找算法的数据结构(B树)&#xff0c;这些数据结构以某种方式引用(指向)数据&#xff0c;这样就可以在这些数据结构上实现高级查找算法&#xff0c;这种数据结构就是索引…

SAP 生产订单修改记录查询

无论在项目实施过程中还是在运维的项目中,经常会遇到生产订单被修改,导致需求发生变更,这个时候用户经常就会需要要求查询生产订单的修改记录。通过SAP的标准程序是没有办法查询到生产订单修改记录,这个时候就从开发的角度去做增强的方式去实现。 1、肯定是在生产订单保存…

AI(二):初体验(Cursor、Copilot、Bito)

Cursor Cursor官网下载&#xff1a;https://www.cursor.so/ && https://github.com/getcursor/cursor Cursor.so是一款基于GPT的代码生成工具&#xff0c;它可以帮助开发者快速生成代码&#xff0c;提高开发效率。GPT是一种自然语言处理技术&#xff0c;可以根据输入…

Lambda语法解析

Lambda语法解析 一.Lambda语法1.Lambda表达式基本形式&#xff1a;2.capture list&#xff08;捕获列表&#xff09;3.捕获列表程序案例 二.Lambda应用1.使用 lambda 表达式对数组排序&#xff0c;并将排序后的元素存储到新数组中&#xff1a;2.使用 lambda 表达式计算两个矩阵…

如何安装Auto-GPT

如何安装Auto-GPT 记录一下如何安装Auto-GPT 文章目录 如何安装Auto-GPT前提克隆项目进入项目目录安装所需的依赖重命名 .env.template 文件填写API_KEY创建auto-gpt.json文件运行 Auto-GPT 前提 在安装Auto-GPT之前&#xff0c;你需要具备以下条件&#xff1a; Git环境Python环…

从历史天气预报 API 看气象大数据的商业价值

引言 近年来&#xff0c;随着气象观测技术的不断提升和气象大数据的快速发展&#xff0c;越来越多的企业开始将气象数据应用于商业领域。其中&#xff0c;历史天气预报 API 作为一种可获取历史气象数据的接口&#xff0c;具有广泛的商业应用价值。 本文将从历史天气预报 API …

数字图像处理【8】频域滤波1—关于傅里叶

这一章是数字图像处理基础的最后一章。系统的介绍傅里叶级数、傅里叶变换、离散傅里叶变换&#xff0c;快速傅里叶变换&#xff0c;以及二维傅里叶变换在图像上的应用。 变换的作用 首先我们先来聊聊什么是“变换”&#xff1f;其实在第一章介绍 HSI 颜色模型的时候&#xff0…

数据库工具——mongostat

参考文档&#xff1a;mongostat​​​​​​​​​​​ mongostat提供了当前运行的mongod或者mongos实例的大概状态。mongostat有点类似Linux的vmstat&#xff0c;但mongostat提供的是mongod或者mongos实例的信息。 从MongoDB 4.4开始&#xff0c;mongostat现在与MongoDB Ser…

第五章——动态规划1

背包问题 01背包问题 有N个物品和容量是V的背包&#xff0c;每个物品有价值vi和权重&#xff08;价值&#xff09;wi属性&#xff0c;每件物品只能用一次&#xff08;要么用0次&#xff0c;要么用1次&#xff09;&#xff0c;在背包能装得下的情况下&#xff0c;挑一部分物品装…

网络原理(四):传输层协议 TCP/UDP

目录 应用层 传输层 udp 协议 端口号 报文长度&#xff08;udp 长度&#xff09; 校验和 TCP 协议 确认应答 超时重传 链接管理 滑动窗口 流量控制 拥塞控制 延时应答 捎带应答 总结 我们第一章让我们对网络有了一个初步认识&#xff0c;第二章和第三章我们通…

bounding box线性回归

#bounding box regression原理 如图所示绿色框为飞机的Ground Truth(GT)&#xff0c;红色为提取的positive anchors&#xff0c;即便红色的框被分类器识别为飞机&#xff0c;但是由于红色的框定位不准&#xff0c;这张图相当于没有正确的检测出飞机。所以我们希望采用一种方法对…