进程概念(详细版)

news2024/11/15 5:57:00
进程的概念

本文主要介绍进程的相关知识

文章目录

    • 认识冯诺依曼体系结构
    • 操作系统的基本概念
      • 操作系统的作用是什么
      • 系统调用和库函数相关概念
    • 进程基本概念
    • 描述进程进程控制块(PCB)
      • task_struct 结构体
    • 进程是如何被操作系统管理起来的
      • 先描述
      • 再组织
      • 描述好,组织好,才好管理
    • 进程的查看
      • 获取进程标识符
        • getpid()和getppid()
    • 进程的创建
        • fork()创建进程
    • 进程的状态
        • 操作系统原理中的进程状态
        • 进程运行和进程终止
        • 进程阻塞
        • 进程挂起
        • 进程
      • R (running)运行状态
      • S (sleeping)睡眠状态
      • D (disk sleep) 深度睡眠状态
      • T (stopped) 暂停状态
      • Z (zombie) 僵尸状态
      • X (dead) 死亡状态
      • 僵尸进程
        • 僵尸进程的危害
      • 孤儿进程
    • 进程优先级
      • 概念
      • 如何查看进程优先级
      • PR(process priority)和NI (nice)是什么?
      • 注意点
      • 如何改变(调节)进程优先级
    • 环境变量
      • 概念:
      • 常见环境变量
      • 环境变量表
      • 如何查看环境变量
      • 如何获取环境变量
        • 三种方式
        • 通过main函数的参数获取
        • 通过第三方变量environ获取
        • 通过系统调用获取
          • getenv()获取环境变量
          • putenv()设置环境变量
      • 环境变量的全局属性
        • 本地变量
        • 环境变量的定义
    • 进程地址空间
        • 程序地址空间
        • 进程地址空间
        • mm_struct()结构组成
        • 为什么需要有虚拟地址这个东西呢?
        • 感知地址空间的存在

认识冯诺依曼体系结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存失败,源站可能有防盗链机制,建议将图片保存下来直接上传下上传(iHNmXu6tRsGe-1677761095381)(C:\Users\华哥\Pictures\素材\20190318201755609.png)(C:\Users\华哥\Pictures\素材\20190318201755609.png)]

几乎所有的计算机都是遵守冯诺依曼体系的,计算机是由一个个硬件组成的,配套着操作系统就可以发挥出其功能。

冯诺依曼体系将计算机的硬件分成了主要的三大块

  • 1.输入输出设备:输入设备有键盘鼠标,写字板,网卡等;输出设备有显示器 打印机等
  • 2.cup(中央处理器,由运算器和控制器组成),主要完成一些算数运算和逻辑运算。
  • 3.存储器,也就是内存,内存的存在是为了适配外设和CPU速度不均的问题。

为什么要有内存这个东西呢?

从技术角度分析

cpu的运算速度 > 寄存器的速度 > 缓存的速度 > 内存的速度 > 外设的速度(磁盘等)

因为数据大多都是需要cpu计算的 但是输入输出设备的速度和cpu的速度完全不是一个级别的,cpu速度远远快于外设速度,那么需要计算的数据直接交给cpu处理的话就会因为外设的速度慢而使得cpu的效率发挥不出来,所以cpu都是不和外设直接打交道的,都是通过内存来交流的,因为内存的速度相比外设快,外设可以将数据加载到内存,然后让cpu找内存拿数据,处理完后返回给内存,再回到外设,这样设计就可以达到视频外设和cpu速度不均的问题,提高效率。

看到这里我们可能会有一个疑惑,cpu和外设速度相差大就让外设加载数据到内存,那么外设加载的速度还是很慢啊,加载到内存也还是会很慢啊,那cpu还是会需要等待内存给它提供来自外设的数据,效率不还是没有提升吗?

这里举例说明吧,我们计算机是有预装载的机制的,就像我们开机的时候,需要等上个几十秒,这就是计算机在把操作系统加载到内存,有了操作系统计算机才可以运行起来,这个等待的过程就是预装载,刚开机会觉得电脑有些卡,但是过了一段时间就很顺畅,这就是因为计算机在加载数据到内存里;还有就是我们在写一个程序的时候会编译运行,运行之前这个程序一定是要被加载到内存的,但是并不是我们什么时候运行就什么时候加载,而是计算机会提前把我们写的代码加载到内存中,这就是预装载,可以是的数据提前被加载到内存,提高效率。(预装载是体系结构规定的)

操作系统的基本概念

任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)。笼统的理解,操作系统包括:

  • 内核(进程管理,内存管理,文件管理,驱动管理)
  • 其他程序(例如函数库, shell程序等等)

操作系统的作用是什么

操作系统生来就是为了管理软硬件资源的,是一个搞管理的软件

系统调用和库函数相关概念

  • 操作系统可以向用户提供服务,但是操作系统不会把自己完全地暴露给用户,而是向用户提供一些接口,供用户开发使用,这些接口就叫做系统调用。
  • 系统调用在使用上功能比较基础,对用户的要求相对较高,所以开发者可以将系统调用适度封装,形成库,有了库,就很有利于上层用户或开发者进行二次开发了。库函数也就是对系统调用的封装的产物。

查看源图像

思考:c/c++代码可以在windows和linux不同的系统下跑起来的,但是不同的系统的系统接口(系统调用)是不同的,那在两个系统上跑的都是一样的代码(c、c++代码),代码的执行结果为什么是一样的呢?

这就要说到c/c++的库了,库会帮我们分辨不同的平台,进而使得我在linux下就是调用的linux的系统调用,在Windows下就是调用的windows的系统调用,是类似多态的原理的,调用printf语句,在linux下就会调linux的对应的系统调用将数据打印到显示器,反之亦然。同一个对象执行相同动作,表现出不同的结果,这就是多态。

进程基本概念

进程的概念有两个层面上的解释:

教科书上的解释:进程是程序的一个执行实例,是正在执行的程序。

内核上的观点:担当分配系统资源(cpu 时间,内存)的实体。

描述进程进程控制块(PCB)

进程的信息会被放到一个叫进程控制块的数据结构中,可以理解成它是进程属性的集合。linux中的PCB实际上是一个叫做task_struct的结构体

task_struct 结构体

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

task_struct中的内容

  • 标示符: 描述本进程的唯一标示符,用来区别其他进程。
  • 状态: 任务状态,退出代码,退出信号等。
  • 优先级: 相对于其他进程的优先级。
  • 程序计数器: 程序中即将被执行的下一条指令的地址。
  • 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
  • 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
  • I/ O状态信息: 包括显示的I/O请求,分配给进程的I/ O设备和被进程使用的文件列表。
  • 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
  • 其他信息
//截取源码部分
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 */
    /* task state */
	int exit_state;
	int exit_code, exit_signal;
	int pdeath_signal;  /*  The signal sent when the parent dies  */
	/* ??? */
	unsigned int personality;
	unsigned did_exec:1;
	unsigned in_execve:1;	/* Tell the LSMs that the process is doing an
				 * execve */
	unsigned in_iowait:1;


	/* Revert to default priority/policy when forking */
	unsigned sched_reset_on_fork:1;

	pid_t pid;//标识符
	pid_t tgid;
    //...
};

进程是如何被操作系统管理起来的

六字真言:“先描述,再组织”

操作系统上跑的是一个个程序,也就是进程,那么操作系统就是要管理这些进程,怎么管理呢?

进程有它的对应的属性(下面会有介绍),因为linux操作系统是用c语言写的,所以进程的属性是用结构体来描述的(先描述),这些结构体会被一个双链表链接起来,所以对进程的管理也就演变成了对数据结构的管理。

先描述

将进程用一个数据结构描述,linux中用task_struct 结构体来描述,将进程的所有属性都包含在这个结构体中(里面会有指针指向进程的代码和数据)。一个进程的组成就是其task_struct 结构体和其对应的代码和数据。

再组织

将一个个进程的数据结构组织在一起,linux中使用双链表将这些结构体一个个连起来,完成对进程数据结构的组织。

描述好,组织好,才好管理

管理进程就是变成了对数据结构的管理,管理好这些数据结构就可以管理好进程!!!

进程在内存中的样子:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cwsCeyYl-1677761095387)(进程概念/image-20221010005652659.png)]

如何将进程运行起来:

在这里插入图片描述

进程的查看

介绍几种方式:

一、ps命令查看

ps axj | grep 文件名 | grep -v grep

将proc程序跑起来,形成一个进程
在这里插入图片描述

用上面的命令进行查看进程!
在这里插入图片描述

二、通过/proc系统文件夹进行查看

这里的查看某个进程是需要明确该进程的pid才可以进行查看的,因为在/proc文件夹中大多都是存的进程的pid,通过pid来标识进程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pHAqbk2Z-1677761095396)(C:\Users\华哥\AppData\Roaming\Typora\typora-user-images\image-20221009002430289.png)]

三、通过top命令查看

在这里插入图片描述

获取进程标识符

getpid()和getppid()

man 3 getpid

在这里插入图片描述

getpid()是获取进程本身的标识符 pid

getppid()是获取当前进程的父进程的 pid

代码演示:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
  while(1)
  {
    printf("我是一个进程!\n,我的pid:%d  ppid:%d\n",getpid(),getppid());
    sleep(1);
  }
  return 0;
}

在这里插入图片描述

进程的创建

fork()创建进程

注意的点:

  • fork()是有两个返回值的 你没听错!如果创建成功会返回子进程的pid给父进程,会给子进程返回给0;如果创建失败就会返回-1。

为什么对父进程就是返回子进程的pid,对创建出的子进程就是返回的0呢?

因为一个父进程可以拥有多个子进程,所以需要父进程需要去标识每个子进程,那么就需要知道子进程的pid,故调用fork()函数创建子进程,fork()会给父进程返回创建出来的子进程的pid;对于子进程,它只有一个父进程,并且一个进程被创建出来就是知道自己和其父亲的pid的,所以fork()对创建出来的进程的返回值为0即可;当创建失败就返回-1;

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
  pid_t id=fork();
  while(1)
  {
  if(id>0)
  {
    printf("我是父进程! 我的pid:%d  ppid:%d \n",getpid(),getppid());
  }
  else if(id==0)
  {
    printf("我是子进程! 我的pid:%d  ppid:%d \n",getpid(),getppid());
  }
  else 
  {
    printf("创建失败!\n");
  }
  sleep(1);//循环一次休眠一秒
  }
  return 0;
}

可以看出运行的结果是交替打印父子进程的信息 因为这里调用fork()创建出了子进程,那么就会有两个进程,从而会使得程序有两个执行流,父子进程都会执行各自的执行流,所以在循环的作用下就会出现交替打印父子进程信息的结果,这也是父子进程执行各自执行流的结果。

在这里插入图片描述

  • 父子进程代码是共享的,数据会各自开辟空间 私有一份(会有写时拷贝,也就是当子进程要改变父进程中的数据就会独立开辟一块空间,建立自己的数据,使得自己不会影响到父进程的数据,实现进程的独立性)

  • fork()一般都会用if else 语句实行分流操作 使得子进程去完成具体的某项工作 而不是和父进程执行一样的操作
    在这里插入图片描述

思考:上面的代码中的pid_t id =fork() 为什么一个id可以接收不同的返回值呢?

进程的状态

操作系统原理中的进程状态

进程运行和进程终止

进程运行就是进程在运行队列中准备就绪,等待cpu执行其代码的状态;进程终止就是进程通过return或exit或遇到异常退出,随时等待操作系统回收其资源的状态。

进程阻塞

什么是进程阻塞?

一个进程不仅仅会申请cpu的资源,还可能会申请其他设备的资源,如磁盘网卡显示器等,当一个进程在等待队列中准备就绪,当cup资源就绪时,开始执行该进程的代码,如果该进程的代码涉及到申请其他资源的时候(例如访问磁盘资源,涉及文件相关操作),如果此时的某个设备资源正在别其他进程使用,使得该进程申请资源时资源不就绪,那么该进程的task_struct 就会被操作系统从运行队列中移除,然后放到操作系统中描述对应设备资源的数据结构中(一般是一个等待队列里),我们称进程等待某个资源就绪的状态为阻塞状态。

在这里插入图片描述

进程挂起

什么是进程挂起?

当内存中的进程太多(其数据和代码都会一并加载到内存中),就会使得内存的空间不足,那么操作系统就会将短期内不会被调度的进程的代码和数据置换回磁盘中,以此来缓解内存空间不足。代码和数据被置换回磁盘的进程就会被操作系统挂起!!!(此操作一般会涉及磁盘的高频访问)

在这里插入图片描述

进程

为了表示进程运行的情况,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 *task_state_array[] = {
	"R (running)",		/*  0 */
	"S (sleeping)",		/*  1 */
	"D (disk sleep)",	/*  2 */
	"T (stopped)",		/*  4 */
	"T (tracing stop)",	/*  8 */
	"Z (zombie)",		/* 16 */
	"X (dead)"		/* 32 */
};

R (running)运行状态

  • R是运行状态,但是并不意味着它一定在运行,它表明进程要么就是在运行中,要么就是在运行队列中

演示:

#include<stdio.h>
#include<unistd.h>
int main()
{
  while(1)
  {
      //啥也不干 就一直死循环 就会一直在运行队列里 就是R状态
  }
  return 0;
}

在这里插入图片描述

S (sleeping)睡眠状态

  • 意味着进程在等待某种资源,一般不是cpu资源(这里的睡眠有时候也叫做可中断睡眠,可被操作系统杀死)
#include<stdio.h>
#include<unistd.h>
int main()
{
  while(1)
  {
    printf("i am a process\n");
    sleep(1);
  }
  return 0;
}

在这里插入图片描述

D (disk sleep) 深度睡眠状态

  • 磁盘休眠状态,也叫不可中断睡眠状态,在这个状态的进程通常会等待IO的结束,操作系统是无法杀掉D状态的进程的
  • 这里可以用dd命令自己去试试。
  • D状态对应操作系统层面上的阻塞状态,只不过一般是进程等待磁盘资源时才是D,等待其他资源一般是S

T (stopped) 暂停状态

  • 可以发送SIGSTOP信号(19)给进程来停止(T)进程。这个被暂停的进程可以通过发送SIGCONT信号(18)让进程继续运行

窗口1:

在这里插入图片描述

窗口2:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V27IKIzV-1677761095420)(进程概念/image-20221013124909383.png)]

监视窗口:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1IfgTBoA-1677761095424)(进程概念/image-20221013124630380.png)]

上面的操作涉及进程信号知识,后面的博客会讲解,这里只需知道19 号信号可以使进程暂停即可!

Z (zombie) 僵尸状态

  • 僵尸状态是一个比较特殊的状态,当进程退出并且父进程(使用wait()系统调用等待子进程)没有读取到子进程退出的返回代码时就会产生僵尸进程
  • 僵尸进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
  • 只要子进程退出,父进程还在运行,但是父进程没有读取子进程状态,子进程就会进入Z状态

演示在下面!

X (dead) 死亡状态

  • X状态只是一个返回状态,是不会在任务列表看到此状态的

僵尸进程

#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
 pid_t id= fork();
 if(id>0)
 {
       //parent
       printf("i am parent\n");
       sleep(15);//休眠15s但是不退出
       exit(0);
 }
 else if(id==0)
 {
      //child
      int n=10;//10s后退出 如果父进程未读取其退出代码 该进程会变成僵尸进程 成Z状态
      while(n--)
      {
          printf("i am child\n");
          sleep(1);
      }
        printf("子进程退出!\n");
        exit(0);
 }
 else 
 {
       printf("creat fail!\n");
 }
      return 0;
}


程序运行:

在这里插入图片描述

脚本监控进程状态:

//shell脚本
while :;do ps axj | head -1 && ps axj | grep  myproc | grep -v grep;sleep 1;echo "#######################################################" ;done

在这里插入图片描述

僵尸进程的危害

  • 进程的退出状态必须要被维持下去,因为它要告诉它的父进程,父进程交给它的任务它完成的怎么样了,但是如果父进程一直不读取,那么子进程就会一直处于Z状态!
  • 维护退出状态本身就是维护进程的相关数据结构,所以操作系统需要维护进程的task_struct (pcb进程控制块),僵尸进程的退出信息一直不被读取,那么操作系统就需要一直维护它的PCB!
  • 维护僵尸进程的PCB会造成内存资源的浪费,存在内存泄漏!!!

孤儿进程

什么是孤儿进程?

顾名思义,就是其父亲不要它了,这个进程成了孤儿。也就是一个进程的父进程提前退出,但是其子进程还没有退出,那么该子进程就会变成失去父亲的孤儿进程(孤儿进程会被1号进程收养,回收资源)

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
  pid_t id= fork();
  if(id==0)
  {
    //child
    int n=10;
    while(n--)
    {
      sleep(1);
      printf("i am child! pid:%d  i am running!\n",getpid());
    }
  }
  else if(id>0)
  {
    int m=5;
    //parent
    while(m--) 
    {
      printf("i am parent! pid:%d  i am running!\n",getpid());
      sleep(1);
  }
    exit(0);
  }
  else{
    //error 
    printf("fork fail!\n");
  }
  return 0;
}

在这里插入图片描述

监控:

在这里插入图片描述

进程优先级

因为计算的各种资源是有限的,但是进程却很多,那么进程之间就会有竞争,类比现实生活中的排队现象,我们都是站成一条长队,排在前面的就优先,同样的对应进程间也有需要进行排队使用资源,于是就有了有进程优先级。

概念

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

如何查看进程优先级

使用top命令就可以看到

在这里插入图片描述

PR(process priority)和NI (nice)是什么?

上图中的PR就是进程优先级。

NI是进程优先级的修正数值,可以通过改变进程的NI值对进程优先级进行适度的调节!

注意点

  • PR越小,进程的优先级越高,代表进程越快被执行!
  • NI值范围是[-20,19]的 一共40个级别

优先级公式 : P R θ ( n e w ) = P R θ ( o l d ) + N I     ( N I ϵ [ − 20 , 19 ] ) 优先级公式:PR_\theta(new)=PR_\theta(old)+NI \space\space\space (NI\epsilon[-20,19]) 优先级公式:PRθ(new)=PRθ(old)+NI   (NIϵ[20,19])

如何改变(调节)进程优先级

更据上面的公式可以知道,进程优先级的调节是通过修改对应进程的NI值来实现的。

//步骤
1、输入top命令
2、再依次输入 r + 修改的进程pid + 回车 + 对应的nice值  

在这里插入图片描述

在这里插入图片描述

前后对比:

在这里插入图片描述

环境变量

概念:

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

常见环境变量

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

环境变量表

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

在这里插入图片描述

如何查看环境变量

//执行下面指令即可
1.查看所有环境变量 env命令会将所有的环境变量都显示出来
env
2.查看具体名称的环境变量
echo $环境变量名

如何获取环境变量

三种方式

通过main函数的参数获取

我们平常看到的main函数大多是不带参数的,但实际上它是有参数的,main函数可以带三个参数!!!

//第一个参数是执行main函数对应的进程的指令的个数,第二个是执行其对应进程的指令字符串,第三个是其对应进程的环境变量(字符指针数组,里面存的是一个个的环境变量字符串)
int main(int argc,char* argv[],char* env[])
{
     printf("%d\n",argc);
  for(int i=0;i<argc;i++)
    printf("%s\n",argv[i]);
  
  for(int i=0;i<argc;i++)
    printf("%s\n",env[i]);
  return 0;
}

在这里插入图片描述

通过第三方变量environ获取

  • libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用的时候,要用extern声明。
#include <stdio.h>
int main(int argc, char *argv[])
{
    //可以通过environ找到存储环境变量的指针数组,进而打印里面的环境变量(一个个字符串)
    extern char **environ;
    int i = 0;
    for(; environ[i]; i++){
    printf("%s\n", environ[i]);
    }
    return 0;
}

通过系统调用获取

getenv()获取环境变量

char getenv(const char name)**

参数name为所需要获取的环境变量的名称,如果该环境变量在环境表中存在那么就会返回所要获取的环境变量(字符串),反之如果不存在就会返回空指针!

putenv()设置环境变量
#include<stdio.h>
#include<stdlib.h
int main(int argc,char* argv[],char* env[])
{
	extern char** environ;
	int i;
    printf("putenv之前环境变量表:\n");
 	for(i=0;environ[i];i++)
    printf("%s\n",environ[i]);
  	putenv("MYPATH = 66666666666666666666666");
	printf("putenv之后环境变量表:\n")
  	for(i=0;environ[i];i++)
    printf("%s\n",environ[i]);
 	return 0;
}

注:由于putenv会存在安全问题(野指针),可以使用setenv()来代替putenv() !

环境变量的全局属性

在命令行中我们可以定义两种变量,一种是本地变量,一种是环境变量。

本地变量

在这里插入图片描述

本地变量不具有全局属性,不会被子进程继承!!!

因为我们自己写的程序运行起来形成的进程,都是有bash创建的子进程,那么我们在命令行中定义本地变量,再到我们自己写的程序中去找在bash中定义的本地变量如果找得到说明本地变量是可以被子进程继承的,否则不可以!!!

注意:这里要清楚的理解我们写的代码形成的进程都是由bash创建的子进程!!!

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

在这里插入图片描述

环境变量的定义

我们只需要使用关键字export就可以将一个本地变量导出为环境变量!!!

export LocalVal=1234567//那么LocalVal就会被导进bash的环境表中了  注:=两边不要加空格!!!
#include<stdio.h>
#include<stdlib.h>
int main()
{
  printf("%s\n",getenv("MyVal"));
  return 0;
}

在这里插入图片描述

进程地址空间

程序地址空间

我们在学语言的时候经常听到什么全局变量是存在静态区,常量字符串是存在常量区,定义的普通变量是存在栈上的,malloc出来的数据是存在堆上的,这里的堆,栈,常量区,静态区都是程序地址空间的叫法!

在这里插入图片描述

但是一个程序运行起来就是一个进程,那么原来的程序地址空间的叫法是不准确的,应该叫做进程地址空间。

进程地址空间

什么是进程地址空间呢?

所谓的进程地址空间是操作系统通过软件的方式,给进程提供一个软件视角(进程地址空间),认为自己是独占着整个系统的所有资源(内存),每个进程运行起来的时候,操作系统都会先给其创建一个进程地址空间(mm_struct),将进程所需要的空间先规划好,当其真正需要使用的时候再给它分配,这样就避免了进程一运行操作系统就需要立马给其分配内存,提高了内存的使用效率。

如何管理地址空间呢?

还是六字真言:“先描述,再组织”

既然进程地址空间是一个个的内核数据结构mm_struct 那么管理好mm_struct 就可以管理好进程地址空间!

mm_struct()结构组成

这里是大概的简略的组成(其真实组成远不止这么点东西,这里只介绍其区域划分),其实源码是有一个vm_area_struct 结构体来完成各个区域的划分的(了解即可,无需深究!)

在这里插入图片描述

在这里插入图片描述

//笼统理解为下面结构
struct mm_struct 
{
    //各个区域的划分
    unsigned int code_start;
    unsigned int code_end;
    
    unsigned int stack_start;
    unsigned int stack_end;
    
    unsigned int init_data_start;
    unsigned int init_data_end;
    
    unsigned int uninit_data_start;
    unsigned int uninit_data_end;
    ...
}

注意:这里的进程地址空间并不是直接对应的物理地址,而是虚拟地址,物理地址和虚拟地址是通过页表转换的!

为什么需要有虚拟地址这个东西呢?

  • 出于安全考虑,进程虽然认为只有其自己独占整个内存,但是这是操作系统为其画的大饼,实则是多个进程在共用一块内存,那么就会有多个进程会用到同一块物理空间的可能,如果每个进程都是直接访问的物理内存就会容易出现野指针的问题,例如原本多个进程共用的空间,其中一个进程终止了对该空间释放,那么就会使得原来与其共用一块内存的进程访问该空间时出现野指针,所以直接访问物理地址是具有安全隐患的!!!

  • 通过添加一层软件层,完成有效的对进程操作内存的风险管理(权限管理),本质是为了保护物理内存各个进程的数据安全

  • 将内存申请和内存使用在时间上解耦。通过虚拟地址空间,来屏蔽底层申请内存的过程,达到进程读写内存操作和OS进行内存管理进行软件层面上的分离。

在这里插入图片描述

感兴趣的可以去写代码验证上面的排列。

感知地址空间的存在

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int a=10;
int main()
{
  pid_t id=fork();
  if(id>0)
  {
    //parent
    int n=10;
    while(n--)
    {
      printf("我是父进程 pid:%d ppid:%d a:%d &a:%p\n",getpid(),getppid(),a,&a);  
      sleep(1);
    }
  }
  else if(id==0)
  {
    //child
    int n=10;
    while(n--)
    {
      if(n==5)
      {
        printf("我是子进程 我修改了a 为 200!\n");
        a=200;
      }
    printf("我是子进程 pid:%d ppid:%d a:%d &a:%p\n",getpid(),getppid(),a,&a);
    sleep(1);
    }
  }
  else 
  {
    perror("fork error!");
  }
  return 0;
}

在这里插入图片描述

在这里插入图片描述

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

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

相关文章

taobao.item.img.delete( 删除商品图片 )

&#xffe5;开放平台免费API必须用户授权 删除商品图片 公共参数 请求地址: HTTP地址&#xff1a;http://gw.api.taobao.com/router/rest 公共请求参数: 公共响应参数: 请求参数 响应参数 点击获取key和secret 请求示例 TaobaoClient client new DefaultTaobaoClient(url…

学习ifconfig实战技巧,成为网络管理高手

文章目录前言一. ifconfig 命令介绍二. 语法格式及常用选项三. 参考案例3.1 显示网络设备信息3.2 启动和关闭指定的网卡3.3 对指定的网卡设备执行修改IP地址操作3.4 启动和关闭ARP协议3.5 使用ifconfig添加网卡总结前言 大家好&#xff0c;又见面了&#xff0c;我是沐风晓月&a…

Neovim for Rust

之前学习 Rust 一直使用的都是 VScode rust-analyzer&#xff0c;最近看到有网友安利 Neovim 于是就试了试&#xff0c;发现确实美观&#xff0c;好用&#xff0c;而且内存占用比较小。我个人很喜欢&#xff0c;也推荐给给大家。 前提&#xff1a;得有个代理&#xff0c;不然大…

一个自学自动驾驶(决策规划控制方向)的研究生学习资料总结(附相关资料的链接)

项目仓库 欢迎访问我的Github主页 项目名称说明chhCpp学习C仓库chhRobotics学习自动驾驶、控制理论相关仓库(python实现)chhRobotics_CPP学习自动驾驶、控制理论相关仓库(c实现)chhML 、chh-MachineLearning学习机器学习仓库chhRL学习强化学习仓库chhTricks存放一些有意思的t…

Unity烘焙常见问题

本文首发于公众号洪流学堂&#xff0c;未经允许&#xff0c;不可转载。 Unity中光影烘焙经常会遇到很多莫名其妙的问题&#xff0c;大智总结了一个问题解决手册&#xff0c;本文是比较常见的一些问题&#xff0c;还有一些不那么常见的问题&#xff0c;《手册全文pdf》获取方法&…

【2023蓝桥杯】2018年第九届C/C++A组真题(解析笔记)

目录 ♥【分数】循环累乘/快速幂运算/最大公因数 ♥【星期一】闰年/周期循环 ♥【乘积尾零】遍历/取余/取整 ♥【第几个幸运数】 遍历 ♥【打印图形】dfs填空 【航班时间】字符串/思维/时间换算 【三体攻击】差分&#xff01;中等难度 ♥【全球变暖】dfs/连通块计数 *…

Python解题 - CSDN周赛第33期

本期四道题全考过&#xff0c;题解在网上也都搜得到。。。没有想法&#xff0c;顺手水一份题解吧。 第一题&#xff1a;奇偶排序 给定一个存放整数的数组&#xff0c;重新排列数组使得数组左边为奇数&#xff0c;右边为偶数。 输入描述&#xff1a;第一行输入整数n。(1<n<…

深究Java Hibernate框架下的Deserialization

写在前面 Hibernate是一个开源免费的、基于 ORM 技术的 Java 持久化框架。通俗地说&#xff0c;Hibernate 是一个用来连接和操作数据库的 Java 框架&#xff0c;它最大的优点是使用了 ORM 技术。 Hibernate 支持几乎所有主流的关系型数据库&#xff0c;只要在配置文件中设置好…

在 The Sandbox 中以全新的 Rabbids 体验庆祝兔年!

育碧(Ubisoft) 和 The Sandbox 联手为你们带来终极的农历新年体验&#xff01; 穿戴上你们新鲜出炉的 Rabbids 人物化身来参加派对吧&#xff0c;保证震撼整个元宇宙&#xff01;这个全新体验为 Rabbids 人物化身持有者专属。没有获得 Rabbids 人物化身吗&#xff1f;不要担心&…

【Java】P2 基础语法与运算符

Java 基础语法 运算符Java注释方法基本数据类型驼峰命名法Scanner类基本运算除法隐式转换逻辑运算符 以及 短路逻辑运算符三元运算符前言 上一节内容涵盖Java的基础知识&#xff0c;包含安装下载&#xff0c;JDK与JRE等。 链接&#xff1a;https://blog.csdn.net/weixin_43098…

java 多线程处理任务

首先介绍一下我的使用场景我在redis set集合中有几十万个行程id&#xff0c;我需要一个脚本来离线计算每个行程的里程&#xff0c;计算完了之后&#xff0c;将公里数填到mongodb的表中&#xff0c;并且删除set集合中这个元素。我的目录结构我们创建一个maven项目&#xff0c;然…

STM32之PWM

PWMPWM&#xff0c;英文名Pulse Width Modulation&#xff0c;是脉冲宽度调制缩写&#xff0c;它是通过对一系列脉冲的宽度进行调制&#xff0c;等效出所需要的波形&#xff08;包含形状以及幅值&#xff09;&#xff0c;对模拟信号电平进行数字编码&#xff0c;也就是说通过调…

Office 365用户报告

通过ADManager Plus的现成Office 365用户报告&#xff0c;您无需复杂的PowerShell脚本&#xff0c;即可查找Office 365环境中用户的重要信息。使用这些报告&#xff0c;您只需点击几次基于Web的控制台&#xff0c;即可提取Office 365环境中活动和不活动用户数量等信息&#xff…

Celery 分布式任务队列

1. 认识 Celery Celery 是一个 基于 Python 开发的分布式异步消息任务队列&#xff0c;可以实现任务异步处理&#xff0c;制定定时任务等。 异步消息队列&#xff1a;执行异步任务时&#xff0c;会返回一个任务 ID 给你&#xff0c;过一段时间后拿着任务 ID 去取执行结果定时…

进程的介绍

文章目录一.进程的概念1.1概念1.2进程的组成1.2.1 PCB中描述进程的特征二.进程的虚拟地址空间三.进程间的通信引入线程一.进程的概念 1.1概念 百科的介绍: 换句话说,一个跑起来的程序,就是一个进程,也就是在操作系统中运行的exe程序就是一个进程,如下图的进程列表 进程是操…

【女士,房间墙上凿个洞,看你在干嘛~】安全攻防内网渗透-绕过防火墙和安全检测,搭建DNS隐蔽隧道

作者&#xff1a;Eason_LYC 悲观者预言失败&#xff0c;十言九中。 乐观者创造奇迹&#xff0c;一次即可。 一个人的价值&#xff0c;在于他所拥有的。所以可以不学无术&#xff0c;但不能一无所有&#xff01; 技术领域&#xff1a;WEB安全、网络攻防 关注WEB安全、网络攻防。…

Java并发包提供了哪些并发工具类?

第19讲 | Java并发包提供了哪些并发工具类&#xff1f; 通过前面的学习&#xff0c;我们一起回顾了线程、锁等各种并发编程的基本元素&#xff0c;也逐步涉及了 Java 并发包中的部分内容&#xff0c;相信经过前面的热身&#xff0c;我们能够更快地理解 Java 并发包。 今天我要…

SpringBoot集成Swagger3.0(入门) 02

文章目录Swagger3常用配置注解接口测试API信息配置Swagger3 Docket开关&#xff0c;过滤&#xff0c;分组Swagger3常用配置注解 ApiImplicitParams,ApiImplicitParam&#xff1a;Swagger3对参数的描述。 参数名参数值name参数名value参数的具体意义&#xff0c;作用。required参…

mes系统核心业务流程及应用场景介绍

现在许多企业已经开始使用MES系统控制和管理工厂的生产过程&#xff0c;实时监控、诊断和控制生产过程&#xff0c;完成单元集成和系统优化。本文将为大家具体介绍一下MES系统的业务流程。 MES系统业务流程 1、计划调度MES系统承接了ERP订单&#xff0c;开始干预生产。该模块…

kaggle RSNA 比赛过程总结

引言 算算时间&#xff0c;有差不多两年多没在打kaggle了&#xff0c;自20年最后一场后&#xff08;其实之前也就打过两场&#xff0c;一场打铁&#xff0c;一场表格赛是金是银不太记得&#xff0c;当时相当于刺激战场&#xff0c;过拟合lb大赛太刺激了&#xff0c;各种trick只…