【Linux04-进程概念下】不愧是操作系统,优美的设计!

news2025/1/18 4:44:01

前言

上期的分享让我们知道进程大概的模样,本期一样重要,能学到操作系统设计的优美,体会到前辈们的智慧。


#环境变量

是什么

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_profilebash启动时通过它俩加载环境变量。

[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来找文件位置,需要自己指明路径

【我自己的可执行程序怎么能直接执行?】
  1. 拷贝到 /usr/bin 下,也就是PATH默认指定的路径

    (但这样不太好,我们的指令并没有经过严格测试,可能有BUG,更可能污染指令池)

  2. 给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

可以看到用environchar* env[]打印的结果一模一样。


所以,获取环境变量一般有三种方式: getenvchar* 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在执行指令的过程中,连物理地址的毛都见不到一根。

原因三:共用进程地址空间的内存划分规则,统一标准,能提高效率。

为什么把物理内存抽象成虚拟内存再映射?

  1. 防止越界非法操作
  2. 对进程共享的代码和数据进行解耦,保证进程独立性
  3. 共用进程地址空间的内存划分规则,统一标准,能提高效率

以上就是进程概念的全部内容,能算是学习Linux“要翻越的第一座大山”了。


今天的分享就到这里

这里是培根的blog,期待与你共同进步!

下期见~

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

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

相关文章

React 入门:实战案例 Github搜索_axios发送请求

文章目录快速搭建 API 服务器在 Search 组件中实现 Axios 发送请求在 App 组件中管理 List 组件的用户列表状态在 List 组件中更新渲染用户列表数据优化完善完整源码最终效果&#xff1a;快速搭建 API 服务器 根据下面步骤来操作&#xff0c;就可以快速搭建一个符合本案例使用…

明年跨境电商外贸的新增量在哪里?来自专家的2个判断

明年跨境电商外贸的新增量在哪里&#xff1f;来自专家的2个判断2022年&#xff0c;外贸大环境不容易&#xff0c;外贸人也不容易。自2021年9月以来&#xff0c;海运费飙涨&#xff0c;库容一降再降。大批的货品滞留库存&#xff0c;部分卖家只好硬着头皮扛下来了高额的仓储费&a…

最佳实践 | 帮助您的游戏在斋月期间大放异彩

作者 / Google Play 游戏业务发展经理 Nimrod Levy世界各地的开发者都发现&#xff0c;在斋月期间&#xff0c;许多庆祝这个神圣月份的国家/地区的用户都会比平时更活跃。这是一个吸引穆斯林玩家的宝贵机会。斋月是伊斯兰历的第 9 个月。世界各地的穆斯林都会在这个月进行斋戒、…

RV1126笔记二十二:pt->onnx->rknn模型转换

若该文为原创文章,转载请注明原文出处。 一、介绍 实现的目标是,把RK提供的yolov5s.pt转成onnx,在把onnx转成rknn,部署到RV1126上面。 这里不训练模型,所以只要搭建好环境后,就可以直接运行测试。 这里只是提供一种转换的方法,有其他的方式,可以自行测试。 由于不…

立根铸魂 崛起数智时代 操作系统产业峰会2022即将启幕!

如今&#xff0c;数字经济成为全球经济增长的主引擎。基础软件是数字经济发展的基础&#xff0c;是制造强国、网络强国、数字中国建设的关键支撑。而基础软件中的操作系统&#xff0c;作为数字基础设施的底座&#xff0c;已经成为推动产业数字化、智能化发展的核心力量。 2022…

Codeforces Round #840 (Div. 2)

A. Absolute Maximization 题目链接&#xff1a;Problem - A - Codeforces 样例输入&#xff1a; 4 3 1 0 1 4 5 5 5 5 5 1 2 3 4 5 7 20 85 100 41 76 49 36样例输入&#xff1a; 1 0 7 125题意&#xff1a;给定一个长度为n的数组a[]&#xff0c;我们可以对这个数组中的数进…

VueJs中setup的使用(上)

前言在写组合式API代码时,首先接触到的是setup这个函数,在一些项目代码里,你会看到有的直接在script标签上添加setup标识,有的在选项式API方式里,以setup()函数,配置选项的方式出现在单文件组件里什么时候用setup()函数方式,什么时候不用,对于有些新手同学,有些困惑,以及它的一…

聚观早报 | 特斯拉上海工厂被曝停产;富士相机X-Pro 3已停产

今日要闻&#xff1a;特斯拉上海工厂被曝停产&#xff1b;富士相机X-Pro 3已停产&#xff1b;字节复活红果小说App&#xff1b;网易云音乐首份乐评报告发布&#xff1b;辛巴年货节单场带货超1400万单特斯拉上海工厂被曝停产 12 月 26 日消息&#xff0c;根据一份内部通知和两位…

十八、Docker可视化管理工具Portainer

1、概述 Portainer分两个版本 开源版本&#xff1a;Portainer Community Edition (CE) 和商业版本&#xff1a;Portainer 商业版 (BE)。 CE 拥有超过 50 万的普通用户&#xff0c;是一个功能强大的开源工具集&#xff0c;可让您轻松地在 Docker、Docker Swarm、Kubernetes 和…

SpingBoot常见注解区分

1、Mapper注解&#xff1a; 作用&#xff1a;在接口类上添加了Mapper&#xff0c;在编译之后会生成相应的接口实现类 添加位置&#xff1a;接口类上面 Mapper public interface UserMapper{/*** param username 用户名* param password 密码* param memberLevelId 会员级别* p…

【Pygame实战】俄罗斯方块 | 太好玩了~停不下来,这种版本(Turtle彩版)你肯定没玩过……(经典怀旧:无人不知的俄罗斯方块)

导语 警报警报&#xff01;听说CSDN游戏专区火了火了~竟然是因为各种形状的方块。 对&#xff01;各种游戏都快烂大街了&#xff0c;俄罗斯方块咋滴就不能火一把了&#xff1f; Python版俄罗斯方块 等你来战&#xff01; 所有文章完整的素材源码都在&#x1f447;&#x1f447;…

分库分表必知

概述 为啥要进行分库分表&#xff1f; 单表数据量太大&#xff0c;比如超过5000w行&#xff0c;查询时扫描的行太多&#xff0c;SQL效率低&#xff0c;CPU出现瓶颈 数据的切分就是通过某种特定的条件&#xff0c;将存放在同一个数据库或同一个表的数据分散存放到多个数据库&a…

干货 | 数字经济创新创业——网络安全

下文整理自清华大学大数据能力提升项目能力提升模块课程“Innovation & Entrepreneurship for Digital Economy”&#xff08;数字经济创新创业课程)的精彩内容。主讲嘉宾&#xff1a;Kris Singh: CEO at SRII, Palo Alto, CaliforniaVisiting Professor of Tsinghua Unive…

在 ArcGIS 中使用函数块给字段赋随机值 (指定范围内随机编号)

在 ArcMap 或 ArcGIS Pro 中通过使用字段计算器,给字段赋随机值。 通过字段计算器内置的 VB、Python 方法可以构建函数块表达式实现各种各样的赋值操作,下面我们来看看通过使用内置的 Python 函数块来实现随机赋值。 (也可以制作成工具箱哦,更加方便) ArcMap 生成随机值…

智能开关继电器-选型篇2

继电器在智能开关中承担着开通、关断的功能&#xff0c;是开关产品最根本的一项功能。这样一个关键的器件我们将分为两个篇章进行撰写&#xff1a;选型篇1、选型篇2、评估篇。其中选型篇1简述继电器的定义、分类&#xff0c;可参考《智能开关继电器-选型篇1》。选型篇2会在基于…

【TypeScript】类型声明文件的讲解与使用

目录 简介 文件类型 类型声明文件使用 简介 我们发现今天所有的JS应用都会引入许多第三方库来完成任务需求。这些第三方库无论是否是TS编写的最终都会编译成JS代码给开发者使用。我们知道TS提供了类型才有了代码提示和类型保护机制&#xff0c;但我们引入的第三方库都会有相…

搞懂 Spark 系列之 Spark Shuffle 的前世今生

注&#xff1a;本文已首发于PowerData公众号&#xff01; 1 Spark Shuffle 是什么&#xff1f; Shuffle 中文意思是“洗牌&#xff0c;混洗”&#xff0c;而在 Hadoop 的 MapReduce 框架中&#xff0c;Shuffle 是 Map 和 Reduce 中间必不可少的连接桥梁。数据在从Map 阶段结束…

优化器核心技术—Join Reorder

Join Reorder 的简介 Join Reorder 是开务数据库 SQL 优化器中的核心优化算法&#xff0c;开务数据库优化器包括 RBO 和 CBO 两部分&#xff0c;负责计划优化&#xff0c;提升 SQL 执行性能。Join Reorder 能够保证在复杂查询执行的场景下&#xff0c;枚举合法的执行路径&…

分布式系统稳定性建设指南

来源&#xff1a; 中国信息通信研究院 系统稳定性能建设是一个系统化工程&#xff0c;需要硬件软件&#xff0c;需要从企业工程建设的全环节进行设计和实施&#xff0c;充分利用以混沌工程、全链路压测为代表的分布式稳定性保障技术&#xff0c;建设保障能力&#xff0c;改造运…

域名系统 DNS(计算机网络-应用层)

目录 互联网的域名结构 顶级域名 TLD(Top Level Domain) 域名服务器 域名系统 DNS 域名解析的过程 域名服务器的四种类型 本地域名服务器 DNS 协议 DNS缓存 DNS提供的其它服务 互联网的域名结构 域名系统 DNS (Domain Name System)&#xff0c;实现主机名&#xff08;域…