Processes and Threads

advertisement
Processes and Threads
CSCI 156
Project 2 Server
• How would you write it?
– Multiple connections on multiple ports
– Communication between input handlers and
output generators
• Some Options
– Multiple processes
– Multiple threads
– One monolithic program
Multiple Processes
pid_t fork();
Child process has value of 0 for the returned pid
Parent has process id of the child
int pid = fork();
if(pid)
{
printf("I'm the parent! pid=%d\n", pid);
}
else
{
printf("I'm just a baby! pid=%d\n", pid);
}
This is frequently used to open a new program with exec:
fork then have the child exec.
Multiple Processes (cont.)
int execve(const char *filename, char *const argv [], char *const
envp[]);
execve() runs the program at path “filename”, with arguments argv and
optional environment variables in envp. argv[0] must be the same as
filename.
pid_t wait(int *status);
wait() blocks until a child process exits. It then returns the child pid, and
fills the status variable with the exit return code of the child.
pid_t waitpid(pid_t pid, int *status, int options);
waitpid() lets you specify a child pid to wait for, and also allows options to
be passed
Multiple Processes (cont.)
int pid, status, error;
char *name[2], *foo;
foo = (char*) malloc(20 * sizeof(char));
printf("give us a string!\n");
scanf("%s", foo);
pid = fork();
if(pid)
{
printf("I'm the parent! pid=%d\n", pid);
if(waitpid(pid, &status, 0) == pid)
{
printf("my baby died!\n");
}
}
else
{
printf("I'm just a baby! pid=%d\n", pid);
name[0] = "/bin/echo";
name[1] = foo;
execve(name[0], name, NULL);
}
Any problems with this
code?
Command Line Arguments
int main (int argc, char* argv[])
{
}
argv - is an array of character pointers to the words that were on
the command line
argc - holds the number of words specified in the command
(length of argv array)
./myPrg 5
results in argc=2 with argv[0]=“./myPrg” and argv[1]=“5”
int x;
x = atoi(argv[1]); /* Now x=5 */
Multiple Processes (cont.)
int main (int argc, char* argv[])
{
int pid, status, error;
char *name[2];
pid = fork();
if(pid)
{
printf("I'm the parent! pid=%d\n", pid);
if(waitpid(pid, &status, 0) == pid)
{
printf("my baby died!\n");
}
}
else
{
printf("I'm just a baby! pid=%d\n", pid);
name[0] = "/bin/echo";
name[1] = argv[1];
execve(name[0], name, NULL);
}
}
Multiple Processes (cont.)
forkbomb (n) – 'explodes' by recursively spawning copies of itself.
eventually overloads the number of available processes, and stalls the
machine.
DO NOT try this on hobbes: Sheryl will destroy you.
main() {for(;;)fork();}
expanded:
int main()
{
while(1)
fork();
}
Multiple Processes (cont.)
Back to the server: we fork once for each new connection and have the children
handle the incoming messages:
create sockets, bind to both ports
loop forever
if(incoming sender connection)
fork
if(child – sender process)
loop waiting for messages on connected socket
if a msg/file, send to all recver processes
done
if(incoming recver connection)
fork
if(child – recver process)
loop waiting for messages from sender processes
if a valid msg/file, send to connected socket
done
done
Multiple Processes (cont.)
Problem with multiple processes is use of Unix IPC methods to communicate
between processes:
SIGNALS: sigaction() sets a function to be called when the program gets a
signal. kill(pid, signal) sends the signal to a process. Can't send actual data, just
two instructions: SIGUSR1 and SIGUSER2
SHARED MEMORY: shmget() is used to create/get a reference to shared
memory, then shmat is used to get a normal pointer to it (shmdt to give it up).
Use a mutex to control access.
PIPES: pipe() creates two file descriptors that can be used with write/read (like
send/recv on sockets). Should be used one-way, created before forking
FIFO (named pipes): special file on disk, created with mkfifo. Uses write/read,
but can be used between non-forked programs
Lots more! http://www.ecst.csuchico.edu/~beej/guide/ipc/
One Monolithic Program
Must keep an array of open sockets, and when a connection is
accept()-ed, add to the list, then have one large loop that checks for incoming
information on each open socket, then checks for new connections. Overall
structure:
create sockets, bind to both ports
loop forever
loop through open sender sockets
check for messages on open “sender” connections
if(incoming message)
if a msg/file
loop on all open “recver” connections
send message to recver
while(there are incoming sender connections)
add to sender socket array
while(there are incoming recver connections)
add to recver socket array
done
Advantages/Disadvantages?
Threads
Threads are similar to processes, but all run under a single process ID,
and share more information than forked processes (exam T/F question?)
Threads have some advantages:
1) less overhead (creating a process = expensive)
2) can take more advantage of SMP support
3) no need to use Unix IPC: can use mutex locks
But also disadvantages:
1) debugging them can be a pain
(better with gdb 6+, kernel 2.6+, but still no fun at all)
2) code is complicated: for simple programs, maybe easier to fork
Let’s create a thread… How?
Need to include the library
#include <pthread.h>
And compile with the -lpthread flag:
gcc -lpthread mySource.c
Like any other type: need to declare and malloc
pthread_t *ghostThread;
ghostThread =
(pthread_t*)malloc(sizeof(pthread_t));
Let’s create a thread (cont.)
We can then set up the thread and actually start it running:
void *our_function (void *parameter) {
printf(“Hello World\n”);
}
int main () {
void* parameter=NULL;
pthread_t* pacmanThread;
pacmanThread =
(pthread_t*)malloc(sizeof(pthread_t));
pthread_create(pacmanThread, NULL,
our_function, parameter);
}
Let’s create a thread (cont.)
int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine)(void*),
void *arg);
The pthread_create() function is used to create a new thread, with
attributes specified by attr, within a process. If attr is NULL, the
default attributes are used. If the attributes specified by attr are
modified later, the thread's attributes are not affected. Upon
successful completion, pthread_create() stores the ID of the created
thread in the location referenced by thread. The thread is created
executing start_routine with arg as its sole argument. If the
start_routine returns, the effect is as if there was an implicit call to
pthread_exit() using the return value of start_routine as the exit
status.
Thread Attribute Type
Used to pass desired attributes to create
Declaration:
pthread_attr_t pthread_custom_attr;
Functions used with:
pthread_attr_init(&pthread_custom_attr);
int pthread_attr_setschedpolicy (pthread_attr_t *attr,
int policy);
Use with policy constants: SCHED_FIFO, SCHED_RR and SCHED_OTHER
int pthread_attr_setstacksize (pthread_attr_t *attr,
size_t stacksize);
Lets create a thread continued
We can then set up the thread using the attributes and
actually start it running:
pthread_create(ghostThread,
pthread_custom_attr,
our_thread_function, parameter);
Some useful pthread functions
void pthread_exit(void *value_ptr);
Called inside our threaded function. The pthread_exit
function terminates the thread that calls it and
makes the value value_ptr available to its parent (if
the parent has joined on it)
int pthread_join(pthread_t thread, void
**value_ptr);
The pthread_join() function suspends execution of the calling
thread until the target thread terminates, unless the target
thread has already terminated. On return from a successful
pthread_join() call with a non-NULL value_ptr argument, the
value passed to pthread_exit() by the terminating thread is
made available in the location referenced by value_ptr.
Locks
Declaration:
/* Global Variables */
int myInt;
pthread_mutex_t myIntLock;
Must be Initialized prior to use:
pthread_mutex_init(&myIntLock, NULL);
Locks also have an attribute field (much like threads):
int pthread_mutex_init(pthread_mutex_t
*mutex, const pthread_mutexattr_t
*attr);
Locking and Unlocking
int pthread_mutex_lock(pthread_mutex_t
*mutex);
Blocks if someone else has the lock..
int pthread_mutex_trylock
(pthread_mutex_t *mutex);
Doesn’t block if someone else has lock. Call returns
with failure. Lets you do other things…
Finished with the lock- let it go:
int pthread_mutex_unlock
(pthread_mutex_t *mutex);
Locks Finale
When all threads finished with the lock:
int pthread_mutex_destroy
(pthread_mutex_t *mutex);
pthread_mutex_destroy(&myIntLock);
Spin Waiting – Not preferred way to go. Why?
while(!pthread_mutex_trylock(&myIntLock)) {
/* Do nothing */
}
/* critical section */
Download