Page 422 - DCAP103_Principle of operating system
P. 422
Unit 14: Case Study of Linux Operating System
In order to better understand the unique capabilities provided by the Linux model, we start Notes
with a discussion of some of the challenging decisions present in multithreaded systems. The
main issue in introducing threads is maintaining the correct traditional UNIX semantics. First
consider fork. Suppose that a process with multiple (kernel) threads does a fork system call.
Should all the other threads be created in the new process? For the moment, let us answer
that question with yes. Suppose that one of the other threads was blocked reading from the
keyboard. Should the corresponding thread in the new process also be blocked reading from
the keyboard? If so, which one gets the next line typed? If not, what should that thread be
doing in the new process? The same problem holds for many other things threads can do. In
a single-threaded process, the problem does not arise because the one and only thread cannot
be blocked when calling fork. Now consider the case that the other threads are not created
in the child process. Suppose that one of the not-created threads holds a mutex that the
one-and-only thread in the new process tries to acquire after doing the fork. The mutex will
never be released and the one thread will hang forever. Numerous other problems exist too.
There is no simple solution.
File I/O is another problem area. Suppose that one thread is blocked reading from a file and
another thread closes the file or does an lseek to change the current file pointer. What happens
next? Who knows? Signal handling is another thorny issue. Should signals be directed at a
specific thread or at the process in general? A SIGFPE (floating-point exception) should probably
be caught by the thread that caused it. What if it does not catch it? Should just that thread be
killed, or all threads? Now consider the SIGINT signal, generated by the user at the keyboard.
Which thread should catch that? Should all threads share a common set of signal masks? All
solutions to these and other problems usually cause something to break somewhere. Getting
the semantics of threads right (not to mention the code) is a nontrivial business. Linux supports
kernel threads in an interesting way that is worth looking at. The implementation is based on
ideas from 4.4BSD, but kernel threads were not enabled in that distribution because Berkeley
ran out of money before the C library could be rewritten to solve the problems discussed above.
Historically, processes were resource containers and threads were the units of execution. A
process contained one or more threads that shared the address space, open files, signal handlers,
alarms, and everything else. Everything was clear and simple as described above. In 2000, Linux
introduced a powerful new system call, clone that blurred the distinction between processes and
threads and possibly even inverted the primacy of the two concepts. Clone is not present in any
other version of UNIX. Classically, when a new thread was created, the original thread(s) and
the new one shared everything but their registers. In particular, file descriptors for open files,
signal handlers, alarms, and other global properties were per process, not per thread. What
clone did was to make it possible for each of these aspects and others to be process specific or
thread specific is called as follows:
pid = clone(function, stack_ ptr, sharing_ flags, arg);
The call creates a new thread, either in the current process or in a new process, depending
on sharing_ flags. If the new thread is in the current process, it shares the address space with
existing threads and every subsequent write to any byte in the address space by any thread is
immediately visible to all the other threads in the process. On the other hand, if the address space
is not shared, then the new thread gets an exact copy of the address space, but subsequent writes
by the new thread are not visible to the old ones. These semantics are the same as POSIX fork.
In both cases, the new thread begins executing at function, which is called with arg as its only
parameter. Also in both cases, the new thread gets its own private stack, with the stack pointer
initialized to stack_ ptr. The sharing flags parameter is a bitmap that allows a much finer grain
of sharing than traditional UNIX systems. Each of the bits can be set independently of the
other ones, and each of them determines whether the new thread copies some data structure
LOVELY PROFESSIONAL UNIVERSITY 415