[新手上路]批处理新手入门导读[视频教程]批处理基础视频教程[视频教程]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
天的白色影子

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

TOP

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

TOP

4-30修订

本帖最后由 qzwqzw 于 2011-4-30 22:28 编辑


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

TOP

32# ▄︻┻═┳一
我得承认关于explorer.exe环境空间的部分内容
写得是过于随意了些
虽然改过一次
仍然不是很满意

“只是需要重新启动explorer.exe才能刷新到它的环境空间中”
这句话未将问题的本质描述清楚
应用程序的环境空间有继承特性
这句话同样适用于explorer.exe以及其他应用程序
在你使用“任务管理器”结束exploer.exe时
应该知道“任务管理器”的进程位于进程树
System->smss.exe->winlogon.exe->taskmgr.exe
而修改注册表中的环境变量不会对这些进程的环境空间产生影响
那么使用taskmgr.exe启动的explorer.exe自然不会继承到新变量

而注销后重新登录或者系统重启
是由userinit.exe启动的explorer.exe
而userinit.exe的主要作用就是读取计算机和用户配置
包括环境变量
那自然会从注册表中抓取到新的变量
再被它的子进程explorer.exe所继承

我原文中的重启explorer.exe
是在这样的测试条件下成功的
打开cmd.exe
set _newvar=value设置一个新变量
taskkill /im:explorer.exe /f杀掉当前的explorer.exe进程
start explorer.exe启动一个心得explorer.exe进程
这个新进程作为cmd.exe的子进程
自然会继承cmd的环境空间
其中就包括新定义的变量_newvar

不知道这样说你是否清楚了?
1

评分人数

天的白色影子

TOP

本帖最后由 qzwqzw 于 2011-5-6 17:17 编辑

05-06修订

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

TOP

41# temp
测试了一下
因为没有现成的inf文件
直接regedit修改HKEY_CURRENT_USER\Environment下的已有变量
然后杀进程、刷新、启进程
结果无效!

另外印象中有直接刷新当前Explorer.exe环境空间的命令行
似乎也是用的rundll32
可惜找不到了
天的白色影子

TOP

49# temp
试了两台PC
均为XP SP2
表现均为注册表更新
而实际cmd未更新
倒是批处理中HDD变量
因为没有setlocal
结果被explorer继承
成为伪全局变量
附件: 您需要登录才可以下载或查看附件。没有帐号?注册
天的白色影子

TOP

这个你有测试成功吗?
天的白色影子

TOP

返回列表