Kernel: το kill δεν πιάνει αυτο
P. Christeas
p_christ at hol.gr
Tue Dec 3 23:24:01 EET 2002
Στις Δευ 02 Δεκ 2002 8:14 μμ, ο/η Giorgos Keramidas έγραψε:
> Μπορούμε να μην γινόμαστε προσβλητικοί χωρίς λόγο στη λίστα; :PPP
βλ. τα πρώτα (από Ιαν. 2002) μου postings.
>
> Συγγνώμη που δεν απάντησα στο mail σου που ρώταγε για σημεία που
> μπορεί να κολλήσει κάποια process. Το μάρκαρα σαν `important' στο
> mutt και μετά το ξέχασα. Το μόνο σχετικό κομμάτι που μπόρεσα να
> καταλάβω διαβάζοντας πριν λίγο καιρό το source του FreeBSD kernel
> (όταν κάποιος ρώτησε κάτι αντίστοιχο σε μια από τις λίστες του
> FreeBSD) ήταν το εξής:
Φαίνεται οτι τα παρακάτω είναι BSD-specific. Υποψιάζομαι οτι το πρόβλημά μου
εμφανίζεται μονο στο Linux. Όταν το εντοπίσω (δηλ. τις ακριβείς συνθήκες) να
είστε σίγουροι οτι θα τρέξουμε το προγραμματάκι και στο BSD για να δουμε τί
γίνεται εκεί.
>
> = Στο αρχείο src/sys/kern/kern_sig.c υπάρχει η killproc() που στέλνει
> το SIGKILL σε διεργασίες. Αυτή ξεκινά με το παρακάτω:
>
> PROC_LOCK_ASSERT(p, MA_OWNED);
>
> Δεν μπορεί δηλαδή το kernel να την καλέσει αν το process είναι
> κλειδωμένο από κάποιο άλλο κομμάτι του kernel. Αν το process είναι
> κλειδωμένο και προσπαθήσει κάποιος να στείλει SIGKILL, το kernel
> πανικοβάλλεται.
>
> = Στο ίδιο αρχείο, στην συνάρτηση psignal έχει ένα σχόλιο για το
> delivery των signals. Το kernel ελέγχει αν η διεργασία είναι
> stopped για κάποιο λόγο. Ακόμα κι αν το signal που προσπαθείς να
> στείλεις είναι το SIGKILL, σε περίπτωση που είναι σταματημένη για
> κάποιο λόγο η διεργασία (π.χ. είναι στην ουρά κάποιου driver και
> περιμένει να τελειώσει ένα I/O request, το οποίο έχει κολλήσει για
> κάποιο λόγο), το μόνο που κάνει η psignal() είναι να προσπαθεί να
> "ξυπνήσει" τα threads της διεργασίας για να πάρουν το signal και να
> τερματίσουν.
>
> # src/sys/kern/kern_sig.c::psignal()
>
> runfast:
> mtx_lock_spin(&sched_lock);
> FOREACH_THREAD_IN_PROC(p, td)
> tdsignal(td, sig, action);
> thread_unsuspend(p);
> mtx_unlock_spin(&sched_lock);
>
> = Η tdsignal() που καλείται από την psignal() για να ξυπνήσει τα
> threads ένα ένα δεν κάνει απολύτως τίποτα αν η διεργασία είναι σε
> uniterruptible κατάσταση.
>
> # src/sys/kern/kern_sig.c::tdsignal()
>
> if (TD_IS_SLEEPING(td)) {
> /*
> * If thread is sleeping uninterruptibly
> * we can't interrupt the sleep... the signal will
> * be noticed when the process returns through
> * trap() or syscall().
> */
> if ((td->td_flags & TDF_SINTR) == 0) {
> return;
> }
>
> Σύμφωνα με τα σχόλια της tdsignal(), αν μια διεργασία κάνει π.χ. ένα
> write() system call, που θα γράψει δεδομένα σε κάποιο NFS
> filesystem, και εντελώς τυχαία εκείνη τη στιγμή έχει πέσει το δίκτυο[1[]
> (οπότε το NFS write() δεν μπορεί να τερματίσει παρά μόνο μετά από
> κάποιου είδους timeout), η διεργασία δεν θα επιστρέψει από το system
> call άμεσα. Για όση ώρα είναι σε τέτοια κατάσταση δεν θα μπορείς
> όχι signal να της στείλεις, αλλά ούτε τον θεό τον ίδιο.
>
> Αυτά...
>
Ευχαριστώ, έδωσες την πιό πλήρη απάντηση μέχρι τώρα. Σίγουρα θα πρέπει να μπώ
στα ενδότερα του Linux kernel για να δω τι συμβαίνει.
Η δική μου περίπτωση είναι πολύ πιό απλή. Μιλάω για ένα TCP socket και μερικά
αρχεία (fd με select). Στο TCP ήταν συνδεδεμένο ένα client από τον localhost
(οπότε δεν υπάρχουν προβλήματα δικτύου). Το πρόβλημα είναι κατά τον
τερματισμό του app (δεν ξέρα αν είναι πριν την exit() ή μέσα σ' αυτήν).
Το signal-handler μου είναι της μορφής
void hnd_signal(ing signum){
if (signum==SIGTERM)
has_to_close=true;
}
και όπως καταλαβαίνεις δεν υπάρχει περίπτωση να κολλήσει εκεί μέσα.
Ο κώδικας είναι πολύ μεγάλος, για να τον στείλω. Προσπαθώ να απομονώσω το
κομματάκι που 'κολλάει'.
Υποπτεύομαι[1] οτι γίνεται το παρακάτω:
η εφαρμογή παίρνει το πρώτο signal και ξεκινάει κανονικά την διαδικασία
τερματισμού.
Σε κάποιο system call φτάνει το δεύτερο signal. εκεί κλειδώνει το signal
delivery του kernel. Το race μοιάζει σαν να περιμένει το system call να
τελειώσουν τα signals και τα signals να επιστρέψει το system call.
δευτερο σενάριο: να έρχονται τα signals τη στιγμή που η εφαρμογή είναι zombie
και το kernel να προσπαθεί να της τα δώσει (κρατώντας την ενεργή), ενώ δεν
υπάρχει εφαρμογή.
Εγώ έχω στείλει αρκετά (5-10 τουλάχιστον) signals, από τα οποία τα πρώτα δεν
είναι SIGQUIT αλλά SIGINT/TERM. Δηλ. η ουρά των signals έχει στην αρχή τα
handleable.
Α, σημαντικό: κατά τον τερματισμό, η εφαρμογή δεν επαναφέρει τους κανονικούς
signal handlers [2,3]
Βλέπω μία στιγμή την εφαρμογή stopped, πρίν το πρόβλημα, και μετά την
ζωντανεύω (run) με SIGCONT. Νομίζω όλες τις φορές που είχα πρόβλημα, είχα
στείλει τουλάχιστον ένα SIGCONT.
Είχα δοκιμάσει να σκοτώσω και το parent process (bash), αλλά πέρασε κι αυτό
στην uninterruptible κατάσταση που περιγράφω.
[1] Ψάχνω ακόμα να εξακριβώσω τις συνθήκες του προβλήματος. Επειδή η εφαρμογή
δεν δέχεται signals, δεν μπόρεσα να της "κολλήσω" τον gdb. Έχω λοιπόν πολύ
λίγα εργαλεία.
[2] ο κανονικός handler για το SIGINT είναι το ignore
[3] δεν προσπαθώ να κάνω καλύτερη την εφαρμογή, προσπαθώ να διορθώσω τον
kernel εδώ.
More information about the Linux-greek-users
mailing list