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
   417   418   419   420   421   422   423   424   425   426   427