Windows 驱动器号不限于 A-Z
仅就标题而言,这纯粹是个冷知识,可通过内置的 subst 工具(及其他方法)验证。
以下示例将 C:foo 目录创建为 +: 驱动器的别名:
subst +: C:foo
此时 +: 驱动器可正常使用(至少在 cmd.exe 中如此,后文将详细说明):
> cd /D +:
+:> tree .
Folder PATH listing
Volume serial number is 00000001 12AB:23BC
+:
└───bar
然而理解其背后的原理,能揭示Windows底层运作机制的诸多奥秘,并发现若干有趣现象。
驱动器字母究竟是什么?🔗
大多数人熟悉的路径是Win32命名空间路径,例如C:foo这类驱动器绝对路径。然而,像CreateFileW这类接受Win32路径的高级API,最终会在调用ntdll.dll中的底层API(如 NtCreateFile。
通过NtTrace可验证此过程:当调用CreateFileW并传入C:foo时,最终会以??C:foo为参数调用NtCreateFile:
NtCreateFile( FileHandle=0x40c07ff640 [0xb8], DesiredAccess=SYNCHRONIZE|GENERIC_READ|0x80, ObjectAttributes="??C:foo", IoStatusBlock=0x40c07ff648 [0/1], AllocationSize=null, FileAttributes=0, ShareAccess=7, CreateDisposition=1, CreateOptions=0x4000, EaBuffer=null, EaLength=0 ) => 0
NtClose( Handle=0xb8 ) => 0
注:关键部分为 ObjectAttributes=“??C:foo”
测试代码与复现信息
createfilew.zig:
const std = @import("std");
const windows = std.os.windows;
const L = std.unicode.wtf8ToWtf16LeStringLiteral;
pub extern "kernel32" fn CreateFileW(
lpFileName: windows.LPCWSTR,
dwDesiredAccess: windows.DWORD,
dwShareMode: windows.DWORD,
lpSecurityAttributes: ?*windows.SECURITY_ATTRIBUTES,
dwCreationDisposition: windows.DWORD,
dwFlagsAndAttributes: windows.DWORD,
hTemplateFile: ?windows.HANDLE,
) callconv(.winapi) windows.HANDLE;
pub fn main() !void {
const path = L("C:\foo");
const dir_handle = CreateFileW(
path,
windows.GENERIC_READ,
windows.FILE_SHARE_DELETE | windows.FILE_SHARE_READ | windows.FILE_SHARE_WRITE,
null,
windows.OPEN_EXISTING,
windows.FILE_FLAG_BACKUP_SEMANTICS | windows.FILE_FLAG_OVERLAPPED,
null,
);
if (dir_handle == windows.INVALID_HANDLE_VALUE) return error.FailedToOpenDir;
defer windows.CloseHandle(dir_handle);
}
构建版本:
zig build-exe createfilew.zig
使用 NtTrace 运行:
nttrace createfilew.exe > createfilew.log
该 ??C:foo 实为 NT 命名空间路径,正是 NtCreateFile 所期望的格式。要理解此路径,需先了解负责处理NT路径的对象管理器。
对象管理器🔗
注:本文主要基于这篇关于NT路径的精彩解析进行阐述,若需更详细内容请务必参阅原文。
对象管理器负责追踪命名对象,我们可通过WinObj工具进行探索。路径??C:foo中的??部分,实为对象管理器内的特殊虚拟文件夹,它整合了GLOBAL??文件夹与用户专属的DosDevices文件夹。
以我的系统为例,对象C:位于GLOBAL??内,实际是指向DeviceHarddiskVolume4的符号链接:

因此,??C:foo 最终解析为 DeviceHarddiskVolume4foo,而路径中 foo 部分的处理则取决于实际设备。
但关键在于:??C:foo 仅是引用设备路径 DeviceHarddiskVolume4foo 的 一种方式 。例如,卷也会通过其全局唯一标识符(GUID)创建命名对象,格式为Volume{18123456-abcd-efab-cdef-1234abcdabcd},该对象同样是指向DeviceHarddiskVolume4等路径的符号链接。因此路径?? Volume{18123456-abcd-efab-cdef-1234abcdabcd}foo 实际上等同于 ??C:foo。
注意:GLOBAL?? 包含名为 Global 的对象,该对象本身又是指向 GLOBAL?? 的符号链接,因此 ??GLOBALGLOBALC:foo(及其任意组合形式)最终都会解析为 DeviceHarddiskVolume4foo。
以上说明表明,命名对象C:本身并无特殊性;对象管理器将其视为普通符号链接进行解析。
那么驱动器号究竟是什么?🔗
在我看来,驱动器字母本质上只是 Win32 路径转换为 NT 路径时形成的约定。具体而言,这取决于 RtlDosPathNameToNtPathName_U 的实现。
换言之,由于RtlDosPathNameToNtPathName_U会将C:foo转换为??C:foo,因此名为C:的对象便会表现得像驱动器盘符。举例说明:在另一个可能的场景中,RtlDosPathNameToNtPathName_U 可能将路径 FOO:bar 转换为 ??FOO:bar,此时 FOO: 也会表现得像驱动器字母。
那么回到标题问题:RtlDosPathNameToNtPathName_U 如何处理 +:foo 这种路径?答案是:与 C:foo 完全相同:
> paths.exe C:foo
path type: .DriveAbsolute
nt path: ??C:foo
> paths.exe +:foo
path type: .DriveAbsolute
nt path: ??+:foo
测试程序代码
paths.zig:
const std = @import("std");
const windows = std.os.windows;
pub fn main() !void {
var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena_state.deinit();
const arena = arena_state.allocator();
const args = try std.process.argsAlloc(arena);
if (args.len <= 1) return error.ExpectedArg;
const path = try std.unicode.wtf8ToWtf16LeAllocZ(arena, args[1]);
const path_type = RtlDetermineDosPathNameType_U(path);
std.debug.print("path type: {}n", .{path_type});
const nt_path = try RtlDosPathNameToNtPathName_U(path);
std.debug.print(" nt path: {f}n", .{std.unicode.fmtUtf16Le(nt_path.span())});
}
const RTL_PATH_TYPE = enum(c_int) {
Unknown,
UncAbsolute,
DriveAbsolute,
DriveRelative,
Rooted,
Relative,
LocalDevice,
RootLocalDevice,
};
pub extern "ntdll" fn RtlDetermineDosPathNameType_U(
Path: [*:0]const u16,
) callconv(.winapi) RTL_PATH_TYPE;
fn RtlDosPathNameToNtPathName_U(path: [:0]const u16) !windows.PathSpace {
var out: windows.UNICODE_STRING = undefined;
const rc = windows.ntdll.RtlDosPathNameToNtPathName_U(path, &out, null, null);
if (rc != windows.TRUE) return error.BadPathName;
defer windows.ntdll.RtlFreeUnicodeString(&out);
var path_space: windows.PathSpace = undefined;
const out_path = out.Buffer.?[0 .. out.Length / 2];
@memcpy(path_space.data[0..out_path.len], out_path);
path_space.len = out.Length / 2;
path_space.data[path_space.len] = 0;
return path_space;
}
因此,如果名为+:的对象位于虚拟文件夹??中,我们可以预期Win32路径+:会像其他驱动器绝对路径那样行为,这正是我们所观察到的现象。
注:当像本文开头示例那样使用subst时,该+:对象会被创建在之前提到的用户级DosDevices文件夹中:

相关影响的初步探索🔗
本节仅聚焦于与我当前工作相关的若干要点。若您对此感兴趣,欢迎深入探究其潜在影响。
explorer.exe 拒绝配合🔗
非A-Z字母驱动器的磁盘无法在文件资源管理器中显示,也无法通过文件资源管理器访问。

在文件资源管理器中尝试导航至 +: 时出错
关于“无法显示”的问题,我推测是 explorer.exe 在遍历 ?? 路径时,仅会识别命名为 A: 至 Z: 的对象。关于“无法访问”的问题则更复杂,我推测explorer.exe在处理地址栏输入的路径时存在特殊逻辑,其中部分机制会限制驱动器字母仅限A–Z(即在实际尝试打开路径前就进行了短路判断)。
PowerShell 同样如此🔗
PowerShell 似乎也拒绝处理非 A–Z 驱动器:
PS C:> cd +:
cd : Cannot find drive. A drive with the name '+' does not exist.
At line:1 char:1
+ cd +:
+ ~~~~~~
+ CategoryInfo : ObjectNotFound: (+:String) [Set-Location], DriveNotFoundException
+ FullyQualifiedErrorId : DriveNotFound,Microsoft.PowerShell.Commands.SetLocationCommand
非ASCII驱动器字母🔗
驱动器字母完全不必局限于ASCII范围,也可使用非ASCII字符。
> subst €: C:foo
> cd /D €:
€:> tree .
Folder PATH listing
Volume serial number is 000000DE 12AB:23BC
€:
└───bar
非ASCII驱动器字母同样与A–Z字母一样不区分大小写:
> subst Λ: C:foo
> cd /D λ:
λ:> tree .
Folder PATH listing
Volume serial number is 000000DE 12AB:23BC
λ:
└───bar
但驱动器字母不能是任意Unicode字符或任意码点,其限制为单个WTF-16码单元(即u16类型,范围≤U+FFFF)。此前使用的工具(subst.exe)在尝试使用代码点大于U+FFFF的驱动器字母时会报错参数无效,但可通过直接调用MountPointManager绕过此限制:

创建`𤭢:`符号链接的代码
const std = @import("std");
const windows = std.os.windows;
const L = std.unicode.wtf8ToWtf16LeStringLiteral;
const MOUNTMGR_CREATE_POINT_INPUT = extern struct {
SymbolicLinkNameOffset: windows.USHORT,
SymbolicLinkNameLength: windows.USHORT,
DeviceNameOffset: windows.USHORT,
DeviceNameLength: windows.USHORT,
};
pub fn main() !void {
const mgmt_handle = try windows.OpenFile(L("\??\MountPointManager"), .{
.access_mask = windows.SYNCHRONIZE | windows.GENERIC_READ | windows.GENERIC_WRITE,
.share_access = windows.FILE_SHARE_READ | windows.FILE_SHARE_WRITE | windows.FILE_SHARE_DELETE,
.creation = windows.FILE_OPEN,
});
defer windows.CloseHandle(mgmt_handle);
const volume_name = L("\Device\HarddiskVolume4");
const mount_point = L("\DosDevices\𤭢:");
const buf_size = @sizeOf(MOUNTMGR_CREATE_POINT_INPUT) + windows.MAX_PATH * 2 + windows.MAX_PATH * 2;
var input_buf: [buf_size]u8 align(@alignOf(MOUNTMGR_CREATE_POINT_INPUT)) = [_]u8{0} ** buf_size;
var input_struct: *MOUNTMGR_CREATE_POINT_INPUT = @ptrCast(&input_buf[0]);
input_struct.SymbolicLinkNameOffset = @sizeOf(MOUNTMGR_CREATE_POINT_INPUT);
input_struct.SymbolicLinkNameLength = mount_point.len * 2;
input_struct.DeviceNameOffset = input_struct.SymbolicLinkNameOffset + input_struct.SymbolicLinkNameLength;
input_struct.DeviceNameLength = volume_name.len * 2;
@memcpy(input_buf[input_struct.SymbolicLinkNameOffset..][0..input_struct.SymbolicLinkNameLength], @as([*]const u8, @ptrCast(mount_point)));
@memcpy(input_buf[input_struct.DeviceNameOffset..][0..input_struct.DeviceNameLength], @as([*]const u8, @ptrCast(volume_name)));
const IOCTL_MOUNTMGR_CREATE_POINT = windows.CTL_CODE(windows.MOUNTMGRCONTROLTYPE, 0, .METHOD_BUFFERED, windows.FILE_READ_ACCESS | windows.FILE_WRITE_ACCESS);
try windows.DeviceIoControl(mgmt_handle, IOCTL_MOUNTMGR_CREATE_POINT, &input_buf, null);
}
(编译后的可执行文件必须以管理员身份运行)
然而仅创建符号链接并不能解决根本问题:
> cd /D 𤭢:
The filename, directory name, or volume label syntax is incorrect.
这是因为无法将驱动器绝对Win32路径𤭢:转换为对应的NT路径。如前所述,关键在于RtlDosPathNameToNtPathName_U的行为机制,经验证该函数不会将驱动器字母大于U+FFFF的绝对路径转换为对应NT路径:
C:foo> paths.exe 𤭢:foo
path type: .Relative
nt path: ??C:foo𤭢:foo
注:此行为符合预期,因Windows的Unicode支持早于UTF-16标准,故Windows通常不处理代理对,而几乎完全直接操作WTF-16码单元。
因此,当检查𤭢:(其WTF-16编码为<0xD852><0xDF62><0x003A><0x005C>)是否为驱动器绝对路径时,会验证path[1] == ‘:’,由于path[1]实际为0xDF62,该验证必然失败。
路径分类不匹配🔗
路径相关函数常在不使用系统特定API的情况下编写,这意味着RtlDosPathNameToNtPathName_U处理文件路径的方式与特定实现的path.isAbsolute处理方式极易产生冲突。
以随机示例说明:Rust仅将包含A–Z驱动器字母的路径视为绝对路径:
use std::path::Path;
fn main() {
println!("C:\ {}", Path::new("C:\foo").is_absolute());
println!("+:\ {}", Path::new("+:\foo").is_absolute());
println!("€:\ {}", Path::new("€:\foo").is_absolute());
}
> rustc test.rs
> test.exe
C: true
+: false
€: false
这是否构成值得修复的问题留作读者练习(我确实不确定是否存在问题),但还存在第二个细节(前文已提及)——文本编码差异可能导致相同路径在不同isAbsolute实现中返回不同结果。正是这个细节促使我深入研究整个问题——最近在对Zig的路径相关函数进行优化时 我发现对于C:这类模式,取值path[0]、path[1]和path[2]时,不同编码会导致它们指向路径的不同部分。例如对于€:(由字符码点<U+20AC><U+003A><U+005C>构成):
- 以WTF-16编码时,
U+20AC可编码为单个u16码点0x20AC,这意味着path[0]将为0x20AC,path[1]为0x3A(:),path[2]为0x5C(),呈现为驱动器绝对路径格式 - 采用WTF-8编码时,
U+20AC被编码为三个u8码点(0xE2 0x82 0xAC),这意味着path[0]将为0xE2,path[1]为0x82,path[2]为0xAC,这完全不像驱动器绝对路径
因此,要实现一种无论编码方式如何都统一处理路径的方案,*必须做出某些决策:
- 若需严格兼容
RtlDetermineDosPathNameType_U/RtlDosPathNameToNtPathName_U,处理 WTF-8 时需解码首个码点并检查<= 0xFFFF(此方案已用于 Zig 标准库,但本人对此并不十分满意) - 若需始终检查
path[0]/path[1]/path[2]且不关心非ASCII驱动器字母,则无论编码类型均检查path[0] <= 0x7F - 若仅关注标准
A–Z驱动器字母,则显式检查该范围( Rust的处理方式)
这并非 EURO 驱动器🔗
整个过程中我发现一个奇怪现象:kernel32.dll API中的SetVolumeMountPointW函数在处理非ASCII驱动器字母时存在独特特性。具体来说,这段代码(尝试创建驱动器€:)会成功:
const std = @import("std");
const windows = std.os.windows;
const L = std.unicode.wtf8ToWtf16LeStringLiteral;
extern "kernel32" fn SetVolumeMountPointW(
VolumeMountPoint: windows.LPCWSTR,
VolumeName: windows.LPCWSTR,
) callconv(.winapi) windows.BOOL;
pub fn main() !void {
const volume_name = L("\\?\Volume{18123456-abcd-efab-cdef-1234abcdabcd}\");
const mount_point = L("€:\");
if (SetVolumeMountPointW(mount_point, volume_name) == 0) {
const err = windows.GetLastError();
std.debug.print("{any}n", .{err});
return error.Failed;
}
}
但查看对象管理器时,€:符号链接并不存在…而¬:却存在:

基于我长期研究Windows系统怪癖的经验,我推测可能存在以下机制:SetVolumeMountPointW函数将0x20AC截断为0xAC,而U+00AC恰好对应¬字符。若情况确如所料,截断驱动器字母而非拒绝路径的行为确实很奇怪,但这也符合非ASCII驱动器字母属于边缘案例的逻辑——这种情况恐怕从未被认真考虑过。
总结🔗
虽然我粗略检索后未发现太多相关资料,但无法确定本文所述内容是否具有新颖性。目前我所知唯一提及非A–Z驱动器字母的文献是《Win32到NT路径转换权威指南》,其中指出:
人们自然会认为驱动器“字母”仅限于A到Z。但事实证明
RtlGetFullPathName_UAPI并未强制此要求,尽管资源管理器shell和命令提示符几乎肯定会强制执行。因此只要路径的第二个字符是冒号,转换就会将其视为驱动器绝对路径或驱动器相对路径。当然,若DosDevices对象目录中缺少相应的符号链接,此方法便收效甚微。
事实上,命令提示符同样未强制执行该限制。我推测围绕此特性还存在更多待发现的特殊情况。

NT路径是对象管理器引用对象的方式。例如注册表分支 HKEY_LOCAL_MACHINE 是 RegistryMachine 的别名
https://learn.microsoft.com/en-us/windows-hardware/drivers/k…
由此可见,NT与Unix的相似之处在于:许多对象本质上只是全局虚拟文件系统布局(即对象管理器命名空间)中的文件。
以驱动器字母开头的路径称为“DOSPath”,因其仅为兼容DOS而存在。但遗憾的是,即使在内核模式下,不同子系统仍可能引用DOSPath。
PowerShell 同样将各类对象暴露为“驱动器”,您完全可以为自定义应用创建专属驱动器。例如默认存在的'hklm:'驱动器路径:
https://learn.microsoft.com/en-us/powershell/scripting/sampl…
Get-PSDrive/New-PSDrive
例如在 Linux/Bash 中无法通过文件路径访问证书,但在 PowerShell/Windows 中可以实现。
强烈建议获取 NtObjectManager PowerShell 模块并探索以下内容:
https://github.com/googleprojectzero/sandbox-attacksurface-a…
ls NtObject:
令人费解的是,历经三十年发展,Windows仍固守着源自80年代的怪异目录命名结构——当软盘驱动器早已绝迹的今天,这种设计已毫无意义。
> Windows仍固守着源自80年代的怪异目录命名结构——当软盘驱动器早已绝迹的今天,这种设计已毫无意义。
我觉得同样的批评也适用于*nix系统,只不过它比Windows更糟糕十年(源自1970年代)。我强烈倾向于文件系统标准(fhs)而非微软的方案,但我们也不必假装fhs不是一堆垃圾(比如/usr/bin与/bin的区分、/etc用于配置文件、/media与/mnt的矛盾等等)。
Unix从根目录开始,这符合自然法则。它不会因存储介质改变特性——你甚至可以将软盘挂载到根目录。
何必纠结/media和/mnt之争?各行其是,我自有主张。
例如Step CA文档建议使用/etc/step-ca/目录(https://smallstep.com/docs/step-ca/certificate-authority-ser…)存放其产品配置。通常我也会这么做,但这次我手动安装时并未遵循常规文档,因此选择了/srv/step-ca。
我认为Unix文件系统布局的…“标准”…已足够明确,任何能力尚可的管理员都能分辨当前哪种方案稍有偏差,并完成任务。至于Windows…祝你好运。我在这两个平台担任系统管理员已有近30年,Windows比Unix更令人费解。
> Unix从根目录开始,这符合自然规律。它不会因存储介质改变特性——你甚至可以将软盘挂载到根目录。
为何某驱动器的根目录是
/,而其他驱动器的根目录却成为该驱动器的子目录?用命名空间概念思考或许有帮助:并非某个驱动器特殊,而是存在从
/起始的视图,某个磁盘文件系统恰好被挂载在此处,其他则挂载在别处; 像初始ramdisk这样的系统中,/目录下根本不存在任何驱动器,只有一块内存区域——尽管后续通常会切换到物理驱动器(许多基于Linux的嵌入式系统不会这样做,因为唯一的“驱动器”是无法承受实际负载的SD卡,所以系统会将“骨架”保存在内存中,并根据需要将eMMC、SD卡等存储设备挂载到文件树中)。我理解你的意思,只是不认为UNIX方式就必然比Windows方式更自然。
从多个角度看,根目录/不必属于你的驱动器之一。
只有根文件系统的根目录才是/
关键在于任何文件系统都可以被选作操作系统的根目录。
其他所有文件系统的根目录——每块驱动器可能存在多个——需要通过指定挂载点来实现,或者在自动挂载器的特殊目录(通常是/run/media)中生成唯一的序列号或设备路径。
* 说明性补充
因为你(或你的发行版)如此配置。并非必须遵循此方式。
Linux生态系统对这些设置的调整具有更高灵活性。
况且命名方案不可或缺;命名规则与存储方案是抽象分离的。
这并非意味着你的/var和/usr位于不同驱动器上——尽管在特定安装场景下确实可能如此。
上述限制均为可选项而非强制要求。而在Windows系统中,这些限制(实际上)是强制执行的。
或许某些Windows高手能绕过强制性限制,但普通Linux用户至少能规避可选限制。
流式传输作为文件访问的事实隐喻可追溯至磁带驱动器时代。尽管随机访问模式更契合当今存储介质特性,我们却仍在使用fscanf式操作。
当然存在替代方案,但资源即流的隐喻在Unix中如此普遍,难以规避。
驱动器字母不过是/mnt,即使使用GUI也能绕过。
那为何Windows默认安装仍使用并显示C:?
因为A盘保留给软盘驱动器,B盘给Zip驱动器。
不,A:和B:是软盘时代为双软驱设计的。
但抛开讽刺不谈,我的问题是:既然Windows允许完全自定义,微软为何仍将C:(或其他字母)作为首个用户分区的默认名称?向硬编码值的旧程序展示默认名称以保持兼容性无可厚非,但至少在资源管理器和微软自控软件中,应该采用更现代易读的命名方式。
A:和B:都用于软盘,双软盘系统早在Zip磁盘出现前就已普遍存在(无论是否配备硬盘),而Zip磁盘问世得太晚(1994年!)根本无法影响MS-DOS的命名标准。
B盘始终代表软盘驱动器。
Zip磁盘的驱动器号通常高于B盘(若仅有一块硬盘则为D:)。但部分(或全部?)Zip驱动器兼容传统3.5英寸软盘,此时软盘会显示为B盘。
你混淆了概念,想的是LS-120超级磁盘。某些机器上,当插入3.5英寸软盘时,它可被设置为显示为A:或B:。
Zip驱动器从不兼容3.5英寸软盘,始终使用首个可用外部存储盘符进行编号(典型机器中即为D:)。
你说得对!感谢指正。
/usr/bin 与 /bin 的区别已不重要,因为所有主流发行版多年来都已采用usrmerge方案,因此 /bin == /usr/bin(通常 /bin 是符号链接)
我喜欢能运行2000年代初期的游戏。过去人们常追求编写能长久运行的软件,但如今连Linux都放弃了'a.out'这类格式。微软可没有奢侈到能假设用户会重新编译、分叉或打补丁软件。当软件无法在最新Windows系统运行时,多数人会责怪微软而非开发者。
好吧,我更倾向于使用面向未来的软件,比如128位的ZFS文件系统。
"该文件系统本身采用128位架构,支持高达256京泽字节的存储容量。所有元数据均动态分配,因此创建时无需预分配inode或限制文件系统可扩展性。所有算法均以可扩展性为设计核心。目录最多可容纳2⁴⁸(256万亿)条目,且对文件系统数量及单个文件系统所含文件数量均无限制。"
https://docs.oracle.com/cd/E19253-01/819-5461/6n7ht6qth/inde…
可不想撞上千兆兆字节的上限啊..
> 目录最多可容纳2⁴⁸(256万亿)条目
我花了一分钟才意识到这里应为2⁴⁸,即便如此也约达281万亿。在根本未使用任何单位的情况下,出现这种关于“兆/太”二进制前缀的混淆真是怪异。
有人粗略估算过:要填满128位存储池的每个字节,所需能量足以将海洋煮沸。甲骨文官网曾有篇详细博文,可惜十年后官网链接全灭了。
等等,你是说Linux破坏了用户空间?我完全错过了这个话题,能否请教相关链接?
> > 但如今Linux已放弃'a.out'这类技术。
> 我完全错过了这个消息,希望能了解更多,冒昧请教能否提供链接?
“a.out的出路” https://lwn.net/Articles/888741/
“Linux 6.1 完成对旧版 a.out 代码的彻底清理” https://www.phoronix.com/news/Linux-6.1-Gutting-Out-a.out (附两篇早期文章链接)
谢谢!
Linux确实会清除看似无人使用的组件,而保留a.out二进制文件本就没有正当理由——毕竟…这在90年代末就该淘汰了吧?
我正在玩一些汇编代码,用nasm生成a.out文件时卡住了——搞不懂为什么加载不了。结果发现是Linux停止支持它了。他们说“没人用”指的是软件包之类的东西,根本不在乎你私下写的代码或其他使用场景。像Windows这样广泛部署的平台,他们可不敢这么想。确实存在运行数十年的商业应用程序,现实中存在持续运行20年以上的系统。
顺便提一句,利用binfmt_misc模块应该能轻松实现a.out加载器。
上方链接提及了在现代内核上运行a.out的工具:
https://lwn.net/ml/linux-kernel/202203161523.857B469@keescoo…
https://github.com/kees/kernel-tools/tree/trunk/a.out
若有人向他们反映仍需a.out格式,或许他们会重新考虑。至少在旧架构时代确实发生过类似情况。
我不喜欢在非沙盒环境下运行2000年代初期的游戏。若你持异议,那是因为我们尚未拥有真正可靠的沙盒方案。理想状态下,在现代操作系统沙盒中运行旧版软件应近乎透明——而非像在虚拟机里安装XP那样。
虽然我理解软件长寿的吸引力,也认为这是崇高而值得追求的目标,但我同样认为,让缺乏维护的软件难以在现代操作系统上运行存在被低估的好处。尤其在当下——普通消费者真正重视个人计算机安全的概念,可以说还不到二十年历史。
源自80年代?微软实际上通过名为QDOS[0]的CP/M 8086半克隆系统继承了驱动器字母机制[0],该系统成为PC-DOS及后续MS-DOS的基础。而CP/M可追溯至1974年。
但加里·基尔达尔在CP/M中引入驱动器字母的概念并非独创,他很可能受到60年代末的TOPS-10[1]和CP/CMS[2]系统的影响。
[0] https://en.wikipedia.org/wiki/86-DOS
[1] https://en.wikipedia.org/wiki/TOPS-10
[2] https://en.wikipedia.org/wiki/CP/CMS
我对Windows的命名结构并不特别欣赏,但这种命名在后期可移动介质与固定驱动器共存的系统(如光驱)中,与软盘驱动器时代同样合理。如今存储介质要么是固定介质,要么是可拆卸驱动器,而非固定驱动器中的可移动介质,这种命名或许显得不合时宜。但可移动介质普及后的历史远短于软盘驱动器普及的时期。
(主要指使用驱动器字母而非类Unix方案。C盘作为首个固定存储设备,如今看似随意,但在软盘时代同样颇具随意性。)
Windows至今仍能运行80年代的软件,向后兼容性始终是其卖点,这点值得肯定。
微软不是在Windows 10中取消了16位应用支持吗?记得当年随身携带的Jezzball可执行文件在不同机器上失效时,我曾深感沮丧。
微软确实从64位Windows系统(无论是Windows 10还是更早版本,具体取决于用户——我遇到的情况是Windows Vista)中移除了内置模拟器(NTVDM)对16位应用的支持。不过,通过第三方模拟器(如DOSBox和NTVDMx64)仍可在64位Windows系统上运行16位应用。
> 通过第三方模拟器(如DOSBox和NTVDMx64)仍可在64位Windows系统上运行16位应用。
或者用Wine,虽然稳定性稍逊但更有趣。
你是指winevdm吗?https://github.com/otya128/winevdm
据我所知Wine本身无法在Windows上运行。
> 据我所知,Wine本身无法在Windows上运行。
若使用足够旧的Windows版本(支持SUA),它确实能运行:)。虽然我始终没能让fontconfig正常工作,导致文本覆盖对话框等问题,但运行我需要的程序已足够。
Wine在WSL v1中运行还算勉强可用,而WSL v2(本质上就是虚拟机)肯定能完美运行。
确实如此,但此时你本质上是在实现“Windows-on-Linux-on-Windows”。不过反正无所谓…应用程序运行速度终究会远超其原始设计硬件平台。
真正的价值在于让Win16应用在64位Windows上运行。
不过Wine可能连这点优势都要失去…
Linux停止支持32位x86架构的时间点应该差不多吧?(仅限i386?)
你是说CPU支持吗?我前几天就在基础版Linux Mint上安装了32位程序。如果真需要运行奔腾4,我也能接受用旧内核。
这正是我的意思,真希望Linux能在架构支持上更像NetBSD。它明明是开源项目,却像企业实体般精打细算地衡量盈利性,实在令人失望。开源项目支持旧架构有个至关重要的理由:既然你承诺了,就该做到。若存在实际障碍(比如缺乏维护者——我绝不相信在众多渴望参与内核维护的开发者中,竟无人愿支持i386架构;既然NetBSD有人维护,Linux也该如此),那完全可以理解。
人们或许会预期微软因不再盈利或其他成本考量而放弃支持,但即便牺牲性能/安全优势,微软仍在维护鲜少有人使用的旧技术。
目前内核仍支持该架构。主要障碍在于某些内存映射机制,这其实是任何人都能修复的问题。
不过就个人而言,虽然我非常重视在新型硬件上运行旧版软件,但对旧硬件运行新版软件的需求仅限于特定范围——32位主流CPU已超出这个范围。
我认为终将不再支持32位软硬件。但两者仍大量存在。不该因硬件过时就淘汰优质设备,这太浪费。16位存在严重局限,但32位在无需>3GB内存的应用场景中依然适用。例如路由器除非处理高负载,否则无需采用64位处理器——芯片面积在此至关重要,这正是路由器普遍采用Arm架构的原因,也是Arm设计Thumb模式(缩减指令宽度=减小芯片面积)的初衷。当涉及数十亿设备时,因减少寄存器/指令宽度而节省的微小成本和能耗终将形成可观的累积效益。
开源领域本不该成为弃用软件的温床。
> 仅因硬件过时就淘汰优质设备实属浪费。
这取决于其能耗水平——当我们讨论二十年前的台式机/笔记本时。
> 对于无需超过3GB内存的应用和环境,32位架构依然适用。
据我所知,若内存≤1GB则无需担忧。32位架构的核心问题在于需要复杂的内存映射机制,无法将全部物理内存一次性映射到内核地址空间。目前尚未听说要彻底淘汰该架构的计划。
对于那些刚好能用32位但无法使用30位系统的设备确实令人困扰,但任何超过1GB内存的新设计只需采用略有差异的内核即可解决。
> 例如路由器除非处理大量负载,否则不应使用64位处理器——芯片尺寸在此处至关重要
我认为这种说法有误,若有疏漏请指正。基础64位内核极其精简,体积几乎与32位内核相当。若系统负载足以运行Linux,64位架构绝不会成为负担。
这确实令人印象深刻。
Linux的目标仅在于代码兼容性——考虑到其自由/开源的起源,这完全合乎逻辑。若文化氛围要求你必须获取所依赖软件的源代码,操作系统开发者为何还要做出妥协,确保你能运行几十年前编译的二进制文件?
我早期的VB6应用(大部分)在Win11上仍能运行
嗯。实际操作中VB6是个特别棘手的问题,因为MDAC(微软数据库访问组件的大杂烩)在Windows 10上都无法安装,而企业级VB6应用极可能需要它。当然,你 无法 在Windows 11原生运行80年代的应用程序,因为它已无法运行16位应用——无论是DOS还是Windows平台的。(所有32位Windows应用从定义上就不可能来自80年代,毕竟带来Win32架构的汤姆·米勒帆船之旅发生在1990年。问题并非出在缺少V86模式——Windows NT for Alpha就曾通过内置模拟器的增强版NTVDM运行DOS应用。纯粹是微软不愿继续支持这种使用场景。)
> 纯粹是微软不愿继续支持该使用场景。
NTVDM依赖虚拟8086模式,而该模式在长模式下不可用。
NTVDM需要重写。考虑到DOSBox等替代方案,我理解微软为何不愿深入这种级别的向后兼容性。
正如我在最初评论中所述,这并非全部真相。(我承认这是官方说法,但必须指出官方版本至多是巧妙地省略了部分事实。)
Windows NT(3.1至10)中存在的NTVDM(针对i386架构)利用了V86模式。另一方面,Windows NT(如4.0)上针对MIPS、PowerPC和Alpha架构的NTVDM,已内置[1] 16位x86模拟器,该功能仅通过ifdef条件编译从i386版本中移除(使后者更为精简)。
微软不愿复活这段近十年历史的代码(自Windows XP x64首次相关起)是否合理?是的。是否也合理地说,他们实际上无需从头编写完整模拟器来履行向后兼容承诺,因为他们早已完成这项工作?同样是。
[1] https://devblogs.microsoft.com/oldnewthing/20060525-04/?p=31…
ReactOS的NTVDM动态链接库可在XP-10环境下运行,还能兼容部分DOS游戏。
等等,帆船之旅的详情是什么?我搜索不到相关信息,但听起来是个精彩的故事。
是啊,我复查帖子时也惊讶于搜索结果的匮乏,但显然我的警惕性还不够——因为我搞错了。我混淆了两个关键点:第五章提到Win32规范最初由卢科夫斯基和伍德在两周内完成
> 卢科夫斯基比伍德更注重细节,但两人有诸多相似之处:惊人的专注力、快速产出大量代码的能力、厌恶冗余文档的倾向,以及近乎狂妄的自信。短短两周内,他们写就八十页论文,详述数百个Windows API在NT版本中的设计方案。
第六章提及 NTFS 规范最初由Miller与另一位同事在其帆船上两周内完成。
> Maritz决定让Miller起草NTFS规范,但保留在实际编码前终止该文件系统的权利。
> 米勒备齐笔墨、笔记本和两周补给,准备乘二十八英尺帆船展开漫长航程。他深信规格书撰写需要独处,而海洋恰能提供这份宁静。[…] 为避免孤身航行,米勒与负责文件系统的佩拉佐利协商,邀请他熟识的程序员从瑞士飞来同行。
> 八月,米勒与搭档启航航行两周。日程简单:晨间工作,边讨论边在笔记本上涂写;随后航行至某处,继续讨论并补充笔记;傍晚抛锚停泊,放松身心。
(我仍相当确信Win32规范诞生于1990年;至少《Showstopper!》一书提及该规范于当年12月17日向应用程序开发者群体展示。)
八十年代在IBM网络上运行DOS 3.1时,我曾将双软驱PC联网,通过测试突破了驱动器限制'!‘ ’@' ‘#’ '^',从而能使用26个软驱(其中24个非本地)。这些功能在3.2版本发布时全被移除了,因此我敢打赌NT网络及其NetBIOS根源也是如此。
灵感源自苏斯博士的《斑马之后》。
这完全不令人费解。他们高度重视维护向后兼容性承诺。
例如Windows 11对DOS不作向后兼容保证,但对那些承诺兼容的操作系统则会履行。
企业客户需要微软尽可能长期维护这些特性。
软件具备的惯性远超硬件——考虑到两者开发难度相当,这种差异实在令人惊叹。
他们已不再那么重视向后兼容性。
例如Windows 10若无二进制补丁就无法运行初代《孤岛危机》。
企业级软件与游戏存在本质差异。
Windows的主要盈利来源是企业市场,因此兼容性投入也集中于此。游戏领域只是附带效应。
据传闻,在Windows上运行16位游戏(如1997年的《Swing》)需修改2-3个DirectX相关文件。
过去列举的典型案例如《模拟城市》,根本算不上企业软件的代表作。
而Win11系统已停止提供32位版本,且64位操作系统不支持16位模式,这意味着任何16位游戏都无法运行。
通过正规渠道发布的软件通常兼容性良好。《孤岛危机》本就不是最稳定的游戏,据我所记得它使用了已弃用的3DNow技术——但Windows系统并未废弃该技术。
反例佐证:上周我运行《加拉帕戈斯:孟德尔的逃亡》时未启用任何兼容补丁或设置,这款1997年的3D游戏直接运行无碍。
> 通过正规渠道发布的软件通常兼容性良好。
但这标准实在太低——过去Windows曾不遗余力地维护向后兼容性,即便针对不符合规范的程序也是如此。
如果你只关心程序能否正常运行(前提是它们被“正确”编译),那么普通的Linux桌面也能做到——无论是原生Linux程序(glibc及其他少数基础系统库具有强大的向后兼容性),还是通过Wine运行的Windows程序。
理论上或许如此。实际应用中,目前至少存在一个直接影响我的案例:某款经Wine补丁修改的Windows软件,因该补丁仍能在Windows系统运行…却无法在Wine环境下工作。
3.5毫米音频接口诞生75年,其电气特性却与近150年前的标准兼容。
维多利亚时代的电传打字机只需简单适配器就能接入串口,至少足以运行CP/M及多数单案例操作系统。
此外某些编程语言支持仅导出鲍迪特字符兼容代码:http://t3x.org/nmhbasic/index.html
因此可通过纸带输入数据,甚至可能支持摩尔斯电码。
没错,扬声器技术尚未发展到让3.5毫米接口淘汰的程度。
许多新设备采用2.5毫米音频接口替代3.5毫米接口。
没错,但这并未让3.5mm接口或1/4英寸接口过时。它们只是相同功能的不同形态。
等着瞧吧,等你们听说PDP-11模拟器在运行它自身的CPU时。
是啊,试着给现在的孩子解释“C盘”为什么不是A盘或B盘……
软件开发者至今仍受限于80列标准,尽管我们已有16:9的4K显示器……这难道不是源于穿孔卡片吗???
为穿孔卡片而来,为可读性而留。
每行80字符的规范看似古怪——它源于技术限制,却实为排版专家熟稔的经验法则,早在个人电脑普及前就已存在。
还记得报纸吗?将文本排版为多栏[0]并非随意怪癖或技术限制的产物。这与优质博客布局在横向屏幕阅读时设定保守最大宽度是同理。
因为每行字符越短,整体可读性就越高。即便考虑连字符造成的可读性损失也是如此。
当然存在临界点。该临界点因媒介与内容性质而异:报纸因处理纯文本且存在其他排版考量,行长限制在50字符左右;书籍则可达80字符。鉴于程序设计并非悠闲的炉边阅读,我倾向于采用前者标准,但某些因素和惯例仍可延长合理行长。例如缩进与语法高亮效果、典型标识符长度(比如CNLabelContactRelationYoungerCousinMothersSiblingsDaughterOrFathersSistersDaughter这类命名)、编辑器对换行处理的优化能力[1]。
最后,既然实际的技术限制已不复存在,偶尔违反行长规则其实并非大问题。
[0] 相关地,大致遵循80字符行长限制的代码库,能在编辑器和多路复用器中解锁更富趣味的列式布局。
[1] 当今编辑器的自动换行功能是否已足够完善,使编写阶段限制行长变得毫无意义?并非如此,尤其对于依赖缩进的语言而言(这点值得商榷)。并非技术上不可行,但考虑到代码正日益成为只写不改的产物,短期内恐怕难以出现完美支持上下文感知自动换行的编辑器。
我确信这是个迷思。如同所有迷思,它表面看似合理,细究之下却站不住脚。
代码并非散文。代码不会始终写满行长后才换行,散文也不需要每句都换行。(别较真这个细节,你明白我的意思)
代码与散文的格式规则本就不同,人类大脑对二者的可读性感知必然存在差异。
据我所知,尚未有专门研究代码行长度优化的可读性研究。或许结果会与散文相同,但我对此存疑。我认为这取决于编程语言特性、关键词长度以及具体代码库规模——更长的关键词和方法/函数名自然需要更长的舒适行长。
行长度更关乎每行的概念密度或词汇密度,而非单纯的字符数量。
80列限制最初纯属技术规范,如今延续仅因向后兼容与传统惯例。
阅读文本时我更倾向遵循排版学原理
避免过宽行距,以免影响我的扫视眼动
运动会干扰我的沉浸感和理解力。
哎呀,手机端显示太糟糕了,多出好几行换行
我不知道如何在HN上强制换行而不产生额外空行…你知道吗?
每行80字符的规范诞生于命令行语言使用简短指令的时代。如今120字符更合理,尤其在PowerShell中。而在bash这类指令简短的环境里,80字符规范依然适用!
> 这就像优质博客布局会为横向屏幕阅读设置保守的最大宽度。
但99.9%的情况下,50字符配32pt字体在43英寸屏幕上只占横向空间的25%。
“优质”个屁。
正确解决方案是让IDE根据编辑器实际尺寸自动换行,但必须理解目标语言的语法才能实现精准换行。
80字符的行宽限制在现代显示器分辨率和超宽屏普及的今天毫无意义。
确实如此,但80列宽度也与可读性推荐的段落宽度(约50em/70字符)相当接近。我个人编写代码时通常不会超过100列。
虽然80字符显然偏短,但我的经验是行长增加反而大幅降低代码可读性。较短行长反而能迫使你精炼表达,优化语句结构。
> 软件开发者当然仍受困于80列规范
你自己说吧,我所有项目都采用至少100列甚至120列的行宽(仅软限制)。
即便没有原始的技术限制,保持行长可读仍是合理目标——不过更重要的在于精简表达本身,而非简单拆分行。
若不设任意行长限制,就更容易混入前缀大量空格的恶意代码。
代码检查和自动格式化能解决这个问题…允许任意行长度无异于自寻死路。
试着给现在的孩子解释/usr吧。
“这显然代表用户,所以是存用户主目录的地方对吧?”
“不,其实它代表Unix系统资源”
(但历史上它确实指“用户”,只是含义不同)
我敢说C盘迟早也会被重新解释成缩写词。
这根本算不上什么对话。历史惯例本就是如此。想想电子流动的方向就明白了。
> 尽管现在已有16:9的4K显示器
但正常人基本不会以100%缩放比例使用,除非你指的是用电视当显示器的那群人,否则这其实帮不上什么忙:
– 100%缩放:可容纳6个80列面板,无像素浪费
– 125%缩放:可容纳4个80列面板,浪费64像素(8列)
– 150%缩放:可容纳4个80列面板,无像素浪费
– 175%缩放:可容纳3个80列面板,浪费274像素(34列)
– 200%缩放:可容纳3个80列面板,无像素浪费
这看似合理,但当需要额外侧边面板时问题就来了。比如行号、滚动条、断点指示器,更糟的是:小地图和目录浏览器。小地图通常占20列/面板,目录浏览器约40列。滚动条与断点指示器合计占2列/面板。行号显示则基本不超过6列/面板。
当使用2个面板时,这相当于额外消耗一个面板的空间,导致3个面板中仅剩2个可用。这就是175%和200%缩放选项的宿命。那么究竟该用多少倍缩放才“合适”?
从PPI角度看:若使用32英寸显示器,150%最优;若用27英寸显示器,则175%更合适。当然,22-24英寸显示器则应采用200%缩放。不过消费者往往被“额外屏幕空间”的卖点吸引,宁愿牺牲全屏视野也要戴上眼镜。或许你更倾向于将上述比例分别降低25%。
总而言之,这些选择并非毫无道理。我个人更倾向100列的边距设置,但确实欣赏那些严格遵循80列规范的文件——它们在并排编辑时体验更佳。
将硬盘驱动器映射为A:和B:完全可行。
此方案通常适用于所有基于Win32 C API的程序。
使用.Net时却会遇到奇怪的问题,比如突然出现无效路径等。
仔细想想,现代交互式命令行界面基于虚拟电传终端的设计简直荒谬至极
试着给现在的孩子解释文件系统吧
发明驱动器盘符的人在地狱有专属位置。据我所知,这玩意儿是从CP/M系统偷来的。
蒂姆·帕特森确实抄袭了CP/M的设计,可能并不知晓更早的先例。但维基百科显示驱动器字母制度历史悠久:https://en.wikipedia.org/wiki/Drive_letter_assignment
我的意思是它之所以成为成功的商业项目,正是因为它不会破坏现有系统——至少不会频繁出错。在Windows上运行老旧软件几乎是理所当然的事,但这在多数行业根本不是常态。
至于令人费解之处,比如我每天都要输入“grep”这种怪异单词。更别提Linux遗留的各种兼容性问题了——它和Windows一样,都在竭力避免破坏用户空间软件的运行。
我曾将游戏分区挂载为驱动器的子路径,结果某些应用程序运行极不稳定。
某些应用(比如Steam)不会解析“当前路径的完整空间”(尽管GetDiskFreeSpaceExW函数完全能接受完整路径),它们会截断到驱动器字母,导致显示的是根驱动器的空间而非实际使用的目录空间——而我的情况是挂载在不同分区上。
令人费解的是,时隔59年,Unix仍固守着60年代末遗留的怪异目录命名结构——在软盘驱动器早已淘汰的今天,这种设计毫无意义。
Unix诞生于软盘驱动器之前,至少在PDP-11架构上如此。
Unix的诞生时间甚至比PDP-11本身还要早些。
ReactOS内置图形化NT对象浏览器(可能以CLSID形式存在),只需打开资源管理器窗口即可浏览整个注册表层次结构及更多内容。
该功能同样适用于Windows系统。
证明:
https://winclassic.net/thread/1852/reactos-registry-ntobject…
太棒了!
看来ReactOS连资深Windows用户都能发现惊喜!
没错。NTVDM DLL同样支持运行DOS二进制文件及其他内容。它还内置了标准纸牌游戏等应用,只需从ReactOS live CD中的CAB文件提取EXE即可直接复用。
PnP PowerShell还包含PSDrive提供程序[0],可将SharePoint Online作为驱动器浏览。这些功能不限于本地资源。
[0] https://pnp.github.io/powershell/cmdlets/Connect-PnPOnline.h…
> 例如在 Linux/Bash 中无法将证书作为文件路径访问,但在 PowerShell/Windows 中可以。
我不明白您的意思。我能“作为文件”访问它们,因为它们本质上就是文件
在任何操作系统中,你可以访问包含证书信息的文件,但无法将单个证书作为独立对象访问。你输出的内容只是列出了可能包含有效证书信息的文件。
这种区别类似于执行'ls /usr/bin/ls'与'ls /proc/12345/…'的差异:前者是字面意义上的文件列表,后者则是访问/操作ls进程(假设进程ID为12345)的方式。在 Windows 中,证书不仅是文件,更是经过解析/处理/验证的特定用途对象。Linux 同样如此,但需依赖 openssl、gnutls 等工具来解析这些信息。若 openssl/gnutls 能为系统中的证书(及 GPG!!)提供虚拟文件系统挂载视图,其功能将类似于 PowerShell 中的 cert:。
Linux 缺乏许多其他操作系统具备的 API,证书管理便是其中之一。
在Linux中,若要实现类似通过Windows虚拟文件系统列出证书的功能,可尝试列出/proc/self/tls/certificates目录(当然该目录实际并不存在,因为Linux认为此类配置应由用户自行处理,而非操作系统API提供)。
请注意
ls命令的用法:现在请用更简洁的方式实现相同功能,无需依赖grep、python和正则表达式切割文本片段的复杂组合。
我始终欣赏Linux爱好者们热衷讨论的姿态——他们既缺乏实践经验,也不愿主动获取经验。
这很棒!但我难以理解的是,文件系统的其他概念如何映射到这些提供者上,尤其是别名、环境、函数或变量?比如创建项、删除项、复制项、查看内容及权限、大小、可见性等属性?
具体到证书提供者:提及证书层次结构时,我联想到的是签发证书的签名层级。但此处暴露的仅是操作系统证书存储库的结构,缺乏上下文关联。且移动证书项的操作远比普通数据文件夹更具深层含义。因此我更倾向于使用certlm/certmgr.msc,它们提供了更丰富的功能。
有时感觉他们把太多东西塞进这个概念里,显得生硬。https://superuser.com/q/1065812/what-is-psprovider-in-powers…
我_怀疑_他们指的是Windows中导入MMC的证书可通过特殊路径访问,但…Linux也能做到,因为它省去了创建证书魔法存储区的步骤。
Linux同样存在魔法存储区,但具体实现细节由OpenSSL等TLS库在运行时决定,且对客户端隐藏。仅证书管理就有无数种方式:GNU TLS可能不使用OpenSSL的路径,每个发行版对证书存储位置也有各自的方案。理想的Unix式方案(Windows/PowerShell已实现)是挂载证书专用虚拟卷,让用户和客户端应用都能查看/操作证书信息。若你曾尝试在不同Linux发行版/部署环境中配置内部证书,想必深谙此道(虽属小问题,我承认)。
虽然并非专为证书设计(据我所知),但Plan9及其衍生系统极力将所有内容抽象为虚拟文件系统(VFS)。当然/proc、/sys等目录非常出色,但仍有部分内容需要专属文件系统视图,却只能被归类为普通文件,例如
/.cache、/.config及所有xdg标准目录。我理解这是标准化路径的体现,但这里抽象的本质并非“文件中的数据”,而是更具体的“缓存”与“配置”。(更具体而言),它们仍应存在于VFS路径中,但不应以文件形式暴露,而应作为“配置设置”或“缓存条目”的抽象层,其底层可由任意技术实现(如Redis、SQLite、S3等)。Windows注册表(其正式名称为配置管理器)在抽象配置方面表现出色,但显然你无法像在Linux中那样自由选择后端实现方案。> Windows注册表(其正式名称为配置管理器)在抽象配置方面表现出色,但显然无法像在Linux中那样自由选择后端实现方案。
理论上,dbus正是通过API而非任意路径-键-值三元组来实现这一功能。你可以运行任意密钥管理器,只要它能正确响应DBUS API调用,调用应用程序就无法知晓具体由谁在管理你的密钥。音频、显示配置和蓝牙API亦是如此,不过某些组件带有“品牌标识”,因此并非完全可互换——它们可能随时发生变化。
Gnome的dconf系统与Windows注册表颇为相似,且由于支持在键值中直接添加文档说明,实际配置系统时会方便许多。
不,他的意思是像虚拟伪文件系统那样访问——比如/proc、/sys等目录
> 例如在linux/bash中无法像访问文件路径那样访问证书
Fuse和p9都存在…如果有人需要在文件系统中按ID获取证书,这功能终将实现。
> 例如在 Linux/Bash 中无法通过文件路径访问证书,但在 PowerShell/Windows 中可以。
当然可以,/usr/share/ca-certificates 就是路径,不过在 Debian 衍生系统中需要运行 ‘update-ca-certificates’ 更新某些文件,比如 /etc/ssl/certs 中的哈希符号链接。
当然系统文件也可通过/sys|/proc访问,但确实远不及Windows注册表集成度高
Windows访问分区也不限于驱动器字母,这只是现有约定。
你完全可以像在Linux/Unix中那样将分区挂载到目录下。
PowerShell 的 Add-PartitionAccessPath 命令可实现此功能:
> mkdir C:Disk
> Add-PartitionAccessPath -DiskNumber 1 -PartitionNumber 2 -AccessPath “C:Disk”
> ls C:Disk
该路径设置可跨重启生效。
我曾多次用此方法将游戏安装到可移动介质。安装程序不喜欢将SD卡设为安装目标,但若C:GamesWhatever实际是NTFS挂载点(在拔出存储卡后立即消失),它们则不会介意。不过此技巧的缺点是会混淆那些检查可用空间的安装程序。
对于永久挂载的驱动器,我更推荐符号链接而非挂载点,这样能更轻松地进行驱动器级别的文件系统维护。你仍可将所有内容保留在C:目录下,将其视为Unix系统中的特殊根目录,但当需要对备份硬盘进行碎片整理时,就无需费力调试分区管理器来显示挂载路径的整理按钮了。
我用此方法将Steam游戏存入内存盘以加速加载。
其实无需PowerShell,磁盘管理工具早就能实现:右键分区→更改驱动器号和路径→添加→挂载到以下空NCTS文件夹。
NTFS挂载点能巧妙解决软件路径自定义限制问题。我可像在*nix系统中那样,选择不同性能或复制策略的虚拟机磁盘进行组合。这种方法非常实用,仅在极少数情况下遇到应用程序“察觉”并拒绝配合的情况。
符号链接在NTFS上同样有效,但挂载点具有优势——其规范路径不会被意外解析并持久化。
需注意仅限NTFS(源目标均需NTFS),不支持文件夹挂载下的exFAT共享驱动器等场景。据我所知ReFS似乎也存在相同限制。
在图形化工具创建/格式化分区时,系统会询问是否分配驱动器号或挂载为路径。
我刚尝试将exFAT分区挂载到“C:exFAT”,完全正常。
反过来试试。尝试将exFAT驱动器挂载到E:盘。
这是因为某些文件系统(如NTFS)会暴露必要的元数据以实现集成,而另一些则不会。FAT和exFAT就属于后者。
RAW分区可挂载到挂载点(或驱动器号)。
以前在SQL Server 2000中还能这样用…
许多程序(据我上次检查,Steam就是如此)会检查父磁盘的可用空间,若空间不足(即使目标目录空间充足)可能拒绝安装。
确实如此。若仅有一个驱动器盘符,该驱动器始终处于活动状态,因此可直接使用反斜杠开头的路径:如WindowsSystem32等。
什么?恕我直言, 搞什么鬼 ?我完全不知道还能这么操作。感谢分享!
常规界面其实也支持这个功能,打开“计算机管理”进入磁盘管理模块,Windows世界里许多关于驱动器的“魔法”操作,本质上就是界面开关而已
当年Windows 2000刚问世时,我常把“Program Files”文件夹放在另一块硬盘上。这样启动程序的速度也更快了,因为系统既从操作系统盘加载,又从程序安装盘加载。
“€:”这段路径的诅咒感简直绝妙。NT内核的灵活性远超用户可见层面的设计,实在令人惊叹。
没错,Windows NT广为人知的只是其DOS界面。这层表象之下,潜藏着不少1980年代末的狂野设计理念。核心要点在于:许多功能基于GUID到各类操作的反向映射实现,而这类映射条目的解析机制贯穿整个用户界面。正因如此,你能在桌面创建名为{hexspew}的快捷方式,它竟能神奇地成为某些系统功能的深度链接——这些功能通常不允许创建快捷方式。同样道理,控制面板里那些看似非官方功能的项目也能随意添加。这些操作在DLL内部被定义为命名符号,因此能执行操作系统支持的任何功能。这也解释了为何Windows始终是恶意软件的温床。
这些GUID并非与NT内核相关,而是属于Windows资源管理器及其基于COM的组件系统。据我所知,它们始于Windows 95时代。
>因此它们能执行操作系统所能实现的任何操作
没错,多年来总有人想到某个功能就直接实现,却未系统性考量这种权限意味着什么——尤其当多用户网络连接和不可信数据成为常态时。
这些问题在编写《NT OS/2设计手册》时根本未被考虑。
听起来很有趣。有链接或示例“十六进制吐槽”吗?
上帝模式快捷方式:https://en.wikipedia.org/wiki/Windows_Master_Control_Panel_s…
除非能用笑脸表情符号当驱动器盘符,否则这玩意儿不够灵活。
简直诅咒般的设计,某些代码页下驱动器盘符甚至无法访问。
据我所知,驱动器仍可访问,只需用其他代码页中与€字符对应的字符作为驱动器号。
只要代码页没有缺失,这应该可行。不过肯定会把不懂这套设置的人搞得一头雾水!
我认为原理并非如此,实际驱动器字母是UTF-16 Unicode路径。应用程序必须能提供编码为该UTF-16值的“ANSI”字符串,才能通过“ANSI”函数打开文件。这不同于8位系统只需相同8位值的情况。
> 非A-Z驱动器的磁盘不会显示在文件资源管理器中,也无法通过文件资源管理器访问。
唉,我把所有驱动器标识符换成表情符号的计划泡汤了 🙁
即便尝试替换,实际可用表情符号也相当有限:多数表情超出基本多文种平面(BMP)范围,无法容纳于单个UTF-16码单元;而剩余部分则由普通字符加表情样式选择符(U+FE0F)组成,同样无法适配。
每当我碰上别人没锁屏的电脑,第一件事就是检查标记为茄子和桃子的驱动器。
只要代码页设置正确,你应该能找到几个笑脸符号。
对于其他情况,我能给的最佳建议是:在驱动器根目录放置自定义自动运行配置文件,将驱动器图标指向其他资源。虽然路径显示仍会很单调,但图形界面将遍布表情符号——尤其当驱动器标签也使用表情符号时。
但你的计算机名称可以使用表情符号。
> 换言之,由于 RtlDosPathNameToNtPathName_U 会将 C:foo 转换为 ??C:foo,因此名为 C: 的对象将表现得像驱动器字母。举例说明:在另一个平行宇宙中,RtlDosPathNameToNtPathName_U 可能将路径 FOO:bar 转换为 ??FOO:bar,此时 FOO: 就能像驱动器字母那样工作。
不知为何我记得初代Xbox 360曾有过“驱动器字母”,它们是完整的字符串。可惜我已无法查阅开发文档,现在怀疑这是否纯属我的记忆虚构。记得类似“Game:foo”和“Hdd0:foo”这样的形式。
你的记忆没错 🙂 这种设计确实存在过。
Xenia模拟器通过虚拟文件系统中的符号链接实现该功能:https://github.com/xenia-canary/xenia-canary/blob/70e44ab6ec…
摘自文章:
> 驱动器号非 A-Z 的磁盘不会在文件资源管理器中显示,也无法通过文件资源管理器访问。
这让我想起 Win9x 系统上老派的 ALT + 255 技巧——添加这个“非法尾随字符”后,普通文件资源管理器就无法访问该目录了。
嘘……当年就是这么把《毁灭公爵》安装包藏在宿舍机房电脑里的。
直到最近,你还能通过修改Windows注册表实现同样效果——让常规工具(如Regedit)无法查看/修改特定条目。据我所知,这个问题在过去五~年间依然存在。
如今情况更糟了https://borncity.com/win/2023/03/11/windows-10-11-mock-folde…
供感兴趣者参考:Linux系统存在类似机制,称为抽象域套接字(Abstract Domain Sockets)。这类Unix域套接字的首字符为NUL字符(‘ ’)
我正在开发一款游戏,每位玩家在Linux计算机上拥有系统资源。核心设计理念是:某些资源(如文件)需要共享或保护,但游戏客户端的核心通信机制必须保持独立,避免干扰真实系统环境。
采用抽象数据套接字能绕过Linux系统多数权限限制。只要掌握套接字的魔术数字,即可获得访问权限。
> 若掌握魔术数字
或在/proc/net/unix目录中定位
正确。这样做能让你在进程试图访问该套接字时实现自定义凭证验证机制,例如SO_PEERCRED。
这听起来简直是编写恶意软件的绝佳方式。我预见很快就会出现隐藏挂载在SQL转义恶意命名驱动器上的案例…
我理解你的观点,但难以想象这如何被武器化。请注意,这些兼容DOS的驱动器字母必须映射到真实的NT路径端点(如驱动器/卷),因此难以想象恶意软件既能构建难以扫描的DOS树结构,又能在其他位置避免暴露该区域供简单扫描。
我敢打赌,市面上肯定存在某些编写拙劣的杀毒软件,它们会在遇到非标准驱动器号时崩溃,至少能制造些许混乱。
虽不确定是否原生支持,但恶意软件可将磁盘映像解密至内存并创建挂载到+的内存盘。或通过用户空间驱动程序模拟环设备,使驱动器扇区仅在运行时解密。
此举将破坏大量分析工具,并显著增加检测难度。
若存在恢复分区或许可行。
> 这听起来简直是编写令人抓狂的恶意软件的绝佳方案。
据我所知,在Windows中操作磁盘需要管理员权限。
等你了解替代数据流机制就知道了…
CS上这篇技术文档详细描述了该规避方法:
https://www.crowdstrike.com/en-us/blog/anatomy-of-alpha-spid…
在运行Macintosh服务时它们曾有其用途。
它们至今仍被积极用于标记“网络标记”,以提示文件来自不可信区域需谨慎处理。据我所知macOS也会应用类似元数据。
虽然还有其他使用场景,但网络标记是我发现最普遍的应用。多数杀毒软件会对异常的替代数据流发出警告,无论其内容为何。
macOS 使用扩展属性(可通过 xattr 操作)。
替代数据流最初是为支持 HFS 资源分叉而设计的。
> 驱动器字母本质上只是将 Win32 路径转换为 NT 路径时形成的约定
CMD还存在“当前驱动器”及“驱动器当前目录”的概念(“X:”指向驱动器X的根目录,而“X:”指向驱动器X的当前目录。当前目录即“.”代表当前驱动器的根目录)。这些机制如何与非标准驱动器字母兼容值得探究。
它们完全正常运作,因为驱动器专属的当前工作目录会被存储在环境中,以通常隐藏的
=<驱动器字母>:环境变量形式存在,该变量与驱动器字母具有相同的WTF-16编码和大小写不敏感特性:嗯,直接使用
%会怎样?这只会与shell交互,因为
%实际上并非环境变量名称的一部分,它只是告诉shell需要获取环境变量值的一种方式。环境块本身是由NULL字符终止的WTF-16字符串列表,格式为<键>=<值>,因此尝试=会更有趣。事实上,将
=用作驱动器字母会以有趣的方式破坏系统:cd以错误代码1退出,但目录切换仍会生效。通过程序转储环境块中以NULL结尾的
<键>=<值>行可发现,环境确实被修改了,但方式出人意料:执行
cd /D =:\前,环境变量包含如下内容(即C:的当前工作目录为C:foo):执行后意外变为:
有趣的是,该行实际将C盘工作目录设为
=:\,且该设置生效:—
您可能还想了解:环境变量名称中出现'='是更普遍的特殊情况,其处理方式不仅限于Windows系统存在不一致性:https://github.com/ziglang/zig/issues/23331
任何处理过多盘阵列文件的人都深知驱动器字母会变得多么诡异。当有人觉得把单个压缩包拆分成7.99GB分段是个好主意,导致你不得不挂载三十六张8.5GB DVD的ISO镜像时,命令行操作就会变得极其棘手。若你未养成用多层引号隔离操作的习惯,很快就会养成——正如文中“+”符号示例所示,某些操作符恰与驱动器字母符号相同。
我记得当年A和B还是常见驱动器字母时,C已是奢侈品,D则纯属资产阶级象征。
但不知为何,以C开头的驱动器字母也显得完全自然。或许因为C恰是大众最熟悉音阶中的第一个音符。开头浪费两个驱动器字母,我们完全负担得起,对吧?
> 我记得当年A和B还是常用驱动器字母。C是奢侈品。D简直是资产阶级专属。
我们家第一台家用电脑(1989或1990年?)是台386SX,配40MB硬盘(看来我们家也算资产阶级了)。父亲不得不将其分区为32MB的C盘和8MB的D盘,因为当时的DOS版本(3.3?)文件系统最大容量仅32MB。它配备两台独立的5.25英寸软驱:一台1.2MB容量,一台360KB容量——虽然1.2MB软驱能读取360KB磁盘,却无法写入360KB软驱可识别的格式,大概是这样。后来(约1991年)我们又添置了3.5英寸软驱,成为驱动器A,1.2MB驱动器改为B盘,360KB则降级为E盘。随电脑附带的FDC(当时还是独立的ISA卡,尚未集成到主板)仅支持两台驱动器,因此他不得不另购支持四台驱动器的FDC。
啊,年轻气盛的你真可爱。按惯例,A和B盘专用于软驱,C盘通常是第一块硬盘。
在单软驱系统中,A:和B:实为映射同一物理驱动器的逻辑分区。这使得用户能(虽然繁琐)在两张软盘间复制文件。
我不记得这种情况,但确实记得用过类似“diskcopy A: A:”的命令实现该操作。
https://en.wikipedia.org/wiki/Drive_letter_assignment#Order_…中明确提及了“幽灵驱动器B”的概念。
该链接来源经核实属实。若在diskcopy命令中设置source=dest参数,同样能实现此功能。
硬盘曾是奢侈品。
虽然初代IBM PC确实可能未配备硬盘,但早在1983年它就成为PC XT的标准配置。直至1980年代末,仅最低配版本才不带硬盘。
许多兼容机确实未配备硬盘。
这完全可以理解。
我1988年接触个人电脑时,所有机器都配有硬盘,且绝对不是“IBM PC”原版而是兼容机。当然这只是我的个人经历,具体情况可能因人而异。
我1986年底购入的首台PC是Leading Edge Model D,配备两台360K软驱且没有硬盘。我编写了一个脚本,让系统启动时将COMMAND.COM等关键文件加载到RAM磁盘,这样就不必时刻把DOS软盘插在A盘了。记得当时他们推出过带20MB硬盘的型号,但那超出了我的预算。
当时我在麻省理工学院读书,学校配有几台装有10MB硬盘的IBM PC XT电脑,但主要计算资源是分时制的DEC VAX机。你可以去几个计算机实验室使用终端,甚至可以远程拨号登录——我就是用2400波特调制解调器从个人电脑(上文提到的那台)拨号登录的,那在当时算是很快的速度了。
这让我想起高中一年级时发生的一件蠢事,大约是1992年。
当时我们上的是愚蠢的“计算机基础”课,教室里摆满没有硬盘的PS/2 Model 25电脑。每人领到一张可启动软盘,里面存着Microsoft Works软件和作业文件(文字处理文档、电子表格等),下课时必须交回软盘批改。
我们按常规方式启动Works——在MS-DOS提示符下输入“works”。
某天无聊时,我在磁盘的AUTOEXEC.BAT文件里添加了“PROMPT Password:”指令,导致从我的磁盘启动时,DOS提示符从“A:>”变成了“Password:”。
两天后,我被叫到教务长办公室。教师厉声质问我如何利用磁盘“入侵网络”——此前我甚至不知道实验室电脑除了电源外并未联网——以及“锁死我的电脑”,并威胁若不交出密码就给我停学处分。
我试图向这位“计算机素养”讲师解释根本不存在密码,但对方显然连AUTOEXEC.BAT和DOS提示符是什么都搞不清楚,更不懂为何用可能不可信的软盘启动联网电脑是糟糕主意。几分钟争辩无果后,我终于妥协。
“行吧。密码是 works 。现在能走了吗?”
那些10MB全高MFM硬盘慢得离谱…你真能按下电源…去给自己倒杯饮料,喝完第一杯,再续第二杯,系统刚好在启动完成时跳出DOS提示符。
讽刺的是,双倍间距/堆叠硬盘反而更快。
请记住,很多情况取决于地域差异。
在俄罗斯,我们学校的教室里摆满没有硬盘的IBM PC——只能不停调换软盘——那还是90年代初。而且还是所名校。
到80年代末,时代已然更迭,人们购买的是AT机而非XT机。
“家里用D盘” 🙂
c:> subst d: c:
D盘通常是CD-ROM驱动器。那么当CD-ROM走向消亡时,D盘去哪儿了?如今它是否永远成了某种系统盘?
其实就是随机分配的?正因如此D盘才常被用作CD-ROM:A盘是首块软驱,B盘是(通常缺失的)第二软驱,C盘是唯一硬盘,D盘则是下一个可用盘符。
我笔记本的D盘是SD卡槽,台式机则是第二块SSD。
可刻录CD刚问世时,我们公司搭建了双硬盘工作站(C:和D:)加CD刻录机(E:)。刻录软件默认硬编码为D:驱动器,却在任何地方(包括错误提示)都未说明。我们花了数小时才搞明白。
“这就是为什么D盘通常被设为CD-ROM驱动器:”
我们曾将CD-ROM驱动器固定为L盘。这样便始终保留了添加硬盘的“空间”,避免字母序列出现断层。D盘用于数据存储,E盘存放交换文件,依此类推。
测试盘和外置盘(临时性设备)则分配在L之后的盘位。严格遵循这种命名规则能避免诸如将空盘克隆到存有数据的盘上这类失误(克隆操作当时非常频繁)。
顺带一提,这条规则适用于所有机器:带硬盘的笔记本电脑C盘为系统盘,L盘为CD-ROM;配备多台CD-ROM的机器则依次分配L、 M等序列递增。
我始终使用J盘(当时未预料到需要添加如此多硬盘)。
主要目的是避免CD安装时丢失安装驱动器——毕竟Windows会通过绝对路径追踪安装路径。如今所有软件均通过下载安装,且Windows会自动将安装介质复制到硬盘,此问题已不那么关键。
没错,具体为何选L盘记不清了。当时大概是考虑最坏情况后,又多预留了些空间。:-)
C盘之后确实按顺序分配。
在CD/DVD驱动器、刻录机、Zip驱动器和额外硬盘的配置下,工作站自然分配到G:或H:并不罕见——这还是在映射网络存储普及之前的时代。
> A代表第一软驱,B代表(通常缺失的)第二软驱
正如另一位评论者所言,当电脑仅配备一个软驱时,A:和B:分别映射至同一软驱内的两张软盘。当系统需要另一张软盘时,DOS会暂停运行并提示用户插入。这解释了为何即使在单软驱电脑上,硬盘也标记为C:而非B:(由于大量软件最终都默认如此,即使在没有软驱的电脑上,该惯例仍得以延续)。
如今D盘通常指第二块内部存储设备,可能是第二块SSD、大容量HDD,或是系统盘的额外分区。若无上述设备,USB闪存盘可能临时获得D盘标识。
C盘是搭载DoubleSpace驱动器的启动分区,D盘则是压缩卷。
Stacker压缩卷 😉
肯定是DriveSpace
在服务器环境中,D盘通常用于存储数据/供应商安装文件/其他需要与操作系统分离备份的内容,避免占用主操作系统盘C盘空间。
取决于配置。如今我将D盘用于共享数据,连接从不使用的Linux系统。在9x/XP时代,D盘曾存放用户数据(重装Windows时保护数据安全),而光驱标记为E盘。
我还启用了驱动器字母分配功能,因此外接USB设备始终标记为X盘。
> 路径 ??C:foo 中的 ?? 部分,实际上是对象管理器内的特殊虚拟文件夹,它整合了 GLOBAL?? 文件夹与每个用户的 DosDevices 文件夹。
哦,原来终端服务器能为每个用户会话挂载不同网络共享(例如用户主目录始终是H:)并保持相同驱动器号的原理在此。
我最早接触的DOS系统里,Z之后的驱动器号是AA。当时我创建了一系列小型RAM驱动器来验证。
那可能是DOS 3.3版本,并非后续版本。不清楚具体何时变更的。
记得Netware系统曾有种驱动器映射机制,驱动器号远超z:
嗯。这似乎可能被恶意软件以相当滑稽的方式滥用(当然,视角度而定)…
若攻击我电脑的恶意软件还从茄子表情驱动器运行,我就要改信阿米什教派了。
在Linux挂载茄子驱动器的分区时,无人侧目
在Windows这么做时,众人却集体失控。
这篇关于驱动器字母在Windows注册表存储方式的参考很有意思:http://www.goodells.net/multiboot/partsigs.shtml
我从未尝试过,但好奇是否能通过直接编辑注册表创建极其奇特的驱动器字母。
> 非ASCII驱动器字母甚至像A-Z那样不区分大小写
好奇的是,在土耳其语环境下执行
subst I: .会生成i:还是ı:?在cygwin.dll的Cygnal分支中,我修改了Cygwin的POSIX chdir()函数及路径解析机制,以支持按驱动器字母命名当前目录的概念。
路径如“f:myfile.txt”实际表示“f:路径到任意目录myfile.txt”,其中路径到任意目录即f盘的当前工作目录。
正是这类细节使替代DLL更接近“原生”运行时库,基于它的应用程序对Windows用户而言行为更符合预期。
https://www.kylheku.com/cygnal/
怀念Amiga系统的'assign'功能。
26个驱动器位应该足够任何人使用。
好奇驱动器盘符中禁止使用的字符有哪些?是否与文件/目录名称禁用字符集相同?
关键问题在于Windows Defender能否扫描这些驱动器?
不清楚它默认在后台扫描什么,但可以自定义扫描完全没有可见挂载点的挂载卷, 例如 我EFI分区里存放的EICAR测试文件[1]:
[1] https://www.eicar.org/download-anti-malware-testfile/
类似的边界案例正是安全漏洞的根源。
若有人将此行为作为未来CVE或严重性评级的赌注发布在市场,能否在此添加赌注链接?
这个话题很适合在《旧事新说》专栏发表。
这篇文章很酷。今天学到了新知识。
如果挂载0x0000会怎样?
深渊之中,黑暗之主正在苏醒
比尔·盖茨将接到电话
这似乎是隐藏恶意软件有效载荷文件的绝佳方式
太神奇了,简直是等待被利用的安全漏洞
想要最大乐趣?按Win+R打开eudcedit
今天就画出你的驱动器盘符吧!
我从未知道Λ是大写版本的λ。
Windows驱动器字母分配荒谬至极。例如视频编辑时使用外置硬盘,其字母可能被其他驱动器抢占,导致工作彻底中断。
挂载时不会发生这种情况。这就像抱怨在Linux系统中,拔掉一个U盘再插入另一个时,第二个U盘会“抢占”/mnt/sdb1之类的挂载点。
人们确实抱怨过这个问题,因此如今Linux系统挂载时会改用磁盘UUID或标签。
问题已解决。Windows的借口是什么?:-)
Windows同样支持UUID。例如:
这完全可以像Linux那样映射到目录实现别名。
Windows NT与UNIX的相似度远超多数人认知;Windows NT只是在底层堆砌了大量DOS/Win9x兼容层,掩盖了其内核设计的卓越性。
我认为这篇文章很好地阐释了这一点。
归根结底,若仔细思考,Win32子系统在NT操作系统上运行的原理,与Wine在Unix系统上运行的概念本质相同。这正是Wine并非模拟器的原因,同样地,XP运行Win9x二进制文件时也并非模拟旧版Win32环境。
没错,NTFS功能相当强大。主要问题在于Windows界面过于简化,未能充分展示其能力。
从这个角度看Linux存在缺陷。开机前插入USB设备会导致启动失败。
> [ .. ] 开机前插入USB设备会导致启动失败。
仅当机器的BIOS配置为将可启动USB设备设为启动顺序优先级时才会发生。所以这与Linux无关——事实上,在Windows机器上也会出现同样的情况。
请记住,在正确配置的Linux安装中,启动分区是通过UUID而非硬件标识符(在/etc/fstab中)来识别的。因此,即使你改变了驱动器的硬件连接点,系统仍然能够启动。
除非存在损坏的内核命令行参数或fstab配置文件,其中仍引用/dev/sd*而非使用UUID=xyz或/dev/disk/by-id/xyz语法。
> 仅当您使用 旧式 内核命令行参数或fstab文件时才会出现此问题——即引用/dev/sd*而非UUID=xyz或/dev/disk/by-id/xyz语法。
已为您修正。过去使用设备路径(/dev/hd* 或 /dev/sd*)引用文件系统分区曾是常态。而采用UUID或by-id符号链接的方案实属创新,其引入正是为解决设备枚举顺序问题。
是啊…在 遥远的过去 确实存在问题
在我这儿完全正常。技术问题。
“在我机器上运行正常”这种回复通常毫无帮助。再用“技术问题”这种侮辱性言论强硬辩护,不仅无益反而显得粗鲁。
另外两位用户能简明扼要说明问题,而非态度傲慢又刻薄。
我清楚记得有位用户在Windows系统中无法访问SMB驱动器,原因是他打印机和电脑机箱都自带这种带n个插槽的多合一读卡器,导致驱动器号冲突。那次我才明白,SMB驱动器字母甚至不属于“全局”驱动器字母池——事后看来显而易见,它们属于用户专属资源(涉及凭证等因素)。
我认为驱动器字母的概念本身存在缺陷。
微软似乎也认同你的观点,毕竟驱动器字母本质是符号链接。这纯属历史遗留产物,目前既无计划也无合理方案能彻底废除它。
1981年个人电脑时代驱动器字母尚有意义。但IT部门管理的网络早已脱离个人范畴——这本就是定义使然。
我始终建议用户通过DFS与完全限定域名路径访问。我们曾在用户桌面添加快捷方式,指向其DFS命名空间中的个人主目录。
若驱动器映射引发问题,可随时调整分配方案;若更便捷,也可直接使用目录作为挂载点。(Win-R, diskmgmt.msc)
若采用默认设置,驱动器字母可能被重置。但若手动为外置驱动器定义字母,该设置将永久生效。(我的外置驱动器设为X盘。不确定若同时连接19个驱动器Windows是否会保留此分配,不过这种情况不会发生。)
仅当实际分配的“驱动器号”为特殊值“auto”时才可能发生。
否则驱动器号将被静态分配,不会被其他卷使用。
除非你毫无好奇心,连用谷歌搜索简单解决方案都做不到——比如用磁盘管理程序分配新驱动器号——否则你永远不会失业。
现在肯定有人会用这个来隐藏恶意软件,不知怎么的…
但愿这篇文章能被收录进计算机历史档案,让后人读到:当今主流操作系统如何固执地要求其受害者——呃,用户——遵守早已失去合理性的陈规陋习,而自由替代系统却没有这种缺陷。
我经常和邻居(终端用户)讨论这个——当他因插入Windows U盘的顺序错误,又把备份覆盖到原始文件时,我解释原因。他的反应总是类似:“电脑还这么落后吗?”我回答:“不,是Windows还这么落后。”
好消息是Linux更成熟。坏消息是Linux用户也必须具备更高阶的操作能力。但这种状况终将改变。
源自Unix的Linux /dev设备路径真的更优越吗?细想之下这设计颇为奇特。“万物皆文件”的理念下,唯特定事物能成为文件,且按惯例仅出现在/dev目录下。Plan 9将“万物皆文件”理念贯彻到底,其设计远胜于此。
编辑补充:Linux中/dev/sdX路径 并非 稳定。自Linux 5.6起,这些路径在每次启动时都可能发生变化。
> Linux的/dev设备路径(源自Unix)真的更优越吗?
完全不优越,正因如此Linux才采用分区UUID来标识特定存储分区,而非依赖硬件标识符。这并非自动实现,需用户手动配置——这也解释了为何Linux用户需掌握比Windows用户更深的知识(以及Linux普及受阻的原因)。
> 编辑补充:Linux中/dev/sdX路径同样不稳定。自Linux 5.6起,这些路径在每次启动时都可能发生变化。
确实如此,这也是采用分区UUID的另一理由。
> Plan 9将“万物皆文件”理念贯彻到底,设计远胜于此。
遗憾的是Plan 9未能获得广泛采用——或许是过于超前于时代吧。
我始终认为这是两种截然不同的数据存储思维模式。
一种是“介质中心”理念。你可能希望路径始终相对特定软盘保持一致,无论它位于哪个驱动器;或相对特定希捷Barracuda硬盘保持一致,无论它连接在哪个SATA接口。
相反,“插槽中心”的思考方式或许更合理。左侧软驱永远是A盘,无论其中装载何种介质。第三个SATA接口始终是/dev/sdc,无论连接多少硬盘或连接顺序如何。
只要保持一致性,两种方式都可行。但我的次要SSD时而切换为/dev/nvme0时而变为/dev/nvme1,实在令人困扰。
> 一种思路是“介质中心化”。你可能希望路径始终相对特定软盘保持一致,无论它位于哪个驱动器;或相对特定希捷巴拉库达硬盘保持一致,无论它连接在哪个SATA接口。
> 相反,采用“插槽中心化”的思考方式或许更合理。左侧软驱始终是驱动器A,无论其内容如何。第三个SATA接口始终是/dev/sdc,无论连接多少设备或顺序如何。
第三种方案——我认为这才是多数用户真正需要的——是“控制器中心化”视角,但需注意:当今多数“可移除介质”都自带控制器。左侧软驱无论插入何物始终是驱动器A,顶部CD-ROM驱动器无论内容如何始终是驱动器D,但存放所有色情内容的希捷扩展USB移动硬盘则始终是驱动器X——无论插入哪个USB端口,因为其控制器与存储介质共存于同一便携塑料外壳中。SCSI、SATA甚至老式IDE硬盘亦是如此;唯有在IDE驱动器问世前,才能找到控制器与介质分离的设备。磁带、CD/DVD/BD及软盘的控制器始终独立于介质存在。
AmigaOS同时支持两种模式。每个驱动器及每种介质都有独立名称。若GAMEDISK位于软盘0号位,既可通过DF0:也可通过GAMEDISK:访问。
甚至可引用未加载的介质(如GAMEDISK2:),系统会提示将介质插入任意驱动器。还存在“虚拟”设备(分配设备),可指向特定设备上的特定目录,例如LIBRARIES:
可悲的是,直接位于
/dev目录下的设备既不具备上述特性,也无法保证确定性——它们遵循“先到先得”的顺序,本质上是不可预测的垃圾机制。若真需要“插槽中心化”连接,理应使用udev管理的/dev/disk/by-path子树结构。Windows驱动器号也与某些分区UUID相关联,因此您可以将分区移动到其他驱动器,或将驱动器移至不同位置(更换SATA/M.2接口)。
可使用mountvol命令查看驱动器号与GUID的映射关系。
这个问题(或多或少)之前讨论过!
https://news.ycombinator.com/item?id=17652502
VMS系统设计为在具有单一驱动器系统的机器集群上运行。具体实现机制对用户是“隐藏”的,用户所见的是可堆叠的“逻辑分区”,用户/进程可对其进行操作而不影响底层文件系统。这在经验不足者手中可能导致 混乱 局面。但这正是NT系统的起源。
完全正确,都是关键点。终有一天,分区及其唯一UUID将成为唯一有效的标识符。届时必须提醒终端用户:切勿复制包含(已失效UUID)的完整分区。听起来荒谬,但我确实经历过这样的讨论。
或许你该教邻居如何为驱动器分配盘符,确保相同内容始终映射到相同字母。毕竟系统支持这种操作。
不过话说回来,Linux系统默认会分配/media/usb0、/media/usb1等路径,这同样存在相同问题。解决方案也如出一辙——若需稳定路径,请手动挂载(Windows用户只需点击几下鼠标即可实现)。
> 反观Linux系统,默认会分配/media/usb0、/media/usb1等路径,存在完全相同的问题。
Linux可利用USB设备的UUID避免混淆,且用户普遍掌握此操作。Windows虽有类似方案,但用户往往不知晓。
> …(但在Windows上只需点击几下鼠标即可完成)
没错,这些操作超出了普通Windows用户的技能范围。这更多关乎技术知识而非操作系统选择——总体而言,Linux奖励知识,而Windows惩罚知识。