当前位置: 首页 > news >正文

Linux_进程概念

进程概念

  • 冯诺依曼体系结构
    • 硬件——冯诺依曼
    • 软件——操作系统
    • 查看进程
      • 查看进程的第一种方式
      • 查看进程的第二种方式
    • 通过系统调用获取进程标识符
    • 通过系统调用创建进程-fork
    • 进程状态
      • 运行态
      • 终止态
      • 阻塞态
      • 挂起态
    • Linux进程状态
      • R(运行状态)
      • S(阻塞状态)
      • D(阻塞状态)
      • Z(僵尸状态)
      • X(死亡状态)
      • t(暂停)
    • 进程优先级(top)
      • Linux下的优先级的相关概念和操作
      • 查看进程优先级的命令
      • 其他概念
    • 进程间切换
    • 环境变量
      • 常见的环境变量
      • 环境变量的c/c++的获取方式
    • 程序地址空间
      • 地址空间验证--Linux
      • 感知地址空间的存在
    • 父子进程独立性

冯诺依曼体系结构

硬件——冯诺依曼

输入:键盘,话筒,摄像头,磁盘,网卡

输出:显示器,音响,磁盘,打印机

存储器:内存

cpu(运算器+控制器)

运算器:运算计算(+,-,*,/)+逻辑计算(|,&,^)

控制器:协调数据流向,流多少

外设不与cpu打交道,外设输入输出数据,是写入内存或从内存中读取

cpu只能对内存读写,不能访问外设

比如:使用qq与好友发送消息的数据流动

image-20220825151441755

从键盘外设输入数据,数据放入到内存,cpu读取内存中的数据,进行计算,再放入到内存中,再把这个数据输出到网卡中,通过网络,数据到达好友的网卡(外设输入),数据进入内存,cpu读取内存中的数据,经过cpu的计算,再放到内存中,输出到显示器上。

在我们写完一个代码,经过编译成一个可执行程序,要运行这个程序,需要先把这个程序加载到内存,为什么要加载到内存?

体系结构规定

对于硬件来说,只能被动的完成某种功能,不能主动的完成某种功能,只能通过软件(操作系统)来进行

软件——操作系统

操作系统本质上就是搞管理的软件

操作系统管理硬件,并不是直接管理硬件,而是通过操作系统管理驱动程序,驱动程序管理硬件来间接管理硬件。

image-20220825153634561

进程:是一个运行起来的程序(程序加载到内存)

管理是对信息的管理,操作系统管理进程,就是先将进程数据化,再管理这个数据,但是在操作系统中会同时存在大量进程,所以就需要对这些进程数据化后的数据进行组织。

linux中,是先将进程的代码和数据先加载到内存,

再对进程的数据化,是采用结构体 task_structpcb:进程控制块)(在这个进程控制块内存储进程所有的属性数据),

对进程的组织化,就是将多个进程的pcb通过双链表来链接起来

由此得出:进程 = 可执行程序代码,数据 + 该进程对应的内核数据结构

操作系统不会直接暴露自己的任何数据结构,代码逻辑,其他数据相关的细节

操作系统是通过系统调用的方式,对用户提供接口服务

linux是由c语言写的,这里所谓的系统调用接口,本质就是c语言接口

查看进程

每个进程都有一个唯一的标识符pid

查看进程的第一种方式

ps axj|grep 'mytest'|grep -v grep //查找mytest进程

image-20220825173726973

查看进程的第二种方式

在/proc中存放实时进程信息,/proc/pid中存放该pid号的进程信息

image-20220825173620610

cwd:进程当前的工作路径

工作路径:该进程在那个路径被打开的,那个路径就叫工作路径

exe:进程对应的可执行程序的磁盘文件

可执行程序是文件,存储在磁盘中

通过系统调用获取进程标识符

  • 进程id(PID

  • 父进程id(PPID

    #include<stdio.h>
    #include<unistd.h>
                                          
    int main()
    {
      printf("该进程的pid为:%d\n",getpid());
      printf("该进程的父进程的pid为:%d\n",getppid());
      return 0;
    }
    

    image-20220825202843150

杀掉进程

kill -9 pid		//杀掉进程

由下图可知,这两个进程的pid的不同,但是它们的父进程的pid相同

image-20220825204547024

这个父进程为bash,几乎我们在命令行上所执行的指令,都是bash进程的子进程

image-20220825203759556

通过系统调用创建进程-fork

  • fork有两个返回值
  • 父子进程代码共享,数据各自开辟空间(由写时拷贝实现)
  • fork会给父进程返回子进程的pid,给子进程返回0
#include<stdio.h>
#include<unistd.h>

int main()
{
  int ret=fork();
  if(ret==0)
  {
      printf("hello world,pid:%d,ppid:%d\n",getpid(),getppid());
  }
  else
  {
      printf("hello byld,pid:%d,ppid:%d\n",getpid(),getppid());
  }
  return 0;
}

fork之后,创建出子进程,父进程和子进程会共享全部代码,一般会执行后续代码

fork之后,创建出子进程,这个子进程是如何创建的?

fork之后,内存中就会存在子进程的task_struct(pcb)+子进程的代码和数据

子进程的task_struct(pcb)以父进程的task_struct(pcb)为模板初始化(并不是完全一样)

printf为什么会打印两次?

因为fork()返回值不同,通过返回值不同,判断,让父子执行不同的代码块

fork为什么会有两个返回值?

因为fork函数返回了两次

那么fork函数为什么会返回两次?

因为fork函数是创建子进程的,在到达fork函数中的return语句时,这个函数的核心功能已经实现了,子进程已经创建了,并且放入到运行队列中,这时有一个父进程,一个子进程,两个进程分别执行return语句,返回两个pid

如何理解进程被运行?

image-20220901150559294

在CPU中,有一个运行队列runqueuerunqueue中会有一个指针,会指向这个双向链表的第一个节点(pcb),等待被调度,调度这个进程时,这个pcb 会被保存到CPU的寄存器中,CPU就会通过这个pcb找到这个进程代码,将代码加载到CPU中的pc指针,执行。

在创建一个子进程之后,这个子进程放入运行队列,等到调度这个进程时,因为这个子进程和父进程共享代码,就会找到父进程的代码,执行。

fork为什么给父进程返回子进程的pid,给子进程返回0 ?

父进程必须标识子进程的方案(因为一个父进程可以有多个子进程),fork之后,需要给父进程返回子进程的pid

子进程最重要是要知道自己被创建成功了,因为子进程找父进程成本低(一个子只有一个父,不用标识)

进程状态

在操作系统,进程状态大致分为四种

  • 运行态
  • 终止态
  • 阻塞态
  • 挂起态

运行态

**概念:**进程只要在运行队列中就叫运行态,随时可以调度

终止态

**概念:**进程依旧存在,但是永久不运行,随时等待被释放

阻塞态

概念:进程等待某种资源(非CPU),资源没有就绪的时候,进程需要在该资源的等待队列中进行排队,此时进程的代码并没有运行,进程所处的状态叫做阻塞

一个进程,在使用资源的时候,不仅仅是在使用CPU资源,进程可能申请其他资源:磁盘,网卡,显卡,显示器资源,声卡,音响

如果我们申请CPU资源,展示无法满足,需要排队——运行队列

那么如果我们申请其他慢设备的资源呢?

也是需要在这些慢设备的等待队列中排队

因为操作系统管理硬件资源,将硬件资源数据化,所以这些硬件都有自己对应的结构体,在结构体中会有对应硬件的属性信息以及等待队列

当进程访问某些资源(磁盘,网卡),该资源如果暂时没有准备好,或者正在给其他进程提供服务,此时,当前进程(pcb)要从**runqueue(运行队列)中移除,将当前进程放入对应设备的描述结构体等待队列**

当这个进程在等待外部资源时,该进程的代码不会被执行(进程卡住了,进程阻塞)

当外部资源准备就绪后,将进程的**pcb放回到CPU的运行队列**中

这些工作都是由操作系统完成的,操作系统对进程的管理工作

挂起态

将源文件编译成可执行程序(.exe),是存储在磁盘中的,将可执行程序加载到内存中,将它数据化成pcb,在内存中就会存在这个进程内核的数据结构 + 对应的代码和数据,如果有多个进程同时运行,内存中就会有多份进程内核的数据结构 + 对应的代码和数据,这时,可能会导致内存的空间不足。

image-20220903172217205

为了解决这个问题,就有了进程挂起

进程挂起:短期内不会被调度(等待的外部资源不会被就绪),他的代码和数据依旧在内存上,会导致空间浪费,操作系统就会把该进程的代码的数据置换到磁盘的swap分区

所以,往往内存不足的时候,伴随着磁盘被高频率访问

Linux进程状态

Linux内核源代码

/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
    "R (running)", /* 0 */
	"S (sleeping)", /* 1 */
	"D (disk sleep)", /* 2 */
	"T (stopped)", /* 4 */
	"t (tracing stop)", /* 8 */
	"X (dead)", /* 16 */
	"Z (zombie)", /* 32 */
};

R(运行状态)

在运行队列中

S(阻塞状态)

浅度睡眠,可以杀掉,可以随时叫醒(操作系统和用户(可以被杀掉)都可以唤醒它)

在等待资源,资源就绪,操作系统将其变为R状态(运行状态)

#include<stdio.h>
#include<unistd.h>
int main()
{
  while(1)
  {
    printf("hello world\n");
    sleep(1);
  }
  return 0;
}

image-20220903182519154

这个进程大部分时间都在等显示器资源,所以处于S(阻塞状态)

D(阻塞状态)

深度睡眠,不可杀掉

在Linux中,如果我们等待的是磁盘资源,进程阻塞所处的状态就是D

Z(僵尸状态)

当Linux中的进程退出的时候,不会直接进入X(死亡状态),而是先进入僵尸状态

为什么要进入僵尸状态?

因为当这个进程执行结束,退出,进程需要把它的任务完成的如何告诉父进程,将进程的执行结果告知父进程

僵尸状态,就是为了维护退出信息(退出信息会写入到pcb中),可以让父进程或者操作系统读取的。(如何读取呢? 通过进程等待让父进程读取)

X(死亡状态)

该状态下的进程,可以被释放

如何模拟僵尸状态?

创建子进程,子进程退出,进入僵尸状态,父进程不退出,也不进程等待(接收子进程的结果)子进程

长时间僵尸,有什么问题?

如果僵尸状态的进程不被回收,该进程就会一直僵尸进程,会导致内存泄漏

僵尸进程不能被杀死??

模拟僵尸状态

#include<stdio.h>                                        
#include<unistd.h> 
int main()
{
    int id=fork();
    if(id==0)
    {
        printf("该进程为子进程,pid为%d,ppid为%d\n",getpid()    ,getppid());
    }
    else
    {
        while(1)
        {
            printf("该进程为父进程,pid为%d,ppid为%d\n",getpid(),getppid());
            sleep(1);
      	}
    }
    return 0;
}

img

孤儿进程

子进程还在运行,但是父进程提前退出

父进程退出,为什么没有进入僵尸状态,而是直接没了

如果父进程提前退出,子进程还在运行,父进程依旧会进入僵尸状态,只不过父进程很快会被bash进程回收,进入死亡状态,释放,而子进程会被1号进程(就是操作系统)领养

模拟孤儿进程

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

int main()
{
    int id=fork();
    if(id==0)
    {
        while(1)
        {
            printf("该进程为子进程,pid为%d,ppid为%d\n",getpid(),getppid());
        	sleep(1);
        }
    }
    else
    {
        printf("该进程为父进程,pid为%d,ppid为%d\n",getpid(),getppid());               
    }
    return 0;
}

image-20220904101801973

t(暂停)

进程被调试的时候,遇到断点的状态

kill - 9 杀死进程

kill -19 暂停进程

kill -18 继续进程

在进程状态中我们可以看到有的进程后有+

状态后面有+是前台进程(可以ctrl+c杀掉进程)

后台进程,不能用ctrl+c,只能用kill -9 杀掉

进程优先级(top)

优先级vs权限

优先级:是进程获取资源的先后顺序

权限:是能还是不能的问题

优先级存在是因为在系统内,进程占大多数,而资源是少数,进程竞争资源是常态,一定要确定优先级

Linux下的优先级的相关概念和操作

ps -l //查看与bash进程相关的进程

image-20220904102151248

在进程信息中,会看到PRINI

  • UID:代表执行者身份
  • PID:代表这个进程的代号
  • PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
  • PRI :代表这个进程可被执行的优先级,其值越小越早被执行
  • NI :代表这个进程的nice值

要更改进程优先级,需要更改的不是PRI,而是NI

NI:进程优先级的修正数据

Linux不允许进程无节制的设置优先级

PRI=PRI_OLD+NI //每次设置优先级,这个PRI_OLD都会被恢复为80

NI范围[-20,19]

PRI范围[60,99]

查看进程优先级的命令

top命令更改已存在进程的nice:

  • top
  • 进入top后按“r”–>输入进程PID–>输入nice值

其他概念

  • 竞争性:系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级

  • 独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰(进程运行具有独立性,不会因为一个进程挂掉或者异常,而导致其他进程出现问题)(进程通过进程地址空间使其具有独立性)

  • 并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行

  • 并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发

    我们的电脑大多都是单CPU,但是我们的电脑有多种进程在跑???

    当这个进程运行时,并不是直到这个进程运行结束,这个进程一直占用这个CPU,我们的操作系统大多都是分时的

    操作系统会给每一个进程,在一次调度周期中,赋予一个时间片的概念,一个进程的时间片运行完,就运行下一个进程,到下一个调度周期,继续上一次的运行,这样就可以在一个时间段内,多个进程会通过切换交叉的方式,让多个进程代码,在一段时间内得到推进(能够继续推进,是因为CPU中有pc指针(程序计数器)),这就叫做并发

操作系统,不是根据简单的队列顺序进行调度的,而是抢占式内核

当一个进程正在运行,如果来了一个优先级更高的进程,调度器会将运行的进程从CPU上剥离下来,放上优先级更高的进程,这就是进程抢占

进程间切换

在CPU中的寄存器,可以临时存储数据

在进程在CPU上运行时,会产生大量数据,这些数据会暂时存放在CPU的寄存器上,在寄存器上的数据,我们称之为这个进程的上下文数据,当这个进程的时间片结束或者被进程抢占,这个进程会从CPU上被剥离,在寄存器上的上下文数据会被存储到这个进程的pcb,等下次调度这个进程时,再从pcb中拿到上下文数据,放到寄存器中,继续执行。

环境变量

为什么我们的代码运行要带路径,而系统的指令不用带路径 ?(系统的指令也是程序)

因为系统中存在相关的环境变量,保存了程序的搜索路径

系统中存在一个环境变量:PATH,可以搜索可执行程序

PATH中保存的是可执行程序的路径,当使用指令是,就在PATH中搜索对应的路径,找到指令,执行,停止搜索

echo $ PATH		//输出PATH内容,加上$是显示PATH内容,不加$就把PATH当成字符串输出了

image-20220902190640293

在PATH这个环境变量中存储了可执行程序的路径(用分隔)

在命令行中也可以定义变量

image-20220902191631694

命令行变量分两种:

  1. 普通变量
  2. 环境变量

我们在上图中定义的是普通变量,所以env中没有,需要将普通变量导出到环境变量

export aaa			//将aaa导出到环境变量
upset				//取消环境变量

image-20220902193146662

如何让自己的程序不带路径运行?

  1. 把我的exe考到usr/bin/中(不建议)(usr/bin/中存储的是系统的指令(可执行程序))

  2. 把我的exe所处的路径添加到PATH中

    export PATH = $PATH:路径 //把自己的路径导入PATH
    

which 查命令也是在环境变量中搜索的

image-20220902194615318

常见的环境变量

  • PATH : 指定命令的搜索路径
  • HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)
  • SHELL : 当前Shell,它的值通常是/bin/bash

echo $HISESIZE 保存命令行数

history 查询写过的命令

history |wc -l 统计写过的命令条数(最多保存3000条)

set 查看本地变量和shell变量

环境变量的c/c++的获取方式

main函数可以带参数吗 ?

int main(int argc,char* argv[],char* env[])
{
    return 0;
}    
//int argc			argv数组中的元素个数
//char* argv[]		argv叫做命令行参数,传入的是程序名和选项
//char* env[]		传入环境变量

命令行参数的意义是什么 ?

同一个程序,不同的选项,执行不同的执行逻辑,执行结果

Linux系统中,会根据不同的选项,让不同的命令,可以有不同的表现

获取环境变量的三种方式

  1. main函数的第三个参数
  2. c语言提供的第三方变量,extern char** envision
  3. getenv("PATH") //获取特定环境变量

环境变量具有全局属性 ?

命令行中的进程的父进程都是BASH进程

本地变量:本质就是bash进程内部定义的变量,不会被子进程继承下去

环境变量:具有全局性,会被子进程继承下去

image-20220902204954236

在上面,我们说过,命令行中的进程都是BASH进程的子进程,本地变量在BASH内部,不会继承到子进程,就是在子进程中本地变量无效,但是上图中echo作为一个子进程,本地变量却有效为什么?

Linux下大部分命令都是通过子进程的方式执行,

但是,还有一部分命令,不通过子进程的反射光是执行,而是由BASH进程自己执行(调用自己对应的函数来完成特定的功能),我们把这种命令叫做内建命令(比如:echo)

程序地址空间

我们以前了解到的程序地址空间,以操作系统的概念,是进程地址空间

进程地址空间

image-20220903194419428

进程地址空间不是内存 !

地址空间验证–Linux

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

int a=0;
int b;

int main(int argc,char* argv[],char* env[])
{
  printf("code      address:    %p\n",main);
  printf("initial   address:    %p\n",&a);
  printf("unintial  address:    %p\n",&b);
  
  char*c1 = (char*)malloc(sizeof(char)*6);
  char*c2 = (char*)malloc(sizeof(char)*6);
  char*c3 = (char*)malloc(sizeof(char)*6);
  char*c4 = (char*)malloc(sizeof(char)*6);

  printf("heap     address:   %p\n",c1);
  printf("heap     address:   %p\n",c2);
  printf("heap     address:   %p\n",c3);
  printf("heap     address:   %p\n",c4);

  printf("stack    address:   %p\n",&c1);
  printf("stack    address:   %p\n",&c2);
  printf("stack    address:   %p\n",&c3);
  printf("stack    address:   %p\n",&c4);

  printf("agrv     address:   %p\n",argv[0]);
  printf("env      address:   %p\n",env[0]);
 
  return 0;
}

image-20220903202208429

经过验证,进程地址空间是符合上图的,地址依次增加,堆和栈相向而生,栈向地址减小的方向生长,堆向地址增大的方向生长

感知地址空间的存在

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

int main()
{
    int _val=0;
  	pid_t id=fork();
  	printf("数据修改前\n");
  	if(id==0)
    {
        printf("该进程为子进程,pid:%d,ppid:%d,_val值为%d,_val地址为%p\n",getpid(),getppid(),_val,&_val);
    }
    else
  	{
    	printf("该进程为父进程,pid:%d,ppid:%d,_val值为%d,_val地址为%p\n",getpid(),getppid(),_val,&_val);
  	}
  	printf("数据修改后\n");
  	if(id==0)
  	{
    	_val=2;
    	printf("该进程为子进程,pid:%d,ppid:%d,_val值为%d,_val地址为%p\n",getpid(),getppid(),_val,&_val);
  	}
  	else
  	{
    	printf("该进程为父进程,pid:%d,ppid:%d,_val值为%d,_val地址为%p\n",getpid(),getppid(),_val,&_val);
  	}
    return 0;
}

image-20220903204830593

通过上面的程序,我们可以发现,当父子进程没有人修改全局数据时,父子是共享该数据的,父子进程读取同一个变量(地址一样),但是后序如果修改了这个数据,父子进程访问的地址依旧一样,但是得到内容却不同,这说明,这个地址一定不是物理地址,而是虚拟地址,那进程地址空间并不是物理内存,而是虚拟内存

那么为什么操作系统不让我直接看到物理内存?

因为内存是硬件,不能阻止你访问,为了防止用户对内存上的数据造成破坏。

进程地址空间是什么?

每一个进程都会有一个进程地址空间,在进程启动的时候,都会让操作系统给他创建一个地址空间,该地址空间就是进程的地址空间

因为,操作系统需要管理进程地址空间,那么就需要对进程地址空间进行先描述,在组织

那么,如何对进程地址空间先描述,在组织 ?

进程地址空间就是内核的一个数据结构,struct mm_struct,进程的pcbtask_struct)也是内核的数据结构,pcb中有一个指针,指向这个mm_struct(进程地址空间),进程地址空间是虚拟地址,虚拟地址通过页表映射到物理地址,通过物理地址就找到了真正存储在物理内存的位置

为了保证进程的独立性,保证多进程运行期间互不影响

进程的内核数据结构是独立的,进程的代码和数据是独立的

**总结:**磁盘中的程序和数据加载到物理内存中,对进程进行描述化,组织化,构建task_srtructmm_struct,操作系统会给每一个进程构建页表结构,页表中虚拟地址和物理地址建立映射关系

什么叫做区域?

mm_struct中存在多个结构体,进程地址空间中分为多个区域(栈区,堆区,代码区…………),每一块区域就是一个结构体(struct),多个结构体通过链表连接起来

**补充:**程序编译完之后,没有加载到内存,由地址,由区域都在磁盘中划分好了

父子进程独立性

数据结构独立:

创建子进程时,创建子进程的内核数据结构(mm_struct,task_struct),对子进程的数据结构进行初始化(父进程为模板),创建子进程,OS也会给子进程创建子进程的页表(子进程的页表也是以父进程为模板初始化)

代码和数据独立:

数据独立:

在刚创建出子进程时,子进程的数据结构,页表都是以父进程为模板初始化,所以没有改变数据时,父进程和子进程的数据对应的虚拟地址相同,页表的映射关系相同,父子进程同一个数据映射到同一个物理地址

但是,如果对数据进行改变,就会放生写时拷贝,将父子进程数据分离,子进程的数据就存储在另一个物理地址,这时,父子进程这个数据的物理地址不同,页表的映射关系也要改变

父子进程访问同一个变量,通过相同的虚拟地址,经过父子进程页表的映射,找到不同的物理地址,拿到不同的值

代码独立:

父子进程的代码是共享的,但是如果父进程退出,而子进程不退出,依旧需要代码,代码不会释放,所以保证了代码的独立

改变数据前:

image-20220903212107260

改变数据后:

image-20220903211958172

同一个变量,为什么会有不同的值?

现在,就可以解决这个问题

img

id是属于父进程进程栈空间中定义的变量,fork内部return会被执行两次,谁先被返回,那这个数据就被修改了,就需要发生写时拷贝,所以,一个变量,会有不同的值,本质是因为大家虚拟地址一样,但是映射后的物理地址不同

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

  1. 保护内存
  2. 进程管理,Linux的内存管理通过地址空间进行功能模块的解耦
  3. 简化进程本身的设计与实现

补充:页表中堆区的映射关系是在运行时建立的

据时,父进程和子进程的数据对应的虚拟地址相同,页表的映射关系相同,父子进程同一个数据映射到同一个物理地址

但是,如果对数据进行改变,就会放生写时拷贝,将父子进程数据分离,子进程的数据就存储在另一个物理地址,这时,父子进程这个数据的物理地址不同,页表的映射关系也要改变

父子进程访问同一个变量,通过相同的虚拟地址,经过父子进程页表的映射,找到不同的物理地址,拿到不同的值

代码独立:

父子进程的代码是共享的,但是如果父进程退出,而子进程不退出,依旧需要代码,代码不会释放,所以保证了代码的独立

改变数据前:

[外链图片转存中…(img-GWhNWo1r-1662258199214)]

改变数据后:

[外链图片转存中…(img-kpWjBHxX-1662258199214)]

同一个变量,为什么会有不同的值?

现在,就可以解决这个问题

[外链图片转存中…(img-JEabystE-1662258199215)]

id是属于父进程进程栈空间中定义的变量,fork内部return会被执行两次,谁先被返回,那这个数据就被修改了,就需要发生写时拷贝,所以,一个变量,会有不同的值,本质是因为大家虚拟地址一样,但是映射后的物理地址不同

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

  1. 保护内存
  2. 进程管理,Linux的内存管理通过地址空间进行功能模块的解耦
  3. 简化进程本身的设计与实现

补充:页表中堆区的映射关系是在运行时建立的

相关文章:

  • 【PAT乙】2022秋季赛后总结
  • nginx + tomcat 搭建负载均衡、动静分离(tomcat多实例)
  • CSRF(跨站请求伪造)攻击和预防
  • 【都 Java17 了,还不了解 Java 8 ? 】一文带你深入了解 Java 8 新特性
  • 解决国产机SVN连接失败的问题
  • MySQL 存储过程创建指定表结构
  • 我们这样的人
  • < Linux > 进程概念(2)
  • Qt5开发从入门到精通——第四篇十二节(不规则窗体)
  • MySQL 5.7.x--命令行自带帮助文档的使用,超级棒!!!
  • 手撕前端面试题【javascript】
  • Qt5开发从入门到精通——第四篇十三节(程序启动画面 )
  • postman+Newman+jenkins实现接口自动化测试持续集成
  • 阿里达摩院(研究型实习生)
  • 汽车电子常用外围硬件电路设计
  • 【跃迁之路】【733天】程序员高效学习方法论探索系列(实验阶段490-2019.2.23)...
  • axios请求、和返回数据拦截,统一请求报错提示_012
  • Iterator 和 for...of 循环
  • js学习笔记
  • PermissionScope Swift4 兼容问题
  • Python socket服务器端、客户端传送信息
  • SegmentFault 技术周刊 Vol.27 - Git 学习宝典:程序员走江湖必备
  • Sublime Text 2/3 绑定Eclipse快捷键
  • vue 配置sass、scss全局变量
  • Yii源码解读-服务定位器(Service Locator)
  • 基于Javascript, Springboot的管理系统报表查询页面代码设计
  • 前端面试之CSS3新特性
  • 如何抓住下一波零售风口?看RPA玩转零售自动化
  • 一些基于React、Vue、Node.js、MongoDB技术栈的实践项目
  • 再谈express与koa的对比
  • 【干货分享】dos命令大全
  • 好程序员web前端教程分享CSS不同元素margin的计算 ...
  • ​软考-高级-信息系统项目管理师教程 第四版【第14章-项目沟通管理-思维导图】​
  • #QT(一种朴素的计算器实现方法)
  • (03)光刻——半导体电路的绘制
  • (1)bark-ml
  • (4.10~4.16)
  • (Matalb时序预测)PSO-BP粒子群算法优化BP神经网络的多维时序回归预测
  • (Repost) Getting Genode with TrustZone on the i.MX
  • (安全基本功)磁盘MBR,分区表,活动分区,引导扇区。。。详解与区别
  • (待修改)PyG安装步骤
  • (牛客腾讯思维编程题)编码编码分组打印下标题目分析
  • (区间dp) (经典例题) 石子合并
  • (三)centos7案例实战—vmware虚拟机硬盘挂载与卸载
  • (已解决)什么是vue导航守卫
  • .NET C#版本和.NET版本以及VS版本的对应关系
  • .Net Core和.Net Standard直观理解
  • .net oracle 连接超时_Mysql连接数据库异常汇总【必收藏】
  • .net 流——流的类型体系简单介绍
  • .Net(C#)常用转换byte转uint32、byte转float等
  • .NET企业级应用架构设计系列之技术选型
  • .NET中winform传递参数至Url并获得返回值或文件
  • .py文件应该怎样打开?
  • /3GB和/USERVA开关
  • @Transactional类内部访问失效原因详解