LINUX.ORG.RU

В Linux 5.15 добавлен системный вызов для быстрого освобождения памяти умирающего процесса

 , , ,


1

1

Новый системный вызов называется process_mrelease и позволяет ускорить освобождение памяти, используемой процессом, получившим сигнал SIGKILL. process_mrelease получает два параметра: pid умирающего процесса и flags. В текущей реализации параметр flags не используется и должен иметь значение 0, однако будущем функциональность системного вызова может быть расширена. Возвращаемые значения: 0 при успешном выполнении и -1 если произошла ошибка, код которой передаётся через errno.

Новая функциональность может быть использована в сервисах наподобие systemd-oomd и lmkd.

Из сообщения о коммите:

In modern systems it’s not unusual to have a system component monitoring memory conditions of the system and tasked with keeping system memory pressure under control. One way to accomplish that is to kill non-essential processes to free up memory for more important ones. Examples of this are Facebook’s OOM killer daemon called oomd and Android’s low memory killer daemon called lmkd.

For such system component it’s important to be able to free memory quickly and efficiently. Unfortunately the time process takes to free up its memory after receiving a SIGKILL might vary based on the state of the process (uninterruptible sleep), size and OPP level of the core the process is running. A mechanism to free resources of the target process in a more predictable way would improve system’s ability to control its memory pressure.

Introduce process_mrelease system call that releases memory of a dying process from the context of the caller. This way the memory is freed in a more controllable way with CPU affinity and priority of the caller. The workload of freeing the memory will also be charged to the caller. The operation is allowed only on a dying process.

After previous discussions [1, 2, 3] the decision was made [4] to introduce a dedicated system call to cover this use case.

The API is as follows,

         int process_mrelease(int pidfd, unsigned int flags);

       DESCRIPTION
         The process_mrelease() system call is used to free the memory of
         an exiting process.

         The pidfd selects the process referred to by the PID file
         descriptor.
         (See pidfd_open(2) for further information)

         The flags argument is reserved for future use; currently, this
         argument must be specified as 0.

       RETURN VALUE
         On success, process_mrelease() returns 0. On error, -1 is
         returned and errno is set to indicate the error.

       ERRORS
         EBADF  pidfd is not a valid PID file descriptor.

         EAGAIN Failed to release part of the address space.

         EINTR  The call was interrupted by a signal; see signal(7).

         EINVAL flags is not 0.

         EINVAL The memory of the task cannot be released because the
                process is not exiting, the address space is shared
                with another live process or there is a core dump in
                progress.

         ENOSYS This system call is not supported, for example, without
                MMU support built into Linux.

         ESRCH  The target process does not exist (i.e., it has terminated
                and been waited on).

[1] https://lore.kernel.org/lkml/20190411014353.113252-3-surenb@google.com/
[2] https://lore.kernel.org/linux-api/20201113173448.1863419-1-surenb@google.com/
[3] https://lore.kernel.org/linux-api/20201124053943.1684874-3-surenb@google.com/
[4] https://lore.kernel.org/linux-api/20201223075712.GA4719@lst.de/

>>> Подробности



Проверено: hobbit ()

Ой какие молодцы!

Я, как и некоторые другие пользователи форумов указывали на явные проблемы менеджмента памяти в Linux, однако «куд-кудах»-адепты продолжали настаивать на том, что всё хорошо, и проблема решается добавлением памяти либо swap. О системах с небольшим кол-вом памяти в которые ты просто не можешь добавить память или включить swap они не слышали, мамкины подкроватные системные администраторы.

И вот пожалуйста - костыль для улучшенной работы костылей, которые решают проблему угрёбищной работы с памятью Linux.

Неплохо, Торвальдс, очень неплохо.

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

А что не так в работе с памятью, в контексте обработки SIGKILL?

Всё так. Я говорю о том что в целом этих проблем бы не возникало, как и надобности в этом системном вызове, если бы ситуации нехватки памяти обрабатывались адекватно. Но у разрабов ядра своё собственно видение этого вопроса, из-за чего и появились упоминаемые в шапке костыли

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

Критикуешь - предлагай

Нет, зачем не это?

Надеюсь, ты уже выслал свой патч с новой подсистемой управления памяти?

Нет конечно, мне же никто за это не заплатил. Хуже того - никто и не заплатит если я это сделаю, хотя «вроде бы всем нужно». А всё потому что Linux давно стал corporate slave.

12309, например, никак не починят. Ха-ха!

reprimand ★★★★★ ()
Ответ на: Требуется пояснение от poisons

Почитай зачем нужны сторонние oom киллеры и почему они вообще существуют, там же, в шапке тем они упоминаются:

Examples of this are Facebook's OOM killer daemon called oomd and Android's low memory killer daemon called lmkd

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

SIGKILL не освобождает память процессов, находящихся в состоянии Disk sleep.

Ты про «uninterruptible sleep»? Такие процессы нельзя вообще убить при помощи SIGKILL, то есть основная проблема не в освобождении памяти таких процессов.

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

Нельзя убить, но можно освободить память.

Например, оом киллер умеет освобождать память таких процессов, при этом они продолжают существовать в состоянии D.

Думаю то же самое и делает новый вызов.

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

В его цитате причина «висит не сам процесс, а ядро, выполняя системный вызов, полученный от этого процесса».

Процесс не отлипнет, пока ядро не доделает то, что делает, и не ответит процессу.

Zombieff ★★ ()

Дёргать сисколами ядро чтобы убить процесс – это тормозит. Как же нам это ускорить? А давайте дёргать ядро ещё больше!

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

Ну адекватность обработки все же зависит от задач.

Есть ситуации в которых крах оконного менеджера вместе с иксами не такая тяжелая ситуация как уход в своп процесса который это вызвал.

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

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

А адекватно это как? Просто да, проблемы есть. Ну, примерно как если бы ты пытался вывести 100 тонн на орбиту и ныл что тяжело, высоко и лебёдку там наверху закрепить негде.

kirill_rrr ★★★★★ ()

Пока не понимаю, как этот вызов работает.

Если память процесса можно безопасно освободить во время D-state, то почему бы не изменить поведение SIGKILL, чтобы он всегда делал это освобождение первым делом? Тогда не будет нужен новый системный вызов, и можно будет обойтись kill(pid, SIGKILL).

А если нельзя, то какую полезную работу он будет совершать? Не случится ли проблемы, если обработчик непрерываемого системного вызова будет в этот момент делать запись в память user-процесса?

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

Пока не понимаю, как этот вызов работает.

не слишком надёжно: может «поймать» память другого процесса, а может и не успеть.

Если память процесса можно безопасно освободить во время D-state, то почему бы не изменить поведение SIGKILL, чтобы он всегда делал это освобождение первым делом?

По многим причинам. Посылка сигнала - асинхронная операция, которая просто битик выставляет. Потом с этим разбирается уже планировщик. Ты же предлагаешь синхронно лочить мм-контекст убиваемого процесса, и начинать там что-то высвобождать. И тут не всё так просто, по тому, что бОльшую часть сигналов процесс может обработать и сам. То есть, тебе бы пришлось проверять, а установлен ли обработчик? Если да - ничего не делаем. Если нет - начинаем память тырить. А если он как раз в это время и установил обработчик? Надо ещё от рейсов избавиться, и непонятно, возможно ли это в данном случае. Но даже если и да - всё равно получится, что убивающий процесс может успеть залочить мм контекст и освобождать ресурсы в своём контексте, а может и не успеть, и убиваемый процесс сам всё отпустит. Получатся разные сценарии убийства, между которыми выбор происходит весьма рандомно, и тд.

В общем, думаю, основные причины - это асинхронщина и вышеозначенные рейсы.

Помимо того, то, что они сделали, позволяет одному процессу раскидывать сигналы кому попало, а другой процесс ходит и подбирает ресурсы. Если делать, как ты говоришь - разделить их не получится.

Не случится ли проблемы, если обработчик непрерываемого системного вызова будет в этот момент делать запись в память user-процесса?

Нет: пока он в D-state, он ждёт. Если он что-то куда-то пишет - это уже не D-state, AFAIK.

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

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

Да, ведь ровно это и делает новый вызов.

Я предлагаю сделать следующий костыль: в реализации kill после проверки прав доступа вставить проверку -

if (sig == SIGKILL) {
  /* тут сделать всё то, что делает process_mrelease,
   * но без проверки установленности того бита, 
   * который ставит SIGKILL
   */
}

Если process_mrelease работает

не слишком надёжно,

то такой подход будет не хуже - в тех местах, где пользовательское приложение хотело бы позвать этот новый вызов, достаточно будет обычного kill. И на системах без патча процесс тоже умрёт, код будет более универсальным.

То есть, тебе бы пришлось проверять, а установлен ли обработчик?

SIGKILL всё равно нельзя обработать.

Если делать, как ты говоришь - разделить их не получится.

А в каких сценариях это полезно? Почему кто-то может захотеть убить процесс SIGKILL’ом, но оттянуть освобождение памяти на потом? И способа вызвать process_mrelease на процесс, который сейчас не находится в «умирающем» состоянии, я пока не вижу.

другой процесс ходит и подбирает ресурсы

А зачем для этого другой процесс? Почему бы ядру не подбирать ресурсы всегда?

Или речь идёт об убийстве процесса сигналом, отличным от SIGKILL (например, SIGTERM в случае, когда процесс не определил свой обработчик), когда сразу за этим следует вызов process_mrelease? Если да, то в каких случаях это полезно?

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

Да, ведь ровно это и делает новый вызов.

Разумеется! Но новый вызов - это новый вызов. А вот переделывать имеющийся асинхронный вызов в месиво синхронности и асинхронности, да так, что выбор между этими поведениями не детерминирован - совершенно плохая идея. Если нужна некая «куча дерьма», то очевидно, что её лучше сделать новым вызовом, которым особо то и пользоваться никто не будет, чем вкорячивать в тот, которым пользуются чуть менее чем все. Ведь неизвестно, насколько, и в каких сценариях, он, при этом, затормозится. А на тормозню нового сисколла всем, мягко говоря, плевать. К тому же он изначально не обязан быть асинхронным, а вот килл - обязан, и это тоже очень важный момент. Такое изменение было бы идеологически не верно.

Я предлагаю

Кому? :) Мне, как лоровскому анонимусу, ничего предлагать не нужно: я ваши слова до разрабов не доведу, да и вообще, всё, что я тут говорю - не более, чем личное мнение анонимуса.

/* тут сделать всё то, что делает process_mrelease,

  • но без проверки установленности того бита,
  • который ставит SIGKILL

Сигкилл ставит бит для планировщика, чтобы тот завершил процесс, а не передал бы ему управление. В данном гипотетическом месте, куда вы поставили свой коммент, соотв бит уже должен быть установлен, иначе рейсы гарантированы. Впрочем, боюсь, их и так не избежать, если, конечно, не ограничиваться одним только СИГКИЛЛом, но это жёсткий костыль. Да и даже тогда нет гарантии, что планировщик сам ни обработает СИГКИИ быстрее вас, и вам будет нечего подбирать. Может быть вам удастся планировщик залочить, но, в случае СМП, это кажется не так уж и хорошо.

Одним словом, я думаю, что сделать так можно, но костыли получатся просто адовыми, а про перформанс - никто не знает, на каких ворк-лоадах этот костыль даст просадки. Вдруг кто-то сигналы раскидывает с огромной скоростью, и ему асинхронность килла крайне важна? Неизвестно ведь.

Почему кто-то может захотеть убить процесс SIGKILL’ом, но оттянуть освобождение памяти на потом?

Вот это не ко мне вопрос точно. Иногда что-то делают просто ради гибкости. Типа, если есть возможность дать юзеру такой выбор - ну чо б и ни дать? С другой стороны, там явно не всё так просто, и, как я понял, тот процесс, который «высвобождает» ресурсы другого, получает на них приоритет в использовании - он может их сразу прибрать к рукам, и они не разлетятся кому попало. Возможно, у них есть какие-то задумки о том, что сигналы должен посылать процесс с определёнными правами и приоритетом, а подбирать ресурсы должен процесс с другими правами и/или приоритетом. В общем, про юз-кейсы вам анонимусы с ЛОРа точно вряд ли расскажут, и я не исключение.

А зачем для этого другой процесс? Почему бы ядру не подбирать ресурсы всегда?

Так и в вашем сценарии речь не про ядро шла, а про того, кто сигнал отправил. Почему надо не ему ресурсы давать, а кому-то третьему - ну я х/з, но, видимо, так надо.

Или речь идёт об убийстве процесса сигналом, отличным от SIGKILL (например, SIGTERM

Разумеется.

Если да, то в каких случаях это полезно?

Про полезности - не ко мне вопрос, а к авторам всех этих многочисленных оом-киллеров. Но рискну предположить, что полезно это в тех случаях, когда приложению сначала делают «лёгкое предупреждение» сигтермом, и только потом, если оно и дальше тупит - шлют сигкилл. Хотя конечно, если это пока лёгкое предупреждение, то, казалось бы, и process_mrelease() звать пока не обязательно. Но, думаю, это делалось не из соображений полезности, а из соображений универсальности. Типа, если после сигкилла можно позвать process_mrelease(), то почему бы нельзя было его позвать и после других завершающих сигналов? Это, всё ж, система общего назначения, и желательно иметь универсальный и консистентный АПИ, даже в тех случаях, где это противоречит здравому смыслу. :)

anonmyous ()