命令行参数:
int main(int argc, char *argv[ ]),main的参数可带可不带。argc参数通常代表后面的char *argv的元素个数有多少。
在linux中会把输入的字符串存到char *argv[ ]中,在数组的结尾为NULL。
命令行参数可以让同一个程序可以通过不同的选项得以达到不同的功能。本质是交给程序不同的选项用来定制不同的功能。命令行解释器的参数是传给父进程的。
环境变量(PATH是环境变量的一个):
环境变量默认是在配置文件中的。当操作系统bash在启动时,会把环境变量从文件导入到自己bath的解释器中。
linux系统中存在全局设置,告诉命令行解释器应该去那些路径寻找可执行程序。系统中的配置登陆的时候被加载到PATH中,因为环境变量路径被加载到PATH中被bash拿到,因此直接使用程序不用加上路径,而自己生成的可执行程序是没有被加载到环境变量中,因此需要指明路径。
安装软件相当于把自己的程序加载到环境变量路径中,也就是usr/bin路径中。usr/bin路径中基本是系统中的操作方法。
命令行中的ls等命令可以直接运行是因为 PATH 存放着路径。
echo $PATH - - - 可以查看环境变量路径。
PATH= $PATH:自己的可执行程序路径 - - - 把自己的可执行程序加载到环境变量中,可以直接运行我的程序。自己添加的环境变量路径是内存级别的,重新登陆自己加的就没了。如果想让自己的程序每次启动被自动加载,需要把路径写入到 .bash_profile 中。
env - - - 查看所有环境变量, echo $NAME - - - 查看单个环境变量内容
可以直接自己创建一个环境变量, export: 设置一个新的环境变量 - - - export XXX=yyy,如果不存就是在本地文件内的环境变量。
通过代码获取环境变量:
environ - - - 系统配的全局变量,是一个二级指针。
extern - - - 声名变量。
下述代码运行结果和 env 的信息是一样的,所以,环境变量也是可以被子进程拿到并访问的,因为本身程序父进程就是bash。
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
extern char **environ;
int i = 0;
for(; environ[i]; i++){
printf("%s\n", environ[i]);
}
return 0;
}
bash 启动的时候默认会给子进程形成两张表,一张是 argv[ ] - - - 命令行参数表,一张是 env[ ] - - - 是环境变量表。bash 交给子进程。命令行参数由用户输入,环境变量表由OS的配置文件来。
下述代码和上述代码完成的任务一样,都是把得到的环境变量打印出来。
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[], char *env[])
{
int i = 0;
for(; env[i]; i++){
printf("%s\n", env[i]);
}
return 0;
}
export 就是把字符串添加env到表中,如果自己添加了一个环境变量也会被上述代码查到。
环境变量具有系统级全局属性,因为环境变量本身会被子进程继承下去。
getenv - - - 获取指定的环境变量。
自己添加环境变量:HELLO=1234565 - - - 这样先产生一个变量名字,export HELLO - - - 把这个变量添加到环境变量表中,可以用上述代码查看到。如果不使用export 导入到环境变量表中,则这个变量只属于本地,在bash内部有效,无法被子进程继承,导成环境变量才可被获取。unset HELLO - - - 释放掉定义的环境变量。
进程的地址空间
运行下述代码后可以看到下图中,地址相同,但该地址内的内容却不相同。这个地址就是 - - - 虚拟地址。
int g_val = 100;
int main()
{
pid_t id = fork();
if(id == 0){ //child,子进程肯定先跑完,也就是子进程先修改,完成之后,父进程再读取
g_val=300;
printf("child[%d]: %d : %d\n", getpid(), g_val, &g_val);
}else{ //parent
printf("parent[%d]: %d : %d\n", getpid(), g_val, &g_val);
}
return 0;
}
因为父子进程的虚拟地址和页表基本一样,子继承父的这两。
如果子进程对数据进行修改后,由于进程具有独立性,子进程修改的时候会在物理内存中开辟新空间把老数据拷贝到新空间中,然后重新对子进程的页表构建映射。然后才进行写入工作 - - - 写时拷贝。
如果没有写入,父子进程都指向同一块物理内存。只有发生写入才会对物理空间进行临时拷贝,写入的进程的页表中虚拟地址指向的物理地址空间更改。
地址空间就是一个内核中的 struct mm_struct 结构体,每一个进程都会有自己的进程地址空间,地址空间中有很多区域。
地址空间可以让无序的空间变得有序,让进程以统一的视角看待物理内存以及自己运行的各个区域。可以使得进程管理模块和内存管理模块进行解耦。拦截非法请求,如果请求的空间在页表中没有找到就说明请求非法。
虚拟地址块的每一个区域都会经过页表映射,所以字符常量区不能被修改就是因为页表在管理权限,页表映射的字符常量区是不能被直接修改的这个权限是在页表完成的。
数据不在物理内存 - - - 缺页中断。
程序本身就有地址。加载地址空间和页表中的地址信息都是从可执行程序中加载的。
fork()的返回值有两个,一个是父,一个是子,而fork是父进程的值,因此会发生写时拷贝,写时拷贝会导致好像同一个变量有不同的值。