进程环境

进程环境

在本篇文章中将要了解进程的环境。主要包括当程序执行时main函数如何被调用;命令行参数如何传递给新程序;典型的存储空间布局样式;进程如何使用环境变量;进程的各种不同终止方式。

C程序的启动和终止
一个c程序的启动与终止过程如下:
这里写图片描述
内核使程序执行的唯一方法是调用一个exec函数。进程自愿终止的唯一方法是显示或隐式地(通过调用exit)调用_exit或_Exit,进程也可以非自愿的由一个信号使其终止。

1、main函数
对于一个c程序总是从main函数开始执行的,main函数的原型如下:

int main(int argc, char *argv[]);

当内核执行C程序时,在调用main前先调用一个特殊的启动例程。可执行程序文件将此启动例程指定为程序的起始地址——这是由连接编辑器设置的,而连接编辑器由C编译器调用。启动例程从内核取得命令行参数和环境变量值,然后为按上述方式调用main函数做好安排。

2、进程终止
有8种方式使进程终止,其中有5种为正常终止:


• 从 main 返回
• 调用 exit
• 调用_exit 或者_Exit
• 最后一个线程从启动例程返回
• 从最后一个线程调用 pthread_exit


3种异常终止方式为:


• 调用abort
• 接到一个信号
• 最后一个线程对取消请求做出响应


其中有三个函数用于正常终止一个进程,根据上图可以看到,其中_exit 和 _Exit 立即进入内核,exit 自动调用终止处理程序(通过函数atexit来登记这些函数)来执行一些处理,然后返回内核。如下:

    #include <stdlib.h>
    void exit(int status);
    void _Exit(int status);
    int atexit(void (*func)(void));

    #include <unistd.h>
    void _exit(int status);

例如:

#include "apue.h"

static void my_exit1(void);
static void my_exit2(void);

int
main(void)
{
    if (atexit(my_exit2) != 0)
        err_sys("can't register my_exit2");

    if (atexit(my_exit1) != 0)
        err_sys("can't register my_exit1");
    if (atexit(my_exit1) != 0)
        err_sys("can't register my_exit1");

    printf("main is done\n");
    return(0);
}

static void
my_exit1(void)
{
    printf("first exit handler\n");
}

static void
my_exit2(void)
{
    printf("second exit handler\n");
}

运行如下:

xucheng@xucheng-Inspiron-5520:~/xc$ ./a.out
main is done
first exit handler
first exit handler
second exit handler

可以看出
终止处理程序每调用一次就会执行一次,并且调用的顺序与执行的顺序相反(栈)。

C程序的存储空间布局
典型的存储空间布局如下:
这里写图片描述
包含以下几个方面:
• 正文段:这是由CPU执行的机器指令部分。通常,正文段是只读共享的,在存储器中只需一个副本。
• 初始化数据段:通常将此段称为数据段,它包含了程序中需要明确赋初值的变量。
例如,任何函数之外的声明 int max=99;
• 未初始化数据段:通常将此段称为 bss 段,意思是”由符号开始的块“,在程序开始执行前,内核将此段中数据初始化为0或空指针。
• 栈:栈空间向低地址增长。自动变量以及每次函数调用时所需保存的信息都存放在此段中。每次函数调用时,其返回地址、以及调用者的环境信息(例如某些机器寄存器)都存放在栈中。然后,新被调用的函数在栈上为其自动和临时变量分配存储空间。通过以这种方式使用栈, C函数可以递归调用。
• 堆:通常在堆中进行动态存储分配。由于历史上形成的惯例,堆位于非初始化数据段顶和栈底之间。

环境变量
每个程序都收到一张环境表。与参数表一样,环境表也是一个字符指针数组,每个指针包含一个以null结尾的c字符串的地址。全局变量environ包含了该指针数组的地址。

extern char**environ;
//environ 称为环境指针,指针数组为环境表,其中各指针指向的字符串为环境字符串

例如:
这里写图片描述

如上所示环境变量字符串的形式是name=value,通常我们要访问环境变量的值是通过函数,而不是访问environ指针,C中定义了以下几个函数来查看环境变量的值以及修改环境变量,如下所示:

//查看环境表的函数
#include <stdlib.h>
char* getenv(const char* name);

//修改环境表的函数:
#include <stdlib.h>
int putenv(const char* str);//成功返回 0,出错返回非0

int setenv(const char* name,const char* value,int rewrite);//成功返回 0,出错返回-1
void unsetenv(const char* name);//成功返回 0,出错返回-1
/*
•   putenv取形式为name=value的字符串,将其放到环境表中。如果name已经存在,则先删除其原来的定义
•   setenv将name设置为value。如果在环境中name已经存在,那么(a)若rewrite非0,则首先删除其现存的定义;(b)若rewrite为0,则不删除其现存定义(name不设置为新的value,而且也不出错)
•   unsetenv删除name的定义。即使不存在这种定义也不算出错

*/

对于以上函数的运用说明,例如:


#include <stdio.h>
#include <stdlib.h>
int
main(void)
{
    char *envVal;
    char *myenv;
    if ((envVal = getenv("PATH")) == NULL) {
        printf("not environment variable PATH\n");
    } else {
        printf("PATH=%s\n", envVal);
    }
    if (setenv("myenv", "xucheng", 0) == -1) {
        printf("setenv error\n");
     } else {
        myenv = getenv("myenv");
        printf("myenv=%s\n", myenv);
    }
    if (putenv("myenv1=hello") != 0) {
         printf("putenv error\n");
    } else {
         myenv = getenv("myenv1");
         printf("myenv1=%s\n", myenv);
    }
    exit(0);
}

编译后结果如下:

xucheng@xucheng-Inspiron-5520:~/xc gccenvironment.cxucheng@xuchengInspiron5520: /xc g c c e n v i r o n m e n t . c x u c h e n g @ x u c h e n g − I n s p i r o n − 5520 :   / x c ./a.out
PATH=/home/xucheng/bin:/home/xucheng/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
myenv=xucheng
myenv1=hello

NOTE:p**utenv和setenv之间的区别。setenv必须分配存储空间,以便依据其参数创建name=value字符串。putenv可以自由的将传递给它的参数字符串直接放到环境中。**因此将存放在栈中的字符串作为参数传递给putenv会发生错误,原因是从当前函数返回时,其栈帧占用的存储区可能被重用。
在存储空间布局中可以知道,环境变量与环境表通常放在进程存储空间的最顶部,删除一个字符串很简单,只要在环境表中找到对应的指针,然后将其后面的指针依次向首部移动一个位置。但是增加一个字符串或者改变一个字符串就不是那么容易,由于环境字符串和环境表通常在进程空间的最顶端,所以他不能向上扩展;同时他也不能移动在他之下的各个栈帧,所以它也不能向下扩展。所以通常的做法如下:


如果修改一个现有的name:
1、如果新value的长度小于或等于现有value的长度,只要将新字符串复制到原字符串的空间中就可以。
2、如果新value的长度大于现有value的长度,则必须调用malloc为新字符串分配空间,然后将新字符串复制到新空间中,并且让环境表中指向name的指针指向新分配的空间。
如果新增加一个name:
对于新增加一个name,首先调用malloc为新字符串分配空间,然后将新字符串name=value复制到新空间中。
1、如果这是第一次增加name,首先必须调用malloc为环境表分配空间,然后将原来的环境表复制到新分配的空间,然后在表尾加上指向新字符串name=value的指针,并且在最后加上空指针,最后让指针指向environ指向新指针表。此时可以看到,虽然环境表从原来的栈顶移到了堆中,但是其中的大多数指针仍然指向栈顶。
2、如果这不是第一次增加name,那么说明在之前已经调用malloc在堆中分配了空间,所以此时只要调用realloc,分配比原来空间多一个指针的空间就可以,然后将指向新字符串name=value的指针添加到表尾。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值