#!/usr/bin/perl # J.Kobus 27/11/2004, 28/11/2005 27/11/2006 30/11/2011 9/01/2015 # Producer-consumer problem using semaphores (A.S.Tanenbaum, Modern # Operating Systems, p.43) # Uwaga. Zamiast korzystania z trzech różnych obiektów można utworzyć jeden obiekt, z # którym związana jest tablica semaforów my ($empty, $empty_slots, $full, $full_slots, $mutex); my ($comm, $d, $maxitem, $user, $usleep); my ($usertime, $systemtime, $maxrss, $ixrss, $idrss, $isrss, $minflt, $majflt, $nswap, $inblock, $oublock, $msgsnd, $msgrcv, $nsignals, $nvcsw, $nivcsw); our ($opt_e, $opt_h, $opt_r, $opt_s, $opt_u); use Getopt::Std; use Text::Wrap; use Errno qw(EAGAIN); use IPC::SysV qw(IPC_PRIVATE S_IRWXU IPC_CREAT); use IPC::Semaphore; use Time::HiRes qw(gettimeofday usleep); use BSD::Resource; use strict; getopts('e:hs:u:'); if ($opt_h) { help(); } if ($opt_r) { # remove semaphors; handy option when you see the following message # 'Can't call method "getval" on an undefined value at ...' $user=$ENV{USER}; system (" ipcs -s | grep $user | gawk \'\{print \$2\}\' > /tmp/.ipcstmp" ); open (TMP,") { if ( ! "" ) { system ("ipcrm -s $_"); } } exit; } if ($opt_s) { $empty_slots = $opt_s; } else { $empty_slots = 5; } if ($opt_e) { $maxitem = $opt_e; } else { $maxitem = 10; } if ($opt_u) { $usleep = $opt_u; } else { $usleep = 1000; } my $run_prod = shift || 1; my $run_cons = shift || 3; my $timeusec; my $item=0; my $width=7; (my $lines,$Text::Wrap::columns)=split(' ',`stty size`); ####################################################################################################### sub cprintf1a { my $f1=shift; my $f2=shift; my $f3=shift; my $tstamp=timeusec(); # printf "%-18s%-3d%-14s\n", $f1, $f2, $f3; printf "[ %-15.4f ] %-24s %-3d%-14s\n", $tstamp, $f1, $f2, $f3; } sub cprintf1b { my $f1=shift; my $f2=shift; my $f3=shift; my $tstamp=timeusec(); # printf "%-18s%-3d%-14s\n", $f1, $f2, $f3; printf "[ %-15.4f ] %-28s %-3d%-14s\n", $tstamp, $f1, $f2, $f3; } sub cprintf1c { my $f1=shift; my $f2=shift; my $f3=shift; my $tstamp=timeusec(); # printf "%-18s%-3d%-14s\n", $f1, $f2, $f3; printf "[ %-15.4f ] %-32s %-3d%-14s\n", $tstamp, $f1, $f2, $f3; } sub cprintf1d { my $f1=shift; my $f2=shift; my $f3=shift; my $tstamp=timeusec(); # printf "%-18s%-3d%-14s\n", $f1, $f2, $f3; printf "[ %-15.4f ] %-36s %-3d%-14s\n", $tstamp, $f1, $f2, $f3; } sub cprintf2 { my $f1=shift; my $f2=shift; my $tstamp=timeusec(); # printf "%-18s %-${width}d%-${width}d%6.2f\n", # $f1,$empty->getval(IPC_PRIVATE),$full->getval(IPC_PRIVATE),$f2; printf "[ %-15.4f ] %-60s %-${width}d%-${width}d\n",$tstamp, $f1,$empty->getval(IPC_PRIVATE),$full->getval(IPC_PRIVATE); } sub down_mutex { $mutex->op(0,-1,0); } sub up_mutex { $mutex->op(0,1,0); } sub down_empty { $empty_slots--; $empty->op(0,-1,0); } sub up_empty { $empty_slots++; $empty->op(0,1,0); } sub down_full { $full_slots--; $full->op(0,-1,0); } sub up_full { $full_slots++; $full->op(0,1,0); } sub run { my $subname=shift; my $runtime=shift; my $start=timeusec(); my $stop=$start; my $tstamp=0; # $max: zależny od maszyny parametr określający dokładność z jaką odmierzany jest czas zajęcia CPU; # wartości rzędu 1000 dają dokładność lepszą niż 10^{-4} dla Lenovo T440s, 10^{-3} dla toru/polonu # my $max=5000; while ( $stop-$start<$runtime) { my $a; for (my $i=1; $i<=$max; $i++) { $a=exp($start); } $stop=timeusec(); } $tstamp=timeusec(); # printf "[ %-15.4f ] run: delay for $subname\n", $tstamp; # printf "[ %-15.4f ] delay for $subname\n", $tstamp; # printf "[ %-15.4f ] delay %-3.1f for $subname\n", $tstamp,$runtime } sub produce_item { my $item = shift; run("produce_item",$run_prod); # cprintf1("produce_item: ",$item,"wyprodukowany"); cprintf1a("producer: produce_item ",$item,""); } sub consume_item { my $item = shift; run("consume_item", $run_cons); # cprintf1("consume_item: ",$item,"zużyty"); cprintf1d("consumer: consume_item ",$item,""); } sub enter_item { my $item=shift; # cprintf1("enter_item: ",$item,"odłożony na półkę"); # cprintf1("enter_item: ",$item,""); cprintf1b("producer: enter_item ",$item,""); } sub remove_item { my $item=shift; # cprintf1("remove_item: ",$item,"zdjęty z półki"); cprintf1c("consumer: remove_item ",$item,""); } sub timeusec { (my $s,my $us)=gettimeofday; $timeusec=$s+$us*0.000001; } sub help { print << "EOFF"; NAZWA $0 - ilustracja rozwiązania problemu producenta-konsumenta SKŁADNIA $0 [-dh] [ -e #elementów ] [ -s #przegródek ] [ -u usleep ] [ czas_prod [czas_kons] ] OPIS Program ilustruje w jaki sposób przy pomocy trzech semaforów można rozwiązać problem producenta-konsumenta (patrz A.S.Tanenbaum, Modern Operating Systems, p.43). Domyślnie wytwarzanych jest 10 elementów, które odkładane są na półkę o 5 przegrodach. Producent (proces rodzica) wytwarza elementy w tempie jednego na sekundę, a konsument (proces potomny) zużywa je w tempie jednego na 3 sekundy. Te wartości można zmienić podając parametry czas_prod i czas_kons. Program pozwala śledzić kolejność i czas trwania poszczególnych faz produkcji i konsumpcji dzięki znacznikom czasu (liczba sekund od początku epoki) wskazującym na moment wykonania danej czynności. Dodatkowo wypisywane są także wartości semaforów empty i full. Dokładność z jaką odmierzany jest czas produkcji i konsumpcji określony jest przez odpowiedni parametr zależny od szybkości procesora (zob. procedura run). Decyduje on o długości trwania obliczeń i powinien być tak dobrany, aby czas wykonania obliczeń nie był większy niż około 100 mikrosekund. -e liczba_elementów zmiana maksymalnej liczby elementów możliwych do wyprodukowania (domyślnie 5) -h pomoc -s liczba_przegród zmiana liczby miejsc, do których można odkładać wytworzone elementy (domyślnie 2) -u czas czas (w mikrosekundach, domyślnie 1000), który upływa między kolejnymi wywołaniami funkcji waitpid przez proces macierzysty w celu sprawdzenia, czy proces potomny jeszcze działa PRZYKŁADY $0 1 5 $0 -e 10 -s 5 0.1 1 $0 -e 10 -s 2 0.1 2 $0 -e50 -s5 0.01 0.1 AUTOR Jacek Kobus EOFF exit; } sub consumer { my $maxitem=shift; my $startc=timeusec(); my ($item, $endc); for ($item=1; $item<=$maxitem; $item++) { cprintf2("consumer: beg",$endc); down_full; down_mutex; remove_item($item); up_mutex; up_empty; $endc=timeusec()-$startc; consume_item($item); cprintf2("consumer: end",$endc); } # zwróć liczbę zużytych elementów return $item; } sub producer { my $maxitem=shift; my $startp=timeusec; my ($item,$endp); for ($item=1; $item<=$maxitem; $item++) { cprintf2("producer: beg",$endp); produce_item($item); down_empty; down_mutex; enter_item($item); up_mutex; up_full; $endp=timeusec()-$startp; cprintf2("producer: end",$endp); } # zwróć liczbę wyprodukownych elementów return $item; } $comm="Producent może wyprodukować maksymalnie $maxitem elementów, ale w magazynie można pomieścić tylko $empty_slots."; print "\n",wrap("","",$comm),"\n\n"; $comm="Elementy są produkowane w tempie jednego na $run_prod sekundy i używane w tempie jednego na $run_cons sekundy."; print wrap("","",$comm),"\n\n"; #my $comm="Zmiana wartości domyślnych: patrz $0 -h\n"; #print wrap("","",$comm),"\n"; # używamy 3 semaforów $mutex = new IPC::Semaphore(IPC_PRIVATE, 100, S_IRWXU | IPC_CREAT); $mutex->op(0, 1, 0); #$empty_slots = 10; # liczba miejsc w buforze $empty = new IPC::Semaphore(IPC_PRIVATE, 100, S_IRWXU | IPC_CREAT); $empty->op(0,$empty_slots,0); $full_slots = 0; $full = new IPC::Semaphore(IPC_PRIVATE, 100, S_IRWXU | IPC_CREAT); $full->op(0,$full_slots,0); printf "%89s\n", "empty full"; my ($item_parent, $item_child); FORK: { if (my $pid = fork) { # proces macierzysty my $start_prod_cons=timeusec(); printf "[ %-15.4f ] parent: CPID=$pid\n", $start_prod_cons; $item_parent=producer ($maxitem); use POSIX ":sys_wait_h"; my $child=0; do { usleep($usleep); $child = waitpid(-1, WNOHANG); } until $child > 0; ($usertime, $systemtime, $maxrss, $ixrss, $idrss, $isrss, $minflt, $majflt, $nswap, $inblock, $oublock, $msgsnd, $msgrcv, $nsignals, $nvcsw, $nivcsw) = getrusage(RUSAGE_SELF); # $inblock, $oublock, $msgsnd, $msgrcv, $nsignals, $nvcsw, $nivcsw) = getrusage(RUSAGE_CHILDREN); $mutex->remove; $empty->remove; $full->remove; printf "\n%-13s\n usertime %6.3f\n systemtime %6.3f\n nvcsw %-d8\n nivcsw %-d8\n", "parent:",$usertime,$systemtime,$nvcsw,$nivcsw; my $stop_prod_cons=timeusec(); } elsif (defined $pid) { # proces potomny; jeśli potomek istnieje, to $pid jest zdefiniowane i równe 0 my $tstamp=timeusec(); my $ppid=getppid(); printf "[ %-15.4f ] child: PPID=$ppid\n", $tstamp; $item_child=consumer($maxitem); ($usertime, $systemtime, $maxrss, $ixrss, $idrss, $isrss, $minflt, $majflt, $nswap, $inblock, $oublock, $msgsnd, $msgrcv, $nsignals, $nvcsw, $nivcsw) = getrusage(); printf "\n%-13s\n usertime %6.3f\n systemtime %6.3f\n nvcsw %-d8\n nivcsw %-d8\n", "child:",$usertime,$systemtime,$nvcsw,$nivcsw; } elsif ($! == EAGAIN) { # EAGAIN przypuszczalnie odwracalny błąd przy tworzeniu potomka # sleep 2; # redo FORK; } else { # złośliwy błąd przy tworzeniu procesu potomnego die "Nie mozna wykonać fork: $!\n"; } } # The elements of the list are, in order: index name meaning usually (quite system # dependent) # 0 utime user time # 1 stime system time # 2 maxrss maximum shared memory or current resident set # 3 ixrss integral shared memory # 4 idrss integral or current unshared data # 5 isrss integral or current unshared stack # 6 minflt page reclaims # 7 majflt page faults # 8 nswap swaps # 9 inblock block input operations # 10 oublock block output operations # 11 msgsnd messages sent # 12 msgrcv messaged received # 13 nsignals signals received # 14 nvcsw voluntary context switches # 15 nivcsw involuntary context switches