Process Creation in Linux

July 10, 2021 0 Comments

Process Creation in Linux

Fork:

Process Creation in Linux, fork() creates a new process by duplicating the calling process. A fork creates a new process or child process exactly the same as its parent process by duplicating address space in user mode and PCB in kernel mode because of this duplication whatever the instruction appears after the fork call is executed by both parent and child.

#include <unistd.h>
pid_t fork(void);

On success, the PID of the child process returns in the parent, and 0 returns in the child. On failure, -1 returns to the parent, no child process is created and errno is set appropriately.

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
        pid_t pid;

        pid = fork();

        switch(pid){
                case 1:
                        perror("fork");
                        exit(1);

                case 0:
                        printf("child pid :%u\n",getpid());
                        exit(1);

                default:
                        printf("parent pid :%u\n",getpid());
                        break;

        }

        return 0;
}

when a parent process terminates the child process can still continue to run even if the parent process terminates. If the parent process terminates init will immediately become a parent for the child process.

When a process terminates after executing all the instructions in the code segment it’s put into a state called an exit state. A process in exit state can’t context for CPU resources allocates for that process is not released or free till the parent collects the exit state.

It always the job of the parent to collect the exit state of its child so that the resources allocated for the child in both user and kernel space are released.

Collecting the exit state of a child process:

The parent process can collect the exit state of a child’s process using any one of the following methods.

  1. Synchronous cleaner
  2. Asynchronous cleaner
  3. Auto cleaner

Synchronous cleaner:

In this method, the parent process will block itself until the child process terminates as and when the child terminates parent will collect the exit state of its child and instructs the kernel to release the resources of the child process. This can achieve using the following interfaces.

#include <sys/wait.h>
pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);

The wait() system call suspends execution of the calling thread until one of its children terminates. The call wait(&wstatus) is equivalent to:

waitpid(-1, &wstatus, 0);

The waitpid() system call suspends execution of the calling thread until a child specified by pid argument has changed state.

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include <sys/wait.h>
int main()
{
        pid_t pid;
        int exit_stat;
        int tri_pid;
        pid = fork();

        switch(pid){
                case 1:
                        perror("fork");
                        exit(1);
                case 0:
                        printf("In child :%u\n",getpid());
                        exit(1);
                default:
                        tri_pid = wait(&exit_stat);
                        if(tri_pid < 0)
                                perror("wait");
                        printf("in parent :%u\n",getpid());
                        printf("exit state of child : %d\n",WEXITSTATUS(exit_stat));
                        break;
        }
        return 0;
}

Asynchronous cleaner:

This method requires the parent process to register a signal handler for SIGCHLD. Process manager delivers SIGCHLD to parent process whenever child process is terminated, stopped, continued.

SA_NOCLDSTOP:

If signum is SIGCHLD, do not receive a notification when child processes stop (i.e., when they receive one of SIGSTOP, SIGTSTP, SIGTTIN, or SIGTTOU) or resume (i.e., they receive SIGCONT) (see wait(2)). This flag is meaningful only when establishing a handler for SIGCHLD.

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int children;

void cleanup(int signo)
{
        printf("cleanup child resource\n");
        wait(&children);
}
int main()
{
        struct sigaction act;
        pid_t cpid;
        cpid = fork();
        if(cpid == 0){
                printf("Child pid %d\n", getpid());
                exit(1);
                //sleep(1);
        }
        else{
                act.sa_flags = SA_NOCLDSTOP;
                act.sa_handler = cleanup;
                if(sigaction(SIGCHLD,&act,NULL) == -1);
                        perror("sigaction");
                printf("Parent pid %d\n", getpid());
                while(1) {
                        printf("child exit status = %d \n",WEXITSTATUS(children));
                        sleep(1);
                }
        }
        return 0;
}

Auto cleaner:

Terminates child process can be set to automatically destroyed without any further instruction from the parent. This requires the parent process to enable the default handler for SIGCHLD with the flag SA_NOCLDWAIT. Auto clean-up is recommended to be applied only when child exit status is not important to a parent.

SA_NOCLDWAIT

If signum is SIGCHLD, do not transform children into zombies when they terminate. See also waitpid(2). This flag is meaningful only when establishing a handler for SIGCHLD, or when setting that signal’s disposition to SIG_DFL.

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int children;

int main()
{
        struct sigaction act;
        pid_t cpid;
        cpid = fork();
        if(cpid == 0){
                printf("Child pid %d\n", getpid());
                exit(1);
        }
        else{
                act.sa_flags = SA_NOCLDWAIT;
                act.sa_handler = SIG_DFL;
                if(sigaction(SIGCHLD,&act,NULL) == -1);
                        perror("sigaction");
                printf("Parent pid %d\n", getpid());
                while(1) {
                        printf("child exit status = %d \n",WEXITSTATUS(children));
                        sleep(1);
                }
        }
        return 0;
}

Copy on write:

Under Linux, fork() is implemented using copy-on-write pages, so the only penalty that it incurs is the time and memory required to duplicate the parent’s page tables, and to create a unique task structure for the child.

when a child process is creates using fork API initially both parent and child will point to the same address space. As a when write operation happens on any data in address space initiates parent or child will enter in creating a copy of that page in which that data resides to the child.

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include <sys/wait.h>
int main()
{
        pid_t pid;
        int a = 10;
        pid = fork();
        switch(pid) {
                case -1:
                        perror("fork");
                        exit(1);

                case 0:
                        printf("child pid %d\n",getpid());
                        a = 20;
                        printf("child: a  %d\n",a);
                        exit(1);
                default:
                        wait(NULL);

                        printf("parent pid %d\n",getpid());
                        printf("parent: a %d\n",a);
                        break;
        }
        return 0;
}

Pread and Pwrite:

When opening a file before the fork, they can read the file in the child process and close the file in the child process, after you can read the parent process they will read a file. In the child process how many characters read, after the next character the parent process will read. To achieve this we have to use pread() and pwrite().

#include <unistd.h>
ssize_t pread(int fd, void *buf, size_t count, off_t offset);
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);

pread() reads up to count bytes from file descriptor fd at offset (from the start of the file) into the buffer starting at buf. The file offset is not changed.

pwrite() writes up to count bytes from the buffer starting at buf to the file descriptor fd at offset. The file offset is not changed. The file referenced by fd must be capable of seeking.

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include <sys/wait.h>
#include<fcntl.h>

int main()
{
        int fd,ret;

        char buf[20];

        pid_t pid;
        off_t offset = 0;
        fd = open("./test.file",O_RDWR);
        if(fd < 0){
                perror("open");
                exit(1);
        }
        printf("new file descriptor :%d\n",fd);

        pid = fork();

        if(pid == 0){
                ret = pwrite(fd,"Welcome to India",16,offset);
                if(ret < 0)
                        perror("child write:");

                ret = pread(fd,buf,4,offset);
                if(ret < 0)
                        perror("child read:");
                buf[ret] = '\0';
                printf("In child:%s\n",buf);

                close(fd);
                ret = pread(fd,buf,4,offset);
                if(ret < 0)
                        perror("child read:");
        }
        else{
                wait(NULL);
                ret = pread(fd,buf,10,offset);
                if(ret < 0)
                        perror("parent read:");
                buf[ret] = '\0';
                printf("In parent:%s\n",buf);
        }

        return 0;
}

Vfork:

#include <unistd.h>
pid_t vfork(void);

vfork just like fork creates a child process of the calling process. vfork() is a special case of clone(2). It is used to create new processes without copying the page tables of the parent process. vfork() differs from fork(2) in that the calling thread is suspended until the child terminates. There are no copy-on-write pages on vfork().

#include<stdio.h>
#include <sys/types.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
        int a = 10;
        pid_t pid;
        pid = vfork();
        switch(pid) {
                case -1:
                        perror("vfork");
                        exit(1);
                case 0:
                        printf("child message %d\n",getpid());
                        a = 20;
                        printf("the a value:%d\n",a);
                        exit(1);

                default:
                        printf("parent message %d\n",getpid());
                        printf("the a value:%d\n",a);
                        break;
        }
        return 0;
}

Clone:

#define _GNU_SOURCE
#include <sched.h>
int clone(int (*fn)(void *), void *stack, int flags, void *arg, ...
                 /* pid_t *parent_tid, void *tls, pid_t *child_tid */ );

Linux threads API provides a function clone that can be used to create a lightweight process. clone() the function is used to run a particular function in a separate thread other than the calling process. A lightweight process can share virtual address space with the parent and register with the kernel using PCB. the lightweight process consumes fewer resources since it can share virtual address space with the parent process.

#define _GNU_SOURCE
#include<sched.h>
#include<stdio.h>
#include<stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#define STACK_SIZE 8192
int child()
{
        printf("Child pid : %d\n", getpid());
        return 0;
}

int main()
{
        void *child1;
        int exit_stat;
        child1 = malloc(STACK_SIZE);
        if(!child1) {
                perror("Malloc Failed");
                return -1;
        }
        if(clone(&child,(char *)child1 + STACK_SIZE,CLONE_VM|SIGCHLD,0) < 0) {
                perror("Clone Failed");
                return -1;
        }
        wait(&exit_stat);
        printf("Parent pid : %d\n", getpid());
        free(child1);
        return 0;

}

Share This: