★ Linux ★ 环境变量和程序地址空间

Ciallo~(∠・ω< )⌒☆ ~ 今天,我将和大家一起学习 linux 的环境变量和程序地址空间~

❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️

澄岚主页:椎名澄嵐-优快云博客

Linux专栏:https://blog.youkuaiyun.com/2302_80328146/category_12815302.html?spm=1001.2014.3001.5482
❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️


目录

壹  环境变量

1.1 基本概念

1.2 常见环境变量

1.3 查看环境变量方法

1.3 环境变量相关的命令

1.4 查看环境变量方法

1.4.1 通过env数组获取或设置环境变量

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

1.4.3 通过第三方变量environ获取环境变量

贰  程序地址空间

2.1 进程系统空间

2.2 虚拟地址

2.3 进程地址空间

2.4 为什么要有虚拟地址空间

2.5 一些小问题

~ 完 ~


壹  环境变量

1.1 基本概念

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

命令行参数

#include <stdio.h>
int main(int argc, char *argv[])
{
    for(int i = 0; i < argc; i++)
    {
        printf("argv[%d]: %s\n", i, argv[i]);
    }
    return 0;
}

 main的命令行参数是实现出现不同子功能的方法~ 各种指令的选项的实现原理也是这样的~如下程序所示:

int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        printf("Usage: %s [-a|-b|-c]\n", argv[0]);
        return 1;
    }
    const char *arg = argv[1];
    if(strcmp(arg, "-a") == 0)
        printf("这是功能一~\n");
    else if(strcmp(arg, "-b") == 0)
        printf("这是功能二~\n");
    else if(strcmp(arg, "-c") == 0)
        printf("这是功能三~\n");
    else
        printf("Usage: %s [-a|-b|-c]\n", argv[0]);
    return 0;
}

要执行一个程序,首先要找到他,上图就用了./code,但为什么用ls等命令时不用 ./ 呢~

因为系统中存在环境变量,来帮助我们找到二进制文件~

如上程序code如果拷贝到 /user/bin 目录下,就能不带 ./ 执行~(不推荐)

环境变量在存储的角度就是:登录时bash会读取系统相关的配置文件,形成的一张char*[]链表(环境变量表,把系统相关的配置文件中把一个个配置文件填写的链表中,bash还会接收一张命令行参数表,所以bash中会有两张表。

所以要执行一个程序,bash会通过PATH找到可执行文件

1.2 常见环境变量

  • PATH : 指定命令的搜索路径
  • HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录) 如:cd ~时bash会把~替换成HOME路径
  • SHELL : 当前Shell,它的值通常是/bin/bash
  • USER :当前用户
  • HISTSIZE : 1000(1000条历史命令)
  • HOSTNAME:当前主机名
  • SSH_TTY :当前登录的终端设备
  • TERM : 终端类型
  • SSH_CLIENT : 当前从哪个客户端登陆的
  • LANG:编码格式
  • PWD : 当前路径
  • OLDPWD :上个操作所在目录

1.3 环境变量相关的命令

  • export: 设置一个新的环境变量

  • unset: 清除环境变量

1.4 查看环境变量方法

  •  echo : 查看单个环境变量
echo $NAME //NAME:你的环境变量名称

  • set : 查看全部环境变量和本地变量
  • env : 查看全部环境变量

1.4.1 通过env数组获取或设置环境变量

#include <stdio.h>
#include <string.h>
// main最多有三个参数
int main(int argc, char *argv[], char *env[])
{
    (void)argc;
    (void)argv;

    for(int i = 0; env[i]; i++)
    {
        printf("env[%d] -> %s\n", i, env[i]);
    }
    return 0;
}

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

  • getenv

查看PATH环境变量的代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[], char *env[])
{
    (void)argc;
    (void)argv;
    (void)env;

    char *value = getenv("PATH");
    if (value == NULL)
        return 1;
    printf("PATH -> %s\n", value);
    
    return 0;
}

只有当前用户能运行的代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[], char *env[])
{
    (void)argc;
    (void)argv;
    (void)env;

    const char *who = getenv("USER");
    if(who == NULL)
        return 1;
    if(strcmp(who, "zmzz") == 0) // 自己用户名
        printf("程序正常运行~\n");
    else
        printf("Only zmzz!!!\n");
    return 0;
}

1.4.3 通过第三方变量environ获取环境变量

头文件:unistd.h

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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
extern char** environ;

int main(int argc, char *argv[], char *env[])
{
    (void)argc;
    (void)argv;
    (void)env;
    for(int i = 0; environ[i]; i++)
    {
        printf("env[%d] -> %s\n", i,  environ[i]);
    }
    return 0;
}

贰  程序地址空间

2.1 进程系统空间

也叫虚拟地址空间,是一个系统的概念,而不是一个语言层的概念。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int g_unval;
int g_val = 100;
int main(int argc, char *argv[], char *env[])
{
    const char *str = "helloworld";
    printf("code addr: %p\n", main);
    printf("init global addr: %p\n", &g_val);
    printf("uninit global addr: %p\n", &g_unval);
    static int test = 10;
    char *heap_mem = (char*)malloc(10);
    char *heap_mem1 = (char*)malloc(10);
    char *heap_mem2 = (char*)malloc(10);
    char *heap_mem3 = (char*)malloc(10);
    printf("heap addr: %p\n", heap_mem); //heap_mem(0), &heap_mem(1)
    printf("heap addr: %p\n", heap_mem1); //heap_mem(0), &heap_mem(1)
    printf("heap addr: %p\n", heap_mem2); //heap_mem(0), &heap_mem(1)
    printf("heap addr: %p\n", heap_mem3); //heap_mem(0), &heap_mem(1)
    printf("test static addr: %p\n", &test); //heap_mem(0), &heap_mem(1)
    printf("stack addr: %p\n", &heap_mem); //heap_mem(0), &heap_mem(1)
    printf("stack addr: %p\n", &heap_mem1); //heap_mem(0), &heap_mem(1)
    printf("stack addr: %p\n", &heap_mem2); //heap_mem(0), &heap_mem(1)
    printf("stack addr: %p\n", &heap_mem3); //heap_mem(0), &heap_mem(1)
    printf("read only string addr: %p\n", str);
    for(int i = 0 ;i < argc; i++)
    {
        printf("argv[%d]: %p\n", i, argv[i]);
    }
    for(int i = 0; env[i]; i++)
    {
        printf("env[%d]: %p\n", i, env[i]);
    }
    return 0;
}
code addr: 0x558ea0e90189
init global addr: 0x558ea0e93010
uninit global addr: 0x558ea0e9301c
heap addr: 0x558ea179d6b0
heap addr: 0x558ea179d6d0
heap addr: 0x558ea179d6f0
heap addr: 0x558ea179d710
test static addr: 0x558ea0e93014
stack addr: 0x7ffcec4cf850
stack addr: 0x7ffcec4cf858
stack addr: 0x7ffcec4cf860
stack addr: 0x7ffcec4cf868
read only string addr: 0x558ea0e91004
argv[0]: 0x7ffcec4d0748
env[0]: 0x7ffcec4d074f
env[1]: 0x7ffcec4d075f
env[2]: 0x7ffcec4d078a
env[3]: 0x7ffcec4d0797
env[4]: 0x7ffcec4d07ac
env[5]: 0x7ffcec4d07bb
env[6]: 0x7ffcec4d07cb
env[7]: 0x7ffcec4d07dc
env[8]: 0x7ffcec4d0dcb
env[9]: 0x7ffcec4d0e00
env[10]: 0x7ffcec4d0e22
env[11]: 0x7ffcec4d0e39
env[12]: 0x7ffcec4d0e44
env[13]: 0x7ffcec4d0e64
env[14]: 0x7ffcec4d0e6e
env[15]: 0x7ffcec4d0e85
env[16]: 0x7ffcec4d0e8d
env[17]: 0x7ffcec4d0ea2
env[18]: 0x7ffcec4d0ec1
env[19]: 0x7ffcec4d0ee4
env[20]: 0x7ffcec4d0f25
env[21]: 0x7ffcec4d0f8d
env[22]: 0x7ffcec4d0fc3
env[23]: 0x7ffcec4d0fd6
env[24]: 0x7ffcec4d0fe8

2.2 虚拟地址

 以下是一个有父子进程的程序, 有一个全局变量gval, 子进程每隔一秒使gval+1,并显示父子进程信息:

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

int gval = 100;

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        while(1)
        {
            printf("子:gval: %d, &gval: %p, pid: %d, ppid: %d\n", gval, &gval, getpid(), getppid());
            sleep(1);
            gval++;
        }
    }
    else
    {
        while(1)
        {
            printf("父:gval: %d, &gval: %p, pid: %d, ppid: %d\n", gval, &gval, getpid(), getppid());
            sleep(1);
        }
    }
}

运行后发现父子进程信息中的gval的地址一样,但gval值不一样,所以这个取地址获得的不是内存地址,是虚拟地址。

原理如下图:父进程的pcb和页表给子进程时是浅拷贝,子进程++时为了确保进程独立性,做了写时拷贝,使得一个虚拟地址对应了不同的物理地址~ 

2.3 进程地址空间

 进程地址空间就是在内核中的一个数据结构(mm_struct),对各进程的内存进行管理。

2.4 为什么要有虚拟地址空间

  1. 可以将“无序”的物理地址转化为“有序”的虚拟地址,供上层用户使用~
  2. 地址转换过程中对地址和操作进行合法性判定,保护物理内存~
  3. 缺页中断:如果代码太大,可以先不在内存中加载,用到时再暂停进程,动态加载,(页表中还有是否在物理内存中的一列)~
  4. 让进程管理和内存管理进行一定程度的解耦合~

2.5 一些小问题

1.  堆不止一个起始地址,mm_struct 怎么管理:

会有一个链表,多段维护, 一个堆区对应一个vm_area_struct

2. 创建进程,先有task_struct,mm_struct等,还是先加载代码和数据?

先有pcb,可以不加载代码和数据,只有task_struct,mm_struct和页表

3. 如何理解进程挂起?

发现内存空间不足时,会清空页表,把物理内存中的数据换出到磁盘的一个特定空间里,内存就空出来了,这就体现了解耦合


~ 完 ~

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值