Processor - Operating Systems - Lecture notes | CMPSCI 377, Study notes of Operating Systems

Material Type: Notes; Professor: Corner; Class: Operating Systems; Subject: Computer Science; University: University of Massachusetts - Amherst; Term: Spring 2009;

Typology: Study notes

Pre 2010

Uploaded on 08/19/2009

koofers-user-yma
koofers-user-yma 🇺🇸

10 documents

1 / 2

Toggle sidebar

This page cannot be seen from the preview

Don't miss anything!

bg1
CMPSCI 377 Operating Systems Spring 2009
Lecture 5: February 10
Lecturer: Mark Corner Scribes: Bruno Silva,Jim Partan
5.1 Processes
5.1.1 Unix process API
Re-visiting the Unix process API from the end of last lecture, is the sleep(1) call necessary to allow the
child process to start? The answer is no, it is not at all necessary. In general, if you think you need to sleep
in a program, you are probably doing something wrong, and just slowing down your program. The call to
waitpid() is a blocking wait, and will first wait to let the child process start (if it hasn’t already), then will
wait until it ends.
5.1.2 Interprocess Communication
Now we consider the following questions: how can the parent process communicate with its child? Or how can
children processes communicate with other children? The exact answer depends on the exact problem being
treated, but in general we call the several different approaches to this question by the name of Interprocess
Communication (IPC).
On possibility for IPC is to use sockets. This approach is based on explicit message passing, and has the
advantage that processes can be distributed anywhere on the Internet, among several different machines.
An application designed to use sockets can fairly easily be re-deployed as a multi-server application when its
needs outgrow a single server.
Another possibility is the use of mmap, which is a hack, although a very common one. Mmap uses memory
sharing as an indirect way of communicating. The way that this works is that all processes map the same
file into a fixed memory location. In this case, however, since we have objects being read from and written
to a shared memory region, we must use some kind of process-level synchronization. Unfortunately, these
synchronization procedures are in general much more expensive than the use of threads itself.
One can also use signals; in this case, processes can send and receive integer numbers associated with
particular signal numbers. In order to be able to receive and treat these numbers, processes must first set
up signal handlers. This approach works best rare events1. In general, signals are not very useful for parallel
or concurrent programming.
The other possibility is the use of pipes. These are unidirectional communication channels which make it
possible for the output of one program to be used as input to another one, just like in Unix (for example,
when we use the pipe symbol, as in “ls |wc -l”. The advantage of using pipes is that they are easy and fast.
1Example: pro cesses treating a SIGSEGV.
5-1
pf2

Partial preview of the text

Download Processor - Operating Systems - Lecture notes | CMPSCI 377 and more Study notes Operating Systems in PDF only on Docsity!

CMPSCI 377 Operating Systems Spring 2009

Lecture 5: February 10

Lecturer: Mark Corner Scribes: Bruno Silva,Jim Partan

5.1 Processes

5.1.1 Unix process API

Re-visiting the Unix process API from the end of last lecture, is the sleep(1) call necessary to allow the child process to start? The answer is no, it is not at all necessary. In general, if you think you need to sleep in a program, you are probably doing something wrong, and just slowing down your program. The call to waitpid() is a blocking wait, and will first wait to let the child process start (if it hasn’t already), then will wait until it ends.

5.1.2 Interprocess Communication

Now we consider the following questions: how can the parent process communicate with its child? Or how can children processes communicate with other children? The exact answer depends on the exact problem being treated, but in general we call the several different approaches to this question by the name of Interprocess Communication (IPC).

On possibility for IPC is to use sockets. This approach is based on explicit message passing, and has the advantage that processes can be distributed anywhere on the Internet, among several different machines. An application designed to use sockets can fairly easily be re-deployed as a multi-server application when its needs outgrow a single server.

Another possibility is the use of mmap, which is a hack, although a very common one. Mmap uses memory sharing as an indirect way of communicating. The way that this works is that all processes map the same file into a fixed memory location. In this case, however, since we have objects being read from and written to a shared memory region, we must use some kind of process-level synchronization. Unfortunately, these synchronization procedures are in general much more expensive than the use of threads itself.

One can also use signals; in this case, processes can send and receive integer numbers associated with particular signal numbers. In order to be able to receive and treat these numbers, processes must first set up signal handlers. This approach works best rare events^1. In general, signals are not very useful for parallel or concurrent programming.

The other possibility is the use of pipes. These are unidirectional communication channels which make it possible for the output of one program to be used as input to another one, just like in Unix (for example, when we use the pipe symbol, as in “ls | wc -l”. The advantage of using pipes is that they are easy and fast.

(^1) Example: processes treating a SIGSEGV.

5-2 Lecture 5: February 10

5.2 Threads

First, remember that different processes keep their own data in distinct address spaces. Threads, on the other hand, explicitly share their entire address space with one another. Although this can make things a lot faster, it comes with the cost of making programming a lot more complicated.

In Unix/POSIX, the threads API is composed by two main calls:

  • pthread create(), which starts a separate thread;
  • pthread join(), which waits for a thread to complete.

Notice that the general syntax for using these is:

pid = pthread_create(&tid, NULL, function_ptr, argument); pthread_join(tid, &result);

Example:

void *run(void *d) { int q = ((int) d); int v = 0; for (int i=0; i<q; i++) v = v + some_expensive_function_call(); return (void *) v; }

int main() { pthread_t t1, t2; int *r1, *r2; int arg1=100; int arg2=666; pthread_create(&t1, NULL, run, &arg1); pthread_create(&t2, NULL, run, &arg2);

pthread_join(t1, (void **) &r1); pthread_join(t2, (void **) &r2);

cout << r1= << *r1 << r2= << *r2 << endl; }

Notice that the above threads maintain different stacks and different sets of registers; except for those, however, they share all their address spaces. Also notice that if you were to run this code in a 2 core machine, it would be expected that it ran roughly twice as fast as it would in a single core machine. If you ran it in a 4 core machine, however, it would run as fast as in the 2 core machine, since there would be no sufficient threads to exploit the available parallelism.