我会把你的问题分解成更小的子集。
我在GCC编译了一个简单的“Hello World”程序,并在Linux上运行成功。这是预期的,因为系统上安装的通常是针对与执行它们相同的目标和主机进行编译的GCC和binutils。或者用通俗易懂的话说,它们将生成适用于Linux内核和GNU C标准库(或兼容的libc)运行时的可执行文件。
也有编译器可以构建在不同类型的操作系统和/或CPU体系结构上运行的程序。这些被称为交叉编译器,例如,您可以使用它们在桌面计算机上构建智能手机应用程序。当您使用Windows Android NDK创建本地Android可执行文件时,实际上正在进行交叉编译,以适用于Linux内核和不同的CPU(很可能是ARM或MIPS)。
然后我将“.exe”扩展名添加到程序中,以查看它是否适用于Windows。
这里有一个误解,我永远不会理解它为什么会首先出现。即文件名后缀与它的功能有关。Windows使用它来查找注册表中的内容,以确定如何处理它,但仅此而已。在Windows中,您可以注册任何后缀名,以被识别为“可执行文件”。Linux(作为Unixoid)执行的是更简单但更强大的操作:每个文件都携带一组标志,以确定它是否可以被执行,如果可以,那么是谁可以执行它。
但是,为了实际上作为可运行程序对系统有用,需要不同的东西,所有可执行文件(对于给定的操作系统)都共享,而不考虑它们的文件名:特定的内部格式。Windows使用所谓的“PE”格式(PE代表可移植可执行文件,这有点可笑,因为唯一支持它的操作系统是Windows)。Linux和*BSD使用ELF(可执行和链接格式),而MacOS X使用称为“Mach二进制格式”的东西,其中Intel CPU的过渡扩展到“Mach通用二进制格式”。从技术上讲,Mach内核(MacOS X的基础)也可以理解“ELF”格式,但我认为在MacOS X中没有启用它。
虽然两种文件格式基本上都是描述如何将文件加载到内存中,它依赖的库必须加载在哪里以及从程序的哪个点实际开始执行等相同的事情,但它们的结构非常不同。Windows 不知道如何加载和执行 ELF 文件,Linux 也不知道如何加载和执行 PE 文件。
即使其中任何一个支持另一个,系统仍将缺少程序期望加载和运行的库。至少需要操作系统相关的运行时环境,该环境负责诸如从操作系统检索启动参数、提供与操作系统特定的内存管理函数的接口等“平凡”任务。这些在 Linux 和 Windows 之间也有根本性的区别。
请注意,可以在操作系统特定的内容之上构建兼容性包装器,用于模拟程序的其他操作系统,并且还知道如何加载、链接和启动外部可执行文件。在 Linux 上加载和执行 Windows 程序的包装器称为 WINE,还存在反向工具 LINE,它在 Windows 上运行 Linux 可执行文件。
但它没有起作用。程序不能在 Windows 中运行的原因是什么。
Windows和Linux不同。 它有完全不同的系统级API,使用不同的二进制格式,甚至像stdin / stdout(由C标准库定义)这样基本的东西也实现非常不同。 哎呀,它们甚至使用不同的calling conventions
Linux使用标准的C调用约定(stdcall)。 Windows主要使用cdecl约定,这是一种混合了Pascal和stdcall约定的约定。
基本上,在未使用特定于操作系统的库的情况下,Windows和Linux下编译的程序中的二进制和汇编指令相同。
嗯,不,它们绝对不同。 仅调用约定的差异就会产生不同的汇编代码。
操作系统只需将程序加载到RAM中,处理器将执行它。
不是那么简单。 程序还必须与操作系统“连接”,才能做任何有用的事情。 您想在控制台上看到打印的字符串吗? 那么您必须将其连接到一些执行此操作的操作系统函数。
在Linux中,控制台已经存在,并且有特殊文件
/dev/console
、
/dev/stdin
、
/dev/stdout
、
/dev/stderr
,它们内部连接到当前的
pts
或
tty
。在进程中,这些始终映射为文件描述符0、1和2。
在Windows中,程序可能需要打开自己的控制台窗口(如果从CLI启动,则会使用该窗口)。因此,它必须调用
AllocConsole
,如果失败则回退到
AttachConsole(parent_PID)
;然后可以使用
GetStdHandle
获取stdin、stdout、stderr。这些获取的
HANDLE
是任意的,需要连接到C stdio(printf、fwrite等)和C++ iostream的内部实现。
CPU不会因为一个二进制文件在RAM中出现而“神奇地”执行它。操作系统必须创建一个新的进程,为此它必须知道二进制文件中某些关键代码所在的位置(入口点)。这些入口点在二进制文件格式的特殊位置上进行描述(除其他事项外)。正如我已经解释过的那样,Windows只理解非常不同于Linux的可执行二进制文件格式(PE)。一旦二进制文件已被加载和链接,它必须实际启动。如上所述,Linux和Windows启动二进制文件的步骤也是不同的。
晚期编辑,但必须要写:
重要的是要理解一个可执行文件的二进制代码并没有在程序开始时完全加载到内存中。实际上,操作系统会创建一种所谓的“虚拟内存映射”。这意味着操作系统会创建一个虚拟地址空间,并结合其他数据结构来构建一个被称为
进程的东西。虚拟地址空间又由一组所谓的“页面”支持,这些页面是映射到实际内存或存储设备的地址空间块。事实上,通常RAM始终被用作磁盘操作的缓存。当程序的二进制代码被映射时,虚拟内存页面将始终指向磁盘存储器,而RAM仅充当缓存。动态分配的内存来自缓存交换空间的磁盘缓存。如果没有交换设备,则从通用磁盘缓存中获取。
如您所见,这些任务列表完全不同。再加上调用约定和二进制格式的差异。
那么,为什么它不起作用呢?
因为可执行文件不仅仅包含静态的汇编指令。程序需要与操作系统进行通信,以生成有意义的内容。而Windows和Linux非常不同。