2013-08-28 23 views
22

Podobne uwagi jak w pytaniu zostały podniesione przed here i here i znam się na bibliotece Google coredump (której nie oceniłem i nie znalazłem, choć mógłbym spróbować pracuj nad tym, jeśli lepiej zrozumiem problem).Zrzuty widełek i rdzenia z wątkami

Chcę uzyskać zrzut podstawowy uruchomionego procesu Linux bez przerywania procesu. Naturalnym podejściem jest powiedzieć:

if (!fork()) { abort(); } 

Ponieważ proces rozwidlony dostaje stałą migawkę kopię pamięci pierwotnego procesu, powinniśmy uzyskać pełny zrzut pamięci, a ponieważ kopia wykorzystuje kopiowanie przy zapisie, to powinien ogólnie tanie. Jednak krytycznym mankamentem tego podejścia jest to, że fork() wyświetla tylko bieżący wątek, a wszystkie inne wątki oryginalnego procesu nie istnieją w rozwidlonej kopii.

Moje pytanie brzmi, czy możliwe jest uzyskanie w jakiś sposób odpowiednich danych z innych, oryginalnych wątków. Nie jestem do końca pewien, jak podejść do tego problemu, ale tutaj jest kilka mniejszych pytań mam wymyślić:

  1. Czy pamięć, która zawiera wszystkie stosy nitek nadal dostępne i w rozwidlonym procesie?

  2. Czy można (szybkie) wyliczyć wszystkie działające wątki w oryginalnym procesie i zapisać adresy baz swoich stosów? Jak rozumiem, podstawa stosu wątków w systemie Linux zawiera wskaźnik do danych księgowych wątku jądra, więc ...

  3. z zapisanymi adresami bazowymi wątków, czy można odczytać odpowiednie dane dla każdego z nich oryginalne wątki w rozwidlonym procesie?

Jeśli jest to możliwe, być może będzie to tylko kwestia dołączając dane z innych wątków do zrzutu rdzenia. Jeśli jednak dane te zostaną już utracone w punkcie rozwidlenia, nie wydaje się, aby istniała jakakolwiek nadzieja na takie podejście.

+0

Nie mam teraz żadnej funkcji enumarion w pthreads ... - ale w odniesieniu do 'fork()' możesz chcieć spojrzeć na 'pthread_atfork()'. Wygląda na to, że używanie procedur obsługi zarejestrowanych przez te ostatnie może być użyte do wykonania własnego modułu wyliczającego pthread. – alk

+0

Być może powodem, dla którego tylko wyświetla bieżący wątek jest implementacja wątku w systemie Linux, używa 'clone()'. – Marcus

+0

@ Marcus: Nie martwiłem się tym, * dlaczego * 'fork()' zachowuje się tak, jak to robi. Jestem całkiem zadowolony z tego, co robi, co wydaje mi się rozsądne. –

Odpowiedz

14

Czy znasz proces checkpoint-restart? W szczególności, CRIU? Wydaje mi się, że może to stanowić dla ciebie łatwą opcję.

Chcę uzyskać zrzut główny działającego procesu Linux bez przerywania procesu [i] w celu uzyskania w jakiś sposób odpowiednich danych z innych, oryginalnych wątków.

Zapomnij o niezakłócaniu procesu. Jeśli się nad tym zastanowisz, zrzut rdzenia musi przerwać proces na czas trwania zrzutu na; Twoim prawdziwym celem musi być zatem zminimalizowanie czasu trwania tej przerwy. Twój pierwotny pomysł użycia fork() przerywa proces, po prostu robi to przez bardzo krótki czas.

  1. Czy pamięć, która zawiera wszystkie stosy wciąż dostępnych i w rozwidloną procesu podaje?

nr fork() zachowuje tylko gwint, który ma rzeczywistej połączenia i stosy na resztę gwintu są przerwane.

Oto procedura użyję, zakładając CRIU jest nieodpowiednia:

  • Czy proces nadrzędny, który generuje zrzut rdzenia procesu potomnego gdy dziecko jest zatrzymany. (Należy pamiętać, że można wygenerować więcej niż jedno zdarzenie zatrzymania, a tylko pierwsze, aż do następnego zdarzenia kontynuacji.)

    Możesz wykryć zdarzenia zatrzymania/kontynuacji za pomocą waitpid(child,,WUNTRACED|WCONTINUED).

  • Opcja: sched_setaffinity() ograniczyć proces z pojedynczym procesorem i sched_setscheduler() (a może sched_setparam()) spadek priorytet procesową IDLE.

    Można to zrobić od procesu macierzystego, który tylko potrzebuje zdolności CAP_SYS_NICE (który można dać go za pomocą setcap 'cap_sys_nice=pe' parent-binary do binarnego dominującej, jeśli masz możliwości systemu plików włączona jak większość obecnych dystrybucji Linuxa zrobić), zarówno w skuteczny i dozwolone zestawy.

    Celem jest zminimalizowanie postępu innych wątków między momentem, w którym wątek decyduje, że chce migawki/zrzut, a momentem, w którym wszystkie wątki zostały zatrzymane. Nie testowałem, ile czasu potrzeba, aby zmiany odniosły skutek - na pewno zdarzają się one najwcześniej pod koniec bieżących czasów. Tak więc ten krok powinien prawdopodobnie zostać zrobiony nieco wcześniej.

    Osobiście nie zawracam sobie głowy. Na mojej czterordzeniowej maszynie, następująca tylko SIGSTOP daje podobne opóźnienia między wątkami, tak jak muteks lub semafor, więc nie widzę potrzeby dążenia do jeszcze lepszej synchronizacji.

  • Gdy wątek w procesie podrzędnym zdecyduje, że chce wykonać migawkę, wysyła do siebie SIGSTOP (przez kill(getpid(), SIGSTOP)). Zatrzymuje to wszystkie wątki w procesie.

    Proces nadrzędny otrzyma powiadomienie, że dziecko zostało zatrzymane. Najpierw przeanalizuje się /proc/PID/task/, aby uzyskać TID dla każdego wątku procesu potomnego (i być może /proc/PID/task/TID/ pseudo-plików dla innych informacji), a następnie dołącza się do każdego TID przy użyciu ptrace(PTRACE_ATTACH, TID). Oczywiście, ptrace(PTRACE_GETREGS, TID, ...) uzyska stan rejestrów dla wątków, które mogą być używane w połączeniu z /proc/PID/task/TID/smaps i /proc/PID/task/TID/mem, aby uzyskać ślad stosu dla wątku i dowolne inne informacje, które są zainteresowane. (Na przykład, można utworzyć debugger kompatybilny plik core dla każdego wątku.)

    Po zakończeniu procesu nadrzędnego zrywanie zrzutu umożliwia kontynuację procesu potomnego. Uważam, że musisz wysłać oddzielny sygnał SIGCONT, aby cały proces potomny był kontynuowany, zamiast polegać tylko na ptrace(PTRACE_CONT, TID), ale nie sprawdziłem tego; zweryfikuj to, proszę.

Sądzę, że powyższe spowodują minimalne opóźnienie w czasie zegar ścienny między wątków w procesie zatrzymania. Szybkie testy na AMD Athlon II X4 640 na Xubuntu i jądrze generalnym 3.8.0-29 wskazują, że ciasne pętle zwiększające zmienną zmienną w innych wątkach tylko przesuwają liczniki o kilka tysięcy, w zależności od liczby wątków (jest za dużo hałas w kilku testach, które zrobiłem, aby powiedzieć coś bardziej konkretnego).

Ograniczenie procesu do pojedynczego procesora, a nawet do priorytetu IDLE, drastycznie zmniejszy to opóźnienie jeszcze bardziej. Funkcja CAP_SYS_NICE umożliwia rodzicowi nie tylko zmniejszenie priorytetu procesu potomnego, ale także podniesienie priorytetu do pierwotnego poziomu; Możliwości systemu plików oznaczają, że proces nadrzędny nie musi nawet być ustawiony, ponieważ wystarcza tylko CAP_SYS_NICE. (Myślę, że byłoby wystarczająco bezpieczne - z pewnymi sprawdzeniami w programie nadrzędnym - do zainstalowania np. Na komputerach uniwersyteckich, gdzie studenci są dość aktywni w znajdowaniu interesujących sposobów na wykorzystanie zainstalowanych programów.)

można utworzyć łatkę jądrową (lub moduł), która zapewnia wzmocnioną kill(getpid(), SIGSTOP), która również próbuje uruchomić inne wątki z uruchomionych procesorów, a tym samym spróbuj zmniejszyć opóźnienie między wątkami. Osobiście nie zawracałbym sobie głowy. Nawet bez manipulowania procesorem/priorytetem uzyskuję wystarczającą synchronizację (wystarczająco małe opóźnienia między zatrzymaniem wątków).

Czy potrzebujesz przykładowego kodu ilustrującego moje pomysły powyżej?

+0

Dziękuję bardzo! Pracuję nad tym (i twoją drugą odpowiedź), i na pewno wrócę z pytaniami! –

+0

Oto pytanie: chcę odwrócić relację rodzic-dziecko w konfiguracji i pozwolić dziecku na śledzenie, zrzucanie i umierania. Więc: czy możliwe jest wysłanie SIGSTOP (konsekwentnie) do pr czyż to nie jest twoje własne dziecko? Czy istnieje sposób na "czekanie" na proces, który nie jest twoim dzieckiem? –

+0

Nieważne: Zauważyłem, że możesz * czekać * na każdy proces, który jest śledzony, a ślad staje się quasi-dzieckiem na czas trwania śladu. –

1

Jeśli zamierzasz pobrać plik core w nieokreślonej lokalizacji i po prostu uzyskać podstawowy obraz procesu działającego bez zabijania, możesz użyć gcore.

Jeśli zamierzasz pobrać plik core w określonej lokalizacji (warunek) i nadal kontynuować proces - prostym podejściem jest programowe wykonanie gcore z tej lokalizacji.

Bardziej klasycznym, czystym podejściem byłoby sprawdzenie interfejsu API, którego gcore używa i osadzonego w aplikacji - ale byłoby to zbyt dużym wysiłkiem w porównaniu do potrzeby przez większość czasu.

HTH!

+0

W jaki sposób 'gcore' zapewnia nieprzerwane działanie procesu? –

+0

@KerrekSB: Przeczytaj stronę [man] (http://www.gsp.com/cgi-bin/man.cgi?topic =gcore). Opcja '-s'" Zatrzymuje proces podczas zbierania obrazu rdzenia i wznawia go po zakończeniu, co gwarantuje, że wynikowy zrzut rdzenia będzie w stałym stanie. Proces zostanie wznowiony, nawet jeśli został już zatrzymany.Ten sam efekt można osiągnąć ręcznie za pomocą kill (1) " – Linuxios

+0

@ Linux: Hm, to jest dla BSD Myślę, że powinienem powiedzieć, że chcę rozwiązania dla systemu Linux –

1

Po otrzymaniu fork otrzymasz pełną kopię uruchomionej pamięci procesów. Obejmuje to wszystkie stosy wątków (w końcu możesz mieć w nich ważne wskaźniki). Ale wątek wywołujący nadal jest wykonywany w potomstwie.

Możesz to łatwo przetestować.Zrób wielowątkowy program i uruchom:

pid_t parent_pid = getpid(); 

if (!fork()) { 
    kill(parent_pid, SIGSTOP); 

    char buffer[0x1000]; 

    pid_t child_pid = getpid(); 
    sprintf(buffer, "diff /proc/%d/maps /proc/%d/maps", parent_pid, child_pid); 

    system(buffer); 

    kill(parent_pid, SIGTERM); 

    return 0; 
} else for (;;); 

Więc wszystko ma swoją pamięć, a podczas tworzenia zrzutu pamięci będzie zawierał wszystkie inne wątki stosy (pod warunkiem, maksymalny rozmiar pliku rdzeń na to pozwala). Jedynymi brakującymi elementami są ich zestawy rejestrów. Jeśli będziesz ich potrzebował, musisz uzyskać rodzica, aby je uzyskać.

Należy jednak pamiętać, że zrzuty pamięci nie są zaprojektowane do przechowywania informacji o środowisku wykonawczym z więcej niż jednego wątku - tego, który spowodował zrzut rdzenia.

Aby odpowiedzieć na niektóre inne pytania:

Można wyliczyć wątki przechodząc przez /proc/[pid]/tasks, ale nie można zidentyfikować ich baz stosu dopóki ich ptrace.

Tak, masz pełny dostęp do innych wątków stosy migawek (patrz wyżej) z rozwidlonym procesie. Ustalenie ich nie jest banalne, ale trafiają do zrzutu pamięci, o ile pozwala na to rozmiar pliku podstawowego. Najlepiej jest zapisać je w jakiejś globalnie dostępnej strukturze, jeśli możesz na tworzenie.

+0

Problem z przechodzeniem przez'/proc/pid/tasks' jest że nie jest zsynchronizowany z zrzutem rdzenia, ponieważ nie mogę zatrzymać wątków rodzica ... –

+0

@KerrekSB Możesz: 'kill (parent_pid, SIGSTOP)' –

+0

Ale to nie jest * zsynchronizowane *. wyłączyć i mieć inną zawartość pamięci do czasu wysłania sygnału, a co ważniejsze, do czasu dostarczenia sygnału do wszystkich wątków –

0

Jeśli Twoim celem jest zignorowanie całego procesu w celu zrozumienia dokładnego stanu wszystkich wątków w określonym punkcie, nie widzę żadnego sposobu, aby to zrobić, który nie wymaga jakiejś procedury obsługi przerwań. Musisz zatrzymać wszystkie procesory i zapisać aktualny stan każdego wątku.

Nie znam żadnego systemu zapewniającego ten rodzaj pełnego zrzutu podstawowego procesu. Zgrubne zarysy procesu byłyby następujące:

  1. Występuje przerwanie we wszystkich procesorach (zarówno rdzeniach logicznych, jak i fizycznych).
  2. zajęte czekać na synchronizację wszystkich rdzeni (to nie powinno zająć dużo czasu).
  3. Klonowanie żądanego obszaru pamięci procesu: duplikowanie tabel stron i zaznaczanie wszystkich stron jako kopii przy zapisie.
  4. kazać procesorowi sprawdzić, czy bieżący wątek jest w procesie docelowym. Jeśli tak, zapisz bieżący wskaźnik stosu dla tego wątku.
  5. dla każdego innego wątku sprawdź blok danych wątku dla bieżącego wskaźnika stosu i zapisz go.
  6. utworzyć wątek jądra, aby zapisać skopiowane obszary pamięci, a wskaźniki stosu nici
  7. wznowić wszystkie rdzenie.

To powinno przechwycić cały stan procesu, w tym migawkę wszystkich procesów uruchomionych w momencie wydania przerwania między procesorami. Ponieważ wszystkie wątki są przerywane (albo przez standardowy proces zawieszenia harmonogramu, albo przez nasz niestandardowy proces przerwań) wszystkie stany rejestrów będą znajdować się na stosie w pamięci procesu. Musisz tylko wiedzieć, gdzie znajduje się wierzchołek każdego stosu wątków. Używanie mechanizmu kopiowania przy zapisie do klonowania tabel stron umożliwia przezroczyste zapisywanie, podczas gdy oryginalny proces może zostać wznowiony.

Jest to bardzo ważna opcja, ponieważ jej główna funkcjonalność wymaga zawieszania wszystkich procesorów na znaczną ilość czasu (synchronizacja, klonowanie, chodzenie we wszystkich wątkach). Powinno to jednak umożliwić dokładne rejestrowanie statusu wszystkich wątków, a także ustalanie, które wątki były uruchomione (i na których procesorach) po osiągnięciu punktu kontrolnego. Zakładam, że istnieją pewne ramy dla tego procesu (na przykład w CRIU). Oczywiście wznowienie procesu spowoduje burzę alokacji stron, ponieważ mechanizm kopiowania przy zapisie chroni sprawdzany stan systemu.