December 26th, 2012

404

The Kernel

Бесконечный менеджмент сжирает всё время, но вот что успелось...

(trivia: война за "исчезающие мегабайты" в pv_ops ядрах под Xen'ом)

упринткашил все функции из nobootmem.c (JFYI: изменение в конфиге настроек вокруг нумы не влияет на ситуацию радикально).

Имеем (3.8.0-rc1+ #6 SMP Wed Dec 26 02:29:43 MSK 2012 x86_64 GNU/Linux), 256Мб.

Отладочный вывод:

free_all_bootmem: nobootmem.c MAX_NUMNODES=1
[ 0.000000] reset_node_lowmem_managed_pages: is_highmem(z)=0, z->managed_pages=3914
[ 0.000000] reset_node_lowmem_managed_pages: is_highmem(z)=0, z->managed_pages=60480
[ 0.000000] reset_node_lowmem_managed_pages: is_highmem(z)=0, z->managed_pages=0
[ 0.000000] reset_node_lowmem_managed_pages: is_highmem(z)=0, z->managed_pages=0

Считаем: (3914+60480)*4=257576к, мы же ожидаем увидеть 262144к, разница 4568к.
Заметим, free в машине показывает 223500к, то есть где-то ещё 30 мб пропало.

Дальше вывод:
[ 0.000000] free_low_memory_core_early: start=65536, end=630784, __free_memory_core(start, end)=138, count=138
[ 0.000000] free_low_memory_core_early: start=1048576, end=16777216, __free_memory_core(start, end)=3840, count=3978
[ 0.000000] free_low_memory_core_early: start=27643904, end=31371264, __free_memory_core(start, end)=910, count=4888
[ 0.000000] free_low_memory_core_early: start=31780864, end=31903744, __free_memory_core(start, end)=30, count=4918
[ 0.000000] free_low_memory_core_early: start=54112256, end=54636544, __free_memory_core(start, end)=128, count=5046
[ 0.000000] free_low_memory_core_early: start=54648832, end=54661120, __free_memory_core(start, end)=3, count=5049
[ 0.000000] free_low_memory_core_early: start=54775808, end=134217664, __free_memory_core(start, end)=19394, count=24443
[ 0.000000] free_low_memory_core_early: start=134217728, end=253755392, __free_memory_core(start, end)=29184, count=53627
[ 0.000000] free_low_memory_core_early: start=257949696, end=260026368, __free_memory_core(start, end)=507, count=54134
[ 0.000000] free_low_memory_core_early: start=260042752, end=264241152, __free_memory_core(start, end)=1025, count=55159
[ 0.000000] free_low_memory_core_early: start=264355840, end=267290816, __free_memory_core(start, end)=716, count=55875
[ 0.000000] free_low_memory_core_early: start=267684624, end=267684672, __free_memory_core(start, end)=0, count=55875
[ 0.000000] free_low_memory_core_early: start=267684680, end=267684736, __free_memory_core(start, end)=0, count=55875
[ 0.000000] free_low_memory_core_early: start=267684740, end=267684800, __free_memory_core(start, end)=0, count=55875
[ 0.000000] free_low_memory_core_early: start=267684808, end=267684864, __free_memory_core(start, end)=0, count=55875
[ 0.000000] free_low_memory_core_early: start=267684872, end=267684928, __free_memory_core(start, end)=0, count=55875
[ 0.000000] free_low_memory_core_early: start=267693232, end=267693248, __free_memory_core(start, end)=0, count=55875
[ 0.000000] free_low_memory_core_early: start=267693360, end=267693376, __free_memory_core(start, end)=0, count=55875
[ 0.000000] free_low_memory_core_early: start=267693408, end=267693440, __free_memory_core(start, end)=0, count=55875
[ 0.000000] free_low_memory_core_early: start=267693544, end=267693568, __free_memory_core(start, end)=0, count=55875
[ 0.000000] free_low_memory_core_early: start=267693672, end=267693696, __free_memory_core(start, end)=0, count=55875
[ 0.000000] free_low_memory_core_early: start=267693800, end=267693824, __free_memory_core(start, end)=0, count=55875
[ 0.000000] free_low_memory_core_early: start=267694048, end=267694080, __free_memory_core(start, end)=0, count=55875
[ 0.000000] totalram_pages = free_all_bootmem()=55875
[ 0.000000] Memory: 223500k/262144k available (3675k kernel code, 448k absent, 38196k reserved, 5084k data, 660k init)


(тут я обратил внимание на последнюю строчку. 262144k available - это то, значение, которое мы ставили.

Ок, нам тут дали подробную распиновку:

223500 доступно.
3675 - ядро.
448 - absent (насколько я понимаю, это тот злосчастный кусочек от 640k до мегабайта, в наследство от msdos).
38196 - зарезервировано.
5084 - data
660 - init

Сумма всего этого 271563, то есть больше, чем есть на самом деле. Разница между available и mem_kb - это 38644к, то есть unavailable + absent.

Отсюда вопрос (оставляем в стороне unavailable):
Что за reserved и какогохера?

Заметим, available строго соответствует дебаг выводу из free_low_memory_core_early

Вчитаемся в функцию. (она попаганена моим дебагом):

unsigned long __init free_low_memory_core_early(int nodeid)
{
    unsigned long count = 0;
    unsigned long t = 0;
    phys_addr_t start, end, size;
    u64 i;
    printk (KERN_ERR "free_low_memory_core_early:%i\n", nodeid);
    for_each_free_mem_range(i, MAX_NUMNODES, &start, &end, NULL){
        t=__free_memory_core(start, end);
        count +=t;
        printk(KERN_ERR "free_low_memory_core_early: start=%lu, end=%lu, __free_memory_core(start, end)=%lu, count=%lu\n", (long unsigned) start, (long unsigned)end,t,count);
    }

    /* free range that is used for reserved array if we allocate it */
    size = get_allocated_memblock_reserved_regions_info(&start);
    if (size)
        count += __free_memory_core(start, start + size);

    return count;
}


У меня есть ощущение, что get_allocated_memblock_reserved_regions_info(&start) возвращает ноль, и вот там-то у нас эти 38Мб и заныканы...

Ща соберу с ещё большим отладом.

upd: так и есть
[ 0.000000] free_low_memory_core_early, size=0

Тогда вопрос: а откуда ядро знает про reserved, если get_allocated_memblock_reserved_regions_info(&start) равно нулю?

Кстати, у меня есть некоторый вопрос про код...

unsigned long __init free_low_memory_core_early(int nodeid)
{
    unsigned long count = 0;
    phys_addr_t start, end, size;
    u64 i;
    printk (KERN_ERR "free_low_memory_core_early:%i\n", nodeid);
    for_each_free_mem_range(i, MAX_NUMNODES, &start, &end, NULL)
        count +=__free_memory_core(start, end);

    /* free range that is used for reserved array if we allocate it */
    size = get_allocated_memblock_reserved_regions_info(&start);
    if (size)
        count += __free_memory_core(start, start + size);
    return count;
}

Почему get_allocated_memblock_reserved_regions_info(&start) вызывается для последнего и только последнего start? Ведь for_each_free_mem_range в финале возвращает... Я не знаю что (пока что), но интуиция мне говорит, что тут что-то не так.

В любом случае, выясняем, как линукс считает reserved в выводе выше.

Греп нас приводит обратно в arch/x86/mm/init_64.c, в void __init mem_init(void).

reservedpages = max_pfn - totalram_pages - absent_pages;

facepalm. facepalm. facepalm. То есть оно считает, что reserved, это всё, что не absent и не totalram_pages.

Другими словами, рыть надо всё-таки именно в район посчёта totalram_pages. Я на 90% уверен, что проблема именно в длинном дампе free_low_memory_core_early (см выше).
404

The Kernel (part3)

Фокусируемся на

for_each_free_mem_range. Это макрос, include/linux/memblock.h

Наиболее ценным там является комментарий:

/**
* for_each_free_mem_range - iterate through free memblock areas
* @i: u64 used as loop variable
* @nid: node selector, %MAX_NUMNODES for all nodes
* @p_start: ptr to phys_addr_t for start address of the range, can be %NULL
* @p_end: ptr to phys_addr_t for end address of the range, can be %NULL
* @p_nid: ptr to int for nid of the range, can be %NULL
*
* Walks over free (memory && !reserved) areas of memblock. Available as
* soon as memblock is initialized.
*/

Оттуда же:

struct memblock {
phys_addr_t current_limit;
struct memblock_type memory;
struct memblock_type reserved;
}

Внезано, там же:


extern void __memblock_dump_all(void);

static inline void memblock_dump_all(void)
{
if (memblock_debug)
__memblock_dump_all();
}

Тут меня осеняет спросить гугль memblock debug linux и получить опцию командной строки memblock=debug. делаем.

Внезапно дебаг, миллионы его.

[ 0.000000] memblock_reserve: [0x00000001a50000-0x00000001a5d000] setup_arch+0x5e4/0xb80
[ 0.000000] MEMBLOCK configuration:
[ 0.000000] memory size = 0xff90000 reserved size = 0x202a000


там ещё много чего. я нашел куда читать, но уже хочу спать. продолжение днём.
404

The Kernel (part4)

Продолжаем. Про memblock ничего внятного нет, но API более-менее понятно. В частности, понятен смысл функции memblock_reserve, а его наличие несколько раз в arch/x86/xen/setup.c сильно обнадёживает.

Однако, когда я включил memblock_debug и посчитал весь вывод memblock_reserve, оказалось, что там всего 16Мб. Это сильно меньше искомых 38.

Следующая загадка:

memblock_reserve(__pa(xen_start_info->mfn_list),
xen_start_info->pt_base - xen_start_info->mfn_list);

xen_start_info->pt_base - xen_start_info->mfn_list - отрицательное число (-213061632)

Мистика...