Linux进程的概念

文章目录

Linux进程的概念

一、进程

1.基本概念

课本概念:程序的一个执行实例,正在执行的程序等
内核观点:担当分配系统资源(CPU时间,内存)的实体。

2.描述进程(PCB)

实际上,我们所写的代码运行起来就是进程,如何管理进程呢?
先描述,再组织;
任何进程在形成之时,操作系统要为该进程创建PCB(进程控制模块);简单的讲,PCB就是一个结构体(Linux操作系统下的PCB是:struct task_struct),里面存放了进程相关的属性信息

3.进程与程序的区别

​ 首先,我们编写好的程序,在经过编译处理之后,所产生的文件(可执行程序)会放在磁盘中,当我们运行程序时,操作系统将磁盘上的文件加载到内存,同时为它创建PCB(进程控制块),这两个组合起来才是进程
image-20240106152753502

4.task_struct-PCB的一种

在Linux中描述进程的结构体叫做task_struct
task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息

5.task_struct的内容分类

  1. 标识符:描述本进程的唯一标识符,用来区分其他进程
  2. 状态:任务状态,退出代码,退出信号等。
  3. 优先级:相对于其他进程的优先级
  4. 程序计数器:程序中即将被执行的下一条指令的地址
  5. 内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
  6. 上下文数据:进程执行时处理器的寄存器中的数据
  7. I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
  8. 记账信息:可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。

二、如何查看进程

1.通过系统文件查看进程

通过系统文件/proc,来查看当前所有的进程

image-20240106153722002

2.通过ps指令查看进程

​ ps指令用于报告当前系统的进程状态。可以搭配kill指令随时中断、删除不必要的程序。ps命令
是最基本同时也是非常强大的进程查看命令,使用该命令可以确定有哪些进程正在运行和他们运行的状态、进程是否结束、进程有没有僵死、哪些进程占用了过多的资源等等,总之大部分信息都是可以通过执行该命令得到的。

ps axj //显示现行终端机下的所有进程,包括其他用户的进程

image-20240106154139750

ps axj | head -1 && ps axj | grep -v grep | grep "a.out" //显示某一个进程的信息

image-20240106154717511

三、如何获取pid和ppid

我们想要获取到pid和ppid,就要用到系统调用接口:
pid_t getpid(void) —返回的是子进程的ID
pid_t getppid(void) —返回的是父进程的ID
我们可以通过man指令对这些函数进行详细说明的查看
image-20240106155234297

1.getpid() — 获取子进程(pid)

#include<stdio.h>
#include<unistd.h>
int main(){
	printf("I am child pid: %d\n",getpid());
	return 0;
}

2.getppid() —获取父进程(ppid)

#include<stdio.h>
#include<unistd.h>
int main(){
	printf("I am father pid : %d\n",getppid());
	return 0;
}

这里将两个代码整合到一起后,通过死循环不断的打印有父子进程的ID,并对进程进行检测,发现ps检测到的ID和系统接口获取到的ID是一样的。

image-20240106160236426

如果想要终止左边的死循环程序有两个办法

  1. ctrl+c
  2. kill -9 5717

四、进程的创建 — fork初识

1.四种主要事件会导致进程的创建

  1. 系统初始化
  2. 正在运行的程序执行了创建程序的系统调用
  3. 用户请求创建一个新进程
  4. 一个批处理作业的初始化

2.用户如何请求创建一个新进程

通过fork函数来进行进程的创建,我们可以man fork查看相关的函数信息。这是一个系统调用,它会创建一个与调用进程相同的副本。在调用fork之后,这两个进程(父进程和子进程)拥有相同的内存映射

image-20240105210121401

请看下面这段代码,我们在执行循环前创建了一个子进程,会有什么样的效果呢?

#include<stdio.h>
#include<unistd.h>
int main()
{
  fork();//创建子进程
  while(1){
    printf("I am child pid:%d I am father ppid:%d\n",getpid(),getppid());
    sleep(1);
  }
  return 0;
}

image-20240106161251782

从上述的结果可以看出,main函数的进程和fork创建的进程打印的结果是一样的,并且通过pid和ppid发现,fork的父进程就是main函数的进程,说明fork所创建出来的子进程是父进程在内存上映射

3.如何让父子进程各有所需

​ 以上创建的子进程所做的事和父进程是一样的,显然意义并不大,我们要能够让所创建的子进程
做和父进程不一样的事,才是我们想要的。那如何实现呢?
首先,对于fork是有两个返回值的
​ 1.如果子进程创建成功,在父进程中返回子进程的PID,而在子进程中返回0
​ 2.如果子进程创建失败,则在父进程中返回-1。

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
int main(){
  pid_t id=fork();
  if(id<0){
    perror("fork fail");
    exit(-1);
  }
  else if(id==0){
    //child process
    printf("I am child process\n");
    exit(0);//child process exit
  }
  //parent process
  printf("I am parent process\n");
  return 0;
}

image-20240105213004311

五、进程的状态

1.进程的状态有哪些

CPU对进程处理,取决于进程当前进程所处的状态,CPU对于不同状态的进程会采取不同的措施

image-20240106163042682

/*
* 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  */  //追踪状态,类似于vs下打断点后直接运行到断点处
"X (dead)",           /* 16 */  //死掉的进程
"Z (zombie)",         /* 32 */  //僵尸进程
};

2.进程状态的查看

ps axj / ps aux

image-20240106163236132

3.进程状态的分析

1.运行状态(R)

​ R运行状态(running):并不意味着进程一定在运行中,它表明进行要么是在运行中要么是在运行队列里面。
image-20240106163425825

上图想要表达的意思是,进程A处于运行中,在一段时间后,就会切换到进程B…,这个时间很快,CPU运行这些进程是采用了时间轮转调度算法。在时间片轮转调度算法中,系统根据先来先服务的原则,将所有的就绪进程排成一个就绪队列,并且每隔一段时间产生一次中断,激活系统中的进程调度程序,完成一次处理机调度,把处理机分配给就绪队列首进程,让其执行指令。当时间片结束或进程执行结束,系统再次将cpu分配给队首进程。

2.浅度睡眠状态(S)

​ S睡眠状态(sleeping):意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))

#include <stdio.h>
#include <unuistd.h>
int main(){
    printf("hello linux!\n");
    sleep(50);
    return 0;
}

image-20240106165026176

​ 处于S状态的进程,是可以被立即终止的

3.深度睡眠状态(D)

​ D磁盘休眠状态(Disk sleep)有时候也叫做不可中断睡眠状态(uninterrupttible sleep),在这个状态的进程通常会等待IO的结束。例如,当进程要求对磁盘进行写入操作,那么在磁盘进行写入期间,该进程就处于深度睡眠状态,是不会被杀掉的,因为该进程需要等待磁盘的回复(是否写入成功)以做出相应的应答。(磁盘休眠状态)

4.停止状态(T)

​ T停止状态(stopped):可以通过发送SIGSTOP信号给进程来停止(T)进程。这个被暂停的进程可以通过发送SIGCONT信号让进程继续运行。

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

​ 在上面的程序跑起来之后,处于浅度睡眠状态,我们可以发送SIGSTOP信号给进程来停止(T)进程;

image-20240106170234234

​ 我们发送SIGSTOP信号给进程来停止(T)进程后,还需要发送SIGCONT信号让进程继续运行。

image-20240106170358308

查看kill相关信号:
kill -l
image-20240106170634460

以上的信号,可以查看教程后,尝试尝试

5.僵死状态(Z)

​ 僵死状态(Zombies)是一个比较特殊的状态。当子进程退出并且父进程没有读取到子进程退出的返回代码时就会出现僵死(尸)进程,僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态码。所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程就会进入Z状态;

6.死亡状态(X)

​ X死亡状态(dead):死亡状态只是一个返回状态,当一个进程的退出信息被读取后,该进程所申请的资源就会立即被释放,该进程也就不存在了,所以你不会在任务列表当中看到死亡状态

六、僵尸进程与孤儿进程

1.僵尸进程的危害

​ 有如下代码,我们执行之后,子进程会不断的打印数据,父进程等待子进程的过程中,我们立刻杀掉子进程,那么子进程就会处于僵尸状态,而此时程序还在运行,父进程在等待子进程退出的状态,我们把这种进程称之为僵尸进程。

#include <stdio.h>
#include <unistd.h>
int main(){
   int ret = fork();
   if(ret == 0){//子进程一直打印
     while(1){
       printf("hello linux!\n");
       sleep(1);
     }
   }
   else{ //parent process do nothing,just sleep
     sleep(100);                                              
   }
   return 0;
}

image-20240106180034911

僵尸进程的危害:
1.进程的退出状态必须被维持下去,因为它要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态?
是的!
2.维护退出状态本身就是要用数据维护,也属于进程的基本信息,所以保存task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护?
是的!
3.那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?
是的!因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!
4.内存泄漏?
是的!

2.孤儿进程

​ 刚刚提到了僵尸进程是由于子进程先退出而父进程没有对子进程的退出信息进行读取;那么父进程先退出,子进程在进入僵尸状态后,其父进程未能对其做出处理,那么就称该进程是孤儿进程。若是一直不处理孤儿进程的退出信息,那么孤儿进程进入僵尸状态时就由1号进程进行处理回收。

#include <stdio.h>
#include <stdlib.h>                                                     
#include <unistd.h>
int main(){
   pid_t ret = fork();
   if(ret == 0){//子进程不断的打印数据
     while(1){
       printf("I am child, running!\n");
       sleep(1);
     }
   }
   else{//父进程打印数据,休眠10秒后,直接退出
     printf("father do nothing!\n");
     sleep(10);
     exit(1);
   }
   return 0;
}

image-20240106181400778

子进程执行的过程中,父进程未退出时,父子进程都是S+状态
子进程执行的过程中,父进程退出了,子进程被1号进程领养了(1号进程:操作系统

七、进程的优先级

1.基本概念

​ cpu资源分配的先后顺序,就是指进程的优先权(priority)。
优先权高的进程有优先执行权利。配置进程优先权对多任务环境的Linux很有用,可以改善系统性能。还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能

2.查看进程优先级

在linux或者unix系统中,用ps -l 命令则会类似输出以下几个内容

image-20240106201719737

我们很容易注意到其中的几个重要信息,有下:
UID:代表执行者的身份
PID:代表这个进程的代号
PPID:代表这个进程是由哪个进程发展衍生而来的,亦是父进程的代号
PRI:代表这个进程可被执行的优先级,其值越小越早被执行
NI:代表这个进程的nice值

3.PRI & NI

​ PRI:即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小 进程的优先级别越高;
​ NI:就是我们所要说的nice值了,其表示进程可被执行的优先级的修正数值;
​ PRI值越小越快被执行,那么加入nice值后,将会值得PRI变为:PRI(new)=PRI(old)+ nice 当nice值为负数的时候,那么该进程将会优先级值将变小,即其优先级会变高,则其越快被执行;调整进程优先级,在Linux下,就是调整进程的nice值;nice其取值范围是-20至19,一共40个级别;
注:linux下,PRI(old)默认是80

4.如何更改进程的优先级

1.用top命令更改已存在进程的nice

​ top命令经常用来监控linux的系统状况,是常用的性能分析工具,能够实时显示系统中各个进程的资源占用情况。
image-20240106202837457

​ 接下来会演示一遍top指令如何修改某个进程的nice值,我们先额外打开一个终端,执行watch -n 1 'ps -aux --sort -pcpu,+pmem | head -6'指令,并且查询得到该指令的pid为22043
image-20240106205005754

想要把watch这个进程的nice值修改了,我们可以在输入top命令后,输入“r”,此时会出现提示:
image-20240106203545018
在这里输入watch指令的PID22043,回车之后,在这里输入你想要修改的nice值,再回车,按q退出,进行查看

此时我们再使用 ps -al指令进行查看,可以发现watch指令的nice值已经修改成功了。

image-20240106204733659

2.用renice命令更改已存在进程的nice

​ 修改指令的格式 renice + 要将nice重新设置的值+进程的pid`

image-20240106225807231

注意:普通用户在通过renice进行修改时,权限是不够的,需要加上sudo

5.其他概念

竞争性:系统进程数目众多,而CPU资源只有少数,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰
并行:多个进程在多个CPU下分别,同时进行运行,这称之为并行
并发:多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发

八、环境变量

1.基本概念

​ 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数
​ 如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找
​ 环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性。

2.常见的环境变量

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

3.查看环境变量

我们可以通过echo命令来查看环境变量
echo $PATH/HOME/SHELL

image-20240106231128429

1.测试PATH

​ 我们在输入指令时(ls/pwd等)并没有输入相应的路径,只要指令正确就一定能运行,但是我们自己生成的可执行程序却要加上./(当前路径下)才可以被执行。这主要是因为系统在环境变量中找不到你当前可执行程序相应的路径。

image-20240106232201185

​ 我们通过echo $PATH查看环境变量,发现有许多路径,并且由(:)号隔开。当我们输入指令时会通过环境变量查找相应的路径。
image-20240106232707134

那如何让我们自己的可执行程序也像系统命令一样直接执行呢?

方法一:
直接将可执行程序拷贝到环境变量中的路径下面,但是不推荐这样做,如果你添加了,忘记删除了,后期在执行某些命令时,突然多出一些东西。污染环境

方法二
直接将可执行程序所在路径拷贝进PATH里面去。通过export指令+环境变量的名(export PATH)对环境变量进行新的设置
`export PATH=/home/ljh
但是这样设置会覆盖原来的环境变量,导致很多指令都用不了了
image-20240106233443739

但也不用担心,我们只要退出再重新启动就恢复了

​ 正确的做法是:
export PATH=$PATH:/home/ljh

image-20240106233850530

2.测试HOME

​ 为什么每次登录不同的用户,所在的家目录都是不一样的,就是因为环境变量HOME

普通用户:

image-20240106234302776

root用户

image-20240106234416500

3.测试SHELL

​ 我们在命令行所输入的指令,都是由命令行解释器进行解释的。我们可以通过查看环境变量SHELL来知道自己当前所用的命令行解释器。
image-20240106234802758

4.和环境变量相关的命令
1.echo指令

这个指令可以查看相关的环境的变量,还可以直接打印一些数据

image-20240106235302181

2.export指令

设置一个新的环境变量,还可以将本地变量导出环境变量;所谓的本地变量就相当于我们在C/C++中定义一个变量;

image-20240106235907813

set | grep myval显示本地变量
env | grep myval查看myval在不在环境变量中
export myval 通过export将本地变量转为环境变量

3.env指令

显示所有的环境变量

image-20240107015254365

4.set指令

显示本地定义的shell变量和环境变量

image-20240107015420623

5.unset指令

清除环境变量;除了环境变量外,还有本地变量,所谓的本地变量就相当于我们在C/C++中定义一个变量。如下图所示:
image-20240107015740746

5.环境的组织方式

​ 每个程序都会收到一张环境表,环境表是一个字符指针数组,每个指针指向一个以‘\0’结尾的环境字符串。
image-20240107015930169

6.通过代码获取环境变量
1.main函数命令行参数的了解

对于main函数我们已经很熟悉了,它其实可以带参数的,main函数有三个参数

int main(int argc,char* argv[],char* envp[]){}
//这里的三个参数就是命令行参数

我们先了解一下前两个参数的作用

#include <stdio.h>  
int main(int argc, char* argv[]){  
    for(int i = 0; i < argc; ++i){  
        printf("argv[%d]:%s\n",i,argv[i]);                 
    }                                                       
    return 0;                                               
} 

image-20240107020619413

​ 结合代码及运行结果可以看出,我们在命令行中只有一个命令,那么对于的数组(argv[])中就有一个元素(字符串“./a.out”),有多个携带的参数就有多个字符串。我们这里想说的是,当我们在命令行中敲出一个命令后通过带入不同的参数选项,会有不同的结果。我们通过模拟实现一下这样的效果。

#include <stdio.h>
#include <string.h>
int main(int argc, char* argv[]){
    if(argc != 2){
        printf("Using: %s -[a|h|ah]\n",argv[0]);
        return 1;
    }
    if(strcmp(argv[1],"-h") == 0){
        printf("hello world!\n");
    }
    else if(strcmp(argv[1],"-a") == 0){
        printf("hello linux!\n");
    }
    else if(strcmp(argv[1],"-ah") == 0){                   
        printf("hello world!\n");
        printf("hello linux!\n");
    }
    else{
         printf("hello C++!\n");
    }
return 0;
}

image-20240107102834148

通过上面的代码我们也实现了通过指令加选项的操作达到不同的效果,指令也
有很多选项,用来完成同一个命令的不同子功能,选项的底层使用的就是命令行参数。

2.main函数的环境变量获取

​ main函数除了有命令行参数,还有环境变量,也就是第三个参数。它是用来获取环境变量的

#include <stdio.h>
#include <string.h>
int main(int argc, char* argv[], char* env[]){
    for(int i = 0; env[i]; i++){
    	printf("%d->%s\n",i, env[i]);                       
    }
    return 0;
}

image-20240107103410423

3.通过第三方变量environ获取

​ 这是系统提供的一个获取环境变量的第三方变量,是一个二级指针
image-20240107103525616

#include <stdio.h>
#include <string.h>
int main(){
    extern char** environ;
    for(int i = 0; environ[i]; i++){
        printf("%d->%s\n",i, environ[i]);
    }
    return 0;
}

image-20240107103821512

7.通过系统调用获取环境变量(常用)

​ 以上两种获取环境变量太过于麻烦,我们最常用的还是通过系统调用来获取环境变量。使用getenv函数获取
image-20240107104024016

#include <stdio.h>
#include <stdlib.h>
int main(){
    printf("%s\n",getenv("PATH"));
    printf("%s\n",getenv("HOME"));
    printf("%s\n",getenv("SHELL"));                         
    return 0;
}

image-20240107104501650

注:环境变量是具有全局属性的,可以被子进程继承下去

#include <stdio.h>
#include <stdlib.h>
int main(){
    char * env = getenv("MYENV");
    if(env){
        printf("%s\n", env);
    }
    return 0;
}

image-20240107104844783

直接运行程序时,发现没有结果,说明该环境变量根本不存在
导出环境变量
export MYENV = "hello world"
再次运行程序,发现有结果了!说明:环境变量是可以被子进程继承下去的!
我们程序执行起来后(进程),其共享父进程bash的环境变量(bash的环境变量是系统给的),那么只有bash的环境变量有所改变,影响的就是全局

九、地址空间的阐述

1.程序地址空间

对于下面的图大家一定都不陌生,接下来通过以下的代码,来正确认识这张图
image-20240107105634151

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> 
int g_unval;
int g_val = 100;
int main(){                    
    int a = 10;                                        
    int b = 20;                        
    const char *s = "hello world";       
    printf("code addr:%p\n", main);       //代码区         
    printf("string rdonly addr:%p\n", s); //字符常量区
    printf("uninit addr:%p\n", &g_unval); //未初始化
    printf("init addr:%p\n", &g_val);     //已初始化
    char *heap = (char*)malloc(10);
    printf("heap addr:%p\n", heap);       //堆区
    printf("stack addr:%p\n", &s);        //栈区           
    printf("stack addr:%p\n", &heap);
    printf("stack addr:%p\n", &a);
    printf("stack addr:%p\n", &b);
    return 0;                           
} 

image-20240107110640475

​ 通过运行后的结果可以看出,空间所谓的分布情况确实如此,但接下来这段代码运行后的结果,会让你很诧异。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int g_val = 100;
int main(){
    if(fork() == 0){
        int ret = 5;
        while(ret){                    
            printf("hello--- %d g_val = %d &g_val = %p\n", ret, g_val, &g_val);
            ret--;                         
            sleep(1);
            if(ret == 3){
                printf("#####child更改数据#####\n");
                g_val = 200;
                printf("#####child更改数据完成#####\n");
            }   
        }                                          
    }                                  
    else{                  
        while(1){
            printf("I am father:g_val = %d &g_val = %p\n", g_val, &g_val);
        }                                                 
    }                                                     
    return 0;                                             
}

image-20240107111801668

​ 通过运行结果我们可以发现,子进程在修改了数据后,父进程并没有被修改掉,父进程数据和子进程数据不一样,但是地址却是一样的???

​ 通过上面的运行结果我们可以总结出:如果C/C++打印出来的地址是物理地址,那么上面的情况绝对不可能出现,所以这里的地址并不是物理地址,而是虚拟地址。

2.进程地址空间

​ 之前说程序的地址空间是不准确的,准确的应该说是进程的虚拟地址空间,每个进程都会有自己的地址空间,认为自己独占物理内存。操作系统在描述进程地址空间时,是以结构体的形式描述的,在Linux中这种结构体是struct mm_struct。它在内核中是一个数据结构类型,是具体进程的地址空间变量。
​ 这些变量就是每个空间的起始位置与结束位置,如下图所示:
image-20240107112426196

​ 进程地址空间就类似于一把尺子,每个空间都有对应的起始位置和结束位置。通过这个虚拟地址去间接访问内存

为什么不能直接访问物理内存?

​ 如果没有进程地址空间的加持,那么程序就会直接访问物理内存,没有区间可言,会存在恶意程序可以随意修改别的进程的内存数据,以达到破坏的目的。有些非恶意的,但是有Bug的程序也可能不小心修改了其他程序的内存数据,就会导致其他程序的运行出现异常。这种情况对用户来说是无法容忍的,因为用户希望使用计算机的时候,其中一个任务失败了,至少不能影响其他的任务。

3.如何通过虚拟地址访问物理地址

​ 每个进程都是独立的虚拟地址空间,两个独立进程的相互地址互不干扰,但是在物理上对每个进程可能也就分了一部分空间某个进程。
​ 每个进程被创建时,其对应的进程控制块和进程虚拟地址空间也会随之被创建。而操作系统可以通过进程的控制块找到其进程地址空间,通过页表对将虚拟地址转换为物理地址,达到访问物理地址的目的。

image-20240107113536038

​ 此时,我们来回答一下刚刚为什么g_val的值发生了变化,但是父进程与子进程的地址还是一样的。
image-20240107113720506

​ 写时拷贝:就是等到修改数据时才真正分配内存空间(物理内存空间),这是对程序性能的优化,可以延迟甚至是避免内存拷贝,当然目的就是避免不必要的内存拷贝。

总结

​ 简而言之,首先,程序数据加载到内存后,由操作系统分配进程PCB(task_struct和mm_struct(进程虚拟地址空间))和页表。此时我们的进程就算是创建好了。
虚拟地址的设计有什么好处:
​ 1.有了虚拟地址,每个进程都认为自己独占内存资源,这样对于操作系统来讲,也更加偏于管理进程。
​ 2.采用间接的地址访问方法访问物理内存。程序中访问的内存地址不再是实际的物理内存地址,而是一个虚拟地址,然后由操作系统将这个虚拟地址映射到适当的物理内存地址上。这样,只要操作系统处理好虚拟地址到物理地址的映射,就可以保证不同的程序最终访问的内存独占位于不同的区域,彼此没有重叠。
​ 3.如果没有进程地址空间的加持,那么程序就会直接访问物理内存,没有区间可言,会存在恶意程序可以随意修改别的进程的内存数据,以达到破坏的目的。反之有利于保护物理内存。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Li小李同学Li

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值