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 */