linux篇linux进程(下)
Posted 东条希尔薇
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux篇linux进程(下)相关的知识,希望对你有一定的参考价值。
作者介绍:
关于作者:东条希尔薇,一名喜欢编程的在校大学生
主攻方向:c++和linux
码云主页点我
作者CSDN主页地址
目录
环境变量
问题引入
我们运行我们自己的c语言程序,是怎么运行的呢?
我们发现,运行我们的程序必须要在前面加上./
这其实是为OS标识程序所在的位置
那么问题来了,为什么有些程序不需要这个东西?比如我们的python
先甩出结论:因为有些程序已经配置在了环境变量中
环境变量定义
-
环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数
-
环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性(这就是我们使用某些程序不需要./的原因)
它的本质是OS在内存中开辟空间用来保存与系统相关的数据
而环境变量一般分为以下三种:
- PATH:指令命令的搜索路径,我们上面的问题就是因为这个变量在作怪
- HOME:指示用户的家目录,每次登录linux都能跳到我们的家路径就是因为这个变量
- SHELL:shell的地址,通常是bash所在的目录
环境变量的操作
如何查看我们的环境变量?
注意:环境变量前需要加$
简单的方法是:
echo $PATH
我们同样可以修改我们的环境变量
一般使用export指令来修改我们的环境变量
export PATH=
因为环境变量是一个个搜索路径,我们把我们想运行的程序添加到环境变量即可
这样就能在任意文件夹下不用加./运行我们的程序了
但是这样修改,坏了,我们的基本指令运行不了了
是因为我们这样设置会覆盖原来的环境变量,导致系统找不到它的程序的位置了
不用着急,export修改环境变量只在当前会话生效,我们退出重新进,环境变量是能够恢复的
怎么修改才能不覆盖原来的环境变量呢?
export PATH=$PATH:...
既然这样修改环境变量只在当前会话生效,那有没有能永久修改环境变量的文件呢?
有的,就在家目录下的两个文件决定,.bash_profile和.bashrc。这两个文件都是隐藏文件,需要-a指令显示
认识命令行参数并获取环境变量
环境变量在OS中,是以一个指针数组组织的,每一个指针指向了一个字符串,这个字符串就是一个个路径。最后一个位置固定是空指针
而我们的命令行参数怎么获取环境变量呢?
我们先普及一下命令行参数
它是main函数里面可以携带的参数,一般有两个
int main(int argc,char*argv[])
其中第一个参数代表你argv中有几个变量,就像在c语言中你传入一个数组,必须要把数组大小也传入进去
第二个参数代表你传入的参数,在linux中,通常以每个程序后面的附加选项实现
在linux中,这种参数是决定你每个命令的选项的,比如ls -l,ls -a等
代码演示:
int main(int argc,char* argv[])
if(argc!=2)
printf("using error\\n");
return -1;
else if(strcmp(argv[1],"-a")==0)
printf("hello bit\\n");
else if(strcmp(argv[1],"-h")==0)
printf("hello you\\n");
else
printf("hello world\\n");
return 0;
这样,我们就能在这个程序后面添加选项了
那么,这个东西和我们的环境变量有什么关系呢?
其实main函数也可以携带第三个参数char* env[],这个参数保存了环境变量的信息
我们可以用代码来打印环境变量
#include<stdio.h>
int main(int argc,char*argv[],char*env[])
int i=0;
for(;env[i];i++)
printf("%s\\n",env[i]);
进程地址空间(重点警告)
程序空间回顾
在学c语言的时候,我们知道,我们的运行内存(32位下是4G)由以下几个部分构成
那么,在OS角度,它到底是不是我们在语言层面上讲的程序内存?
我们可以来段代码实验一下,以下程序打印了父子进程得到的全局变量的数据和地址
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int g_val = 0;
int main()
pid_t id = fork();
if(id < 0)
perror("fork");
return 0;
else if(id == 0) //child
printf("child[%d]: %d : %p\\n", getpid(), g_val, &g_val);
else //parent
printf("parent[%d]: %d : %p\\n", getpid(), g_val, &g_val);
sleep(1);
return 0;
结果当然一样,这个很好理解,因为我们知道父子进程共用一个数据
但是,如果我们把子进程的数据修改一下
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int g_val = 0;
int main()
pid_t id = fork();
if(id < 0)
perror("fork");
return 0;
else if(id == 0) //child
g_val=10;//在这里修改数据
printf("child[%d]: %d : %p\\n", getpid(), g_val, &g_val);
else //parent
printf("parent[%d]: %d : %p\\n", getpid(), g_val, &g_val);
sleep(1);
return 0;
结果如下:
我们吃惊的发现,它们的地址居然还是一样的
而根据我们进程是写时拷贝的原则(只要同一块数据修改,就另外开辟空间存储被修改的进程数据)这个现象是解释不通的!
所以现在只有一点解释的通:打印的地址根本不是真实的物理地址
而我们把这个叫做虚拟地址,我们在语言层面下看到的地址,其实全部都是虚拟地址!
进程地址空间
我们知道,OS中有物理地址,而每个进程都有自己的虚拟地址,它们有啥相互联系呢?
我们先引入一个小故事:
假如张三是个富二代,他有三个兄弟。而张三的爸爸给他们说,你们每个人都有上亿的资产。但实际上爸爸只有1亿,却没有直接给张三三兄弟。而是他们要用多少,他爸就给他们多少,这样,每个人都潜意识的认为自己拥有上亿资产
OS也是这样运行的,这里有一个重要的结论
每个进程都认为自己独占一整个物理空间,都按照自己独占物理空间的方式来划分自己的内存区域
而每个进程后面,维护这个界限的,是一个结构体,它的模拟结构如下
struct mm_struct
int s_begin;
int s_end;
//规定栈区的界限
int h_begin;
int h_end;
//规定堆区的界限
//等等等等
而进程地址空间的本质就是一个个的结构体,地址就被这些结构体抽象为char[410241024]的数组(4g大小),通过划分界限来确定自己的区域
但这样设计,会不会有把物理空间用完的情况呢?
可能性是有,但是极小(几乎忽略不计),所以,我们不用考虑这种情况
既然,每个进程都有一个自己的虚拟地址,怎么把它映射在物理地址呢?
OS通过页表+MMU来对齐进行映射(其实MMU是内置在CPU中的一个硬件)
页表我们可以把它理解成一个哈希表,它负责把虚拟地址映射到物理地址
所以,我们本节的第一小节中的父子进程,它们的写实拷贝是发生在物理层面上的,页表修改了映射而已
OS这样设计的原因
同样以一个小故事引入吧:
假如张三今年8岁,每年都会收到压岁钱,但是它妈妈怕他乱用,于是帮助他来管理,每次用多少,他妈就给他多少
这里的进程就相当于我们的张三,我们的进程难免有时候会因为错误使用而发生一些非法访问内存的问题,所以我们需要通过管理,来约束进程的使用
页表就能很好的管理内存,防止每个进程过度使用物理内存
其次,如果我们的程序有一行向堆区申请了很大的空间,但是很多空间却被浪费了,这么大的空间浪费发生在物理内存上实在太可惜了
所以,OS允许用户进行浪费空间的行为,当然,仅仅是在虚拟内存中浪费罢了,未使用空间根本就不会为你建立物理映射。如果你真要使用这么大的空间,OS也可以利用其它进程暂未使用的物理空间借给你
最后,我们的CPU在访问进程的时候,需要访问上下文数据等等数据,而我们的物理空间只有一块,进程却有上百个。让CPU单独维护每个进程在物理空间的开始地址,这样无疑增加了CPU的负担
所以,OS会为每个进程划分统一的标准,方便CPU的访问,而每个功能在虚拟空间的地址是相对确定的
以上是关于linux篇linux进程(下)的主要内容,如果未能解决你的问题,请参考以下文章