zur Übersicht: Linux: meine Software und Konfigurationstipps
06.12.2005
Ich habe mich lange geärgert, dass bestimmte, nachrangige Prozesse mit hoher I/O-Last mein KDE komplett zum Stillstand bringen, obwohl die CPU-Last irgendwo bei 2% herumdüpelt. Seit dem Kernel 2.6.13 hat Linux einen neuen Scheduler (CFQ – Complete Fair Queueing), der es erlaubt, dieses Problem zu lösen. Es gibt drei Prioritätsklassen: real time, best effort und idle. Prozesse in der Gruppe idle kommen nur dann zum Zug, wenn kein Prozess einer höheren Gruppe noch I/O-Zugriffe ausstehen hat.
Die Herausforderung liegt also darin, die I/O-Priorität aller "unwichtigen" Prozesse auf idle
zu reduzieren. Da es schwierig wäre, alle fraglichen Prozesse so starten zu lassen, habe ich einen zentralen Ansatz gewählt: Ein Script prüft alle paar Sekunden, ob neue Prozesse erzeugt worden sind, und manipuliert sie gegebenenfalls.
Das Script setzt die Priorität auf idle
, wenn folgende Bedingungen erfüllt sind:
Die real UID des Prozesses ist die eines Benutzers (also innerhalb eines von Schwellenwerten begrenzten Bereichs).
Es wurde noch keine I/O-Klasse gesetzt.
Die CPU-Priorität ist größer oder gleich null.
Der Kommandoname steht nicht auf der Blacklist.
Im Whitelistmodus: Der Kommandoname steht auf der Whitelist.
Das Script schreibt bei jedem Durchlauf seine Aktivitäten auf die Standardausgabe, also die (Nicht-)Behandlung neuer Prozesse und das Löschen nicht mehr vorhandener aus der Liste. Da das Script sinnigerweise als Hintergrundprozess gestartet wird, kann man sich den Hash der aktuell bekannten Prozesse ausgeben lassen, indem man dem Script das Signal -USR1 schickt. Es wird dann eine Datei (siehe Script, momentan /tmp/ioniced.dump) erzeugt, die zeilenweise (unsortiert) die PIDs und durch Leerzeichen getrennt den I/O-Status enthält. Wurde die I/O-Prioritätsklasse gesetzt, wird der entsprechende Wert ausgegeben. Wurde der Wert unverändert gelassen, wird statt dessen 0 ausgegeben.
Dies ist mein erstes Perl-Script, entsprechend grauenvoll mag es für Experten aussehen. Aber es scheint korrekt zu funktionieren.
Ich habe ein kleines, sehr einfaches bash-Script geschrieben, das ich iops
nenne, das die Ausgabe von ps
um die ionice-Ausgabe ergänzt. Leider kann man mit ionice
immer nur die Angabe für einen Prozess abfragen, weshalb die Ausgabe nicht sonderlich schnell ist, da ionice für jeden Prozess einmal aufgerufen werden muss.
Dieses Script wird hoffentlich irgendwann mal überflüssig, wenn ps
um um eine entsprechende Option ergänzt wird. Das Script erlaubt keine Modifikation der ps-Ausgabe, ließe sich aber leicht anpassen.
#!/bin/bash
version=1.0
# This script shows the ionice value for all processes by adding this value
# to the output of ps.
type ionice &>/dev/null || { echo "ionice not available" >&2; exit 1; }
export i=0
/bin/ps -e -o pid,nice,args |
while read pid nice rest
do
if [ $i -eq 0 ]
then
#printf %5s' ' "$pid"
#printf %13s' ' IONICE
#echo "$rest"
echo " PID IONICE NI COMMAND"
else
ionice_value=$(ionice -p "$pid" 2>/dev/null | sed -n 's/:.* ///p')
if [ -z "$ionice_value" ]
then
ionice_value=exited
fi
printf %5i' ' $pid
printf %13s' ' $ionice_value
printf %3i' ' $nice
echo "$rest"
fi
((i++))
done
Das Script zum Download – und die Signatur dazu.
#!/usr/bin/perl -w # version 1.1, 05.12.2005, Hauke Laging, http://www.hauke-laging.de/software/ # This software is licensed under the Gnu GPL, see http://www.fsf.org/ # This script has the task to prevent I/O intensive system processes from # annoying the user by slowing the system down. These typical administrative # processes are not required to complete within a short time so it makes sense # to reduce the I/O priority of these tasks (which usually impacts the performance # of user processes much more than the CPU proirity). # # This script does not affect processes which have been niced to a value below zero # (higher priority) or already ioniced (to any value). # This is the most simple way to prevent a process from being affected by this script. # You can configure if a white list or black list approach is used: Either only the # configured programs are affected or only those not. # # The script runs as a daemon and reads the current PID list from ps every few # seconds. It cannot (or at least does not) prevent tasks from starting with # high I/O priority (high := high enough to annoy) but this problem es solved # after a few seconds. # # Thus this script should be started in the background. It writes to STDOUT what # it does/doesn't do. # # This script requires kernel 2.6.13 or above and the availability of ionice. # definitions # signals: # USR1: The current list of known processes and their I/O priotity change status # is dumped into the file $dump_file. Values 1,2 and 3 represent set priorities # whereas 0 represents an unchanged priority. @args = ("/bin/bash","-c","type ionice"); system(@args); if ($? != 0) { print "The program 'ionice' is not available; aborting.\n"; exit 1; } use integer; $min_user_id = 1000; $max_user_id = 2000; $syslog_tag="ioniced.pl"; $0=$syslog_tag; $sleep = 5; $leave_inid_pids_untouched=1; $whitelist=1; $dump_file="/tmp/ioniced.dump"; # gegen Ueberlaeufe: gelegentlich alle bekannten PIDs auf Vorhandensein pruefen %known_pids = (); # Werte: 1,2,3 fuer die gesetzten, 0 fuer die unveraenderten %skip_these = ("init"=>0,"ps"=>0,"sshd"=>0,"syslog-ng"=>0,"httpd2-prefork"=>0,"pdflush"=>0); # Werte: Dummys %ionice_these = (find=>3,tar=>3,updatedb=>3,mandb=>3,cp=>3,mv=>3,rpm=>3,gzip=>3); # values: new I/O priority level sub dump_ioprios { if (not open(FH,"> $dump_file")) { return; } foreach (keys %known_pids) { print FH $_," ",$known_pids{$_},"\n"; } close(FH); } $SIG{USR1}="dump_ioprios"; # BEGIN: initialize the hash with the present pids @pslines = qx/ps -eo pid,ruid,nice,comm/; $i=0; foreach (@pslines) { $i++; next if $i == 1; # header line pf ps output $_ =~ s/^\s+//; $_ =~ s/\s+$//; @elements = split(" +",$_); $pid=$elements[0]; $ruid=$elements[1]; $nice=$elements[2]; $command=$elements[3]; $change_it=1; $change_it=0 if ($leave_inid_pids_untouched); $change_it=0 if ($ruid>=$min_user_id && $ruid<=$max_user_id); $change_it=0 if ($nice<0); $change_it=0 if (exists($skip_these{$command})); if ($change_it) # execute this one conditionally only because it takes time { $ionice_status=qx/ionice -p $pid/; $ionice_status =~ s/:.*$//; $change_it=0 if ($ionice_status != "none"); } if ($whitelist) { $change_it=0 unless (exists($ionice_these{$command})); } if ($change_it) { $io_class=3; $explicit_io_class=$ionice_these{$command}; if ($whitelist && defined($explicit_io_class) && ($explicit_io_class eq 1 || $explicit_io_class eq 2 || $explicit_io_class eq 3)) { $io_class=$explicit_io_class; } $known_pids{$pid}=$io_class; system("ionice -c${io_class} -p${pid}"); print "PID ${pid} ($command) has been changed to I/O class ${io_class}.\n"; if ($whitelist) { system("logger -t \"${syslog_tag}\" \"PID ${pid} ($command) has been changed to I/O class ${io_class}.\""); } } else { $known_pids{$pid}=0; print "PID ${pid}'s ($command) I/O class has not been changed.\n" } } print "Initialization finished.\n"; # END: initialize the hash with the present pids while (1) { sleep $sleep; print "\n\nChecking for new PIDs:\n"; @pslines = qx/ps -eo pid,ruid,nice,comm/; $i=0; %current_pids=(); # Werte: Dummys foreach (@pslines) { $i++; next if $i == 1; # header line pf ps output $_ =~ s/^\s+//; $_ =~ s/\s+$//; @elements = split(" +",$_); $pid=$elements[0]; $ruid=$elements[1]; $nice=$elements[2]; $command=$elements[3]; $current_pids{$pid}=0; next if (exists($known_pids{$pid})); next if ($command eq "ps"); $change_it=1; $change_it=0 if ($ruid>=$min_user_id && $ruid<=$max_user_id); $change_it=0 if ($nice<0); $change_it=0 if (exists($skip_these{$command})); if ($change_it) # execute this one conditionally only because it takes time { $ionice_status=qx/ionice -p $pid/; $ionice_status =~ s/:.*$//; chomp($ionice_status); $change_it=0 if ($ionice_status ne "none"); } if ($whitelist) { $change_it=0 unless (exists($ionice_these{$command})); } if ($change_it) { $io_class=3; $explicit_io_class=$ionice_these{$command}; if ($whitelist && defined($explicit_io_class) && ($explicit_io_class eq 1 || $explicit_io_class eq 2 || $explicit_io_class eq 3)) { $io_class=$explicit_io_class; } $known_pids{$pid}=$io_class; system("ionice -c${io_class} -p${pid}"); print "PID ${pid} ($command) has been changed to I/O class ${io_class}.\n"; if ($whitelist) { system("logger -t \"${syslog_tag}\" \"PID ${pid} ($command) has been changed to I/O class ${io_class}.\""); } } else { $known_pids{$pid}=0; print "PID ${pid}'s ($command) I/O class has not been changed.\n" } } print "\n\nPID check\n"; foreach (keys %known_pids) { unless (exists($current_pids{$_})) { delete($known_pids{$_}); print "PID ",$_," does not exist any more and has been deleted from the hash.\n"; } } }