Linux进程概念(下)

Linux进程概念

  • 1. 命令行参数
  • 2. 环境变量
    • 2.1 环境变量的概念
    • 2.2 环境变量的使用和一些问题
    • 2.3 获取环境变量
    • 2.4 深入理解环境变量
    • 2.5 环境变量相关的命令
  • 3. 进程地址空间
    • 3.1 基本概念
    • 3.2 为什么要有地址空间

1. 命令行参数

main函数也可以带参数的,如下

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

image-20240426171713697

命令行整个一行是一个大的字符串,以空格作为分隔符,被分割成了5个子串。

  • 第一个参数argc是,命令行以空格作为分隔符有几个字符串,比如上面是5个字符串,argc就是5。
  • 第二个参数argv是一个指针数组,保存着每个子串的地址。并且有效元素要比实际上命令行的子串多一个,最后一个一般以NULL结尾。

识别这些字符串子串和传参是操作系统自动帮我做的。为什么main函数要这么设计呢?

比如我们想实现一个命令行版的计算器:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char* argv[])
{
	if (argc != 4)
	{
		printf("Use it incorrectly, please conform to the following usage.\nUsage:%s op[-add|sub|mul|div] d1 d2", argv[0]);
	}
	int x = atoi(argv[2]);
	int y = atoi(argv[3]);
	// 一定有四个命令行参数
	if (strcmp(argv[1], "-add") == 0)
	{
		printf("%d+%d=%d\n",x ,y ,x+y);
	}
	else if (strcmp(argv[1], "-sub") == 0)
	{
		printf("%d-%d=%d\n",x ,y ,x-y);
	}
	else if (strcmp(argv[1], "-mul") == 0)
	{
		printf("%d*%d=%d\n",x ,y ,x*y);
	}
	else if (strcmp(argv[1], "-div") == 0)
	{
		if (0 == y)
		{
			printf("%d/%d=error!\nZero cannot be used as the divisor.\n",x ,y);
		}
		else
		{
			printf("%d/%d=%d\n",x ,y ,x/y);
		}
	}
	else
	{
		printf("Use it incorrectly, please conform to the following usage.\nUsage:%s op[-add|sub|mul|div] d1 d2", argv[0]);
	}
	return 0;
}

使用一下:

image-20240426190236092

由此我们可以理解原来使用的命令和main函数这么设计的原因就是:

比如我们原来用的ls命令(这些命令就是用C语言写的!),带着命令行选项(和我们上述写的命令行计算器带选项相似), 就可以实现同一选项实现不同功能。

命令行参数(选项),可以支持各种指令级别的命令行选项设置!

2. 环境变量

2.1 环境变量的概念

基本概念:

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

命令env查看当前操作系统所有环境变量。

image-20240427152643373

系统重会存在大量的环境变量,每一个环境变量都有它自己的特殊用途用来完成特定的系统功能~!

常见环境变量:

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

查看环境变量方法:

echo $NAME NAME:你的环境变量名称

2.2 环境变量的使用和一些问题

为什么执行系统命令的时候不需要带./,但是执行我们自己的可执行程序却需要呢?

首先使用命令echo $PATH查看环境变量
image-20240427103727659
不用带./的原因就是,ls等这些系统级别的指令都存储在这个环境变量里,执行的时候系统会依次检索这些目录。

如果想让我们的可执行程序不带./就可以执行,很简单,把可执行程序所在的目录添加到环境变量即可。

使用命令PATH=$PATH:你要添加的目标目录(该命令是直接接到原有的环境变量之后)
image-20240427105055255

此时再次来查看环境变量的路径
image-20240427105156622
发现我们的可执行程序所在的目录已经在环境变量里了,来用一下~
image-20240427105321119
此时,就不用带./了捏!

如何删除呢?

直接用命令PATH=原目录(意思就是直接复制一份老的环境变量直接覆盖即可)
image-20240427105829339
这时不带./我们的可执行程序就又跑不了了。

如果我们干个“坏事”,把环境变量直接整没,PATH=""
image-20240427110425373
然后,然后就会这样了,几乎所有命令都不能使用了,怎么办捏,系统是不是就崩了!其实不用过于担心, 重新登陆一下系统就好了(果然重启解决99%的问题)。

我们对环境变量的修改,仅仅只在内存级别的修改,我们知道内存是易失性存储器,只要系统重启就会恢复原有的模样。默认更改环境变量,只限于本次登录,如果重新登录的话环境变量会自动恢复。

如果我们直接把我们自己的可执行程序直接拷贝到默认的环境列表中,也可以做到如此效果,这个操作我们称之为程序安装

我们在登陆Linux的时候发现
为什么普通用户默认所处目录/home/XXX而超级用户所处/root呢?
登陆的时候:

  1. 输入用户名和密码
  2. 认证
  3. 形成环境变量(PATH、PWD、HOME等)
  4. 根据用户名初始化HOME=/rootHOME=/home/XXX
  5. cd $HOME

2.3 获取环境变量

  1. 学习一个调用,获取环境变量:getenv(const char *name)

image-20240427153455700

那么环境变量的作用体现在哪里呢?可以实现系统级别的过滤,比如下段简单的代码:

#include <stdio.h>
#include <stdlib.h>
int main()
{
    char *user = getenv("USER");
   	// 如果用户不是超级用户,直接返回
    if (strcmp(user, "root"))
    {
        printf("The user is incorrect,please switch users!\n");
        return 1;
    }
    printf("%s is my command test\n", user);
    return 0;
}

image-20240427155625887

当前不是超级用户,执行非法。我们切换一下用户再次执行。

image-20240427160315422

成功执行!

  1. 在学习命令行参数的时候我们知道,main函数可以带两个参数,那么只能带两个参数吗?main函数其实还可以带第三个参数的:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char* argv[], char* env[])
{
	int i = 0;
	for (i = 0; env[i]; ++i)// 这个和argv相似,最后都会放一个NULL
	{
		printf("pid:%d,env[%d]:%s\n", getpid(), i, env[i]);
	}
	return 0;
}

image-20240427170822473

可以看到,与我们直接在命令行使用env命令基本相似。

系统启动我们的程序的时候,可以选择给我们的进程(main)提供两张表:

  1. 命令行参数表
  2. 环境变量表
  1. 如果不想使用命令行参数来查看环境变量表,可以使用C语言为我们提供的一个全局标量environ

image-20240427195554683

int main()
{
    extern char **environ;
    int i = 0;
    for ( ; environ[i]; ++i)
    {
        printf("%d: %s\n", i, environ[i]);
    }
    return 0;
}

libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用时 要用extern声明。

执行一下,同样会得到我们想要的效果。

2.4 深入理解环境变量

上面我们做过实验,用PATH=""将路径直接覆盖为空,会导致大部分命令使用不了,但是重新登陆之后又会恢复如初。当前我们直接更改的是bash进程内部的环境变量信息!每一次重新登陆,都会给我们形成新的bash解释器并且新的bash解释器自动从某一位置读取形成自己的环境变量表信息。

命令行启动的进程都是shell/bash的子进程,子进程的命令行参数和环境变量,是父进程bash给我们传递的!那么父进程的环境变量信息又从哪里来呢?

环境变量信息是以脚本配置文件的形式存在的。

进入家目录下,查看隐藏文件,会发现有一个.bash_profile的文件

image-20240427175808519

当我们登录时,bash会自动加载该文件,配置文件中的内容,为我们bash进程形成一张环境变量信息表!

我们可以通过一些特殊的指令,添加我们自己的环境变量:

把这种定义的变量称之为shell定义的本地变量,但是它并没有存在bash的环境变量表中。使用命令export 你的环境变量名,把你的环境变量导出到它自己的环境表进程中,再次用env查看发现,bash和我们的可执行程序(也就是子进程)都有了我们定义的环境变量。

image-20240427192354987

也可以直接在定义环境变量的同时直接导出

export 你的环境变量名=内容

image-20240427192741892

但是该自定义环境变量依旧是存储在内存里的,一旦重新登陆,又消失不见了,那么应该怎么才能永久保存呢?

再次进入bash_profile文件,加入我们对应的自定义环境变量:
image-20240427193858073

保存退出,再次重新登陆,环境变量便永久存在了
image-20240427194212405

命令行是支持定义本地变量的,比如这里定义了变量a
image-20240427205112232

本地变量 vs 环境变量

  • 本地变量只在bash进程内部有效,并且不会被子进程继承。
  • 环境变量通过让所有子进程继承的方式,实现自身的全局性。

当我们清空环境变量时会导致大部分命令使用不了,在Linux中这大部分是用不了的命令,是在磁盘中真正存在并且需要由fork创建子进程来执行的命令。但是在shell中还有一种命令,并不会创建子进程,它的执行风险非常低,由bash自己来执行,就等同与bash内一个函数,诸如echoexport这样的命令依旧能够继续使用。

Linux的命令分类:

  1. 常规命令。需要shell、fork创建子进程,让子进程执行的命令。
  2. 内建命令。shell命令行的一个函数,echo就是内建命令,所以可以直接读取shell内部定义的本地变量。

2.5 环境变量相关的命令

  1. echo: 显示某个环境变量值
  2. export: 设置一个新的环境变量
  3. env: 显示所有环境变量
  4. unset: 清除环境变量
  5. set: 显示本地定义的shell变量和环境变量

3. 进程地址空间

3.1 基本概念

在学习C/C++时,我们学习过这样的空间布局:

image-20240429170909420

用一段代码验证下:

#include <stdio.h>
#include <stdlib.h>

int un_gval;
int init_gval = 100;

int main(int argc, char* argv[], char* env[])
{
    printf("code address:%p\n", main);                   
    const char *str = "hello Linux";                     
    printf("read only char address:%p\n", str);                             
    printf("init global value address:%p\n", &init_gval);
    
    char *heap1 = (char*)malloc(100);
    char *heap2 = (char*)malloc(100);
    char *heap3 = (char*)malloc(100);
    char *heap4 = (char*)malloc(100);
    printf("heap1 address:%p\n", heap1);
    printf("heap2 address:%p\n", heap2);
    printf("heap3 address:%p\n", heap3);
    printf("heap4 address:%p\n", heap4);
    
    printf("stack address:%p\n", &str);
    printf("stack address:%p\n", &heap1);
    printf("stack address:%p\n", &heap2);
    printf("stack address:%p\n", &heap3);
    printf("stack address:%p\n", &heap4);
    
    int i = 0;
    for (; argv[i]; ++i)
    {
        printf("argv[%d]:%p\n", i, argv[i]);
    }
    
    for (i = 0; env[i]; ++i)
    {
        printf("env[%d]:%p\n", i, env[i]);
    }
    return 0;
}

运行结果:
image-20240429160704161

堆向上生长,栈向下生长,堆栈相对而生也得已验证。栈区虽然整体是向下生长但是局部是向上使用的。

当我们定义一个int整型取地址时,我们发现所得到地址只有一个字节,但是int却是四个字节,难道不应该得到四个字节的地址吗。

其实我们取地址一般都取到的是这个变量的地址,所以能得出一个结论:C/C++进程访问的本质是起始地址+偏移量的访问形式!

再来段代码感受一下:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int g_val = 100;
int main()
{
    pid_t id = fork();
    if (id == 0)
    {
        int count = 5;
        while (1)
        {
            printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);
            sleep(1);
            if (count == 0)
            {
                g_val = 200;
                printf("sub-process is changed: 100->200\n");   
            }
            count--;
        }
    }
    else
    {
        // father
        while (1)
        {
            printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);
            sleep(1);
        }
    }
}

输出结果:
image-20240429174555649

g_val的值未改动之前:

我们发现,输出出来的变量值和地址是一模一样的,因为子进程按照父进程为模版,父子并没有对变量进行进行任何修改。

g_val的值改动之后,父子进程,输出地址是一致的,但是变量内容不一样!能得出如下结论:

  • 变量内容不一样,所以父子进程输出的变量绝对不是同一个变量!
  • 但地址值是一样的,说明,该地址绝对不是物理地址!
  • 在Linux地址下,这种地址叫做虚拟地址!
  • 我们在用C/C++语言所看到的地址,全部都是虚拟地址!物理地址,用户一概看不到,由OS统一管理!

OS必须负责将虚拟地址转化成物理地址。

Untitled (1)

上面的图就足矣说名问题,同一个变量,地址相同,其实是虚拟地址相同,内容不同其实是被映射到了不同的物理地址!

这里的写时拷贝是在物理内存中的,由操作系统来做,并且不影响上层语言。

地址空间也要被OS管理起来,每一个进程都要有地址空间,在系统中,一定要对地址空间做管理!

如何管理地址空间呢?(经典六字)

先描述,再组织。

地址空间最终一定是一个内核的数据结构对象!就是一个内核的结构体!

在Linux中,进程/虚拟地址空间这个东西就是一个结构体,大概描述一下:

struct mm_struct
{
    long code_start;
    long code_end;
    long data_start;
    long data_end;
    long heap_start;
    long stack_start;
    long stack_end;
}

3.2 为什么要有地址空间

  1. 让进程以统一的视角看待内存

所以任意一个进程,可以通过地址空间+页表的方式将乱序的数据内存,变成有序,并且分门别类的规划好!无论什么改动,我们只需改变映射关系即可,不用在物理内存中挨个去找,大大提高了管理内存的效率。

  1. 其实页表除了保存虚拟地址和物理地址外,还有一个访问权限字段
    image-20240502110940484

所以说,存在虚拟地址空间可以有效的进行进程访问内存的安全检查!

  1. 将进程管理和内存管理进行解耦

通过页表,让进程映射到不同的物理内存处,从而实现进程的独立性

挂起在Linux中如何体现呢?

image-20240502160546239

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

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

相关文章

Linux内核之原子操作:atomic_long_dec用法实例(六十七)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

2023-2024年汽车行业报告/方案合集(精选345份)

汽车行业报告/方案&#xff08;精选345份&#xff09; 2023-2024年 来源&#xff1a;2023-2024年汽车行业报告/方案合集&#xff08;精选345份&#xff09; 【以下是资料目录】 2023中国汽车科技50强 2023中国智能汽车产业发展与展望 2023比亚迪海豹汽车拆解报告 2023新能…

PotatoPie 4.0 实验教程(31) —— FPGA实现摄像头图像高斯滤波

什么是高斯滤波 高斯滤波是一种常见的图像处理技术&#xff0c;用于去除图像中的噪声和平滑图像。它的原理基于统计学中的高斯分布&#xff08;也称为正态分布&#xff09;。 在高斯滤波中&#xff0c;一个二维的高斯核函数被用来对图像中的每个像素进行加权平均。这个高斯核…

【沉淀之华】从0到1实现用户推荐 - 实时特征系统构建,包含特征计算,特征存储,特征查询,特征补偿超详细思路分享

文章目录 背景介绍设计初衷基本概念 技术架构"四高"特征存储特征计算特征查询特征补偿 技术难点Q&A彩蛋 背景介绍 设计初衷 作为用户推荐系统的支撑系统之一&#xff1a;用户实时特征系统有着举足轻重的重要&#xff0c;甚至说它是一起推荐行为触发的必要条件。…

【经典算法】LeetCode 160. 相交链表(Java/C/Python3/Go实现含注释说明,Easy)

目录 题目描述思路及实现方式一&#xff1a;哈希表思路代码实现Java版本C语言版本Python3版本Golang版本 复杂度分析 方式二&#xff1a;双指针思路代码实现Java版本C语言版本Python3版本Golang版本 复杂度分析 总结相似题目 标签(题目类型)&#xff1a;链表 题目描述 给你两…

C语言——操作符保姆级教学(含整形提升及算数转换)

操作符 一.操作符的分类二.原码、反码、补码三.移位操作符1.左移操作符&#xff1a;<<2.右移操作符&#xff1a;>> 四.位操作符1.按位与—— &2.按位或—— |3.按位异或—— ^4.按位取反—— ~ 五.逗号表达式六.条件操作符七.操作符的属性&#xff1a;优先级、…

如何配置和使用Apollo的component里的plugin

关于如何使用Apollo的Component里的plugin&#xff0c;在Apollo的文档里只有如果和开发的说明却没有找到一个清楚完整说明怎么把plugin跑起来的说明&#xff0c;例如我想把lidar_detection_filter按我们的需求对目标过滤算法作修改然后编译完后&#xff0c;执行 cyber_launch …

【数据结构】链表专题3

前言 本篇博客我们继续来讨论链表专题&#xff0c;今天的链表算法题是经典中的经典 &#x1f493; 个人主页&#xff1a;小张同学zkf ⏩ 文章专栏&#xff1a;数据结构 若有问题 评论区见&#x1f4dd; &#x1f389;欢迎大家点赞&#x1f44d;收藏⭐文章 目录 1.判断链表是否…

Dom获取属性操作

目录 1. 基本认知 1.1 目的和内容 1.2 什么是DOM 1.3 DOM对象 1.4 DOM树 2. 获取DOM元素对象 2.1 选择匹配到的第一个元素 2.2 选择匹配到的多个元素 2.3 其他获取DOM元素方法 3. 操作元素内容 3.1 元素对象.innerText 属性 3.2 元素对象.innerHTML 属性 4. 操作元…

springcloud微服务搭建多数据源(mysql,oracle,postgres,等等)管理模块,支持通过注解方式切换不同类型的数据库

1.背景 同一套微服务管理系统&#xff0c;业务完全一样&#xff0c;但不同的客户可能要求使用自己熟悉的数据库&#xff0c;比如&#xff0c;mysql&#xff0c;oracle&#xff0c;postgres&#xff0c;还有一些国产数据库。如果能够将数据库模块独立出来&#xff0c;兼容各家的…

IDEA启动项目报错:Error running ‘‘: Command line is too long.

1、在workspace.xml 2、 在标签 <component name"PropertiesComponent"> 添加 <property name"dynamic.classpath" value"true" />

MySQL 运维篇

回顾基本语句&#xff1a; 数据定义语言(DDL) 这类语言用于定义和修改数据库的结构&#xff0c;包括创建、删除和修改数据库、 表、视图和索引等对象。 主要的语句关键字包括 CREATE 、 DROP 、 ALTER 、 RENAME 、 TRUNCATE 等。 create database 数据库 &#xff1b; cr…

【MySQL | 第十一篇】一条SQL语句在MySQL的执行过程

文章目录 11.一条SQL语句在MySQL的执行过程11.1MySQL整体架构11.2SQL语句在Server层执行流程11.3拓展&#xff1a;InnoDB存储引擎的更新操作11.3.1问题&#xff1a;为什么写了redolog日志还要写binlog日志&#xff1f;11.3.2问题&#xff1a;为什么要两阶段提交&#xff1f;11.…

《QT实用小工具·四十七》可交互的创意动态按钮

1、概述 源码放在文章末尾 该项目实现了可交互的创意动态按钮&#xff0c;包含如下功能&#xff1a; 所有颜色自定义 鼠标悬浮渐变 两种点击效果&#xff1a;鼠标点击渐变 / 水波纹动画&#xff08;可多层波纹叠加&#xff09; 额外鼠标移入/移出/按下/弹起的实时/延迟共8种事…

springboot 自动配置源码解读

什么是自动装配 当我们程序依赖第三方功能组件时&#xff0c;不需要手动将这些组件类加载到IOC容器中。例如 当程序需要用到redis时&#xff0c;在pom.xml文件中引入依赖&#xff0c;然后使用依赖注入的方式直接从IOC容器中拿到相应RedisTemplate实例。 SpringBootApplication …

【已解决】json文件太大无法打开怎么办+ijson报错

下载了一个json文档&#xff0c;尝试了全部的编辑器都打不开。。。 搜了搜或许可以使用ijson GitHub - ICRAR/ijson: Iterative JSON parser with Pythonic interfaces "Ijson is an iterative JSON parser with standard Python iterator interfaces." 示例代码&…

【C++ —— 多态】

C —— 多态 多态的概念多态的定义和实现多态的构成条件虚函数虚函数的重写虚函数重写的两个例外协变&#xff1a;析构函数的重写 C11 override和final重载、覆盖(重写)、隐藏(重定义)的对比 抽象类概念接口继承和实现继承 多态的继承虚函数表多态的原理动态绑定和静态绑定 单继…

VTK 的可视化方法:Glyph

VTK 的可视化方法&#xff1a;Glyph VTK 的可视化方法&#xff1a;Glyph标量、向量、张量将多边形数据的采集点法向量标记成锥形符号参考 VTK 的可视化方法&#xff1a;Glyph 模型的法向量数据是向量数据&#xff0c;因此法向量不能像前面讲到的通过颜色映射来显示。但是可以通…

25 JavaScript学习:var let const

JavaScript全局变量 JavaScript中全局变量存在多种情况和定义方式&#xff0c;下面详细解释并提供相应的举例&#xff1a; 使用var关键字声明的全局变量&#xff1a; var globalVar "我是全局变量";未使用var关键字声明的变量会成为全局变量&#xff08;不推荐使用&…

【前端】-【防止接口重复请求】

文章目录 需求实现方案方案一方案二方案三 需求 对整个的项目都做一下接口防止重复请求的处理 实现方案 方案一 思路&#xff1a;通过使用axios拦截器&#xff0c;在请求拦截器中开启全屏Loading&#xff0c;然后在响应拦截器中将Loading关闭。 代码&#xff1a; 问题&…