Как записать в память __user из верхней половины обработчика прерывания?

Я работаю над проприетарным драйвером устройства. Драйвер реализован в виде модуля ядра. Затем этот модуль соединяется с процессом пользовательского пространства.

Важно, чтобы каждый раз, когда устройство генерирует прерывание, драйвер обновлял набор счетчиков непосредственно в адресном пространстве процесса пользовательского пространства из верхней половины обработчика прерывания. Драйвер знает PID и task_struct пользовательского процесса, а также виртуальный адрес, по которому находятся счетчики в контексте пользовательского процесса. Однако у меня возникли проблемы с выяснением того, как код, работающий в контексте прерывания, может использовать контекст mm пользовательского процесса и записывать в него. Подытожу, что мне нужно сделать:

  1. Получить адрес физической страницы и смещение, соответствующее виртуальному адресу счетчиков в контексте пользовательского процесса.

  2. Настройте сопоставления в таблице страниц и запишите на физическую страницу, соответствующую счетчику.

Для этого я пробовал следующее:

  1. Попробуйте использовать контекст mm пользовательской задачи, как показано ниже:

        use_mm(tsk->mm); 
        /* write to counters. */
        unuse_mm(tsk->mm);
    

    Это, по-видимому, приводит к зависанию всей системы.

  2. Дождитесь прерывания, когда наш пользовательский процесс был процессом current. Затем используйте copy_to_user().

Я не большой специалист по программированию ядра. Если есть хороший способ сделать это, пожалуйста, сообщите и заранее спасибо.


person Anirban Ghoshal    schedule 04.04.2016    source источник


Ответы (4)


Ваш драйвер должен быть тем, кто отображает память ядра для процесса пользовательского пространства. Например, вы можете реализовать обратный вызов .mmap для struct file_operation для своего устройства.

Драйвер ядра может писать в адрес ядра, который он отображает, в любое время (даже в обработчике прерывания). Процесс пользовательского пространства немедленно увидит все изменения на своей стороне отображения (используя адрес, полученный с помощью системного вызова mmap()).

person Tsyvarev    schedule 04.04.2016
comment
Это, возможно, «правильный» способ делать вещи. Он работает с дизайном ядра, а не против него. - person tangrs; 05.04.2016

Архитектура Unix не одобряет подпрограммы прерывания, обращающиеся к пользовательскому пространству, потому что процесс может (теоретически) быть выгружен при возникновении прерывания. Если процесс выполняется на другом процессоре, это тоже может быть проблемой. Я предлагаю вам написать ioctl для синхронизации счетчиков, а затем пусть процесс будет вызывать этот ioctl каждый раз, когда ему потребуется доступ к счетчикам.

person G-Man Says 'Reinstate Monica'    schedule 04.04.2016
comment
Он предназначен для работы только на архитектурах с одним процессором. Во втором подходе, как упоминалось в моем вопросе, copy_to_user() не работает. Однако я с удивлением обнаружил, что если я разыменовываю виртуальный адрес и записываю его непосредственно из моего модуля драйвера, когда последним контекстом процесса перед прерыванием был мой пользовательский процесс, счетчик корректно обновляется в пользовательском пространстве. Я полагаю, что это не отказоустойчиво, так как могут быть потенциальные сбои страниц? - person Anirban Ghoshal; 04.04.2016

Вне контекста прерывания ваш драйвер должен будет проверить, доступна ли пользовательская память (используя access_ok), и закрепить пользовательскую память, используя get_user_pages или get_user_pages_fast (после определения смещения страницы начала закрепляемой области и числа страниц, охватываемых закрепляемой областью, включая выравнивание страниц по обоим концам). Также потребуется сопоставить список страниц с адресным пространством ядра, используя vmap. Адрес возврата из vmap плюс смещение начала региона на его странице дадут вам адрес, к которому может получить доступ ваш обработчик прерываний.

В какой-то момент вы захотите прекратить доступ к пользовательской памяти, что будет включать в себя обеспечение того, чтобы ваша процедура прерывания больше не обращалась к ней, вызов vunmap (передача указателя, возвращенного vmap) и последовательность вызовов put_page для каждого страниц, закрепленных get_user_pages или get_user_pages_fast.

person Ian Abbott    schedule 04.04.2016

Я не думаю, что то, что вы пытаетесь сделать, возможно. Рассмотрим эту ситуацию: (при условии, как работает ваше устройство)

  1. Некоторая функция выделяет память пользовательского пространства для счетчиков и предоставляет их адрес в PROCESS X.

  2. Происходит переключение и выполняется ПРОЦЕСС Y.

  3. Ваше устройство прерывает.

Адрес ваших счетчиков недоступен.

Вам необходимо запланировать асинхронное событие режима ядра (нижняя половина), которое будет выполняться при выполнении PROCESS X.

person user3344003    schedule 05.04.2016
comment
В контексте прерывания я как бы проверяю, является ли current == my_task, где my_task ссылкой на структуру задачи моего пользовательского процесса. Я обновляю память пользовательского пространства тогда и только тогда, когда проверка возвращает true. Разве это не означает, что сопоставления VA находятся в контексте моего пользовательского процесса, когда устройство прерывается? - person Anirban Ghoshal; 05.04.2016
comment
Что, если после этой проверки и до обновления произойдет изменение контекста? Вы можете заблокировать все прерывания? - person user3344003; 07.04.2016
comment
У меня есть preempt_disable() до этой проверки, но я не знаю, достаточно ли этого. - person Anirban Ghoshal; 08.04.2016