1 How to conserve battery power using laptop-mode
2 -----------------------------------------------
4 Document Author: Bart Samwel (bart@samwel.tk)
5 Date created: January 2, 2004
6 Last modified: April 3, 2004
11 Laptopmode is used to minimize the time that the hard disk needs to be spun up,
12 to conserve battery power on laptops. It has been reported to cause significant
31 If you just want to use it, run the laptop_mode control script (which is included
32 at the end of this document) as follows:
36 Then set your harddisk spindown time to a relatively low value with hdparm:
40 The value -S 4 means 20 seconds idle time before spindown. Your harddisk will
41 now only spin up when a disk cache miss occurs, or at least once every 10
42 minutes to write back any pending changes.
44 To stop laptop_mode, run "laptop_mode stop".
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! It's
52 wise to turn OFF laptop mode when you're almost out of battery --
53 although this will make the battery run out faster, at least you'll
54 lose less work when it actually runs out. I'm still looking for someone
55 to submit instructions on how to turn off laptop mode when battery is low,
56 e.g., using ACPI events. I don't have a laptop myself, so if you do and
57 you care to contribute such instructions, please do.
59 * Most desktop hard drives have a very limited lifetime measured in spindown
60 cycles, typically about 50.000 times (it's usually listed on the spec sheet).
61 Check your drive's rating, and don't wear down your drive's lifetime if you
64 * If you mount some of your ext3/reiserfs filesystems with the -n option, then
65 the control script will not be able to remount them correctly. You must set
66 DO_REMOUNTS=0 in the control script, otherwise it will remount them with the
67 wrong options -- or it will fail because it cannot write to /etc/mtab.
69 * If you have your filesystems listed as type "auto" in fstab, like I did, then
70 the control script will not recognize them as filesystems that need remounting.
72 * It has been reported that some versions of the mutt mail client use file access
73 times to determine whether a folder contains new mail. If you use mutt and
74 experience this, you must disable the noatime remounting in the control script
75 by setting DO_REMOUNT_NOATIME=0.
81 Laptop-mode is controlled by the flag /proc/sys/vm/laptop_mode. When this
82 flag is set, any physical disk read operation (that might have caused the
83 hard disk to spin up) causes Linux to flush all dirty blocks. The result
84 of this is that after a disk has spun down, it will not be spun up anymore
85 to write dirty blocks, because those blocks had already been written
86 immediately after the most recent read operation
88 To increase the effectiveness of the laptop_mode strategy, the laptop_mode
89 control script increases dirty_expire_centisecs and dirty_writeback_centisecs in
90 /proc/sys/vm to about 10 minutes (by default), which means that pages that are
91 dirtied are not forced to be written to disk as often. The control script also
92 changes the dirty background ratio, so that background writeback of dirty pages
93 is not done anymore. Combined with a higher commit value (also 10 minutes) for
94 ext3 or ReiserFS filesystems (also done automatically by the control script),
95 this results in concentration of disk activity in a small time interval which
96 occurs only once every 10 minutes, or whenever the disk is forced to spin up by
97 a cache miss. The disk can then be spun down in the periods of inactivity.
99 If you want to find out which process caused the disk to spin up, you can
100 gather information by setting the flag /proc/sys/vm/block_dump. When this flag
101 is set, Linux reports all disk read and write operations that take place, and
102 all block dirtyings done to files. This makes it possible to debug why a disk
103 needs to spin up, and to increase battery life even more. The output of
104 block_dump is written to the kernel output, and it can be retrieved using
105 "dmesg". When you use block_dump, you may want to turn off klogd, otherwise
106 the output of block_dump will be logged, causing disk activity that is not
109 If 10 minutes is too much or too little downtime for you, you can configure
110 this downtime as follows. In the control script, set the MAX_AGE value to the
111 maximum number of seconds of disk downtime that you would like. You should
112 then set your filesystem's commit interval to the same value. The dirty ratio
113 is also configurable from the control script.
115 If you don't like the idea of the control script remounting your filesystems
116 for you, you can change DO_REMOUNTS to 0 in the script.
118 Thanks to Kiko Piris, the control script can be used to enable laptop mode on
119 both the Linux 2.4 and 2.6 series.
125 * Bartek Kania reports getting up to 50 minutes of extra battery life (on top
126 of his regular 3 to 3.5 hours) using very aggressive power management (hdparm
127 -B1) and a spindown time of 5 seconds (hdparm -S1).
129 * You can spin down the disk while playing MP3, by setting the disk readahead
130 to 8MB (hdparm -a 16384). Effectively, the disk will read a complete MP3 at
131 once, and will then spin down while the MP3 is playing. (Thanks to Bartek
134 * Drew Scott Daniels observed: "I don't know why, but when I decrease the number
135 of colours that my display uses it consumes less battery power. I've seen
136 this on powerbooks too. I hope that this is a piece of information that
137 might be useful to the Laptop Mode patch or it's users."
139 * One thing which will cause disks to spin up is not-present application
140 and dynamic library text pages. The kernel will load program text off disk
141 on-demand, so each time you invoke an application feature for the first
142 time, the kernel needs to spin the disk up to go and fetch that part of the
145 So it is useful to increase the disk readahead parameter greatly, so that
146 the kernel will pull all of the executable's pages into memory on the first
149 The supplied script does this.
151 * In syslog.conf, you can prefix entries with a dash ``-'' to omit syncing the
152 file after every logging. When you're using laptop-mode and your disk doesn't
153 spin down, this is a likely culprit.
155 * Richard Atterer observed that laptop mode does not work well with noflushd
156 (http://noflushd.sourceforge.net/), it seems that noflushd prevents laptop-mode
157 from doing its thing.
163 Please note that this control script works for the Linux 2.4 and 2.6 series.
165 --------------------CONTROL SCRIPT BEGIN------------------------------------------
168 # start or stop laptop_mode, best run by a power management daemon when
169 # ac gets connected/disconnected from a laptop
171 # install as /sbin/laptop_mode
173 # Contributors to this script: Kiko Piris
179 # Original Linux 2.4 version by: Jens Axboe
181 # Remove an option (the first parameter) of the form option=<number> from
182 # a mount options string (the rest of the parameters).
183 parse_mount_opts () {
188 sed 's/,'"$OPT"'=[0-9]*,/,/g' | \
195 # Remove an option (the first parameter) without any arguments from
196 # a mount option string (the rest of the parameters).
197 parse_nonumber_mount_opts () {
202 sed 's/,'"$OPT"',/,/g' | \
209 # Find out the state of a yes/no option (e.g. "atime"/"noatime") in
210 # fstab for a given filesystem, and use this state to replace the
211 # value of the option in another mount options string. The device
212 # is the first argument, the option name the second, and the default
213 # value the third. The remainder is the mount options string.
216 # parse_yesno_opts_wfstab /dev/hda1 atime atime defaults,noatime
218 # If fstab contains, say, "rw" for this filesystem, then the result
219 # will be "defaults,atime".
220 parse_yesno_opts_wfstab () {
228 PARSEDOPTS1="$(parse_nonumber_mount_opts $OPT $L_OPTS)"
229 PARSEDOPTS1="$(parse_nonumber_mount_opts no$OPT $PARSEDOPTS1)"
230 # Watch for a default atime in fstab
231 FSTAB_OPTS="$(cat /etc/fstab | sed 's/ / /g' | grep ^\ *"$L_DEV " | awk '{ print $4 }')"
232 if [ -z "$(echo "$FSTAB_OPTS" | grep "$OPT")" ] ; then
233 # option not specified in fstab -- choose the default.
234 echo "$PARSEDOPTS1,$DEF_OPT"
236 # option specified in fstab: extract the value and use it
237 if [ -z "$(echo "$FSTAB_OPTS" | grep "no$OPT")" ] ; then
238 # no$OPT not found -- so we must have $OPT.
239 echo "$PARSEDOPTS1,$OPT"
241 echo "$PARSEDOPTS1,no$OPT"
246 # Find out the state of a numbered option (e.g. "commit=NNN") in
247 # fstab for a given filesystem, and use this state to replace the
248 # value of the option in another mount options string. The device
249 # is the first argument, and the option name the second. The
250 # remainder is the mount options string in which the replacement
254 # parse_mount_opts_wfstab /dev/hda1 commit defaults,commit=7
256 # If fstab contains, say, "commit=3,rw" for this filesystem, then the
257 # result will be "rw,commit=3".
258 parse_mount_opts_wfstab () {
265 PARSEDOPTS1="$(parse_mount_opts $OPT $L_OPTS)"
266 # Watch for a default commit in fstab
267 FSTAB_OPTS="$(cat /etc/fstab | sed 's/ / /g' | grep ^\ *"$L_DEV " | awk '{ print $4 }')"
268 if [ -z "$(echo "$FSTAB_OPTS" | grep "$OPT=")" ] ; then
269 # option not specified in fstab: set it to 0
270 echo "$PARSEDOPTS1,$OPT=0"
272 # option specified in fstab: extract the value, and use it
273 echo -n "$PARSEDOPTS1,$OPT="
274 echo "$FSTAB_OPTS" | \
276 sed 's/.*,'"$OPT"'=//' | \
294 echo "Unhandled kernel version: $KLEVEL ('uname -r' = '$(uname -r)')"
299 # Shall we remount journaled fs. with appropiate commit interval? (1=yes)
302 # And shall we add the "noatime" option to that as well? (1=yes)
305 # age time, in seconds. should be put into a sysconfig file
308 # Dirty synchronous ratio. At this percentage of dirty pages the process which
309 # calls write() does its own writeback
313 # Allowed dirty background ratio, in percent. Once DIRTY_RATIO has been
314 # exceeded, the kernel will wake pdflush which will then reduce the amount
315 # of dirty memory to dirty_background_ratio. Set this nice and low, so once
316 # some writeout has commenced, we do a lot of it.
318 DIRTY_BACKGROUND_RATIO=5
320 READAHEAD=4096 # kilobytes
322 # kernel default dirty buffer age
325 DEF_DIRTY_BACKGROUND_RATIO=10
327 DEF_XFS_AGE_BUFFER=15
328 DEF_XFS_SYNC_INTERVAL=30
329 DEF_XFS_BUFD_INTERVAL=1
331 # This must be adjusted manually to the value of HZ in the running kernel
332 # on 2.4, until the XFS people change their 2.4 external interfaces to work in
333 # centisecs. This can be automated, but it's a work in progress that still needs
334 # some fixes. On 2.6 kernels, XFS uses USER_HZ instead of HZ for external
335 # interfaces, and that is currently always set to 100. So you don't need to
336 # change this on 2.6.
339 if [ ! -e /proc/sys/vm/laptop_mode ]; then
340 echo "Kernel is not patched with laptop_mode patch."
344 if [ ! -w /proc/sys/vm/laptop_mode ]; then
345 echo "You do not have enough privileges to enable laptop_mode."
349 if [ $DO_REMOUNT_NOATIME -eq 1 ]; then
350 NOATIME_OPT=",noatime"
355 AGE=$((100*$MAX_AGE))
356 XFS_AGE=$(($XFS_HZ*$MAX_AGE))
357 echo -n "Starting laptop_mode"
359 if [ -d /proc/sys/vm/pagebuf ] ; then
360 # (For 2.4 and early 2.6.)
361 # This only needs to be set, not reset -- it is only used when
362 # laptop mode is enabled.
363 echo $XFS_AGE > /proc/sys/vm/pagebuf/lm_flush_age
364 echo $XFS_AGE > /proc/sys/fs/xfs/lm_sync_interval
365 elif [ -f /proc/sys/fs/xfs/lm_age_buffer ] ; then
366 # (A couple of early 2.6 laptop mode patches had these.)
367 # The same goes for these.
368 echo $XFS_AGE > /proc/sys/fs/xfs/lm_age_buffer
369 echo $XFS_AGE > /proc/sys/fs/xfs/lm_sync_interval
370 elif [ -f /proc/sys/fs/xfs/age_buffer ] ; then
372 # But not for these -- they are also used in normal
374 echo $XFS_AGE > /proc/sys/fs/xfs/age_buffer
375 echo $XFS_AGE > /proc/sys/fs/xfs/sync_interval
376 elif [ -f /proc/sys/fs/xfs/age_buffer_centisecs ] ; then
378 # And not for these either. These are in centisecs,
379 # not USER_HZ, so we have to use $AGE, not $XFS_AGE.
380 echo $AGE > /proc/sys/fs/xfs/age_buffer_centisecs
381 echo $AGE > /proc/sys/fs/xfs/xfssyncd_centisecs
382 echo 3000 > /proc/sys/fs/xfs/xfsbufd_centisecs
387 echo "1" > /proc/sys/vm/laptop_mode
388 echo "30 500 0 0 $AGE $AGE 60 20 0" > /proc/sys/vm/bdflush
391 echo "5" > /proc/sys/vm/laptop_mode
392 echo "$AGE" > /proc/sys/vm/dirty_writeback_centisecs
393 echo "$AGE" > /proc/sys/vm/dirty_expire_centisecs
394 echo "$DIRTY_RATIO" > /proc/sys/vm/dirty_ratio
395 echo "$DIRTY_BACKGROUND_RATIO" > /proc/sys/vm/dirty_background_ratio
398 if [ $DO_REMOUNTS -eq 1 ]; then
399 cat /etc/mtab | while read DEV MP FST OPTS DUMP PASS ; do
400 PARSEDOPTS="$(parse_mount_opts "$OPTS")"
403 PARSEDOPTS="$(parse_mount_opts commit "$OPTS")"
404 mount $DEV -t $FST $MP -o remount,$PARSEDOPTS,commit=$MAX_AGE$NOATIME_OPT
407 mount $DEV -t $FST $MP -o remount,$OPTS$NOATIME_OPT
410 if [ -b $DEV ] ; then
411 blockdev --setra $(($READAHEAD * 2)) $DEV
418 U_AGE=$((100*$DEF_UPDATE))
419 B_AGE=$((100*$DEF_AGE))
420 echo -n "Stopping laptop_mode"
421 echo "0" > /proc/sys/vm/laptop_mode
422 if [ -f /proc/sys/fs/xfs/age_buffer ] && [ ! -f /proc/sys/fs/xfs/lm_age_buffer ] ; then
423 # These need to be restored, if there are no lm_*.
424 echo "$(($XFS_HZ*$DEF_XFS_AGE_BUFFER))" > /proc/sys/fs/xfs/age_buffer
425 echo "$(($XFS_HZ*$DEF_XFS_SYNC_INTERVAL))" > /proc/sys/fs/xfs/sync_interval
426 elif [ -f /proc/sys/fs/xfs/age_buffer_centisecs ] ; then
427 # These need to be restored as well.
428 echo "$((100*$DEF_XFS_AGE_BUFFER))" > /proc/sys/fs/xfs/age_buffer_centisecs
429 echo "$((100*$DEF_XFS_SYNC_INTERVAL))" > /proc/sys/fs/xfs/xfssyncd_centisecs
430 echo "$((100*$DEF_XFS_BUFD_INTERVAL))" > /proc/sys/fs/xfs/xfsbufd_centisecs
434 echo "30 500 0 0 $U_AGE $B_AGE 60 20 0" > /proc/sys/vm/bdflush
437 echo "$U_AGE" > /proc/sys/vm/dirty_writeback_centisecs
438 echo "$B_AGE" > /proc/sys/vm/dirty_expire_centisecs
439 echo "$DEF_DIRTY_RATIO" > /proc/sys/vm/dirty_ratio
440 echo "$DEF_DIRTY_BACKGROUND_RATIO" > /proc/sys/vm/dirty_background_ratio
443 if [ $DO_REMOUNTS -eq 1 ]; then
444 cat /etc/mtab | while read DEV MP FST OPTS DUMP PASS ; do
445 # Reset commit and atime options to defaults.
448 PARSEDOPTS="$(parse_mount_opts_wfstab $DEV commit $OPTS)"
449 PARSEDOPTS="$(parse_yesno_opts_wfstab $DEV atime atime $PARSEDOPTS)"
450 mount $DEV -t $FST $MP -o remount,$PARSEDOPTS
453 PARSEDOPTS="$(parse_yesno_opts_wfstab $DEV atime atime $OPTS)"
454 mount $DEV -t $FST $MP -o remount,$PARSEDOPTS
457 if [ -b $DEV ] ; then
458 blockdev --setra 256 $DEV
465 echo "Usage: $0 {start|stop}"
472 --------------------CONTROL SCRIPT END--------------------------------------------
478 Dax Kelson submitted this so that the ACPI acpid daemon will
479 kick off the laptop_mode script and run hdparm.
481 ---------------------------/etc/acpi/events/ac_adapter BEGIN-------------------------------------------
483 action=/etc/acpi/actions/battery.sh
484 ---------------------------/etc/acpi/events/ac_adapter END-------------------------------------------
486 ---------------------------/etc/acpi/actions/battery.sh BEGIN-------------------------------------------
490 # cat /proc/acpi/processor/CPU0/throttling for more info
494 # spindown time for HD (man hdparm for valid values)
495 # I prefer 2 hours for acad and 20 seconds for batt
499 # ac/battery event handler
501 status=`awk '/^state: / { print $2 }' /proc/acpi/ac_adapter/AC/state`
505 echo "Setting HD spindown for AC mode."
506 /sbin/laptop_mode stop
507 /sbin/hdparm -S $ACAD_HD /dev/hda > /dev/null 2>&1
508 /sbin/hdparm -B 255 /dev/hda > /dev/null 2>&1
509 #echo -n $ACAD_CPU:$ACAD_THR > /proc/acpi/processor/CPU0/limit
513 echo "Setting HD spindown for battery mode."
514 /sbin/laptop_mode start
515 /sbin/hdparm -S $BATT_HD /dev/hda > /dev/null 2>&1
516 /sbin/hdparm -B 1 /dev/hda > /dev/null 2>&1
517 #echo -n $BATT_CPU:$BATT_THR > /proc/acpi/processor/CPU0/limit
521 ---------------------------/etc/acpi/actions/battery.sh END-------------------------------------------
526 Bartek Kania submitted this, it can be used to measure how much time your disk
529 ---------------------------dslm.c BEGIN-------------------------------------------
531 * Simple Disk Sleep Monitor
533 * Licenced under the GPL
543 #include <sys/ioctl.h>
544 #include <linux/hdreg.h>
554 /* Check if the disk is in powersave-mode
555 * Most of the code is stolen from hdparm.
556 * 1 = active, 0 = standby/sleep, -1 = unknown */
557 int check_powermode(int fd)
559 unsigned char args[4] = {WIN_CHECKPOWERMODE1,0,0,0};
562 if (ioctl(fd, HDIO_DRIVE_CMD, &args)
563 && (args[0] = WIN_CHECKPOWERMODE2) /* try again with 0x98 */
564 && ioctl(fd, HDIO_DRIVE_CMD, &args)) {
565 if (errno != EIO || args[0] != 0 || args[1] != 0) {
566 state = -1; /* "unknown"; */
568 state = 0; /* "sleeping"; */
570 state = (args[2] == 255) ? 1 : 0;
572 D(printf(" drive state is: %d\n", state));
577 char *state_name(int i)
579 if (i == -1) return "unknown";
580 if (i == 0) return "sleeping";
581 if (i == 1) return "active";
583 return "internal error";
586 char *myctime(time_t time)
588 char *ts = ctime(&time);
589 ts[strlen(ts) - 1] = 0;
600 time_t curr_time = 0;
602 time_t active_time = 0;
603 time_t sleep_time = 0;
604 time_t unknown_time = 0;
605 time_t total_time = 0;
609 printf("Starting measurements\n");
611 last_state = check_powermode(fd);
612 start_time = last_time = time(0);
613 printf(" System is in state %s\n\n", state_name(last_state));
617 curr_state = check_powermode(fd);
619 if (curr_state != last_state || endit) {
622 time_diff = curr_time - last_time;
624 if (last_state == 1) active_time += time_diff;
625 else if (last_state == 0) sleep_time += time_diff;
626 else unknown_time += time_diff;
628 last_state = curr_state;
629 last_time = curr_time;
631 printf("%s: State-change to %s\n", myctime(curr_time),
632 state_name(curr_state));
635 changes--; /* Compensate for SIGINT */
637 total_time = time(0) - start_time;
638 printf("\nTotal running time: %lus\n", curr_time - start_time);
639 printf(" State changed %d times\n", changes);
641 tmp = (float)sleep_time / (float)total_time * 100;
642 printf(" Time in sleep state: %lus (%.2f%%)\n", sleep_time, tmp);
643 tmp = (float)active_time / (float)total_time * 100;
644 printf(" Time in active state: %lus (%.2f%%)\n", active_time, tmp);
645 tmp = (float)unknown_time / (float)total_time * 100;
646 printf(" Time in unknown state: %lus (%.2f%%)\n", unknown_time, tmp);
656 puts("usage: dslm [-w <time>] <disk>");
660 int main(int ac, char **av)
664 int settle_time = 60;
666 /* Parse the simple command-line */
670 settle_time = atoi(av[2]);
675 if (!(fd = open(disk, O_RDONLY|O_NONBLOCK))) {
676 printf("Can't open %s, because: %s\n", disk, strerror(errno));
681 printf("Waiting %d seconds for the system to settle down to "
682 "'normal'\n", settle_time);
685 puts("Not waiting for system to settle down");
687 signal(SIGINT, ender);
695 ---------------------------dslm.c END---------------------------------------------