From: Bart Samwel This patch contains the following updates to the laptop mode docs: * Various improvements to the text, added a hint and a caveat. * Control script changes to support XFS (in 2.4, 2.6 with lm_* sysctl values and 2.6 without lm_* sysctl values). * Control script changes to reset the the "atime" mount option to the value listed in fstab when laptop mode is stopped. * Bug fix in dslm.c.--- --- 25-akpm/Documentation/laptop-mode.txt | 204 ++++++++++++++++++++++++++++++---- 1 files changed, 180 insertions(+), 24 deletions(-) diff -puN Documentation/laptop-mode.txt~laptop-mode-doc-update Documentation/laptop-mode.txt --- 25/Documentation/laptop-mode.txt~laptop-mode-doc-update 2004-04-04 16:40:33.014162448 -0700 +++ 25-akpm/Documentation/laptop-mode.txt 2004-04-04 16:40:33.020161536 -0700 @@ -3,6 +3,7 @@ How to conserve battery power using lapt Document Author: Bart Samwel (bart@samwel.tk) Date created: January 2, 2004 +Last modified: April 3, 2004 Introduction ------------ @@ -40,15 +41,20 @@ The value -S 4 means 20 seconds idle tim now only spin up when a disk cache miss occurs, or at least once every 10 minutes to write back any pending changes. -To stop laptop_mode, remount your filesystems with regular commit intervals -(e.g., 5 seconds), and run "laptop_mode stop". +To stop laptop_mode, run "laptop_mode stop". Caveats ------- * The downside of laptop mode is that you have a chance of losing up - to 10 minutes of work. If you cannot afford this, don't use it! + to 10 minutes of work. If you cannot afford this, don't use it! It's + wise to turn OFF laptop mode when you're almost out of battery -- + although this will make the battery run out faster, at least you'll + lose less work when it actually runs out. I'm still looking for someone + to submit instructions on how to turn off laptop mode when battery is low, + e.g., using ACPI events. I don't have a laptop myself, so if you do and + you care to contribute such instructions, please do. * Most desktop hard drives have a very limited lifetime measured in spindown cycles, typically about 50.000 times (it's usually listed on the spec sheet). @@ -63,6 +69,14 @@ Caveats * If you have your filesystems listed as type "auto" in fstab, like I did, then the control script will not recognize them as filesystems that need remounting. +* If you have XFS, make SURE that you set the XFS_HZ value in the control script + correctly, to the value of HZ of your running kernel. Laptop mode will not + work correctly if it is set too low, and you may lose data if it is set too + high. The reason for this problem is that XFS does not export its sysctl + variables in centisecs (like most other subsystems do) but in "jiffies", + which is an internal kernel measure. Once this is fixed things will get better. + + The details ----------- @@ -88,7 +102,11 @@ If you want to find out which process ca gather information by setting the flag /proc/sys/vm/block_dump. When this flag is set, Linux reports all disk read and write operations that take place, and all block dirtyings done to files. This makes it possible to debug why a disk -needs to spin up, and to increase battery life even more. +needs to spin up, and to increase battery life even more. The output of +block_dump is written to the kernel output, and it can be retrieved using +"dmesg". When you use block_dump, you may want to turn off klogd, otherwise +the output of block_dump will be logged, causing disk activity that is not +normally there. If 10 minutes is too much or too little downtime for you, you can configure this downtime as follows. In the control script, set the MAX_AGE value to the @@ -132,6 +150,10 @@ Tips & Tricks The supplied script does this. +* In syslog.conf, you can prefix entries with a dash ``-'' to omit syncing the + file after every logging. When you're using laptop-mode and your disk doesn't + spin down, this is a likely culprit. + Control script -------------- @@ -139,7 +161,7 @@ Control script Please note that this control script works for the Linux 2.4 and 2.6 series. --------------------CONTROL SCRIPT BEGIN------------------------------------------ -#!/bin/sh +#! /bin/sh # start or stop laptop_mode, best run by a power management daemon when # ac gets connected/disconnected from a laptop @@ -148,28 +170,120 @@ Please note that this control script wor # # Contributors to this script: Kiko Piris # Bart Samwel +# Micha Feigin +# Andrew Morton # Dax Kelson +# # Original Linux 2.4 version by: Jens Axboe +# Remove an option (the first parameter) of the form option= from +# a mount options string (the rest of the parameters). parse_mount_opts () { + OPT="$1" + shift echo "$*" | \ - sed 's/commit=[0-9]*//g' | \ + sed 's/.*/,&,/' | \ + sed 's/,'"$OPT"'=[0-9]*,/,/g' | \ sed 's/,,*/,/g' | \ sed 's/^,//' | \ sed 's/,$//' | \ cat - } +# Remove an option (the first parameter) without any arguments from +# a mount option string (the rest of the parameters). +parse_nonumber_mount_opts () { + OPT="$1" + shift + echo "$*" | \ + sed 's/.*/,&,/' | \ + sed 's/,'"$OPT"',/,/g' | \ + sed 's/,,*/,/g' | \ + sed 's/^,//' | \ + sed 's/,$//' | \ + cat - +} + +# Find out the state of a yes/no option (e.g. "atime"/"noatime") in +# fstab for a given filesystem, and use this state to replace the +# value of the option in another mount options string. The device +# is the first argument, the option name the second, and the default +# value the third. The remainder is the mount options string. +# +# Example: +# parse_yesno_opts_wfstab /dev/hda1 atime atime defaults,noatime +# +# If fstab contains, say, "rw" for this filesystem, then the result +# will be "defaults,atime". +parse_yesno_opts_wfstab () { + L_DEV=$1 + shift + OPT=$1 + shift + DEF_OPT=$1 + shift + L_OPTS="$*" + PARSEDOPTS1="$(parse_nonumber_mount_opts $OPT $L_OPTS)" + PARSEDOPTS1="$(parse_nonumber_mount_opts no$OPT $PARSEDOPTS1)" + # Watch for a default atime in fstab + FSTAB_OPTS="$(cat /etc/fstab | sed 's/ / /g' | grep ^\ *"$L_DEV " | awk '{ print $4 }')" + if [ -z "$(echo "$FSTAB_OPTS" | grep "$OPT")" ] ; then + # option not specified in fstab -- choose the default. + echo "$PARSEDOPTS1,$DEF_OPT" + else + # option specified in fstab: extract the value and use it + if [ -z "$(echo "$FSTAB_OPTS" | grep "no$OPT")" ] ; then + # no$OPT not found -- so we must have $OPT. + echo "$PARSEDOPTS1,$OPT" + else + echo "$PARSEDOPTS1,no$OPT" + fi + fi +} + +# Find out the state of a numbered option (e.g. "commit=NNN") in +# fstab for a given filesystem, and use this state to replace the +# value of the option in another mount options string. The device +# is the first argument, and the option name the second. The +# remainder is the mount options string in which the replacement +# must be done. +# +# Example: +# parse_mount_opts_wfstab /dev/hda1 commit defaults,commit=7 +# +# If fstab contains, say, "commit=3,rw" for this filesystem, then the +# result will be "rw,commit=3". +parse_mount_opts_wfstab () { + L_DEV=$1 + shift + OPT=$1 + shift + L_OPTS="$*" + + PARSEDOPTS1="$(parse_mount_opts $OPT $L_OPTS)" + # Watch for a default commit in fstab + FSTAB_OPTS="$(cat /etc/fstab | sed 's/ / /g' | grep ^\ *"$L_DEV " | awk '{ print $4 }')" + if [ -z "$(echo "$FSTAB_OPTS" | grep "$OPT=")" ] ; then + # option not specified in fstab: set it to 0 + echo "$PARSEDOPTS1,$OPT=0" + else + # option specified in fstab: extract the value, and use it + echo -n "$PARSEDOPTS1,$OPT=" + echo "$FSTAB_OPTS" | \ + sed 's/.*/,&,/' | \ + sed 's/.*,'"$OPT"'=//' | \ + sed 's/,.*//' | \ + cat - + fi +} + KLEVEL="$(uname -r | cut -c1-3)" case "$KLEVEL" in - "2.4") - true - ;; - "2.6") + "2.4"|"2.6") true ;; *) - echo "Unhandled kernel level: $KLEVEL ('uname -r' = '$(uname -r)')" + echo "Unhandled kernel version: $KLEVEL ('uname -r' = '$(uname -r)')" exit 1 ;; esac @@ -199,7 +313,13 @@ DEF_AGE=30 DEF_UPDATE=5 DEF_DIRTY_BACKGROUND_RATIO=10 DEF_DIRTY_RATIO=40 +DEF_XFS_AGE_BUFFER=15 +DEF_XFS_SYNC_INTERVAL=30 +# This must be adjusted manually to the value of HZ in the running kernel, +# until the XFS people change their external interfaces to work in centisecs +# like the rest of the external world. Unfortunately this cannot be automated. :( +XFS_HZ=1000 if [ ! -e /proc/sys/vm/laptop_mode ]; then echo "Kernel is not patched with laptop_mode patch." @@ -214,7 +334,25 @@ fi case "$1" in start) AGE=$((100*$MAX_AGE)) + XFS_AGE=$(($XFS_HZ*$MAX_AGE)) echo -n "Starting laptop_mode" + + if [ -d /proc/sys/vm/pagebuf ] ; then + # This only needs to be set, not reset -- it is only used when + # laptop mode is enabled. + echo $XFS_AGE > /proc/sys/vm/pagebuf/lm_flush_age + echo $XFS_AGE > /proc/sys/fs/xfs/lm_sync_interval + elif [ -f /proc/sys/fs/xfs/lm_age_buffer ] ; then + # The same goes for these. + echo $XFS_AGE > /proc/sys/fs/xfs/lm_age_buffer + echo $XFS_AGE > /proc/sys/fs/xfs/lm_sync_interval + elif [ -f /proc/sys/fs/xfs/age_buffer ] ; then + # But not for these -- they are also used in normal + # operation. + echo $XFS_AGE > /proc/sys/fs/xfs/age_buffer + echo $XFS_AGE > /proc/sys/fs/xfs/sync_interval + fi + case "$KLEVEL" in "2.4") echo "1" > /proc/sys/vm/laptop_mode @@ -232,26 +370,36 @@ case "$1" in cat /etc/mtab | while read DEV MP FST OPTS DUMP PASS ; do PARSEDOPTS="$(parse_mount_opts "$OPTS")" case "$FST" in - "ext3") mount $DEV -t $FST $MP -o remount,$PARSEDOPTS,commit=$MAX_AGE,noatime ;; - "reiserfs") mount $DEV -t $FST $MP -o remount,$PARSEDOPTS,commit=$MAX_AGE,noatime ;; - "xfs") mount $DEV -t $FST $MP -o remount,$PARSEDOPTS,commit=$MAX_AGE,noatime ;; + "ext3"|"reiserfs") + PARSEDOPTS="$(parse_mount_opts commit "$OPTS")" + mount $DEV -t $FST $MP -o remount,$PARSEDOPTS,commit=$MAX_AGE,noatime + ;; + "xfs") + mount $DEV -t $FST $MP -o remount,$OPTS,noatime + ;; esac - blockdev --setra $((READAHEAD * 2)) $DEV done fi + if [ -b $DEV ] ; then + blockdev --setra $(($READAHEAD * 2)) $DEV + fi echo "." ;; stop) U_AGE=$((100*$DEF_UPDATE)) B_AGE=$((100*$DEF_AGE)) echo -n "Stopping laptop_mode" + echo "0" > /proc/sys/vm/laptop_mode + if [ -f /proc/sys/fs/xfs/age_buffer ] && [ ! -f /proc/sys/fs/xfs/lm_age_buffer ] ; then + # These need to be restored though, if there are no lm_*. + echo "$(($XFS_HZ*$DEF_XFS_AGE_BUFFER))" > /proc/sys/fs/xfs/age_buffer + echo "$(($XFS_HZ*$DEF_XFS_SYNC_INTERVAL))" > /proc/sys/fs/xfs/sync_interval + fi case "$KLEVEL" in "2.4") - echo "0" > /proc/sys/vm/laptop_mode echo "30 500 0 0 $U_AGE $B_AGE 60 20 0" > /proc/sys/vm/bdflush ;; "2.6") - echo "0" > /proc/sys/vm/laptop_mode echo "$U_AGE" > /proc/sys/vm/dirty_writeback_centisecs echo "$B_AGE" > /proc/sys/vm/dirty_expire_centisecs echo "$DEF_DIRTY_RATIO" > /proc/sys/vm/dirty_ratio @@ -260,19 +408,27 @@ case "$1" in esac if [ $DO_REMOUNTS -eq 1 ]; then cat /etc/mtab | while read DEV MP FST OPTS DUMP PASS ; do - PARSEDOPTS="$(parse_mount_opts "$OPTS")" + # Reset commit and atime options to defaults. case "$FST" in - "ext3") mount $DEV -t $FST $MP -o remount,$PARSEDOPTS ;; - "reiserfs") mount $DEV -t $FST $MP -o remount,$PARSEDOPTS ;; - "xfs") mount $DEV -t $FST $MP -o remount,$PARSEDOPTS ;; + "ext3"|"reiserfs") + PARSEDOPTS="$(parse_mount_opts_wfstab $DEV commit $OPTS)" + PARSEDOPTS="$(parse_yesno_opts_wfstab $DEV atime atime $PARSEDOPTS)" + mount $DEV -t $FST $MP -o remount,$PARSEDOPTS + ;; + "xfs") + PARSEDOPTS="$(parse_yesno_opts_wfstab $DEV atime atime $OPTS)" + mount $DEV -t $FST $MP -o remount,$PARSEDOPTS + ;; esac - blockdev --setra 256 $DEV done fi + if [ -b $DEV ] ; then + blockdev --setra 256 $DEV + fi echo "." ;; *) - echo "$0 {start|stop}" + echo "Usage: $0 {start|stop}" ;; esac @@ -379,7 +535,7 @@ int check_powermode(int fd) } else { state = (args[2] == 255) ? 1 : 0; } - D(printf(" drive state is: %s\n", state)); + D(printf(" drive state is: %d\n", state)); return state; } _