Thursday, August 21, 2008

Unix Processes - From a System programmer's perspective

There are numerous funky definitions of a process. The most weird one that I came across was - " A process is the animated spirit of a program." The most common one and favourite of University lecturers is - "A program in execution". As many as 11 definitions were on offer when I entered the 5th semester. Well, to set the record straight, here's the Unix process standard definition that I unearthed - “An address space with one or more threads executing within that address space, and the required system resources for those threads.” A process entity minimally contains the following - A process id, Code section, Data Section, File descriptors. Processes also contain stack space for storing the local variables as well as function arguments. Processes also have environmental variables.

The init process on a Linux/Unix system has pid 1.

Process priority - Taken into consideration by the process scheduler. Programs which utilize CPU in smaller bursts and may be wait for user input get higher priority awarded. Programs which are CPU intensive, like perform complex calculations all the time get their priorities lowered since they hog the CPU.

1// Nice - Niceness is the property which is the measure of priority of a process.
Processes with high priority have less nice value. Processes with low priority have high nice values.

If you run nice with a process name, it increases the nice value of the process by 10.
Besides nice You can use renice and explicitly mention the nice value you want.
# nice process_name
(Increases the nice value by 10)

#renice value pid
(Sets the value "value" to pid "pid")

How do You create a process?
There are lots of ways. Let's look at the first -
2// a. system () - It takes a string and runs it as a command or a binary in a shell.
It's not very efficient obviously because it also invokes a shell i.e. another process.
So, it's the least preferable way to start a process.

We all know law of conservation of energy. Somewhat in a similar vain, You can't create a process. A new process creation is either of the following two scenarios -
- Replacement of an existing process image with a new one.
- Creating a new process image by copying an existing process image.

3// b. Replacing a process image- exec functions - There's a whole set of functions for doing this.
#include
char **environ;
int execl(const char *path, const char *arg0, ..., (char *)0);
int execlp(const char *file, const char *arg0, ..., (char *)0);
int execle(const char *path, const char *arg0, ..., (char *)0, char *const
envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);

Always remember, while putting exec-family functions in Your code, If you put anything useful
after this call, that'll not be executed, since the existing process image would be replaced by what You put inside exec*().

4// c. creating new process - Copying the process image - fork() :
--------------------------------------------
pid_t new_pid;
new_pid = fork();
switch(new_pid) {
case -1 : /* Error */
break;
case 0 : /* This is child */
break;
default : /* This is parent */
break;
}

Remember, fork () returns 0 if it's the child process and a non zero value if parent.
On failure, fork will return -1.

5// wait () system call : Called within the parent code, it'll wait till the child process stops/finishes executing. This call returns the pid of the child process that exited.
The exit status of the child can be captured using some macros.

if (pid != 0) {
int stat_val;
pid_t child_pid;
child_pid = wait(&stat_val);
printf(“Child has finished: PID = %d\n”, child_pid);
if(WIFEXITED(stat_val)) {
printf(“Child exited with code %d\n”, WEXITSTATUS(stat_val));
} else {
printf(“Child terminated abnormally\n”);
}
exit(exit_code);
}

------------- Macros on exit status of child --------------------------
WIFEXITED(stat_val) Nonzero if the child is terminated normally.
WEXITSTATUS(stat_val) If WIFEXITED is nonzero, this returns child exit code.

Why wait() is important is, supposing, you don't call wait() in the parent and the child process
finishes, the child process entry is not freed up in the process table since it is waiting its exit status to be collected by the parent process. Such a child process becomes a Zombie or a defunct process. Supposing the parent process abnormally terminates, init process becomes the parent of the child process.


6 // waitpid() : It can be used to wait for a specific child. Where it finds its most use is where you don't want parent process to be stuck for the child process to finish. You want asynchronous ness and as and when the child process terminates, the parent collects the pid. To achieve this, a particular option is passed called WNOHANG.


Tip: Always remember that if you create one child process, then the pid returned by fork()
and wait() will be the same. Otherwise, happy debugging :)

No comments: