Проблема может возникнуть только с датаграммами. Ядро _полностью_ принимает датаграмму, и только после этого отдает ее целиком в юзер-спэйс. Если она в юзерский буфер не влезла, то невлезшая часть отрезается и теряется навсегда.
В случае поточного сокета -- действительно, никаких проблем:
int sock;
char buf[1];
int r, cnt = 0;
while ((r = recv(sock, buf, sizeof(buf), 0)) >= 0)
{
if (r) printf("получено %u байт\n", cnt += r);
}
udp.c:udp_ioctl().
я знал про FIONREAD, прежде чем писать ответ
решил убедиться, что udp socket его поддерживает,
обнаружил, что там он фигурирует, как SIOCINQ.
не может быть, что в манах этого нет. у меня
они столетней давности, попробуйте у себя
zgrep SIOCINQ /usr/man/man*/*
кстати, пришел сюда не консольным браузером,
увидел какие-то зеленые звездочки, их раньше,
кажется, не было?
Ха! UDP -- это, конечно, тоже датаграммы, но не все датаграммы идут через udp. Про netlink знаешь? Это протокол для общения линух-ядра с юзер-спэйсом. Тоже датаграммный.
Про FIONREAD я тоже знал, и попытался его заюзать для netlink. И обломился -- оно не поддерживается. Потому и обрадовался, когда ты про SIOCINQ написал. А тут такой облом -- оно, оказывается, только для UDP :(.
ЗЫ: в net/netlink/af_netlink.c, в netlink_ops, в поле ioctl стоит sock_no_ioctl. Если ты ядро знаешь, то может быть подскажешь, где еще можно поглядеть? Вдруг на более верхнем уровне (по отношению к netlink) поддерживается способ узнать размер датаграммы, отдаваемой netlink'ом в юзер-спэйс?
ЗЗЫ: насчет звездочек почитай правила форума. Макс недавно новую рюшечку присобачил. Обсуждение ее имеется в форуме Linux-org-ru.
Вот сейчас допишу эту мессагу, возьму карандаш и кусок бумаги, и напишу большими буквами:
ТОРМОЗ! ЧИТАЙ МАНЫ!
И приклею ее к монитору. Я man recv читал хрен знает сколько раз еще в прошлом веке :) (при работе с поточными сокетами). Решил, что знаю его наизусть. И когда возникла необходимость, я в этот ман даже не полез. А зря, как выяснилось.
> чего сделать-то нужно? я про netlink не знаю ничего, но мне казалось, что размер сообщений там ограничен.
Не, сколько надо -- столько я и отправляю из ядра. В общем случае -- чем больше, тем лучше, дабы избежать переключений контекста. Чего мне надо? Да ничего, все уже сделано. Надо было считалку трафика сделать. Я ее к iptables прикрутил -- чтобы считала кому, откуда, на какой порт, с какого порта, в какое время, сколько байт пришло. А в юзер-спэйс повесил демона, который получает эту инфу из ядра и пишет в БД. Взял ULOG и похакал его (довольно грязным способом) -- чтобы оверхэда не было: у него порядка 200 байт на пакет идет в юзер-спэйс, а у меня -- 20 байт.
Так вот, демона хотел написать так, чтобы он автоматом получал размер датаграммы, и делал realloc для буфера, если она большая. Ман recv я не прочитал, FIONREAD обломился, и я забил на это -- просто знаю заранее, сколько отправляю, такой буфер в демоне и выделяю malloc'ом. Просто хотелось мне сделать все красиво...
> вы бы и написали конкретно, чего нужно. собственно, я этого до сих пор так и не понял.
Да ничего, просто чел вопрос задал, я ему пример кода привел, и заодно написал про датаграммы, чтобы он на грабли случайно не наступил (потому что приведенный мной пример кода с датаграммами работать не будет).
ЗЫ: я вчера решил, что зря тебе спасибо сказал. Оказалось -- нифига. Так что еще раз: спасибо.
> В общем случае -- чем больше, тем лучше,
> дабы избежать переключений контекста.
в linux НЕ не происходит переключения контекста
при выполнении системного вызова, процесс и ядро
работают в одном адресном пространстве, точнее,
ядро работает в адресном пространстве процесса,
который делает syscall. а если совсем точно, то
в контексте этого процесса. я полагаю, что вы все
это знаете, но на всякий случай.
> автоматом получал размер датаграммы, и делал
> realloc для буфера, если она большая.
есть очень простой способ. выделить буфер, в который
пакет заведомо поместится. и этот способ совсем не так
плох, как кажется. при выделении (MAP_ANONYMOUS) памяти
память реально не выделяется, страницы для нее будут
выделяться _только_ при записи, даже при при чтении не
будут.
поскольку пакет, скорее всего, не нужно сохранять никуда,
а необходимо как-то обработать и результат отослать БД,
этот буфер может спокойно жить в стеке.
> в linux НЕ не происходит переключения контекста
при выполнении системного вызова, процесс и ядро
работают в одном адресном пространстве, точнее,
ядро работает в адресном пространстве процесса,
который делает syscall. а если совсем точно, то
в контексте этого процесса.
Что есть переключение контекста:
1) Проверка наличия в памяти нового TSS и его размера.
2) Проверка привилегий.
3) Проверка наличия в памяти старого TSS.
4) Проверка наличия в памяти сегментов кода и стека для новой задачи.
5) Проверка размера сегментов кода и стека для новой задачи.
6) Сохранение регистров общего назначения: EAX, EBX, ECX, EDX, ESI, EDI, EBP.
7) Сохранение флагов: EFLAGS.
8) Сохранение указателей кода и стека: EIP, ESP.
9) Сохранение селекторов: CS, DS, ES, SS.
10) Загрузка в TR адреса нового TSS.
11) Загрузка всех вышеперечисленных регистров.
12) Загрузка LDTR.
Что мы имеем при системном вызове? Инструкция "int 80h". Лезем в IDT за адресом обработчика. Извлекаем селектор для его сегмента кода. Проверяем привилегии. Проверяем наличие в памяти и размер сегмента кода для обработчика. Проверяем наличие в памяти TSS текущей задачи. Лезем в TSS за селектором стека для обработчика прерываний с уровнем привилегий #0. Проверяем привилегии. Проверяем его наличие в памяти. Извлекаем смещение для вершины стека. Проверяем размер сегмента стека. Сохраняем в TSS регистры SS, ESP. Загружаем регистры SS, ESP для обработчика. Кидаем ему на стек EFLAGS, CS, EIP. Запрещаем прерывания. Загружаем регистры CS, EIP для обработчика. Обработчик сохраняет на стеке регистры общего назначения и делает вызов нужной функции. Потом [почти] такая же (по объему) процедура выполняется в обратном порядке.
Чем не переключение задач? Не загружается TR и не проверяется наличие нового TSS в памяти. LDT в linux не используется вообще, AFAIK.
По сути -- тот же самый context switch. Ну или во всяком случае не сильно дешевле. Потому что по любому идет смена привилегий 3-0, потом обратно 0-3.
> есть очень простой способ. выделить буфер, в который
пакет заведомо поместится. и этот способ совсем не так
плох, как кажется. при выделении (MAP_ANONYMOUS) памяти
память реально не выделяется, страницы для нее будут
выделяться _только_ при записи, даже при при чтении не
будут.
AFAIK, работает это только при наличии поганой фишки overcommit. Почему "поганой"? Потому что я делаю запрос памяти, мне ее дают (вместо того, чтобы вернуть -ENOMEM), а когда я ее начинаю юзать, меня убивает OOM-killer. Одно слово: ДЕРЬМО! Зла не хватает. Если я правильно слышал звон, эту фишку выкинули из 2.6. Надеюсь, и из 2.4 выкинут или сделают опциональной. А то из-за какого-то сраного нетшкафа (вроде из-за него overcommit сделали?) невозможно построить систему с предсказуемым поведением.
> поскольку пакет, скорее всего, не нужно сохранять никуда,
а необходимо как-то обработать и результат отослать БД,
этот буфер может спокойно жить в стеке.
IMHO, сильно большой буфер в стеке делать не очень хорошо. Потому что независимо от наличия/отсутствия overcommit программа будет убита, если ей не хватит стека (-ENOMEM невозможно вернуть, если программа что-то пишет в отсутствующую память или ставит ESP за пределы сегмента стека).
> Что есть переключение контекста:
>
> 1) Проверка наличия в памяти нового TSS и его размера.
> ....
> 10) Загрузка в TR адреса нового TSS.
> 11) Загрузка всех вышеперечисленных регистров.
это __switch_to(), и так оно приблизительно и было в 2.2,
в 2.4 и выше все не так. один TSS на процессор (не процесс!),
инициализруется при старте. то есть, с точки зрения процессора
переключения задач не происходит вообще, при переключении
контекста ядром в TSS пишется новое значение supervisor esp
и все. так что в этом смысле переключение контекста делает еще
меньше, чем вы думаете.
> Что мы имеем при системном вызове? Инструкция "int 80h".
> Лезем в IDT за адресом обработчика.
а вот и не лезем! IDT читается только в момент загрузки,
при инициализации. дальше в общем все правильно...
> LDT в linux не используется вообще, AFAIK.
legacy tls только. можно сказать, и не используется.
> По сути -- тот же самый context switch. Ну или во всяком
> случае не сильно дешевле.
а вот здесь вы сильно ошибаетесь. и дело даже не в том, что
int 80, несмотря на все эти переключения стэка, сохранения
регистров работает все равно очень быстро (int + iret меньше
сотни тактов на PIII, кажется), и не в том, что начиная с 2.6
используется sysenter вместо int 80, что еще быстрее.
дело в том, что говоря о переключении контекста вы забыли
сущую мелочь - switch_mm(). на i386 это, в сущности, загрузка
cr3. и вот этот switch_mm() (и связанный с ним tlb_flush())
и есть основная цена вопроса. именно поэтому процессы имеют
mm и active_mm, поэтому переключение между потоками происходит
гораздо быстрее, чем между процессами, тут много чего можно
написать.
так что syscall _намного_ дешевле переключения контекста.
Ни хрена себе!!! не могу удержаться, Рой Джонс, нокаут во
втором раунде!!! ну ни фига себе...
так, это я отвлекся немного, на чем мы остановились.
> AFAIK, работает это только при наличии поганой фишки overcommit.
> я делаю запрос памяти, мне ее дают (вместо того, чтобы вернуть -ENOMEM),
> а когда я ее начинаю юзать, меня убивает OOM-killer.
где разница? если вы делаете realloc(), и overcommit включен будет
то же самое, если нет - ENOMEM, так лучше его получить сразу, при
старте.
но это offtopic, здесь идет о максимальном размере одного пакета,
который по любому должен уместиться в sk_buff, так что 256K хватит.
> Если я правильно слышал звон, эту фишку выкинули из 2.6.
нет. это настраивается, см в Documentation, где - не помню, дома
только Windows. а лучше см security/dummy.c:vm_enough_memory(),
используется, в частности, в do_mmap_pgoff(), которая и выделяет
память. наверняка спутал имена :) там 3 режима возможны.
> IMHO, сильно большой буфер в стеке делать не очень хорошо.
> Потому что независимо от наличия/отсутствия overcommit программа
> будет убита, если ей не хватит стека
верно, держать "большой" буфер в стеке можно только если мы можем
оценить сверху размер использованного стека перед вызовом этой
функции во-первых. общий размер известен - 8Mb, так что если
мы не выходим за этот предел и получаем OOM-kill, мы его получим
в любом случае, где бы мы этот буфер не поместили.
во-вторых, так стоит делать в том случае, если эта функция будет
вызываться постоянно, поскольку у нас нет хорошего и простого способа
вернуть использованную (то-есть, куда писали) память в стеке системе.
но разумеется, я и не утверждал, что это рецепт универсален.
то же касается и буфера максимального размера, это не всегда
хороший способ. все, что я утверждал, что он не так плох, как
кажется на первый взгляд.
с уважением.
прочитал на трезвую голову, чего писал...
> > Лезем в IDT за адресом обработчика.
>
> а вот и не лезем! IDT читается только в момент загрузки,
виноват, невнимательно прочитал, что вы написали.
имел в виду, что idt_descr не читается. idt_table
читается, вы правы.
другие придирки...
> Проверяем наличие в памяти TSS текущей задачи.
как это можно проверить? читаем просто регистр tr,
это и есть TSS.
> Запрещаем прерывания.
нет. вектор 0x80 trap gate, прерывания не запрещаются.
но, повторю, это придирки, все это не важно. если
так расписывть, то и доступ к памяти очень медленно
происходит: читаем DS, читаем GDT entry..., pgd table,
pmd, pte...
по поводe overcommit. на самом деле, он очень давно
существует. по умолчанию он как раз 0 - no overcommit,
так что зря вы ругаетесь на 2.4. другое дело, что это
все равно не гарантирует, что процесс не будет убит
в тот момент, когда пишет в память выделенную ему
ранее. потому, что реализовать такие гарантии трудно,
как раз 2.6 и пытается это делать в strict overcommit
mode. а вообще - mlock().
кроме того есть OOM-killer, это другая история. процесс
может быть убит потому, что другому процессу (или ядру)
понадобилась память.
> это __switch_to(), и так оно приблизительно и было в 2.2
Вообще-то я описывал то, что делает процессор (аппаратно). Поэтому меня сомнения берут, что это какая-то функция в ядре. Однако ядро я почти не знаю, поэтому, собственно, не спорю, а так, удивляюсь.
> в 2.4 и выше все не так. один TSS на процессор (не процесс!),
инициализруется при старте. то есть, с точки зрения процессора
переключения задач не происходит вообще, при переключении
контекста ядром в TSS пишется новое значение supervisor esp
и все
"Не верю!" Как и куда сохраняются все регистры той задачи, которая выполнялась перед переключением на новую? Ядро все делает вручную? Если да, то какой в этом смысл?
> читаем просто регистр tr, это и есть TSS
Если мне склероз не изменяет, процессор при чтении адреса стека для обработчика прерываний лезет в TSS, то бишь в ОЗУ.
> вектор 0x80 trap gate
То есть после "int 80h" мы вернемся опять на "int 80h", а не следующую за ней инструкцию?
> legacy tls только
Не очень понял. LDT -- это таблица дескрипторов сегментов, локальная для данной задачи (данного TSS) (в отличие от GDT, которая является глобальной для всех). При чем здесь tls? Или мы под этим термином понимаем разные вещи?
> начиная с 2.6 используется sysenter вместо int 80
Ааа... Эээ... То есть если я поставлю ядро 2.6 вместо 2.4, то моя glibc не будет работать с ним? (Она-то ведь int 80h юзает.) Иначе говоря, совместимость поломана нафиг и кардинально, или я чего-то не понял?
> на i386 это, в сущности, загрузка cr3.
Ага. Понял. Сначала не понял, зачем Linus'у понадобился такой геморрой: каждому процессу -- по каталогу страниц. Потом дошло: 1 каталог == 4 Гб памяти "всего-навсего". (Кто бы мог подумать в 80-х годах, что этого будет недостаточно?)
> по умолчанию он как раз 0 - no overcommit
И тем не менее, абсолютно корректно написанная программа умирает по вине ядра. Для меня важно только это.
> другое дело, что это все равно не гарантирует, что процесс не будет убит в тот момент, когда пишет в память выделенную ему ранее
Тогда в чем смысл overcommit? Если включен он или выключен -- результат один и тот же.
> потому, что реализовать такие гарантии трудно
В чем трудность? Даем процессу память только если она есть. После этого отданная память больше никому не дается -- пока процесс ее не отдаст обратно ядру. Юзает эту память процесс, или законсервировал ее на зиму -- ядру на это покласть. Самому ядру понадобилось? Обойдется. Само ядро ничего от себя лично не делает. Только от какого-нибудь процесса. Вернет этому процессу ошибку "нет памяти". Пускай обрабатывает. Недостаточно памяти для работы сетевого стека? Ну значит не будет работать сетевой стек. Нет памяти -- это означает, что ее действительно нет, а не то что она есть у кого-то, у кого ее можно отнять. В гробу я видал сетевой стек (и любой другой вспомогач), если из-за него умрет полезная задача.
> где разница? если вы делаете realloc(), и overcommit включен будет
то же самое, если нет - ENOMEM, так лучше его получить сразу, при
старте
Видишь ли, привычка -- вторая натура. А привычка такая: жрать минимум вычислительных ресурсов. Код примерно такой:
void do_something()
{
size_t need = what_size_i_need();
if (size < need && do_realloc)
{
char *new = realloc(buf, need);
if (new)
{
buf = new;
size = need;
}
else
{
write_to_log("prg[pid]: out of memory, fix it and `kill -HUP' me");
do_realloc = 0;
}
}
if (size >= need)
/* do something */;
else
/* try to do something anyway */;
}
void sighup_handler(int sig)
{
do_realloc = 1;
}
Идея в том, что программа может и обойтись имеющейся памятью, пожертвовав при этом некоторй оптимизацией. Например, в случае считалки трафика, можно скинуть БД на диск и заюзать ее буфер для датаграммы. Или, в крайнем случае, некоторое время терять часть трафика, но только _часть_, а не весь. Syslog намылит админу вышеприведенную мессагу, и админ примет меры. А если прога просто умерла, он об этом не узнает. Или должен будет извращаться с daemontools от DJB, или вешать на cron скрипт, который будет проверять наличие процесса и слать мыло, если процесса нет. В принципе, в примере с демоном-считалкой не те объемы памяти жрутся, но... привычка -- вторая натура :).
Более глобальная идея такова: программа не имеет права завершаться до тех пор, пока не выполнила всю работу, или выполнение работы стало абсолютно невозможным. В случае любого системного демона: он не имеет права самостоятельно умирать, если вообще запустился. Потому что самопроизвольная смерть какой-либо программы делает поведение системы непредсказуемым. Если демон не может выполнять свою работу, он должен сидеть и ждать, когда такая возможность появится. Система ведь меняется: например, сложилась нештатная ситуация, наплодилась куча процессов, которые сожрали всю память, а через пару минут они отработают и помрут, и память появится. А если какая-то программа из-за такой ситуации умерла (по воле программиста, а не ядра), то в помойку такую программу (вместе с программистом).
ЗЫ: во блин, понаписал-то. С каждым разом посты все длиннее и длиннее :).
ЗЗЫ: Ты мне тут дофига полезной инфы подкинул, так что если живешь в Мурманске, можем встретиться -- я тебя пивом поить буду.
> > в 2.4 и выше все не так. один TSS на процессор (не процесс!),
>
> "Не верю!" Как и куда сохраняются все регистры той задачи, которая
> выполнялась перед переключением на новую? Ядро все делает вручную?
вручную. см include/asm/не-помню-где.h:switch_to(prev, next, last).
сохранить-то нужно всего ничего: ebp, esi, edi. (cs, ds не меняются
в ядре, ecx,edx сохранит компилятор). эти регистры сохраняем в стеке
ядра текущего процесса, переключаемся на стек другого глядя в
task_struct.thread_struct, и вызываем __switch_to(). user-level
регистры сохранять не надо, они уже и так сохранены, мы ведь в
kernel mode. что-то путано написал, лучше сами посмотрите :)
> Если да, то какой в этом смысл?
а быстрее. но, главное, теперь количество процессов, которые можно
создать, ограниченно только памятью.
> читаем просто регистр tr, это и есть TSS
>
> Если мне склероз не изменяет, процессор при чтении адреса стека
> для обработчика прерываний лезет в TSS, то бишь в ОЗУ.
да, но проверить что там именно TSS, а не мусор, он не может.
> > вектор 0x80 trap gate
>
> То есть после "int 80h" мы вернемся опять на "int 80h",
> а не следующую за ней инструкцию?
нет, процессор сохраняет адрес текущей инструкции (а не следующей),
если был fault, напр page fault. в самой таблице idt указаний что
сохранять нету.
> > legacy tls только
>
> Не очень понял. LDT -- это таблица дескрипторов сегментов > При чем здесь tls?
modify_ldt(). используется для реализации thread local storage (tls)
в libpthreads. еще для чего-то в эмуляторах разных. в новой ntpl
не используется.
> начиная с 2.6 используется sysenter вместо int 80
>
> Ааа... Эээ... То есть если я поставлю ядро 2.6 вместо 2.4,
> то моя glibc не будет работать с ним? (Она-то ведь int 80h юзает.)
это я плохо выразился. конечно, 2.6 и int 80 поддерживает, для
совместимости. на самом деле более интересны вот какие ситуации:
1. новая libc хочет sysenter, ядро не поддерживает.
2. libc и ядро хотят sysenter, процессор не поддерживает.
в обоих случаях все будет работать.
> > на i386 это, в сущности, загрузка cr3.
>
> Ага. Понял. Сначала не понял, зачем Linus'у понадобился такой геморрой:
> каждому процессу -- по каталогу страниц. Потом дошло: 1 каталог == 4Гб
> памяти "всего-навсего".
а защита памяти процессов друг от друга?
> по умолчанию он как раз 0 - no overcommit
> Тогда в чем смысл overcommit? Если включен он или выключен
> результат один и тот же.
ох... будем считать для простоты, что malloc() всегда вызывает
mmap(MAP_ANONYMOUS), это не такое уж и сильное упрощение.
в случае overcommit == 1 malloc() сработает всегда, и иногда
это очень полезно. например, для "разреженных" структур данных.
скажем, простая реализация электронной таблицы:
cell cell_array[100000][100000].
и все работает, потому что только небольшое число ячеек будет
использовано, хотя индексы могут быть очень большие. только,
бога ради, не обьясняйте мне, что такая реализация никуда не
годится, это для примера.
другой пример. mmap("огромный файл", PROT_WRITE | MAP_PRIVATE).
и мы собираемся только совсем немного в нем поменять для личного
пользования, пару страничек. но ядро о наших намерениях не знает,
и без overcommit mmap() вернет -ENOMEM.
или большой процесс делает fork() + exec("маааленькая программа),
без overcommit fork() не сработает.
когда overcommit == 0, malloc() в некоторых случаях вернет NULL.
тогда, когда заведомо ясно, что столько памяти найти негде.
типа предыдущего mmap(), если размер огромного файла больше
RAM + SWAP.
повторяю, в 2.6 есть strict overcommit, 2, когда ядро будет
стараться считать точнее, чем в no overcommit.
> > потому, что реализовать такие гарантии трудно
>
> В чем трудность? Даем процессу память только если она есть.
об этом можно книгу написать, без шуток. судя по следующим
строчкам вы даже отдаленно не представляете всей сложности :)
что значит, память есть? ну, например, у нас swap пустой,
значит память есть. и вот нам понадобилась страничка для процесса.
неиспользованной памяти нет, надо что-то в swap сбросить, да вот
беда - у нас нет свободного буфера для I/O, и приходит oom killer.
но это так, семечки. подумайте хотя бы вот о чем. как правильно
сосчитать, какой максимум памяти может понадобиться всем живым
процессам? хотя и это не так сложно.
а что значит даем ее процессу? тут же выделяем страницы + mlock()?
прощай тогда скорость навеки, и прощай такое понятие, как swap.
то, что вы описываете, это MS-DOS. многие realtime OS так работают,
но для системы общего назначения это не годится.
> Самому ядру понадобилось? Обойдется. Само ядро ничего от себя
> лично не делает. Только от какого-нибудь процесса. Вернет этому
> процессу ошибку "нет памяти".
ага, только это процесс уже exit() давно сделал, некому ошибку
возвращать. тот же ввод-вывод.
> > где разница? если вы делаете realloc(), и overcommit включен будет
> > то же самое, если нет - ENOMEM, так лучше его получить сразу, при старте
>
> Видишь ли, привычка -- вторая натура. А привычка такая: жрать минимум
> вычислительных ресурсов. Код примерно такой:
блин, я уже пожалел, что написал про буфер максимального размера.
ну в чем вы меня убеждаете? что это не всегда подходит? да я сам
уже 2 раза писал, что это не всегда лучший метод! но это хороший
метод если мы заранее знаем верхнюю границу во-первых, и вероятность
достижения этой границы (или близко к ней) высока. и это не жрет
никаких лишних ресурсов! и это абсолютно не увеличивает проблем
с overcommit.
кстати, про realloc(). в данном конкретном случае, когда буфер
используется каждый раз для чтения нового пакета (то есть затираем
старое содержимое), это плохая функция. free() + malloc().
потому что. в случае, если mremap() не сработает, а он может,
нам понадобится памяти old_size + new_size и копирование.
тут-то и будет у нас пресловутый OOM-kill, если old_size + new_size
достаточно большой. плюс (точнее минус) скорость.
> так что если живешь в Мурманске, можем встретиться
:)) вспоминается классика: Будете у нас, на Колыме...
Уж лучше вы к нам!
Для этого свой cr3 на каждую задачу не обязателен. Есть менее геморройный способ: манипулирование границами сегментов.
> другой пример. mmap("огромный файл", PROT_WRITE | MAP_PRIVATE). и мы собираемся только совсем немного в нем поменять для личного пользования, пару страничек. но ядро о наших намерениях не знает, и без overcommit mmap() вернет -ENOMEM.
А что, размер отображаемого блока, указываемый при вызове mmap, никак не используется этой функцией? Она всегда весь файл mmap-ит?
> у нас нет свободного буфера для I/O, и приходит oom killer.
При старте ядра резервируем 4К памяти на всякий случай. Эти 4К никому в юзер-спэйс не отдаем. Если у нас нет памяти вообще, юзаем эти 4К как временный буфер для тех нужд, которые удовлетворить совершенно необходимо в данный момент. Операцию выполняем атомарно. После ее завершения буфер освобождаем.
> какой максимум памяти может понадобиться всем живым процессам?
А на кой ляд ядру об этом заранее заморочиваться? Понадобится память процессу -- он сам к ядру придет и скажет "дай".
> а что значит даем ее процессу? тут же выделяем страницы + mlock()?
Зачем "+ mlock()" сразу? Выделяем страницы, если они есть, и все.
Ну где тут вообще проблема?!?! Есть файл /proc/meminfo. Если его могу прочитать я, значит содержащаяся в нем инфа известна и ядру тоже, так? Вот приходит в ядро запрос на память. Ядро делает элементарную проверку:
if (mem.free + swap.free < required) return -ENOMEM;
Все!!!!
> ага, только это процесс уже exit() давно сделал, некому ошибку возвращать. тот же ввод-вывод.
Ну и тем более. Просто ввод/вывод не выполнится из-за отсутствия памяти. Плохо? Конечно. Но покилять кого-то -- еще хуже.
вы знаете, я даже начал было отвечать по пунктам,
но сломался. на конкретные вопросы я постараюсь
ответить, а так... несерьезно все это.
посмотрите на это с другой стороны. почему авторы
Windows, Unix занимаются вредительством, и не пишут
эти системы так, как предлагаете вы? тем более, что
это так просто.
особенно про сегменты понравилось...
всего доброго.
> посмотрите на это с другой стороны. почему авторы Windows, Unix занимаются вредительством, и не пишут эти системы так, как предлагаете вы? тем более, что это так просто.
Не, в том-то и дело, что надежный софт сделать сложнее, чем ненадежный. И чем сама по себе программа сложнее, тем (при прочих равных) сложнее сделать ее полностью предсказуемой.
Почему linux ненадежен? IMHO, проблема здесь не техническая, а психологическая: Linus'у не сильно охота заморочиваться. То есть, он бы, наверно, не отказался от того, чтобы linux был надежен, но, поскольку это требует немалых усилий, то ну их нах, эти заморочки.
Про Windows даже говорить не хочется -- там надежность никто никогда не ставил во главу угла.
Про прочие UNIX'ы -- не в курсе. Про QNX: то что я читал в свое время -- мне понравилось. Но поддержка железного зоопарка у нее никакая. А когда узнал цены на девелоперский софт под нее, она мне разонравилась окончательно (было это давно, как сейчас -- не знаю). DOS или самоизобретенный велосипед: проблемы с железом, а также необходимость все делать самому: стек TCP/IP и т.п.
Потому, собственно, и злюсь: linux подходит почти по всему. Но, блин, сильно напрягает непредсказуемость, про которую узнал сравнительно недавно (про саму возможность SIGKILL вместо -ENOMEM). Вот и стремлюсь узнать про это поподробнее.
Ладно, чувствуется, что тебя конкретно запарил спор с дилетантом. За полезную инфу -- спасибо.
> Но, блин, сильно напрягает непредсказуемость, про которую узнал
> сравнительно недавно (про саму возможность SIGKILL вместо -ENOMEM).
mlockall(). после этого процессу память будет распределяться
вся и сразу, и она никогда не будет своппироваться, вот вам
и предсказуемость.
если всем процессам сделать, получим что-то похожее на управление
памятью в qnx.
еще раз напоминаю про strict overcommit mode.