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:
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.