Page 1 of 1

Layered C Library

Posted: Tue Jul 20, 2010 12:50 pm
by NickJohnson
I made an early decision when moving to userspace to have two layers to my C library. There is a lower layer, which contains nothing confirming to the standard, and doesn't use any functions from the upper layer, but has all of the OS-specific stuff, like file descriptors and IPC. The upper layer is just a normal C library, stripped of as much OS-specific stuff as possible. The problem is, I've been running into some issues with this separation.

First, both layers need access to the heap (in fact, the lower layer needs _another_ heap for persistent data, which is a huge mess in itself), so there are two separate heap APIs, one of which calls part of the other internally. This seems excessively pyramid-like, and makes all of the lower layer code unclear when it does memory allocation (it must use alloc_malloc(stdalloc, size) and alloc_free(stdalloc, ptr) instead of malloc(size) and free(ptr)).

Second, because all of the convenient (and safe, due to caching) I/O routines are in the higher layer, it is nearly impossible to debug the lower layer, even in the non-I/O areas, because nothing can be printed.

There are many other small dependency issues, as well as the added complexity of linking two libraries for basic functionality instead of one. The point is, there are too many cross-dependencies between the two libraries, because of the fact that the lower library needs platform independent stuff from the upper one, and the upper one's I/O routines need platform dependent stuff from the lower one.

What do you think I should do? Combining the two would allow me to remove a lot of redundant code and interface, but the whole idea at the beginning was to have the upper C library easily replaced by another C library, such as pdclib (after Solar finishes it in 2030 :P), and that would remove the advantage. I could also try stratifying into three layers, the lowest for platform independent standard stuff (stdlib, stdint, etc.), the middle for platform dependent nonstandard stuff (read, write, send, etc.), and the highest for platform dependent standard stuff (stdio).

Re: Layered C Library

Posted: Tue Jul 20, 2010 3:07 pm
by Owen
NickJohnson wrote:I made an early decision when moving to userspace to have two layers to my C library. There is a lower layer, which contains nothing confirming to the standard, and doesn't use any functions from the upper layer, but has all of the OS-specific stuff, like file descriptors and IPC. The upper layer is just a normal C library, stripped of as much OS-specific stuff as possible. The problem is, I've been running into some issues with this separation.

First, both layers need access to the heap (in fact, the lower layer needs _another_ heap for persistent data, which is a huge mess in itself), so there are two separate heap APIs, one of which calls part of the other internally. This seems excessively pyramid-like, and makes all of the lower layer code unclear when it does memory allocation (it must use alloc_malloc(stdalloc, size) and alloc_free(stdalloc, ptr) instead of malloc(size) and free(ptr)).
Personally, I think things would work better for you if you used a different naming scheme for your base library (E.G. AllocMemory, FreeMemory), so as to make things less confusing (doing this would make bad code look bad). AllocMemory/FreeMemory would be equivalent to malloc/free, while you would have other routines (e.g. HeapAlloc/HeapFree) for allocating from an arbitrary heap.
Second, because all of the convenient (and safe, due to caching) I/O routines are in the higher layer, it is nearly impossible to debug the lower layer, even in the non-I/O areas, because nothing can be printed.
Then put some I/O routines in the lower layer - even if only for debugging. The way I see it, this layer should be your OS' native API, and libc should simply be a standard conforming binding on top of that (to make writing portable code easier)
There are many other small dependency issues, as well as the added complexity of linking two libraries for basic functionality instead of one. The point is, there are too many cross-dependencies between the two libraries, because of the fact that the lower library needs platform independent stuff from the upper one, and the upper one's I/O routines need platform dependent stuff from the lower one.

What do you think I should do? Combining the two would allow me to remove a lot of redundant code and interface, but the whole idea at the beginning was to have the upper C library easily replaced by another C library, such as pdclib (after Solar finishes it in 2030 :P), and that would remove the advantage. I could also try stratifying into three layers, the lowest for platform independent standard stuff (stdlib, stdint, etc.), the middle for platform dependent nonstandard stuff (read, write, send, etc.), and the highest for platform dependent standard stuff (stdio).
The multi-layered approach is nothing new: a successful example is Windows (Which is tri-layered - libc -> kernel32 -> ntdll).

I think there are two issues you are experiencing:
  • The presumption that your library should be low level, and the C runtime low level; to my eyes, there should be high(er) level things in the OS library. libc should be something that linking against is optional - particularly for apps written in other languages (E.G. Pascal)
  • A lack of debugging support from the kernel. I've always felt that it should be at least possible to build the kernel so that it and applications running under it can be debugged from another machine. This is particularly vital for low level (and driver) development

Re: Layered C Library

Posted: Tue Jul 20, 2010 5:33 pm
by NickJohnson
Owen wrote: Personally, I think things would work better for you if you used a different naming scheme for your base library (E.G. AllocMemory, FreeMemory), so as to make things less confusing (doing this would make bad code look bad). AllocMemory/FreeMemory would be equivalent to malloc/free, while you would have other routines (e.g. HeapAlloc/HeapFree) for allocating from an arbitrary heap.
I already essentially do this, but it seems like it would be better to be able to use malloc/free instead of AllocMemory/FreeMemory (for me, samalloc and safree) if they are actually the same functions.

I guess the better example of this kind of problem is in my string manipulation functions. Both layers use string manipulation extensively, particularly the I/O subsystems, but <string.h> is in the upper layer. I have a bunch of architecture-optimized functions in the lower layer, but they are not as complete as the ones in the higher layer, so not only do I have effectively two copies of the same string functions, but they are also frequently inconsistent.
Owen wrote:I think there are two issues you are experiencing:

* The presumption that your library should be low level, and the C runtime low level; to my eyes, there should be high(er) level things in the OS library. libc should be something that linking against is optional - particularly for apps written in other languages (E.G. Pascal)
* A lack of debugging support from the kernel. I've always felt that it should be at least possible to build the kernel so that it and applications running under it can be debugged from another machine. This is particularly vital for low level (and driver) development
But if I were to port Pascal and it were to use my native API/lower level library, which is written in C, wouldn't that Pascal implementation have to be written in C and be best written in standard C so it is portable? Either way, my current design makes the upper layer interchangeable, even though my current design is giving me problems.

I guess my real question is this: should I bother keeping this layered design when it would be simpler to combine the standard and native APIs, and when the only C library that could easily be "switched in" for the upper layer is pdclib (others would be hard, because they assume a UNIX-like system)?

Re: Layered C Library

Posted: Tue Jul 20, 2010 5:43 pm
by gerryg400
The layered approach makes sense for I/O type stuff where you are presenting for example Posix (or other) API to the developer. It could sit on your native API. However, some c functions (e.g. str* and mem*) work more or less at the hardware level and have no interaction with your O/S. These functions could sit in a tall vertical slice that is available to all, next to the layered functions. So perhaps you need a horizontal division as well as a vertical one.
Just my thoughts.