一、环境变量
我们说程序是一个可执行的二进制代码,在Linux下一个命令也是一个程序,例如ls命令,我们知道当我们敲下ls,回车之后,系统就会执行这条命令。但是我们自己的可执行程序却要指明路径才可以执行。
ls命令是系统命令,是放在bin目录下。
如何让自己写的可执行程序也像系统命令那样不用指明路径来执行
方法一:
那么我们自己讲自己写的可执行命令放到/bin 目录下,这样就是将自己写的命令置为系统命令
例如:
但是这样会污染我们的系统命令集,我不建议这样做,所以最后将我们添加的命令删除掉
方法二:
将自己的可执行程序路径放到环境变量
认识一下常见的几个环境变量
HOME:指定用户的主工作目录
HISTSIZE:保存历史命令的条数
SHELL:当前Shell,一般时/bin/bash
PATH环境变量。指导操作系统搜索可执行成程序的路径
export :将本地变量导出为环境变量
来看下面的例子:
运行结果成功的打印出环境变量的值
当我们想打印自己在当前bash下定义的变量my_env时
我们试图用上面的函数印出环境变量的值,并且我们自己定义一个变量my_env,发生的段错误
我们分别用env和set命令来查看都有哪些变量,发现
用set命令查看的是所有的 环境变量(如HOME)和所有本地变量(本bash)(如my_env)
用env命令查看的是所有的 环境变量(如HOME)
环境变量具有全局特性,本地变量作用域只在本地,不会被子进程继承
其他查看环境变量的方法:
#include <stdio.h> #include <stdlib.h> int main() { extern char **environ; int i=0; for(i=0;environ[i];++i) { printf("%s\n",environ[i]); } return 0; }
这里environ是指向环境变量表的指针,环境变量表是一个字符指针数组,每个指针为一个以'\0'结尾的环境变量字符串,数组最后一个元素为NULL;
下来看一下进程创建时的内存地址空间:
来看一个例子:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int g_val=100;//定义一个全局变量 int main() { int pid=fork(); if(pid<0) { perror("fork"); } else { if(pid==0) { printf("child,pid:%d,ppid:%d,g_val:%d,&g_val:%p\n",getpid(),getppid(),g_val,&g_val); exit(0); } else { sleep(3);//这里是保证了父进程在子进程后面调度 printf("parent,pid:%d,ppid:%d,g_val:%d,&g_val:%p\n",getpid(),getppid(),g_val,&g_val); } } return 0; }
结果为:全局变量的值和地址都相同,这也是我们预期的。
那再看一下代码执行后,结果是怎样的:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int g_val=100;//定义一个全局变量 int main() { int pid=fork(); if(pid<0) { perror("fork"); } else { if(pid==0) { g_val=200;//对全局变量进行修改 printf("child,pid:%d,ppid:%d,g_val:%d,&g_val:%p\n",getpid(),getppid(),g_val,&g_val); return 0; } else { printf("parent,pid:%d,ppid:%d,g_val:%d,&g_val:%p\n",getpid(),getppid(),g_val,&g_val); } } exit(0); return 0; }
我们看到这里的全局变量的地址相同但是值却不同了,如果这里是我们真实的物理内存,很明显这种情况是不可能发生的,那么这里的地址到底是什么呢,
当一个进程在执行时,操作系统为其分配了一个与物理内存大小相同的空间,我们这里称其为虚拟地址空间,在进行取数据时,虚拟地址与实际的物理地址之间建立一种映射关系,实现如下图:
创建进程的另外一种方法:
我们知道 创建子进程用到系统调用fork()
还有一种创建子进程的方法是vfork();
.vfork()用于创建一个子进程,
而子进程和父进程共享地址空间(fork()的子进程具有独立的地址空间)
.vfork()保证了子进程先运行,在它调用exec或者exit之后父进程才可能被调度运行,
其实就是在子进程运行期间父进程处于挂起(T)状态,子进程调度结束后,再来调度父进程。
来看下面代码:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int g_val=100;//定义一个全局变量 int main() { int pid=vfork(); if(pid<0) { perror("vfork"); } else { if(pid==0) { printf("brfore:child,pid:%d,ppid:%d,g_val:%d,&g_val:%p\n",getpid(),getppid(),g_val,&g_val); g_val=200;//对全局变量进行修改 printf("after:child,pid:%d,ppid:%d,g_val:%d,&g_val:%p\n",getpid(),getppid(),g_val,&g_val); exit(0); } else { printf("parent,pid:%d,ppid:%d,g_val:%d,&g_val:%p\n",getpid(),getppid(),g_val,&g_val); } } return 0; }执行结果:
发现当子进程对全局变量进行修改之后,父进程中的值改变了,正如上面所说的子进程和父进程公用同一块地址空间
其实就是父进程和子进程公用同一张页表。地址空间模型如下图:
用vfork()创建子进程时,如果上面的代码将exit(0)注掉:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int g_val=100;//定义一个全局变量 int main() { int pid=vfork(); if(pid<0) { perror("vfork"); } else { if(pid==0) { printf("brfore:child,pid:%d,ppid:%d,g_val:%d,&g_val:%p\n",getpid(),getppid(),g_val,&g_val); g_val=200;//对全局变量进行修改 printf("after:child,pid:%d,ppid:%d,g_val:%d,&g_val:%p\n",getpid(),getppid(),g_val,&g_val); } else { printf("parent,pid:%d,ppid:%d,g_val:%d,&g_val:%p\n",getpid(),getppid(),g_val,&g_val); } } return 0; }
看到子进程和父进程运行完成后,再次循环执行,知道发生了段错误。
我们知道在main()函数中调用return (),和普通函数调用return ()函数效果是不一样的,并且return ()和exit()也是不一样,
在main()函数中调用return(),是将整个程序结束,
而在普通函数中调用return(),只是结束该函数,返回该函数调用处,程序从其调用处的下面开始执行。
exit()在任意位置使用时,都会使程序结束
我们上面的代码中子进程结束时没有调用exit(),那么它执行完后,会走到main()函数的return 0处,return 0,是一种正常的退出,返回其调用前,继续执行,所以会出现循环执行的结果,并且知道当一个函数走到return 处时,该函数的栈帧就销毁了,但你还再次想执行函数时,就会发生段错误。