Linux和Windows程序有哪些不同之处?

3
我使用GCC编译了一个简单的“Hello World”程序,在Linux中运行成功。然后我添加了“.exe”扩展名来测试在Windows下是否能正常工作。但是它没有工作。为什么程序不能在Windows中工作呢?基本上,如果不使用特定于操作系统的库,那么在Windows和Linux下编译的程序的二进制和汇编指令是相同的。操作系统只需要将程序加载到RAM中,处理器就会执行它。所以,为什么它不能工作呢?

2
请展示给我们代码。 - Lithu T.V
3
@LithuT.V 一个好笑的笑话! - vines
1
我只是想要代码,以确保他在没有操作系统支持能力的情况下是如何做到的。 - Lithu T.V
3
这个方案行不通的原因有很多,涵盖了系统结构、系统库/头文件、处理器架构、链接接口实现定义行为等方面。 - dhein
呵呵..我挂了!! - Lithu T.V
显示剩余3条评论
4个回答

13
我会把你的问题分解成更小的子集。
我在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,它们内部连接到当前的ptstty。在进程中,这些始终映射为文件描述符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非常不同。


谢谢 datenwolf 和大家。现在我对操作系统环境下可执行文件是如何执行有了一些概述,但是否有一本书/电子书更详细地介绍代码在编译时实际发生的事情,并深入讲解可执行文件和系统调用/系统中断的工作原理的信息? - Rahulsingh190
Linux内核是开源的,你可以直接查看它或者查看关于它的许多书籍。对于ELF格式,你可以直接查看规范 - m0skit0
@user2952250:就像m0skit0所说的那样。在Windows方面,权威的资料来自于《Windows Internals》一书。http://technet.microsoft.com/en-us/sysinternals/bb963901.aspx - datenwolf

9

现代操作系统并不简单。如果你将Linux可执行文件添加.EXE扩展名,并在Windows中运行它,那么它是无法工作的。现代操作系统除了代码和数据之外,还有许多额外的信息存储在可执行文件中。Linux使用ELF可执行文件格式,而Windows使用EXE格式。这种格式包括了重定位信息、内存大小、分区以及仅对使用该可执行文件格式的操作系统相关的信息。因此,仅更改文件扩展名是不起作用的。

另外,操作系统系统调用及其调用方式也有所不同。Linux使用软件中断(INT 0x80,如果我没记错的话)进行系统调用,而Windows则使用另一种方法。这意味着您必须为每个目标重新编译,因为甚至系统调用的方式也不相同,C编译器需要与正确的 libc 链接。


实际上,如果硬件支持快速系统调用指令,Linux 不再使用软件中断。 - datenwolf
@datenwolf 我也是这么认为的,但当我检查源代码(2.4版)时,它确实有。谢谢你的澄清。 - m0skit0
2.4版本已经过时了。是的,那个内核系列依赖于int 0x80。2.6/3.x版本也有这个代码,以支持没有快速系统调用能力的基于x86的架构。然而,在足够现代的发行版和CPU上,将使用快速路径。 - datenwolf

2
Linux和Windows可执行文件的主要区别在于格式。Linux使用ELF格式,而Windows使用PE格式
您需要在每个平台上编译您的程序。对于Windows来说,MinGW可能是一个有趣的选择。

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接