php加载脚本参数和环境变量

17 篇文章 1 订阅

        通过上篇文章 聊聊php脚本执行流程 我们知道 php 脚本执行流程大致分为以下几步:

加载命令行参数和环境变量
信号处理
模块初始化
请求初始化
执行请求

        今个就来看看是如何加载命令行参数和环境变量的,这块是在 save_ps_args() 方法中处理的。

extern char** environ;
// save the original argv[] location here
static int save_argc;
static char** save_argv;
// This holds the 'locally' allocated environ from the save_ps_args method. This is subsequently free'd at exit.
static char** frozen_environ, **new_environ;
// will point to argv area
static char *ps_buffer;
// space determined at run time
static size_t ps_buffer_size;

/*
 * Call this method early, before any code has used the original argv passed in from main().
 * If needed, this code will make deep copies of argv and environ and return these to the caller for further use.
 * The original argv is then 'clobbered' to store the process title.
 */
char** save_ps_args(int argc, char** argv)
{
    save_argc = argc;
    save_argv = argv;

    /*
     * If we're going to overwrite the argv area, count the available space.
     * Also move the environment to make additional room.
     */
    char* end_of_area = NULL;
    int non_contiguous_area = 0;
    int i;

    // check for contiguous argv strings
    for (i = 0; (non_contiguous_area == 0) && (i < argc); i++) {
        if (i != 0 && end_of_area + 1 != argv[i])
            non_contiguous_area = 1;
        end_of_area = argv[i] + strlen(argv[i]);
    }

    // check for contiguous environ strings following argv
    for (i = 0; (non_contiguous_area == 0) && (environ[i] != NULL); i++) {
        if (end_of_area + 1 != environ[i])
            non_contiguous_area = 1;
        end_of_area = environ[i] + strlen(environ[i]);
    }

    if (non_contiguous_area != 0)
        goto clobber_error;
    ps_buffer = argv[0];
    ps_buffer_size = end_of_area - argv[0];

    // move the environment out of the way
    new_environ = (char **) malloc((i + 1) * sizeof(char *));
    frozen_environ = (char **) malloc((i + 1) * sizeof(char *));
    if (!new_environ || !frozen_environ)
        goto clobber_error;
    for (i = 0; environ[i] != NULL; i++) {
        new_environ[i] = strdup(environ[i]);
        if (!new_environ[i])
            goto clobber_error;
    }
    new_environ[i] = NULL;
    environ = new_environ;
    memcpy((char *)frozen_environ, (char *)new_environ, sizeof(char *) * (i + 1));
    ...
    char** new_argv;
    int i;

    new_argv = (char **) malloc((argc + 1) * sizeof(char *));
    if (!new_argv)
        goto clobber_error;
    for (i = 0; i < argc; i++) {
        new_argv[i] = strdup(argv[i]);
        if (!new_argv[i]) {
            free(new_argv);
            goto clobber_error;
        }
    }
    new_argv[argc] = NULL;
    ...
    argv = new_argv;
    ...
    /* make extra argv slots point at end_of_area (a NUL) */
    int i;
    for (i = 1; i < save_argc; i++)
        save_argv[i] = ps_buffer + ps_buffer_size;
    ...
    return argv;
    ...
}

        方法首先通过循环遍历 argv 和 environ 两个数组来确定需要当前所用空间的边界,并记录在 end_of_area 中,最终减去初始存储位置 argv[0] 便得到所需空间大小,也保存在 ps_buffer_size 中。数组 argv 很显然是运行 php 脚本的参数,可通过 gdb 打印验证:

(gdb) p argv
$17 = (char **) 0x7fffffffe3f8
(gdb) p argv[0]
$18 = 0x7fffffffe670 "/usr/bin/php"
(gdb) p argv[1]
$19 = 0x7fffffffe67d "test.php"
(gdb) p argv[2]
$20 = 0x0

        而 environ 通过前面的关键字 extern 可以知道,是引入的外部变量,实际上就是指向当前进程的环境变量表的指针,本身也是存储在栈空间的。可打印几个看下:

(gdb) p environ
$21 = (char **) 0x7fffffffe410
(gdb) p environ[0]
$22 = 0x7fffffffe686 "HOSTNAME=localhost.localdomain"
(gdb) p environ[1]
$23 = 0x7fffffffe6a5 "SHELL=/bin/bash"
(gdb) p environ[33]
$24 = 0x7fffffffefae "BASH_FUNC_module()=() {  eval `/usr/bin/modulecmd bash $*`\n}"
(gdb) p environ[34]
$25 = 0x0

        也可通过进程 pid 来进行验证:

[root@localhost main]# ps -ef | grep test.php
lucas  4333 20199  0 16:28 pts/2    00:00:00 /usr/bin/php test.php
root   9042 21330  0 16:38 pts/0    00:00:00 grep test.php
[root@localhost main]# cat /proc/4333/environ 
HOSTNAME=localhost.localdomainSHELL=/bin/bashTERM=xterm...BASH_FUNC_module()=() {  eval `/usr/bin/modulecmd bash $*`

        在 /proc/4333/environ 文件中保存的就是当前进程的全部环境变量,只不过并不是以数组形式保存,而是拼接成字符串。通过大写字母来标识下一个 key,如果上一个 value 的最后一个字符也是大写字母,就在中间加一个 ‘_’ 符号以作区分。插一句,如何知道变量是存储在栈区还是堆区呢?这里还是通过进程 pid 查看:

[root@localhost cli]# cat /proc/4333/maps 
00400000-00ec3000 r-xp 00000000 08:01 268775                             /usr/local/php-7.1/bin/php
010c3000-01181000 rw-p 00ac3000 08:01 268775                             /usr/local/php-7.1/bin/php
01181000-011c8000 rw-p 00000000 00:00 0                                  [heap]
...
7ffffffea000-7ffffffff000 rw-p 00000000 00:00 0                          [stack]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

        由上可以看到,堆区间范围是0x1181000-011c8000,栈区间范围是0x7ffffffea000-7ffffffff000。回到 save_ps_args() 方法中,为什么程序要计算这两个变量的存储位置呢,而且在计算 argv 后,紧接着就计算 environ,难不成这两个变量在内存中是连着存储的?事实上确实如此!我们看下 c 程序在内存中存储空间分布情况:

        从低到高分别是:正文->初始化数据->未初始化数据->堆->栈->命令行参数和环境变量。 可以看到参数和环境变量是一起存储在进程地址空间顶部的,再通过 gdb 打印看下:

(gdb) p argv[0]
$38 = 0x7fffffffe670 "/usr/bin/php"
(gdb) p argv[1]
$39 = 0x7fffffffe67d "test.php"
(gdb) p argv[2]
$40 = 0x0
(gdb) p environ[0]
$41 = 0x7fffffffe686 "HOSTNAME=localhost.localdomain"
(gdb) p argv[3]
$42 = 0x7fffffffe686 "HOSTNAME=localhost.localdomain"

        argv[0] 存储的是字符串 “/usr/bin/php”,末尾还存储一个 '\0',共13个字符,存储地址也就是 0x7fffffffe670~0x7fffffffe67c;

        argv[1] 存储的是字符串 "test.php",算上结束符,共9个字符,存储地址应该就是 0x7fffffffe67d~0x7fffffffe685;

        而地址 0x7fffffffe686 存储的是啥呢?正是数组 environ 的第一个元素。所以数组 argv 存储地址后面紧接着就是数组 environ 的首地址!这样即使参数只有两个 argv[0] 和 argv[1],并在 argv[2] 被赋值为空的前提下,打印 argv[3] 也能看到具体的值,而不是报错。

        通过循环遍历数组 environ 后,i 值就保存了环境变量的个数,由于程序是初次运行,所以需要添加环境变量。但是通过进程地址空间分布我们知道,该部分是存储在地址空间的顶部,所以它不能再向高地址方向扩展;同时它低地址处存储的是已经分配的各栈帧,所以也不能向低地址方向扩展。只能调用 malloc 为新的环境表分配空间,接着将原来的环境表复制到新分配区,并按顺序复制每个环境变量,然后再将一个空指针放在末尾,最后将 environ 指向新指针表。同时如果原来的环境表位于栈顶上方,就还得将此表移至堆中。

        注意在将原环境表元素复制到新分配空间时,这里调用的是 strdup() 方法。是因为新申请的 new_environ 是在堆区间,变量 environ 是在栈区,而在 strdup() 方法内部,会自动调用 malloc()  方法先分配与参数 environ[i] 相同大小的一段空间,然后将参数内容复制到该地址空间,最后返回该地址。而从 new_environ 复制到 forzen_environ 过程中,由于这俩都是在堆区,所以直接通过 memcpy() 即可。

        截止此时,环境变量已经存储在堆区了,而命令行参数依然存储在栈空间上方,也需要 copy 一份到堆空间来。这里是声明了变量 new_argv,同样也是循环遍历 argv 数组,通过 strdup() 方法复制内容,最终再将 argv 指向新分配的参数地址 new_argv,并返回。这里可以通过打印调用 save_ps_args() 前后 argv 存储地址看到:

(gdb) p argv
$43 = (char **) 0x7fffffffe3f8
(gdb) n
1212		argv = save_ps_args(argc, argv);
(gdb) 
1214		cli_sapi_module.additional_functions = additional_functions;
(gdb) p argv
$44 = (char **) 0x11a7e20

        调用前存储地址为 0x7fffffffe3f8,恰好位于栈地址空间范围内,调用后存储地址为 0x11a7e20,也正好位于堆区中。

        至此,脚本参数和环境变量是如何处理的就基本理清,欢迎交流。

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值