一、环境变量
基本概念
环境变量(Environment Variables)是操作系统提供的一种机制,用于存储和传递配置信息、系统参数、用户偏好设置等。
环境变量的作用
常见的环境变量
查看环境变量
echo $NAME 其中NAME为你要查找的环境变量名称,比如PATH,USER等
可执行文件的默认执行路径
我们先写一个简单程序
#include <stdio.h>
int main()
{
printf("hello world");
return 0;
}
对上面的源代码编译形成一个可执行文件 hello ,当我们在命令行输入 hello 来执行这个可执行文件时候发现执行不了,报错 Command 'hello' not found 这是为什么呢? 其实在 Linux 和类 Unix 系统中,可执行文件的默认执行路径是由环境变量 PATH 决定的。PATH 环境变量是一个以冒号(:)分隔的目录列表,操作系统会在这些目录中按顺序查找可执行文件,而 hello 这个命令我们默认执行路径也就是PATH指定的路径,而不是当前路径。 解决方法:
export PATH=$PATH:你的可执行文件所处路径
然后再执行 source ~/.bashrc 是配置文件生效
ln –s /path/to/your/program/your_executable /usr/local/bin/your_executable
如果在 /usr/local/bin 中找到了符号链接 your_executable,操作系统会解析这个符号链接,找到它指向的实际文件 /path/to/your/program/your_executable,并运行该文件。
alias myprogram='/path/to/your/program/your_executable'
将上述命令添加到你的 shell 配置文件中(如 ~/.bashrc 或 ~/.zshrc),以永久生效。
环境变量相关命令
- export : 在全局环境变量表中设置一个全新的环境变量
- env : 显示所有环境变量
- unset : 清楚环境变量
- set : 显示本地定义的shell变量和环境变量
环境变量的组织方式
每个程序都有一个环境变量表,环境变量表是一个字符指针数组,每个指针指向一个以‘ \\0 ’ 结尾的环境字符串
代码获取环境变量
- 命令⾏第三个参数
#include <stdio.h>
int main(int argc, char *argv[], char *env[])
{
int i = 0;
for(; env[i]; i++)
{
printf("%s\\n", env[i]);
}
return 0;
}
- 通过 extern 第三方变量 environ 获取
#include <stdio.h>
int main(int argc, char *argv[])
{
extern char **environ;
int i = 0;
for(; environ[i]; i++)
{
printf("%s\\n", environ[i]);
}
return 0;
}
通过系统调用获取
通过系统调用 getenv(name) 来获取环境变量,其实现我们可以通过 man 手册来查找(man getenv)
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("%s\\n", getenv("PATH"));
return 0;
}
子进程继承环境变量
- 环境变量通常具有全局属性,可以被子进程继承下去。
代码演示
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("%s\\n", getenv("PATH"));
return 0;
}
由我们在编译上面代码形成可执行程序,在运行时就会帮我们打印 PATH 环境变量可知,环境变量是会被子进程继承的(注意: 任何在命令行执行的可执行文件都是由 shell 进行 fork() 创建的子进程,在通过 进程切换 执行的命令,所以上面可进程就是 shell 的子进程)
程序地址空间
程序地址空间布局: 但上面的地址空间布局就是物理地址空间布局吗,答案是不是的。 我们可以通过一段程序验证
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int g_val = 0;
int main()
{
pid_t id = fork();
if(id < 0)
{
perror("fork");
return 0;
}
else if(id == 0)
{ //child
printf("child pid %d : %d : %p\\n", getpid(), g_val, &g_val);
}
else
{ //parent
printf("parent pid %d : %d : %p\\n", getpid(), g_val, &g_val);
}
sleep(1);
return 0;
}
输出结果:
parent pid 4635 : 0 : 0x56436823f014
child pid 4636 : 0 : 0x56436823f014
我们可以看见父子进程的 gval 的值是不一样的,但地址确实一样的,我们就可以得出一下结论:
- 我们看到的地址值绝对不是物理地址,而是虚拟地址,即我们在C/C++语言中看到的地址都是虚拟地址!而不是物理地址,物理地址用户是看不到的,统一由操作系统管理
- OS必须负责虚拟地址带物理地址的转换
进程地址空间
由上面可知 程序地址空间 的说法不准确,那应该为什么呢,准确来说应该为 进程地址空间
虚拟地址转换为物理地址
通过这个我们就可以看出,同一个变量地址相同,内容不同,其实是虚拟地址空间相同,但是物理内存上的地址其实是不同的。 也就是相当于操作系统给编译器画了一张大饼,告诉编译器这里的空间你全部可以用,但其是并不是全部可以用,你申请空间时 操作系统 就会看你申请的空间物理内存是否足够容纳的下,如果容纳不下,操作系统就不会帮你申请,直接打回你的申请,就像你兄弟告诉你我有100块,你说给我200块,你兄弟说不行一样
浅谈虚拟内存管理
那么既然有虚拟内存,那么操作系统就需要对虚拟内存管理,那操作系统是怎么对虚拟内存管理的呢? 和进程一样,也是先描述,再组织
描述
每一个进程都有一个 task_struct ,在 task_struct 中就有一个描述进程地址空间的结构体 mm_struct (内存描述符)
struct task_struct
{
/*…*/
struct mm_struct *mm; //对于普通的⽤⼾进程来说该字段指向他的
//虚拟地址空间的⽤⼾空间部分,对于内核线程来说这部分为NULL。
struct mm_struct *active_mm; // 该字段是内核线程使⽤的。当该进程是内核线程时,
//它的mm字段为NULL,表⽰没有内存地址空间,可也并不是真正的没有,这是因为所有进程关
//于内核的映射都是⼀样的,内核线程可以使⽤任意进程的地址空间。
/*…*/
}
struct mm_struct
{
/*…*/
struct vm_area_struct *mmap;
/* 指向虚拟区间(VMA)链表 */
struct rb_root mm_rb;
/* red_black树 */
unsigned long task_size;
/*具有该结构体的进程的虚拟地址空间的⼤⼩*/
/*…*/
// 代码段、数据段、堆栈段、参数段及环境段的起始和结束地址。
unsigned long start_code, end_code, start_data, end_data;
unsigned long start_brk, brk, start_stack;
unsigned long arg_start, arg_end, env_start, env_end;
/*…*/
}
每一个进程都有独立的 mm_struct ,这样保证了进程之间的独立性。
组织
虚拟地址组织方式有两种:
linux内核使用 vm_area_struct 结构来表示一个独立的虚拟内存区域(VMA),一个进程使用多个 vm_area_struct 来表示不同位置的虚拟内存区域。
struct vm_area_struct {
unsigned long vm_start; //虚存区起始
unsigned long vm_end;
//虚存区结束
struct vm_area_struct* vm_next, * vm_prev;
//前后指针
struct rb_node vm_rb;
//红⿊树中的位置
unsigned long rb_subtree_gap;
struct mm_struct* vm_mm;
//所属的 mm_struct
pgprot_t vm_page_prot;
unsigned long vm_flags;
//标志位
struct {
struct rb_node rb;
unsigned long rb_subtree_last;
} shared;
struct list_head anon_vma_chain;
struct anon_vma* anon_vma;
const struct vm_operations_struct* vm_ops; //vma对应的实际操作
unsigned long vm_pgoff;
//⽂件映射偏移量
struct file* vm_file;
//映射的⽂件
void* vm_private_data;
//私有数据
atomic_long_t swap_readahead_info;
#ifndef CONFIG_MMU
struct vm_region* vm_region;
/* NOMMU mapping region */
#endif
#ifdef CONFIG_NUMA
struct mempolicy* vm_policy;
/* NUMA policy for the VMA */
#endif
struct vm_userfaultfd_ctx vm_userfaultfd_ctx;
} __randomize_layout;
通过上面的描述,我们可以用图来表示以上结构:
评论前必须登录!
注册