Linux_进程概念详解(续)_命令行参数_环境变量_进程地址空间

本篇文章是Linux_进程概念详解的续篇,请先阅读Linux_进程概念详解再来阅读本篇。

命令行参数

在C / C++中,每个程序都必须有一个main函数,该函数有很多的版本,我们最常用的就是不带参数的版本,也就是下面第一条语句

int main()
int main(int argc, char *argv[])
int main(int argc, char *argv[], char *env[])    //env后面会讲

由语句可知main函数可以接收两个参数:argcargv。这两个参数允许程序接收来自命令行的输入参数。

  • argc(Argument Count)是一个整数,表示传递给程序的参数数量,包括程序本身的名称。
  • argv(Argument Vector)是一个字符串数组,包含了每一个传递给程序的参数。

现在我们通过一个简单的例子来理解argcargv的用法。 

int main(int argc, char* argv[])
{
    printf("argc: %d\n",argc);    // 打印argc
    for(int i = 0;i < argc;i++)
    {
        printf("argv[%d]: %s\n", i, argv[i]);    
    }
    return 0;
}

在这个程序中,我们首先打印出argc的值,即参数的数量。然后,我们遍历argv数组并打印出每个参数。 下面是输入不同参数打印的不同结果

 argc记录了每次输入的参数个数,而argv(指针数组)则是将输入的每一个参数当作一个字符串打印出来

注意:

  • argv[0]始终是执行程序的命令或程序名

  • argc至少为1,因为它包括了程序本身的名称。

  • 如果没有传递任何额外的参数,argc将为1,argv数组只包含一个元素,即argv[0]


为什么要有命令行参数

我们再来看一段代码

int main(int argc,char* argv[])
{
     if(argc != 2){  
         printf("Usage: code opt\n");  
         return 1;  
      }  
    
     if(strcmp(argv[1], "-opt1") == 0)  
         printf("功能1\n");   
    
      else if(strcmp(argv[1], "-opt2") == 0)  
         printf("功能2\n");  

     else if(strcmp(argv[1], "-opt3") == 0)
         printf("功能3\n");   
     else  
         printf("默认功能\n");  
     return 0;  
}

运行该代码

这是在干什么呀,输入的参数不同,输出的功能不同,可能大家现在觉得有点怪,别急。现在大家在来想一想,我们在Linux上输入的ls命令

大家知道,Linux上的命令基本上都是用C语言写的,而这些命令的各种选项是怎么实现的呢?其实就是用命令行参数实现的!!! 下面我来回答一下标题的问题,同一个程序,可以根据命令行参数,根据选项不同,表现出不同的功能。比如:指令中选项的实现

环境变量

现在我们来谈谈开篇提到的 env,env其实就是环境变量。

环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数。这些变量通常具有某些特殊用途,还有在系统当中通常具有全局特性

见一见环境变量本身_来看代码:

// 获取环境变量
int main(int argc,char* argv[], char* env[])
{
    for(int i = 0;env[i];i++)
    {
        printf("env[%d]:%s\n",i ,env[i]);
    }
}

上述代码结果也可以直接在命令行输入env直接查看! 

常见的环境变量

环境变量,最开始都是在系统的配置文件中的

PATH : 指定命令的搜索路径

我们在Linux下创建一个code.c文件,在文件里写入如下代码

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

然后./code运行该代码,然后就会输出hello world!,很好,非常正确。不过我的问题是为什么要./code运行代码而不是直接code运行呢?像我们的pwd、ls等命令直接输入就可以运行,为什么要输入./呢?其实就是环境变量PATH的原因,PATH告诉了shell,你应该去那一条路径下查指令!下面是Linux下PATH的路径集合(系统可执行文件的搜索集合)

[lyx@VM-16-7-centos]$ echo $PATH    // 查询环境变量PATH
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/lyx/.local/bin:/home/lyx/bin

为什么叫做路径集合呢?很简单,就是因为它包含多条路径,每条路径之间以:作为分割符。当shell在运行任何一条命令行命令时,都会首先去该路径集合里面去查,如果找到了就会直接执行,否则就会输出command not found。

现在知道为什么要输入./code才可以运行了叭,因为code不在系统可执行文件的搜索集合里面,所以要./指明路径。如果我们想让自己的程序直接运行,可以将我们自己程序的路径拷贝到系统目录里面;也可以把自己程序的路径添加到PATH中。两种方法都可以。

HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)

任何一个用户都有自己的主工作目录,HOME保存的就是当前用户的主工作目录。

PWD : 保存当前进程所在的工作路径

获取环境变量我们可以使用系统调用getenv(),从而模拟出pwd命令的功能

// envtest.c
// 模拟pwd功能
#include <stdio.h>
#include <stdlib.h>
int main()
{
    printf("%s\n",getenv("PWD"));
    return 0;
}

从而我们实现了一个pwd。

USER:当前正在使用系统的用户

如果我们想要区分用户是谁,只需要看USER就可以了。使用场景:我可以让我的程序,通过USER环境变量识别用户身份,只让指定的用户访问。请看下面代码

int main()
{
    const char* who = getenv("USER");
    if(strcmp(who, "lyx") == 0){
        printf("程序正常执行命令!\n");
        return 0;
    }
    else{
        printf("无权访问!\n");
        return 1;
    }
}

理解环境变量

int main()
{
    char * env = getenv("MYENV");
    if(env){
        printf("%s\n", env);
    }
    return 0;
}

运行上述代码发现没有结果,说明MYENV环境变量不存在。但是如果在命令行输入export MYENV="hello world" ,再次运行代码 ,发现有结果了。为什么?

要想解释清楚上述问题,我们来谈谈本地变量的概念,我们在命令行输入a=10,然后通过echo $a,命令行就会输出10,a就是本地变量。本地变量和环境变量都是由shell维护的,但是env只会查找环境变量,想要查找环境变量和本地变量就要使用set命令。我们可以通过export a命令将本地变量变为环境变量。也可以直接export b=20,将b导为环境变量。

我们首次登陆时OS会为我们创建一个属于当前用户bash进程,用来进行命令行解释,bash进程会根据我们的配置文件形成一张环境变量表(char* env[],该表的指针会指向每一个环境变量)和一张命令行参数表(char* argv[]),除此之外还会形成一张本地变量表(也是一个指针数组),所以当在命令行输入a=10时,bash识别a=10不是一条命令,就会把a=10当作一个字符串连接到本地变量表中。而所谓的export就是把a=10从本地变量表迁移到环境变量表中!

所以上述问题,就是将MYENV导成了环境变量,而环境变量可以被所有的bash之后的进程全部看到(可被继承)!!自然而然的就可以打印出结果,因此,环境变量具有“全局属性”

环境变量为什么需要具有“全局属性”呢?

  • 系统的配置信息,尤其是具有“指导性”的配置信息,它是系统配置起效的一种表现。
  • 进程具有独立性!环境变量可以用来在进程之间传递数据(只读数据)。
获取环境变量的方法
  1. 通过main()第三个参数获取
  2. 使用系统调用getenv()获取
  3. 使用全局变量environ()获取
// 使用environ获取环境变量
extern char** environ;    
int main()
{
    for(int i = 0;environ[i];i++){
        printf("%s\n",environ[i]);
    }
    return 0;
}

进程地址空间

老样子,先上代码

int gval = 100;
int main()
{
    printf("我是一个进程, pid: %d, ppid: %d\n",getpid(),getppid());

    pid_t id = fork();
    if(id == 0){
        // 子进程
        while(1)
        {
            printf("我是子进程, pid: %d, ppid: %d, gval: %d, &gval: %p\n",getpid(),getppid(),gval,&gval);
            gval++;
            sleep(1);
        }
    }
    else{
        // 父进程
        while(1)
        {
            printf("我是父进程, pid: %d, ppid: %d, gval: %d, &gval: %p\n",getpid(),getppid(),gval,&gval);
            sleep(1);
        }    
    }
}

根据运行结果我们发现一个有违我们认知的事情:父子进程,输出地址是一致的,但是变量内容不一样!既然变量内容不一样,所以父子进程输出的变量绝对不是同一个变量,也不对啊,它们的地址是相同的啊!所以我们可以大胆推断该地址绝对不是物理地址!其实在Linux地址下,这种地址叫做虚拟地址


1.理解地址空间

其实所谓的进程虚拟地址空间就是操作系统给进程画的大饼,操作系统(4G内存)让每个进程都以为自己有4G大小的内存空间,而实际上是让所有的进程共享这4个G的内存空间

操作系统让每一个进程都认为自己是独占系统物理内存大小,进程之间彼此不知道,不关心对方的存在,从而实现一定程度的隔离。操作系统在管理所谓的进程虚拟地址空间时,本质是管理一个内核数据结构对象(类似PCB) mm_struct(大饼)。

2.理解区域划分

之前我们在C语言阶段接触到的内存区域划分,又是怎样划分的呢?其实在mm_struct结构体中有很多的开始、结束地址。比如栈的起始地址、栈的结束地址;堆的起始地址、堆的结束地址等等,都是以整型变量作为划分的。

3.理解地址空间上的地址

a. 地址本质上就是一个数字,可以被保存在unsigned long中。b. 空间范围内的地址,我们可以随便用,暂时不需要记录它的地址


有了上面三点理解,我们再来谈谈虚拟地址空间

每一个进程(task_struct)中都含有一个struct mm_struct* mm指针,该指针指向一个进程虚拟地址空间,创建一个进程不仅仅要创建该进程的PCB,还要为该进程创建虚拟地址空间。不管进程中间怎么玩,最终的代码和数据还是要加载到物理内存的。

当一个程序从磁盘加载到内存,该内存就会有自己的物理内存地址;在虚拟地址空间该程序也会有自己的虚拟地址。而操作系统会在虚拟地址空间和物理内存之间创建一个名为页表的东西,这个页表就记录着虚拟地址与物理地址的映射关系。
假设我们在编译器上定义gval=200;当我们取gval的地址时,我们所看到的地址就是虚拟地址!!而当我们将gval修改为100后,它对应的物理内存会发生变化,但是虚拟地址不会发生变化,只是页表中的映射关系变了!!

补充

  • 关于变量和地址

        变量名实际上就是地址,所以我们看到的虚拟地址是一样的,这也证明了fork的返回值id既大于0也等于0,因为我们用的页表不同。

  • 重新理解进程和进程独立性

        进程 = 内核数据结构(task_struct / mm_struct / 页表) + 自己的代码和数据。

        a. 内核数据结构各自私有一份

        b. 代码和数据也是独立的

综上,上面代码结果出现的地址相同,变量值却相同的现象,就得到了合理的解释。

我们把虚拟地址空间 + 页表叫做虚拟内存管理方案 。我们在用C/C++语言所看到的地址,全部都是虚拟地址!物理地址,用户一概看不到,由OS统一管理。OS负责将 虚拟地址 转化成 物理地址 。

页表

上面罗里吧嗦的说了那么多关于页表的东西,现在我们来简单了解一下页表。

权限标记位——rwx

我们在之前学习C语言的时候,只知道代码区是只读的,可是为什么是只读的呢?举个例子:

char* str = "hello world";    // 字符串常量
*str = 'H';

上述代码中的char类型的指针str指向常量区,而我们要想执行第二句代码修改常量区的值时,程序编译能过,但一运行就会报错。为什么?

能编过就说明不是编译器的问题,而这个问题是在运行时发现的问题,当代码按区域加载到内存之后,经过映射填充我们的页表,之后就会把权限带上。因为常量区的代码是只读的!所以你进程想要修改,对不起,你没有写权限就不让你修改,甚至我(OS)会将你杀死。这也是我们程序为什么会崩溃的原因。

所以上层编译器为了解决上述问题,就引入了const关键字。如果你不想内容被修改,你就加上const,这样编译器就能检查出来问题了

检查目标内容是否在内存中的标记位——isexists

假如,有一个G大小的代码文件,程序在运行的时候,内存是一次性读取全部读取呢?还是只读取被调用的代码呢?答案是只读取一部分。内存中的资源是非常宝贵的,既然你这些代码在当前这个时间里面没用,我读取你做什么,那不就是浪费资源吗。所以isexists标记位的存在就是为了分批加载、挂起等操作设定的

为什么要存在地址空间

1.  虚拟内存 + 页表可以有效保护内存。当进程经过页表访问物理内存时,如果该进程的权限不对或者访问页表时,页表中没有该进程的映射关系,那么系统就会阻止其访问,甚至将该进程杀掉!

        野指针就是一个很好的例子:野指针用到的实际上是虚拟地址,当一个指针指向一块错误的虚拟地址时,该指针就变为了野指针;野指针的出现导致的程序崩溃,就是因为野指针在转化时,权限不对或者地址不存在映射关系,被系统直接杀掉了。

2.  进程(task_struct)管理和内存(mm_struct)管理在系统层面进行了解耦合。如果没有地址空间和页表,对应进程的相关数据就必须直接在物理内存中加载出来,只有物理地址的话,要申请内存,就必须malloc或者new物理地址,从而使物理内存与进程的耦合度特别高。ps. 不了解 解耦合 与 耦合度的建议百度一下。

3.  让进程以统一的视角看待物理内存,代码和数据可以加载到物理内存的任意地方,从而让“无序”变“有序”。“无序”变“有序”的意思就是,可以在物理内存随便加载代码和数据,这样虽然会导致代码和数据是无序的,但是经过页表的映射关系,在进程看来这些代码和数据,甚至是栈区、堆区都是在一起并且按照一定规则呈现的。

总结

简单总结一下

地址空间的本质就是struct mm_struct,上述所有内容都是由OS系统自动完成的。我们知道在进程PCB中有一个指针指向mm_struct,所以只要把PCB管理好了,mm_struct就管理好了。最后再问大家一个问题:为什么全局变量、字符常量等具有全局性,在整个程序运行期间都会有效?原因就是这些数据在地址空间中,随着进程一直存在!而其地址,可以被大家一直看到。

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

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

相关文章

关于 文件操作详解 笔记 (含代码解析)

文件 磁盘&#xff08;硬盘&#xff09;上的⽂件是⽂件。 程序设计中&#xff0c;我们⼀般谈的⽂件有两种&#xff1a;程序⽂件、数据⽂件&#xff08;从⽂件功能的⻆度来分类 &#xff09; 程序⽂件 程序⽂件包括源程序⽂件&#xff08;后缀为.c&#xff09;,⽬标⽂件&#…

【测试】BUG篇——BUG

bug的概念 定义&#xff1a;⼀个计算机bug指在计算机程序中存在的⼀个错误(error)、缺陷(flaw)、疏忽(mistake)或者故障(fault)&#xff0c;这些bug使程序⽆法正确的运⾏。Bug产⽣于程序的源代码或者程序设计阶段的疏忽或者错误。 准确的来说&#xff1a; 当且仅当规格说明&am…

项目_C_Ncurses_Flappy bird小游戏

Ncurses库 概述 什么是Ncurses库&#xff1a; Ncurses是一个管理应用程序在字符终端显示的函数库&#xff0c;库中提供了创建窗口界面、移动光标、产生颜色、处理键盘按键等功能。 安装Ncurses库&#xff1a; sudo apt-get install libncurses5-dev 头文件与编译&#xf…

老人桌面 1.3.5|专为老人设计的便捷实用桌面应用

老人桌面是一款专为老人设计的便捷实用桌面应用&#xff0c;具有超大字体设计&#xff0c;符合老人视力水平&#xff0c;撞色简洁的应用界面&#xff0c;拯救老人视觉体验。此外&#xff0c;还提供了常用的实用小工具&#xff0c;让老人能够轻松使用手机。 大小&#xff1a;5.…

Oracle-19g数据库的安装

简介 Oracle是一家全球领先的数据库和云解决方案提供商。他们提供了一套完整的技术和产品&#xff0c;包括数据库管理系统、企业级应用程序、人工智能和机器学习工具等。Oracle的数据库管理系统是业界最受欢迎和广泛使用的数据库之一&#xff0c;它可以管理和存储大量结构化和…

界面耻辱纪念堂--可视元素03

更多的迹象表明&#xff0c;关于在程序里使用新的动态界面元素&#xff0c;微软的态度是不确定的&#xff0c;其中一个是仅仅需要对比一下Office97 里的“Coolbars”和“标准工具条”。Coolbar 按钮直到用户指针通过的时候才成为按钮&#xff08;否则是平的&#xff09;。 工具…

SpringBoot Data JPA基本使用

一、项目起步 1.1 pom配置 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId> </dependency><dependency><groupId>org.springframework.boot</groupId><…

Android终端GB28181音视频实时回传设计探讨

技术背景 好多开发者&#xff0c;在调研Android平台GB28181实时回传的时候&#xff0c;对这块整体的流程&#xff0c;没有个整体的了解&#xff0c;本文以大牛直播SDK的SmartGBD设计开发为例&#xff0c;聊下如何在Android终端实现GB28181音视频数据实时回传。 技术实现 Andr…

光伏仿真系统在光伏项目开发中有哪些应用场景?

光伏仿真系统在光伏项目开发中的应用场景广泛&#xff0c;涵盖了从项目规划、设计优化到运维管理的全过程。 一、项目规划与选址 1、气象模拟与评估 光伏仿真系统能够基于历史气象数据和先进的预测模型&#xff0c;模拟不同地理位置、不同季节和时间段的光照强度、温度、湿度…

【学术论文投稿】Java入门:零基础小白也能轻松掌握的全攻略

【IEEE | 往届见刊1个月检索 | 国际双会场】第四届智能电力与系统国际学术会议(ICIPS 2024)_艾思科蓝_学术一站式服务平台 更多学术论文投稿请看&#xff1a;https://ais.cn/u/nuyAF3 目录 【IEEE | 往届见刊1个月检索 | 国际双会场】第四届智能电力与系统国际学术会议(ICIPS…

『网络游戏』三端增加数据.dll替换【32】三端

修改服务器脚本&#xff1a;DBMgr 增加数据库 修改客户端脚本&#xff1a;MainCityWnd.cs 拖拽绑定 查看服务端PlayerData调用的协议位置 在客户端中替换 之后客户端就可以调用服务端新增的数据了

Elasticsearch Ingest Pipelines

1. 前言 在将第三方数据源的数据导入到Elasticsearch中时&#xff0c;原始数据长什么样&#xff0c;索引后的文档就是什么样。文档数据结构不统一&#xff0c;导致后续数据分析时变得麻烦&#xff0c;以往需要额外写一个中间程序来读取原始数据&#xff0c;转换加工后再写入到…

Linux下Docker方式Jenkins安装和配置

一、下载&安装 Jenkins官方Docker仓库地址&#xff1a;https://hub.docker.com/r/jenkins/jenkins 从官网上可以看到&#xff0c;当前最新的稳定版本是 jenkins/jenkins:lts-jdk17。建议下在新的&#xff0c;后面依赖下不来 所以&#xff0c;我们这里&#xff0c;执行doc…

智绘城市地图:使用百度地图 API 实现智能定位

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

测网速小程序,纯前端

搜索&#xff1a;证寸照制作 源码介绍: 测网速小程序源码&#xff0c;是一款纯前端无需服务器的测网速小程序&#xff0c;依赖百度开发者中心js接口&#xff0c;真正的永久使用的小工具源码&#xff0c;很实用&#xff0c;可以单独运行&#xff0c;测网速很流畅~ 合法域名: ht…

深入理解 pnpm(Performant NPM) 的实现原理及其与 npm 的区别

深入理解 pnpm 的实现原理及其与 npm 的区别 在 JavaScript 生态系统中&#xff0c;包管理器是开发者日常工作中不可或缺的工具。npm&#xff08;Node Package Manager&#xff09;作为 Node.js 的默认包管理器&#xff0c;已经广泛应用于各种项目中。然而&#xff0c;随着项目…

力扣之接雨水(42)

刷题不在多&#xff0c;而在精。 这道题号称字节的保洁阿姨都能做出的。 方法一&#xff1a;动态规划 下面这幅图简直封神&#xff0c;看了下图我才做出来的。 两个方向遍历&#xff0c;然后取相同覆盖值-原始值&#xff08;heigth数组&#xff09; 这种方法更好理解。但是也有…

厨房老鼠数据集:掀起餐饮卫生监测的科技浪潮

厨房老鼠数据集&#xff1a;掀起餐饮卫生监测的科技浪潮 摘要&#xff1a;本文深入探讨了厨房老鼠数据集在餐饮行业卫生管理中的重要性及其相关技术应用。厨房老鼠数据集通过收集夜间厨房图像、老鼠标注信息以及环境数据&#xff0c;为深度学习模型提供了丰富的训练样本。基于…

新手爬虫DAY1

这个错误信息表明在你的Python程序中&#xff0c;re.search() 函数没有找到预期的匹配项&#xff0c;因此返回了 None。当你尝试在 None 对象上调用 group(1) 方法时&#xff0c;Python 抛出了一个 AttributeError。 具体来说&#xff0c;错误发生在 pc.py 文件的第6行&#x…

QT开发--QT基础

第0章 QT工具介绍 0.1 编译工具 uic&#xff0c;rcc&#xff0c;moc&#xff0c;qmake 都是 qt 的工具 uic 主要是 编译 .ui文件 -> ui_xxx.h //.ui文件 .h rcc 主要是 编译 资源文件.qrc文件 -> xxx.rcc …