85

I'm trying to determine the technical details of why software produced using programming languages for certain operating systems only work with them.

It is my understanding that binaries are specific to certain processors due to the processor specific machine language they understand and the differing instruction sets between different processors. But where does the operating system specificity come from? I used to assume it was APIs provided by the OS but then I saw this diagram in a book: Diagram

Operating Systems - Internals and Design Principles 7th ed - W. Stallings (Pearson, 2012)

As you can see, APIs are not indicated as a part of the operating system.

If for example I build a simple program in C using the following code:

#include<stdio.h>

main()
{
    printf("Hello World");

}

Is the compiler doing anything OS specific when compiling this?

BЈовић
  • 14,031
  • 8
  • 62
  • 82
  • 17
    Do you print to a window? or a console? or to graphics memory? How do you put the data there? Looking at printf for Apple][+ would be quiet different than for a Mac OS 7 and again quite different than Mac OS X (just sticking with one 'line' of computers). –  Jul 08 '14 at 02:10
  • @MichaelT To the console. I was looking for just generally why software becomes OS specific. – user139929 Jul 08 '14 at 02:27
  • 4
    Because if you wrote that code for Mac OS 7, it would show up in text in a new window. If you did it on Apple ][+, it would be writing to some segment of memory directly. On Mac OS X, it writes it out to a console. Thus, thats three different ways of writing handling the code based on the execution hardware that is handled by the library layer. –  Jul 08 '14 at 03:45
  • Was there ever actually a C compiler for the Apple ][+? – Gort the Robot Jul 08 '14 at 03:56
  • 2
    @StevenBurnap yep - http://en.wikipedia.org/wiki/Aztec_C –  Jul 08 '14 at 03:59
  • Huh...I used that back in the day for Z80 but either didn't know or have forgotten that there was an Apple ][+ version. – Gort the Robot Jul 08 '14 at 04:00
  • 11
    Your FFT function will happily run under Windows or Linux (on the same CPU), without even recompiling. But then how are you going to display the result? Using an operating system API, of course. (printf from msvcr90.dll is not the same as printf from libc.so.6) – user253751 Jul 08 '14 at 06:53
  • I think the point to take here is, that source code is mostly OS independent. Not the executable. Why is that is already answered. – jnovacho Jul 08 '14 at 09:33
  • 2
    user139929: your original hunch is correct - many software are written to OS-specific APIs. As for how many is "many", one should keep in mind that some companies and programmers value cross-platform more highly than others; some companies and programmers find it more convenient to write software to a single platform. We cannot speculate on deficiencies of a textbook diagram - it is to be taken as an illustration, not a dogma. We are not here to criticize a textbook author just because a diagram happens to omit some details. – rwong Jul 08 '14 at 09:42
  • 1
    Many software that are already heavily integrated with a Graphical User Interface (GUI) are already OS-dependent. This is because each operating system has its own GUI styles and customs; There are cross-platform GUIs but not every company and programmer makes the decision to use them. To discuss the pros and cons of cross-platform vs. platform-specific GUI development is a question too broad to be answered in this place. One would not be able to comprehend without at least some working experience on some kind of commercial-scale GUI projects. – rwong Jul 08 '14 at 09:48
  • 9
    Even if APIs are "not part of the operating system", they are still different if you go from one OS to the other. (Which, of course, raises the question of what the phrase "not part of the operating system" really means, according to the diagram.) – Theodoros Chatzigiannakis Jul 08 '14 at 10:21
  • APIs being "part of the OS" is irrelevant. A C++ API (e.g. the Standard Library) may be the same (or close enough) between two OSes. However, the implementation of that API will be different. Example: the Linux and Windows operating systems each have different ways of accessing the frame buffer, and there may be an additional layer of a console on top of that. So displaying your "Hello World" may use a platform-independent API, but the binary absolutely must be platform-dependent. –  Jul 08 '14 at 18:24
  • @JohnGaughan your explanation does not explain why binaries are platform-dependent. – user253751 Jul 10 '14 at 09:59
  • @immibis If two implementations of an API are different, their binaries are different. Linking against those different implementations will require different symbol imports, which affects the linking stage, producing different binaries. –  Jul 10 '14 at 14:03
  • 1
    The library binaries will be different, yes. Still doesn't explain why the program binary needs to be different if it could just say "import printf". I know why they are different, but your explanation doesn't cover it. – user253751 Jul 10 '14 at 23:48
  • API is part of the figure (left column, label Application Programming Interface for border between libraries and application programs) – Basile Starynkevitch Oct 05 '14 at 16:59

11 Answers11

83

You mention on how if the code is specific to a CPU, why must it be specific also to an OS. This is actually more of an interesting question that many of the answers here have assumed.

CPU Security Model

The first program run on most CPU architectures runs inside what is called the inner ring or ring 0. How a specific CPU arch implements rings varies, but it stands that nearly every modern CPU has at least 2 modes of operation, one which is privileged and runs 'bare metal' code which can perform any legal operation the CPU can perform and the other is untrusted and runs protected code which can only perform a defined safe set of capabilities. Some CPUs have far higher granularity however and in order to use VMs securely at least 1 or 2 extra rings are needed (often labelled with negative numbers) however this is beyond the scope of this answer.

Where the OS comes in

Early single tasking OSes

In very early DOS and other early single tasking based systems all code was run in the inner ring, every program you ever ran had full power over the whole computer and could do literally anything if it misbehaved including erasing all your data or even doing hardware damage in a few extreme cases such as setting invalid display modes on very old display screens, worse, this could be caused by simply buggy code with no malice whatsoever.

This code was in fact largely OS agnostic, as long as you had a loader capable of loading the program into memory (pretty simple for early binary formats) and the code did not rely on any drivers, implementing all hardware access itself it should run under any OS as long as it is run in ring 0. Note, a very simple OS like this is usually called a monitor if it is simply used to run other programs and offers no additional functionality.

Modern multi tasking OSes

More modern operating systems including UNIX, versions of Windows starting with NT and various other now obscure OSes decided to improve on this situation, users wanted additional features such as multitasking so they could run more than one application at once and protection, so a bug (or malicious code) in an application could no longer cause unlimited damage to the machine and data.

This was done using the rings mentioned above, the OS would take the sole place running in ring 0 and applications would run in the outer untrusted rings, only able to perform a restricted set of operations which the OS allowed.

However this increased utility and protection came at a cost, programs now had to work with the OS to perform tasks they were not allowed to do themselves, they could no longer for example take direct control over the hard disk by accessing its memory and change arbitrary data, instead they had to ask the OS to perform these tasks for them so that it could check that they were allowed to perform the operation, not changing files that did not belong to them, it would also check that the operation was indeed valid and would not leave the hardware in an undefined state.

Each OS decided on a different implementation for these protections, partially based on the architecture the OS was designed for and partially based around the design and principles of the OS in question, UNIX for example put focus on machines being good for multi user use and focused the available features for this while windows was designed to be simpler, to run on slower hardware with a single user. The way user-space programs also talk to the OS is completely different on X86 as it would be on ARM or MIPS for example, forcing a multi-platform OS to make decisions based around the need to work on the hardware it is targeted for.

These OS specific interactions are usually called "system calls" and encompass how a user space program interacts with the hardware through the OS completely, they fundamentally differ based on the function of the OS and thus a program that does its work through system calls needs to be OS specific.

The Program Loader

In addition to system calls, each OS provides a different method to load a program from the secondary storage medium and into memory, in order to be loadable by a specific OS the program must contain a special header which describes to the OS how it may be loaded and run.

This header used to be simple enough that writing a loader for a different format was almost trivial, however with modern formats such as elf which support advanced features such as dynamic linking and weak declarations it is now near impossible for an OS to attempt to load binaries which were not designed for it, this means, even if there were not the system call incompatibilities it is immensely difficult to even place a program in ram in a way in which it can be run.

Libraries

Programs rarely use system calls directly however, they almost exclusively gain their functionality though libraries which wrap the system calls in a slightly friendlier format for the programming language, for example, C has the C Standard Library and glibc under Linux and similar and win32 libs under Windows NT and above, most other programming languages also have similar libraries which wrap system functionality in an appropriate way.

These libraries can to some degree even overcome the cross platform issues as described above, there are a range of libraries which are designed around providing a uniform platform to applications while internally managing calls to a wide range of OSes such as SDL, this means that though programs cannot be binary compatible, programs which use these libraries can have common source between platforms, making porting as simple as recompiling.

Exceptions to the Above

Despite all I have said here, there have been attempts to overcome the limitations of not being able to run programs on more than one operating system. Some good examples are the Wine project which has successfully emulated both the win32 program loader, binary format and system libraries allowing Windows programs to run on various UNIXes. There is also a compatibility layer allowing several BSD UNIX operating systems to run Linux software and of course Apple's own shim allowing one to run old MacOS software under MacOS X.

However these projects work through enormous levels of manual development effort. Depending on how different the two OSes are the difficulty ranges from a fairly small shim to near complete emulation of the other OS which is often more complex than writing an entire operating system in itself and so this is the exception and not the rule.

Vality
  • 971
  • 6
    +1 "Why is software OS specific?" Because history. – Paul Draper Jul 09 '14 at 03:07
  • 2
    is the CPU security model x86-originated? why and when was the model invented? – n611x007 Jul 09 '14 at 15:39
  • 8
    @naxa No, it long predates x86, it was first partially implemented for Multics in 1969 which is the first OS with useful multi-user time-sharing features requiring this model in the GE-645 computer, however this implementation was incomplete and relied on software support, the first complete and secure implementation in hardware was in it's successor, the Honeywell 6180. This was fully hardware based and allowed Multics to run code from multiple users without the opportunity to cross-interfere. – Vality Jul 09 '14 at 15:50
  • @Vality Also, IBM LPAR is ~1972. – Elliott Frisch Aug 15 '14 at 01:27
  • @ElliottFrisch wow, that is impressive. I had not realised it was quite that early. Thanks for that info. – Vality Aug 15 '14 at 01:34
  • I think it might be worthwhile to add a bit about VMs like Java, and interpreters like Python, and their limitations. – supercheetah Oct 05 '14 at 19:55
49

As you can see, APIs are not indicated as a part of the operating system.

I think you are reading too much into the diagram. Yes, an OS will specify a binary interface for how operating system functions are called, and it will also define a file format for executables, but it will also provide an API, in the sense of providing a catalog of functions that can be called by an application to invoke OS services.

I think the diagram is just trying to emphasize that operating system functions are usually invoked through a different mechanism than a simple library call. Most of the common OS use processor interrupts to access OS functions. Typical modern operating systems are not going to let a user program directly access any hardware. If you want to write a character to the console, you are going to have to ask the OS to do it for you. The system call used to write to the console will vary from OS to OS, so right there is one example of why software is OS specific.

printf is a function from the C run time library and in a typical implementation is a fairly complex function. If you google you can find the source for several versions online. See this page for a guided tour of one. Down in the grass though it ends up making one or more system calls, and each of those system calls is specific to the host operating system.

  • 4
    What if all the program did was to add two number, with no input or output. Would that program still be OS specific? – Paul Jul 08 '14 at 07:21
  • @Paul No, but for example, all graphical work are OS specific. Furthermore, some OS are POSIX compliant, other are not, filesystem are OS dependent too, you can t open /home/user/file on a Windows OS, and can t open C:\ProgramData\file in Unix OS. – DrakaSAN Jul 08 '14 at 07:48
  • 2
    OS'es are meant to put most hardware specific stuff behind/in an abstraction layer. However, the OS itself (the abstraction) can differ from implementation to implementation. There's POSIX some OS'es (more or less) adhere to and maybe some others but overall OS'es simply differ too much in their "visible" part of the abstraction. As said before: you can't open /home/user on windows and you can't access HKEY_LOCAL_MACHINE... on a *N*X system. You can write virtual ("emulation") software for this to help bring these systems closer together but that will always be "3rd party" (from OS POV). – RobIII Jul 08 '14 at 08:15
  • 16
    @Paul Yes. In particular, the way it is packaged into an executable would be OS-specific. – OrangeDog Jul 08 '14 at 09:09
  • @OrangeDog Assuming the two Operating Systems have at least the same ABI and exectuable format (not unreasonable), it is concieveavble that such a program could be runnable without modification on an operating system it was not designed for. – Tim Seguine Jul 08 '14 at 12:20
  • IMO "not designed for" negates the assumption that they're compatible, rendering your argument moot. – OrangeDog Jul 08 '14 at 12:24
  • @OrangeDog no, "not designed for" can just mean for example that I didn't know that it exists at the time of compiling my executable. For example, quite a few windows XP programs will run on windows 7, even though they were clearly never designed for that purpose. – Tim Seguine Jul 08 '14 at 12:26
  • 4
    @TimSeguine I disagree with your example of XP vs 7. Much work is done by Microsoft to ensure the same API exists in 7 as was in XP. Clearly what has happened here is that the program was designed to run against a certain API or contract. The new OS just adhered to the same API/contract. In the case of windows however that API is very much proprietary, which is why no other OS vendor supports it. Even then there are a heck of a lot of examples of programs that do NOT run on 7. – ArTs Jul 08 '14 at 17:59
  • @OrangeDog AFAIK, you can theoretically write an executable loader, like binfmtpe for your favorite OS for your favorite executable format, be it ELF, PE, COFF, or whatever others may (have once been) exist – n611x007 Jul 09 '14 at 15:04
  • @naxa By that logic, any code will run on any system, provided you write or install install the appropriate compatibility, emulation or virtualisation software. – OrangeDog Jul 09 '14 at 15:06
  • @OrangeDog I mean, I was thinking, if a PE executable contains zero OS-specific API calls (or anything 'above' that), to my understanding, it could still have some valid machine code. Thus a loader could theoretically load it for good. Where I could use clarification is whether the binary interface refers solely to the OS call mechanism? The figure of OP above shows a part where 'application' code directly connects to 'instruction set architecture' and 'execution hardware'. I wonder what part is it, and if is there some narrow scope utilizing only that? – n611x007 Jul 09 '14 at 15:36
  • 3
    @Paul: A program that does no Input/Output is the empty program, which should compile to a no-op. – Bergi Jul 09 '14 at 16:08
  • @ArTs The thread I was responding to had nothing to do with APIs, but to executable packaging. My statement was that exectuables that were compiled for Windows XP(without any extra work or conscious planning from the programmer) will often load and work on a different operating system (windows 7).Note that I am speaking of what the programmer has knowledge of, not what Microsoft plans or practices in general, and that I am speaking solely of application formats, not about API stability. That is a completely different thing; something I wasn't claiming. – Tim Seguine Jul 09 '14 at 17:16
  • @TimSeguine again I have to disagree, as a programmer, I do have to consciously follow what Microsoft plans for each API. Microsoft does publish guidance on what APIs will be kept stable. – ArTs Jul 09 '14 at 17:19
  • @ArTs I wasn't making any claims about APIs. Executable packaging has nothing to do with APIs. Your statements about APIs are correct, and I agree with you. I am only saying that they have nothing to do with the claim I was making. I think we are talking past each other. – Tim Seguine Jul 09 '14 at 17:22
  • @Bergi It's conceivable (and trivial!) that you could make a program that just operates on some values and does no IO, which is what I was asking about. – Paul Jul 09 '14 at 18:17
  • 2
    @Paul, it is probably more complex than you suspect. First of all, compilers will typically stick in some code that sets up the programming model used by the compiler. Google 'C startup code' for an example. Also, OS expect the executable to be in a particular file format, which is more or less OS specific, and contains a bunch of metadata about the program. Some OS will support a few different formats. For example, Windows still supports the COM format from MS-DOS for small (<64k) programs. COM is just the characters 'MZ' followed by the bare machine code with no metadata. – Charles E. Grant Jul 09 '14 at 19:20
  • @CharlesE.Grant Thanks for the explanation. I understood (and pretty much expected) that the OS expects the executable to be in a specific format. :) – Paul Jul 09 '14 at 19:25
17

Is the compiler doing anything OS specific when compiling this?

Probably. At some point during the compiling and linking process, your code is turned into an OS-specific binary and linked with any required libraries. Your program has to be saved in a format that the operating system expects so that the OS can load the program and start executing it. Furthermore, you're calling the standard library function printf(), which at some level is implemented in terms of the services that the operating system provides.

Libraries provide an interface -- a layer of abstraction from the operating system and the hardware -- and that makes it possible to recompile your program for a different operating system or different hardware. But that abstraction exists at the source level -- once the program is compiled and linked, it's connected to a specific implementation of that interface that's specific to a given OS.

Caleb
  • 39,147
  • 8
  • 95
  • 152
12

There are a number of reasons, but one very important reason is that the Operating System has to know how to read the series of bytes that make up your program into memory, find the libraries that go with that program and load them into memory, and then start executing your program code. In order to do this, the creators of the OS create a particular format for that series of bytes so that the OS code knows where to look for the various parts of the structure of your program. Because the major Operating Systems have different authors, these formats often have little to do with each other. In particular, the Windows executable format has little in common with the ELF format most Unix variants use. So all this loading, dynamic linking and executing code has to be OS specific.

Next, each OS provides a different set of libraries for talking to the hardware layer. These are the APIs you mention, and they are generally libraries that present a simpler interface to the developer while translating it to more complex, more specific calls into the depths of the OS itself, these calls often being undocumented or secured. This layer is often quite grey, with newer "OS" APIs are built partially or entirely on older APIs. For example, in Windows, many of the newer APIs Microsoft has created over the years are essentially layers on top of the original Win32 APIs.

An issue that does not arise in your example, but that is one of the bigger ones that developers face is the interface with the window manager, to present a GUI. Whether the window manager is part of the "OS" sometimes depends on your point of view, as well as the OS itself, with the GUI in Windows being integrated with the OS at a deeper level, while the GUIs on Linux and OS X being more directly separated. This is very important because today what people typically call "The Operating System" is a much bigger beast than what textbooks tend to describe, as it includes many, many application level components.

Finally, not strictly an OS issue, but an important one in executable file generation is that different machines have different assembly language targets, and so the actual generated object code must different. This isn't strictly speaking an "OS" issue but rather a hardware issue, but does mean that you will need different builds for different hardware platforms.

warren
  • 619
  • 7
  • 15
Gort the Robot
  • 14,774
  • 4
  • 51
  • 60
  • 2
    It may be worthwhile to note that simpler executable formats can be loaded using only a tiny amount of RAM (if any) beyond that required to hold the loaded code, while more complex formats may require a much larger RAM footprint during, and in some cases even after, loading. MS-DOS would load COM files up to 63.75K by simply reading sequential bytes to RAM starting at offset 0x100 of an arbitrary segment, load CX with the ending address, and jump to that. Single-pass compilation could be accomplished without back-patching (useful with floppies) by... – supercat Jul 08 '14 at 15:04
  • 1
    ...having the compiler include with each routine a list of all patch-points, each of which would include the address of the previous such list, and putting the address of the last list at the end of the code. The OS would just load the code as raw bytes, but a small routine within the code could apply all necessary address patches before running the main portion of the code. – supercat Jul 08 '14 at 15:06
9

From another answer of mine:

Consider early DOS machines, and what Microsoft's real contribution to the world was:

Autocad had to write drivers for each printer they could print to. So did lotus 1-2-3. In fact, if you wanted your software to print, you had to write your own drivers. If there were 10 printers, and 10 programs, then 100 different pieces of essentially the same code had to be written separately and independently.

What windows 3.1 tried to accomplish (along with GEM, and so many other abstraction layers) is make it so the printer manufacturer wrote one driver for their printer, and the programmer wrote one driver for the windows printer class.

Now with 10 programs and 10 printers, only 20 pieces of code have to be written, and since the microsoft side of the code was the same for everyone, then examples from MS meant that you had very little work to do.

Now a program wasn't restricted to just the 10 printers they chose to support, but all the printers whose manufacturers provided drivers for in windows.

So the OS provides services to the applications so the applications don't have to do work that is redundant.

Your example C program uses printf, which sends characters to stdout - an OS specific resource that will display the characters on a user interface. The program doesn't need to know where the user interface is - it could be in DOS, it could be in a graphical window, it could be piped to another program and used as input to another process.

Because the OS provides these resources, programmers can accomplish much more with little work.

However, even starting a program is complicated. The OS expects an executable file to have certain information at the beginning that tells the OS how it should be started, and in some cases (more advanced environments like android or iOS) what resources will be required that need approval since they touch resources outside the "sandbox" - a security measure to help protect users and other apps from misbehaving programs.

So even if the executable machine code is the same, and there are no OS resources required, a program compiled for Windows won't run on an OS X operating system without an additional emulation or translation layer, even on the same exact hardware.

Early DOS style operating systems could often share programs, because they implemented the same API in hardware (BIOS) and the OS hooked into the hardware to provide services. So if you wrote and compiled a COM program - which is just a memory image of a series of processor instructions - you could run it on CP/M, MS-DOS, and several other operating systems. In fact you can still run COM programs on modern windows machines. Other operating systems don't use the same BIOS API hooks, so the COM programs won't run on them without, again, an emulation or translation layer. EXE programs follow a structure that includes much more than mere processor instructions, and so along with the API issues it won't run on a machine that doesn't understand how to load it into memory and execute it.

Adam Davis
  • 3,856
7

Actually, the real answer is that if every OS did understand the same executable binary file layout, and you only limited yourself to standardized functions (like in the C standard library) that the OS provided (which OSes do provide), then your software would, in fact, run on any OS.

Of course, the reality is that's not the case. An EXE file doesn't have the same format as an ELF file, even though both contain binary code for the same CPU.* So each operating system would need to be able to interpret all file formats, and they simply didn't do this in the beginning, and there was no reason for them to start doing so later (almost certainly for commercial reasons rather than technical ones).

Furthermore, your program probably needs to do things that the C library doesn't define how to do (even for simple things like listing the contents of a directory), and in those cases every OS provides its own function(s) for achieving your task, naturally meaning there won't be a lowest common denominator for you to use (unless you make that denominator yourself).

So in principal, it's perfectly possible. In fact, WINE runs Windows executables directly on Linux.
But it's a ton of work and (usually) commercially unjustified.

*Note: There's a lot more to an executable file than just binary code. There's a ton of information that tells the operating system what libraries the file depends on, how much stack memory it needs, what functions it exports to other libraries that may depend on it, where the operating system might find relevant debug information, how to "re-locate" the file in memory if necessary, how to make exception handling work correctly, etc. etc.... again, there could be a single format for this that everyone agrees on, but there simply isn't.

user541686
  • 8,102
  • Fun fact: there is a standardised posiz binary format, which can be run across OSes. It's just not commonly used. – Marcin Jul 09 '14 at 16:08
  • @Marcin: Seems like you don't consider Windows to be an OS. (Or are you saying Windows can run POSIX binaries?!) For the purposes of my answer POSIX isn't the kind of standard I'm referring to. The X in POSIX stands for Unix. It was never intended to be used by e.g. Windows, even though Windows does happen to have a POSIX subsystem. – user541686 Jul 09 '14 at 19:49
  • Something can run across multiple OSes without running across all OSes; 2. Windows since NT has been able to run posix binaries.
  • – Marcin Jul 09 '14 at 19:56
  • 1
    @Marcin: (1) Like I said, the X in POSIX stands for UNIX. It is not a standard that was meant to be followed by other OSes, it was just an attempt to reach a common denominator between the various Unixes, which is great but not that amazing. The fact that there are multiple flavors of Unix OSes out there is completely irrelevant to the point I've been trying to make regarding compatibility across other operating systems than Unix. (2) Can you provide a reference for #2? – user541686 Jul 09 '14 at 20:03
  • I'm sorry, if you're going to play the references game, why don't you provide a reference for what POSIX stands for? Bolding the word "Unix" is not a reference. – Marcin Jul 09 '14 at 20:15
  • http://technet.microsoft.com/en-us/library/bb496474.aspx – Marcin Jul 09 '14 at 20:18
  • @Marcin: What kind of a reference is that supposed to be?! The word POSIX doesn't even appear on there, let alone a mention of executable file formats. As for yours, here you go; your turn. – user541686 Jul 09 '14 at 21:05
  • 1
    @Mehrdad: Marcin is right; Windows SUA (Subsystem for Unix Applications) is POSIX compliant – MSalters Jul 10 '14 at 11:12
  • @MSalters: Not quite -- if you (and Marcin) read my very first comment, you will see I was already aware of that and mentioned it before either of you did. However, that's not what Marcin is saying, even though maybe that's what he thinks he's saying. He's making a stronger claim: that there exists a "POSIX binary format" that Windows can execute, for which I'm asking him to provide a reference, because (correct me if I'm wrong) I'm fairly sure that even the "POSIX" executables in Windows in fact use the PE file format, not some "POSIX binary format" (if such a thing even exists). – user541686 Jul 10 '14 at 11:20