Linux, sygnały i spowolnienie procesu

Linux, sygnały i spowolnienie procesu

Przygotowywałem w ciągu ostatnich dni komputer do zainstalowania Win7 i zmiany układu partycji na dysku. Ot backup ważniejszych danych, wgranie muzyki do końca na telefon i tym podobne czynności. Backup danych windowsowych ograniczył się do pogodzenia z ich stratą – zbyt wiele tam jest śmiecia, żeby chciało mi się zgadywać które fragmenty są ważne, a które nie. Cenne dane trzymam na partycji linuksowej, dzięki trickowy z colinux który opisałem w poprzedniej notce.

O możliwość wygodnego importu ustawień użytkownika zgranych do spakowanej paczki, systemów z Redmond nie posądzam (chociaż podejrzewam, że jest narzędzie do tego).
Pod Linuksem mogę napisać: tar --lzma -cf /media/c/home.tar.lzma /home* i po iluś godzinach mieć pełny backup. Wszystko byłoby fajnie gdyby nie dwa szczegóły: musiałem w tym czasie popracować na wirtualnej maszynie, a lzma zarzyna procesor razem z ntfs-3g. To piekielnie obciąża komputer.

Google podpowiedziało obniżenie priorytetu procesów dla cpu i io, jednak nie o to chodzi. Nawet jeśli oba procesy będą chętnie pozwalać innym procesom na pracę, to laptop będzie wył wiatrakiem, cache będzie się często wymieniał, responsywność całości spadnie. Nie o to chodzi. Chcę aby te 2 procesy pracowały z mniejszym obciążeniem komputera, a nie niższym priorytetem.

Z pomocą przyszedł prosty skrypt znaleziony w ^g. Niestety nie zapisałem sobie skąd jest ten skrypt, więc nie dam creditsa. Skrypt prezentuje się tak:

#!/bin/sh
while true; do
  kill -s STOP $1
  sleep 0.2
  kill -s CONT $1
  sleep 0.5
done

Jego zadaniem jest naprzemienne wysyłanie sygnałów SIGSTOP i SIGCONT do procesów wskazanych przez wybrany PID. Tak jakbym miał rozwiązanie! No, może nie do końca.

Ten skrypt co prawda skutecznie zbija zbyt wysokie zużycie procesora i dysku, jednak na raz obsłuży tylko jeden proces. Dodatkowo, jeśli dam ^C to nie wiem, w którym momencie zostanie przerwane przetwarzanie pętli i może być tak, że lzma dostanie SIGCONT, a ntfs-3g SIGSTOP. Nie wiem, co się wtedy stanie, ale chyba wolę nie wiedzieć.

Drugi problem to fakt, że potrzebuję tyle razy odpalić ten skrypt, ile PIDów chcę spowolnić. Dla 2 procesów to jeszcze ujdzie, ale wygodniej byłoby odpalić jeden skrypt dla wielu PIDów, tak jak się wykonuje kill `pidof httpd` prawda?

Być może da się rozwiązać oba problemy stosując skrypty powłoki, jednak nie czuję się w tym na tyle pewnie, by się tego zadania podjąć. Zamiast tego poszukałem, jak wygląda obsługa sygnałów w C/C++. Z pomocą przyszedł artykuł na cc.byexamples.com dotyczący sygnałów. Nieocenioną pomoc stanowił też manual systemowy. Od ponad pół roku nie używałem C i składnię niektórych poleceń mogłem zapomnieć 🙂

Wykorzystując szkielet programu z linku powyżej napisałem taki program:

edit:
Korzystając z okazji chciałbym pozdrowić wszystkich studentów WATu którzy tu mogą przypadkiem trafić szukając materiałów na Systemy Operacyjne! 😀
#include<stdio.h>
#include<stdlib.h>  
#include<signal.h>  
#include<string.h>  
#include<unistd.h>  
  
struct sigaction osa;  
int *pids;
int pidCount;

void help();
void goSleep();
void wake();

void bypass_sigint(int sig_no){  
    printf("attempt to wake sleeping processes up\n");
    wake();
    printf("done... exiting!\n\n");
    
    if(pids) free(pids);
    sigaction(SIGINT,&osa,NULL);  
    kill(0,SIGINT);  
}  


  
int main(int argc, char** argv)  
{ 
  if(argc<2) {help(argv[0]); return (0);}
  
  int i;
  int work=10;
  int nap=10;
  pidCount=0;
  
  pids=(int*) malloc(sizeof(int) );
  pids[0]= -1;
  
  
  
  for(i=1; i<argc; i++){
   if(argv[i][0]=='-'){
     //opcja znaleziona
     if( (strlen(argv[i])==2 && argv[i][1]=='w') ||
         (strlen(argv[i])==6 && 0==strcmp(argv[i],"--work") )
       ){
           i++;
           work=atoi(argv[i] ); 
         }
     if( (strlen(argv[i])==2 && argv[i][1]=='n') ||
         (strlen(argv[i])==5 && 0==strcmp(argv[i],"--nap")  )
       ){
           i++;
           nap=atoi(argv[i] );
         }
     
   }else if(argv[i][0]>='0' && argv[i][0]<='9'){
    //mamy PID
      
      pidCount++;
      pids=realloc(pids, pidCount*sizeof(int));
      pids[pidCount-1]=atoi(argv[i]) ;
      
   }
  }
  
  
  printf("pidCount: %d\n",pidCount);
  if(!pidCount) return 0;
    
  printf("Working on pids: \n");
  for(i=0;i<pidCount;i++){
      printf("%d: %d\n", i+1, pids[i]);
  }
  
  
    struct sigaction sa,osa;  
    memset(&sa, 0, sizeof(sa));  
    sa.sa_handler = &bypass_sigint;  
    sigaction(SIGINT, &sa,&osa);  
  
     
    while(1){
                usleep(work);
                goSleep();
                //printf("stopped\n");
                usleep(nap);
                wake();
                //printf("working\n");
    }  
    free(pids);
    return 0;  
} 

void help(char*programName){
    printf("%s [-n usec] [-w usec] pid1 [pid2 [pid3 [pid4]]]\n", programName);
    printf("Slows down processes by sending them STOP and CONT signals\n");
    printf("--nap usec\n");
    printf("-n usec  \t  sleep for usec microseconds, default 10\n");
    printf("--work usec\n");
    printf("-w usec  \t  run for usec microseconds, default 10 \n\n");
}


void goSleep(){
  int i;
 if(pidCount)
  for(i=0;i<pidCount;i++){
      kill(pids[i], SIGSTOP);
  }
}

void wake(){
  int i;
 if(pidCount)
  for(i=0;i<pidCount;i++){
      kill(pids[i], SIGCONT);
  }
}


Słowem wyjaśnienia, co się dzieje:

Sercem programu jest pętla i 2 funkcje: wake, goSleep. Zadaniem tych 2 funkcji jest odpowiednio uśpienie i polecenie kontynuowania pracy przez wysłanie sygnałów SIGSTOP i SIGCONT procesom. Do wysyłania sygnału służy polecenie kill. Pętla natomiast usypia programy, czeka określony czas, wybudza, czeka i tak w kółko. Dla wygody dałem możliwość ustawienia długości spania i czekania, bo czasem wpisane na sztywno parametry mogą być niewygodne. Przedział czasu na spanie i pracę jest podany w mikrosekundach, dla polecenia usleep. Bałem się, że czas w sekundach zaowocuje wyczuwalną czkawką systemu.

Funkcja bypass_sigint zajmuje się obsługą sygnału INT, który jest wysyłany przez wciśnięcie ^C. W tej funkcji następuje przywrócenie procesów do życia, wyświetlenie komunikatów, zwolnienie tablicy z zapisanymi PIDami i na końcu wyjście. Sygnał SIGCONT wysłany do procesu który już pracuje jest ignorowany, więc nie muszę tutaj specjalnie rozpatrywać tej sytuacji.

Domyślam się, że niektóre elementy progamu można poprawić, zadbać o obsługę sygnałów przerwania pracy, czy błędów. Można też zmodyfikować program, aby za jednym wywołaniem, różne PIDy otrzymały różne interwały pracy i przestoju, jednak gdy to pisałem, nie potrzebowałem tych funkcjonalności.

Program podczas swojej pracy żre 3-4% procesora na mojej maszynie. Myślę, że da się ten wynik obniżyć, jednak nadal jest to dużo lepszy wynik, niż 100% zajęte przez lzma i ntfs-3g 🙂

Program jest na licencjach GPLv3 i beerware, czyli jeśli ktoś chce go wykorzystywać do celów komercyjnych, musi mi kupić piwo. 😀 Ewentualnie swój program rozpowszechnić na GPLv3.

Ps. Jak na joggerze wygodnie publikować źródła?

Bibliografia:
http://cc.byexamples.com/20070520/tap-the-interrupt-signal/
http://www.digipedia.pl/man/signal.2.html
http://linux.die.net/man/2/signal
http://linux.die.net/man/3/kill
I o tym samym, ale pod windows:
Wstrzymywanie procesów pod Windows, j00ru.vexillium.org
Opis wykorzystania cpulimit jako daemona do kontrolowania procesów, by Grzglo.jogger.pl

Edit: Joggerowicz Szymon wskazał program Cpulimit, który realizuje dokładnie to, czego potrzebowałem przed napisaniem mojego programu. Cpulimit jest o tyle lepszy, że ograniczenie podaje się w nim w postaci procentowego obciążenia procesora, którego proces nie może przekroczyć.
Podlinkowałem wpis Grzegorza dotyczący cpulimit.

  • Wow, stary, pisanie softu w C do takich pierdół to IMO overkill 🙂 Lepiej po prostu użyć skryptów basha.

  • A nice oraz ionice?

  • A nie prościej przyciąć taktowanie procka? Nawet stacjonarne to czasem potrafią, a co dopiero laptopy…

    A co do wspomnianego MS i ichniego importu/eksportu, to mają do tego standardowe toolsy:
    http://en.wikipedia.org/wiki/Windows_Easy_Transfer
    http://en.wikipedia.org/wiki/User_State_Migration_Tool

    W tym prostszym można sobie nawet wyklikać zrzucenie backupu gdzieś na NAS czy pendrive.

  • @Dodek: Nie znam basha na tyle, by dodać w skrypcie obsługę sygnałów, czy jakiś odpowiednik destruktora obiektu. Nie wiem też, jak w bashu uzyskać wygodny dostęp do wielu argumentów. W ogóle kiepsko znam basha 😉 Jakieś echo, grep, sed jeszcze zrobię, ale nic więcej… Użyłem tego języka, który znam.

    @Fluxid: W trzecim akapicie napisałem, że to rozwiązanie nie wchodzi w grę

    @Hoppke: Zrobiłem to, ale jeszcze potrzebowałem pracować równolegle, a komputer tylko zwolnił. Nie byłoby problemu, gdybym miał inny komputer do pracy w tym samym czasie.
    Na szukanie rozwiązań w wiki nie wpadłem.. Mój błąd. Już jest po ptakach, piszę właśnie z win7.
    Czeka mnie teraz instalowanie całego potrzebnego softu (vs, vlc, flex itp itd)

  • mt3o: rozumiem, ale w C pisałbym takie coś pewnie parę godzin, a w bashu kilkanaście minut 🙂

  • Szymon

    cpulimit ?

  • jARRodx

    killall?

  • del system32?

  • @Szymon: Właśnie takiego narzędzia potrzebowałem! Cpulimit jest o tyle wygodniejszy, że pozwala podać wprost, jakiego użycia program nie może przekroczyć, nie trzeba podawać samemu czasu.
    Dzięki, uzupełnię wpis o ten program.

    @Dodek: Dwa semestry praktyki C z domieszką C++ i pisze się tyle samo. Po odliczeniu czasu na siedzenie w dokumentacji, który dla mnie i C jest mniejszy 😛

  • Szymon

    lubię się czasem przydać 😉

  • "Maksymalne zużycie procesora przez wybrane lub wszystkie procesy w Linuksie w – cpulimit daemon":http://grzglo.jogger.pl/2009/08/02/cpulimit-daemon/

  • Jak wydadzą Ubuntu 9.10 spróbuje jeszcze raz przesiąść się na Linuxa

  • IMO bez sensu — w zamian cały proces będzie trwał o wiele dłużej, nie za bardzo widzę co w tym lepszego.

  • @mina86: to, że możesz w tym czasie pracować w trochę bardziej komfortowych warunkach. Opisałem mój problem w notce. Na komputerze miał pracować lzma/ntfs-3g, vbox i ja. A bez tego programiku pracował tylko lzma/ntfs-3g. To, czy paczka się tworzyła 5 czy 8 godzin nie miało znaczenia. Wiesz, to jest coś za coś. Moc procesora nie jest nieograniczona i jeśli ktoś ma mieć jej więcej, to ktoś inny ma jej mniej. To samo z dostępem do pamięci i dysku.

  • Tylko nie widzę w czym to rozwiązanie jest lepsze od nice -n 20.

  • W tym, ze mniej sie rozpycha w buforze odczytu/zapisu. Dalem na poczatku najnizszy priorytet w dostepie do dysku i cpu, ale to bylo za malo. Komputer mial czkawke, stad bardziej stanowcze rozwiazanie.