geomagas Δημοσ. 1 Ιουνίου 2014 Δημοσ. 1 Ιουνίου 2014 Καλό μας μήνα! The drill: Προσπαθώντας να ξεσκουριάσω το C part of my brain, είπα να κάνω λίγη εξάσηκση. Το θέμα του drill είναι μία εφαρμογή με δύο threads: - Το main thread, αφού δημιουργήσει το δεύτερο, περιμένει είσοδο από το πληκτρολόγιο κάπως έτσι: while((in=mygetch())!='q') printf("You pressed %c\n",in); Επειδή τα παλιά τα χρόνια "ήμουν" σε DOS και εκεί υπήρχε το notorious conio.h, αλλά τώρα αυτά πρέπει να γίνουν σε *nix, και προκειμένου να προχωρήσω βρήκα έτοιμη την mygetch() για blocking είσοδο από το πληκτρολόγιο: int mygetch ( void ) { int ch; struct termios oldt, newt; tcgetattr ( STDIN_FILENO, &oldt ); newt = oldt; newt.c_lflag &= ~( ICANON | ECHO ); tcsetattr ( STDIN_FILENO, TCSANOW, &newt ); ch = getchar(); tcsetattr ( STDIN_FILENO, TCSANOW, &oldt ); return ch; } - Το δεύτερο thread τώρα, αφού κάνει connect σε κάποιο server (βλ. sockets) διαχειρίζεται την επικοινωνία μαζί του, και μέσα σ' όλα, κάνει printf() τα μηνύματα που λαμβάνει. Όλα καλά μέχρι εδώ. Μόλις πατηθεί το q, γίνεται το ανάλογο cleanup και όλοι είμαστε χαρούμενοι. Τι γίνεται τώρα: Το πρόγραμμα θα πρέπει να μπορεί να τρέχει στο background. Όταν όμως το ξεκινώ έτσι, γίνεται suspended! Είναι βέβαιο ότι ο ένοχος είναι η mygetch() διότι: 1) Αν αλλάξω το loop με while(1); όλα δουλεύουν κανονικά. 2) φτιάχνω ένα minimum example, χωρίς threads, sockets και λοιπούς συγγενείς: #include <stdio.h> #include <termios.h> #include <unistd.h> int mygetch ( void ) { int ch; struct termios oldt, newt; tcgetattr ( STDIN_FILENO, &oldt ); newt = oldt; newt.c_lflag &= ~( ICANON | ECHO ); tcsetattr ( STDIN_FILENO, TCSANOW, &newt ); ch = getchar(); tcsetattr ( STDIN_FILENO, TCSANOW, &oldt ); return ch; } int main(int argc, char *argv[]) { char in; while((in=mygetch())!='q') printf("You pressed %c\n",in); return 0; } ...και έχει την ίδια συμπεριφορά (suspended στο background). Bottom line: Αν και μία εξήγηση του λόγου που γίνονται αυτά θα ήταν χρήσιμη και σε μένα και σε άλλους, αυτό που θα ήθελα κυρίως αυτή τη στιγμή είναι μία εναλλακτική, ώστε να το ξεπεράσω και να ασχοληθώ με το κυρίως πρόβλημα. Θα προτιμούσα η εναλλακτική να μην έχει να κάνει με curses/ncurses. Ιδέες;
imitheos Δημοσ. 1 Ιουνίου 2014 Δημοσ. 1 Ιουνίου 2014 % ./a.out & [1] 1762 [1] + suspended (tty input) ./a.out Όπως υπέθεσες φταίει η getch. Επειδή έχεις είσοδο εκπέμπεται signal και το πρόγραμμα πρέπει να σταματήσει και να περιμένει για την είσοδο. Πολλές φορές χρειάζεσαι στο πρόγραμμά σου να κάνεις trap κάποιο signal ώστε να μην φτάσει εκεί που πρέπει. Στην παρούσα περίπτωση όμως αυτό δεν σε βολεύει γιατί πώς θα δεχτεί την είσοδο το πρόγραμμα ? Εναλλακτική λύση δυστυχώς δεν υπάρχει (τουλάχιστον δεν μου έρχεται εμένα στο μυαλό). Αυτό που μπορείς να κάνεις είναι να βλέπεις αν τρέχεις σε τερματικό και να αλλάζεις τον τρόπο που δουλεύεις ανάλογα. Ο κλασικός τρόπος για να γίνει αυτό είναι με την isatty η οποία, όπως λέει και το όνομά της, σου εμφανίζει αν κάποιο handle είναι tty. #include <stdio.h> #include <termios.h> #include <unistd.h> int main(int argc, char *argv[]) { char in; char ttyin; ttyin=isatty(fileno(stdin)); if (ttyin) { printf("stdin is a terminal. running until q\n"); while((in=getchar())!='q') printf("You pressed %c\n",in); } else { printf("stdin is not a terminal. running until killed\n"); /* while(1) { * } */ } return 0; } % cc -Wall kk.c % ./a.out stdin is a terminal. running until q fjq You pressed f You pressed j % ./a.out < /dev/null stdin is not a terminal. running until killed % echo "hello" | ./a.out stdin is not a terminal. running until killed % ./a.out & [1] 2098 stdin is a terminal. running until q [1] + suspended (tty input) ./a.out Όπως βλέπεις, ελέγχουμε αν το standard input είναι tty (αντί για fileno(stdin) θα μπορούσα να βάλω κατευθείαν 0 απλά το έκανα έτσι για να είναι πιο παραστατικό) και σε οποιαδήποτε περίπτωση έχουμε redirection του, το πρόγραμμα παίζει όπως θέλουμε. Στην περίπτωση όμως που βάζουμε το πρόγραμμα στο παρασκήνιο δεν έχουμε redirection οπότε προσπαθεί να τρέξει την getchar. Στη παρούσα περίπτωση λοιπόν που σε ενδιαφέρει το background, μπορείς να τρέξεις μια άλλη συνάρτηση που λέγεται tcgetpgrp η οποία σου επιστρέφει το ID της εφαρμογής που τρέχει στο τερματικό που έχει ανοίξει κάποιο handle. Ας αλλάξουμε λοιπόν λίγο τον κώδικα. #include <stdio.h> #include <termios.h> #include <unistd.h> int main(int argc, char *argv[]) { pid_t mypid, mypgrp; mypid = getpid(); mypgrp = tcgetpgrp(fileno(stdin)); printf("Process ID = %d\n", mypid); printf("Process Foreground Group = %d\n", mypgrp); return 0; } Το pid_t δεν πρέπει να μεταχειρίζεται σαν int αλλά το έκανα έτσι στο printf χάριν ευκολίας. Ας δούμε τώρα τι έξοδο παίρνουμε στις διάφορες περιπτώσεις. % cc -Wall kk.c % ./a.out Process ID = 2186 Process Foreground Group = 2186 % ./a.out < /dev/null Process ID = 2188 Process Foreground Group = -1 % echo hello | ./a.out Process ID = 2191 Process Foreground Group = -1 % ./a.out& [2] 2193 Process ID = 2193 Process Foreground Group = 1740 [2] - done ./a.out Στην περίπτωση που το πρόγραμμα τρέχει κανονικά στο προσκήνιο, τότε η tcgetpgrp επιστρέφει το ίδιο ID με την getpid γιατί το πρόγραμμά σου είναι αυτό που έχει ανοιχτό το standard input. Όταν τρέχεις στο background, τότε επιστρέφει το ID του shell από το οποίο έτρεξες το πρόγραμμά γιατί εκεί ανήκει το standard input. Με το συνδυασμό αυτών των δύο συναρτήσεων μπορείς να παίξεις και να το φέρεις στα μέτρα σου. Να μπορείς να χρησιμοποιήσεις πάντα την getchar δεν νομίζω να γίνεται αλλά περίμενε μήπως ρίξει κανείς άλλος ιδέες.
migf1 Δημοσ. 1 Ιουνίου 2014 Δημοσ. 1 Ιουνίου 2014 Καλησπέρα, έχω πολύ καιρό να ασχοληθώ με τέτοια πράγματα, αλλά από ότι θυμάμαι πρέπει να κάνεις monitoring το keyboard στο foreground process, κι αν έχεις δραστηριότητα τότε να κάνεις wake (fg) το κοιμώμενο process (bg) ή να του στείλεις τα keys ως input. Αν τα θυμάμαι σωστά, το keyboard είναι πάντα attached στο foreground process. Ή μπορείς να το κάνεις έμμεσα. To fg process να στέλνει το keyboard input σε κάποιο file (ή pipe) και το background process να διαβάζει ως input του τα περιεχόμενα αυτού του file. Το πως κάνεις monitor το keyboard state, αν δεν θες να χρησιμοποιήσεις έτοιμα libs, μάλλον δεν είναι και πολύ όμορφο αν το κάνεις χειροκίνητα. Με ένα πρόχειρο googling βρήκα αυτό εδώ: http://unix.stackexchange.com/questions/94322/is-it-possible-for-a-daemon-i-e-background-process-to-look-for-key-presses-fr που ρωτάει άλλο πράγμα, αλλά στις απαντήσεις περιλαμβάνεται το keyboard monitoring (βασικά με ioctl() και input.h ).
migf1 Δημοσ. 1 Ιουνίου 2014 Δημοσ. 1 Ιουνίου 2014 Στα *nix, δεν υπαρχει η εννοια του main loop; Τί εννοείς;
defacer Δημοσ. 1 Ιουνίου 2014 Δημοσ. 1 Ιουνίου 2014 Το main loop δεν είναι θέμα λειτουργικού αλλά framework (ας το πούμε). Το εκάστοτε windowing system είναι αυτό που σου λέει "λοιπόν εδώ η δουλειά γίνεται με messages, εγώ τροφοδοτώ και συ επεξεργάζεσαι". Χωρίς windowing system ή τέλος πάντων άλλο setup που να λειτουργεί έτσι δεν έχει νόημα να μιλάμε για loop.
geomagas Δημοσ. 1 Ιουνίου 2014 Μέλος Δημοσ. 1 Ιουνίου 2014 Κατ' αρχάς, ευχαριστώ για τις απαντήσεις. Πολλή τροφή για σκέψη, και θα πρέπει να τα πάρω με τη σειρά! Όπως υπέθεσες φταίει η getch. Επειδή έχεις είσοδο εκπέμπεται signal και το πρόγραμμα πρέπει να σταματήσει και να περιμένει για την είσοδο. Πολλές φορές χρειάζεσαι στο πρόγραμμά σου να κάνεις trap κάποιο signal ώστε να μην φτάσει εκεί που πρέπει. Στην παρούσα περίπτωση όμως αυτό δεν σε βολεύει γιατί πώς θα δεχτεί την είσοδο το πρόγραμμα ? Ναι, το κάτάλαβα ότι παίζει κάτι τέτοιο, αλλά υπέθεσα ότι θα γίνει suspend το thread και όχι όλο το process, λάθος; Αν γίνει suspend το thread που περιμένει είσοδο από το πληκτρολόγιο, δεν με ενδιαφέρει, γι αυτό άλλωστε και τα χώρισα έτσι. Αρκεί να συνεχίσει να τρέχει το δεύτερο. Εναλλακτική λύση δυστυχώς δεν υπάρχει (τουλάχιστον δεν μου έρχεται εμένα στο μυαλό). Αυτό που μπορείς να κάνεις είναι να βλέπεις αν τρέχεις σε τερματικό και να αλλάζεις τον τρόπο που δουλεύεις ανάλογα. Θα προτιμούσα να είναι πιο generic, με την έννοια να μη χρειάζεται να είναι aware για τέτοια πράγματα. Αλλά άσε με να "χωνέψω" τη μέθοδο, και το συζητάμε. Είπαμε, τροφή για σκέψη! EDIT: Είδα λίγο αυτό: #include <stdio.h> #include <termios.h> #include <unistd.h> int main(int argc, char *argv[]) { char in; char ttyin; ttyin=isatty(fileno(stdin)); if (ttyin) { printf("stdin is a terminal. running until q\n"); while((in=getchar())!='q') printf("You pressed %c\n",in); } else { printf("stdin is not a terminal. running until killed\n"); /* while(1) { * } */ } return 0; } Εδώ κάνεις τον έλεγχο στην αρχή. Τι γίνεται αν ξεκινήσεις στο foreground και μετά ^Z και bg; Δεν θα ήταν σωστότερο να έχεις ένα loop και μετά branch σε κάθε iteration; -------- @migf1, ισχύει το ίδιο (για τη χώνεψη! ) -------- Στα *nix, δεν υπαρχει η εννοια του main loop; Εννοείς κάτι σαν το WndProc (ή κάπως έτσι, δεν θυμάμαι καλά) των Windows; Αν ναι, δεν είμαι σίγουρος για το τι ισχύει σε *nix, αλλά αυτό είναι σίγουρα (τουλάχιστον) ένα layer παραπάνω από αυτό που χρειάζομαι. Μόλις τελειώσω με τις "οπλασκήσεις", θα ασχοληθώ.
geomagas Δημοσ. 1 Ιουνίου 2014 Μέλος Δημοσ. 1 Ιουνίου 2014 Καλησπέρα, έχω πολύ καιρό να ασχοληθώ με τέτοια πράγματα, αλλά από ότι θυμάμαι πρέπει να κάνεις monitoring το keyboard στο foreground process, κι αν έχεις δραστηριότητα τότε να κάνεις wake (fg) το κοιμώμενο process (bg) ή να του στείλεις τα keys ως input. Αν τα θυμάμαι σωστά, το keyboard είναι πάντα attached στο foreground process. Ή μπορείς να το κάνεις έμμεσα. To fg process να στέλνει το keyboard input σε κάποιο file (ή pipe) και το background process να διαβάζει ως input του τα περιεχόμενα αυτού του file. Το πως κάνεις monitor το keyboard state, αν δεν θες να χρησιμοποιήσεις έτοιμα libs, μάλλον δεν είναι και πολύ όμορφο αν το κάνεις χειροκίνητα. Με ένα πρόχειρο googling βρήκα αυτό εδώ: http://unix.stackexchange.com/questions/94322/is-it-possible-for-a-daemon-i-e-background-process-to-look-for-key-presses-fr που ρωτάει άλλο πράγμα, αλλά στις απαντήσεις περιλαμβάνεται το keyboard monitoring (βασικά με ioctl() και input.h ). Οφείλω να ομολογήσω ότι κακώς επικέντρωσα την προσοχή στο πληκτρολόγιο, καθώς αυτό που ουσιαστικά με ενδιαφέρει είναι το stdin. Με άλλα λόγια, θα πρέπει να περιμένω και καταστάσεις όπως: ./a.out <<< executethiscommandstreamandexitq Κατά τα άλλα, εδώ δεν έχουμε ένα fg και ένα bg process, αλλά μόνο ένα process με δύο threads που, ως επί το πλείστον, θα πρέπει να περνούν την ώρα τους σε κατάσταση blocked: το ένα blocked πάνω σε ένα socket και το άλλο blocked πάνω στο stdin (αν υπάρχει τέτοια έννοια τελικά...). Εφόσον τα δύο threads βλέπουν κοινή μνήμη, δεν υπάρχει λόγος για pipes και files. Θέλοντας να συνδυάσω το ioctl() στο link που έβαλες με το stdin, οδηγήθηκα εδώ, όπου ο τύπος προτείνει πάνω-κάτω την mygetch() που έχω ήδη...
migf1 Δημοσ. 1 Ιουνίου 2014 Δημοσ. 1 Ιουνίου 2014 ... Κατά τα άλλα, εδώ δεν έχουμε ένα fg και ένα bg process, αλλά μόνο ένα process με δύο threads που, ως επί το πλείστον, θα πρέπει να περνούν την ώρα τους σε κατάσταση blocked: το ένα blocked πάνω σε ένα socket και το άλλο blocked πάνω στο stdin (αν υπάρχει τέτοια έννοια τελικά...). Εφόσον τα δύο threads βλέπουν κοινή μνήμη, δεν υπάρχει λόγος για pipes και files. ... Ναι αλλά εσύ είπες πως θέλεις να τρέχει το process σου (με τα 2 threads) στο background. Μόλις το στείλεις στο background παύει να έχει πρόσβαση στο stdin (που από default είναι attached στο keyboard)... δεν νομίζω πως μπορείς να κάνεις κάτι για αυτό, γιατί έτσι δουλεύει το *nix (ψάξε το κι εσύ γιατί μπορεί να θυμάμαι λάθος, αν και είμαι 99% σίγουρος πως έτσι γίνεται). Άρα, με ένα μόνο process ότι και να κάνεις με τα threads μέσα του μόλις μπει στο background πάπαλα το stdin. Access στο stdin έχουν μονάχα τα foreground processes. Οπότε, αυτό που εννοούσα παραπάνω είναι να δουλέψεις με 2 processes (π.χ. με fork()). ΥΓ. Τα threads πώς τα κάνεις suspend και resume? Από ότι θυμάμαι ελάχιστα *nix implementations υποστηρίζουν τις pthread_suspend/resume_np(), οπότε υποθέτω το κάνεις με condition vars & mutexes, τα οποία όμως κοντρολάρουν το suspension/resume τους εκ των έσω (δηλαδή δεν τα κοντρολάρει ο πατέρας τους). Γενικώς, δες μήπως τελικά σε εξυπηρετεί καλύτερα να το κάνεις multi-processed αντί για multi-threaded (και να κάνεις share τη μνήμη με mmap ή posix/sysv_shm... ανάλογα τι έχεις διαθέσιμο στην πλατφόρμα σου).
geomagas Δημοσ. 2 Ιουνίου 2014 Μέλος Δημοσ. 2 Ιουνίου 2014 Ναι αλλά εσύ είπες πως θέλεις να τρέχει το process σου (με τα 2 threads) στο background. Μόλις το στείλεις στο background παύει να έχει πρόσβαση στο stdin (που από default είναι attached στο keyboard)... δεν νομίζω πως μπορείς να κάνεις κάτι για αυτό, γιατί έτσι δουλεύει το *nix (ψάξε το κι εσύ γιατί μπορεί να θυμάμαι λάθος, αν και είμαι 99% σίγουρος πως έτσι γίνεται). Ναι οκ, ας μην έχω πρόσβαση στο stdin. Ας υποθέσουμε αρχικά ότι δεν μ' ενδιαφέρει (ξέχνα για λίγο το παράδειγμα με το <<< παραπάνω). Η αρχική μου απορία ήταν γιατί γίνεται suspend ολόκληρο το process και όχι μόνο το thread που υποτίθεται ότι περιμένει στο stdin. Εννοώ, και το άλλο thread περιμένει πάνω στο socket, αλλά δεν μπλοκάρει όλο το process, έτσι; Άρα, με ένα μόνο process ότι και να κάνεις με τα threads μέσα του μόλις μπει στο background πάπαλα το stdin. Access στο stdin έχουν μονάχα τα foreground processes. Οπότε, αυτό που εννοούσα παραπάνω είναι να δουλέψεις με 2 processes (π.χ. με fork()). Α οκ, στην αρχή νόμισα ότι εσύ είχες καταλάβει λάθος. Δεν κατάλαβα ότι ήταν εναλλακτική. Sorry! ΥΓ. Τα threads πώς τα κάνεις suspend και resume? Δεν τα κάνω explicitly. Το δεύτερο thread είναι αυτό: void *connection_loop(void *arg) { irc_connection *conn=(irc_connection *)arg; int numbytes; char buf[MAXDATASIZE],*readat=buf; while(numbytes=recv(conn->sockfd,readat,MAXDATASIZE-1-(readat-buf),0)) { if(numbytes<0) { perror("recv"); pthread_exit(&(conn->thread_result)); } else { readat[numbytes]='\0'; readat=process_buffer(conn,buf); } } return NULL; } Βλέπεις ότι θα μπλοκάρει στη recv(), καθώς το socket είναι blocking. Το κυρίως thread τώρα (αφαιρώντας τις "σάλτσες") είναι κάπως έτσι: irc_connection *server=irc_connect(params); char in; while((in=mygetch())!='q') printf("You pressed %c\n",in); irc_disconnect(server); Η irc_connect() αφού κάνει το κονέ και κάποιο initialization, εκτελεί int err=pthread_create(&(conn->thread),NULL,&connection_loop,conn); ...ελέγχει το err κι επιστρέφει. Η irc_disconnect() ...well, you get the point! Χτυπιέμαι λοιπόν να καταλάβω γιατί η recv() μπλοκάρει μόνο το thread της, ενώ η getch() μπλοκάρει όλο το process. Και αυτό, μόνο στο background. Όταν το πρόγραμμα είναι στο foreground, η καθεμία μπλοκάρει μόνο το δικό της thread!
migf1 Δημοσ. 2 Ιουνίου 2014 Δημοσ. 2 Ιουνίου 2014 Ναι οκ, ας μην έχω πρόσβαση στο stdin. Ας υποθέσουμε αρχικά ότι δεν μ' ενδιαφέρει (ξέχνα για λίγο το παράδειγμα με το <<< παραπάνω). Η αρχική μου απορία ήταν γιατί γίνεται suspend ολόκληρο το process και όχι μόνο το thread που υποτίθεται ότι περιμένει στο stdin. Εννοώ, και το άλλο thread περιμένει πάνω στο socket, αλλά δεν μπλοκάρει όλο το process, έτσι; ... Χτυπιέμαι λοιπόν να καταλάβω γιατί η recv() μπλοκάρει μόνο το thread της, ενώ η getch() μπλοκάρει όλο το process. Και αυτό, μόνο στο background. Όταν το πρόγραμμα είναι στο foreground, η καθεμία μπλοκάρει μόνο το δικό της thread! Όταν στέλνεις το process στο background, το thread που περιμένει είσοδο από την stdin κάνει suspend όλο το process. Έτσι δουλεύει το *nix (αλλά όπως σου είπα και πριν, ψάξτο και μόνος σου γιατί μπορεί να το θυμάμαι λάθος, αν και είμαι 99% σίγουρος). EDIT: Αντί να έχεις getch() δοκίμασε να κάνεις poll απευθείας το keyboard device με ioctl() όπως λέει εκείνο το link που σου έδωσα... αυτό λογικά δεν θα σου κάνει suspend το process.
H_ANARXIA_EINAI_PSEMA Δημοσ. 2 Ιουνίου 2014 Δημοσ. 2 Ιουνίου 2014 Ρε μάγκες γιατί να μην είναι στην C11 η λύση, τώρα που έβαλαν και τα threads;
migf1 Δημοσ. 2 Ιουνίου 2014 Δημοσ. 2 Ιουνίου 2014 Ρε μάγκες γιατί να μην είναι στην C11 η λύση, τώρα που έβαλαν και τα threads; Τι εννοείς; Αφενός η υλοποίηση της <thread.h> στην C11 συνήθως είναι wrapper είτε πάνω στην pthread είτε πάνω στο win32 api (ανάλογα την πλατφόρμα) κι αφετέρου το πρόβλημα εδώ δεν έχει να κάνει με το thread library αλλά με το λειτουργικό. @geomagas: Έβγαλες άκρη τελικά ή το παράτησες;
geomagas Δημοσ. 2 Ιουνίου 2014 Μέλος Δημοσ. 2 Ιουνίου 2014 @geomagas: Έβγαλες άκρη τελικά ή το παράτησες; Όχι δεν το παράτησα, απλά το έκανα suspend γιατί πρέπει να βγάλουμε και το ψωμί μας... Άλλωστε, δουλεύω παράλληλα και το κομμάτι της επικοινωνίας με το server, που είναι και το πραγματικό ζητούμενο. Εκεί, έχει πολλή δουλειά... Το απόγευμα ελπίζω να βρω χρόνο να ασχοληθώ.
H_ANARXIA_EINAI_PSEMA Δημοσ. 2 Ιουνίου 2014 Δημοσ. 2 Ιουνίου 2014 Τι εννοείς; Αφενός η υλοποίηση της <thread.h> στην C11 συνήθως είναι wrapper είτε πάνω στην pthread είτε πάνω στο win32 api (ανάλογα την πλατφόρμα) κι αφετέρου το πρόβλημα εδώ δεν έχει να κάνει με το thread library αλλά με το λειτουργικό. Εννοώ ότι ο geomagas δεν έχει δώσει testcase και παραπονιέται για κάτι που πιστεύει οτί ειναι OS specific. Δεν ξέρω πιο είναι το πρόβλημα αλλά αν υπαρχει standard λύση γιατί να μπλεχτείς με pthreads?
Προτεινόμενες αναρτήσεις
Δημιουργήστε ένα λογαριασμό ή συνδεθείτε για να σχολιάσετε
Πρέπει να είστε μέλος για να αφήσετε σχόλιο
Δημιουργία λογαριασμού
Εγγραφείτε με νέο λογαριασμό στην κοινότητα μας. Είναι πανεύκολο!
Δημιουργία νέου λογαριασμούΣύνδεση
Έχετε ήδη λογαριασμό; Συνδεθείτε εδώ.
Συνδεθείτε τώρα