I am learning about embedded and operating systems. I would like to know the order of control flow that occurs when a user-space C application calls a function, for example, printf().

I am slightly confused as to which order is correct:

printf() -> User-space library -> system call -> kernel driver

printf() -> User-space library -> kernel driver -> system call

printf() -> System call -> user-space library -> kernel driver

printf() -> Kernel driver -> system call -> user-space library

  • $\begingroup$ What are your thoughts? What is your current understanding? How are you thinking about this? $\endgroup$ – D.W. Dec 3 '20 at 18:54

There is no such thing as a C application. Code written in C is compiled to a binary ELF file on Linux or a .exe file on Windows.

The answer to the question is it depends if the function requires a syscall or not. If it doesn't require a syscall (on Linux):

  1. You launch the ELF executable
  2. The ELF executable is interpreted and the code and data is placed in memory. A new process takes place. All code including the Linux kernel code that the process requires is placed into memory and linked against the user code (dynamic linking).
  3. When the process calls a function, its arguments are placed on the stack, the function code runs and the function returns.

It is as simple as that for a situation when the kernel function doesn't require a syscall.

If it requires a syscall:

  1. Steps 1 and 2 above.
  2. The function is called as usual with its arguments on the stack.
  3. When the kernel function requires to access hardware (like when using printf() to print to the screen), it uses system calls.
  4. A system call is done using software interrupt 0x80 (the assembly instruction int 0x80).
  5. The CPU is interrupted, looks in the IDT (https://wiki.osdev.org/Interrupt_Descriptor_Table), determines the address of the handler and jumps to the handler.
  6. The interrupt handler for a system call contains a special instruction: SYSENTER (https://wiki.osdev.org/Sysenter). The SYSENTER instruction makes the processor switch from user mode to kernel mode. A special MSR register contains the address at which the processor will jump when executing a SYSENTER instruction. To write to this register, the OS uses the WRMSR special instruction at boot.
  7. The CPU jumps to the address specified by the MSR register. It is now in kernel mode. It runs whatever code is needed to perform the requested operation (specified in EAX by the caller). It then returns the result to the caller by using certain registers depending on the system call. To return it calls SYSEXIT.
  8. The request was fulfilled, so the user code can continue where it was.

Here is an example of Hello World app in assembly which does a syscall. Your printf() function will be compiled to something similar.

section .data
    hello:     db 'Hello world!',10    ; 'Hello world!' plus a linefeed character
    helloLen:  equ $-hello             ; Length of the 'Hello world!' string
section .text
    global _start

    mov eax,4            ; The system call for write (sys_write)
    mov ebx,1            ; File descriptor 1 - standard output
    mov ecx,hello        ; Put the offset of hello in ecx
    mov edx,helloLen     ; length of the string in edx
    int 80h              ; Call the kernel

    mov eax,1            ; The system call for exit (sys_exit)
    mov ebx,0            ; Exit with return code of 0 (no error)
    int 80h

The right answer would be

printf() -> User-space library -> system call -> kernel driver

The printf() function is placed in RAM alongside your code (and dynamically linked against it), the function is called, makes a system call which brings the processor in kernel mode using SYSENTER and then calls kernel drivers which do the required task like writing to the screen or writing to IO devices.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.