--- 2.5.20/mm/page-writeback.c~laptop-mode Tue Jun 4 15:27:54 2002 +++ 2.5.20-akpm/mm/page-writeback.c Tue Jun 4 15:27:54 2002 @@ -76,6 +76,21 @@ int dirty_writeback_centisecs = 5 * 100; */ int dirty_expire_centisecs = 30 * 100; +/* + * A global sysctl-controlled flag which puts the machine into "laptop mode" + */ +int laptop_mode; + +/* + * When in laptop mode, this sysctl sets the interval between global flushes, + * in centiseconds. + */ +int laptop_writeback_centisecs = 5 * 60 * 100; + +/* + * A flag which is set when the disk is spun up. + */ +static int disk_activity_seen; static void background_writeout(unsigned long _min_pages); @@ -157,6 +172,8 @@ void balance_dirty_pages_ratelimited(str /* * writeback at least _min_pages, and keep writing until the amount of dirty * memory is less than the background threshold, or until we're all clean. + * + * In laptop mode, just write all dirty data. */ static void background_writeout(unsigned long _min_pages) { @@ -169,7 +186,8 @@ static void background_writeout(unsigned struct page_state ps; get_page_state(&ps); - if (ps.nr_dirty < background_thresh && min_pages <= 0) + if (!laptop_mode && ps.nr_dirty < background_thresh && + min_pages <= 0) break; nr_to_write = MAX_WRITEBACK_PAGES; writeback_unlocked_inodes(&nr_to_write, WB_SYNC_NONE, NULL); @@ -205,8 +223,10 @@ static struct timer_list wb_timer; * * older_than_this takes precedence over nr_to_write. So we'll only write back * all dirty pages if they are all attached to "old" mappings. + * + * When operating in laptop mode, writeback all dirty data. */ -static void wb_kupdate(unsigned long arg) +static unsigned long wb_kupdate(void) { unsigned long oldest_jif; unsigned long start_jif; @@ -216,14 +236,66 @@ static void wb_kupdate(unsigned long arg sync_supers(); get_page_state(&ps); - + nr_to_write = ps.nr_dirty; oldest_jif = jiffies - (dirty_expire_centisecs * HZ) / 100; start_jif = jiffies; next_jif = start_jif + (dirty_writeback_centisecs * HZ) / 100; - nr_to_write = ps.nr_dirty; - writeback_unlocked_inodes(&nr_to_write, WB_SYNC_NONE, &oldest_jif); + + if (laptop_mode) { + nr_to_write *= 2; + writeback_unlocked_inodes(&nr_to_write, WB_SYNC_NONE, NULL); + } else { + writeback_unlocked_inodes(&nr_to_write, + WB_SYNC_NONE, &oldest_jif); + } blk_run_queues(); yield(); + return next_jif; +} + +/* + * Insert comment here + */ +static unsigned long laptop_kupdate(void) +{ + static enum { + idle, /* Waiting for disk activity */ + wait_for_inactivity, /* Waiting for I/O to stop */ + } state = idle; + static unsigned long last_flush_jifs; + unsigned long interval = (laptop_writeback_centisecs * HZ) / 100; + unsigned long ret = jiffies + (dirty_writeback_centisecs * HZ) / 100; + + if (time_after(last_flush_jifs, jiffies)) + last_flush_jifs = jiffies; /* sanify the start-up state */ + + if (time_after(jiffies, last_flush_jifs + interval)) + disk_activity_seen = 1; /* force writeback */ + + switch (state) { + case idle: + if (disk_activity_seen) { + ret = wb_kupdate(); + last_flush_jifs = jiffies; + state = wait_for_inactivity; + } + break; + case wait_for_inactivity: + disk_activity_seen = 0; + state = idle; + } + return ret; +} + +static void kupdate(unsigned long unused) +{ + unsigned long next_jif; + unsigned long (*fn)(void); + + fn = wb_kupdate; + if (laptop_mode) + fn = laptop_kupdate; + next_jif = (*fn)(); if (time_before(next_jif, jiffies + HZ)) next_jif = jiffies + HZ; @@ -232,7 +304,7 @@ static void wb_kupdate(unsigned long arg static void wb_timer_fn(unsigned long unused) { - if (pdflush_operation(wb_kupdate, 0) < 0) + if (pdflush_operation(kupdate, 0) < 0) mod_timer(&wb_timer, jiffies + HZ); } @@ -246,6 +318,23 @@ static int __init wb_timer_init(void) return 0; } module_init(wb_timer_init); + +/* + * Device drivers call in here to indicate that the disk has spun up. + */ +void disk_spun_up(void) +{ + if (laptop_mode && !disk_activity_seen) + disk_activity_seen = 1; +} +EXPORT_SYMBOL(disk_spun_up); + +/* + * Journalling filesystems which perform their own writeback scheduling + * need these. + */ +EXPORT_SYMBOL(laptop_mode); +EXPORT_SYMBOL(laptop_writeback_centisecs); /* * A library function, which implements the vm_writeback a_op. It's fairly --- 2.5.20/include/linux/sysctl.h~laptop-mode Tue Jun 4 15:27:54 2002 +++ 2.5.20-akpm/include/linux/sysctl.h Tue Jun 4 15:27:54 2002 @@ -145,6 +145,8 @@ enum VM_DIRTY_SYNC=13, /* dirty_sync_ratio */ VM_DIRTY_WB_CS=14, /* dirty_writeback_centisecs */ VM_DIRTY_EXPIRE_CS=15, /* dirty_expire_centisecs */ + VM_LAPTOP_MODE=16, /* Enter "laptop" writeback mode */ + VM_LAPTOP_WB_CS=17, /* Periodic flushtime when in laptop mode */ }; --- 2.5.20/kernel/sysctl.c~laptop-mode Tue Jun 4 15:27:54 2002 +++ 2.5.20-akpm/kernel/sysctl.c Tue Jun 4 15:27:54 2002 @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -278,6 +279,11 @@ static ctl_table vm_table[] = { {VM_DIRTY_EXPIRE_CS, "dirty_expire_centisecs", &dirty_expire_centisecs, sizeof(dirty_expire_centisecs), 0644, NULL, &proc_dointvec}, + {VM_LAPTOP_MODE, "laptop_mode", &laptop_mode, sizeof(laptop_mode), + 0644, NULL, &proc_dointvec}, + {VM_LAPTOP_WB_CS, "laptop_writeback_centisecs", + &laptop_writeback_centisecs, sizeof(laptop_writeback_centisecs), + 0644, NULL, &proc_dointvec}, {0} }; --- 2.5.20/drivers/ide/ide.c~laptop-mode Tue Jun 4 15:27:54 2002 +++ 2.5.20-akpm/drivers/ide/ide.c Tue Jun 4 15:27:54 2002 @@ -1043,6 +1043,7 @@ static void do_request(struct ata_channe void do_ide_request(request_queue_t *q) { + disk_spun_up(); do_request(q->queuedata); } --- 2.5.20/fs/jbd/transaction.c~laptop-mode Tue Jun 4 15:27:54 2002 +++ 2.5.20-akpm/fs/jbd/transaction.c Tue Jun 4 15:27:54 2002 @@ -45,7 +45,8 @@ extern spinlock_t journal_datalist_lock; static transaction_t * get_transaction (journal_t * journal, int is_try) { - transaction_t * transaction; + transaction_t *transaction; + unsigned long expires; transaction = jbd_kmalloc (sizeof (transaction_t), GFP_NOFS); if (!transaction) @@ -56,14 +57,25 @@ static transaction_t * get_transaction ( transaction->t_journal = journal; transaction->t_state = T_RUNNING; transaction->t_tid = journal->j_transaction_sequence++; - transaction->t_expires = jiffies + journal->j_commit_interval; - /* Set up the commit timer for the new transaction. */ - J_ASSERT (!journal->j_commit_timer_active); + /* + * Set up the commit timer for the new transaction. In laptop mode + * we expect commits to be forced by core kernel kupdate activity, so + * just set the transaction to expire five seconds after that in case + * something changes or goes wrong there. + */ + expires = jiffies; + if (laptop_mode) + expires += 5 * HZ + (laptop_writeback_centisecs * HZ) / 100; + else + expires += journal->j_commit_interval; + + transaction->t_expires = expires; + J_ASSERT(!journal->j_commit_timer_active); journal->j_commit_timer_active = 1; journal->j_commit_timer->expires = transaction->t_expires; add_timer(journal->j_commit_timer); - + J_ASSERT (journal->j_running_transaction == NULL); journal->j_running_transaction = transaction; --- 2.5.20/include/linux/fs.h~laptop-mode Tue Jun 4 15:27:54 2002 +++ 2.5.20-akpm/include/linux/fs.h Tue Jun 4 15:27:54 2002 @@ -1291,5 +1291,9 @@ static inline ino_t parent_ino(struct de return res; } +extern void disk_spun_up(void); +extern int laptop_mode; +extern int laptop_writeback_centisecs; + #endif /* __KERNEL__ */ #endif /* _LINUX_FS_H */ --- 2.5.20/Documentation/filesystems/proc.txt~laptop-mode Tue Jun 4 15:28:32 2002 +++ 2.5.20-akpm/Documentation/filesystems/proc.txt Tue Jun 4 15:35:04 2002 @@ -985,6 +985,38 @@ for writeout by the pdflush daemons. It Data which has been dirty in-memory for longer than this interval will be written out next time a pdflush daemon wakes up. +laptop_mode +----------- + +Setting this entry to '1' will put the kernel's dirty data writeout +algorithms into a mode which is better suited to laptop/notebook +computers. This mode is specifically designed to minimise the +frequency of disk spinups. Laptop mode works as follows: + +- Dirty data remains in memory for longer periods of time (controlled + by laptop_writeback_centisecs). + +- If there is pending dirty data and the disk is spun up for any + reason (even for a read) then all dirty data will be written back + shortly afterwards. ie: when the disk is spun up, make good use of + it. + +- When the decision is made to write back some dirty data, the kernel + will write back all dirty data. + +laptop_writeback_centisecs +-------------------------- + +This tunable determines the maximum age of dirty data when the machine +is operating in Laptop mode. The default value is 30000 - five +minutes. This means that if applications are generating a small amount +of write traffic, the disk will spin up once per five minutes. + +If the disk is spun up for any other reason (such as for a read) then +all dirty data will be flushed anyway, and this timer is reset to zero. + +laptop_writeback_centisecs has no effect when the machine is not +operating in Laptop mode. kswapd ------