Table of Contents

    Process_state_manchine


    Understanding processes in Linux is fundamental for working efficiently with the system. Processes are instances of executing programs and are a core part of any Unix-like operating system. Below is a detailed tutorial on Linux processes, including how to create and manage them using C programming language examples.

    What is a Process?

    In Linux, a process is a running instance of a program. Each process is assigned a unique Process ID (PID) and has its own address space, files, and resources. Processes can create other processes through a system call named fork().


    Process identification

    Identifying a process in C on a Linux system can be done using the getpid() and getppid() functions. These functions are used to get the process ID (PID) of the current process and the PID of the parent process, respectively.


    Getting the Current Process ID with getpid()

    The getpid() function returns the process ID of the calling process. This is useful for when you need to reference the current process programmatically.

    Example:

    #include <stdio.h>
    #include <unistd.h>
    
    int main() {
        // Get the current process ID
        pid_t pid = getpid();
    
        // Print the process ID
        printf("The current process ID is %d\n", pid);
    
        return 0;
    }
    

    code execution on linux SOC: exemple1


    Getting the Parent Process ID with getppid()

    The getppid() function returns the parent process ID of the calling process. This can be particularly useful in a child process created by fork(), to reference the PID of the process that created it.

    Example:

    #include <stdio.h>
    #include <unistd.h>
    
    int main() {
        // Fork a new process
        pid_t pid = fork();
    
        if (pid == 0) {
            // In child process
            printf("This is the child process. PID: %d, Parent PID: %d\n", getpid(), getppid());
        } else if (pid > 0) {
            // In parent process
            printf("This is the parent process. PID: %d\n", getpid());
        } else {
            // Fork failed
            fprintf(stderr, "Fork failed.\n");
            return 1;
        }
    
        return 0;
    }
    

    code execution on linux SOC: exemple2

    @startuml
    !theme toy
    
    participant "Main Process" as main
    participant "fork()" as fork
    participant "Child Process" as child
    
    main -> fork : Calls fork()
    alt pid == 0
        == Child Process ==
        fork -> child : In child process
        child -> child : printf("This is the child process. PID: %d, Parent PID: %d\n", getpid(), getppid())
        child -> main : Child process ends, return 0
    else pid > 0
        == Parent Process ==
        fork -> main : In parent process (Main Process continues)
        main -> main : printf("This is the parent process with PID: %d\n", getpid())
        main -> main : Parent process ends, return 0
    else
        == Fork Failed ==
        fork -> main : Fork failed
        main -> main : fprintf(stderr, "Fork failed.\n")
        main -> main : return 1
    end
    @enduml
    
    

    In this example, the parent process forks a child process. Both processes then print their PID and, in the case of the child, also the parent's PID. This demonstrates how to identify and differentiate processes in a C program running on Linux.


    Creating a Process with fork()

    The fork() system call is used to create a new process by duplicating the current process. The new process is called the child process, and the original process is called the parent process.

    Example:

    #include <stdio.h>
    #include <unistd.h>
    
    int main() {
        pid_t pid = fork();
    
        if (pid == 0) {
            // Child process
            printf("This is the child process with PID: %d\n", getpid());
        } else if (pid > 0) {
            // Parent process
            printf("This is the parent process with PID: %d\n", getpid());
        } else {
            // Fork failed
            fprintf(stderr, "Fork failed.\n");
            return 1;
        }
    
        return 0;
    }
    

    code execution on linux SOC: exemple3

    @startuml
    !theme toy
    
    participant "Main Process" as main
    participant "fork()" as fork
    participant "Child Process" as child
    
    main -> fork : Calls fork()
    alt pid == 0
        == Child Process ==
        fork -> child : In child process
        child -> child : printf("This is the child process with PID: %d\n", getpid())
        child -> main : Child process ends, return 0
    else pid > 0
        == Parent Process ==
        fork -> main : In parent process (Main Process continues)
        main -> main : printf("This is the parent process with PID: %d\n", getpid())
        main -> main : Parent process ends, return 0
    else
        == Fork Failed ==
        fork -> main : Fork failed
        main -> main : fprintf(stderr, "Fork failed.\n")
        main -> main : return 1
    end
    @enduml
    

    Process Synchronization with wait()

    To synchronize the parent process with the termination of its child processes, you can use the wait() system call. This call allows the parent to wait for all of its children to terminate.

    Example:

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/wait.h>
    
    int main() {
        pid_t pid = fork();
    
        if (pid == 0) {
            // Child process
            printf("Child process with PID %d\n", getpid());
        } else if (pid > 0) {
            // Parent process waits for the child to finish
            wait(NULL);
            printf("Child has finished: Parent PID %d\n", getpid());
        } else {
            // Fork failed
            fprintf(stderr, "Fork failed.\n");
            return 1;
        }
    
        return 0;
        }
    

    code execution on linux SOC: exemple4

    @startuml
    !theme toy
    
    participant "Main Process" as main
    participant "fork()" as fork
    participant "Child Process" as child
    participant "wait()" as wait
    
    main -> fork : Calls fork()
    alt pid == 0
        == child Process ==
        fork -> child : In child process
        child -> child : printf("Child process with PID %d\n", getpid())
        child -> main : Child process ends, return 0
    else pid > 0
        == Parent Process ==
        fork -> main : In parent process
        main -> wait : Calls wait(NULL)
        wait -> main : Child has finished
        main -> main : printf("Child has finished: Parent PID %d\n", getpid())
        main -> main : Parent process ends, return 0
    else
        == Fork Failed ==
        fork -> main : Fork failed
        main -> main : fprintf(stderr, "Fork failed.\n")
        main -> main : return 1
    end
    @enduml
    @enduml
    
    

    Executing a New Program with exec()

    The exec() family of functions replaces the current process image with a new process image. It is commonly used in conjunction with fork() to replace the child process's image.

    Example:

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/wait.h>
    
    int main() {
        pid_t pid = fork();
    
        if (pid == 0) {
            // Child process
            char *args[] = {"/bin/ls", "-l", NULL};
            execvp(args[0], args);
            // If execvp returns, it must have failed.
            printf("execvp failed\n");
        } else if (pid > 0) {
            // Parent process
            wait(NULL); // Wait for the child to finish
        } else {
            // Fork failed
            fprintf(stderr, "Fork failed.\n");
            return 1;
        }
    
        return 0;
    }
    

    code execution on linux SOC: exemple5

    @startuml
    !theme toy
    
    participant "Main Process" as main
    participant "fork()" as fork
    participant "Child Process" as child
    participant "wait()" as wait
    participant "execvp()" as execvp
    
    main -> fork : Calls fork()
    alt pid == 0
        == Child Process ==
        fork -> child : In child process
        child -> execvp : execvp("/bin/ls", "-l")
        execvp -> child : If execvp fails
        child -> child : printf("execvp failed\n")
        child -> child : Child process ends, return 0
    else pid > 0
        == Parent Process ==
        fork -> main : In parent process
        main -> wait : wait(NULL) for child to finish
        wait -> child : Waiting...
        child -> wait : Child has finished
        wait -> main : Child has finished
        main -> main : Parent process ends, return 0
    else
        == Fork Failed ==
        fork -> main : Fork failed
        main -> main : fprintf(stderr, "Fork failed.\n")
        main -> main : return 1
    end
    @enduml
    
    

    Zombie Processes :trollface:

    Zombie processes occur when a child process has completed its execution but still has an entry in the process table. This happens because the operating system needs to keep some information about the process so the parent process can retrieve the child's exit status. A process becomes a zombie, or "defunct", if the parent process does not call wait() (or a related method) to read its exit status. The entry is removed from the process table once the exit status is read, allowing the system to fully clean up the resources associated with the child process.


    How Zombie Processes Occur

    1. Process Termination: When a child process finishes execution, it becomes a zombie until its parent reads its exit status.
    2. Parent Not Calling wait(): If the parent process fails to call wait(), the child remains in a zombie state, occupying a slot in the process table but not executing or consuming other system resources besides that.

    Example Demonstrating a Zombie Process

    This C program demonstrates the creation of a zombie process by intentionally not calling wait() in the parent process after the child process terminates.

    #include <stdlib.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <stdio.h>
    
    int main() {
        pid_t pid = fork();
    
        if (pid > 0) {
            // Parent process
            printf("Parent process, PID: %d\n", getpid());
            printf("Child PID: %d\n", pid);
            printf("Parent sleeping for 30 seconds...\n");
            sleep(30); // Sleep to simulate delay in calling wait(), leaving child as zombie
        } else if (pid == 0) {
            // Child process
            printf("Child process, PID: %d\n", getpid());
            exit(0); // Child exits immediately
        } else {
            // Fork failed
            fprintf(stderr, "Fork failed.\n");
            return 1;
        }
    
        return 0;
    }
    

    code execution on linux SOC: exemple6

    @startuml
    !theme toy
    
    participant "Main Process" as main
    participant "fork()" as fork
    participant "Child Process" as child
    participant "sleep()" as sleep
    
    main -> fork : Calls fork()
    alt pid > 0
        == Parent Process ==
        fork -> main : In parent process
        main -> main : printf("Parent process, PID: %d\n", getpid())
        main -> main : printf("Child PID: %d\n", pid)
        main -> main : printf("Parent sleeping for 30 seconds...\n")
        main -> sleep : sleep(30)
        sleep -> main : After 30 seconds
    else pid == 0
        == Child Process ==
        fork -> child : In child process
        child -> child : printf("Child process, PID: %d\n", getpid())
        child -> child : exit(0)
    else
        == Fork Failed ==
        fork -> main : Fork failed
        main -> main : fprintf(stderr, "Fork failed.\n")
        main -> main : return 1
    end
    @enduml
    
    

    Observing a Zombie Process

    While the parent process is sleeping (and before it exits, if it does not call wait()), you can observe the zombie process by running the ps command in another terminal:

    ps aux | grep Z
    

    This command filters processes to show those with a status of 'Z', which stands for "zombie". You will see the child process listed as a defunct process.


    Handling Zombie Processes

    To prevent processes from becoming zombies, the parent process must call wait() or waitpid() to collect the exit status of its terminated children. Alternatively, if a parent process exits before its children, the children are adopted by the init process (PID 1), which periodically calls wait() to clean up any zombies it inherits.


    Process Termination

    A process can terminate normally by returning from its main function, calling exit(), or being terminated by another process (e.g., with a signal).


    Exiting a Process Example:

    #include <stdlib.h>
    
    int main() {
        // Terminate the process
        exit(0);
    }
    

    Release the resources of child processes asynchronously

    The SIGCHLD signal is used in Unix operating systems to indicate to a parent process that one of its child processes has finished, has been interrupted, or has resumed after being interrupted. By default, SIGCHLD is ignored, meaning that the parent process does not get notified when its child processes terminate. However, a program can choose to handle SIGCHLD to get notified about these events and take appropriate actions, such as using wait() to collect the child's termination status and thus prevent zombie processes.

    Handling SIGCHLD is particularly useful in concurrent programs where the parent process spawns multiple child processes and needs to clean them up properly when they terminate, without blocking on a wait() call for any specific child.


    Example: Using SIGCHLD to Handle Child Process Termination

    The following example demonstrates how a parent process can use SIGCHLD to be notified when a child process terminates and then clean up the zombie process by calling wait() within the signal handler.

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/wait.h>
    #include <signal.h>
    
    void sigchld_handler(int signum) {
        // Wait for all children that have terminated, non-blocking call
        while (waitpid(-1, NULL, WNOHANG) > 0) {
            // Child was cleaned up
        }
    }
    
    int main() {
        // Set up the SIGCHLD handler
        struct sigaction sa;
        sa.sa_handler = sigchld_handler; // Pointer to our handler function
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; // Restart functions if interrupted by handler
        if (sigaction(SIGCHLD, &sa, NULL) == -1) {
            perror("sigaction");
            return 1;
        }
    
        // Create a child process
        pid_t pid = fork();
        if (pid == 0) {
            // Child process
            printf("Child process PID: %d\n", getpid());
            // Child exits immediately
            exit(0);
        } else if (pid > 0) {
            // Parent process
            printf("Parent process PID: %d, child PID: %d\n", getpid(), pid);
            printf("Parent waiting for child to terminate...\n");
            // Parent does some work, not blocking on wait()
            sleep(10); // Simulate doing some work
            printf("Parent finished work.\n");
        } else {
            // Fork failed
            fprintf(stderr, "Fork failed.\n");
            return 1;
        }
    
        return 0;
    }
    

    code execution on linux SOC: exemple7

    @startuml
    !theme toy
    
    participant "Main Process" as main
    participant "sigaction()" as sigaction
    participant "fork()" as fork
    participant "Child Process" as child
    participant "sigchld_handler()" as handler
    participant "sleep()" as sleep
    
    main -> sigaction : Set up SIGCHLD handler
    sigaction -> main : Handler set
    
    main -> fork : Create a child process
    alt pid == 0
        == Child Process ==
        fork -> child : In child process
        child -> child : printf("Child process PID: %d\n", getpid())
        child -> child : exit(0)
        child -> handler : SIGCHLD sent to handler
        handler -> handler : waitpid(-1, NULL, WNOHANG) > 0
        handler -> handler : Child cleaned up
    else pid > 0
        == Parent Process ==
        fork -> main : In parent process
        main -> main : printf("Parent process PID: %d, child PID: %d\n", getpid(), pid)
        main -> main : printf("Parent waiting for child to terminate...\n")
        main -> sleep : sleep(10)
        sleep -> main : After 10 seconds
        main -> main : printf("Parent finished work.\n")
    else
        == Fork Failed ==
        fork -> main : Fork failed
        main -> main : fprintf(stderr, "Fork failed.\n")
        main -> main : return 1
    end
    @enduml
    
    

    How It Works:

    • Signal Handler Setup: The sigaction struct and sigaction() function are used to specify how the SIGCHLD signal should be handled. The handler function sigchld_handler() is defined to call waitpid() with the WNOHANG option. This means waitpid() will return immediately if no child has exited, allowing the parent to clean up zombie processes without blocking.
    • Child Process Creation: The program creates a child process using fork(). The child process exits immediately, which generates a SIGCHLD signal.
    • Signal Handling: Upon receiving SIGCHLD, the parent process's signal handler is invoked, which calls waitpid() to clean up the zombie process created by the exiting child.
    • Non-blocking Behavior: The parent process continues its execution (simulated by sleep(10)) without being blocked by child processes waiting to be cleaned up.

    This approach allows the parent process to be promptly informed about child process terminations and efficiently clean up without dedicating specific code in the main execution flow to wait for child process termination, thus improving the concurrency and responsiveness of the application.


    Conclusion

    These examples and explanations should give you a solid foundation in managing processes in Linux using C. Remember, processes are a critical part of Linux and understanding how to create, manage, and synchronize them is key to developing efficient applications.

    📝 Article Author : SEMRADE Tarik
    🏷️ Author position : Embedded Software Engineer
    🔗 Author LinkedIn : LinkedIn profile

    Comments