0%

CS302 Operating System Week5 Note 0x00

User space invoke system calls

E.g.

  • fork()
  • exec*()
  • wait()

Kernel space keep PCB.

When invoking a system call(memory view)

  • When running a program code of a user process.
  • As the code is in use-space memory, so the program counter is pointing to that region.
  • When the process is calling the system call "getpid()".
  • Then, the CPU switches from the user-space to the kernel-space, and reads the PID of the process from the kernel.
  • When the CPU has finished executing the "getpid()" system call
    • It switches back to the user-space memory, and continues running that program code.

When invoking a system call (CPU view)

  • user process
    • user process executing
    • call system call
  • kernel
    • trap mode bit = 0
    • execute system call
    • return mode bit = 1
  • return from system call

Process real time cost(wall-clock time)

User time VS System time

  • With the tool "time"
    • Real-time
    • user time
    • sys time

Accessing hardware costs the process more time.

  • The user time and the sys time together define the performance of an application

  • Function calls cause overhead
    • Stack pushing
  • Sys calls may cause even more
    • Sys call is from another "process" (the kernel)
    • Switching to another "process" -> context switch(will see later)

fork() inside the kernel

  • Inside kernel, processes are arranged as a doubly linked list, called the task list.

fork() in action - array of opened files?

Array of opened files contains:

Array Index Description
0 Standard Input Stream; FILE *stdin;
1 Standard Output stream; FILE *stdout;
2 Standard Error Stream; FILE *stderr;
3 or beyond Storing the files you opened. e.g., fopen(), open(), etc.
  • That's why a parent process shares the same terminal output stream as the child process.

  • Stream is just a logical object for you to read as a sequence of bytes

Working of system calls exec*()

  • exec*()l is called
  • The process returns to user-space but is executing another program

exec*() in action

  • Process 1234 invoked exec*()
  • get in kernel space
    • kernel space will operate the memory of current process:
      • local variable is cleared
      • Dynamically-allocated memory is cleared
      • Global variable is reset based on the new code
      • Code and constants are changed to the new program code.
    • The kernel will also
      • reset the register values(e.g., program counter)

working of system calls wait() and exit()

exit() kernel-view

  • The kernel frees all the allocated memory(I guess in kernel space).
  • The list of opened files are all closed.(So it's okay to skip fclosed()); though not recommended
  • Then, the kernel frees everything on the user-space memory about the concerned process, including program code and allocated memory.
  • Process ID stills in the kernel's process table
    • Why?
      • [Wiki] This entry is still needed to allow the process that started the (now zombie) process to read its exit status.
    • The status of the child is now called zombie ("terminated").
  • Last but no least, the kernel notifies the parent of the child process about the termination of its child.
  • The kernel sends a SIGCHLD signal to the parent.

Summary -- what the kernel does for exit()

  • Step(1) Clean up most of the allocated kernel-space memory (e.g., process's running time info)
  • step(2) Clean up the exit process's user-space memory
  • Step(3) Notify the parent with SIGCHLD.

wait() kernel view's registering signal handling routine

  • By default, every process does not respond to the SIGCHLD
    • i.g., the parent ignores his child unless he is waiting for the child
  • But if a process has called wait()
    • The kernel will register a signal handling routine for that process.(in kernel space)
  • When SIGCHLD comes, the corresponding signal handling routine is invoked!
  • Note: the parent is still inside the wait() system call

  • Default Handler of SIGCHLD registered by the kernel
    • Accept and remove the SIGCHLD signal
    • Destroy the child process in the kernel-space(remove it from process table, task-list, etc.)
  • The kernel
    • Deregisters the signal handling routine for the parent
    • returns the PID of the terminated child as the return value of wait()
  • The parent is ignoring SIGCHLD again

Overall - normal case

Parent's wait() after child's exit()

wait() and exit() short summary

  • exit() system call turns a process into a zombie when
    • The process call exit()
    • The process returns from main()
    • The process terminates abnormally
      • The kernel knows that the process is terminated abnormally. Hence, the kernel invokes exit() for it.
  • wait() and waitpid() are to reap zombie child processes
    • It is a must that you should never leave any zombies in the system
    • wait() and waitpid() pause the caller until
      • A child terminates/stops, OR
      • The caller receives a signal (i.e., the signal interrupted the wait())
  • Linux will label zombie process as "<defunct>"
  • To look for them
1
2

ps aux | grep defunct
1
2
3
4
5
6
7
8
9
10
11
12
13
int main(void)
{
int pid;
if( (pid = fork()) !=0 ) {
printf("Look at the status of the child process %d\n", pid);
while( getchar() != '\n' ); // "enter" here. Child process is defunct/zombie
wait(NULL);
printf("Look again!\n"); // "enter" here. Child process vanish.
while( getchar() != '\n' );
}
return 0;
}

This program requires you to type “enter” twice before the process terminates.

You are expected to see the status of the child process changes (ps aux [PID]) between the 1st and the 2nd “enter"

Calling wait() is important

  • It is not only about process execution suspension…
  • It is about system resource management.
    • A zombie takes up a PID;
    • The total number of PIDs are limited;
    • Read the limit: cat /proc/sys/kernel/pid_max
    • It is 32768.
  • What will happen if we don’t clean up the zombies?

The fork bomb

1
2
3
4
5

int main(void) {
while( fork() );
return 0;
}
  • Deliberately missing wait()
  • Do not try this on department’s machines…

The first process

  • We now focus on the process-related events
    • The kernel, while it is booting up, creates the first process – init.
  • The init process:
    • has PID = 1, and
    • is running the program code /sbin/init.
  • Its first task is to create more processes…
    • Using fork() and **exec*()**.

PRocess blossoming

  • You can view the tree with the command:
    • pstree or
    • pstree -A for the ASCII-character-only display.

Process blossoming... with orphans?

  • However, termination can happen, at any time and in any place…
    • This is no good because an orphan turns the hierarchy from a tree into a forest!
    • Plus, no one would know the termination of the orphan.

Process blossoming…with re-parent

  • In Linux
    • The init process will become the step-mother of all orphans
    • It's called re-parenting
  • In Windows
    • It maintains a forest-like process hierarchy......

Re-parenting example

1
2
3
4
5
6
7
8
9
10
11
12
13
int main(void) {
int i;
if(fork() == 0) {
for(i = 0; i < 5; i++) {
printf("(%d) parent's PID = %d\n",
getpid(), getppid() );
sleep(1);
}
}
else
sleep(1);
printf("(%d) bye.\n", getpid());
}

Output:

1
2
3
4
5
6
7
8
$ ./reparent
(1235) parent's PID = 1234
(1235) parent's PID = 1234
(1234) bye.
$ (1235) parent’s PID = 1
(1235) parent’s PID = 1
(1235) parent’s PID = 1
(1235) bye.

What had happened during re-parenting?

Background jobs

  • The re-parenting operation enables something called background jobs in Linux
    • It allows a process runs without a parent terminal/shell
1
2
3
$ ./infinite_loop &
$ exit
[ The shell is gone ]
1
2
3
4
5
6
$ ps –C infinite_loop

PID TTY
1234 ... ./infinite_loop

$ _

Process lifecycle

Process lifecycle - Ready

Process lifecycle - Running

Process lifecycle - Blocking

Process lifecycle - Interruptible wait

  • Example. Reading a file.
    • Sometimes, the process has to wait for the response from the device and, therefore, it is blocked
      • this blocking state is interruptible
      • E.g., “Ctrl + C” can get the process out of the waiting state (but goes to termination state instead).

Process lifecycle - Un-Interruptible wait

Sometimes, a process needs to wait for a resource until it really gets what it wants

Process lifecycle - return back to ready

Process lifecycle - going to die