Today, you can definitely write an OS in C because you have compilers readily available on some OSes. You just have to decide with what executable format you are going to go and then parse it from a bootloader like a UEFI bootloader which has to be written in C as well. Linux is compiled to an ELF executable which is then compressed. Linux follows the multiboot specification (https://www.gnu.org/software/grub/manual/multiboot/multiboot.html) which allows one bootloader (GRUB) to boot the Linux kernel by following the specification.
Am I right in thinking that fork, malloc, etc. are wrapper methods that call the Kernel?
You are quite right. For example, when you write a C app on Linux, you are probably using gcc to compile your app. gcc comes with glibc which is the library implementation of the C standard on Linux. You can look at the source code of glibc (https://github.com/lattera/glibc) to look at the implementation.
For example, malloc is in malloc/malloc.c. Its definition is a one liner like strong_alias (__libc_malloc, malloc) (https://code.woboq.org/userspace/glibc/malloc/malloc.c.html#malloc). 904
I didn't study the source code much but I think this means that a malloc call is a __libc_malloc call. The link I provided is good because it has the function definitions to the right that you can click on to go to the line.
__libc_malloc definition ``` void * __libc_malloc (size_t bytes) { mstate ar_ptr; void victim; void (hook) (size_t, const void ) = atomic_forced_read (__malloc_hook); if (__builtin_expect (hook != NULL, 0)) return (*hook)(bytes, RETURN_ADDRESS (0));
if USE_TCACHE
/ int_free also calls request2size, be careful to not pad twice. / size_t tbytes; checked_request2size (bytes, tbytes); size_t tc_idx = csize2tidx (tbytes); MAYBE_INIT_TCACHE (); DIAG_PUSH_NEEDS_COMMENT; if (tc_idx < mp_.tcache_bins /&& tc_idx < TCACHE_MAX_BINS/ / to appease gcc / && tcache && tcache->entries[tc_idx] != NULL) { return tcache_get (tc_idx); } DIAG_POP_NEEDS_COMMENT;
endif
if (SINGLE_THREAD_P) { victim = _int_malloc (&main_arena, bytes); assert (!victim || chunk_is_mmapped (mem2chunk (victim)) || &main_arena == arena_for_chunk (mem2chunk (victim))); return victim; } arena_get (ar_ptr, bytes); victim = _int_malloc (ar_ptr, bytes); / Retry with another arena only if we were able to find a usable arena before. / if (!victim && ar_ptr != NULL) { LIBC_PROBE (memory_malloc_retry, 1, bytes); ar_ptr = arena_get_retry (ar_ptr, bytes); victim = _int_malloc (ar_ptr, bytes); } if (ar_ptr != NULL) __libc_lock_unlock (ar_ptr->mutex); assert (!victim || chunk_is_mmapped (mem2chunk (victim)) || ar_ptr == arena_for_chunk (mem2chunk (victim))); return victim; } ```
__libc_malloc calls _int_malloc which does the main work. It is very long so you can browse it on the link I provided. It is defined as: /* ------------------------------ malloc ------------------------------ */ static void * _int_malloc (mstate av, size_t bytes) I'm pretty sure that this is the malloc you are looking for. In _int_malloc there are these lines: /* There are no usable arenas. Fall back to sysmalloc to get a chunk from mmap. */ if (__glibc_unlikely (av == NULL)) { void *p = sysmalloc (nb, av); if (p ! 268