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)
- kernel space will operate the memory of current process:
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").
- Why?
- 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 |
|
1 | int main(void) |
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 |
|
- 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
orpstree -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 | int main(void) { |
Output:
1 | $ ./reparent |
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 | $ ./infinite_loop & |
1 | $ ps –C 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).
- Sometimes, the process has to wait for the response from the device and, therefore, it is blocked
Process lifecycle - Un-Interruptible wait
Sometimes, a process needs to wait for a resource until it really gets what it wants
- Doesn’t want to be “Ctrl-C” interruptible
- Un-interruptible status
- No way to signal it to wake up unless it returns itself
- Check online! The only solution is … Who set this?
- E.g., syscall call
- Why set this?
- Easier programming for lazy programmer (e.g., a driver program for a DVD drive)
- The programmer “thinks” the wait is very short and robust .
- This is one the top reasons that hang your machine / process today!
- …
- http://unix.stackexchange.com/questions/96797/what-does-the-interruptible-sleep-state-indicate
- http://stackoverflow.com/questions/767551/how-to-stop-uninterruptible-process-on-linux