Управляемые ресурсы в ядре Linux (также известны как Device Resource Management или devres API), о которых я писал небольшую заметку ранее, — вещь крайне полезная, но не стоит воспринимать этот вспомогательный набор функций как серебрянную пулю при написании драйверов или модификации существующих. Рассмотрим случаи, где нужно аккуратно применять данные методы.
Про прерывания и tasklet'ы доходчиво описано в статье в блоге компании EmBox, поэтому подразумевается, что читатель уже ознакомлен с этим или подобным материалом.
Возьмём в качестве примера следующий псевдокод:
Внимательный читатель сразу же воскликнет: «Так здесь же условия гонки, приводящие к бесконечному циклу!», и будет прав.
Давайте разберёмся почему. Tasklet'ы выполняются в контексте мягких прерываний (softirq), а следовательно существует вероятность задержки между планированием (
Как вылечить? Да очень просто, следите за руками:
Как раз тот редкий случай, когда мы пользуемся devres API в момент удаления объекта.
Рассмотрим теперь следующий псевдокод:
Теперь представим себе такой сценарий:
Почему так происходит? Да потому что память, выделенная на этапе
Как лечить? Тоже просто. Не пользоваться памятью, выделенной с помощью
P.S. На самом деле проблема более широкая, и она поднимается для обсуждения на будущем Kernel Summit 2015.
Удачи в отладке!
Регистрирование обработчика прерывания при наличии tasklet'ов
Про прерывания и tasklet'ы доходчиво описано в статье в блоге компании EmBox, поэтому подразумевается, что читатель уже ознакомлен с этим или подобным материалом.
Возьмём в качестве примера следующий псевдокод:
struct my_struct { … struct tasklet_struct *tasklet; int irq; }; void tasklet_handler(…) { do_the_things_right(…); } irqreturn_t irq_handler(void *param) { struct my_struct *ms = param; … tasklet_schedule(&ms->tasklet); return IRQ_HANDLED; } int probe(…) { struct my_struct *ms; int err; ms = devm_kzalloc(…); … tasklet_init(&ms->tasklet, tasklet_handler, (unsigned long)ms); … err = devm_request_irq(ms->irq, irq_handler, …, ms); if (err) return err; return 0; } int remove(…) { struct my_struct *ms = …; … tasklet_kill(&ms->tasklet); }
Внимательный читатель сразу же воскликнет: «Так здесь же условия гонки, приводящие к бесконечному циклу!», и будет прав.
Давайте разберёмся почему. Tasklet'ы выполняются в контексте мягких прерываний (softirq), а следовательно существует вероятность задержки между планированием (
tasklet_schedule()) и выполнением задачи. В это время может произойти как раз удаление драйвера из памяти, пользователь вызвал rmmod my_module. Конечно, мы явно зовём удаление tasklet'а, см. tasklet_kill(), но обработчик прерывания всё ещё активен, т.к. мы воспользовались devres API и запланировали его удаление в порядке очереди после выполнения ->remove()!Как вылечить? Да очень просто, следите за руками:
int remove(…) { struct my_struct *ms = …; … devm_free_irq(ms->irq, ms); tasklet_kill(&ms->tasklet); }
Как раз тот редкий случай, когда мы пользуемся devres API в момент удаления объекта.
Что прячет драйвер, к примеру, символьного устройства?
Рассмотрим теперь следующий псевдокод:
int closecb(…) { struct my_struct *ms = …; do_something_on_close(ms, …); } struct file_ops fops = { .close = closecb, … }; int probe(…) { struct my_struct *ms; int err; ms = devm_kzalloc(…); … err = register_char_device(ms, "node_served_by_driver", &fops, …); if (err) return err; return 0; } int remove(…) { struct my_struct *ms = …; … }
Теперь представим себе такой сценарий:
- Убедимся, что драйвер загрузился и привязался к устройству.
- Откроем
/dev/node_served_by_driver, и сделаем так, чтобы устроство оставалось открытым. - Отвяжем драйвер от устройства, например, выполнив команду:
или просто отключением устройства от шины, если возможно, например, отсоединением USB накопителя.echo our_device_name > /sys/bus/platform/drivers/our_driver_name/unbind - Теперь закроем устройство.
- Наслаждаемся падением ядра.
Почему так происходит? Да потому что память, выделенная на этапе
->probe() высвобождается в момент отвязки устройства. А мы всё ещё используем этот участок памяти! При этом драйвер устройства не удаляется и не может быть удалён, т.к. держится программой, открывшей устройство, и остаётся в памяти до момента явного закрытия и удаления. Как лечить? Тоже просто. Не пользоваться памятью, выделенной с помощью
devm_kzalloc(), в файловых операциях в драйвере, аккуратно следить за временем жизни объектов. По мнению автора devres API префикс dev там не просто так, а с целью указать, что ресурсы имеют непосредственное отношения к железу, а не к обработке событий от пользователя.P.S. На самом деле проблема более широкая, и она поднимается для обсуждения на будущем Kernel Summit 2015.
Удачи в отладке!
