LINUX.ORG.RU

Ошибка в malloc/free?

 


0

2

в одной программке обнаружилась ошибка при работе с памятью.

ошибка проявляется только если суммарный размер блоков > 4G если меньше тест проходит миллиард итераций без проблем.

количество итераций до появления ошибки не постоянно.

ниже минимальный код воспроизводящий ошибку, учет выделенной памяти тут не ведется.

Причем ошибка проявляется даже если выделенные блоки вообще не используются.

У кого имеется физической памяти 8 и более Gb проверьте плз, воспроизводится ли ошибка у вас. Но будьте осторожны, у меня один раз система ушла в полный ступор, пришлось перегружать (в той версии теста я заполнял выделенные блоки данными).

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#define COUB (16*1000*1000)

typedef void *ptr;

ptr	arb[COUB];

void main() {
  int cn,na,szn;
  ptr pb;

    // init:
    for (cn=0; cn < COUB; cn++) arb[cn]=NULL;

    // test:
    for (cn=0; cn < (1000*1000*1000); cn++) {

	if ((cn & 0xFFFF)==0) printf("cn=%d \n",cn);

	na =(rand())% COUB;
	pb=arb[na];
	if (pb != NULL) {
	    free(pb);
	    arb[na]=NULL;
	} else {
	    szn =(rand()) % 2048;
	    if (szn < 32) szn=32;
	    pb=malloc(szn);
	    if (pb==NULL) {
             printf(" !!! malloc()==NULL  szn=%u   (cn=%u)  \n",szn,cn); exit(0); };
	    arb[na]=pb;
	};
    };

};



Последнее исправление: Ushenin (всего исправлений: 1)

Ответ на: комментарий от Ushenin

А памяти точно хватает? По моим подсчетам математическое ожидание размера требуемой памяти составляет 15 Гб. Если памяти не хватает, то факт того что malloc возвращает 0 ошибкой не будет.

Да и вообще правильно ли я понимаю, что ошибка проявляется в том, что malloc 0 возвращает?

DesertFox
()
Ответ на: комментарий от DesertFox

Хотя странно.... Если выделенную память не заполнять, то физическая память не должна выделяться. То есть потребление памяти не должно быть таким большим.

DesertFox
()
Ответ на: комментарий от DesertFox

я контролирую количество свободной памяти в процессе работы теста, свободной памяти более чем достаточно.

при выделении маленьких блоков у каждого из них создается заголовок, то есть физическая память выделяется.

Ushenin
() автор топика

Кстати, в зависимости от значения vm.overcommit_memory система может вообще не выделить памяти больше, чем есть физической, даже если её не заполнять.

AITap ★★★★★
()
Ответ на: комментарий от DesertFox

я же написал что убрал все лишнее,

при желании можно добавить memset или в цикле рандомно заполнять.

суть в том что ошибка проявляется даже тогда когда память не используется.

Ushenin
() автор топика

Manhunt, почему тупняк?

#include <stdio.h>

int main()
{
        printf("size of int %d\n", sizeof(int));
        printf("size of 16000000 %d\n", sizeof(16000000));
        printf("size of 16000000L %d\n", sizeof(16000000L));
}
$ ./a.out 
size of int 4
size of 16000000 4
size of 16000000L 8
$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/4.7/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Debian 4.7.1-7' --with-bugurl=file:///usr/share/doc/gcc-4.7/README.Bugs --enable-languages=c,c++,go,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.7 --enable-shared --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.7 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --enable-plugin --enable-objc-gc --with-arch-32=i586 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.7.1 (Debian 4.7.1-7)

Т.е. это тоже, конечно, ошибка, но просто не связанная с выделением памяти.

DesertFox
()
Ответ на: комментарий от DesertFox

ошибка проявляется по разному, иногда просто segfault, но чаще сообщает что была попытка повторного free или испорчен next/prev

т.е. похоже портится служебная информация в заголовке блока.

Ushenin
() автор топика
Ответ на: комментарий от DesertFox

прежде чем соваться в исходники системных библиотек нужно убедится в том что ошибка воспроизводится на разных системах.

для меня это в настоящее время не имеет практического значения, но если в системной библиотеке действительно есть ошибка то нужно сообщить разработчикам.

Ushenin
() автор топика
Ответ на: комментарий от Ushenin

Просто интересно узнать в какой момент в программе происходит сегфолт.

DesertFox
()
Ответ на: комментарий от AITap

не выделить памяти больше, чем есть физической

в смысле RAM+swap?

Да, с учётом параметра vm.overcommit_ratio

Ого, это значит, что теперь имея, например 4GB ram+swap может быть:

p1 = malloc(4GB);
if (!p1) {
   printf("Sorry, no more memory!\n");
   exit(1);
}
p2 = malloc(20MB);
if (!p2) {
   printf("Sorry, no more memory!\n");
   exit(1);
}
// we are here thanks to overcommit :)
// but somewhere later we may get OOM-kill...
Т.е., например, присвоив значение p2[56] = 123; мы можем уложить прогу? Вместо корректного сообщение о том, что памяти-то больше нет?

Хорошо, а можно/как теперь в С ловить это?

gag ★★★★★
()
Последнее исправление: gag (всего исправлений: 1)
Ответ на: комментарий от DesertFox

В случае OVERCOMMIT_NEVER всё отлавливается ещё на этапе malloc() (хотя можно попытаться malloc()нуть ровно столько памяти, сколько доступно, чтобы следующий malloc() вызвал OOM). В случае нехватки памяти, когда выделять память становится неоткуда, просыпается OOM-killer.

AITap ★★★★★
()
Ответ на: комментарий от gag

Нет, сначала придётся заполнить выделенные 4 гигабайта какими-нибудь данными. После этого придёт OOM-killer и расставит всё по своим местам.

AITap ★★★★★
()
Ответ на: комментарий от gag

проблема проявляется когда создается и удаляется много мелких блоков.

ваш пример должен работать без проблем.

Ushenin
() автор топика
Ответ на: комментарий от AITap

Да, я имел ввиду, что вся память, на которую malloc выдал ОК, заполнена. Т.е. я в своей проге из-за настроек ОС не имею возможности полагаться на malloc? До чего дошёл прогресс.

gag ★★★★★
()
Ответ на: комментарий от Ushenin

В примере я неявно имел ввиду, что всю память, на которую мне malloc выдал ОК, «я выделил», я, конечно, заполнил.

gag ★★★★★
()

Не воспроизводится

Конфигурация:
64-битный core i5
8 Гб оперативки
2 Гб свопа
glibc 2.15
Linux kernel 3.4.11

Запуск теста:
gcc -O0 c.c
strace ./a.out

Результаты:
1. При #define COUB (8*1000*1000) миллиард итераций проходится быстро и без проблем (проверил несколько раз)
2. При #define COUB (16*1000*1000) машина уходит в своп, тест работает очень медленно. Ждал больше часа, терпения не хватило, выключил с помощью Ctrl-C.
3. При #define COUB (32*1000*1000) машина также уходит в своп, но минут через 50 вся доступная память исчерпывается, и получаем:

brk(0x2430a8000)                        = 0x2430a8000
brk(0x2430c9000)                        = 0x2430c9000
brk(0x2430ea000)                        = 0x2430ea000
brk(0x24310b000)                        = 0x24310b000
+++ killed by SIGKILL +++
Убито
$ dmesg | tail -n 4
[554284.619967] [17952]  1000 17952     4100      390   1       0             0 bash
[554284.619971] [18193]   108 18193     8969      238   2       0             0 postgres
[554284.619974] Out of memory: Kill process 17925 (a.out) score 921 or sacrifice child
[554284.619977] Killed process 17925 (a.out) total-vm:9474644kB, anon-rss:7585008kB, file-rss:36kB
4. Если ограничить память с помощью bash-евского «ulimit -v», то тест стабильно завершается сообщением о том, что malloc вернул NULL

Manhunt ★★★★★
()
Последнее исправление: Manhunt (всего исправлений: 1)
Ответ на: Не воспроизводится от Manhunt

Оставлю до завтра крутиться с #define COUB (12*1000*1000), что на практике соответствует потреблению памяти (показания утилиты top) VIRT=6078m и RES=5,9g. Понятно, что теоретический пик потребления памяти в такой конфигурации приведет к срабатыванию OOM killer, но я очень сомневаюсь что на деле такой пик будет достигнут.

Manhunt ★★★★★
()

16Гб, glibc 2.16, linux 3.5.5 - не воспроизвелось

wota ★★
()
Ответ на: комментарий от gag

Если память заполнена, то из-за работы с ней ничего не случится. Но OOM-killer убивает самых «жирных», а не тех, кому понадобилась память.

mky ★★★★★
()
Ответ на: Не воспроизводится от Manhunt

3. При #define COUB (32*1000*1000) машина также уходит в своп, но минут через 50 вся доступная память исчерпывается,

Думаю, что ради эксперимента можно было бы уменьшить своп с 2 ГБ до, скажем, 100 МБ. Тогда не будет уходить такая уйма времени, а?

gag ★★★★★
()
Ответ на: комментарий от mky

Если в проге malloc() вернул не NULL, а OOM-killer взял и прибил прогу, тут что-то не то. Откуда мне знать, что ОС оптимистично ему это позволила (в надежде, что нужен просто непрерывный кусок памяти, но он никогда не будет полностью использоваться), а когда дело дошло до занять эту память почти всю, вспомнила, что а её-то памяти и нет.

Хм, фокус с overcommit/OOM-killer прям как долговой кризис в ЕС.

gag ★★★★★
()
Ответ на: Не воспроизводится от Manhunt

На 8Gb нужно поставить COUB (10*1024*1024), если использовать 8 то общий размер блоков меньше 4Gb, а если 16 то суммарно будет около 8Gb т.е. память переполниться, вчера я не сообразил (у меня 16Gb)

У меня ошибка проявляется на ядрах 2.6.32 и 3.2 но там и там glib 2.11

Ushenin
() автор топика
Ответ на: комментарий от gag

своп вообще не нужно использовать, ошибка должна проявляться в RAM

Ushenin
() автор топика
Ответ на: комментарий от gag

overcommit был придуман до появления Linux'а.

Если в проге malloc() вернул не NULL, а OOM-killer взял и прибил прогу, тут что-то не то.

Я имел в виду, что фактически любая программа в любой момент времени может быть без всякого предупреждения убита OOM-killer'ом, ведь помимо malloc() ещё много из-за чего системе может поребоватся память.

а когда дело дошло до занять эту память почти всю, вспомнила, что а её-то памяти и нет.

Тут всё сложнее. Допустим есть fork() и есть copy-on-write. Это ведь тоже overcommit. Процесс форкается и, по хорошему, система должна выделить ему объём памяти, равный объёму сегмента данных. Код у родителя и потомка общий, а данные каждый может изменить по своему. А может не изменять. И если запретить overcommit, то «жирный» процесс, занявший 51% памяти не сможет сделать fork(), хотя может его потомок должен сделать совсем немного действий и сделать exec().

mky ★★★★★
()
Ответ на: комментарий от Manhunt

Оставлю до завтра крутиться с #define COUB (12*1000*1000)

За прошедшие 12 часов так и не упало. Выключаю с помощью Ctrl-C. Показания top перед остановом: VIRT=6063m, RES=5,9g.

Manhunt ★★★★★
()
Ответ на: комментарий от Ushenin

У меня ошибка проявляется на ядрах 2.6.32 и 3.2 но там и там glib 2.11

А процессор какой? Попробуй загрузиться с livecd http://software.opensuse.org/122/ru , будет такая же система как у меня (ядро и glibc). Если и на ней проблема воспроизведется (для чистоты эксперимента можно попробовать 12*1024*1024), то стоит как следует проверить аппаратную часть.

Manhunt ★★★★★
()
Ответ на: комментарий от Manhunt

такая же система как у меня

Ну, или почти такая же... у меня сверху еще апдейты установлены, и ядро совершенно точно апдейтилось с момента выпуска дистрибутива.

Manhunt ★★★★★
()
Последнее исправление: Manhunt (всего исправлений: 1)
Ответ на: комментарий от Manhunt

Процессор i7 2600

ошибка проявляется, при 10 и 12 млн. блоков.

при 8 млн. работает без ошибок, но только если запускать на свежей системе (сразу после перезагрузки). Если 8 запускать после 10 или 12 то тест вываливается по segfault.

Пока основное подозрение на glib 2.11, но если в свежих версиях это пофиксили то разработчиков беспокоить не надо.

Менять систему пока не могу, может быть на выходных поэкспериментирую.

Ushenin
() автор топика
Ответ на: комментарий от Ushenin

при 8 млн. работает без ошибок, но только если запускать на свежей системе
Пока основное подозрение на glib 2.11

Не ясно, как glibc в своих malloc-ах могла бы отличать свежую систему от несвежей.

Manhunt ★★★★★
()
Ответ на: комментарий от Manhunt

Возможно возникает какая-то фрагментация в системном менеджере памяти.

Но это гадание на кофейной гуще, нужна статистика.

Кстати если запустить пару таких процессов то они влияют друг на друга (второй почти сразу вылетает).

Ushenin
() автор топика
Ответ на: комментарий от Ushenin

Кстати если запустить пару таких процессов то они влияют друг на друга (второй почти сразу вылетает).

Так запускай второй под gdb. Интересно же посмотреть, на чём именно он вылетает.

i-rinat ★★★★★
()
Ответ на: комментарий от i-rinat

Я кору смотрел, вылетает внутри библиотечных вызовов, причем это происходит в разное время после запуска теста.

Ковырять глубже пока нет смысла, нужно подтверждение ошибки на других системах.

Ushenin
() автор топика
Ответ на: комментарий от i-rinat

почему неизбежное?

Если выяснится что ошибка проявляется только на glib 2.11 то я просто обновлюсь.

А если ошибка ни у кого кроме меня не проявится буду разбираться с железом.

Ushenin
() автор топика
Ответ на: комментарий от anonymous

В перемещающих сборщиках мусора ошибок больше в несколько раз просто потому что там кода больше гораздо больше. :)

Но все это не имеет отношения к данной теме.

Ushenin
() автор топика

На всякий случай поставь memtester и проверь от рута:

# memtester 4095
(число 4095 замени на чуть меньше чем объем доступной в данный момент оперативной памяти в мегабайтах, смотреть его командой `free -m` в строке "-/+ buffers/cache")

Вообще, проблема довольно легко локализуется так:

  • вытаскиваешь себе LiveCD своего дистрибутива, на котором есть memtest86+ и проверяешь память
  • если ошибок памяти нет, то прямо на livecd cобираешь свой бинарник и проверяешь
  • если на livecd бинарник тоже не работает — проблема в дистрибутиве или железе, проверяй на livecd другого дистрибутива
  • если с livecd бинарник работает, то собираешь его статически (gcc -static ...) и проверяешь статический
  • если статический бинарник тоже работает, сохраняешь и этот статический бинарник и динамический себе на винт, ребутишься в свою основную систему, и запускаешь эти же самые бинарники в ней
  • если в ней статический бинарник не работает — проблема в ядре
  • если статический работает, а динамический не работает — проблема в библиотеках
  • если оба сохраненных бинарника работают, а пересобранный локально не работает — проблема в компиляторе или девелореских библиотеках

Дальше можно аналогичным методом выяснить, в чем именно баг, проверить, исправлен ли он в новой версии пакета, и если не исправлен — отматерить пакет в багтрекере.

PS: у меня не воспроизводится

anonymous
()
Ответ на: комментарий от anonymous

Потратил 3 дня и кучу нервов, в итоге оказалось что один модуль памяти сбоит :(

Совсем недавно все тестировал, железо достаточно новое, проблем не было поэтому ни как не ожидал такой подлянки.

Ushenin
() автор топика
Ответ на: комментарий от Ushenin

Потратил 3 дня и кучу нервов, в итоге оказалось что один модуль памяти сбоит :(

Тогда сначала стоит вытащить/почистить/вставить планки, вдруг контакт в разъеме сбоит. Но даже если память сгорела — не страшно, память легко заменить. :) Хуже, когда память вылетает на роутере, там микросхему ж не выпаяешь, придется шаманить.

Кстати, про шаманство. Ядро умеет изолировать сбойные участки памяти. Если сбойных участков мало, можно дописать в параметры ядра memtest=3 (число — количество тестов, чем больше, тем больше тестов, то есть надежнее детект, но дольше проверка), и ядро само найдет поврежденную память. Вручную сбойные участки можно помечать параметром memmap=... Подробности — в kernel-parameters.txt.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.