04 - Process API - Part I

Outline

  1. What is exec()?
  2. The exec family of functions
  3. fork() and exec()

What is exec()?

The Process API

Common process operations available in Operating Systems

Notes:

Example: Run this in a terminal

less .usr/share/dict/words

Find PID of a process

ps -u

From another terminal you can sen a kill signal to the process running:

kill -SIGKILL 83358

To suspend the process (send it to the background):

kill -TSTP 83358

Send a STOP signal:

kill -STOP 83358

From Program to Process

Pasted image 20260204142137.png|300

Notes:

Process API

A process can run more than one program. The currently running process can ask that the OS load a different program into the same process.

The new program inherits some process state, such as current directory, open file handles, privileges etc.

This is done at the system level, with only four syscalls:

  1. fork()
  2. exec()—Altered carbon on Netflix!
  3. wait()
  4. exit()

Notes:

Running another program from a C/C++ program

  1. Assuming the other program's name is name.
  2. The current program makes a system call execXX("name", arglist).
  3. The kernel loads the "name" executable program from the disk into the process.
  4. The kernel copies arglist into the process.
  5. The kernel calls main(arglist) of the "name" program.
int main () {
    char *args[] = { "ls", "-l", "-a", NULL };
    printf ("=====BEFORE========\n");
    execvp (args[0], args);
    printf ("======AFTER========\n");
}
davidkebo@linux:~/code/week4/exec\$ ./a.out
=====BEFORE=======
total 32
drwxrwxr-x 2 davidkebo davidkebo 4096 Sep 12 04:25.
drwxrwxr-x 6 davidkebo davidkebo 4096 Sep 12 04:13 ..
-rwxrwxr-x 1 davidkebo davidkebo 17416 Sep 12 04:25 a.out
-rw-rw-r-- 1 davidkebo davidkebo 279 Sep 12 04:25 exec1.cpp

Notes:

What happens in exec()

Initial state of memory before hitting line: execvp("ls")

Pasted image 20260206135125.png|500

Pasted image 20260206135149.png|500

Where is the second message?

int main () {
    char *args[] = { "ls", "-l", "-a", NULL };
    printf ("=====BEFORE=======\n");
    execvp (args[0], args);
    printf ("======AFTER========\n");
}
davidkebo@linux:~/code/week4/exec$ ./a.out
=====BEFORE=======
total 32
drwxrwxr-x 2 davidkebo davidkebo 4096 Sep 12 04:25 .
drwxrwxr-x 6 davidkebo davidkebo 4096 Sep 12 04:13 ..
-rwxrwxr-x 1 davidkebo davidkebo 17416 Sep 12 04:25 a.out
-rw-rw-r-- 1 davidkebo davidkebo 279 Sep 12 04:25 exec1.cpp

Example: command interpreter I

Now we want to write our own shell, lets try it!

char command[MAX_COMMAND_LENGTH];
while (true) {
    command = read_command(stdin);
    if (command == "exit") break;
    execvp(command,...);
}

Caution: execvp() loads the executable for the new command into the process's memory. It therefore overwrites the executable for the command interpreter!

Notes:

Example: command interpreter II

char command[MAX_COMMAND_LENGTH];
    while (true) {
        command = read_command(stdin);
        if (command == "exit") break;
        if (fork() != 0) {
            /* parent - go to loop */
                ...
        } else {
            /* child */
            execvp (command, ...);
        }
    }

Notes:

Example: command interpreter (solved)

char command[MAX_COMMAND_LENGTH];
while (true) {
    command = read_command(stdin);
    if (command == "exit") break;
    if ((pid = fork()) > 0) {
        /* parent - run command in foreground, i.e., wait */
        waitpid(pid, &status, 0);
    } else {
        /* child */
        execvp(command, ...);
    }
}

Notes:

Exec family of functions

The exec functions execute a file. They replace the current process image with a new process image. Even though they are similar, there are differences between them, and each one of them receives different information as arguments.

int exedl ( const char *path, const char *arg, ... );
int execlp( const char *file, const char *arg, ... );
int execle( const char *path, const char *arg, ..., char *const envp[] );
int execv ( const char *path, char *const argv[] );
int execvp( const char *file, char *const argv[] );
int exeque( const char *path, char *const argv[], char *const envp[] );

The first three are of the form execl and accept a variable number of arguments. To use this function, we must include the <stdarg.h> header file.

The latter three are of the form execv in which case the arguments are passed using an array of (char *) where the last entry is NULL.

Differences among the seven exec functions

Pasted image 20260206141913.png|600

Relationship between the six exec functions

Pasted image 20260206141950.png|500

Notes:

Exec family of functions: execl()

execl() receives the location of the executable file as its first argument. The next arguments will be available to the file when it's executed. The last argument must be NULL.

int execl(const char *pathname, const char *arg,..., NULL)
#include <unistd.h>
int main(void) {
    const char *file = "/usr/bin/echo";
    const char *arg1 = "Hello world!";
    execl(file, file, arg1, NULL);
    return 0;
$ gcc execl.c -o execl
$ ./execl
Hello world!

Notes:

Exec family of functions: execlp()

execlp() is similar to execl(). However, execlp() uses the PATH environment variable to look for the file. Therefore, the path to the executable file is not needed.

int execlp(const char *file, const char *arg,..., NULL)
#include <unistd.h>
int main(void) {
    const char *file = "echo";
    const char *arg1 = "Hello world!";
    execlp(file, file, arg1, NULL);
    return 0;
}
$ gcc execlp.c -o execlp
$ ./execlp
Hello world!

Notes:

Exec family of functions: execle()

With execle(), we can pass environment variables to the function, and it will use them:

int execle(const char *path, const char *arg, ..., NULL, char *const envp[])
#include <unistd.h>
int main(void) {
	const char *file = "/usr/bin/printenv";
	const char *arg1 = "MAKEFILES";
	const char *const env[] = {"MAKEFILES=foo", NULL};
	execle(file, file, arg1, NULL, env);
	return 0;
}
$ gcc execle2.c -o execle2
$ ./execle2
foo

Notes:

MAKEFILES=foo printenv

Note how you can define your main function in C++:

int main(int argc, char** argv, char** envp) {
	for (char** p = envp; *p;  p++) {
		printf("$s\n", *p)
	}
}

Exec family of functions: execv()

execv() receives a vector of arguments that will be available to the executable file. In addition, the last element of the vector must be NULL.

int execv(const char *pathname, char *const argv[])
#include <unistd.h>
int main(void) {
    char *path = "/usr/bin/echo";
    char *const args[] = {"echo", "Hello world!", NULL};
    execv(path, args);
    return 0;
}
$ gcc execv.c -o execv
$ ./execv
Hello world!

Exec family of functions: execvp()

execvp() looks for the program in the PATH environment variable.

int execvp(const char *file, char *const argv[])
#include <unistd.h>
int main(void) {
	char *file = "echo";
	char *const args[] = {"echo", "Hello world!", NULL};
	execvp(file, args);
	return 0;
}
$ gcc execvp.c -o execvp
$ ./execvp
Hello world!

...

Creating new processes

The following program creates a new process by invoking the fork() system call.

It also uses system calls getpid() to get the calling process’s ID and getppid() for the parent’s ID.

The following is the output when run twice:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
    printf("Hello!! My ID is %d, my parent ID is %d.\n",getpid(), getppid());
    pid_t pid = fork();
    printf("Bye!! My ID is %d, my parent ID is %d.\n",getpid(), getppid());
    return 0;
}
davidkebo@linux:~/code/week4/forks\$ ./a.out
Hello!! My ID is 75189, my parent ID is 74907.
Bye!! My ID is 75189, my parent ID is 74907.
Bye!! My ID is 75190, my parent ID is 75189.

Pasted image 20260206143704.png|150

Notes: