From here on, Makefiles
with all
(default) and clean
rules are required. Your source code must compile by running make in the corresponding folder.
In this task you will familiarize yourself with the build system make
.
In the directory task1 we provide some source code. This exercise doesn't involve coding C, therefore don't modify the files in task1. The task here is to create a Makefile in this directory which can be used to prepare and compile the provided code.
Your Makefile shall meet the following criteria:
- The rules
all
,create_main
andclean
must be availableall
should prepare (create themain.c
file - see below) and build the code.create_main
should create the missingmain.c
file. (>>
from shell scripting might be helpful). This main file should look exactly like this:
#include "hello.h" #include "goodbye.h" #include <stdlib.h> int main(void) { hello(); goodbye(); return EXIT_SUCCESS; }
clean
should remove all artifacts as well as the created executable and themain.c
file.
- When running just
make
in thetask1
directory, theall
rule should be executed. - All calls to the C compiler should use the flags
-std=c11 -Wall -Wextra
. - Make sure to use
.PHONY
correctly (see here). - The C files should first be compiled into intermediary object files (
hello.o
,goodbye.o
andmain.o
), which are then combined into the final executable in a separate rule. - For clarity, the make file should echo
Compiling xxx
, before actually compiling the object files. (rule prerequisites might help here.)
If everything works correctly, you should be able to call make
in the directory task1
,
which generates the executable main
, and some object files. This call should output this (Note the compiler commands might differ depending on your system):
Compiling main:
cc -std=c11 -Wall -Wextra -c -o main.o main.c
Compiling hello:
cc -std=c11 -Wall -Wextra -c -o hello.o hello.c
Compiling goodbye:
cc -std=c11 -Wall -Wextra -c -o goodbye.o goodbye.c
cc main.o hello.o goodbye.o -o main
Running this executable should print:
Hello World.
Bye World.
When running make clean
, the executable, main.c
and the object files should be deleted.
Comprehensive documentation of GNU make can be found online.
make
is mainly used for building code, but it can also be helpful for many other tasks.
For each rule, the recipe which is executed can run arbitrary shell commands (as seen before with creating the main.c
file).
The idea of this part of the task is automating part of the homework submission process:
- Cleaning up build artifacts from the different tasks.
- Building the zip archive which you can then submit.
For all tasks in which you work with C code there should be a Makefile with a clean
rule in the corresponding directory,
as specified in the previous part of the task.
Create a Makefile in the directory which contains the directories task1
, task2
, and task3
.
This Makefile should also have a clean
rule, which calls make clean
in the directories of the three different tasks
(you do not have to do it for tasks 2 and 3 if you do not submit them).
Additionally, define a rule called zip
which first deletes the archive if it already exists
and then zips the content of the directory in which this Makefile is stored.
The generated zip archive should only contain the files we want in the submission,
so there should be no extra files in this directory or any child directories.
To ensure that no build artifacts are included in the archive,
write the zip
rule in such a way that it first calls the clean
rule described in the previous paragraph.
Hint: For information on how make
should be called in a Makefile, check the GNU make documentation.
Hint: You can recursively zip a directory with zip -r ../archive.zip .
This will put everything contained in the current directory into the archive, which is stored in the parent directory.
Try to write your Makefiles in a way which allows you to continue using them for future homework with minimal modification.
Create a program that registers handler(s) for the signals SIGINT
, SIGSTOP
, SIGCONT
, SIGKILL
, SIGUSR1
, and SIGUSR2
using sigaction
(you must not use signal
).
When a signal is received, it outputs a message indicating which signal was received.
After registering the signal handler(s), call usleep
in a loop to prevent the process from exiting. On a high level, your program should look as follows:
// include necessary header files
// define signal handler
int main(void) {
// use sigaction to register signal handler
// allow required signals
while (true) {
usleep(100);
// more code (if needed)
}
return EXIT_SUCCESS;
}
Have a look at the following man pages sigaction(2)
, signal(7)
, and signal-safety(7)
.
Read also SIG30-C and SIG31-C.
You can use the shell commands ps
and pgrep
to find the process ID (PID) of a process.
kill
can be used to send a signal to a process.
What difference do you notice between SIGINT
and SIGKILL
?
What happens when you press CTRL + c
while the program is running?
What can the signals SIGSTOP
and SIGCONT
be used for?
for
Note: In order to use sigaction
and usleep
you have to define _POSIX_C_SOURCE
and _BSD_SOURCE
feature test macros.
This can be done by adding the compile flag -D_POSIX_C_SOURCE -D_BSD_SOURCE
.
See the feature_test_macros(7)
man page for more information.
On modern systems the _BSD_SOURCE
feature test macro has been deprecated and replaced by _DEFAULT_SOURCE
, however, this is not available on zid-gpl
.
You may ignore the deprecation warning that is emitted when using _BSD_SOURCE
, however, your program should not generate any other warnings.
Write a program that creates N
child processes using fork(2)
, where each one estimates the probability of rolling a specific target number T
on a single dice through a method based on repeated trials, referred to as a dice-roll simulation, using S
steps. The values for N
and S
can be taken from the command line. T
should be randomly generate in each child process.
First, the parent process creates all children processes, then awaits until they have exited, and finally prints Done.
. The parent must wait for all children only after they have all been created.
Every child:
- Computes the probability of rolling a specific target number
T
on a single dice using a "dice-roll" simulation withS
steps. - prints a message of the form
Child <i> PID = <PID>. DR_p(<T,S>) = <DR_p(T,S)>. Elapsed time = <t> (s).
where<i>
is the child number,<PID>
is the child's process id,<T>
is the target number on a single dice rolling,<S>
is the random samples parameter,<DR_p(S)>
is the calculation result, and<t>
is the elapsed time since the parent was started.
Make sure to only once measure the starting time of the parent process.
Hint: You can use clock_gettime()
(have a look at the man page clock_gettime(2)
) to measure wall time. For this to work you have to define -D_POSIX_C_SOURCE=199309L
feature test macros (see Task 2).
Use the following implementation for the dice-roll simulation:
double DR_p(int T, int64_t S) {
int64_t hit_count = 0;
for (int64_t i = 0; i < S; ++i) {
const int roll = rand() % 6 + 1;
if (roll == T) {
hit_count++;
}
}
return (double)hit_count / S;
}
Hint: A parent process can wait for its children using wait(2)
or waitpid(2)
.
Example output:
$ ./task3 3 100000000
Child 0 PID = 2942722. DR_p(2,100000000) = 0.166671. Elapsed time = 7.931573 (s)
Child 2 PID = 2942724. DR_p(4,100000000) = 0.166730. Elapsed time = 8.062064 (s)
Child 1 PID = 2942723. DR_p(5,100000000) = 0.166717. Elapsed time = 8.099407 (s)
Done.
Experiment with different values for N
and S
.
Analyze the obtained output.
Is the order of the messages consistent?
Can the order of these messages be predicted?
What does it depend on?
Notice that the function DR_p
uses rand(3)
to repeatedly generate pseudo-random numbers.
By default, this function returns the same sequence of numbers every time.
To get different estimates from each process, seed the random number generator using srand(getpid())
.
Does it matter whether you do this before or after the call to fork()
?
Explain your answer.
Submit your solution as a zip archive via OLAT, structured as follows, where csXXXXXX is your UIBK login name. Your zip archive must not contain binaries.
exc02_csXXXXXX.zip
├── Makefile # second Makefile from task1
├── group.txt # optional
├── task1
│ ├── Makefile
│ ├── goodbye.c
│ ├── goodbye.h
│ ├── hello.c
│ └── hello.h
├── task2
│ ├── Makefile
│ ├── signal_handler.c
│ └── solution.txt
└── task3
├── Makefile
├── solution.txt
└── task3.c
Requirements
- Auto-format all source files
- Check your submission on ZID-GPL
- Check your file structure
- Submit zip
- Mark solved exercises in OLAT
- Any implementation MUST NOT produce any additional output
- If you work in a group, create a
group.txt
file according to the format specified below.
If you worked in a group, the group.txt
file must be present and have one line per student which contains the proseminar group in the beginning, followed by the matriculation number and the student's name. For example, if the group consists of Jane Doe, who is in proseminar group 2 and has matriculation number 12345678, and Max Mustermann, who is in proseminar group 3 and has matriculation number 87654321, the group.txt
file should look like this:
ps_group_2 12345678 Jane Doe
ps_group_3 87654321 Max Mustermann