• 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
      #include
                                            
      int main()
      {
        printf("该进程的pid为:%d\n",getpid());
        printf("该进程的父进程的pid为:%d\n",getppid());
        return 0;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

      image-20220825202843150

    杀掉进程

    kill -9 pid		//杀掉进程
    
    • 1

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

    image-20220825204547024

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

    image-20220825203759556

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

    • fork有两个返回值
    • 父子进程代码共享,数据各自开辟空间(由写时拷贝实现)
    • fork会给父进程返回子进程的pid,给子进程返回0
    #include
    #include
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    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 */
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    R(运行状态)

    在运行队列中

    S(阻塞状态)

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

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

    #include
    #include
    int main()
    {
      while(1)
      {
        printf("hello world\n");
        sleep(1);
      }
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    image-20220903182519154

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

    D(阻塞状态)

    深度睡眠,不可杀掉

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

    Z(僵尸状态)

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

    为什么要进入僵尸状态?

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

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

    X(死亡状态)

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

    如何模拟僵尸状态?

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

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

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

    僵尸进程不能被杀死??

    模拟僵尸状态

    #include                                        
    #include 
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    img

    孤儿进程

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

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

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

    模拟孤儿进程

    #include
    #include  
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    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当成字符串输出了
    
    • 1

    image-20220902190640293

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

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

    image-20220902191631694

    命令行变量分两种:

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

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

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

    image-20220902193146662

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

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

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

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

    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[]		传入环境变量
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

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

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

    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
    #include
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    image-20220903202208429

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

    感知地址空间的存在

    #include
    #include
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    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. 简化进程本身的设计与实现

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

  • 相关阅读:
    kotlin aes 加密解密
    单链表反转
    【C语言】每日一题(半月斩)——day3
    如何评价微软发布的Phi-3,手机都可以运行的小模型
    通用场景图像分割
    如何理解Linux文件IO?
    SpringBoot开发实战(微课视频版)
    关于ETL的两种架构(ETL架构和ELT架构)
    synchronized 、ReentrantLock
    李阳:京东零售OLAP平台建设和场景实践
  • 原文地址:https://blog.csdn.net/weixin_53230235/article/details/126686371