【Linux】进程的基本概念(以及进程地址空间的初步了解)

目录

一.什么是进程

进程和程序的区别

Linux查看进程 

进程的信息

fork函数

 二.进程状态

操作系统上进程状态的概念

运行

阻塞

挂起 

Linux中的进程状态

 R状态

S状态和D状态

T状态

t状态

X状态

Z状态

 三.进程的优先级

修改进程优先级

四.环境变量 

常见的环境变量

PATH

HOME

PWD

模拟pwd命令

getenv函数

main函数的两个参数

​编辑

 main函数的第三个参数

五.进程地址空间 

什么是进程地址空间 

为什么要有地址空间 

一.什么是进程

根据书本上的概念来解释,一个运行起来被加载到内存中)的程序称为进程

光这样一句话很难体会到什么是进程,所以我们来展开讲一下。

进程和程序的区别

进程被加载到内存上的,而程序是我们写好的一份二进制文件存在磁盘上的。 


那么到底什么是进程呢?

首先我们来讲一下PCB进程控制块,PCB进程控制块在Linux中就是一个名叫task_struct的结构体,在这个结构体里面,它可以存放进程的各种信息,当一个程序被加载到内存中的时候操作系统都会为这个程序生成一个PCB进程控制块,这个控制块里面存放了这个程序的各种信息,以便于维护这个进程。

所以进程可以理解成,一个二进制程序文件被加载到内存中,并且操作系统会为其构建一个PCB结构体来存放这个程序的信息,以便来维护它。

Linux查看进程 

在Linux当中,我们可以使用        ps ajx        命令查看进程。

也可以直接查看/proc文件夹。 

如下图所示,使用ps ajx来查看进程。

进程的信息

在查看进程信息中,我们可以发现有两栏:PPID、PID,这两个分别是父进程id进程id

那具体又是什么意思呢?

当我们运行一个程序的时候,它自己的进程id就叫做PID,它的父进程id就叫做PPID。


那为什么进程有会分为进程和父进程呢?

首先我们来了解一下操作系统,操作系统是一款管理软件,它可以管理好我们的软硬件资源,当我们要运行我们的程序的时候,操作系统会帮助我们去运行它,但是注意的是,操作系统不会亲自去运行该程序,而是操作系统会去派生一个子进程来运行我们的程序,为什么呢?

如果是操作系统亲自去运行我们的程序,那要是程序运行崩溃了,这就会导致我们的操作系统也一起崩溃,所以为了防止这种情况,操作系统会去派生一个子进程来运行我们的程序,即使程序运行崩溃了,最多就这个子进程崩溃了,而不会影响到我们的操作系统。

fork函数

在我们的Linux里面,提供了一个fork函数,这个函数可以在程序中创建子进程。

fork函数是去创建一个子进程,同时它会有两个返回值,这两个返回值分别给到父进程和子进程,父进程会返回子进程的id子进程会返回0,如果子进程创建失败,则返回小于0的数。所以根据这两个返回值,我们可以很好的分开父进程和子进程所运行的代码。

如下代码:

 当我们的程序运行起来的时候,会有两个进程在运行,子进程运行else if处的代码,父进程运行else处的代码。


运行结果如下所示。

 二.进程状态

知道了进程是什么后,那么每个进程都会有很多种状态,那各种状态又代表什么意思?

操作系统上进程状态的概念

 进程的状态有很多:运行,新建,就绪,阻塞,挂起等等,我们在这里挑一下讲解。

运行

有的人认为,一个进程正在被CPU处理,就叫做运行,其实这并不完全正确,因为一个进程没有被CPU处理,也可以有运行的状态。

假设内存中有多个进程,而同时又有多个进程需要被CPU处理,那么它们是怎么做的呢?

操作系统会为CPU创建一个CPU运行队列,凡是要被CPU处理的进程都会进到这个队列中,而当进程被放入到CPU运行队列中,则称为运行状态

同时CPU处理运行队列是一种轮转的方式,假设现在我们的CPU只有一个核,但是这个时候我们的CPU运行队列很长,有20个进程都需要被运行,但是我们的CPU只有一个核,也就是说我们的CPU一次只能处理一个进程,但是我们在用电脑的时候,并没有因为CPU一次只能处理一个进程而导致其他的进程无法运行,那是因为我们的CPU有一个轮转的机制,对于CPU运行队列上的进程,我们的CPU会轮流去执行所有进程,且轮转的速度非常快,快到我们感受不到,所以当我们的电脑有很多进程的时候并没有感觉到其他进程卡死,那是因为CPU的轮转速度很快。

阻塞

在一个进程中,不仅会有计算的部分,还会有输出的部分,比如我们写了个代码,将程序中的所有数据写入到磁盘中,那么在这个进程中,不仅需要CPU去处理,还需要用到磁盘

那么如果此时有别的进程正在占用磁盘资源又会怎么样呢?操作系统也会给每个硬件创建一个运行队列,将需要用到此硬件资源的进程按顺序的放入队列中,此时CPU正在处理的进程而在磁盘队列中排队,就叫阻塞

挂起 

当进程的阻塞状态较多的时候,会占用非常多的内存空间,当内存空间满了的时候,为了能使时计算机正常运行,操作系统会将还没排队到的阻塞进程暂时的放入磁盘中,这个进程的状态叫做挂起。

Linux中的进程状态

上面介绍的都是大部分操作系统下的解释,那么接下来看看Linux中,状态是如何表示的。

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状态

 R状态就是对应上面的运行状态。

S状态和D状态

S状态可以对应上面的阻塞状态或者是挂起状态,至于是哪一个,由操作系统决定。

D状态时Linux下特有的,叫做深度睡眠状态,这个状态在这里不做讲解。

T状态

T状态就是暂停的意思,指这个进程停止运行了,但它还在。 

t状态

 t状态表示该进程正在被追踪,简单的理解就是,当我们使用gdb调试器对这个进程的代码进行调试打断点的时候,就是这个状态。

X状态

X状态就是死亡状态,就是简单的指该进程已经被结束掉了。 

Z状态

Z状态是僵尸状态的意思,为什么会有僵尸状态呢?

因为当一个进程结束的时候,不会立马消失,它的父进程或者操作系统会对该进程进行收集信息,简单的理解就是,走了也得有个交代,是怎么走的,要调查清楚。 

 三.进程的优先级

所谓的进程优先级就是cpu资源的分配顺序,优先级高的进程会优先使用cpu资源,反之。 

在linux系统中,我们可以使用ps -l命令来查看进程的优先级。 

 进程的优先级由两部分组成,分别是PRI和NI。

PRI就是进程的优先级,该值越小则优先级越高,反之。

而NI是该进程的优先级可修改数值,也就是说,我们可以通过效果NI值来修改进程的优先级。        

进程优先级=PRI+NI值

修改进程优先级

若要修改进程的优先级,我们可以用top命令、再按r,再输入进程的pid、最后输入NI值来修改进程的优先级。 

注意,NI的值调整只在[-20,19]这个区间中,超过了,会默认选边界的值。

四.环境变量 

对于环境变量来说,可能大部分同学都知道,比如说,要写Java的代码时,需要配置jdk的环境变量,又或者使用vscode时,也需要配置环境变量,但是环境变量到底是什么,为什么要配置环境变量? 


首先,我们来看一个现象。

我们在使用Linux时,会使用到各种各样的命令,同时,Linux也有着一个理念:Linux下一切皆文件,也就是说,Linux命令其实也是一个文件,只不过是用C\C++编写的后编译而成的可执行程序,同时,我们也可以编写自己的可执行程序。

 pwd命令的路径


但是现在有一个问题,我们在运行自己的可执行程序的时候,通常要在前面加上./,而命令不用。如下: 

 那么是什么原因导致的呢?首先,我们来看一下Linux系统中的环境变量,通过env命令查看。

把全部环境变量打印出来有很多,但不用怕,我们把我们需要的截图出来即可。

我们可以看到,在Linux系统中,有一个这样的环境变量,这里面正好有我们命令的路径。

同时在这里再引入另一个问题,当我们要运行我们的程序的时候,操作系统怎么知道我们的程序在那,它是怎么找到我们的程序的,那既然涉及到找程序,那必须要有该程序的路径,路径又有绝对路径相对路径,当我们运行我们的程序的时候,也是去找我们程序的位置,即./test中的./就是该程序的相对路径,所以操作系统会根据这个路径去找我们的程序,并运行起来。


所以当我们的操作系统要去运行pwd命令时,它也要去找该命令的路径,但是这个路径不需要我们提供,因为有一个环境变量,这个环境变量有这些命令的路径,操作系统会根据这个环境变量的路径去找到我们的命令。


所以说,这就是环境变量的意义,当我们在vscode中配置C语言的环境变量时,就是给一个路径,让我们的程序去这个路径中找到C语言的库。

常见的环境变量

接下来介绍一下常见的环境变量,以及如何查看单个环境变量。

PATH

是Linux系统中命令的路径

HOME

用户的家目录

PWD

用户所在目录


说到底,环境变量就是一堆字符串的全局变量,这些字符串的作用是,当我们要去使用某些命令或者功能时,需要通过这些环境变量去找,而且这个环境变量是内存级别的变量,当我们启动机器时,它才会被加载到内存中,关机后就会消失。

模拟pwd命令

既然环境变量中存放着我们需要用到的信息,那么我们可以使用这些环境变量来模拟实现pwd命令。

getenv函数

在Linux中,有一个函数,可以去获取我们的环境变量。 

 getenv函数,给一个环境变量的名字,返回它的环境变量的内容。使用方法如下。


我们编写的这个程序,是去PWD这个环境变量中获取我们所在的目录,并将其输出,所以我们自己写的pwd命令就简单的完成了。效果如下。

main函数的两个参数

有一些同学可能就会见过,在main函数的参数里,有两个参数。 

那么这两个参数分别又是什么呢?

char* argv[]:这是一个指针数组,每个位置里面放着一个char类型的指针

int argc:记录着argv[]这个数组的大小

那么这个指针数组里面的char指针又指向什么内容呢?我们来运行下面的代码来看看。


 

通过运行这个代码我们可以看到,当我们输入./test是它就输出./test

当我们输入./test -a -l时,它就输出./test   -a  -l。

这就是我们在使用命令时候的选项,比如说:ls -a -l

它就是通过这种方法来识别我们的选项,然后根据你所输入的选项来输出对应的信息。

 main函数的第三个参数

main函数不仅有前两个参数,还是第三个参数。

第三个参数是char* env[],这也是一个指针数组,这个数组里面每一个指针都指向一个环境变量。我们来运行看看。

所以说,要在程序中获取环境变量,除了可以使用getenv函数外,还能用char* env[]参数来获取,不过一个第一种方法用的比较多。

五.进程地址空间 

什么是进程地址空间?

在进行解释前,我们先来看一段程序。

下面是这段程序的代码。

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 
  4 int global=10;//定义一个全局变量
  5 
  6 int main()
  7 {
  8     pid_t id=fork();//创建子进程
  9     if(id<0)
 10     {
 11         printf("创建子进程失败\n");
 12         return 1;
 13     }
 14     else if(id==0)//说明是子进程
 15     {
 16        int cnt1=3; 
 17        while(cnt1--)
 18        {
 19            printf("我是子进程,全局变量global:%d,它的地址是:%p\n",global,&global);
 20            sleep(1);
 21        }
 22 
 23        global=20;//在子进程中改变global的值
 24 
 25        int cnt=3;                                                                                                                                                                             
 26        while(cnt--)                                                                  
 27        {                                                                             
 28            printf("我是子进程,全局变量global:%d,它的地址是:%p\n",global,&global);
 29            sleep(1);
 30        }              
 31     }                 
 32     else//说明是父进程
 33     {               
 34        int cnt2=6;                  
 35        while(cnt2--)                                                                 
 36        {                                                                             
 37            printf("我是父进程,全局变量global:%d,它的地址是:%p\n",global,&global);
 38            sleep(1);                
 39        }                            
 40     }                               
 41     return 0;
 42 }

在这段代码中,我做的事情是:创建了一个全局变量global,且创建了一个子进程,在子进程和父进程中,刚开始我都进行了相同的操作,将全局变量的值和地址都进行一个输出,3秒后,我在子进程中将全局变量global的值进行改变,再进行一个输出。

现在我们来看一下运行的结果。 

在前3秒的运行结果中,我们可以看到,父进程和子进程都进行了相应的输出,它们打印出来的global的值都是相同的,这应该没什么问题,且它们打印出来的地址也是相同,这样看起来也确实没什么问题。

但是当在子进程中将global的值进行改变的时候,只有子进程的global改变了,而父进程的global没有改变,这看起来也很合理,但是这个时候发现,父进程和子进程的global的地址竟然也是相同的???


看到这个现象难免会觉得很奇怪,我们来分析一下:

我们知道,当一个程序运行起来的时候,它会被加载到内存中,相应的它所创建的变量也会在内存中,同时我们知道内存在电脑中其实是一个硬件,即内存条,就是说这些变量和程序都是放在内存条中的。

这时我们在看上面的代码结果,发现父进程和子进程global这个变量的地址是相同的,但是值不同,我们在把这个结果套入我们的内存条中分析,它们的地址相同,说明在这个内存条上,父进程和子进程的global的位置是相同的,但是值不同,说明在这个位置的内存上的值不同,也就是说相同的位置却又两个不同的值

这看上去显然不合理,所以说上面结果所打印出来的值其实不是真实的地址,而是虚拟的

什么是进程地址空间 

上面的问题,我们先放一放,我们来解释一下什么是地址空间。 


在学习C/C++的时候,应该都见过这个图, 

为什么这个图叫做C/C++的内存分布,而不是内存条的内存分布?

那既然有C/C++的内存分布,那是不是说明还有其他语言的内存分布,为什么同一块内存条上,不同的语言会有不同的内存分布呢?

那是因为这个C/C++内存分布其实是虚拟地址,这个内存分布的地址不是真实的,这个虚拟地址就叫做进程的地址空间


那么进程的地址空间到底是什么?

在linux中,进程的地址空间其实就是一个结构体变量,当一个程序被运行起来的时候操作系统会给它创建这个结构体的变量,在这个结构体变量中,它记录了栈区、堆区、未初始化数据区,初始化数据区的起始位置和终止位置,也就是虚拟地址,且当程序运行起来的时候,会通过页表将这个结构体变量上的虚拟地址映射到物理内存中,光看这句话很难理解,我们来看一下图。

当我们把一个程序运行起来的时候,会将这个程序加载到内存中,同时,操作系统会为这个进程创建一个PCB进程控制块,同时也会创建这个进程的地址空间,最后通过页表将地址空间中的虚拟地址映射到物理内存中。

为什么要有地址空间 

知道了什么是地址空间后,那么为什么要有地址空间呢?

那是因为要保证进程的独立性,同时也是为了安全考虑的,如果没有地址空间,让进程直接去访问物理内存,那要是越界了怎么办,有了地址空间的存在,要是越界访问了能很好的进行判断并阻止。

那么进程的独立性又是如何保证的?

我们接着来看开始那段程序:


首先我们要先知道,父进程是如何创建子进程的。

当我们调用fork函数去创建子进程的时候,操作系统会帮我们把父进程的数据和代码全盘拷贝一份,同时PCB进程控制块进程的地址空间页表也会全盘拷贝下来。这个时候子进程就创建好了,这对于前3秒的运行结果来看,没什么问题,但是当我们更改全局变量global的时候,这里会发生一个写时拷贝,操作系统会为子进程的global变量开一块新空间,将新的值20写进去,写完后,再改变子进程页表的映射关系来保证进程的独立性,使父进程和子进程的global变量互不影响。

光看这段看也很难理解,我们来看一下图:

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

谷粒商城实战-25-分布式组件-SpringCloud Alibaba-Nacos配置中心-加载多配置集

文章目录 一&#xff0c;拆分配置集二&#xff0c;配置文件中配置多配置集1&#xff0c;引用多配置集2&#xff0c;验证 三&#xff0c;多配置集总结1&#xff0c;使用场景2&#xff0c;优先级 这一节介绍如何加载多个配置集。 大多数情况下&#xff0c;我们把配置全部放在一个…

批量提取网页表格内容至excel文件

问题背景 将网页的表格内容&#xff08;5237个股票信息&#xff09;复制粘贴到excel文件中 网址&#xff1a;A股上市公司名单-A股上市公司名录-A股上市公司大全-商业计划书-可研报告-中商产业研究院数据库-中商情报网 实现代码 # 导入包 import pandas as pd import time# 创…

ATT 和 GATT:数据表示和交换

背景介绍 BLE的通信和以太网&#xff0c;wifi有个重大的不同是&#xff1a;BLE通信的设备往往有特定的功能。且这个功能不会在运行中发生变化。 因此蓝牙设备通信的时候&#xff0c;只能访问预先定义好的&#xff08;也就是配置文件profile&#xff09;的功能。 那profile里写…

Vue+SpringBoot实现仿网盘项目

目录 一、效果展示 二、前端代码 三、后端代码及核心解释 四、进阶开发与思路 一、效果展示 1.1读取文件夹内的文件 1.2删除功能 1.3 上传文件 1.4 文件下载 对应的网盘实际地址与对应下载内容&#xff1a; 二、前端代码 2.1 创建vue项目&#xff08;需要有vuex与router&…

Spark SQL 概述

Spark SQL 概述 Spark SQL 是 Apache Spark 的一个模块&#xff0c;专门用于处理结构化数据。它集成了 SQL 查询和 Spark 编程的强大功能&#xff0c;使得处理大数据变得更加高效和简便。通过 Spark SQL&#xff0c;用户可以直接在 Spark 中使用 SQL 查询&#xff0c;或者使用 …

C++基础语法:链表和数据结构

前言 "打牢基础,万事不愁" .C的基础语法的学习 引入 链表是最基础的数据集合,对标数组.数组是固定长度,随机访问,链表是非固定长度,不能随机访问.数组查找快,插入慢;链表是插入快,查找慢. 前面推导过"数据结构算法数据集合".想建立一个数据集合,就要设计数…

Python-找客户软件

软件功能 请求代码&#xff1a; 填充表格&#xff1a; 可以search全国各个区县的所有企业信息&#xff0c;过滤手机号、查看是否续存/在业状态。方便找客户。 支持定-制-其他引-留-阮*件&#xff08;XHSS&#xff0c;DYY&#xff0c;KS&#xff0c;Bi-li*Bi-li&#xff09; V*…

嵌入式转行2个星期,一些真心话建议~

在开始前刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「嵌入式的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01; 嵌入式转行2个星期&…

【计算机方向】中科院三区,国人发文占比>50%,录用容易,认可度不低~

今天小编带来计算机领域SCI快刊的解读&#xff01; 如有相关领域作者有意投稿&#xff0c;可作为重点关注&#xff01; 期刊解析 01 期刊信息 出版商&#xff1a;Springer Singapore ISSN&#xff1a;1672-6529 E-ISSN&#xff1a;2543-2141 期刊官方网站: https://www.sprin…

市面上值得入手的骨传导耳机怎么选?一次给你搞定全方位的选购攻略

随着骨传导耳机市场的日益发展&#xff0c;有很多人使用了一些不合适的骨传导耳机导致听力损伤等问题&#xff0c;这些问题也引起很多人日益关注的。原因大致就是&#xff0c;市面上出现了大量由非专业品牌贴牌和有网红生产的骨传导耳机产品&#xff0c;他们的核心技术的研发和…

CSDN回顾与前行:我的创作纪念日——2048天的技术成长与感悟

CSDN回顾与前行&#xff1a;我的创作纪念日——2048天的技术成长与感悟 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 前言 时光荏苒&#xff0c;岁月如梭。转眼间&#xff0c;从我在CSDN上写下第一篇技术博客《2-6 带头结点的链式表操作…

寻找并可视化交互

「AI秘籍」系列课程&#xff1a; 人工智能应用数学基础 人工智能Python基础 人工智能基础核心知识 人工智能BI核心知识 人工智能CV核心知识 使用特征重要性、弗里德曼 H 统计量和 ICE 图分析相互作用 本文中的代码需要安装 R 语言包 药物的副作用可能取决于你的性别。吸入…

解决浏览器 CORS跨域问题

跨域问题其实就是不同源请求导致 解决跨域问题时&#xff0c;Chrome 插件 requestly 解决 1、Chrome 应用商店 &#xff1a;chrome://extensions/ 搜索 requestly 插件。并添加到扩展程序 2、打开扩展程序&#xff0c;为当前接口设置请求头 在response Header 中设置 Acce…

samba共享windows和ubuntu的文件

通过Samba服务器实现Windows与Ubuntu之间的文件共享是一个常见的需求&#xff0c;下面是实现这一目标的详细步骤&#xff1a; 一、Ubuntu开启Samba服务器 安装Samba&#xff1a; 打开终端&#xff0c;使用以下命令安装Samba服务&#xff1a; sudo apt update sudo apt install…

辐射神经场算法——Instant-NGP / Mipi-NeRF 360 / 3D Gaussian Splatting

辐射神经场算法——Instant-NGP / Mipi-NeRF 360 / 3D Gaussian Splatting 1. Instant-NGP1. MultiResolution Hash Encoding1.2 Accelerated Ray Marching1.3 实验结果 2. Mip-NeRF 3602.1 场景参数化2.2 在线蒸馏2.3 失真正则化2.4 实验结果 3. 3D Gaussian Splatting3.1 Dif…

Monaco 使用 DefinitionProvider

DefinitionProvider 可以弹出方法定义&#xff0c;效果如下&#xff0c;按住 command 鼠标左键&#xff0c;弹出方法说明。 点击时 Monaco Editor 会调用注册函数&#xff0c;注册函数返回文件地址和需要显示的位置&#xff0c;实现代码如下 return monaco.languages.register…

自主研发接口测试框架

测试任务&#xff1a;将以前完成的所有的脚本统一改写为unitest框架方式 1、需求原型 1.1 框架目录结构 V1.0&#xff1a;一般的设计思路分为配置层、脚本层、数据层、结果层&#xff0c;如下图所示 V 2.0&#xff1a;加入驱动层testdriver 1.2 框架各层需要完成的工作 1、配…

Swiper轮播图实现

如上图&#xff0c;列表左右滚动轮播&#xff0c;用户鼠标移动到轮播区域&#xff0c;动画停止&#xff0c;鼠标移开轮播继续。 此例子实现技术框架是用的ReactCSS。 主要用的是css的transform和transition来实现左右切换动画效果。 React代码&#xff1a; import React, { us…

WEB-INF 泄露-RoarCTF-2019-EasyJava(BUUCTF)

题目页面 点开help 这里存在文件下载漏洞&#xff0c;参数选择POST传参&#xff08;使用HackBar插件&#xff09; 查看文件内容 下载存有web信息的XML文件&#xff0c;这里补充一点知识点 WEB-INF主要包含一下文件或目录&#xff1a; /WEB-INF/web.xml&#xff1a;Web应用程序…

关于思维和智能体模型的思考(1)

思维的本质&#xff1a;它的能力似乎源自于那些智能体之间复杂的交错关联 --马文 明斯基 最近阅读美国马文 明斯基写的书《心智社会》&#xff0c;觉得忽然开朗。他对人类思维&#xff0c;智能&#xff0c;智能体等概念做了十分优雅的解读。 个人觉得&#xff0c;他利…