Pull to refresh

Comments 11

Хорошая статья. Хотя фраза
Ещё существуют brk/sbrk, deprecated древние пережитки прошлого, которые изменяют размер одного-единственного интервала «данных» и в современных системах эмулируются mmap’ом.
заставила чесать репу очень сильно. В самой распространённой на сегодня операционке brk, разумеется, реализован отдельно, так что не очень понятно о каких «современных» системах идёт речь. О тех, которые, по мнению Танненбаума, «победили» и которые де-факто мало кто использует, что ли?

P.S. То, что mmap и brk работают с одними и теми же структурами данных, в общем-то, очевидно (иначе они бы «наступали на пятки друг другу»), но это всё-таки разные функции. И, кстати, Linux'овый загрузчик таки память с помощью brk и выделает поначалу (совсем «не работающий» bdk не даст вам запустить ничего… если не ошибаюсь то один вызов должен отработать обязательно, а вот уже потом — можно возращать ошибку и ld.so переключится на mmap).
Спасибо!

Я имел в виду (цитата из мана Linux) Avoid using brk() and sbrk(): the malloc(3) memory allocation package is the portable and comfortable way of allocating memory. И еще (цитата из мана Darwin) The brk and sbrk functions are historical curiosities left over from earlier days before the advent of virtual memory management. На маке вдобавок sbrk еще и кастрированый, выделяет не больше 4 Mb за раз.
Ну и всякие вот такие комментарии. Коллеги подсказывают что glibc интенсивно пользуется sbrk. Ситуация двоякая выходит — с одной стороны советуют не пользоваться, с другой — много реализаций malloc-а используют.
Да, у glibc malloc построен на brk и дальше — fallback на mmap. А пользоваться не рекомендуют потому, что там есть граница (не помню точно какая… 900MB, кажется), после которой (s)brk «вдруг» перестаёт работать. Если вы используете malloc, то он «прозрачно перейдёт на mmap», а если brk напрямую — то у вас на машинке с парой гогив памяти память вдруг «кончится» до того, как вы дойдёте до половины…
sbrk не рекомендуют пользоваться, потому что у sbrk может быть только один пользователь. Посмотрите на интерфейс — void *sbrk(intptr_t inc), т.е. вы должны знать текущий конец сегмента sbrk, который после аллокации станет началом выделнного куска памяти. Если вы и malloc из libc станут оба звать sbrk, не обновляя значение конца сегмента друг другу, затрется чужая память.

Эта же проблема влияет и на многопоточный malloc(), если две нитки одновременно захотели получить куски памяти от системы, то они должны сериализоваться, чтобы не затереть значение конца сегмента.

Поэтому новые реализации malloc предпочитают mmap, например jemalloc. Единственная видимая проблема от использования mmap — не работающий RLIMIT_DATA.
Всё правда, за исключением одной проблемы: интерфейс у sbrk вполне себе «правильный», такой же, как у malloc'а и mmap'а, в сущности.

Все описанные вами проблемы наблюдаются только с brk() — но да, в силу отсутствия «настоящего» sbrk(2) в Linux пользоваться этим интерфейсом нельзя. В Solaris, если не ошибаюсь — можно.

P.S. Особенно шикарна реализация sbrk в musl'е: 100% безопасна, 100% совместима со всеми стандартами. Пользуйся — не хочу.

Нет, проблема общая и для brk() и для sbrk(): нужно знать текущий _end, согласованный с результатом вызова. brk() принимает новый _end как аргумент, sbrk() — инкремент, но проблему то это не решает. Посмотрите, что будет, если другая нитка вклинится между чтением _end и вызовом. Проблема интерфейса, а не реализации, разве что musl ее в самом деле решил окончательно.

Современные GC аллокаторы, кстати, устроены похоже, но у них такая область своя для каждой нитки.
sbrk() — инкремент, но проблему то это не решает.
Как «не решает»? Решает, конечно. Задёте размер, получаете адрес, где можно разместить объект указанного размера. Всё в точности как в malloc или mmap. Никакой разницы. То есть вообще никакой.

А вот уже реализация sbrk в библиотеке — тут проблемы. Было бы то же самое сделано в ядре под ядрёным локом — не было бы никаких проблем.

P.S. А про musl — вы бы сходили по ссылке, что ли. Там всё ведь именно как написано: 100% совместимо, 100% безопасно, пользуйся — не хочу. Причём последняя часть — тоже неотъемлемая часть описания этого решения :-)
Эта граница — MMAP_THRESHOLD, ее можно менять через mallopt(). Ничего внезапно работать не перестает, (s)brk — простой механизм, который просто сдвигает границу, освободить дырку в середине он не может. Потому до определенной границы им пользоваться хорошо потому что он проще и быстрее, а после нельзя по соображениям экономии памяти.
MMAP_THRESHOLD — это совсем о другом. Это о том — выделять память через механизм «для мелких объектов» или напрямую через mmap.

А про «внезапность» — это с точки зрения «наивного» пользователя, который просто запускает программу на машинке с парой гигов памяти и огорчается, что всю память ему не дают. Почему не дают — понятно, но это только если подумать про всякие разделяемые библиотеки, ASLR и прочее. Но если вы — PHP-программист и все эти слова — «где-то слышали, но как работет — не знаю», то это действительно может оказаться для вас неожиданностью.
UFO just landed and posted this here
Sign up to leave a comment.