counting free pages in mem_map_t

Gabor Kuti (seasons@falcon.sch.bme.hu)
Mon, 2 Nov 1998 09:57:56 +0100 (CET)


Sorry for the long letter :O

My project in suspending the machine is so far going well..
I'm [hopefully] at that point when kernel data is atomically duplicated so
I can use I/O to save it to disk.

here is the current source for kernel/suspend.c. My comments appearing
only in this letter are started by //.

My problems are:
a) Is there _etext variable for all architectures? [I think there is but
the question has raised because of the alpha boot code doesn't count for
code, data, reserved, init pages as i386 does, but in vmlinux.ldx there is
_etext, _text, etc..]. This variable is needed that I won't save kernel
code just data&bss&stack.. [look in count_and_copy_data_pages()]. I'm
working on i386.

b) What could be the problem when counting data pages? I got about 80
pages more free as counted compared to nr_free_pages. I'm not sure but a
memory region got by __get_free_pages() [order > 0], its count field is
only incremented for the first page of the area, so 'latter' pages has a
count==0 which I count as free? If so, how to solve it?
The output of count_and_copy_free_pages(NULL):
suspend (count_and_copy_data_pages, 83): nr_free_pages: 6812 freepages: 6891
suspend (count_and_copy_data_pages, 84): count_data_pages: code 212 data 166
reserved 102 udatapages 821

c) can the define ADDRESS be in linux/mm.h instead of page_alloc.c? I need it.

<The following section is theoretical right now not a question.. I think
this way it will work...>

The entry point is at machine_suspend(). It is activated by sysrq-q. It
queues this function to tq_afteridle. This tq if called from the scheduler
like this:
run_task_queue(&tq_scheduler);
if(prev == init_task)
run_task_queue(&tq_afterinit);

With this I make sure that no scheduled tq-s left without running.
I run machine_suspend() at a given state, so when I will call restore [the
same way] I can return from rtq(tq_afterinit) as it just returned from
suspend() [I read the pages and copy them back to their original
position]. I will have to save registers also with SAVE_ALL. Before
RESTORE_ALL [in function machine_restore()] I will have to modify the
stack pointer.. [SS:SI? I don't know :(] so the machine will continue its
work as before.
Of course I will have to call several device drivers to restore their
context.. [the screen is the only one I know of but will think of it
later..]
</section>

<program>
#include <linux/mm.h>
#include <linux/swap.h>
#include <linux/swapctl.h>
#include <linux/suspend.h>
#include <linux/smp_lock.h>

#define SUSPEND_DEBUG 1

#ifdef SUSPEND_DEBUG
#define suspend_debug(f, a...) { \
printk ("suspend (%s, %d): ", \
__FUNCTION__, __LINE__); \
printk (f, ## a); \
}
#else
#define suspend_debug(f, a...) /**/
#endif

#define ADDRESS(x) (PAGE_OFFSET + ((x) << PAGE_SHIFT))

extern unsigned long create_suspend_pd(int nr_copy_pages);
extern void mark_backup_pages_occupied(int nr_copy_pages);

/* References to section boundaries */
extern char _text, _etext, _edata, __bss_start, _end;

/*
* We try to keep some more pages free that I/O operations success
* without paging
*/

#define PAGES_FOR_IO 64

static const char *funct = "machine_suspend";
static const char *revision = "0.01";

DECLARE_TASK_QUEUE(tq_afteridle);

// This code is taken from arch/i386/mm/init.c as I remember just section
// init is cut

/* if p != NULL it also copies the counted pages */
static int count_and_copy_data_pages(struct ppe *p)
{
unsigned long tmp;
/* One variable called datapages should be enough */
int datapages = 0, reservedpages = 0, udatapages=0, freepages=0,codepages=0;

for(tmp = 0; tmp < num_physpages; tmp++) {
if(PageReserved(mem_map+tmp)) {
if (tmp >= MAP_NR((unsigned long) &_text) && tmp < MAP_NR((unsigned long) &_edata)) {
if (tmp < MAP_NR((unsigned long) &_etext)) {
codepages++;
continue; /* we don't need codepages */
} else
datapages++;

} else if (tmp >= MAP_NR((unsigned long) &__bss_start)
&& tmp < MAP_NR((unsigned long) _end))
datapages++;
else
reservedpages++;
} else if(atomic_read(&mem_map[tmp].count))
udatapages++;
else {
freepages++;
continue;
}
if(p) {
p->orig_address = ADDRESS(tmp);
copy_page(p->address, p->orig_address);
p++;
}
}

if(!p) {
suspend_debug("nr_free_pages: %d freepages: %d\n",nr_free_pages,freepages);
suspend_debug("count_data_pages: code %d data %d reserved %d udatapages %d\n",codepages,datapages,reservedpages,udatapages);
}

return datapages+reservedpages+udatapages;
}

void machine_suspend(void)
{
int nr_needed_pages, nr_copy_pages;
unsigned long pd;
struct ppe *pd_start;
/*
* First we want to make as much free pages as possible and duplicate
* all reserved pages since we want to save kernel state atomically..
*/

suspend_debug("before shrink_mem: %d\n",nr_free_pages);
// This func does repeatedly calls do_try_to_free_page() while it won't
// fail for many times, so nearly all processes are swapped out.

shrink_mem();
suspend_debug("after shrink_mem: %d\n",nr_free_pages);

/*
* Now we check that we have enough to save pages. (Later we might
* open here a proper sized swapfile to save the rest)
* If not we abort the operation
*/

/*
* We protect ourself so we can save kernel state atomically.
// This is a question.. I don't want _any_ other code to run until I
// duplicated data pages..
* Should it be before shrinking?
*/
lock_kernel();

nr_copy_pages = count_and_copy_data_pages(NULL);
// some extra pages so it won't try to swap later as I'm saving.
nr_needed_pages = nr_copy_pages + PAGES_FOR_IO+freepages.high;

suspend_debug("pages needed: %d+%d+%d=%d free: %d\n",nr_copy_pages,PAGES_FOR_IO,freepages.high,nr_needed_pages,nr_free_pages);
if(nr_free_pages < nr_needed_pages) {
printk(KERN_NOTICE "%s: Couldn't get enough free pages, on %d pages short\n",funct, nr_needed_pages-nr_free_pages);
goto out_no_pages;
}

/*
* Well this must be tricky. We need badly pages to make a copy from
* non-free pages but we should not change data structures.
*/
// This function just goes thru the free pages in free_area and builds a
// directory from them.
pd = create_suspend_pd(nr_copy_pages);
if(!pd) {
/* Shouldn't happen */
printk(KERN_ERR "Couldn't allocate enough pages\n");
goto out_no_pages;
}
pd_start = (struct ppe *)pd;
count_and_copy_data_pages(pd_start);

/*
* At this point state is saved. Now we mark all extra used pages
* used in free_area and then start flushing to disk
*/
mark_backup_pages_occupied(nr_copy_pages);
// here to come the saving part.

out_no_pages:
printk(KERN_NOTICE "%s: Continuing normal operation\n",funct);
unlock_kernel();
machine_suspend_enabled = 1;
return;
}
</program>

Sigh :)

Seasons
\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
"One who has time to complain has time to submit patches." <chinese proverb>
\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.rutgers.edu
Please read the FAQ at http://www.tux.org/lkml/