Skip to content
Snippets Groups Projects
Commit d9319ffc authored by Celina's avatar Celina
Browse files

ue6 done

parent 8f53d50c
Branches
No related tags found
No related merge requests found
No preview for this file type
File added
File added
File deleted
File added
// compile: gcc -Wall -Werror -Wextra -pedantic -lpthread -lrt -std=gnu11 aufgabe1.c -o aufgabe1
// ausführung ./aufgabe1 number_producer number_consumer
// ausführung ./aufgabe1 number_producer number_consumer
// Vorlage vorlesung 8 folie 22 f
#include <stdio.h>
......@@ -70,8 +70,8 @@ int main(int argc, char const *argv[])
long t; // thread ids
// initialize semaphores
sem_init(&mutex, 0, 1);
sem_init(&empty, 0, 0);
sem_init(&full, 0, NUM_PLACES);
sem_init(&empty, 0, 0); //bei 0 leer, bei 3 full
sem_init(&full, 0, NUM_PLACES); //bei 3 leer
sem_init(&producing, 0, num_producer);
for (t = 0; t < NUM_PLACES; t++) { // initialize buffer
......
No preview for this file type
......@@ -38,13 +38,18 @@ Die Ausführung erfolgt äquivalent zu Aufgabe 1 (./aufgabe2 number\_producer nu
\section{Bewertung der Lösungen \hfill (10 Punkte)}
\textit{Bewerten Sie Ihre Lösungen aus Aufgabe 1 und 2 in Hinblick auf die Anforderungen zur Sicherung des kritischen Abschnitts.}
\textbf{Gegenseitiger Ausschluss}\\
Die Anforderungen zur Sicherung des kritischen Abschnitts entnehmen wir der 8. VL Folie 43.
\textbf{Gegenseitiger Ausschluss:}
Sowohl in Aufgabe 1 als auch Aufgabe 2 ist der gegenseitige Ausschluss durch die Semaphore ''mutex'' gewährleistet, welche den kritischen Abschnitt des Produzenten und des Konsumenten gleichermaßen sichert.
\textbf{Behinderungsfreiheit}\\
\textbf{Behinderungsfreiheit:}
Die Behinderungsfreiheit ist nicht gegeben. Wenn der Buffer zum Beispiel voll belegt ist und ein Produzent Einlass in den kritischen Abschnitt begehrt, wird er durch die Semaphore ''full'' davon abgehalten, auch wenn sich darin gerade kein anderer Thread befindet. Analog für ''empty'' auch in Aufgabe 2.
\textbf{Verklemmungsfreiheit und Fairness}\\
\textbf{Verklemmungsfreiheit und Fairness:}
Die Lösung von Aufgabe 1 ist weder verklemmungsfrei noch verhungerungsfrei (fair). Es ist möglich, dass die Konsumenten-Threads beendet werden, bevor die letzten vier Produzenten Threads an die Reihe kommen, welche den Buffer dann auffüllen und der letzte Produzent wird dann blockiert, weil der Buffer voll ist und nie wieder ein Konsument arbeitet.
In Aufgabe 2 kann dies nicht passieren, da die Konsumenten einfach aufhören, wenn es keine Produzenten mehr gibt und die Produzenten beliebig lange weiter produzieren können, da der Buffer eine flexible Größe hat.
......
...
Thread 5 takes 997 from the buffer at place 1
Thread 1 puts 997 into the buffer at place 1
Thread 0 puts 997 into the buffer at place 2
Thread 3 takes 997 from the buffer at place 2
Thread 4 takes 997 from the buffer at place 1
Thread 2 puts 998 into the buffer at place 1
Thread 5 takes 998 from the buffer at place 1
Thread 1 puts 998 into the buffer at place 1
Thread 0 puts 998 into the buffer at place 2
Thread 3 takes 998 from the buffer at place 2
Thread 4 takes 998 from the buffer at place 1
Thread 2 puts 999 into the buffer at place 1
Thread 5 takes 999 from the buffer at place 1
Thread 1 puts 999 into the buffer at place 1
Thread 0 puts 999 into the buffer at place 2
Simulation ended
No preview for this file type
// compile: gcc -Wall -Werror -Wextra -pedantic -lpthread -lrt -std=gnu11 aufgabe1.c -o aufgabe1
// ausführung ./aufgabe1 number_producer number_consumer
// ausführung ./aufgabe1 number_producer number_consumer
// Vorlage vorlesung 8 folie 22 f
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
#define NUM_PLACES 3 // buffer size
int NUM_THREADS;
sem_t producing;
pthread_mutex_t mutex;
typedef struct monitor
{
int last; // index of last element
sem_t empty;
sem_t full;
int buffer[NUM_PLACES]; // buffer
int prod_count; //number of producing threads
pthread_mutex_t mutex;
pthread_cond_t cond;
} monitor_t;
monitor_t monitor;
void put(long threadId, int producted){
void iJustDied() {
pthread_cond_broadcast(&monitor.cond);
}
pthread_mutex_lock(&mutex); // enter critical section
printf("mutex suceed %ld\n", threadId);
if(NUM_PLACES - monitor.last){
printf("waiting %ld\n", threadId);
sem_wait(&(monitor.full)); // if buffer is full wait here
void ablegen (long threadId, int product) {
pthread_mutex_lock(&monitor.mutex);
while (NUM_PLACES == monitor.last) {
pthread_cond_wait(&monitor.cond, &monitor.mutex);
}
printf("Thread %ld puts %d into the buffer at place %d\n", threadId, producted, monitor.last);
monitor.buffer[monitor.last] = producted; // "real" production
monitor.last++; // increase buffer iterator
//pthread_cond_signal(&(monitor.busy)); // let consumer take
printf("unlocking mutex %ld\n", threadId);
sem_post(&(monitor.empty)); // buffer not empty anymore
pthread_mutex_unlock(&mutex); // leave critical section
printf("Thread %ld puts %d into the buffer at place %d\n", threadId, product, monitor.last);
monitor.buffer[monitor.last] = product; // "real" production
monitor.last++;
pthread_cond_broadcast(&monitor.cond);
pthread_mutex_unlock(&monitor.mutex);
}
void take(long threadId){
//int error;
// sem_wait(&(monitor.empty)); // wait here if buffer is empty
printf("mutex suceed %ld\n", threadId);
if(NUM_PLACES - monitor.last == NUM_PLACES ){
printf("waiting %ld\n", threadId);
sem_wait(&(monitor.empty));
void entnehmen (long threadId) {
pthread_mutex_lock(&monitor.mutex);
while (monitor.last == 0 && monitor.prod_count > 0) {
pthread_cond_wait(&monitor.cond, &monitor.mutex);
}
if (monitor.prod_count > 0) {
monitor.last--;
printf("Thread %ld takes %d from the buffer at place %d\n", threadId, monitor.buffer[monitor.last], monitor.last);
}
pthread_mutex_lock(&mutex);
printf("Thread %ld takes from the buffer at place %d\n", threadId, monitor.last);
monitor.last--; // take things from buffer
printf("unlocking mutex %ld\n", threadId);
sem_post(&(monitor.full)); // one more thread can put something into the buffer
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&monitor.cond);
pthread_mutex_unlock(&monitor.mutex);
}
void* Producer (void *threadid) {
for (int i = 0; i < 1000; i++) {
put((long) threadid, i);
for (int i = (int) threadid * 1000; i < ((int) threadid + 1) * 1000; i++) {
ablegen((long) threadid, i);
}
sem_wait(&producing); // take a way producing thread
monitor.prod_count--;
iJustDied();
pthread_exit(NULL);
}
void* Consumer (void *threadid) {
// int error; // error message trywait
int producing_threads;
sem_getvalue(&producing, &producing_threads);
while(producing_threads){ // as long as someone produces
take((long) threadid);
printf("Consumer %ld took some from the production",(long) threadid);
sem_getvalue(&producing, &producing_threads); // refresh runtime variable
while (monitor.prod_count > 0) { // as long as someone produces
entnehmen((long) threadid);
}
pthread_exit(NULL);
}
int main(int argc, char const *argv[])
{
if(argc !=3){ // check if enough arguemntes
printf("\n To many or not enoguh arguments needs to be exactly 2 \n");
int main(int argc, char const *argv[]) {
if (argc !=3) { // check if enough arguemntes
printf("\n Too many or not enough arguments needs to be exactly 2 \n");
exit(-1);
}
int num_producer = strtol(argv[1], NULL, 10); // cast cl arguemnts to long
int num_comsumer = strtol(argv[2], NULL, 10);
// source for above https://stackoverflow.com/a/9748402
if(num_producer < 1 || num_comsumer < 1){ // prevent some stupid input
if (num_producer < 1 || num_comsumer < 1) { // prevent some stupid input
printf("There must be at least one producer and one consumer\n");
exit(-1);
}
......@@ -101,10 +86,19 @@ int main(int argc, char const *argv[])
int rc; // thread return code
long t; // thread ids
sem_init(&(monitor.empty), 0, 0);
sem_init(&(monitor.full),0,NUM_PLACES);
sem_init(&producing, 0, num_producer);
monitor.last = 0;
monitor.prod_count = num_producer;
if (pthread_mutex_init(&monitor.mutex, NULL) != 0) {
perror("pthread_mutex_init");
exit(1);
}
if (pthread_cond_init(&monitor.cond, NULL) != 0) {
perror("pthread_cond_init");
exit(1);
}
for (t = 0; t < NUM_PLACES; t++) { // initialize buffer
monitor.buffer[t] = 0; // no production yet
}
......@@ -115,23 +109,19 @@ int main(int argc, char const *argv[])
if(rc){ // error handling
printf("in main: Pthread error %d",rc);
}
printf("created producer %ld \n", t);
}
else {
rc = pthread_create (&threads[t], NULL, Consumer, (void *)t);
if(rc){ // error handling
printf("in main: Pthread error %d",rc);
}
printf("created consumer %ld \n", t);
}
}
for (t = 0; t < NUM_THREADS; t++) { // join threads
pthread_join (threads[t], NULL);
}
// cleaning up
sem_destroy(&(monitor.empty));
sem_destroy(&(monitor.full));
sem_destroy(&producing);
pthread_cond_destroy(&monitor.cond);
printf("Simulation ended\n");
return 0;
}
// compile: gcc -Wall -Werror -Wextra -pedantic -lpthread -lrt -std=gnu11 aufgabe1.c -o aufgabe1
// ausführung ./aufgabe1 number_producer number_consumer
// Vorlage vorlesung 8 folie 22 f
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
#define NUM_PLACES 3 // buffer size
int NUM_THREADS;
sem_t producing;
pthread_mutex_t mutex;
typedef struct monitor
{
int last; // index of last element
sem_t empty;
sem_t full;
int buffer[NUM_PLACES]; // buffer
} monitor_t;
monitor_t monitor;
void put(long threadId, int producted){
pthread_mutex_lock(&mutex); // enter critical section
printf("mutex suceed %ld\n", threadId);
if(NUM_PLACES - monitor.last){
printf("waiting %ld\n", threadId);
sem_wait(&(monitor.full)); // if buffer is full wait here
}
printf("Thread %ld puts %d into the buffer at place %d\n", threadId, producted, monitor.last);
monitor.buffer[monitor.last] = producted; // "real" production
monitor.last++; // increase buffer iterator
//pthread_cond_signal(&(monitor.busy)); // let consumer take
printf("unlocking mutex %ld\n", threadId);
sem_post(&(monitor.empty)); // buffer not empty anymore
pthread_mutex_unlock(&mutex); // leave critical section
}
void take(long threadId){
//int error;
// sem_wait(&(monitor.empty)); // wait here if buffer is empty
printf("mutex suceed %ld\n", threadId);
if(NUM_PLACES - monitor.last == NUM_PLACES ){
printf("waiting %ld\n", threadId);
sem_wait(&(monitor.empty));
}
pthread_mutex_lock(&mutex);
printf("Thread %ld takes from the buffer at place %d\n", threadId, monitor.last);
monitor.last--; // take things from buffer
printf("unlocking mutex %ld\n", threadId);
sem_post(&(monitor.full)); // one more thread can put something into the buffer
pthread_mutex_unlock(&mutex);
}
void* Producer (void *threadid) {
for (int i = 0; i < 1000; i++) {
put((long) threadid, i);
}
sem_wait(&producing); // take a way producing thread
pthread_exit(NULL);
}
void* Consumer (void *threadid) {
// int error; // error message trywait
int producing_threads;
sem_getvalue(&producing, &producing_threads);
while(producing_threads){ // as long as someone produces
take((long) threadid);
printf("Consumer %ld took some from the production",(long) threadid);
sem_getvalue(&producing, &producing_threads); // refresh runtime variable
}
pthread_exit(NULL);
}
int main(int argc, char const *argv[])
{
if(argc !=3){ // check if enough arguemntes
printf("\n To many or not enoguh arguments needs to be exactly 2 \n");
exit(-1);
}
int num_producer = strtol(argv[1], NULL, 10); // cast cl arguemnts to long
int num_comsumer = strtol(argv[2], NULL, 10);
// source for above https://stackoverflow.com/a/9748402
if(num_producer < 1 || num_comsumer < 1){ // prevent some stupid input
printf("There must be at least one producer and one consumer\n");
exit(-1);
}
NUM_THREADS = num_producer + num_comsumer; // producer + consumer
pthread_t threads[NUM_THREADS]; // thread array
int rc; // thread return code
long t; // thread ids
sem_init(&(monitor.empty), 0, 0);
sem_init(&(monitor.full),0,NUM_PLACES);
sem_init(&producing, 0, num_producer);
for (t = 0; t < NUM_PLACES; t++) { // initialize buffer
monitor.buffer[t] = 0; // no production yet
}
for (t = 0; t < NUM_THREADS; t++) { // creating threads
if (t < num_producer){
rc = pthread_create (&threads[t], NULL, Producer, (void *)t);
if(rc){ // error handling
printf("in main: Pthread error %d",rc);
}
printf("created producer %ld \n", t);
}
else {
rc = pthread_create (&threads[t], NULL, Consumer, (void *)t);
if(rc){ // error handling
printf("in main: Pthread error %d",rc);
}
printf("created consumer %ld \n", t);
}
}
for (t = 0; t < NUM_THREADS; t++) { // join threads
pthread_join (threads[t], NULL);
}
// cleaning up
sem_destroy(&(monitor.empty));
sem_destroy(&(monitor.full));
sem_destroy(&producing);
printf("Simulation ended\n");
return 0;
}
File added
// compile: gcc -Wall -Werror -Wextra -pedantic -lpthread -lrt -std=gnu11 aufgabe2.c -o aufgabe2
// ausführung ./aufgabe2 number_producer number_consumer
// Vorlage vorlesung 8 folie 22 f
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#define NUM_PLACES 3 // buffer size
int NUM_THREADS;
typedef struct monitor
{
int last; // index of last element
int buffer[NUM_PLACES]; // buffer
int prod_count; //number of producing threads
pthread_mutex_t mutex;
pthread_cond_t cond;
} monitor_t;
monitor_t monitor;
void iJustDied() {
pthread_cond_broadcast(&monitor.cond);
}
void ablegen (long threadId, int product) {
pthread_mutex_lock(&monitor.mutex);
while (NUM_PLACES == monitor.last) {
pthread_cond_wait(&monitor.cond, &monitor.mutex);
}
printf("Thread %ld puts %d into the buffer at place %d\n", threadId, product, monitor.last);
monitor.buffer[monitor.last] = product; // "real" production
monitor.last++;
pthread_cond_broadcast(&monitor.cond);
pthread_mutex_unlock(&monitor.mutex);
}
void entnehmen (long threadId) {
pthread_mutex_lock(&monitor.mutex);
while (monitor.last == 0 && monitor.prod_count > 0) {
pthread_cond_wait(&monitor.cond, &monitor.mutex);
}
if (monitor.prod_count > 0) {
monitor.last--;
printf("Thread %ld takes %d from the buffer at place %d\n", threadId, monitor.buffer[0], 0);
for (int i = 0; i < NUM_PLACES - 1; i++) {
monitor.buffer[i] = monitor.buffer[i+1];
}
}
pthread_cond_signal(&monitor.cond);
pthread_mutex_unlock(&monitor.mutex);
}
void* Producer (void *threadid) {
for (int i = (int) threadid * 1000; i < ((int) threadid + 1) * 1000; i++) {
ablegen((long) threadid, i);
}
monitor.prod_count--;
iJustDied();
pthread_exit(NULL);
}
void* Consumer (void *threadid) {
while (monitor.prod_count > 0) { // as long as someone produces
entnehmen((long) threadid);
}
pthread_exit(NULL);
}
int main(int argc, char const *argv[]) {
if (argc !=3) { // check if enough arguemntes
printf("\n Too many or not enough arguments needs to be exactly 2 \n");
exit(-1);
}
int num_producer = strtol(argv[1], NULL, 10); // cast cl arguemnts to long
int num_comsumer = strtol(argv[2], NULL, 10);
if (num_producer < 1 || num_comsumer < 1) { // prevent some stupid input
printf("There must be at least one producer and one consumer\n");
exit(-1);
}
NUM_THREADS = num_producer + num_comsumer; // producer + consumer
pthread_t threads[NUM_THREADS]; // thread array
int rc; // thread return code
long t; // thread ids
monitor.last = 0;
monitor.prod_count = num_producer;
if (pthread_mutex_init(&monitor.mutex, NULL) != 0) {
perror("pthread_mutex_init");
exit(1);
}
if (pthread_cond_init(&monitor.cond, NULL) != 0) {
perror("pthread_cond_init");
exit(1);
}
for (t = 0; t < NUM_PLACES; t++) { // initialize buffer
monitor.buffer[t] = 0; // no production yet
}
for (t = 0; t < NUM_THREADS; t++) { // creating threads
if (t < num_producer){
rc = pthread_create (&threads[t], NULL, Producer, (void *)t);
if(rc){ // error handling
printf("in main: Pthread error %d",rc);
}
}
else {
rc = pthread_create (&threads[t], NULL, Consumer, (void *)t);
if(rc){ // error handling
printf("in main: Pthread error %d",rc);
}
}
}
for (t = 0; t < NUM_THREADS; t++) { // join threads
pthread_join (threads[t], NULL);
}
// cleaning up
pthread_cond_destroy(&monitor.cond);
printf("Simulation ended\n");
return 0;
}
File added
// compile: gcc -Wall -Werror -Wextra -pedantic -lpthread -std=gnu11 aufgabe3.c -o aufgabe3
// ausführung ./aufgabe3 number_producer number_consumer amount_consume
// Vorlage vorlesung 8 folie 22 f
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#define NUM_PLACES 3 // buffer size
int NUM_THREADS;
typedef struct monitor
{
int last; // index of last element
int buffer[NUM_PLACES]; // buffer
int prod_count; //number of producing threads
pthread_mutex_t mutex;
pthread_cond_t cond;
} monitor_t;
monitor_t monitor;
int amount_consume;
void iJustDied() {
pthread_cond_broadcast(&monitor.cond);
}
void ablegen (long threadId, int product) {
pthread_mutex_lock(&monitor.mutex);
while (NUM_PLACES == monitor.last) {
pthread_cond_wait(&monitor.cond, &monitor.mutex);
}
printf("Thread %ld puts %d into the buffer at place %d\n", threadId, product, monitor.last);
monitor.buffer[monitor.last] = product; // "real" production
monitor.last++;
pthread_cond_broadcast(&monitor.cond);
pthread_mutex_unlock(&monitor.mutex);
}
void entnehmen (long threadId) {
pthread_mutex_lock(&monitor.mutex);
while (monitor.last < amount_consume && monitor.prod_count > 0) {
pthread_cond_wait(&monitor.cond, &monitor.mutex);
}
if (monitor.prod_count > 0) {
for (int j = 0; j < amount_consume; j++) {
monitor.last--;
printf("Thread %ld takes %d from the buffer at place %d\n", threadId, monitor.buffer[0], 0);
for (int i = 0; i < NUM_PLACES - 1; i++) {
monitor.buffer[i] = monitor.buffer[i+1];
}
}
}
pthread_cond_signal(&monitor.cond);
pthread_mutex_unlock(&monitor.mutex);
}
void* Producer (void *threadid) {
for (int i = (int) threadid * 1000; i < ((int) threadid + 1) * 1000; i++) {
ablegen((long) threadid, i);
}
monitor.prod_count--;
iJustDied();
pthread_exit(NULL);
}
void* Consumer (void *threadid) {
while (monitor.prod_count > 0) { // as long as someone produces
entnehmen((long) threadid);
}
pthread_exit(NULL);
}
int main(int argc, char const *argv[]) {
if (argc != 4) { // check if enough arguemntes
printf("\n Too many or not enough arguments needs to be exactly 3 \n");
exit(-1);
}
int num_producer = strtol(argv[1], NULL, 10); // cast cl arguemnts to long
int num_comsumer = strtol(argv[2], NULL, 10);
amount_consume = strtol(argv[3], NULL, 10);
if (num_producer < 1 || num_comsumer < 1) { // prevent some stupid input
printf("There must be at least one producer and one consumer\n");
exit(-1);
}
if (amount_consume > NUM_PLACES || amount_consume <= 0) {
printf("Can't consume more than there is and have to consume something.");
exit(-1);
}
NUM_THREADS = num_producer + num_comsumer; // producer + consumer
pthread_t threads[NUM_THREADS]; // thread array
int rc; // thread return code
long t; // thread ids
monitor.last = 0;
monitor.prod_count = num_producer;
if (pthread_mutex_init(&monitor.mutex, NULL) != 0) {
perror("pthread_mutex_init");
exit(1);
}
if (pthread_cond_init(&monitor.cond, NULL) != 0) {
perror("pthread_cond_init");
exit(1);
}
for (t = 0; t < NUM_PLACES; t++) { // initialize buffer
monitor.buffer[t] = 0; // no production yet
}
for (t = 0; t < NUM_THREADS; t++) { // creating threads
if (t < num_producer){
rc = pthread_create (&threads[t], NULL, Producer, (void *)t);
if(rc){ // error handling
printf("in main: Pthread error %d",rc);
}
}
else {
rc = pthread_create (&threads[t], NULL, Consumer, (void *)t);
if(rc){ // error handling
printf("in main: Pthread error %d",rc);
}
}
}
for (t = 0; t < NUM_THREADS; t++) { // join threads
pthread_join (threads[t], NULL);
}
// cleaning up
pthread_cond_destroy(&monitor.cond);
printf("Simulation ended\n");
return 0;
}
No preview for this file type
......@@ -20,21 +20,23 @@ grammieren Sie zur Absicherung einen Monitor, der mindestens die Funktionen Able
anbietet. Die jeweilige Anzahl der beteiligten Produzent*innen bzw. Konsument*innen soll beliebig, aber
fest gewählt werden können. Die einzelnen Aktionen und der Zustand des Puffers soll jeweils ausgegeben
werden. Dokumentieren Sie Ihr jeweiliges Programm und stellen Sie immer die Ausgaben des jeweiligen
Programms zur Verfügung.} \\
Vorlage Monitor
\url{https://gist.github.com/banderson623/5161756}
Programms zur Verfügung.}
\lstinputlisting[style=C, title=Aufgabe 1]{aufgabe1.c}
\lstinputlisting[style=C, firstline=1, lastline=17, title=Ausgabe Aufgabe 1]{Ausgabe.txt}
% ///////////////////// Aufgabe 2 ///////////////////
\section{Reihenfolge erhaltende Produktion \hfill (8 Punkte)}
\textit{Erweitern Sie Ihre Lösung der Aufgabe 1 so, dass die Produkte in der Reihenfolge entnommen werden,
in der sie abgelegt wurden.} \\
in der sie abgelegt wurden.}
\lstinputlisting[style=C, title=Aufgabe 2]{aufgabe2.c}
% ///////////////////// Aufgabe 3 ///////////////////
\section{Unterscheidung des Konsums \hfill (10 Punkte)}
\textit{Erweitern Sie Ihre Lösung der Aufgabe 2 so, dass die Konsument*innen jeweils beim Entnehmen eine
beliebig, aber fest gewählte Anzahl von Produkten abholen.} \\
beliebig, aber fest gewählte Anzahl von Produkten abholen.}
\lstinputlisting[style=C, title=Aufgabe 3]{aufgabe3.c}
% /////////////////////// END DOKUMENT /////////////////////////
\end{document}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment