Search code examples
operating-systemsystem-callsexecution

How applications remain in execution?


How an application running in an operating system remain in execution even when they have completed the task they were requested for, for example I open a text editor application, write some texts and save it, but still the application is in execution i.e. it is running. Isn't it that a program returns immediately out of the execution (i.e. it get terminated) when it has completed all the instructions. So how is this achieved ?? I am curious how application development frameworks do it. Similarly how a web server is always running, as ideally it should be.

One naive approach could be to use a loop which is always true, i.e.

while(1) {
    // perform the operations
}

I also asked it to AI, to which it suggested that maybe the applications use blocking I/O system calls like select(), poll(), or epoll_wait().

But I am not sure what it is and how things work under the hood.


Solution

  • This actually behaves differently at different levels of the computation.


    At the CPU level, unless you tell it to do something specific to avoid running (like "halt" or "call syscall 1234 which happens to be exit()", the CPU will just continue running the program's code segment. So if the compiler didn't insert an instruction to end the program, and the code segment now contains garbage after the last instruction, the CPU will execute these garbage instructions (until something happens, e.g. segfault).


    At the programming language level, if we take C for example, if you have this main():

    int main(int argc, char **argv) {
      printf("hello world\n");
      return 0;
    }
    

    then what actually happens when you link the program, is that the resulting binary contains an entry point, roughly behaving like so:

    void _start() {
      c_runtime_setup();
      int ret = main(argc, argv);
      exit(ret);
    }
    

    So the language will exit() to avoid running garbage code, but the program inside main() can also call exit() if it wants to. (For that matter, the setup phase can also exit before even entering main()).


    At the framework level, if you were to sketch their main() in pseudo code, you'd get something like (gross oversimplification):

    int main() {
      register_handlers();
      while (true) {
        event = wait_event();
        process_event(event);
      }
    }
    

    Where:

    • register_handlers() creates a map between events and event handlers.
    • wait_event() waits for some event - could be something like epoll_wait() in a web server or waiting for a keyboard/mouse input in a GUI app.
    • process_event() consults the event handler map and invokes the appropriate handler, like HTTP handlers in a web servers, or "save file to disk" method in a GUI app.

    epoll_wait() and friends are OS system calls that take a set of resources to monitor, and the OS blocks the calling thread until (at least) one of those resource has experienced an event (or an external event like timeout or signal has been raised).
    "events", as mentioned above, can be keyboard inputs, network packets, file writes, etc..

    In many of these frameworks, this "event loop" may actually be running on a dedicated thread rather than the main thread, and in many frameworks, the event handlers run (or are expected to run) on yet a separate thread.

    That means that while the event handler to save to file writes to disk is still running, the event loop is already waiting for the next event, rather than exit.

    One event that's usually registered is "user clicked the 'exit' menu item", and the handler for that would be "exit the program".
    Another event that usually exits the program is "received OS interrupt signal" (SIGINT on Unix).