前言
上期的分享让我们知道进程大概的模样,本期一样重要,能学到操作系统设计的优美,体会到前辈们的智慧。
#环境变量
是什么
OS提供,往往有特殊功能的全局变量(/etc/profile.d是设置环境变量的目录)
[bacon@VM-12-5-centos ~]$ cd /etc/profile.d
[bacon@VM-12-5-centos profile.d]$ ll
total 80
-rw-r--r--  1 root root  771 Nov 17  2020 256term.csh
-rw-r--r--  1 root root  841 Nov 17  2020 256term.sh
-rw-r--r--  1 root root 1348 Oct  2  2020 abrt-console-notification.sh
-rw-r--r--  1 root root  660 Apr  1  2020 bash_completion.sh
-rw-r--r--. 1 root root  196 Mar 25  2017 colorgrep.csh
-rw-r--r--. 1 root root  201 Mar 25  2017 colorgrep.sh
-rw-r--r--  1 root root 1741 Nov 16  2020 colorls.csh
-rw-r--r--  1 root root 1606 Nov 16  2020 colorls.sh
-rw-r--r--  1 root root   80 Apr  1  2020 csh.local
-rw-r--r--  1 root root 1706 Nov 17  2020 lang.csh
-rw-r--r--  1 root root 2703 Nov 17  2020 lang.sh
-rw-r--r--. 1 root root  123 Jul 31  2015 less.csh
-rw-r--r--. 1 root root  121 Jul 31  2015 less.sh
-rwxr-xr-x  1 root root  772 Nov 15  2021 mpi-selector.csh
-rwxr-xr-x  1 root root  743 Nov 15  2021 mpi-selector.sh
-rw-r--r--  1 root root   81 Apr  1  2020 sh.local
-rw-r--r--  1 root root  105 Dec 16  2020 vim.csh
-rw-r--r--  1 root root  269 Dec 16  2020 vim.sh
-rw-r--r--. 1 root root  164 Jan 28  2014 which2.csh
-rw-r--r--. 1 root root  169 Jan 28  2014 which2.sh
[bacon@VM-12-5-centos profile.d]$ cat 256term.sh
# Enable 256 color capabilities for appropriate terminals
# Set this variable in your local shell config (such as ~/.bashrc)
# if you want remote xterms connecting to this system, to be sent 256 colors.
# This must be set before reading global initialization such as /etc/bashrc.
#   SEND_256_COLORS_TO_REMOTE=1
# Terminals with any of the following set, support 256 colors (and are local)
local256="$COLORTERM$XTERM_VERSION$ROXTERM_ID$KONSOLE_DBUS_SESSION"
if [ -n "$local256" ] || [ -n "$SEND_256_COLORS_TO_REMOTE" ]; then
  case "$TERM" in
    'xterm') TERM=xterm-256color;;
    'screen') TERM=screen-256color;;
    'Eterm') TERM=Eterm-256color;;
  esac
  export TERM
  if [ -n "$TERMCAP" ] && [ "$TERM" = "screen-256color" ]; then
    TERMCAP=$(echo "$TERMCAP" | sed -e 's/Co#8/Co#256/g')
    export TERMCAP
  fi
fi
unset local256
具体常见的有:
HOME
PATH
SHELL
USER
#...
若要获取某个环境变量的内容如获取PATH的内容:$PATH
[bacon@VM-12-5-centos 8]$ echo $PATH
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/bacon/.local/bin:/home/bacon/bin:/home/bacon/linux/4-process/8
环境变量的组织方式

环境变量的加载
在用户的工作目录~下,有.bashrc和.bash_profile,bash启动时通过它俩加载环境变量。
[bacon@VM-12-5-centos ~]$ pwd
/home/bacon
[bacon@VM-12-5-centos ~]$ cat .bashrc
# .bashrc
# Source global definitions
if [ -f /etc/bashrc ]; then
	. /etc/bashrc
fi
# Uncomment the following line if you don't like systemctl's auto-paging feature:
# export SYSTEMD_PAGER=
# User specific aliases and functions
alias vim='/home/bacon/.VimForCpp/nvim'
alias vim='/home/bacon/.VimForCpp/nvim'
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:~/.VimForCpp/vim/bundle/YCM.so/el7.x86_64
alias vim='/home/bacon/.VimForCpp/nvim'
[bacon@VM-12-5-centos ~]$ cat .bash_profile
# .bash_profile
# Get the aliases and functions
if [ -f ~/.bashrc ]; then
	. ~/.bashrc
fi
# User specific environment and startup programs
PATH=$PATH:$HOME/.local/bin:$HOME/bin
export PATH
有哪些
*命令行变量(局部)
[bacon@VM-12-5-centos 11]$ var=1024 
[bacon@VM-12-5-centos 11]$ echo $var
1024
命令行可以定义局部普通变量,只在当前进程有效(bash)
PATH
:标识指令路径。(默认指定/usr/bin)
功能:提供一串或多串路径给系统使用。
构成:/a/b/c:/d/e/f:/g/h/i(先找a/b/c,再找d/e/f,最后g/h/i)

读者们或许有过这样的疑惑:系统的可执行程序(指令),和我的可执行程序都是可执行,系统的可执行写出文件名就可以直接执行,而我的可执行就得指定路径。
【我自己的可执行程序为什么要./fileName指定路径?】
 
要执行一个可执行程序,需要先找到它的位置。
当系统的可执行程序要执行,能且会根据PATH这个环境变量,到/usr/bin下找。
当我们自己的可执行程序要执行,不会根据PATH来找文件位置,需要自己指明路径。
【我自己的可执行程序怎么能直接执行?】
-  拷贝到 /usr/bin下,也就是PATH默认指定的路径(但这样不太好,我们的指令并没有经过严格测试,可能有BUG,更可能污染指令池) 
-  给PATH追加另外的路径(我们希望直接执行的程序的路径) [bacon@VM-12-5-centos 8]$ ls myHello myHello.c [bacon@VM-12-5-centos 8]$ myHello -bash: myHello: command not found [bacon@VM-12-5-centos 8]$ pwd /home/bacon/linux/4-process/8 [bacon@VM-12-5-centos 8]$ export PATH=$PATH:/home/bacon/linux/4-process/8 #export导入原来的环境变量 [bacon@VM-12-5-centos 8]$ myHello myHello:hello hello hello!如果没有$PATH来获取原来的环境变量再追加,就会覆盖原PATH,导致大部分系统指令不可用。不过这里的PATH是内存级的(每次bash启动时加载到内存),重启即可重置。 
PWD
:当前路径
功能:提供当前路径给系统使用
[bacon@VM-12-5-centos ~]$ echo $PWD
/home/bacon
如ls指令显示当前路径下的内容,怎么知道当前路经是什么?
[bacon@VM-12-5-centos 12]$ ls
makefile  mycmd  mycmd.c
[bacon@VM-12-5-centos 12]$ env | grep PWD
OLDPWD=/home/bacon/linux/4-process
PWD=/home/bacon/linux/4-process/12
就是依靠PWD环境变量。
HOME
:家目录
[bacon@VM-12-5-centos ~]$ echo $HOME
/home/bacon
…
特性
我们使用中发现,bash的子进程,都可以使用bash启动时加载的环境变量:
- 环境变量具有全局属性(子进程可以从父进程继承环境变量,应对不同场景,如USER确定身份)
- 本地变量只在当前进程有效
怎么操作
env:查看所有环境变量
[bacon@VM-12-5-centos ~]$ env
XDG_SESSION_ID=76445
HOSTNAME=VM-12-5-centos
TERM=xterm
SHELL=/bin/bash
HISTSIZE=3000
SSH_CLIENT=113.90.31.45 51003 22
SSH_TTY=/dev/pts/0
USER=bacon
#...
$[环境变量名]:获取环境变量的值
[bacon@VM-12-5-centos ~]$ echo $PATH
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/bacon/.local/bin:/home/bacon/bin
getenv:获取环境变量
#include <stdlib.h> 
char *getenv(const char *name);
The getenv() function searches the environment list to find the environment variable name, and returns a pointer to the corresponding value string.
可以实现一个简单的身份验证:
#define USER "USER"
int main()
{
    const char* who = getenv(USER);
    if(strcmp(who, "root") == 0)
    {
        printf("%s\n", who);
    }
    else 
    {
        printf("permisson denied!\n");
    }
    return 0;
}
[bacon@VM-12-5-centos 12]$ ./mycmd 
permisson denied!
[bacon@VM-12-5-centos 12]$ sudo ./mycmd 
root
sudo的本质其实就是把环境变量USERNAME改成root。
export [环境变量名]:查看、新增、修改或删除环境变量(此次登陆有效)
[bacon@VM-12-5-centos 12]$ export MYENV="hello ENV"
[bacon@VM-12-5-centos 12]$ export | grep MYENV
declare -x MYENV="hello ENV"
[bacon@VM-12-5-centos 12]$ env | grep MYENV
MYENV=hello ENV
成功创建环境变量。
set:显示本地变量和环境变量
[bacon@VM-12-5-centos 12]$ MYENV=123123
[bacon@VM-12-5-centos 12]$ env | grep MYENV #env查不到本地变量
[bacon@VM-12-5-centos 12]$ set | grep MYENV #set可以
MYENV=123123
unset:清除环境变量
[bacon@VM-12-5-centos 12]$ MYENV_=456456
[bacon@VM-12-5-centos 12]$ export MYENV_
[bacon@VM-12-5-centos 12]$ env | grep MYENV_
MYENV_=456456
[bacon@VM-12-5-centos 12]$ unset MYENV_
[bacon@VM-12-5-centos 12]$ env | grep MYENV_
#通过代码获取环境变量
1、通过命令行参数获取
读者朋友们有没有见过main函数的参数呢?现在需要拿出来玩玩了!
int main(int argc, char* argv[], char* env) { ... }
我们先看看前两个关于命令行参数的参数
argc = argument count 参数个数
argv = argument vector 参数数组
int main(int argc, char* argv[])
{
    int i = 0;
    for(i=0; i < argc; ++i)
    {
        printf("argv[%d] = %s\n", i, argv[i]);
    }
    return 0;
}
[bacon@VM-12-5-centos 13]$ ./mycmd 
argv[0] = ./mycmd
[bacon@VM-12-5-centos 13]$ ./mycmd -a
argv[0] = ./mycmd
argv[1] = -a
[bacon@VM-12-5-centos 13]$ ./mycmd -a -b
argv[0] = ./mycmd
argv[1] = -a
argv[2] = -b
看完也能知道:
命令行操作就是一个字符串,命令行解释器在进行解析的时候,会将其从左到右按空格分割,成为 “指令”、“参数”等。
即
"ls -a -b -c -d -e"
=
"ls" "-a" "-b" "-c" "-d" "-e"

命令行参数的意义是什么呢?
//./mycmd -a/-b/-c
int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        printf("Usage:\n\t%s[-a/-b/-c]\n", argv[0]);
        return 1;
    }
    if(strcmp("-a", argv[1]) == 0)
        printf("fuction a...\n");
    if(strcmp("-b", argv[1]) == 0)
        printf("fuction b...\n");
    if(strcmp("-c", argv[1]) == 0)
        printf("fuction c...\n");
    return 0;
}
[bacon@VM-12-5-centos 13]$ ./mycmd 
Usage:
	./mycmd[-a/-b/-c]
[bacon@VM-12-5-centos 13]$ ./mycmd -a
fuction a...
[bacon@VM-12-5-centos 13]$ ./mycmd -b
fuction b...
[bacon@VM-12-5-centos 13]$ ./mycmd -c
fuction c...
命令行参数的意义:通过传入的参数,执行不同的功能。
main的参数除了以上两个,还有env,环境变量。
*环境变量其实就是字符串,如PATH=/usr/bin,等号左边的PATH就是变量名,右边的/usr/bin就是值

int main(int argc, char* argv[], char* env[])
{
    int i = 0;
    for(i = 0; env[i]; ++i)
    {
        printf("env[%d] = %s\n", i, env[i]);
    }
  	return 0;
}
[bacon@VM-12-5-centos 13]$ ./mycmd 
env[0] = XDG_SESSION_ID=84774
env[1] = HOSTNAME=VM-12-5-centos
env[2] = TERM=xterm
env[3] = SHELL=/bin/bash
env[4] = HISTSIZE=3000
#...
还有一点需要注意:以上的命令行参数表和环境变量表,不管有没有手动传参,都会生成。
2、environ
#include <unistd.h>
extern char **environ; //全局二级指针
environ 指向 char* env[]
int main()
{
    extern char** environ;
    int i = 0;
    for(i = 0; environ[i]; ++i)
    {
        printf("*(environ+%d) = %s\n", i, environ[i]);
    }
    return 0;
}
[bacon@VM-12-5-centos 13]$ ./mycmd 
*(environ+0) = XDG_SESSION_ID=84774
*(environ+1) = HOSTNAME=VM-12-5-centos
*(environ+2) = TERM=xterm
*(environ+3) = SHELL=/bin/bash
*(environ+4) = HISTSIZE=3000
可以看到用environ和char* env[]打印的结果一模一样。
所以,获取环境变量一般有三种方式: getenv 、char* env[]、extern char** environ。
最推荐getenv,按需获取,其他两种方式获取完还要做字符串解析,不方便。
#有趣的现象
[bacon@VM-12-5-centos 13]$ myval=8848
[bacon@VM-12-5-centos 13]$ env | grep myval
[bacon@VM-12-5-centos 13]$ set | grep myval
myval=8848
[bacon@VM-12-5-centos 13]$ echo $myval
8848
我们知道,环境变量是具有全局属性的,但myval是本地变量,不能被echo这个子进程继承,那是如何打印出结果的?
按下不表,回头解答。
#进程地址空间(重要)
学习C语言的时候,我们浅浅了解过C/C++内存分布,其实也叫做进程地址空间:

验证:
int uninit_var;
int init_var = 100;
int main(int argc, char* argv[], char* env[])
{
    //环境变量
    printf("%-15s = %p\n", "env", getenv("PATH"));
    //命令行参数
    printf("%-15s = %p\n", "cmdLine arg", &argv[0]);
    //栈
    int var_first = 0;
    int var_second = 0;
    printf("%-15s = %p\n", "stack_first", &var_first);
    printf("%-15s = %p\n", "stack_second", &var_second);
    
    //堆
    int* p = (int*)malloc(sizeof(int));
    printf("%-15s = %p\n", "heap", p);
    //未初始化数据
    printf("%-15s = %p\n", "uninitData", &uninit_var);
    //已初始化数据
    printf("%-15s = %p\n", "initData", &init_var);
    //代码段
    printf("%-15s = %p\n", "code", main);
    return 0;
}
[bacon@VM-12-5-centos 15]$ ./mycmd 
env             = 0x7ffe21898e14
cmdLine arg     = 0x7ffe218978e8
stack_first     = 0x7ffe218977f4
stack_second    = 0x7ffe218977f0
heap            = 0x1c7b010
uninitData      = 0x60104c
initData        = 0x601044
code            = 0x4005cd

*等下的讲解中,我们将对地址空间进行部分简化来降低学习成本。
之前我们认为这就是内存,内存就是这。其实不然,这是“虚拟内存”。来看一个现象。
int global_var = 100;
int main()
{
    pid_t id = fork();
    assert(id >= 0);
    if(id > 0)
    {
        while(1)
        {
            printf("PARENT process:id=%d\tpid=%d\tppid=%d\t|\tglobal_var=%d\t&global_var=%p\n", id, getpid(), getppid(), global_var, &global_var);
            sleep(1);
        }
    }
    else
    {
        int cnt = 0;
        while(1)
        {
            printf("CHILD  process:id=%d\tpid=%d\tppid=%d\t|\tglobal_var=%d\t&global_var=%p\n", id, getpid(), getppid(), global_var, &global_var);
            sleep(1);
            if(cnt == 3)
            {
                global_var = 999;
                printf("global_var changed to 999!\n");
            }
            ++cnt;
        }
    }
    return 0;
}
[bacon@VM-12-5-centos 14]$ ./mycmd 
PARENT process:id=8414		pid=8413	ppid=2621	|	global_var=100	&global_var=0x601064
CHILD  process:id=0			pid=8414	ppid=8413	|	global_var=100	&global_var=0x601064
PARENT process:id=8414		pid=8413	ppid=2621	|	global_var=100	&global_var=0x601064
CHILD  process:id=0			pid=8414	ppid=8413	|	global_var=100	&global_var=0x601064
PARENT process:id=8414		pid=8413	ppid=2621	|	global_var=100	&global_var=0x601064
CHILD  process:id=0			pid=8414	ppid=8413	|	global_var=100	&global_var=0x601064
PARENT process:id=8414		pid=8413	ppid=2621	|	global_var=100	&global_var=0x601064
CHILD  process:id=0			pid=8414	ppid=8413	|	global_var=100	&global_var=0x601064
PARENT process:id=8414		pid=8413	ppid=2621	|	global_var=100	&global_var=0x601064
global_var changed to 999!
CHILD  process:id=0			pid=8414	ppid=8413	|	global_var=999	&global_var=0x601064
PARENT process:id=8414		pid=8413	ppid=2621	|	global_var=100	&global_var=0x601064
CHILD  process:id=0			pid=8414	ppid=8413	|	global_var=999	&global_var=0x601064
PARENT process:id=8414		pid=8413	ppid=2621	|	global_var=100	&global_var=0x601064
CHILD  process:id=0			pid=8414	ppid=8413	|	global_var=999	&global_var=0x601064
PARENT process:id=8414		pid=8413	ppid=2621	|	global_var=100	&global_var=0x601064
CHILD  process:id=0			pid=8414	ppid=8413	|	global_var=999	&global_var=0x601064

相同的地址,代表父子进程打印的是同一个变量。但同一个变量居然有不同的值?
那我们能推导:这里打印的地址绝对不是物理地址 ==> 曾经学的语言层面的地址(指针),不是物理地址。
这里的地址是 虚拟地址,以前学的内存分布其实叫做 进程地址空间。
要把这个现象搞清楚,我们得先学习进程地址空间。
感性理解
其实操作系统给进程画了一张大饼。怎么理解呢?
大富翁张三有10个亿的资产和三个私生子张小一、张小二、张小三,且三个儿子互不知道对方的存在。
张小一是搞工程的,张小二是搞投资的,张小三还在国外念书。张三跟每个孩子都是:“孩子啊,好好工作,爸就你一个孩子,那10个亿的资产迟早是你的。”
张小一安心了,专心搞工程,偶尔工程需要资金周转,会找张三要个几万几十万;张小二也安心,专心研究自己的投资,偶尔需要了,也会找张三要个几万;张小三也很安心,乖乖念书,正常开销。
就这样,张三实际上花不了多少钱,却也能让儿子们安心工作学习。孩子们都以为自己有10个亿,其实没有。
着眼进程,操作系统给进程们画了张大饼:**操作系统告诉进程它独占系统资源。**进程就安心了,跑自己的程序,偶尔请求点资源。
操作系统画的这块大饼,是进程看到的地址空间,也叫做进程地址空间。

*简化后的地址空间和内存
每个进程都以为自己不愁吃穿家财万贯,不过只是不愁吃穿,家里有多少钱自己都搞不清。
给进程创建的地址空间多了,要不要被操作系统管理起来呢?要的。一样的四步走:抽象、具象、组织和操作。
地址空间的抽象:mm_struct。
地址空间的抽象,必然包含了区域的划分(栈、堆和代码段等),所谓划分其实就是画了根三八线,怎么理解?
通过小胖和小美的例子来理解:
小胖和小美是一男一女两个小学生,互为同桌。但小胖体型较宽,又坐不住,手就老是伸到了小美的桌子上,很打扰小美的学习,小美就提出要画三八线。

这时候可以这样描述桌子…
伪代码:
struct disk
{
  	size_t boy_start;
  	size_t boy_finish;
  	size_t girl_start;
  	size_t girl_finish;
}
struct disk = {1, 50, 51, 100};
但小胖还是一直越界,小美忍了一段时间终于忍不下去了,于是大发雷霆,跟小胖重新画线:

伪代码:
disk->boy_finish = 35;
disk->girl_start = 36;
划分好了以后,怎么用就是自己的事了,小胖可以在[1,5]放铅笔,[7,11]放红领巾。

尺子上的每一个刻度,就代表桌子上的一个位置,地址就是这么回事,就是给一个区域划分的。
其实内存的划分和调整,和这个三八线的划分调整一样。比如动态变化的栈和堆,变化的本质其实就是调整规定区域的数字stack_finish/heap_finish。
伪代码:
struct mm_struct
{
		unsigned int code_start, code_finish; //代码段
  	unsigned int data_start, data_finish; //数据段
		unsigned int heap_start, heap_finish; //堆区
  	unsigned int stack_start, stack_finish; //栈区
  	//...
}
mm_struct也是task_struct中的一个成员,新建一个进程时,也需要给它创建进程地址空间
不信看看源码:
struct task_struct {
	volatile long state;	/* -1 unrunnable, 0 runnable, >0 stopped */
	void *stack;
	atomic_t usage;
	unsigned int flags;	/* per process flags, defined below */
	unsigned int ptrace;
	int lock_depth;		/* BKL lock depth */
  
	//...
	
  //指向为进程创建的地址空间
	struct mm_struct *mm, *active_mm;
  
  //...
}
struct mm_struct {
	struct vm_area_struct * mmap;		/* list of VMAs */
	struct rb_root mm_rb;
	//...
  
  //区域划分
	unsigned long total_vm, locked_vm, shared_vm, exec_vm;
	unsigned long stack_vm, reserved_vm, def_flags, nr_ptes;
	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;
  
  //...
 
}
所以,创建进程地址空间很像是这样的…
伪代码:
struct task_struct task1;
struct mm_struct* pointer = (struct mm_struct*)malloc(sizeof(struct mm_struct));
pointer->code_start = 0x1000;
pointer->code_finish = 0x1100;
pointer->data_start = 0x1200;
pointer->data_finish = 0x1300;
task1->mm = pointer;
//...
进程的视角:嗯…代码段数据段,各区域都很齐全!安心跑我的。
操作系统的视角:这个进程运行用不了多少资源,这一小块用来画大饼都足够了。
进程以为自己独占资源,真正占多少资源还是操作系统决定的。
感性理解进程地址空间:操作系统给进程画大饼,有限的内存给每个进程都划分自己的代码段、数据段等。
理性理解
有了上面的了解,大概能理清一些操作系统软件层面的内存和硬件层面的物理内存

*简化后的地址空间、物理内存和磁盘
软件和硬件之间,到底怎么转换的,虚拟内存和物理内存又有什么联系?
对待物理内存,我们还能更进一步了解:

-  内存和磁盘之间的读写成为 IO操作(input output) -  每次IO的基本单位是4KB 
-  每个4KB又叫做 page 页 因此我们可以将内存看作一个元素为page的数组,对于32位机器,有2^32个地址,能表示4GB的空间,这个数组的大小就是 4GB/4KB 
 
-  
有了这层理解,我再告诉你,虚拟地址和物理地址通过页表映射。

先不用太关心页表到底是个啥,很复杂,我们目前只需要知道:
变量在内存中的地址,都是虚拟地址,但变量本身一定是存在物理地址上,二者通过页表的映射建立联系。
这样一来,我们就可以通过虚拟地址访问物理内存了。抽象后的虚拟地址,因其连续性,也叫线性地址。我们的数组得益于此,能够线性存储,达到高效访问。
理性理解进程地址空间:将物理内存抽象,得到的虚拟地址,和物理地址通过页表映射建立联系。
为什么
你讲了这么多,我们到底要这么多复杂的东西干嘛,为什么要区分出虚拟地址和物理地址,这有什么好处吗?
【不区分,如果进程越界非法操作呢?】

这岂不是完蛋了嘛。
那虚拟地址怎么解决这个问题的?通过压岁钱例子来理解:
7岁的张三过年收到很多压岁钱,父母不管他,让他开心花,最后花了很多冤枉钱买了一堆可能根本就不喜欢的东西,张三开心两天也觉得没意思了。
张三的父母发现这个问题……决定下一年帮他保管试试看。
又过年了,8岁的张三还是收到很多压岁钱,但父母会帮他保管,想买什么了,跟父母一说,合理就买,不合理就不买。最后这钱花了很久,买的都是有意思的东西,张三也开心了很久。
“不合理就不买”,本质上就是一种保护,页表也这么做:当你想访问的空间非法,就拒绝你。
我们总写的野指针,越界访问,都没让我们的操作系统崩溃,就是有页表的保护。
原因一:越界非法操作不安全。
再来解决一下遗留问题:相同的地址,不同的值。


这不对劲了啊,如果子进程里面想 global_var = 999,就会影响到父进程看到的global_var,加入父进程里面有这样的代码:
if(global_var == 100) 
{
		//...
}
就会被彻底影响——进程独立性不存在了!
为了保证进程独立性,若被多个进程共享的数据要被修改,会进行一种操作:写时拷贝。
即,在物理内存上找一块空间把数据做一份拷贝,这一块物理内存的地址通过页表的映射,虚拟地址和原数据的一模一样。(页表太厉害啦!)

这也能解释为什么同一地址会有不同值:原数据在物理内存的另一块空间上被拷贝了一份,但新空间的虚拟地址通过页表映射后,和原数据的虚拟地址相同,本质他们已经是两块不同的空间了!
原因二:对进程的代码和数据进行解耦,更好地保证进程独立性。
最后一个原因不太好理解。
想一个问题:磁盘里的可执行程序里面,有没有地址呢?或者说需不需要地址这种东西的存在呢?
有的,需要的。我们以前看汇编,是有地址的,比如编译的最后一步,链接。链接本质到底是在做什么?说白了就是把“外部符号”都变成“已知符号”,把原本无意义的占位地址替换成符号对应的真正的地址。
也就意味着,程序没有加载到内存的时候,内部就早已经有地址啦!
而且,进程地址空间的一套区域划分规则,不是只有操作系统在用的!!程序编译的时候也会用!!
所以,所谓链接时“真正的地址”又是什么呢?就是编译器根据进程地址空间的划分规则编出来的地址,也叫逻辑地址。

当sort.exe加载进内存,这些地址根本不用变!
- sort.exe载入,页表为其各个虚拟地址建立映射,和物理地址联系起来
同时
- 代码段可以通过main的地址确定code_start(因为main是程序的入口);也可以通过程序的结束地址确定code_finish(是程序的结束)

因此,程序内的地址(逻辑地址/虚拟地址)载入内存后,CPU拿来就能用。
而且,物理内存中的 main()、quickSort()、 arr[]都具有两套地址,一套是载入内存天然具有的物理地址,一套是编译时的逻辑地址(虚拟地址)。CPU在执行指令的过程中,连物理地址的毛都见不到一根。
原因三:共用进程地址空间的内存划分规则,统一标准,能提高效率。
为什么把物理内存抽象成虚拟内存再映射?
- 防止越界非法操作
- 对进程共享的代码和数据进行解耦,保证进程独立性
- 共用进程地址空间的内存划分规则,统一标准,能提高效率
以上就是进程概念的全部内容,能算是学习Linux“要翻越的第一座大山”了。
今天的分享就到这里
这里是培根的blog,期待与你共同进步!
下期见~



















