thread | os代写 | Computer Systems – COMP30023: Computer Systems

School of Computing and Information Systems

thread | os代写 | Computer Systems – 该题目是一个常规的thread的练习题目代写, 涵盖了thread/Computer Systems等程序代做方面, 这是值得参考的Computer Systems代写的题目

OOP代写 代写oop

COMP30023: Computer Systems

Practical Week 3

1 Introduction

In this practical, we will be exploring threads, processes and interprocess communication. NOTE: You can do this lab without following the order.

2 Creating a Thread

The main() function of a C program runs on its own thread (commonly called the main thread)

We can create additional, independent threads by using thepthreadcreate function provided bypthread.h.

thread1.c on the LMS creates such a thread. The thread runs the function sayhello() upon creation.
  1. Compile and run thread1.c. Note the use of the-lpthreadoption to explicitly link the pthread library Command: $gcc thread1.c -o thread1 -lpthread && ./thread
  2. Notice how the second thread said hello before the first thread? This is because the pthreadjoin function will wait for the thread specified in the function call to finish executing before proceeding with the current thread. In this scenario the main thread waited for the other thread to join it before proceeding.
  3. Can you guess what might happen if we did not call the pthreadjoin function? Comment that line in the code, compile and rerun to observe the output. Discuss with your classmates (or demonstrator) if you are unable to understand the behaviour you observe.
  4. Do you think these threads are user or kernel threads?

3 Threads and Race Conditions

Race conditions, where the final result of a computation depends on the order in which threads happened to run, may occur when several threads access a shared resource.

  1. The code thread2.c given on the LMS has two threads accessing the common global variable count. This code has a race condition. Run the code several times and observe that the output changes each time.
  2. We can solve race conditions such as these by defining a section of code that can only be executed by one thread at a time (called a critical section or critical region). We can use amutex to define a critical section. The methods pthreadmutexlock(&lock) and pthreadmutexunlock(&lock) can be used to define a critical section, where lock is a global variable that is of type pthreadmutext.
  3. The definition, initialisation, and destroying of the mutex have been written for you in thread2.c. De- termine the critical section that would prevent the race condition and use the function calls to lock and unlock the mutex to fix the race condition.
pthreadmutexlock(&lock)
/* Code in Critical Section */
pthreadmutexunlock(&lock)
  1. Challenge task: try to introduce a deadlock into your program.

4 OS Processes

4.1 fork

fork()creates a new process by duplicating the calling process. The new process is referred to as the child process. The calling process is referred to as the parent process. The child process and the parent process run in separate memory spaces. At the time offork()both memory spaces have the same content.

First, compile the demo fork program (see Appendix): $gcc -o fork fork.c

Run the program in the background. $./fork &

While the program runs in background, runtopin tree mode to watch how child processes are spawned from the parent process. $top

Whiletopis running, pushShift + Vto enable forrest view^1. Find theforkprogram and watch how child processes get spawned.

4.2 exec

Taken from the manpages verbatim: The exec family of functions shall replace the current process image with a new process image. The new image shall be constructed from a regular, executable file called the new process image file. There shall be no return from a successful exec, because the calling process image is overlaid by the new process image. First, compile the demo exec program:

$gcc -o exec exec.c

Run the program. $./exec

What program does it actually exec into? Discuss with your classmate and demonstrator.

4.3 pipe

The pipe() function shall create a pipe and place two file descriptors, one each into the arguments fildes[0] and fildes[1], that refer to the open file descriptions for the read and write ends of the pipe. Their integer values shall be the two lowest available at the time of the pipe() call. First, compile the demo pipe program:

$gcc -o pipe pipe.c

Run the program. $./pipe

You now have a brief idea howexec,forkandpipeworks. You are encouraged to author a simple C program that can utilise all 3 libc functions. An idea would be to author a program that forks a new process and waits for input from the parent process (viastdin) to print from the child process. For example, try simulating execution of$ ls *.c | wc -l.

(^1) This view shows parent-child relationships between processes

A thread1.c

/*************************************

Demo for pthread commands compile: gcc threadX.c -o threadX -lpthread ***************************************/

#include <pthread.h> #include <stdio.h>

void* say_hello(void* param); /* the work_function */

int main(int args, char** argv) { pthread_t tid; /* thread identifier */

/* create the thread */
pthread_create(&tid, NULL, say_hello, NULL);
/* wait for thread to exit */
pthread_join(tid, NULL);

printf("Hello from first thread\n"); return 0; }

void* say_hello(void* param) { printf("Hello from second thread\n"); return NULL; }

B thread2.c

#include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h>

#define ITERATIONS 1000000

void* runner(void* param); /* thread doing the work */

int count = 0; pthread_mutex_t lock;

int main(int argc, char** argv) { pthread_t tid1, tid2;

if (pthread_mutex_init(&lock, NULL) != 0) {
printf("mutex init failed\n");
exit(1);
}
if (pthread_create(&tid1, NULL, runner, NULL)) {
printf("Error creating thread 1\n");
exit(1);
}
if (pthread_create(&tid2, NULL, runner, NULL)) {
printf("Error creating thread 2\n");
exit(1);
}
/* wait for the threads to finish */
if (pthread_join(tid1, NULL)) {
printf("Error joining thread\n");
exit(1);
}
if (pthread_join(tid2, NULL)) {
printf("Error joining thread\n");
exit(1);
}
if (count != 2 * ITERATIONS)
printf("** ERROR ** count is [%d], should be %d\n", count, 2 * ITERATIONS);
else
printf("OK! count is [%d]\n", count);

pthread_exit(NULL); pthread_mutex_destroy(&lock); return 0; }

/* thread doing the work / void runner(void* param) { int i, temp; for (i = 0; i < ITERATIONS; i++) { temp = count; /* copy the global count locally / temp = temp + 1; / increment the local copy / count = temp; / store the local value into the global count */ } return NULL; }

C fork.c

#include <stdio.h> #include <sys/types.h> #include <unistd.h>

int main(int argc, char** argv) { pid_t root = getpid(); // when forking, program does not start again since memory/register values are exactly the same // i.e. instruction pointer is at the same line too. So fork() wont execute again. pid_t pid = fork(); printf("from %d forking into %d\n", root, pid); sleep(20);

// watch 2 different PIDs spawn 2 more child processes.
pid_t mypid = getpid();
pid = fork();
printf("from %d forking into %d\n", mypid, pid);
sleep(20);

if (getpid() == root) { sleep(20); printf("root exiting\n"); } else { printf("Child — PID %d exiting\n", getpid()); } return 0; }

D exec.c

#include<unistd.h>

int main(int argc, char **argv) { return execv("/usr/bin/ls", argv); }

E pipe.c

/*****************************************************************************
Excerpt from "Linux Programmers Guide - Chapter 6"
(C)opyright 1994-1995, Scott Burkett
*****************************************************************************
MODULE: pipe.c
*****************************************************************************/

#include <stdio.h> #include <sys/types.h> #include <unistd.h> #include <stdlib.h> #include <string.h>

int main(void) { int fd[2], nbytes; pid_t childpid; char string[] = "Hello, world!\n"; char readbuffer[80];

pipe(fd);
if ((childpid = fork()) == -1) {
perror("fork");
exit(1);
}
if (childpid == 0) {
/* Child process closes up input side of pipe */
close(fd[0]);
/* Send "string" through the output side of pipe */
write(fd[1], string, (strlen(string) + 1));
exit(0);
} else {
/* Parent process closes up output side of pipe */
close(fd[1]);
/* Read in a string from the pipe */
nbytes = read(fd[0], readbuffer, sizeof(readbuffer));
printf("Received string: %s", readbuffer);
}

return (0); }

Sample solutions

2

A couple of (observed) possibilities:

$ ./thread Hello from first thread $ ./thread Hello from first thread Hello from second thread $ ./thread Hello from first thread Hello from second thread Hello from second thread

In the first case, the main thread returns and terminates the process before the completion ofprintf. In the second case, theprintfhappens before the process is terminated. In the third case, why does the second thread print twice?https://stackoverflow.com/questions/

You will probably not encounter the second and third cases unless youre on a multi-core system (note that the allocated VMs are single-core).

What if we want the main thread to exit but allow the second thread to run to completion? We can replacepthreadjoinwithpthreadexit. pthreadexitterminates the calling thread (the main thread). When the last thread in the process terminates,exit(3)is called with an exit status code of zero (terminating the process). See: $ man 3 pthreadexit

pthreadsis an API, defined in the POSIX standard. Whether user-level threads or kernel-level threads are used is implementation specific. The Linux implementation ofpthreadsuse kernel-level threads^23. It is also possible to create threads using theclonesystem call in Linux (see TB 10.3.3).

3

Why cant I put the locks aroundpthreadcreate,pthreadjoin? The main thread will obtain lock, callpthreadfunction, then unlock. This does not solve the problem because the code which contains the race condition is not executed by the main thread.

What is the race condition? Access/modification of global variablecount. For example:

  1. Thread A copiescounttotemp. It is interrupted.
  2. Thread B gets a chance to run and it copiescounttotemp, incrementstempand saves it back.
  3. Thread A resumes at some point, it incrementstemp(which is outdated) and saves it tocount. 4.countis now lower than it should be.

Solution: Put lock/unlock statements immediately outside the loop. Why not put the locks inside? Because of performance overheads in locking/unlocking.

However, consider scenario:

for each l oop iteration perform long task which is independent critical section to update some global variable

Then it may be worthwhile to surround the critical section with the lock instead.

(^2) https://stackoverflow.com/questions/ (^3) https://stackoverflow.com/questions/

Challenge

Taking inspiration from lecture slides:

void* runner(void* param) { for (int i = 0; i < 10000; i++) { pthread_mutex_lock(&lock1); printf("thread 1 lock 1\n"); pthread_mutex_lock(&lock2); printf("thread 1 lock 2\n"); // do work pthread_mutex_unlock(&lock1); pthread_mutex_unlock(&lock2); } } void* runner2(void* param) { for (int i = 0; i < 10000; i++) { pthread_mutex_lock(&lock2); printf("thread 2 lock 2\n"); pthread_mutex_lock(&lock1); printf("thread 2 lock 1\n"); // do work pthread_mutex_unlock(&lock2); pthread_mutex_unlock(&lock1); } }

4

Example execution offork.c:

./fork (pid 1150653) pid_t root = getpid(); root = 1150653 pid_t pid = fork(); printf("from %d forking into %d\n", root, pid);

./fork (pid 1150653) |_ ./fork (pid 1150654) from 1150653 forking into 1150654 parent process gets child processs pid for fork from 1150653 forking into 0 child process gets 0 as return value for fork

pid_t mypid = getpid(); pid = fork(); printf("from %d forking into %d\n", mypid, pid); ./fork (pid 1150653) |_ ./fork (pid 1150654) |_ ./fork (pid 1150684) |_ ./fork (pid 1150683) from 1150653 forking into 1150683 fork 1150654 forking into 1150684 from 1150653 forking into 0 from 1150654 forking into 0

if (getpid() != root) printf("Child — PID %d exiting\n", getpid()); Child — PID 1150654 exiting Child — PID 1150683 exiting Child — PID 1150684 exiting ./fork (pid 1150653) |_ ./fork (pid 1150654 – Zombie) |_ ./fork (pid 1150683 – Zombie)

printf("root exiting\n");

Why do I still see the child processes after they have exited? They are zombie processes: seehttps://stackoverflow.com/questions/4825379.

/*****************************************************************************

ls *.c | wc -l Adapted from pipe.c Excerpt from "Linux Programmers Guide – Chapter 6" (C)opyright 1994-1995, Scott Burkett *****************************************************************************/

#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <stdlib.h> #include <glob.h>

int main(int argc, char* argv[]) { int fd[2]; pid_t childpid; pipe(fd); if((childpid = fork()) == -1) { perror("fork"); exit(1); } if(childpid == 0) { /* Child process closes up input side of pipe / close(fd[0]); / redirect stdout, https://stackoverflow.com/questions/1720535 */ dup2(fd[1], STDOUT_FILENO);

/* glob for .c, https://stackoverflow.com/questions/53686987 / glob_t globbuf; globbuf.gl_offs = 1; glob(".c", GLOB_DOOFFS, NULL, &globbuf); globbuf.gl_pathv[0] = "ls"; execv("/usr/bin/ls", globbuf.gl_pathv); } else { / Parent process closes up output side of pipe / close(fd[1]); / redirect stdin / dup2(fd[0], STDIN_FILENO); char args[] = { "wc", "-l", NULL }; execv("/usr/bin/wc", args); } return 0; }