Saturday, August 23, 2008

Linux / Unix Multi-threading - System programming.

Creating Multiple Threads.

1. pthread_create( ): pthread library is being used for achieving multi-threading in Linux/Unix system programming. Always remember to define the flag -D_REENTRANT during cc/gcc compile. It brings in the re-entrant versions of the libraries into linking. Another thing is, You have to include the flag -lpthread during compilation so that the pthread library functions come into affect.

pthread_create has the following params -
a. param 1 - pthread_t thread_id
b. param 2 - pthread_attr_t * attr ( Thread attribute. )
c. param 3 - the start function for the thread.
d. param 4 - a void * which will be passed to the thread start function as a parameter.

2. pthread_join( ) : pthread_join is like wait( ) for processes. By calling pthread_join( ), the caller will wait for the thread created to terminate.

It takes two parameters.
a. thread-id of the thread to which to join.
b. return value from the thread, it's a double pointer.

3. pthread_exit( ) : It terminates the calling thread. It's implicitly called when the start function for a thread finishes.

4. pthread_detach ( ) and pthread_rejoin ( ) : If two threads don't require synchronization, pthread_detach is called and the threads can rejoin by calling pthread_rejoin.

5. Thread attributes - First You have to initialize the attributes and in the end You have to destroy the attribute once You are done. There are lots of things you can do by working with the thread attributes. Like You can alter the scheduling policy, calculation of the scheduling units and lots of other things like create threads in detached state, specifying stack size etc.

6. pthread_cancel ( ) - If one thread has to ask another thread to terminate, then use pthread_cancel ( ). It acts like a signal which terminates the called thread.

Thursday, August 21, 2008

OS - The most basic definition.

The simplest way that you could possibly define an OS, is a program that controls and governs the hardware, while also providing a platform for the applications to learn. So, clearly there are two subparts, the Kernel that deals with hardware and the application execution environment support.

sigaction() - Handle a signal being a system programmer.

sigaction( ) - Sigaction provides a robust signal handling interface. It offers a lot more granularity, ability to customize and control and hence is the preferred interface now- a-days.

At the heart of sigaction is a structure struct sigaction containing atleast the following elements.

void (*) (int) sa_handler /* function, SIG_DFL or SIG_IGN
sigset_t sa_mask /* signals to block in sa_handler
int sa_flags /* signal action modifiers

To handle signals using sigaction( ), it's a two step process.
Step 1 - Populate the sigaction structure.
Have your sa_handler assigned to the signal hander function name.
Mostly you'd set sa_flags to Null. I've not used them.
sa_mask is for blocking any signal in the sa_handler. The important thing about using it is that once signal handling is done, You have to reset it. By default it doesn't get reset.
sigemptyset ( ) is a helper function that You can use to initialize it to block no signals.

Step 2 - Register the signal handling details with sigaction.

Basically, in sigaction ( ), You pass three parameters.
Param-1 : int signal_number
Param-2 : const struct *act : This is the pointer which should point to the above populated structure. When a signal arrives, first the signal mask provided in the act structure would be set, then the signal handler function gets called.
Param-3 : struct *oact :The signal hadling action of the last signal caught is populated in it.
I've kept this parameter NULL. Mostly You'll also keep it NULL.

We are basically done with sigaction. However, there are some notable details.
sa_flags : There are some important flags -
SA_RESTART - If a system call or function has stopped due to arrival of the signal, after signal handling is over, the function will be restarted rather than returning with error EINTR.
SA_RESETHAND - Once the signal is caught, the default behaviour of the signal will be restored. i.e. the signal handler registered will be cleared.

SA_NODEFER- Don't add the signal to the signal mask while handling it. Generally, when a signal is getting serviced, the signal would be added to the signal mask so that the signal handling in the middle of execution is not corrupted. However, if You want this behaviour to change and say, your handler to be capable of serving parallel signals at the same time, then use this mask. However, to achieve it, the signal handling code has to be re-entrant.

An important element of Signal handling is using the Signal Sets.
They are used to define the behaviour of a process on receipt of a signal.

sigemptyset( ) - Initialize the signal set to be empty.
sigismember( ) - Find out whether a particular signal is part of the signal set.
sigaddset( ) - Add signal to the set.
sigdelset( ) - Delete the signal from the set.
sigfillset( ) - Fill the set with all the signals.
sigprocmask() - For the signal mentioned, the signal mask is set and the old mask is written.



Well, this completes the signal hadling.

Singals - A system programmer's perspective

First things first.
1. You raise a signal - i.e - Send a signal.
2. You catch a signal -i.e- Your signal handler handles the signal.

Well most people like to define signals as software interrupts. I'm not even going into that muddle.
But it's the most erroneous definition possible. First of all, software interrupts are different entities and are quite different. Signals though have some similarities with interrupts.

Signals are events within the Linux/Unix system which causes your registered handler to be called.
If you don't have a handler registered, your program is going to get killed. That's all.

But yes, signals can be largely looked as software entities. A process can send signal to another process.

SIGKILL Kill (can’t be caught or ignored.)
SIGSTOP Stop executing. (Can’t be caught or ignored.)

Well, as you can see, the above two signals are special.

1// signal () - It's the call which registers your signal handler with the OS.
As you can expect, it takes two arguments.
arg 1 - signal num - The signal number for which you want the hadler to respond.
arg 2 - The signal hadler function pointer.

2//kill() - This is the function which is used to send a signal to a process.
As you can expect, it also takes two parameters.
param 1 - pid - pid of the process to which to send the signal.
param 2 - sig num - The signal number.

3// alarm () - This is a functionality which is used to schedule a signal after specified number of
seconds. The signal received is always SIGALRM.

However simple signal() interface might look, it's not now-a-days used by programmers.
People prefer sigaction() now-a-days.

My next post would deal with sigaction because it's a topic by itself.

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 :)

Random Learnings

As I keep browsing and reading things from time to time, these are the things which I come across.

1. cflow - Cflow helps you to get a function call trace or a function call graph in a .c file or a collection of source files. So, If you get a new project and there's a lot of code in it or say many files, what you can do is, get a call graph and get an idea in terms of funcgtions, what's happening. Obviously, there's no comparison to getting a function spec or a design doc, but since it's very difficult to get these things in a product companies, it might just help.
www.gnu.org/software/cflow/

2. ctags - Well, every body knows ctags. You can use ctags as a cross reference kind of a thing. Supposing, You see a function in your code and want to know where it's defined, as in, have a look at what that function does from within your code, ctags is helpful. I personally always used cscope for such things, but will give it a try too.
CTRL + ] for jumping to the symbol.
CTRL + T for jumping back from the symbol.
http://ctags.sourceforge.net/ctags.html

3. Profiling your program (prof/gprof) - This is done to analyse performance issues of your program. If you wanted to find out where your program is spending time, you can get your program monitored using profiling. You can use prof/gprof for this. Doing it requires the following -
# gcc -pg -o prog prog.c
The pg flag is for gprof.
Now, when You run the program, the program will be profiled.
# ./prog.c
It will produce a monitoring file in your current directory, which will tell you in terms of number of seconds, where your program spent time (Like I/O, mcopy etc.)
# ls -l will show a file called gmon.out, it may be mon.out for prof.
This will show the time split like follows -

 %   cumulative   self              self     total          
time seconds seconds calls ms/call ms/call name
33.34 0.02 0.02 7208 0.00 0.00 open
16.67 0.03 0.01 244 0.04 0.12 offtime
16.67 0.04 0.01 8 1.25 1.25 memccpy
16.67 0.05 0.01 7 1.43 1.43 write
16.67 0.06 0.01 mcount
0.00 0.06 0.00 236 0.00 0.00 tzset
0.00 0.06 0.00 192 0.00 0.00 tolower
0.00 0.06 0.00 47 0.00 0.00 strlen
0.00 0.06 0.00 45 0.00 0.00 strchr
0.00 0.06 0.00 1 0.00 50.00 main
0.00 0.06 0.00 1 0.00 0.00 memcpy
0.00 0.06 0.00 1 0.00 10.11 print
0.00 0.06 0.00 1 0.00 0.00 profil
0.00 0.06 0.00 1 0.00 50.00 report
http://www.cs.utah.edu/dept/old/texinfo/as/gprof.html#SEC1


3. Memory Debugging Tools (Electric fence/valgrind) - Often times, even though you exceed the memory boundary allocated by you, you still don't get segmentation fault. This is because, the amount of memory you asked for, you might have been allocated little bit extra to align with page boundary and stuff. These kind of errors can be easily detected with Electric fence and valgrind.

To use electric fence, do the following -
# gcc prog.c -lefence

#./a.out
It'll throw segmentation fault wherever applies.
http://linux.maruhn.com/sec/electricfence.html (Here's the distro)

valgrind in my opinion is a more advanced tool.
You run your program under valgrind and it gives info about
access violation errors as well as memory leaks.
# valgrind --leak-check=yes -v prog.c
It'll give you verbose output describing a lot of stuff.
http://valgrind.org/