BSD, Linux and Apple all write their own ones.
Are you sure that BSD<>Apple here? With respect to libc I mean.
It's not libc as such, it is the source code behind it.
They are written in C, which requires a C compiler and runtime. Those C runtimes and the libraries that go with them are for the most part the same, and written long ago.
Two of the three targets you name use LLVM, which is not that old. And as you said, C runtimes are paired with compilers....
Yes, they use LLVM.
The thing is: would you switch to LLVM if it couldn't compile your gcc project? Or with a minimum of fuss?
Those code bases are HUGE.
POSIX was a successful project to standardize the API of different OSes, and it is one of the highest level interfaces in the C runtimes and libraries (libc). Richard Stallman had a lot to do with that.
Mostly different Unices, with some extremist claiming it was universal (as long as the other OS was unix like or emulated it)
And that's what they all used to build that kernel and the rest of their OS. On that level, there is even little difference between Windows and Linux.
Yes. There is a very big difference. The Windows kernel is not POSIX, and only C apps use the C runtime, as on Windows there is a difference between the C language runtime (ms*crt) and the general application user library (user32.dll)
Windows is just as POSIX-compliant as the *NIXes, just in a slightly different manner. The threading model and the Inter-Process Communication are the main differences. But even those are based on partly the same source code on the lowest level.
Although their APIs are quite different. But as programmer, you tend to use the libc API in the first place, with all the platform SDK headers that go with it. And they are very much alike. They all use the same code underneath.
This is not true for Windows at all. The Platform SDK headers are not on top of the C runtime. The C runtime uses the Platform SDK headers.
It's both.
Let's start at the bottom.
Without a kernel, there is no memory management. So, no malloc. No variables, unless they're registers. And some CPU's have only a single, or a few usable registers. So, the first thing you have to do is make the memory manager.
But, how are you going to do that? Is your kernel going to hand out memory allocation for each variable? Or only for larger blocks?
The thing that interests the kernel is the prevention of conflicts. And perhaps the coherence, but that's a different discussion.
Like a subroutine might have to push the registers it wants to use on the stack, and pop them back before returning, it's up to the kernel to make sure all the memory a process can access is available when it is running. Or to have an interrupt in place to do that JIT, on the moment the process tries to access it.
Most current kernels that I know of use paging to do that, and almost all of those use 4kB pages. For the most part because the CPU can do that as well. So, the moment a process tries to access some part of memory, the kernel is going to make sure the right memory page is "loaded" at that location. And that's all most kernels do for memory management. (Well, there's drivers and other resources, but that's not relevant here.)
To your application (the process), it looks like you have kilobytes (for 16-bit applications), megabytes, gigabytes or even an almost limitless amount of memory (for 64-bit applications). But how you are going to make sure your variables, buffers and instances all have their own space is up to you.
And the same goes for the kernel, as it also needs space for those things. So, they both have their own, fine-grained memory manager as well. Which is probably the same one, if both are written in C. Or if you use cmem in Lazarus. Or a different one if you don't.
So, there are two (actually more, but not for now) different memory management schemes, of which the fine-grained one is probably the same for the kernel as well as many applications.
Another great example are sockets. Almost everyone still uses the same socket code, as written nearly 50 years ago by people from Berkley University, while the internet was still called ARPANET.
And all those things are part of the runtime and/or default library. The kernel needs to make sure that your average C application compiles, which requires that both of those are supported. And it needs that runtime itself as well to function.
Only Intel builds their own compiler and runtime, everyone else uses gcc. Even a new design like LLVM needs to have a functional compiler before it is able to build itself. And it should be compatible with it.
The same goes for FPC.
Next up the ladder has historically been C++, or Objective-C for Apple. Same thing, different implementation. The main difference is the event (messaging) model. You need a functional C compiler, with the "default" libraries and runtime to bootstrap it. Which expands the libraries and runtime available.
And you use THAT language to build the rest of your OS (driver and software management, userland, at least one (G)UI and the all-important API).
When building a high-level application, you can stick to the high-level API for your interface. But that API is part of the OS, which uses all those lower-level interfaces itself. Which are included in the headers of the platform SDK. And the only reason you might not be able to use those as well is that they might run in (protected) kernel mode.
But even so, it all uses the same code underneath (for the most part).
If you want to make your code platform independent, you can either stick to the common demeanor, or you can research the header files and libraries to see where the different platforms diverge. And what the actual code underneath does.
On that level, even the threading model of Windows and *NIX is alike.
Then again, it was more different in the past. Or at least, it looked like it. Critical sections versus semaphores. But nowadays everyone uses the same, simplified mutexes for everything. Although spinlocks are still used by the kernel in some rare cases.
So, behind the screens, on the lowest level, Windows actually uses something that is very much alike the simplified pthread model that the *NIXes use as well.
And while Apple has become a bit less compliant with the changes originating by their move to make everything reference counted, and as they use a much simplified event model for their GUI (they skip the formal events and use messages for everything), that's only an issue if you use the top level API. Which you have to do if you want to use their GUI.
FPC is one of the few non-Apple development platforms which can do that. Delphi, for example, circumvents it.
TL;DR: almost everything is build on top of the same, huge library of code that was written about 40 years ago. Most of the differences are in the top levels, the actual runtimes and libraries are mostly the same.