]>
Commit | Line | Data |
---|---|---|
3f65c653 | 1 | diff -Nbaur linux-2.6.1/Documentation/laptop-mode.txt linux-2.6.1-withlaptopmode/Documentation/laptop-mode.txt |
2 | --- linux-2.6.1/Documentation/laptop-mode.txt 1970-01-01 01:00:00.000000000 +0100 | |
3 | +++ linux-2.6.1-withlaptopmode/Documentation/laptop-mode.txt 2004-01-10 10:47:14.000000000 +0100 | |
4 | @@ -0,0 +1,475 @@ | |
5 | +How to conserve battery power using laptop-mode | |
6 | +----------------------------------------------- | |
7 | + | |
8 | +Document Author: Bart Samwel (bart@samwel.tk) | |
9 | +Date created: January 2, 2004 | |
10 | + | |
11 | +Introduction | |
12 | +------------ | |
13 | + | |
14 | +Laptopmode is used to minimize the time that the hard disk needs to be spun up, | |
15 | +to conserve battery power on laptops. It has been reported to cause significant | |
16 | +power savings. | |
17 | + | |
18 | +Contents | |
19 | +-------- | |
20 | + | |
21 | +* Introduction | |
22 | +* The short story | |
23 | +* Caveats | |
24 | +* The details | |
25 | +* Tips & Tricks | |
26 | +* Control script | |
27 | +* ACPI integration | |
28 | +* Monitoring tool | |
29 | + | |
30 | + | |
31 | +The short story | |
32 | +--------------- | |
33 | + | |
34 | +If you just want to use it, run the laptop_mode control script (which is included | |
35 | +at the end of this document) as follows: | |
36 | + | |
37 | +# laptop_mode start | |
38 | + | |
39 | +Then set your harddisk spindown time to a relatively low value with hdparm: | |
40 | + | |
41 | +hdparm -S 4 /dev/hda | |
42 | + | |
43 | +The value -S 4 means 20 seconds idle time before spindown. Your harddisk will | |
44 | +now only spin up when a disk cache miss occurs, or at least once every 10 | |
45 | +minutes to write back any pending changes. | |
46 | + | |
47 | +To stop laptop_mode, remount your filesystems with regular commit intervals | |
48 | +(e.g., 5 seconds), and run "laptop_mode stop". | |
49 | + | |
50 | + | |
51 | +Caveats | |
52 | +------- | |
53 | + | |
54 | +* The downside of laptop mode is that you have a chance of losing up | |
55 | + to 10 minutes of work. If you cannot afford this, don't use it! | |
56 | + | |
57 | +* Most desktop hard drives have a very limited lifetime measured in spindown | |
58 | + cycles, typically about 50.000 times (it's usually listed on the spec sheet). | |
59 | + Check your drive's rating, and don't wear down your drive's lifetime if you | |
60 | + don't need to. | |
61 | + | |
62 | +* If you mount some of your ext3/reiserfs filesystems with the -n option, then | |
63 | + the control script will not be able to remount them correctly. You must set | |
64 | + DO_REMOUNTS=0 in the control script, otherwise it will remount them with the | |
65 | + wrong options -- or it will fail because it cannot write to /etc/mtab. | |
66 | + | |
67 | +* If you have your filesystems listed as type "auto" in fstab, like I did, then | |
68 | + the control script will not recognize them as filesystems that need remounting. | |
69 | + | |
70 | +The details | |
71 | +----------- | |
72 | + | |
73 | +Laptop-mode is controlled by the flag /proc/sys/vm/laptop_mode. When this | |
74 | +flag is set, any physical disk read operation (that might have caused the | |
75 | +hard disk to spin up) causes Linux to flush all dirty blocks. The result | |
76 | +of this is that after a disk has spun down, it will not be spun up anymore | |
77 | +to write dirty blocks, because those blocks had already been written | |
78 | +immediately after the most recent read operation | |
79 | + | |
80 | +To increase the effectiveness of the laptop_mode strategy, the laptop_mode | |
81 | +control script increases dirty_expire_centisecs and dirty_writeback_centisecs in | |
82 | +/proc/sys/vm to about 10 minutes (by default), which means that pages that are | |
83 | +dirtied are not forced to be written to disk as often. The control script also | |
84 | +changes the dirty background ratio, so that background writeback of dirty pages | |
85 | +is not done anymore. Combined with a higher commit value (also 10 minutes) for | |
86 | +ext3 or ReiserFS filesystems (also done automatically by the control script), | |
87 | +this results in concentration of disk activity in a small time interval which | |
88 | +occurs only once every 10 minutes, or whenever the disk is forced to spin up by | |
89 | +a cache miss. The disk can then be spun down in the periods of inactivity. | |
90 | + | |
91 | +If you want to find out which process caused the disk to spin up, you can | |
92 | +gather information by setting the flag /proc/sys/vm/block_dump. When this flag | |
93 | +is set, Linux reports all disk read and write operations that take place, and | |
94 | +all block dirtyings done to files. This makes it possible to debug why a disk | |
95 | +needs to spin up, and to increase battery life even more. | |
96 | + | |
97 | +If 10 minutes is too much or too little downtime for you, you can configure | |
98 | +this downtime as follows. In the control script, set the MAX_AGE value to the | |
99 | +maximum number of seconds of disk downtime that you would like. You should | |
100 | +then set your filesystem's commit interval to the same value. The dirty ratio | |
101 | +is also configurable from the control script. | |
102 | + | |
103 | +If you don't like the idea of the control script remounting your filesystems | |
104 | +for you, you can change DO_REMOUNTS to 0 in the script. | |
105 | + | |
106 | +Thanks to Kiko Piris, the control script can be used to enable laptop mode on | |
107 | +both the Linux 2.4 and 2.6 series. | |
108 | + | |
109 | + | |
110 | +Tips & Tricks | |
111 | +------------- | |
112 | + | |
113 | +* Bartek Kania reports getting up to 50 minutes of extra battery life (on top | |
114 | + of his regular 3 to 3.5 hours) using very aggressive power management (hdparm | |
115 | + -B1) and a spindown time of 5 seconds (hdparm -S1). | |
116 | + | |
117 | +* You can spin down the disk while playing MP3, by setting the disk readahead | |
118 | + to 8MB (hdparm -a 16384). Effectively, the disk will read a complete MP3 at | |
119 | + once, and will then spin down while the MP3 is playing. (Thanks to Bartek | |
120 | + Kania.) | |
121 | + | |
122 | + | |
123 | +Control script | |
124 | +-------------- | |
125 | + | |
126 | +Please note that this control script works for the Linux 2.4 and 2.6 series. | |
127 | + | |
128 | +--------------------CONTROL SCRIPT BEGIN------------------------------------------ | |
129 | +#!/bin/sh | |
130 | + | |
131 | +# start or stop laptop_mode, best run by a power management daemon when | |
132 | +# ac gets connected/disconnected from a laptop | |
133 | +# | |
134 | +# install as /sbin/laptop_mode | |
135 | +# | |
136 | +# Contributors to this script: Kiko Piris | |
137 | +# Bart Samwel | |
138 | +# Dax Kelson | |
139 | +# Original Linux 2.4 version by: Jens Axboe | |
140 | + | |
141 | +parse_mount_opts () { | |
142 | + echo "$*" | \ | |
143 | + sed 's/.*/,&,/g' | \ | |
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 "Usage: $0 {start|stop}" | |
251 | + ;; | |
252 | + | |
253 | +esac | |
254 | + | |
255 | +exit 0 | |
256 | +--------------------CONTROL SCRIPT END-------------------------------------------- | |
257 | + | |
258 | + | |
259 | +ACPI integration | |
260 | +---------------- | |
261 | + | |
262 | +Dax Kelson submitted this so that the ACPI acpid daemon will | |
263 | +kick off the laptop_mode script and run hdparm. | |
264 | + | |
265 | +---------------------------/etc/acpi/events/ac_adapter BEGIN------------------------------------------- | |
266 | +event=ac_adapter | |
267 | +action=/etc/acpi/actions/battery.sh | |
268 | +---------------------------/etc/acpi/events/ac_adapter END------------------------------------------- | |
269 | + | |
270 | +---------------------------/etc/acpi/actions/battery.sh BEGIN------------------------------------------- | |
271 | +#!/bin/sh | |
272 | + | |
273 | +# cpu throttling | |
274 | +# cat /proc/acpi/processor/CPU0/throttling for more info | |
275 | +ACAD_THR=0 | |
276 | +BATT_THR=2 | |
277 | + | |
278 | +# spindown time for HD (man hdparm for valid values) | |
279 | +# I prefer 2 hours for acad and 20 seconds for batt | |
280 | +ACAD_HD=244 | |
281 | +BATT_HD=4 | |
282 | + | |
283 | +# ac/battery event handler | |
284 | + | |
285 | +status=`awk '/^state: / { print $2 }' /proc/acpi/ac_adapter/AC/state` | |
286 | + | |
287 | +case $status in | |
288 | + "on-line") | |
289 | + echo "Setting HD spindown to 2 hours" | |
290 | + /sbin/laptop-mode stop | |
291 | + /sbin/hdparm -S $ACAD_HD /dev/hda > /dev/null 2>&1 | |
292 | + /sbin/hdparm -B 255 /dev/hda > /dev/null 2>&1 | |
293 | + #echo -n $ACAD_CPU:$ACAD_THR > /proc/acpi/processor/CPU0/limit | |
294 | + exit 0 | |
295 | + ;; | |
296 | + "off-line") | |
297 | + echo "Setting HD spindown to 20 seconds" | |
298 | + /sbin/laptop-mode start | |
299 | + /sbin/hdparm -S $BATT_HD /dev/hda > /dev/null 2>&1 | |
300 | + /sbin/hdparm -B 1 /dev/hda > /dev/null 2>&1 | |
301 | + #echo -n $BATT_CPU:$BATT_THR > /proc/acpi/processor/CPU0/limit | |
302 | + exit 0 | |
303 | + ;; | |
304 | +esac | |
305 | +---------------------------/etc/acpi/actions/battery.sh END------------------------------------------- | |
306 | + | |
307 | +Monitoring tool | |
308 | +--------------- | |
309 | + | |
310 | +Bartek Kania submitted this, it can be used to measure how much time your disk | |
311 | +spends spun up/down. | |
312 | + | |
313 | +---------------------------dslm.c BEGIN------------------------------------------- | |
314 | +/* | |
315 | + * Simple Disk SLeep Monitor | |
316 | + * by Bartek Kania | |
317 | + * Licenced under the GPL | |
318 | + */ | |
319 | +#include <unistd.h> | |
320 | +#include <stdlib.h> | |
321 | +#include <stdio.h> | |
322 | +#include <fcntl.h> | |
323 | +#include <errno.h> | |
324 | +#include <time.h> | |
325 | +#include <string.h> | |
326 | +#include <signal.h> | |
327 | +#include <sys/ioctl.h> | |
328 | +#include <linux/hdreg.h> | |
329 | + | |
330 | +#ifdef DEBUG | |
331 | +#define D(x) x | |
332 | +#else | |
333 | +#define D(x) | |
334 | +#endif | |
335 | + | |
336 | +int endit = 0; | |
337 | + | |
338 | +/* Check if the disk is in powersave-mode | |
339 | + * Most of the code is stolen from hdparm. | |
340 | + * 1 = active, 0 = standby/sleep, -1 = unknown */ | |
341 | +int check_powermode(int fd) | |
342 | +{ | |
343 | + unsigned char args[4] = {WIN_CHECKPOWERMODE1,0,0,0}; | |
344 | + int state; | |
345 | + | |
346 | + if (ioctl(fd, HDIO_DRIVE_CMD, &args) | |
347 | + && (args[0] = WIN_CHECKPOWERMODE2) /* try again with 0x98 */ | |
348 | + && ioctl(fd, HDIO_DRIVE_CMD, &args)) { | |
349 | + if (errno != EIO || args[0] != 0 || args[1] != 0) { | |
350 | + state = -1; /* "unknown"; */ | |
351 | + } else | |
352 | + state = 0; /* "sleeping"; */ | |
353 | + } else { | |
354 | + state = (args[2] == 255) ? 1 : 0; | |
355 | + } | |
356 | + D(printf(" drive state is: %s\n", state)); | |
357 | + | |
358 | + return state; | |
359 | +} | |
360 | + | |
361 | +char *state_name(int i) | |
362 | +{ | |
363 | + if (i == -1) return "unknown"; | |
364 | + if (i == 0) return "sleeping"; | |
365 | + if (i == 1) return "active"; | |
366 | + | |
367 | + return "internal error"; | |
368 | +} | |
369 | + | |
370 | +char *myctime(time_t time) | |
371 | +{ | |
372 | + char *ts = ctime(&time); | |
373 | + ts[strlen(ts) - 1] = 0; | |
374 | + | |
375 | + return ts; | |
376 | +} | |
377 | + | |
378 | +void measure(int fd) | |
379 | +{ | |
380 | + time_t start_time; | |
381 | + int last_state; | |
382 | + time_t last_time; | |
383 | + int curr_state; | |
384 | + time_t curr_time = 0; | |
385 | + time_t time_diff; | |
386 | + time_t active_time = 0; | |
387 | + time_t sleep_time = 0; | |
388 | + time_t unknown_time = 0; | |
389 | + time_t total_time = 0; | |
390 | + int changes = 0; | |
391 | + float tmp; | |
392 | + | |
393 | + printf("Starting measurements\n"); | |
394 | + | |
395 | + last_state = check_powermode(fd); | |
396 | + start_time = last_time = time(0); | |
397 | + printf(" System is in state %s\n\n", state_name(last_state)); | |
398 | + | |
399 | + while(!endit) { | |
400 | + sleep(1); | |
401 | + curr_state = check_powermode(fd); | |
402 | + | |
403 | + if (curr_state != last_state || endit) { | |
404 | + changes++; | |
405 | + curr_time = time(0); | |
406 | + time_diff = curr_time - last_time; | |
407 | + | |
408 | + if (last_state == 1) active_time += time_diff; | |
409 | + else if (last_state == 0) sleep_time += time_diff; | |
410 | + else unknown_time += time_diff; | |
411 | + | |
412 | + last_state = curr_state; | |
413 | + last_time = curr_time; | |
414 | + | |
415 | + printf("%s: State-change to %s\n", myctime(curr_time), | |
416 | + state_name(curr_state)); | |
417 | + } | |
418 | + } | |
419 | + changes--; /* Compensate for SIGINT */ | |
420 | + | |
421 | + total_time = time(0) - start_time; | |
422 | + printf("\nTotal running time: %lus\n", curr_time - start_time); | |
423 | + printf(" State changed %d times\n", changes); | |
424 | + | |
425 | + tmp = (float)sleep_time / (float)total_time * 100; | |
426 | + printf(" Time in sleep state: %lus (%.2f%%)\n", sleep_time, tmp); | |
427 | + tmp = (float)active_time / (float)total_time * 100; | |
428 | + printf(" Time in active state: %lus (%.2f%%)\n", active_time, tmp); | |
429 | + tmp = (float)unknown_time / (float)total_time * 100; | |
430 | + printf(" Time in unknown state: %lus (%.2f%%)\n", unknown_time, tmp); | |
431 | +} | |
432 | + | |
433 | +void ender(int s) | |
434 | +{ | |
435 | + endit = 1; | |
436 | +} | |
437 | + | |
438 | +void usage() | |
439 | +{ | |
440 | + puts("usage: dslm [-w <time>] <disk>"); | |
441 | + exit(0); | |
442 | +} | |
443 | + | |
444 | +int main(int ac, char **av) | |
445 | +{ | |
446 | + int fd; | |
447 | + char *disk = 0; | |
448 | + int settle_time = 60; | |
449 | + | |
450 | + /* Parse the simple command-line */ | |
451 | + if (ac == 2) | |
452 | + disk = av[1]; | |
453 | + else if (ac == 4) { | |
454 | + settle_time = atoi(av[2]); | |
455 | + disk = av[3]; | |
456 | + } else | |
457 | + usage(); | |
458 | + | |
459 | + if (!(fd = open(disk, O_RDONLY|O_NONBLOCK))) { | |
460 | + printf("Can't open %s, because: %s\n", disk, strerror(errno)); | |
461 | + exit(-1); | |
462 | + } | |
463 | + | |
464 | + if (settle_time) { | |
465 | + printf("Waiting %d seconds for the system to settle down to " | |
466 | + "'normal'\n", settle_time); | |
467 | + sleep(settle_time); | |
468 | + } else | |
469 | + puts("Not waiting for system to settle down"); | |
470 | + | |
471 | + signal(SIGINT, ender); | |
472 | + | |
473 | + measure(fd); | |
474 | + | |
475 | + close(fd); | |
476 | + | |
477 | + return 0; | |
478 | +} | |
479 | +---------------------------dslm.c END--------------------------------------------- | |
480 | diff -Nbaur linux-2.6.1/drivers/block/ll_rw_blk.c linux-2.6.1-withlaptopmode/drivers/block/ll_rw_blk.c | |
481 | --- linux-2.6.1/drivers/block/ll_rw_blk.c 2004-01-09 20:29:05.000000000 +0100 | |
482 | +++ linux-2.6.1-withlaptopmode/drivers/block/ll_rw_blk.c 2004-01-09 20:29:39.000000000 +0100 | |
483 | @@ -27,6 +27,7 @@ | |
484 | #include <linux/completion.h> | |
485 | #include <linux/slab.h> | |
486 | #include <linux/swap.h> | |
487 | +#include <linux/writeback.h> | |
488 | ||
489 | static void blk_unplug_work(void *data); | |
490 | static void blk_unplug_timeout(unsigned long data); | |
491 | @@ -2302,6 +2303,15 @@ | |
492 | mod_page_state(pgpgout, count); | |
493 | else | |
494 | mod_page_state(pgpgin, count); | |
495 | + | |
496 | + if (unlikely(block_dump)) { | |
497 | + char b[BDEVNAME_SIZE]; | |
498 | + printk("%s(%d): %s block %Lu on %s\n", | |
499 | + current->comm, current->pid, | |
500 | + (rw & WRITE) ? "WRITE" : "READ", | |
501 | + (unsigned long long)bio->bi_sector, bdevname(bio->bi_bdev,b)); | |
502 | + } | |
503 | + | |
504 | generic_make_request(bio); | |
505 | return 1; | |
506 | } | |
507 | @@ -2593,6 +2603,12 @@ | |
508 | disk_stat_add(disk, write_ticks, duration); | |
509 | break; | |
510 | case READ: | |
511 | + /* | |
512 | + * schedule the writeout of pending dirty data when the disk is idle. | |
513 | + * (postpone writeback until system is quiescent again.) | |
514 | + */ | |
515 | + if (unlikely(laptop_mode)) | |
516 | + disk_is_spun_up(1); | |
517 | disk_stat_inc(disk, reads); | |
518 | disk_stat_add(disk, read_ticks, duration); | |
519 | break; | |
520 | diff -Nbaur linux-2.6.1/fs/buffer.c linux-2.6.1-withlaptopmode/fs/buffer.c | |
521 | --- linux-2.6.1/fs/buffer.c 2004-01-09 20:29:10.000000000 +0100 | |
522 | +++ linux-2.6.1-withlaptopmode/fs/buffer.c 2004-01-09 20:29:45.000000000 +0100 | |
523 | @@ -855,10 +855,13 @@ | |
524 | struct buffer_head *bh = head; | |
525 | ||
526 | do { | |
527 | - if (buffer_uptodate(bh)) | |
528 | + if (buffer_uptodate(bh)) { | |
529 | set_buffer_dirty(bh); | |
530 | - else | |
531 | + if (unlikely(block_dump)) | |
532 | + printk("%s(%d): dirtied buffer\n", current->comm, current->pid); | |
533 | + } else { | |
534 | buffer_error(); | |
535 | + } | |
536 | bh = bh->b_this_page; | |
537 | } while (bh != head); | |
538 | } | |
539 | diff -Nbaur linux-2.6.1/include/linux/sysctl.h linux-2.6.1-withlaptopmode/include/linux/sysctl.h | |
540 | --- linux-2.6.1/include/linux/sysctl.h 2004-01-09 20:29:10.000000000 +0100 | |
541 | +++ linux-2.6.1-withlaptopmode/include/linux/sysctl.h 2004-01-09 20:29:46.000000000 +0100 | |
542 | @@ -154,6 +154,8 @@ | |
543 | VM_SWAPPINESS=19, /* Tendency to steal mapped memory */ | |
544 | VM_LOWER_ZONE_PROTECTION=20,/* Amount of protection of lower zones */ | |
545 | VM_MIN_FREE_KBYTES=21, /* Minimum free kilobytes to maintain */ | |
546 | + VM_LAPTOP_MODE=22, /* vm laptop mode */ | |
547 | + VM_BLOCK_DUMP=23, /* block dump mode */ | |
548 | }; | |
549 | ||
550 | ||
551 | diff -Nbaur linux-2.6.1/include/linux/writeback.h linux-2.6.1-withlaptopmode/include/linux/writeback.h | |
552 | --- linux-2.6.1/include/linux/writeback.h 2003-12-24 05:19:46.000000000 +0100 | |
553 | +++ linux-2.6.1-withlaptopmode/include/linux/writeback.h 2004-01-04 12:10:58.000000000 +0100 | |
554 | @@ -71,12 +71,15 @@ | |
555 | * mm/page-writeback.c | |
556 | */ | |
557 | int wakeup_bdflush(long nr_pages); | |
558 | +void disk_is_spun_up(int postpone_writeback); | |
559 | ||
560 | -/* These 5 are exported to sysctl. */ | |
561 | +/* These are exported to sysctl. */ | |
562 | extern int dirty_background_ratio; | |
563 | extern int vm_dirty_ratio; | |
564 | extern int dirty_writeback_centisecs; | |
565 | extern int dirty_expire_centisecs; | |
566 | +extern int block_dump; | |
567 | +extern int laptop_mode; | |
568 | ||
569 | struct ctl_table; | |
570 | struct file; | |
571 | diff -Nbaur linux-2.6.1/kernel/sysctl.c linux-2.6.1-withlaptopmode/kernel/sysctl.c | |
572 | --- linux-2.6.1/kernel/sysctl.c 2003-12-24 05:19:46.000000000 +0100 | |
573 | +++ linux-2.6.1-withlaptopmode/kernel/sysctl.c 2003-12-24 06:24:53.000000000 +0100 | |
574 | @@ -700,6 +700,26 @@ | |
575 | .strategy = &sysctl_intvec, | |
576 | .extra1 = &zero, | |
577 | }, | |
578 | + { | |
579 | + .ctl_name = VM_LAPTOP_MODE, | |
580 | + .procname = "laptop_mode", | |
581 | + .data = &laptop_mode, | |
582 | + .maxlen = sizeof(laptop_mode), | |
583 | + .mode = 0644, | |
584 | + .proc_handler = &proc_dointvec, | |
585 | + .strategy = &sysctl_intvec, | |
586 | + .extra1 = &zero, | |
587 | + }, | |
588 | + { | |
589 | + .ctl_name = VM_BLOCK_DUMP, | |
590 | + .procname = "block_dump", | |
591 | + .data = &block_dump, | |
592 | + .maxlen = sizeof(block_dump), | |
593 | + .mode = 0644, | |
594 | + .proc_handler = &proc_dointvec, | |
595 | + .strategy = &sysctl_intvec, | |
596 | + .extra1 = &zero, | |
597 | + }, | |
598 | { .ctl_name = 0 } | |
599 | }; | |
600 | ||
601 | diff -Nbaur linux-2.6.1/mm/page-writeback.c linux-2.6.1-withlaptopmode/mm/page-writeback.c | |
602 | --- linux-2.6.1/mm/page-writeback.c 2003-12-24 05:19:46.000000000 +0100 | |
603 | +++ linux-2.6.1-withlaptopmode/mm/page-writeback.c 2004-01-04 17:50:03.000000000 +0100 | |
604 | @@ -28,6 +28,7 @@ | |
605 | #include <linux/smp.h> | |
606 | #include <linux/sysctl.h> | |
607 | #include <linux/cpu.h> | |
608 | +#include <linux/quotaops.h> | |
609 | ||
610 | /* | |
611 | * The maximum number of pages to writeout in a single bdflush/kupdate | |
612 | @@ -81,6 +82,16 @@ | |
613 | */ | |
614 | int dirty_expire_centisecs = 30 * 100; | |
615 | ||
616 | +/* | |
617 | + * Flag that makes the machine dump writes/reads and block dirtyings. | |
618 | + */ | |
619 | +int block_dump; | |
620 | + | |
621 | +/* | |
622 | + * Flag that puts the machine in "laptop mode". | |
623 | + */ | |
624 | +int laptop_mode; | |
625 | + | |
626 | /* End of sysctl-exported parameters */ | |
627 | ||
628 | ||
629 | @@ -195,7 +206,18 @@ | |
630 | if (nr_reclaimable + ps.nr_writeback <= dirty_thresh) | |
631 | dirty_exceeded = 0; | |
632 | ||
633 | - if (!writeback_in_progress(bdi) && nr_reclaimable > background_thresh) | |
634 | + if (unlikely(laptop_mode) && pages_written > 0) { | |
635 | + /* | |
636 | + * Schedule full writeout to happen soon. We don't postpone | |
637 | + * previously scheduled full writeouts, otherwise a writing | |
638 | + * process throttled by balance_dirty_pages will be able to | |
639 | + * postpone the full writeout indefinitely, keeping the disk | |
640 | + * spun up as a result. | |
641 | + */ | |
642 | + disk_is_spun_up(0); | |
643 | + } | |
644 | + | |
645 | + if (!unlikely(laptop_mode) && !writeback_in_progress(bdi) && nr_reclaimable > background_thresh) | |
646 | pdflush_operation(background_writeout, 0); | |
647 | } | |
648 | ||
649 | @@ -327,6 +349,8 @@ | |
650 | oldest_jif = jiffies - (dirty_expire_centisecs * HZ) / 100; | |
651 | start_jif = jiffies; | |
652 | next_jif = start_jif + (dirty_writeback_centisecs * HZ) / 100; | |
653 | + if (laptop_mode) | |
654 | + wbc.older_than_this = NULL; | |
655 | nr_to_write = ps.nr_dirty + ps.nr_unstable + | |
656 | (inodes_stat.nr_inodes - inodes_stat.nr_unused); | |
657 | while (nr_to_write > 0) { | |
658 | @@ -343,6 +367,11 @@ | |
659 | } | |
660 | if (time_before(next_jif, jiffies + HZ)) | |
661 | next_jif = jiffies + HZ; | |
662 | + if (laptop_mode) { | |
663 | + sync_inodes(0); | |
664 | + sync_filesystems(0); | |
665 | + DQUOT_SYNC(NULL); | |
666 | + } | |
667 | if (dirty_writeback_centisecs) | |
668 | mod_timer(&wb_timer, next_jif); | |
669 | } | |
670 | @@ -363,6 +392,28 @@ | |
671 | return 0; | |
672 | } | |
673 | ||
674 | +static struct timer_list laptop_mode_wb_timer; | |
675 | + | |
676 | +static void laptop_mode_wb_timer_fn(unsigned long unused) | |
677 | +{ | |
678 | + mod_timer(&wb_timer, jiffies); | |
679 | +} | |
680 | + | |
681 | +/* | |
682 | + * We've spun up the disk and we're in laptop mode: schedule writeback | |
683 | + * of all dirty data in 5 seconds. | |
684 | + * | |
685 | + * Laptop mode writeback will be delayed if it has previously been | |
686 | + * scheduled to occur within 5 seconds. That way, the writeback will | |
687 | + * only be triggered if the system is truly quiet again. | |
688 | + */ | |
689 | +void disk_is_spun_up(int postpone_writeback) | |
690 | +{ | |
691 | + if (postpone_writeback || !timer_pending(&laptop_mode_wb_timer)) | |
692 | + mod_timer(&laptop_mode_wb_timer, jiffies + 5 * HZ); | |
693 | +} | |
694 | + | |
695 | + | |
696 | static void wb_timer_fn(unsigned long unused) | |
697 | { | |
698 | if (pdflush_operation(wb_kupdate, 0) < 0) | |
699 | @@ -434,6 +485,11 @@ | |
700 | wb_timer.data = 0; | |
701 | wb_timer.function = wb_timer_fn; | |
702 | add_timer(&wb_timer); | |
703 | + | |
704 | + init_timer(&laptop_mode_wb_timer); | |
705 | + laptop_mode_wb_timer.data = 0; | |
706 | + laptop_mode_wb_timer.function = laptop_mode_wb_timer_fn; | |
707 | + | |
708 | set_ratelimit(); | |
709 | register_cpu_notifier(&ratelimit_nb); | |
710 | } | |
711 | @@ -525,6 +581,8 @@ | |
712 | __mark_inode_dirty(mapping->host, | |
713 | I_DIRTY_PAGES); | |
714 | } | |
715 | + if (unlikely(block_dump)) | |
716 | + printk("%s(%d): dirtied page\n", current->comm, current->pid); | |
717 | } | |
718 | return ret; | |
719 | } |