进程的创建,fork()和vfork()的不同以及认识虚拟地址空间,环境变量的了解

一、环境变量

我们说程序是一个可执行的二进制代码,在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 处时,该函数的栈帧就销毁了,但你还再次想执行函数时,就会发生段错误。


猜你喜欢

转载自blog.csdn.net/misszhoudandan/article/details/80104638