[新手上路]批处理新手入门导读[视频教程]批处理基础视频教程[视频教程]VBS基础视频教程[批处理精品]批处理版照片整理器
[批处理精品]纯批处理备份&还原驱动[批处理精品]CMD命令50条不能说的秘密[在线下载]第三方命令行工具[在线帮助]VBScript / JScript 在线参考
返回列表 发帖

[系统相关] [讨论]环境变量的存储(2011-05-18更新)

05-18修订

  • 修订Windows环境空间的分类以及优先级
  • 增加无需注销更新系统环境变量的方案


05-06修订

  • 增加Windows环境变量的注册表存储位置
  • 修改explorer.exe下修改环境变量的说明


4-30修订

  • 增加Windows的环境变量排序、动态变量、和setlocal/endlocal
  • 增加msdos的set命令、autoexec.bat、setx.exe工具的说明
  • 修改windows下环境空间和变量读写的说明


因为看了置顶主题“批处理变量表机制的猜测及测试”
http://www.bathome.net/thread-12030-1-1.html
有感于中间讨论的东西太多太杂
很多常识性的概念和论点被反复讨论
大段的测试代码看的让人头晕
又充斥了一堆似是而非甚至是谬误的言论
所以将变量存储的一些细节另发主题
这里是纯理论探讨
代码测试请另开主题

一、MS-DOS下的环境变量
MS-DOS系统太久远了
关于环境变量的处理这里不做过多的讨论
只大概明确两点
1.环境变量空间分为两类:全局和局部
全局环境由系统核心COMMAND.COM独立创建和维护
局部环境由COMMAND载入exe等应用程序时创建
局部环境创建时会复制全局环境的值
环境空间地址写入程序的PSP
但应用程序结束后不会写会全局环境
大多数操作环境的函数都是针对局部环境的
要写入全局环境需要通过特殊手段得到全局环境的起始地址
但是set命令是针对全局环境进行操作的
因为它是command.com的内部命令
它必然有command.com解释执行
所以自然会索引到command.com的环境空间
也就是全局环境空间
通过C盘根目录的config.sys和autoexec.bat文件可以配置MS-DOS的环境变量

2.MSDOS环境变量是按字节存储的
是按ASCII表顺序存储
变量名会统一转换为大写处理
所以不存在大小写的比较问题
COMSPEC例外,因为它在系统启动时指引IO.SYS寻找COMMAND.COM
其形式是“ 【变量名】=【变量值】【字节0】”
读取变量的方式是顺序遍历
之所以没有用二分搜索遍历
是因为那是的变量空间相对较小
变量的使用频率也相对较低
相对于与变量的索引性能而言
顺序遍历简单而够用
写入变量的方式是顺序遍历后插入
也就是说新变量后的变量会顺序后移

二、Windows下的环境变量
到了Windows系统下发生了很多变化

首先,环境空间变成了三级:
1)系统环境空间,存储在注册表 HKLM\System\CurrentControlSet\Control\Session Manager\Environment\
2)用户环境空间,存储在注册表 HKCU\Environment\  和  HKCU\Volatile Environment
3)启动环境空间,存储在文件 系统盘:\AUTOEXEC.BAT

为了兼容性考虑
位于系统盘根目录的autoexec.bat
仍可以预定义Windows的环境变量
为了安全性考虑
系统只解释autoexec.bat中的环境变量设置语句
如set、path、append等
其它语句不予理会

系统启动时应用程序根据自己的需要读取环境变量
然后写入应用程序自己的环境空间
如果某个环境变量在多个环境空间被定义
将按照“系统、启动、用户”的顺序依次读取并设置
最后被设置的值为环境变量的当前值

path变量是一个特例
它是由"系统、用户、启动"三级空间的对应值组合而来的
也就是说当前path=系统path+用户path+启动path
同样的例子还有LibPath 和 Os2LibPath

应用程序的环境空间有继承特性
如果A进程启动了B进程
那么B进程作为A进程的子进程
将缺省集成A进程的环境空间

而MS-DOS的全局环境空间
类似于现在Windows下explorer.exe的环境空间
因为explorer.exe是Windows默认的外壳
很多情况下用户所启动的程序
包括从开始运行输入cmd、点击“命令提示符”或者批处理文件所启动的cmd.exe
都是作为explorer.exe的子进程运行
所以会继承它的环境空间

两类环境变量通常在“控制面板”的“系统属性”中更改
这不仅了修改了注册表中的相对应的键值
也同时修改了explorer.exe自己的环境空间
所以不用重启电脑马上可以在cmd.exe看到这个变化

也可以直接修改注册表中对应子键的键值
但是需要注销当前用户或重启计算机
由userinit.exe进程重新读取到新的变量状态
从而继承到explorer.exe的环境空间中
在新启动的类似cmd.exe的子进程中才能观察到这种变化

若要无需注销而更新环境空间
可以向所有窗口发送一个 WM_SETTINGCHANGE 广播消息
相关的应用程序(资源管理器、任务管理器、控制面板等)会执行更新
代码示例
  1. SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0,
  2.     (LPARAM) "Environment", SMTO_ABORTIFHUNG,
  3.     5000, &dwReturnValue);
复制代码
另外,在微软发行的Windows XP Service Pack 2 Support Tools(并非SP2)中
附带了一个命令行工具setx.exe
它可以在命令行中直接修改用户变量或者系统变量

其次,因为Windows系统自2000版本开始内部已全面采用Unicode编码
所以Windows的环境变量也是以Unicode形式存储
也就是说无论英文、汉字还是日文都会占用两个字节的空间
这也就解释了用set获取变量的长度或者子串时都是以字符计数
而非以ANSI存储(对于ASCII是单字节,对于GBK是双字节)的字节计数

环境变量的排序规则也从ASCII更新到了Unicode编码排序
只不过因为Windows支持了小写的变量名形式
所以它的排序比较是忽略大小写的
包括全局英文的大小写也被忽略

再次,对于Windows下的COMMAND.COM曾经有过讨论
它仅仅是虚拟机(ntvdm)封装下的cmd.exe的入口程序
当我们启动command.com时
实际上是启动了ntvdm.exe进程
这个ntvdm不仅仅提供了command命令到cmd.exe命令的虚拟通道
也对16位程序所需的内存环境做了虚拟
包括内存中的环境变量
可以通过创建PIF文件(16位DOS程序的快捷方式)定制内存环境(包括变量环境)
也可以修改%winir%\system32下的CONFIG.NT和AUTOEXEC.NT配置默认的虚拟内存环境

NTVDM虚拟环境最主要的变化就是
把双字节的Unicode映射为了单字节的ASCII编码和双字节的本地化编码(GB)
而debug.exe因为程序本身是为16位环境设计
必须运行在NT虚拟机环境下
启动debug.exe则同时会启动ntvdm.exe
所以debug操作的是ntvdm虚拟出的内存环境
所以它所看到的是单字节的环境变量存储

另外,cmd下多了很多特殊的环境变量
如果cmd的命令扩展被启用(默认是启用状态)
有几个动态环境变量可以被引用
每次变量数值被扩展时
这些变量数值都会被动态计算
它们不会出现在 SET 显示的变量列表中
也不存在于cmd的环境空间中
%CD% - 扩展到当前目录字符串。
%__CD__% - 扩展到当前目录字符串。末尾总会附带分隔路径的斜线。
%DATE% - 用跟 DATE 命令同样的格式扩展到当前日期。
%TIME% - 用跟 TIME 命令同样的格式扩展到当前时间。
%RANDOM% - 扩展到 0 和 32767 之间的任意十进制数字。
%ERRORLEVEL% - 扩展到当前 ERRORLEVEL 数值。
%CMDEXTVERSION% - 扩展到当前命令处理器扩展名版本号。
(NT4下是1;2000,XP下是2;Vista,Win7未确认)
%CMDCMDLINE% - 扩展到调用命令处理器的原始命令行。

还有一些动态变量更为特殊
它们占用了环境空间但是却不会出现在set命令的列表中
不过可以用一些不常见的用法看到它的存在 set,
即set后面直接跟一个半角逗号(也可以是分号或者 set "")
在命令输出的最上方会多出类似下面的变量
=::=::\         标识未访问盘符的当前路径
=C:=C:\Documents and Settings\Administrator   标识已访问盘符的当前路径
=ExitCode=00000001      以32位的16进制数标识Exit /b所指定的错误返回码
它使十进制到十六进制数据转换更加快捷
=ExitCodeASCII=00000001      以ASCII表中对应的字符标识Exit /b所指定的错误返回码
指定的返回码必须大于等于32(也就是空格的ASCII码值)它才会被设置
它使ASCII码值转字符更加方便

最后说说setlocal/endlocal
它会将cmd.exe的当前环境空间另选新的内存空间备份一份
直到endlocal把这个备份空间恢复到当前空间并回收内存
嵌套使用setlocal时会将环境空间多次备份
而后endlocal时会从新到旧恢复空间并收回内存
3

评分人数

    • 523066680: 细致入微PB + 30 技术 + 10
    • CrLf: 当时没看懂,现在才明白此贴的价值技术 + 1
    • applba: 膜拜技术 + 1
天的白色影子

真正的高人,看得我云里雾里,这个可能要慢慢消化了。。。
***共同提高***

TOP

本帖最后由 batman 于 2011-4-27 22:20 编辑

“所以Windows的环境变量也是以Unicode形式存储”

这点我有疑问,因为cmd中变量+=号+值的最大字节数是8196,也就是说我当我设置名为str的变量时,str=后面可以跟上8192个单字节字符,如果说无论什么字符都占两个字节,那么为什么后面还能跟8192个单字节字符?
***共同提高***

TOP

请确认你的信息来源
这条信息应该是错误的
环境变量的最大长度为8192个字符数(而非字节数)
具体可以用代码测试
限于顶楼规定我就不粘贴了
天的白色影子

TOP

4# qzwqzw
是我搞错了,确实是字符数,8192这个数字也记错了,2的13次方这下不会错了。。。
***共同提高***

TOP

本帖最后由 powerbat 于 2011-4-27 22:55 编辑

http://technet.microsoft.com/en-us/library/bb490954.aspx
The maximum individual environment variable size is 8192bytes.

http://www.bathome.net/viewthrea ... muid=29086#pid21569
已经讨论过,微软的说法中“字节(byte)”是不准确的,实际可以为字符。

“所以Windows的环境变量也是以Unicode形式存储”,难怪如此,虽然字面上单个变量最大长度为8192字节/字符,但存储时占用空间可能为(8192*2)个字节。

TOP

感觉有点热哦

本帖最后由 neorobin 于 2011-4-27 23:01 编辑

楼主所述如果可以列出网上资料 (希望有微软的) 来源 (当然是部分难以查找的资料, 我该先自己搜搜学学), 我想看看, 谢谢!

TOP

很好奇这些结论是怎么证明出来的...

TOP

还是linux方便,setenv()、getenv() 自由使用

TOP

限于时间仓促
有些特殊存储的环境变量未及表述
比如date、time、random、cmdcmdline、cd等动态变化的状态变量
比如::、=c:、=d:、=e:等无法正常读取和写入的当前路径变量
有空再说吧
天的白色影子

TOP

本帖最后由 hanyeguxing 于 2011-4-28 13:47 编辑

8# zm900612


  父进程 explorer.exe 在新建 cmd.exe 进程的用户空间为其建立了 PEB 之后,还通过 KlInitPeb (在父进程的用户空间执行)对此 PEB 加以进一步的初始化,在此之前已经准备好了 PPB 作为调用参数之一传下来。进程参数块是一个 RTL_USER_PROCESS_PARAMETERS 数据结构,里面带下来的参数中有一个指针 Environment ,指向一个“宽字符 Unicode” 串,这就是各环境变量的定义。

  参数 PPB 实际涉及两块数据。一块是进程参数块本身,这是有固定大小的;另一块的内容是一些环境变量字符串,这些字符串游离在进程参数块外面,并且也没有固定的长度。这里的目的就是要把这两块数据都复制到新建 cmd.exe 进程的用户空间去,并相应地设置好相关数据结构中的指针。为此,程序中首先要确定这些环境变量字符串所占的长度 EnvSize 。

  知道了这些环境变量字符串所占的长度 EnvSize 以后,如果非 0,就要在新建 cmd.exe 进程的用户空间分配相应的区间。这里在调用 NtAllocateVirtualMemory 时的第 2 个参数、即指针 EnvPtr 的值已预先设置为 NULL ,表示对起始地址没有特定的要求;并且第 3 个参数、即要求在所分配起始地址中前导 0 的个数也是 0 ,因此可以分配在任意的部位 (前导 0 的个数实际上大致上给定了一个部位)。对于这样的分配要求,内存管理会在目标空间从低到高扫描,以找到第一个符合大小要求的区间。由于用户空间的起点是 0x10000 ,这又是第一次要求由内存管理自由分配,所以实际分配的位置一定在 0x10000 处。另一方面,由于区间分配的粒度是 64KB ,所以实际分配的一般总是 64KB ,因为很难设想 EnvSize 会大于 64KB 。实际分配的位置和大小则通过参数 EnvPtr 和 EnvSize1 返回。然后通过 NtWriteVirtualMemory 将来自参数 Ppb、或当前进程的环境变量字符串复制到新建 cmd.exe 进程用户空间的这个区间中。

  接着把 PPB 复制过去并对新建进程用户空间 PPB 和 PEB 中的几个成分作出修正,然后 NtReadVirtualMemory 从 PEB 读取其 ImageBaseAddress 字段,并通过参数 ImageBaseAddress 返回目标映像装入用户空间后的起始地址。

  再接着就是创建新进程的第一个线程、堆栈、TEB 等等。。。。。。
寒夜孤星:在没有说明的情况下,本人所有代码均运行在 XP SP3 下 (有问题请发贴,QQ临时会话已关闭)

TOP

我说他们瞎折腾他们又不听,还是兄理顺了点思路,不过要搞清楚还是需要更详细的解释,期待内行!~
寂寞是黑白的,但黑白不是寂寞,是永恒。BAT 需要的不是可能,而是智慧。

TOP

12# cjiabing
折腾不好吗?理论不都是折腾出来的。。。

ps:当初牛顿直接把那个苹果吃掉,万有引力定律将会在何方?
***共同提高***

TOP

11# hanyeguxing


这个...惭愧,基本上还是看不懂...

TOP

本帖最后由 ▄︻┻═┳一 于 2011-4-28 17:01 编辑

1# qzwqzw


在“系统属性”中更改全局变量和用户变量后
不仅仅了修改了注册表中的相对应的键值
也同时修改了explorer.exe的环境变量
所以如果我们仅仅修改注册表中的环境变量键值
那么需要重新启动explorer.exe才能读取到环境空间中


重启explorer.exe? 何种方式重启,是任务管理器结束 然后 start explorer的方式还是takill 杀explorer 然后 start explorer?

还是 用注销的方式 ?

TOP

返回列表