Line 0
Link Here
|
|
|
1 |
How to conserve battery power using laptop-mode |
2 |
----------------------------------------------- |
3 |
|
4 |
Document Author: Bart Samwel (bart@samwel.tk) |
5 |
Date created: January 2, 2004 |
6 |
|
7 |
Introduction |
8 |
------------ |
9 |
|
10 |
Laptopmode is used to minimize the time that the hard disk needs to be spun up, |
11 |
to conserve battery power on laptops. It has been reported to cause significant |
12 |
power savings. |
13 |
|
14 |
Contents |
15 |
-------- |
16 |
|
17 |
* Introduction |
18 |
* The short story |
19 |
* Caveats |
20 |
* The details |
21 |
* Tips & Tricks |
22 |
* Control script |
23 |
* ACPI integration |
24 |
* Monitoring tool |
25 |
|
26 |
|
27 |
The short story |
28 |
--------------- |
29 |
|
30 |
If you just want to use it, run the laptop_mode control script (which is included |
31 |
at the end of this document) as follows: |
32 |
|
33 |
# laptop_mode start |
34 |
|
35 |
Then set your harddisk spindown time to a relatively low value with hdparm: |
36 |
|
37 |
hdparm -S 4 /dev/hda |
38 |
|
39 |
The value -S 4 means 20 seconds idle time before spindown. Your harddisk will |
40 |
now only spin up when a disk cache miss occurs, or at least once every 10 |
41 |
minutes to write back any pending changes. |
42 |
|
43 |
To stop laptop_mode, remount your filesystems with regular commit intervals |
44 |
(e.g., 5 seconds), and run "laptop_mode stop". |
45 |
|
46 |
|
47 |
Caveats |
48 |
------- |
49 |
|
50 |
* The downside of laptop mode is that you have a chance of losing up |
51 |
to 10 minutes of work. If you cannot afford this, don't use it! |
52 |
|
53 |
* Most desktop hard drives have a very limited lifetime measured in spindown |
54 |
cycles, typically about 50.000 times (it's usually listed on the spec sheet). |
55 |
Check your drive's rating, and don't wear down your drive's lifetime if you |
56 |
don't need to. |
57 |
|
58 |
* If you mount some of your ext3/reiserfs filesystems with the -n option, then |
59 |
the control script will not be able to remount them correctly. You must set |
60 |
DO_REMOUNTS=0 in the control script, otherwise it will remount them with the |
61 |
wrong options -- or it will fail because it cannot write to /etc/mtab. |
62 |
|
63 |
* If you have your filesystems listed as type "auto" in fstab, like I did, then |
64 |
the control script will not recognize them as filesystems that need remounting. |
65 |
|
66 |
The details |
67 |
----------- |
68 |
|
69 |
Laptop-mode is controlled by the flag /proc/sys/vm/laptop_mode. When this |
70 |
flag is set, any physical disk read operation (that might have caused the |
71 |
hard disk to spin up) causes Linux to flush all dirty blocks. The result |
72 |
of this is that after a disk has spun down, it will not be spun up anymore |
73 |
to write dirty blocks, because those blocks had already been written |
74 |
immediately after the most recent read operation |
75 |
|
76 |
To increase the effectiveness of the laptop_mode strategy, the laptop_mode |
77 |
control script increases dirty_expire_centisecs and dirty_writeback_centisecs in |
78 |
/proc/sys/vm to about 10 minutes (by default), which means that pages that are |
79 |
dirtied are not forced to be written to disk as often. The control script also |
80 |
changes the dirty background ratio, so that background writeback of dirty pages |
81 |
is not done anymore. Combined with a higher commit value (also 10 minutes) for |
82 |
ext3 or ReiserFS filesystems (also done automatically by the control script), |
83 |
this results in concentration of disk activity in a small time interval which |
84 |
occurs only once every 10 minutes, or whenever the disk is forced to spin up by |
85 |
a cache miss. The disk can then be spun down in the periods of inactivity. |
86 |
|
87 |
If you want to find out which process caused the disk to spin up, you can |
88 |
gather information by setting the flag /proc/sys/vm/block_dump. When this flag |
89 |
is set, Linux reports all disk read and write operations that take place, and |
90 |
all block dirtyings done to files. This makes it possible to debug why a disk |
91 |
needs to spin up, and to increase battery life even more. |
92 |
|
93 |
If 10 minutes is too much or too little downtime for you, you can configure |
94 |
this downtime as follows. In the control script, set the MAX_AGE value to the |
95 |
maximum number of seconds of disk downtime that you would like. You should |
96 |
then set your filesystem's commit interval to the same value. The dirty ratio |
97 |
is also configurable from the control script. |
98 |
|
99 |
If you don't like the idea of the control script remounting your filesystems |
100 |
for you, you can change DO_REMOUNTS to 0 in the script. |
101 |
|
102 |
Thanks to Kiko Piris, the control script can be used to enable laptop mode on |
103 |
both the Linux 2.4 and 2.6 series. |
104 |
|
105 |
|
106 |
Tips & Tricks |
107 |
------------- |
108 |
|
109 |
* Bartek Kania reports getting up to 50 minutes of extra battery life (on top |
110 |
of his regular 3 to 3.5 hours) using very aggressive power management (hdparm |
111 |
-B1) and a spindown time of 5 seconds (hdparm -S1). |
112 |
|
113 |
* You can spin down the disk while playing MP3, by setting the disk readahead |
114 |
to 8MB (hdparm -a 16384). Effectively, the disk will read a complete MP3 at |
115 |
once, and will then spin down while the MP3 is playing. (Thanks to Bartek |
116 |
Kania.) |
117 |
|
118 |
* Drew Scott Daniels observed: "I don't know why, but when I decrease the number |
119 |
of colours that my display uses it consumes less battery power. I've seen |
120 |
this on powerbooks too. I hope that this is a piece of information that |
121 |
might be useful to the Laptop Mode patch or it's users." |
122 |
|
123 |
|
124 |
Control script |
125 |
-------------- |
126 |
|
127 |
Please note that this control script works for the Linux 2.4 and 2.6 series. |
128 |
|
129 |
--------------------CONTROL SCRIPT BEGIN------------------------------------------ |
130 |
#!/bin/sh |
131 |
|
132 |
# start or stop laptop_mode, best run by a power management daemon when |
133 |
# ac gets connected/disconnected from a laptop |
134 |
# |
135 |
# install as /sbin/laptop_mode |
136 |
# |
137 |
# Contributors to this script: Kiko Piris |
138 |
# Bart Samwel |
139 |
# Dax Kelson |
140 |
# Original Linux 2.4 version by: Jens Axboe |
141 |
|
142 |
parse_mount_opts () { |
143 |
echo "$*" | \ |
144 |
sed 's/commit=[0-9]*//g' | \ |
145 |
sed 's/,,*/,/g' | \ |
146 |
sed 's/^,//' | \ |
147 |
sed 's/,$//' | \ |
148 |
cat - |
149 |
} |
150 |
|
151 |
KLEVEL="$(uname -r | cut -c1-3)" |
152 |
case "$KLEVEL" in |
153 |
"2.4") |
154 |
true |
155 |
;; |
156 |
"2.6") |
157 |
true |
158 |
;; |
159 |
*) |
160 |
echo "Unhandled kernel level: $KLEVEL ('uname -r' = '$(uname -r)')" |
161 |
exit 1 |
162 |
;; |
163 |
esac |
164 |
|
165 |
# Shall we remount journaled fs. with appropiate commit interval? (1=yes) |
166 |
DO_REMOUNTS=1 |
167 |
|
168 |
# age time, in seconds. should be put into a sysconfig file |
169 |
MAX_AGE=600 |
170 |
|
171 |
# Allowed dirty ratio, in pct. should be put into a sysconfig file as well. |
172 |
DIRTY_RATIO=40 |
173 |
|
174 |
# kernel default dirty buffer age |
175 |
DEF_AGE=30 |
176 |
DEF_UPDATE=5 |
177 |
DEF_DIRTY_BACKGROUND_RATIO=10 |
178 |
DEF_DIRTY_RATIO=40 |
179 |
|
180 |
|
181 |
if [ ! -e /proc/sys/vm/laptop_mode ]; then |
182 |
echo "Kernel is not patched with laptop_mode patch." |
183 |
exit 1 |
184 |
fi |
185 |
|
186 |
if [ ! -w /proc/sys/vm/laptop_mode ]; then |
187 |
echo "You do not have enough privileges to enable laptop_mode." |
188 |
exit 1 |
189 |
fi |
190 |
|
191 |
case "$1" in |
192 |
start) |
193 |
AGE=$((100*$MAX_AGE)) |
194 |
echo -n "Starting laptop_mode" |
195 |
case "$KLEVEL" in |
196 |
"2.4") |
197 |
echo "1" > /proc/sys/vm/laptop_mode |
198 |
echo "30 500 0 0 $AGE $AGE 60 20 0" > /proc/sys/vm/bdflush |
199 |
;; |
200 |
"2.6") |
201 |
echo "1" > /proc/sys/vm/laptop_mode |
202 |
echo "$AGE" > /proc/sys/vm/dirty_writeback_centisecs |
203 |
echo "$AGE" > /proc/sys/vm/dirty_expire_centisecs |
204 |
echo "$DIRTY_RATIO" > /proc/sys/vm/dirty_ratio |
205 |
echo "$DIRTY_RATIO" > /proc/sys/vm/dirty_background_ratio |
206 |
;; |
207 |
esac |
208 |
if [ $DO_REMOUNTS -eq 1 ]; then |
209 |
cat /etc/mtab | while read DEV MP FST OPTS DUMP PASS ; do |
210 |
PARSEDOPTS="$(parse_mount_opts "$OPTS")" |
211 |
case "$FST" in |
212 |
"ext3") mount $DEV -t $FST $MP -o remount,$PARSEDOPTS,commit=$MAX_AGE ;; |
213 |
"reiserfs") mount $DEV -t $FST $MP -o remount,$PARSEDOPTS,commit=$MAX_AGE ;; |
214 |
"xfs") mount $DEV -t $FST $MP -o remount,$PARSEDOPTS,commit=$MAX_AGE ;; |
215 |
esac |
216 |
done |
217 |
fi |
218 |
echo "." |
219 |
;; |
220 |
stop) |
221 |
U_AGE=$((100*$DEF_UPDATE)) |
222 |
B_AGE=$((100*$DEF_AGE)) |
223 |
echo -n "Stopping laptop_mode" |
224 |
case "$KLEVEL" in |
225 |
"2.4") |
226 |
echo "0" > /proc/sys/vm/laptop_mode |
227 |
echo "30 500 0 0 $U_AGE $B_AGE 60 20 0" > /proc/sys/vm/bdflush |
228 |
;; |
229 |
"2.6") |
230 |
echo "0" > /proc/sys/vm/laptop_mode |
231 |
echo "$U_AGE" > /proc/sys/vm/dirty_writeback_centisecs |
232 |
echo "$B_AGE" > /proc/sys/vm/dirty_expire_centisecs |
233 |
echo "$DEF_DIRTY_RATIO" > /proc/sys/vm/dirty_ratio |
234 |
echo "$DEF_DIRTY_BACKGROUND_RATIO" > /proc/sys/vm/dirty_background_ratio |
235 |
;; |
236 |
esac |
237 |
if [ $DO_REMOUNTS -eq 1 ]; then |
238 |
cat /etc/mtab | while read DEV MP FST OPTS DUMP PASS ; do |
239 |
PARSEDOPTS="$(parse_mount_opts "$OPTS")" |
240 |
case "$FST" in |
241 |
"ext3") mount $DEV -t $FST $MP -o remount,$PARSEDOPTS ;; |
242 |
"reiserfs") mount $DEV -t $FST $MP -o remount,$PARSEDOPTS ;; |
243 |
"xfs") mount $DEV -t $FST $MP -o remount,$PARSEDOPTS ;; |
244 |
esac |
245 |
done |
246 |
fi |
247 |
echo "." |
248 |
;; |
249 |
*) |
250 |
echo "$0 {start|stop}" |
251 |
;; |
252 |
|
253 |
esac |
254 |
|
255 |
exit 0 |
256 |
|
257 |
--------------------CONTROL SCRIPT END-------------------------------------------- |
258 |
|
259 |
|
260 |
ACPI integration |
261 |
---------------- |
262 |
|
263 |
Dax Kelson submitted this so that the ACPI acpid daemon will |
264 |
kick off the laptop_mode script and run hdparm. |
265 |
|
266 |
---------------------------/etc/acpi/events/ac_adapter BEGIN------------------------------------------- |
267 |
event=ac_adapter |
268 |
action=/etc/acpi/actions/battery.sh |
269 |
---------------------------/etc/acpi/events/ac_adapter END------------------------------------------- |
270 |
|
271 |
---------------------------/etc/acpi/actions/battery.sh BEGIN------------------------------------------- |
272 |
#!/bin/sh |
273 |
|
274 |
# cpu throttling |
275 |
# cat /proc/acpi/processor/CPU0/throttling for more info |
276 |
ACAD_THR=0 |
277 |
BATT_THR=2 |
278 |
|
279 |
# spindown time for HD (man hdparm for valid values) |
280 |
# I prefer 2 hours for acad and 20 seconds for batt |
281 |
ACAD_HD=244 |
282 |
BATT_HD=4 |
283 |
|
284 |
# ac/battery event handler |
285 |
|
286 |
status=`awk '/^state: / { print $2 }' /proc/acpi/ac_adapter/AC/state` |
287 |
|
288 |
case $status in |
289 |
"on-line") |
290 |
echo "Setting HD spindown to 2 hours" |
291 |
/sbin/laptop-mode stop |
292 |
/sbin/hdparm -S $ACAD_HD /dev/hda > /dev/null 2>&1 |
293 |
/sbin/hdparm -B 255 /dev/hda > /dev/null 2>&1 |
294 |
#echo -n $ACAD_CPU:$ACAD_THR > /proc/acpi/processor/CPU0/limit |
295 |
exit 0 |
296 |
;; |
297 |
"off-line") |
298 |
echo "Setting HD spindown to 20 seconds" |
299 |
/sbin/laptop-mode start |
300 |
/sbin/hdparm -S $BATT_HD /dev/hda > /dev/null 2>&1 |
301 |
/sbin/hdparm -B 1 /dev/hda > /dev/null 2>&1 |
302 |
#echo -n $BATT_CPU:$BATT_THR > /proc/acpi/processor/CPU0/limit |
303 |
exit 0 |
304 |
;; |
305 |
esac |
306 |
---------------------------/etc/acpi/actions/battery.sh END------------------------------------------- |
307 |
|
308 |
Monitoring tool |
309 |
--------------- |
310 |
|
311 |
Bartek Kania submitted this, it can be used to measure how much time your disk |
312 |
spends spun up/down. |
313 |
|
314 |
---------------------------dslm.c BEGIN------------------------------------------- |
315 |
/* |
316 |
* Simple Disk SLeep Monitor |
317 |
* by Bartek Kania |
318 |
* Licenced under the GPL |
319 |
*/ |
320 |
#include <unistd.h> |
321 |
#include <stdlib.h> |
322 |
#include <stdio.h> |
323 |
#include <fcntl.h> |
324 |
#include <errno.h> |
325 |
#include <time.h> |
326 |
#include <string.h> |
327 |
#include <signal.h> |
328 |
#include <sys/ioctl.h> |
329 |
#include <linux/hdreg.h> |
330 |
|
331 |
#ifdef DEBUG |
332 |
#define D(x) x |
333 |
#else |
334 |
#define D(x) |
335 |
#endif |
336 |
|
337 |
int endit = 0; |
338 |
|
339 |
/* Check if the disk is in powersave-mode |
340 |
* Most of the code is stolen from hdparm. |
341 |
* 1 = active, 0 = standby/sleep, -1 = unknown */ |
342 |
int check_powermode(int fd) |
343 |
{ |
344 |
unsigned char args[4] = {WIN_CHECKPOWERMODE1,0,0,0}; |
345 |
int state; |
346 |
|
347 |
if (ioctl(fd, HDIO_DRIVE_CMD, &args) |
348 |
&& (args[0] = WIN_CHECKPOWERMODE2) /* try again with 0x98 */ |
349 |
&& ioctl(fd, HDIO_DRIVE_CMD, &args)) { |
350 |
if (errno != EIO || args[0] != 0 || args[1] != 0) { |
351 |
state = -1; /* "unknown"; */ |
352 |
} else |
353 |
state = 0; /* "sleeping"; */ |
354 |
} else { |
355 |
state = (args[2] == 255) ? 1 : 0; |
356 |
} |
357 |
D(printf(" drive state is: %s\n", state)); |
358 |
|
359 |
return state; |
360 |
} |
361 |
|
362 |
char *state_name(int i) |
363 |
{ |
364 |
if (i == -1) return "unknown"; |
365 |
if (i == 0) return "sleeping"; |
366 |
if (i == 1) return "active"; |
367 |
|
368 |
return "internal error"; |
369 |
} |
370 |
|
371 |
char *myctime(time_t time) |
372 |
{ |
373 |
char *ts = ctime(&time); |
374 |
ts[strlen(ts) - 1] = 0; |
375 |
|
376 |
return ts; |
377 |
} |
378 |
|
379 |
void measure(int fd) |
380 |
{ |
381 |
time_t start_time; |
382 |
int last_state; |
383 |
time_t last_time; |
384 |
int curr_state; |
385 |
time_t curr_time = 0; |
386 |
time_t time_diff; |
387 |
time_t active_time = 0; |
388 |
time_t sleep_time = 0; |
389 |
time_t unknown_time = 0; |
390 |
time_t total_time = 0; |
391 |
int changes = 0; |
392 |
float tmp; |
393 |
|
394 |
printf("Starting measurements\n"); |
395 |
|
396 |
last_state = check_powermode(fd); |
397 |
start_time = last_time = time(0); |
398 |
printf(" System is in state %s\n\n", state_name(last_state)); |
399 |
|
400 |
while(!endit) { |
401 |
sleep(1); |
402 |
curr_state = check_powermode(fd); |
403 |
|
404 |
if (curr_state != last_state || endit) { |
405 |
changes++; |
406 |
curr_time = time(0); |
407 |
time_diff = curr_time - last_time; |
408 |
|
409 |
if (last_state == 1) active_time += time_diff; |
410 |
else if (last_state == 0) sleep_time += time_diff; |
411 |
else unknown_time += time_diff; |
412 |
|
413 |
last_state = curr_state; |
414 |
last_time = curr_time; |
415 |
|
416 |
printf("%s: State-change to %s\n", myctime(curr_time), |
417 |
state_name(curr_state)); |
418 |
} |
419 |
} |
420 |
changes--; /* Compensate for SIGINT */ |
421 |
|
422 |
total_time = time(0) - start_time; |
423 |
printf("\nTotal running time: %lus\n", curr_time - start_time); |
424 |
printf(" State changed %d times\n", changes); |
425 |
|
426 |
tmp = (float)sleep_time / (float)total_time * 100; |
427 |
printf(" Time in sleep state: %lus (%.2f%%)\n", sleep_time, tmp); |
428 |
tmp = (float)active_time / (float)total_time * 100; |
429 |
printf(" Time in active state: %lus (%.2f%%)\n", active_time, tmp); |
430 |
tmp = (float)unknown_time / (float)total_time * 100; |
431 |
printf(" Time in unknown state: %lus (%.2f%%)\n", unknown_time, tmp); |
432 |
} |
433 |
|
434 |
void ender(int s) |
435 |
{ |
436 |
endit = 1; |
437 |
} |
438 |
|
439 |
void usage() |
440 |
{ |
441 |
puts("usage: dslm [-w <time>] <disk>"); |
442 |
exit(0); |
443 |
} |
444 |
|
445 |
int main(int ac, char **av) |
446 |
{ |
447 |
int fd; |
448 |
char *disk = 0; |
449 |
int settle_time = 60; |
450 |
|
451 |
/* Parse the simple command-line */ |
452 |
if (ac == 2) |
453 |
disk = av[1]; |
454 |
else if (ac == 4) { |
455 |
settle_time = atoi(av[2]); |
456 |
disk = av[3]; |
457 |
} else |
458 |
usage(); |
459 |
|
460 |
if (!(fd = open(disk, O_RDONLY|O_NONBLOCK))) { |
461 |
printf("Can't open %s, because: %s\n", disk, strerror(errno)); |
462 |
exit(-1); |
463 |
} |
464 |
|
465 |
if (settle_time) { |
466 |
printf("Waiting %d seconds for the system to settle down to " |
467 |
"'normal'\n", settle_time); |
468 |
sleep(settle_time); |
469 |
} else |
470 |
puts("Not waiting for system to settle down"); |
471 |
|
472 |
signal(SIGINT, ender); |
473 |
|
474 |
measure(fd); |
475 |
|
476 |
close(fd); |
477 |
|
478 |
return 0; |
479 |
} |
480 |
---------------------------dslm.c END--------------------------------------------- |