12

I understand that processors belonging to different architectures have different instruction set and therefore a program compiled for one processor (hardware platform) can't run on a processor with different ISA.

But why is a compiled program Operating System dependent?

For example a C program compiled on Windows running on a x86 Intel processor doesn't run on Linux running on the same x86 Intel processor.

What happens during compilation that makes the executable dependent on OS? For example does gcc do something different that msvc during compilation in the case of C?

I have read a related question but couldn't find a deep and satisfying answer.

3 Answers3

24

The Operating System provides services to application programs. These services may include reacting to keyboard input, reacting to mouse input, drawing to the screen, printing to the terminal, reading and writing files, sending and receiving data over the network, and many, many more.

Different Operating Systems offer these services in different ways, using different names for the functions, different parameters, different semantics, and so on. Therefore, an application program needs to be adapted to each OS. This adaptation is typically called "porting" the application.

There are actually two aspects to consider here:

The API is what I mentioned above: the names, types, parameters, and semantics of the various functions that provide the Operating System services.

For example, in Linux, you create a new process with clone, in Windows, you use CreateProcess. In Linux, you open a file with open, in Windows, you use CreateFile.

However, even when Operating Systems have the same API, that does not automatically mean you can run the same binary in both of them. Because even if the names, types, parameters, and semantics of the functions are the same, that does not automatically mean that the encoding of the binary data in memory is the same, or that the way in which arguments are passed is the same. How all of this is encoded in memory and into the CPU is defined by the Application Binary Interface, in other words, that's what the "binary" in "ABI" means. A concrete example of what is defined in the ABI is the so-called calling convention, i.e., how to pass arguments to functions (on the stack, on the heap, in registers, which registers, etc.).

For example, there is the international POSIX standard which defines a portable API for Unix-like Operating Systems: if you write your program using only the functions defined in the POSIX standard, the program should be able to be compiled for every POSIX-compliant OS. But, the program will still have to be compiled for every OS, since they have only the same API but not the same ABI.

In the late 1980s Intel, AT&T, SCO, and some others got together and defined the Intel Binary Compatibility Standard (iBCS), which is an ABI defined for POSIX implementations running on x86. Any program which only uses functions defined in POSIX and is compiled against iBCS will run on every OS that provides an implementation of iBCS. Linux did have an iBCS compatibility layer in the 1990s, but it has long since been removed.

In the 2000, the Linux Standard Base (LSB) was created as an effort to define ABI standards for Linux. The idea was that an application that was compiled against the LSB could be installed and ran on any LSB-compliant Linux distribution. Furthermore, the LSB specification should be precise enough that other Unix-like OS should be able to implement an LSB compatibility layer and thus allow LSB-compliant applications to be used on those OSes as well. However, the LSB was never widely accepted, and the latest release was in 2015.

Nowadays, Linux actually serves the function of such an ABI-compatibility layer. There are multiple Unix-like Operating Systems which can execute Linux programs, for example FreeBSD (Linuxulator), NetBSD (compat_linux), and some of the OpenSolaris forks like OpenIndiana and OmniOS using a feature called Branded Zones.

Jörg W Mittag
  • 6,663
  • 27
  • 26
0

Of course Joerg's answer is correct, this is just another point of view thinking about where exactly in the "program" the incompatibilities are found.

Assuming identical hardware for the moment.

Sometimes, the differences are not strictly speaking in the operating system, but are in the compiler, the linker or the libraries.

  • Often a particular operating system requires you use a particular compiler, and compilers might have different calling conventions. For example, C compiler 1 might put the first argument in a register while compiler 2 passes all arguments on the stack.
  • Most of the operating-system specific code is really in the system calls, which are very often actually only called from libraries -- there being a little stub library function for each system call. Even in two systems with same calling conventions, it's the system call which might differ.

Imagining now same hardware and same calling conventions.

And thinking about compilation, linkage, and loading, we can ask when does the operating-system-incompatibility arrive? It may well be when the dynamically-linked library is linked: ie, at load time.

In the case of the classic program, if "the compiled program" means helloworld.o, it will have probably have no OS-specifc code at all: just library calls. Similarly, after (dynamic) linking, it just has unresolved external functions. So the actual executable helloworld might have no OS-specific parts at all, and one could imagine different, compatible, libraries-with-system-calls, which are linked at run-time.

With static linking helloworld would have embedded system call traps, pulled from the library, and thus be specific to a given set of system calls, almost always specific to a given operating system or emulation of that operating system.

jonathanjo
  • 101
  • 2
0

As the first thing when you try to start a program the operating system loads it from permanent storage and passes control to it. For that to work, the program in permanent storage must be in a certain format dictated by the operating system. But this is not standardised between operating systems.

So a program written for processor X which your computer supports must be formatted for Linux to run on Linux, or for windows to run on windows, or for macOS to run on macOS and so on. When you build your program it is built to run on one OS and won’t even start to run on another.

gnasher729
  • 32,238
  • 36
  • 56