进程概念(详解)

2023-10-27

基本概念

一般课本上都会这样说:一个可执行程序被执行后就变成了一个进程。
站在操作系统的角度:进程是担当分配系统资源的实体。
如果说给进程一个准确的定义,应该是:
内核数据结构(后面内容会提到) + 该进程对应的代码和数据。

进程的描述-pcb

pcb

操作系统是一个软硬件资源管理的软件,那么相比进程也要被操作系统(OS)管理。
那么操作系统是如何对进程进行管理的呢?答案肯定是:先描述,在组织,对一个进程我们首先要用计算机语言对其进行描述,再利用相关的数据结构将其组织管理起来。
在操作系统的书籍上称描述进程的结构体为pcb,在linux操作系统下这个结构体叫做task_struct,这是在操作系统内核中创建的一种数据结构。但是一台计算机上会同时有多个进程(你可以打开你的任务管理器,看到许多进程正在跑着),操作系统是如何将这么多进程组织起来的呢?
是将各个进程的pcb(process contro block)利用链表这种数据结构对其组织起来。

在这里插入图片描述
操作系统也是在内存中的。

task_struct中内容分类

操作系统内核中创建pcb来完成对进程的管理,那么这个结构体里究竟都有什么内容呢?

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

这里的部分内容在后面会提到。

查看进程

查看进程的信息

  • /proc系统文件夹里查看
    这是一个内存级的文件目录,也就是启动机器后被加载到内存上的。
    在这里插入图片描述
    可以看到,这些蓝色数字(进程的PID)标示的文件夹,就是关于每个进程的信息。
    在这里插入图片描述
    进入到15290文件夹中,可以看到这里面的内容就是关于进程PID为15290的进程的。

  • ps -axj 命令查看
    在这里插入图片描述
    由于很多进程都在运行着,用ps -axj命令查看时非常难以分辨,所以通常配合 grep 指令来查看进程的信息。

ps -axj | head -1 && ps -axj | grep bash | grep -v grep
我们知道我们的命令行解释器就是一个一直在运行的进程,上面命令就是查询它的信息,为了去掉关于grep 这个进程的信息可以使用,grep -v选项将其去掉
在这里插入图片描述
在这里插入图片描述
可以看到如果不用grep -v将grep这个进程的信息也会被显示出来。

我们自己写的程序当被 ./ 被执行的时候也会变成进程,那么操作系统也会为其创建pcb,那么也应该能通过此命令查询到。

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

在这里插入图片描述
在这里插入图片描述

通过系统调用来查看进程的标识符

  • PID :进程id
  • PPID :父进程的id
    我们知道我们自己写的程序是通过bash来创建子进程执行的,所以使用命令行bash启动的程序,其父进程都是bash。
    在这里插入图片描述
    getpid/getppid这两个系统调用一个返回的是当前进程的id,一个返回的是当前进程父进程的id。
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <sys/types.h>
  4 
  5 int main()
  6 {
  7   pid_t id = getpid();
  8   pid_t pid = getppid();
  9   printf("当前进程的pid:%d,父进程的pid:%d\n",id,pid);                                                                                                          
 10   return 0;
 11 }
~

我们通过ps命令查看一下bash的pid与打印出来的是否一置。
在这里插入图片描述
在这里插入图片描述
可以看到test这个进程的父进程就是bash。

创建进程(fork)

用户可以通过fork这个系统调用来创建子进程的。
在这里插入图片描述
在这里插入图片描述
可以看到 fork() 这个函数很特殊,成功创建子进程后居然有两个返回值,给父进程返回子进程pid,给子进程返回 0,如果创建失败那么就返回 -1。
至于为什么会这样,在下面的进程地址空间中会讲到。

fork()函数调用后的变化:

  • fork之后,执行流会变成两个执行流。
  • fork执行之后父子进程,父子进程谁先执行是随机的由调度器决定先调度谁。
  • fork之后,fork之后的代码共享,通常我们采用if else 语句来进行分流,父子进程执行不同的代码
  • 不同进程之间是相互独立的,父子进程也是如此,这是如何实现的呢?
    对于代码:代码是只读的,父子进程公用一份代码谁读谁的代码互不影响。
    对于数据:当有一个执行流想要修改数据的时候会发生写时拷贝(下面的进程地址空间会提到),来保证进程的独立性。
    使用fork( )创建子进程的例子:
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <sys/types.h>
  4 #include <stdlib.h>
  5 int main()
  6 {
  7   int a = 100;
  8   pid_t id = fork();
  9   if(id == 0)
 10   {
 11     //child
 12     while(1)
 13     {
 14       --a;
 15       printf("pid: %d,ppid: %d,a = %d,&a = %p\n",getpid(),getppid(),a,&a);
 16       sleep(1);
 17     }
 18   }
 19   else if(id > 0)
 20   {
 21     //parent
 22     while(1)
 23     {
 24       printf("pid: %d,ppid: %d,a = %d,&a = %p\n",getpid(),getppid(),a,&a);                                                                                       
 25       sleep(1);
 26     }
 27   }
 28   else
 29   {
 30     perror("fork()\n");
 31     exit(1);
 32   }
 33   return 0;
 34 }

在这里插入图片描述

可以看到对于变量a,子进程中对其进行了修改,由于进程的独立性所以并不会影响到父进程中a变量的值,但是它们的地址确是一样的?这就说明这里的地址不会是物理地址,因为如果是物理地址那么地址一样,值就必然一样。(至于怎么回事?下面进程地址空间中会提到)。

杀掉进程(kill)

上面写的代码是一个死循环的代码,会一直跑下去,我们要如何终止该进程呢?

  • ctrl + c

  • kill -9 pid
    在这里插入图片描述

  • killall 进程名
    在这里插入图片描述

进程状态

在Linux操作系统下,进程的状态主要分为一下七种。

  • R 运行状态
    一个进程处于运行状态,那么就说明此进程的pcb维护在运行队列当中,正在被CPU或等待被CPU调度的状态,那么就是说可以同时存在多个运行状态。

ps -axj 指令查看进程的信息

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <sys/types.h>
  4 
  5 int main()
  6 {
  7   while(1)
  8   {
  9     ;
 10   }
 11                                                                                                                                                                  
 12   return 0;
 13 }

在这里插入图片描述
可以看到这个死循环的进程就处于R状态。

  • S 休眠状态
    S状态表明进程需要等待某种资源的就绪,处于一种阻塞状态,例如一个进程要访问显示器这个资源但是但是并不只是它这一个进程要访问显示器资源,所以OS会为每个硬件维护一个排队的队列,将等待需要这种资源的进程都维护在这个队列中。一旦进程所需的资源全部都就绪后,那么它就会被维护在CPU的运行队列中。
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <sys/types.h>
  4 
  5 int main()
  6 {
  7   int n = 0;
  8   scanf("%d",&n);                                                            
  9   return 0;
 10 }

在等待键盘输入的过程中,进程就处于睡眠状态。
在这里插入图片描述

  • D 不可中断休眠状态
    这个状态与S状态不同,这是一种不可中断的休眠状态,S状态的进程可以被OS杀死,但是D状态的进程是不可以被OS杀死的,只能等待其自己苏醒。而D状态通常是在向磁盘中高IO的状态,导致磁盘空间不足导致该进程为D状态,一般情况下是不会出现这种状态的。
  • T 停止状态
    T状态是处于一种暂停状态,它可以使OS认定该进程当前的行为可能存在危险等,该进程就会被OS暂停,或者用户可以手动对其发送信号对其进行暂停。

kill -l 指令 列出所有控制进程的信号

在这里插入图片描述
向运行的进程发送SIGSTOP信号,就会让该进程暂停处于T状态。(在T状态时发送SIGCONT指令可以让其继续运行)

kill -19 PID

在这里插入图片描述

kill -18 PID 使暂停的进程继续运行

  • t 追踪状态
    t状态使一种追踪暂停状态(tracing stop)这种状态的进程最常见的就是我们在使用gdb调试时,打一个断点如果不进行其他操作那么这个进程就会停在断点处。
    在这里插入图片描述

  • X 死亡状态
    当一个进程死亡一瞬间的时候就是死亡状态,但是它是一瞬间的,可能会观察不到,因为一个进程死亡后就会变成僵尸进程。

  • Z 僵尸状态
    进程死亡后的状态。一个进程死亡后,会处于僵尸状态如果其父进程不为其“收尸”的话,那么其会一直占用资源,造成内存泄漏。

特殊进程

特殊的进程主要分为两种一种是僵尸进程,另一种是孤儿进程。

僵尸进程

一个进程死亡后,会处于僵尸状态其进程控制块pcb会一直占用操作系统内核资源,所以某个进程死亡后,其父进程要对其资源进行释放也就是进程等待(后面会分享)。
下面演示一下僵尸进程:

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 #include <sys/types.h>
  5 
  6 int main()
  7 {
  8   pid_t id = fork();
  9   if(id == 0)
 10   {
 11     //child
 12     while(1)
 13     {
 14       printf("我是子进程,我的pid :%d,我的ppid: %d\n",getpid(),getppid());
 15       sleep(1);                                                              
 16     }
 17   }
 18   else if(id > 0)
 19   {
 20     //parent
 21     while(1)
 22     {
 23       printf("我是父进程,我的pid :%d,我的ppid: %d\n",getpid(),getppid());
 24       sleep(1);
 25     }
 26   }
 27   else
 28   {
 29     perror("fork");
 30     exit(-1);
 31   }
 32   return 0;
 33 }

在这里插入图片描述
在这里插入图片描述

这里有的人会有疑惑,明明两个进程都在运行状态,为什么查出来回事休眠状态呢?
这是由于CPU的速度太快了,因为这两个进程都会访问显示器资源,他们的大部分时间都会在显示器的等待队列中排队,而被CPU调度的时间是非常短的,因为CPU的速度极快。可以打个比方在100秒的时间内,这个进程的pcb有99秒都在显示器的等待队列中,只有1秒在CPU的运行队列中,而我们这样查询很难查询到其正处于被调度的状态,所以查出来的结果就是休眠状态,这是很正常的。

kill - 9杀死子进程

在这里插入图片描述
可以看到子进程就处于了Z(僵尸)状态。
但是我们也有办法,父进程通过进程等待的方式会为子进程”收尸“。

孤儿进程

当父进程先于子进程结束的时候,子进程会成为孤儿进程,但是会被PID为1的进程收养,这个PID为1的进程是操作系统。如果操作系统不对其收养,那么其结束的时候,没有人给其“收尸”,这样会造成内存泄漏。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

通过kill -9 杀死父进程,我们发现子进程的状态变为了S,与之前相比少了一个+,没有+表示此进程在后台运行无法通过ctrl C 终止,能通过kill 的方式将其杀死。

在这里插入图片描述
可以看到父进程先于子进程退出后,子进程会被操作系统收养(PID为1的进程)。子进程死亡后,操作系统会释放其相关资源,避免内存泄漏。

进程的优先级

之前提到进程运行就是其pcb维护在CPU的运行队列当中,但在这个队列当中是否存在优先级呢?先调度谁后调度谁。

ps -la 指令查看进程相关的信息

在这里插入图片描述

  • UID:代表执行者的身份
  • PID:这个进程的标识符
  • PPID:这个进程父进程的标识符
  • PRI:这个进程的优先级,值越小优先级越高
  • NI:代表这个进程的nice值

PRI与nice

PRI使进程的优先级,值越小优先级越高,nice值使对进程优先级的修正值,nice的取值范围是 -20 -19。
(new)PRI = (old)PRI + nice,所以nice取负值的时候,会提高进程的优先级。
注意: old PRI 是指从80开始的。
更改进程的优先级是修改该进程的nice值。

top 指令修改已经存在进程的nice值
1,top指令
2,输入r
3,输入要修改的进程的PID
4,输入新的nice值

在这里插入图片描述
如果是普通用户只能调整nice为正值。
在这里插入图片描述

  • nice -n指令 进程启动前调整进程nice值
    在这里插入图片描述
    在这里插入图片描述

  • renice指令 进程运行中调整nice值
    在这里插入图片描述
    普通用户使用renice要进行提权。

环境变量

定义

环境变量一般是指在操作系统中用来指定操作系统运行环境的一些参数

在我们安装许多软件的时候,都需要配置环境变量,你是否为此烦恼过,这环境变量就是是个啥东西?
例如你在下载Vs Code 的时候,给其配置gcc编译器的时候,就需要将gcc编译器在磁盘中的路径添加到环境变量表中,这其中的原理是什么呢?
在这里插入图片描述
我们发现bash中自带的ls指令直接敲ls就能执行,但是为什么我们自己写的mytest程序,直接敲mytest却会提示not found,必须加上 ./mytest 才能运行,这是为什么呢?

首先说明一下,Linux的指令与我们写的程序没有什么不同,Linux操作系统就是由C语言编写的,其中的大部分指令都是C程序,但是bash自带的指令例如ls这个可执行程序,它的地址储存在了环境变量PATH中,我们输入ls时,bash会自动去环境变量中去找ls的路径,并执行。而我们自己写的可执行程序,必须加./来指明其路径。

在这里插入图片描述
可以看到ls的路径是在 /usr/bin 目录下,为了验证上述说法,查看一下PATH环境变量看看其是否包含了此路径。
在这里插入图片描述
可以看到PATH环境变量中包含了许多路径,它们是以:分隔的。可以看到 /usr/bin 路径就在其中。

查看环境变量

  • env 指令

通过env指令可以查看所有的环境变量
在这里插入图片描述

  • environ 全局变量
    在进程中都会有environ这个环境变量,其类型是一个指针数组,其中维护了所有的环境变量。其中这个指针数组的最后一个有效字符串的下一个位置存的是NULL。
  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 #include <sys/types.h>
  5 extern char** environ;
  6 int main()
  7 {
  8 
  9   int i = 0;
 10   for(;environ[i]; i++)
 11   {
 12     printf("%d -> %s\n",i,environ[i]);                                                                                                                           
 13   }
 14   return 0;
 15 }

在这里插入图片描述

  • main函数的envp参数
    main函数也是可以传参的,应该见过的是main函数这两个参数main(int argc,char*[] argv),其实main函数还可以传环境变量的参数 envp其类型也是指针数组类型,

int main(int argc,char* argv[ ] ,char * envp[ ] );
envp中与environ全局变量的结构一致,其最后一个有效元素的下一个位置为NULL。

    1 #include <stdio.h>
    2 #include <stdlib.h>
    3 #include <unistd.h>
    4 #include <sys/types.h>
    5 
    6 int main(int argc,char* argv[],char *envp[])
    7 {
    8   int i = 0;
    9   for(;envp[i];i++)
   10   {
   11     printf("%d -> %s\n",i,envp[i]);                                                                                                                            
   12   }
   13 }

在这里插入图片描述

  • 通过调用 getenv( ) 获取某个环境变量

在这里插入图片描述

参数为环境变量的名称,我们知道环境变量是key - value的结构,key是其名称,value表示其内容。

  1 #include <stdio.h>  
  2 #include <stdlib.h>  
  3 #include <unistd.h>  
  4 #include <sys/types.h>  
  5 
  6 
  7 int main()
  8 {
  9   char* env_path = getenv("PATH");
 10   if(env_path != NULL)
 11     printf("PATH : %s\n",env_path);                                                                                                                              
 12   return 0;
 13 }

在这里插入图片描述
与前面的方法不同,getenv一次只能获取一个环境变量。

设置环境变量(export)

在之前讲到,Linux指令不用加 ./只用其名字就可以直接运行,是因为其路径存在了PATH环境变量中,当运行该程序时系统会自动去PATH环境变量中去找。
那可不可以将自己写的可执行程序的路径添加到PATH中呢?
答案是可以的。

export PATH = $PATH:要添加的路径
$PATH的意思是显示PATH环境变量中的内容,如果不加这句之前的PATH环境变量会被覆盖掉,而我们添加路径其实是追加。

在这里插入图片描述
可以看到成功为PATH环境变量添加了一个新的路径,下面来验证一下test目录下自己写的程序可不可以不加./运行。
在这里插入图片描述
神奇的一幕发生了,居然是可以的!!!

  • 环境变量是一张内存级的表,也就是说虽然你当前修改了PATH环境变量的内容,但是你重启机器的时候PATH环境变量又会变回原来的内容。

在这里插入图片描述
重启机器后发现确实之前更改的PATH环境变量又恢复了最初的内容。

环境变量这张表是内存级的,说明在开机的时候会被加载到内存,那么是从哪里加载的呢?

在这里插入图片描述
家目录下有关于环境变量的文件 .bashrc .bash_profile
在这里插入图片描述
在这里插入图片描述
这两个配置文件是 /etc 路径下的。
在这里插入图片描述
可以看到这些文件里都是关于一些环境变量的配置,当开机时环境变量就是从这些系统的配置文件中加载到内存的。

本地变量与环境变量

命令行式的定义变量

我们在bash这个命令行解释器下是可以直接定义变量的。
在这里插入图片描述

  • set指令

set指令会将所有变量打印出来,包括环境变量和本地变量

在这里插入图片描述
这里只截取了一部分,可以看到刚刚定义的myval变量也被打印了出来。

set指令与env指令的区别是:
env指令只打印环境变量
set指令打印环境变量与本地变量

环境变量与本地变量的区别

  • 环境变量具有全局属性,会被子进程继承
  • 环境变量表是维护在shell内部的
  • 本地变量只存在于当前shell进程
  • 可以通过export 本地变量 将其倒成环境变量

在这里插入图片描述
可以看到通过export成功的将myval这个本地变量倒成了环境变量。

  • 验证环境变量会被子进程继承
    我们写的任何一个程序都是shell的子进程,所以可以通过mytest这个可执行程序,调用getenv获取myval环境变量。
  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 #include <sys/types.h>
  5 
  6 
  7 
  8 int main()
  9 {
 10   char* env_myval = getenv("myval");
 11   if(env_myval != NULL)
 12     printf("myval : %s\n",env_myval);                                                                                                                            
 13   return 0;                                                                                                   
 14 }   

在这里插入图片描述

  • 验证本地变量不能被子进程继承
    首先定义一个本地变量,通过在mytest这个shell的子进程中调用getenv查看其环境变量表中有无value这个变量。
    在这里插入图片描述
  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 #include <sys/types.h>
  5 
  6 
  7 int main()
  8 {
  9   char* env_value = getenv("value");
 10   if(env_value != NULL)
 11     printf("value : %s\n",env_value);
 12   else 
 13     printf("Not have value\n");                                                                                                                                  
 14   return 0;                                                                                                                                                
 15 } 

在这里插入图片描述

  • unset 指令取消本地和环境变量
    刚刚我们将myval倒成了环境变量,使用unset myval 可将其取消掉。

在这里插入图片描述
同样unset可以将value这个本地变量取消掉。
在这里插入图片描述

命令行参数

我们在使用Linux指令的时候,通常一个指令会搭配不同的选项使用,例如ls -a ,ls -l等等,不同的搭配实现的功能也不同。但这是如何实现的呢?

在上面分享环境变量的时候提到main函数的参数,其中有两个参数 int argc,char* argv[ ]
argv是一个表结构,有argc个元素,会将我们输入的不同选项存入其中,从而达到不同的选项实现不同的功能的目的。argv[0]存的是指令的名称,后序存的是指令选项。

下面就运用这个规则,写出一个具有不同选项的程序:

  1 #include <stdio.h>
  2 #include <string.h>
  3 int main(int argc,char* argv[])
  4 {
  5   if(argc == 2)
  6   {                                                                                                                                                              
  7     if(strcmp(argv[1],"-a") == 0)
  8       printf("功能1\n");
  9     else if(strcmp(argv[1],"-b") == 0)
 10       printf("功能2\n");
 11     else if(strcmp(argv[1],"-c") == 0)
 12       printf("功能3\n");
 13   }
 14   return 0;
 15 }

在这里插入图片描述

进程地址空间

简单理解

在这里插入图片描述
这是在上面介绍fork函数的时候留下的一个问题,为什么统一个变量地址都是相同的,但内容却不一样,很显然不是物理地址,说明我们使用的地址是虚拟地址。

一个C/C++程序员通常把内存分为堆区,栈区,数据段,代码段等等。
在这里插入图片描述
这些其实都不是真正的物理内存,而是在进程控制块pcb中维护的进程地址空间(也就是所说的线性空间或者虚拟空间)。

  • 进程地址空间是维护在pcb中一个数据结构mm_struct{}。
  • 地址空间是一个线性区域,其空间区域的划分只需要指明起始地址和终止地址。

对于32为机器来说地址空间中存在一个线性范围从 0x00000000 - 0xffffffff这样每一个地址对应一个字节,由于这些地址是线性的故称为线性地址空间 。
而不同的区域其实就是对这些线性地址进行区域的划分。
在这里插入图片描述

  • 例如堆区栈区在调整区域大小的时候,起始就是修改了边界值。
  • 数据和代码只会存在物理内存中,但是进程直接用的是虚拟地址,在虚拟地址和物理地址间有一个页表的结构,完成从虚拟地址到物理地址的映射。
    在这里插入图片描述

父进程通过fork创建出子进程,子进程的pcb中的大部分内容都是从父进程的pcb中拷贝的,也就是说父进程中的某个变量a的地址,在子进程中地址是不变的。
但是为了保证进程的独立性,父子进程不能互相干扰,由于他们中a变量的地址是相同的,不能因为一个进程同修改了a变量从而影响另一个进程,所以在修改数据的时候会发生写时拷贝。
写时拷贝:当父子某个进程修改数据时,OS会先在物理内存中新开辟一块空间,拷贝一份原来的数据,然后通过页表修改虚拟地址到物理地址的映射关系,从而保证了进程的独立性。

  • 那么现在也就能理解fork为什么会有两个返回值?
    其实就是发生了写时拷贝,在fork函数return的时候其实已经完成了fork函数的主题内容,
    那么此时就已经创建出来了子进程,fork函数之后的代码会共享,那么return也会返回两次,return的本质就是写入,写入就是对原有数据做更改,那么就会发生写时拷贝,从而就形成了父子进程都会接收到返回值,且返回值的内容不同,但是存放返回值变量的地址相同(虚拟地址)。

深入理解

malloc的本质

在说明malloc本质之前首先,要明确三点:
1,进程在向OS申请内存空间的时候,OS不会立马将空间的使用权交给进程,而是在进程需要使用的时候在为其分配内存。
2,OS是不允许出现任何不高效的行为的。
3,如果进程申请了内存,但是其没有立马使用,在进程申请之后与使用之前这段时间内,进程没有使用该空间,别的进程也不能使用该空间,这无疑就是一种不高效的行为,OS是不会允许这样的行为出现的。
malloc的实现过程:
进程中需要申请内存的时候,首先OS会在虚拟地址空间上为其分配内存,当进程需要用到这块空间的时候,OS会为其在物理内存上开辟,并将使用权交给进程(这种操作叫做缺页中断),这无疑大大提高了OS的效率。

编译器同样会遵守虚拟地址空间的规则

编译器在编译代码的时候,其内部有这样的虚拟地址嘛? 答案是有的。

objdump -S a.out

上面的指令会对可执行程序进行反汇编,在反汇编中我们会发现就形成了虚拟地址。
在这里插入图片描述
也就是说在编译的阶段就有了数据段代码段这样的概念,在被加载到内存的时候,是整块加载的。
在CPU执行代码的时候,首先会通过某种操作找到第一条语句的物理地址,执行玩这条语句的时候,会获取下一条语句的地址,而CPU获取的都是虚拟地址,然后通过页表的映射找到物理地址,这样就形成了一个闭环。

使用虚拟地址空间的好处

1, 防止地址随意访问,保护物理内存和其他进程

假设没有虚拟地址空间,那么CPU执行代码的时候直接与物理内存交互,如果你的代码有问题出现了野指针的问题,那么CPU对野指针指向的物理空间做相关操作的时候,如果这个地址是其他进程的地址,那么这就会破坏了其他的进程,这是非常不安全的。
而有了虚拟地址空间以及页表的存在,相当于增加了一层软件层做保护,CPU得到的地址都是虚拟地址,在通过页表做映射的时候,页表会对地址做相关的检查,避免出现上面的哪种问题。

2,进程的管理与内存管理解耦

在管理进程中使用的地址都是虚拟地址,CPU并不关心其真实的物理地址,这样进程的管理和内存的管理就达到了解耦,两个互相不受各自的影响。

3,可以让进程以统一的视角看待自己的代码和数据

对于每个进程而言,他能看到的都是其虚拟地址空间,并且会认为CPU单独的为它服务,并不关心真实的物理地址,从而达到了使进程以统一的视角看待自己的代码和数据。

进程的代码和数据会一直在内存中嘛

进程的代码和数据被加载到内存时,并不会一下都加载到内存中,而是虚拟地址中用的部分代码和数据会被加载到内存当中,这样使内存的利用率提高。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

进程概念(详解) 的相关文章

  • 使用 sh 运行 bash 脚本

    我有 bash 脚本 它需要 bash 另一个人尝试运行它 sh script name sh 它失败了 因为 sh 是他的发行版中 dash 的符号链接 ls la bin sh lrwxrwxrwx 1 root root 4 Aug
  • iptables通过注释删除特定规则

    我需要删除一些具有相同评论的规则 例如 我有带有 comment test it 的规则 所以我可以像这样获得它们的列表 sudo iptables t nat L grep test it 但是我怎样才能删除所有带有注释 测试它 的 PR
  • 有谁知道在哪里定义硬件、版本和序列号。 /proc/cpuinfo 的字段?

    我想确保我的 proc cpuinfo 是准确的 目前它输出 Hardware am335xevm Revision 0000 Serial 0000000000000000 我可以在代码中的哪里更改它以给出实际值 这取决于 Linux 的
  • 为arm构建WebRTC

    我想为我的带有arm926ej s处理器的小机器构建webrtc 安装 depot tools 后 我执行了以下步骤 gclient config http webrtc googlecode com svn trunk gclient s
  • 在我的 index.php 中加载 CSS 和 JS 等资源时出现错误 403

    我使用的是 Linux Elementary OS 并在 opt 中安装了 lampp My CSS and JS won t load When I inspect my page through browser The console
  • 多处理:仅使用物理核心?

    我有一个函数foo它消耗大量内存 我想并行运行多个实例 假设我有一个有 4 个物理核心的 CPU 每个核心有两个逻辑核心 我的系统有足够的内存来容纳 4 个实例foo并行但不是 8 个 此外 由于这 8 个核心中的 4 个是逻辑核心 我也不
  • 查找哪些页面不再与写入时复制共享

    假设我在 Linux 中有一个进程 我从中fork 另一个相同的过程 后forking 因为原始进程将开始写入内存 Linux写时复制机制将为进程提供与分叉进程使用的不同的唯一物理内存页 在执行的某个时刻 我如何知道原始进程的哪些页面已被写
  • Linux 上有关 getBounds() 和 setBounds() 的 bug_id=4806603 的解决方法?

    在 Linux 平台上 Frame getBounds 和 Frame setBounds 的工作方式不一致 这在 2003 年就已经有报道了 请参见此处 http bugs java com bugdatabase view bug do
  • 如何使用 GOPATH 的 Samba 服务器位置?

    我正在尝试将 GOPATH 设置为共享网络文件夹 当我进入 export GOPATH smb path to shared folder I get go GOPATH entry is relative must be absolute
  • “make install”将库安装在 /usr/lib 而不是 /usr/lib64

    我正在尝试在 64 位 CentOS 7 2 上构建并安装一个库 为了这个目的我正在跑步 cmake DCMAKE BUILD TYPE Release DCMAKE INSTALL PREFIX usr DCMAKE C COMPILER
  • 在两次之间每分钟执行一次 Cronjob

    我需要在 crontab 中每分钟运行一个 bash 脚本8 45am and 9 50am每天的 Code 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 8 home pull sh gt ho
  • 我如何知道 C 程序的可执行文件是在前台还是后台运行?

    在我的 C 程序中 我想知道我的可执行文件是否像这样在前台运行 a out 或者像这样 a out 如果你是前台工作 getpgrp tcgetpgrp STDOUT FILENO or STDIN FILENO or STDERR FIL
  • Urwid:使光标不可见

    我正在使用 urwid 它是一个用于在 ncurses 中设计终端用户界面的 Python 框架 但有一件事我在 urwid 中无法做到 而这在 Curses 中很容易做到 使光标不可见 现在 选择按钮时光标是可见的 而且看起来很丑 有办法
  • Windows CE 与嵌入式 Linux [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 现在我确信我们都清楚 Linux 与 Windows 桌面的相对优点 然而 我对嵌入式开发世界的了解却少得多 我主要对行业解决方案感兴
  • 高效的内存屏障

    我有一个多线程应用程序 其中每个线程都有一个整数类型的变量 这些变量在程序执行期间递增 在代码中的某些点 线程将其计数变量与其他线程的计数变量进行比较 现在 我们知道在多核上运行的线程可能会无序执行 一个线程可能无法读取其他线程的预期计数器
  • 捕获实时流量时如何开启纳秒精度?

    如何告诉 libpcap v1 6 2 将纳秒值存储在struct pcap pkthdr ts tv usec 而不是微秒值 捕获实时数据包时 Note This question is similar to How to enable
  • Mcrt1.o和Scrt1.o有什么用?

    我坚持使用以下两个文件 即 Mcrt1 o 和 Scrt1 o 谁能帮我知道这两个文件的用途 如何使用它 我们以 gcrt1 o 为例 在使用 pg 选项编译进行性能测试时非常有用 谢谢 表格的文件 crt o总是 C 运行时启动代码 大部
  • 警告:请求的映像平台 (linux/amd64) 与检测到的主机平台 (linux/arm64/v8) 不匹配

    警告 请求的映像平台 linux amd64 与检测到的主机平台 linux arm64 v8 不匹配 并且未请求特定平台 docker 来自守护程序的错误响应 无法选择具有功能的设备驱动程序 gpu 我在 mac 上尝试运行此命令时遇到此
  • Awk - 计算两个文件之间的每个唯一值和匹配值

    我有两个文件 首先 我尝试获取第 4 列中每个唯一字段的计数 然后匹配第二个文件的第二列中的唯一字段值 File1 第 4 列的每个唯一值和 File2 第 2 列包含我需要在两个文件之间匹配的值 所以本质上 我试图 gt 如果 file2
  • Monkeyrunner/jython 中未找到 JDBC 驱动程序错误

    我需要在中插入一些东西DB 我在用着JDBC as a connector jython the script mysql数据库和脚本正在运行CentOS 我的代码看起来像这样 from com android monkeyrunner i

随机推荐

  • Qtcreator远程调试出现“The selected build of GDB does not support Python scripting.It cannot be used .."...

    版权声明 本文为博主原创文章 未经博主允许不得转载 https blog csdn net aristolto article details 77370853 之前使用的是Qt4 7后来换成了Qt5 x Qtcreator的版本使用4 0
  • 基数统计—— HyperLogLog 算法

    目录 基数计数基本概念 基数计数方法 B树 bitmap 概率算法 HLL 直观演示 HLL 的实际步骤 算法来源 N次伯努利过程 解释 LogLogCounting 均匀随机化 分桶平均 偏差修正 误差分析 算法应用 误差控制 内存使用分
  • 无线传感网WSN

    第一章 绪论 WSN定义 无线传感网络是由大量的静止或移动的传感器以自组织和多跳的方式构成的无线网络 WSN系统组成 传感器节点 汇聚节点和管理节点 WSN的三个基本要素 传感器 感知对象 用户 观测者 WSN特点 1 自组织性 2 以数据
  • docker添加新的环境变量_在Dockerfile中,如何更新PATH环境变量?

    I have a dockerfile that download and builds GTK from source but the following line is not updating my image s environme
  • linux下添加简单的开机自启动脚本

    在rc local脚本中添加开机自启动程序 1 编辑文件 etc rc local vim ect rc local 2 编辑完 etc rc local需要添加执行权限 否则调用失败 chmod x ect rc d rc local 3
  • 信息学奥赛C++语言: 验证子串

    题目描述 输入两个字符串 验证其中一个串是否为另一个串的子串 输入 输入两个字符串 每个字符串占一行 长度不超过200且不含空格 输出 若第一个串s1是第二个串s2的子串 则输出 s1 is substring of s2 否则 若第二个串
  • Mac开发环境搭建

    一 jdk安装 1 1 jdk下载 首先查看你的Mac 系统是否已经安装了JDK 打开终端窗口并输入java version 如果没有安装过jdk会提示需要安装 Java Downloads Oracle 小技巧 mac如何打开终端 Com
  • CTFshow刷题记录

    https shimo im docs hJvkwcW8XvPxqyQK ctfshow misc
  • open【部署、使用教程】

    目录 1 创建证书 2 安装openVPN Server端并配置 3 将证书移动到相对路径 4 开启内核转发功能 否则会无法启动openVPN 5 启动服务 加入开机自启 6 启动后服务端会生成一个tun0的虚拟网卡 用于不同网段之间相互通
  • 让CSS flex布局最后一行列表左对齐的N种方法

    写在前面 精简版 可以直接移步我的另一篇博客 适用于行列数都不确定的情况 https blog csdn net HD243608836 article details 129854063 一 justify content对齐问题描述 在
  • 通信总线协议一 :UART

    文章目录 通信总线协议一 UART 1 通信基础 1 1 并行 串行 1 2 单工 双工 1 3 同步 异步 1 4 波特率 2 Uart通信协议 3 硬件连接 4 uart存在的问题 通信总线协议一 UART 1 通信基础 1 1 并行
  • 如何在线将Ubuntu 18.04升级到Ubuntu 20.04

    将Ubuntu 18 04升级到Ubuntu 20 04 在将系统升级运行到下一个主要发行版本之前 请确保已备份当前系统 以使您可以恢复原状 以防万一 备份过程不在本指南的范围内 运行系统更新 首先将Ubuntu 18 04系统软件包更新和
  • 深入学习jquery源码之trigger()与triggerHandler()

    深入学习jquery源码之trigger 与triggerHandler trigger type data 概述 在每一个匹配的元素上触发某类事件 这个函数也会导致浏览器同名的默认行为的执行 比如 如果用trigger 触发一个 subm
  • C#异步编程案例学习——异步加载大资源文件1 之 使用 BeginInvoke 与 EndInvoke

    C 异步编程案例学习 异步加载大资源文件1 之 使用 BeginInvoke 与 EndInvoke C 中 BeginInvoke 与 EndInvoke 的一个简单的使用案例 异步编程模型模式 APM 异步编程中的 BeginInvok
  • CentOS 6.4利用xampp安装bugfree3

    1 下载xampp 安装 http www apachefriends org zh cn xampp html 直接执行 run文件安装 默认会安装到 opt lampp 2 启动xampp root localhost opt lamp
  • 米哈游服务器位置,米哈游(米哈游账号中心系统)

    只要绑定了手机号就不要紧了 别人也无法用这个邮箱改密码 别人用也是无法验证 去官网下载崩坏3 不要在小米游戏这种第三方应用市场下载 有人买了米哈游的账号被找回吗 我买的三无号直接登不上 登陆的时候提示说没有这个账号 所以最好不要买 自己注册
  • 对SFBC空频编码和FSTD-SFBC频率切换分集与空频编码的MIMO系统误码率matlab仿真

    目录 一 理论基础 二 核心程序 三 测试结果 一 理论基础 Space Frequency Block Code SFBC 是TD LTE系统中的一种抗干扰技术 其基本原理与Wimax中基于Alamuti 编码的STBC类似 LTE标准中
  • 服务器如何安装 宝塔国外版本

    linux服务器如何安装 宝塔国外版本 centos安装方式 yum install y wget wget O install sh http www aapanel com script install 6 0 en sh bash i
  • fiddlerJScript脚本工具类,轻松写出fiddler 脚本,完成数据抓取

    fiddler是常用的抓包工具 fiddlerJScript脚本是用JScript NET 编写的 使用起来和JS 还是有很多区别的 目前网络上的文章比较分散 有些内容也比较旧 对新手不友好 所以自己动手写了一个工具类 封装了一些常用的方法
  • 进程概念(详解)

    进程概念 基本概念 进程的描述 pcb pcb task struct中内容分类 查看进程 查看进程的信息 通过系统调用来查看进程的标识符 创建进程 fork 杀掉进程 kill 进程状态 特殊进程 僵尸进程 孤儿进程 进程的优先级 PRI