Я новичёк в режиме ядра и я взял пример chrdev модуля Linux, работает нормально.
При чтении устройста данные пользователю отправляет через put_user, в функции
static ssize_t device_read(struct file *flip, char *buffer, size_t len, loff_t *offset)
если я в этой функции попытаюсь записать или прочитать из buffer, то получаю ошибку в ядре,
хотя я проверил: указатель buffer валидный и он одинаковый и в режиме ядра и в режиме пользователя.
перед вызовом read() в режиме ядра буфер проинициализировал.
Сначала я подумал, что модуль работает не в 0-кольце, но проверил два младших бита регистра CS, всё в порядке, модуль в 0 кольце, программа в 3-м кольце.
В чём может быть дело ? Почему в режиме ядра я не могу напрямую обращаться к памяти пользовательского режима ?
Здравствуйте, maks1180, Вы писали:
M>Я новичёк в режиме ядра и я взял пример chrdev модуля Linux, работает нормально. M>При чтении устройста данные пользователю отправляет через put_user, в функции M>static ssize_t device_read(struct file *flip, char *buffer, size_t len, loff_t *offset)
Непонятно. device_read есть только в fs/dlm/user.c, но там нет put_user.
M>если я в этой функции попытаюсь записать или прочитать из buffer, то получаю ошибку в ядре, M>хотя я проверил: указатель buffer валидный и он одинаковый и в режиме ядра и в режиме пользователя. M>перед вызовом read() в режиме ядра буфер проинициализировал.
Проинициализировал как? Нулями не считается. Запиши туда ненули, а потом что там тебе нужно. Я всю последнюю фразу не понял Это вызов в режиме ядра, или инициализировал в режиме ядра?
M>Сначала я подумал, что модуль работает не в 0-кольце, но проверил два младших бита регистра CS, всё в порядке, модуль в 0 кольце, программа в 3-м кольце. M>В чём может быть дело ? Почему в режиме ядра я не могу напрямую обращаться к памяти пользовательского режима ?
Можешь, только памяти может не быть на месте (не выделена ещё или в свопе), и тогда будет page fault, который окей если это пользовательская память (тогда ядро вытащит страницу из свопа или наконец выделит память, и повторит чтение), или не окей, если это память самого ядра — тогда будет краш. Эти все put_user() — они чтоб ядро различало чья это память. Можно попробовать перед чтением вызвать get_user_pages_fast() (а потом не забыть put_page()).
Здравствуйте, aik, Вы писали:
aik>Можешь, только памяти может не быть на месте (не выделена ещё или в свопе), и тогда будет page fault, который окей если это пользовательская память (тогда ядро вытащит страницу из свопа или наконец выделит память, и повторит чтение), или не окей, если это память самого ядра — тогда будет краш. Эти все put_user() — они чтоб ядро различало чья это память. Можно попробовать перед чтением вызвать get_user_pages_fast() (а потом не забыть put_page()).
put_user() — это такой специальный memcpy, у которого адрес начала и конца кода лежит в специальной отдельной секции. И если происходит исключение, связанное с обращением к памяти, ядро проверяет, часом не из put_user() ли оно прилетело. И если оттуда, относится к нему особым образом.
Здравствуйте, Евгений Музыченко, Вы писали:
Pzz>>если происходит исключение, связанное с обращением к памяти, ядро проверяет
ЕМ>А самостоятельно обработать исключение, как в винде, модуль ядра может?
Не знаю. Вроде нет. Никогда не было причин разбираться.
Здравствуйте, Pzz, Вы писали:
ЕМ>>А самостоятельно обработать исключение, как в винде, модуль ядра может?
Pzz>Не знаю. Вроде нет. Никогда не было причин разбираться.
Когда процесс обменивается с модулем ядра относительно небольшими порциями данных, нет проблем использовать промежуточную буферизацию. А когда идет какой-нибудь аудио/видеопоток, уже разбитый на достаточно большие порции, и нужно его не просто копировать, а разбирать/формировать на ходу, то промежуточное порционное копирование может заметно усложнить обработку. Возможность перехватить исключение процессора тут довольно удобна.
Здравствуйте, Евгений Музыченко, Вы писали:
Pzz>>Не знаю. Вроде нет. Никогда не было причин разбираться.
ЕМ>Когда процесс обменивается с модулем ядра относительно небольшими порциями данных, нет проблем использовать промежуточную буферизацию. А когда идет какой-нибудь аудио/видеопоток, уже разбитый на достаточно большие порции, и нужно его не просто копировать, а разбирать/формировать на ходу, то промежуточное порционное копирование может заметно усложнить обработку. Возможность перехватить исключение процессора тут довольно удобна.
В UNIX когда процесс делает write(), он переходит в режим ядра, оставаясь на контексте процесса, и адрес буфера, переданный write(), остается валидным в режиме ядра (с той только оговоркой, что переданный адрес может и в user space быть невалидным, и в отличии от процесса, ядру при этом надо не падать, а возвращать ошибку).
Я не очень понимаю, как возможность обработки исключений в драйвере избавляет от копирований. Во-первых, пользовательская память (ее адреса) привязаны к процессу и перестают быть валидными при переключении текущего процесса. Т.е., надо или лочить процесс, или переходить в физические адреса (т.е., постоянно надрючивать memory mapping, а это очень дорого на современных CPU) или все равно копировать.
Во-вторых, самые "агрессивные" потребители больших потоков данных, а именно, сеть и дисковая подсистема, в любом случае вынуждены делать это лишнее копирование. На их фоне видео не создает столько потока, и вообще, обработка видеопотока — штука тяжелая, и мне не очень верится, что копирование вносит заметную лепту в эту нагрузку, а аудиопоток, по сравнению с другими, оперирует совсем уж какими-то копеешными объемами данных.
Здравствуйте, Pzz, Вы писали:
Pzz>В UNIX когда процесс делает write(), он переходит в режим ядра, оставаясь на контексте процесса, и адрес буфера, переданный write(), остается валидным в режиме ядра (с той только оговоркой, что переданный адрес может и в user space быть невалидным, и в отличии от процесса, ядру при этом надо не падать, а возвращать ошибку).
Именно.
Pzz>Я не очень понимаю, как возможность обработки исключений в драйвере избавляет от копирований.
Полностью от копирований оно не избавляет, конечно. Если драйверу нужно тупо передать порцию данных туда-сюда, то проще воспользоваться копированием. Если же, например, от процесса идет поток данных сложной структуры (тот же видео/аудиопоток), его может быть нужно раскладывать по разным областям памяти устройства, или особым образом обрабатывать некоторые кадры. Возможность делать это, имея непосредственный доступ к памяти процесса, может заметно облегчить/ускорить обработку.
Pzz>пользовательская память (ее адреса) привязаны к процессу и перестают быть валидными при переключении текущего процесса. Т.е., надо или лочить процесс, или переходить в физические адреса
Зачем "лочить процесс", если можно фиксировать только нужную область памяти? И это нужно только при асинхронном доступе из драйвера. При синхронном (вызвали драйвер, передали порцию, он обработал и вернулся без запуска асинхронного доступа) драйверу проще работать с памятью процесса, как самому процессу.
Pzz>обработка видеопотока — штука тяжелая
Если математическая, то да. Но нередко нужно не преобразовывать картинку, а только перегруппировать данные в потоке.
Pzz>мне не очень верится, что копирование вносит заметную лепту в эту нагрузку, а аудиопоток, по сравнению с другими, оперирует совсем уж какими-то копеешными объемами данных.
Такая ситуация была не всегда, На заре и линуксов, и unix'ов вообще, почти любая поточная передача в реальном времени считалась серьезной нагрузкой, и ее оптимизировали, как могли. Все эти схемы взаимодействия процесса с драйвером появились еще тогда.
Здравствуйте, Евгений Музыченко, Вы писали:
ЕМ>Когда процесс обменивается с модулем ядра относительно небольшими порциями данных, нет проблем использовать промежуточную буферизацию. А когда идет какой-нибудь аудио/видеопоток, уже разбитый на достаточно большие порции, и нужно его не просто копировать, а разбирать/формировать на ходу, то промежуточное порционное копирование может заметно усложнить обработку. Возможность перехватить исключение процессора тут довольно удобна.
Процесс же может замапить память из драйвера и писать/читать оттуда/туда. Драйвер вызовут для mmap(), он там выделит и замапит что надо для DMA и привет — ни исключений, ни копирований. Или драйвер подпишется на page fault, если сразу не захочет всё выделять.
Здравствуйте, Евгений Музыченко, Вы писали:
Pzz>>Я не очень понимаю, как возможность обработки исключений в драйвере избавляет от копирований.
ЕМ>Полностью от копирований оно не избавляет, конечно. Если драйверу нужно тупо передать порцию данных туда-сюда, то проще воспользоваться копированием. Если же, например, от процесса идет поток данных сложной структуры (тот же видео/аудиопоток), его может быть нужно раскладывать по разным областям памяти устройства, или особым образом обрабатывать некоторые кадры. Возможность делать это, имея непосредственный доступ к памяти процесса, может заметно облегчить/ускорить обработку.
Ну и пожалуйста. В UNIX на контексте условного write() все то же самое.
Или хочется прям по структурам в пользовательской памяти указателем полазить? IMHO, это — стрёмная практика.
ЕМ>Зачем "лочить процесс", если можно фиксировать только нужную область памяти? И это нужно только при асинхронном доступе из драйвера. При синхронном (вызвали драйвер, передали порцию, он обработал и вернулся без запуска асинхронного доступа) драйверу проще работать с памятью процесса, как самому процессу.
Ты не можешь просто так взять и зафиксировать нужную область памяти, при переключении контекстов она просто выйдет из области видимости.
Но пока ты на контексте системного вызова, ты остаешься на контексте вызвавшего процесса.
Pzz>>обработка видеопотока — штука тяжелая
ЕМ>Если математическая, то да. Но нередко нужно не преобразовывать картинку, а только перегруппировать данные в потоке.
Все равно заголовки всякие там разбирать. Дело небыстрое.
Pzz>>мне не очень верится, что копирование вносит заметную лепту в эту нагрузку, а аудиопоток, по сравнению с другими, оперирует совсем уж какими-то копеешными объемами данных.
ЕМ>Такая ситуация была не всегда, На заре и линуксов, и unix'ов вообще, почти любая поточная передача в реальном времени считалась серьезной нагрузкой, и ее оптимизировали, как могли. Все эти схемы взаимодействия процесса с драйвером появились еще тогда.
Знаешь, на заре UNIX-ов вообще (а так же Windows NT вообще), UNIX-овская простая и уютная, как домашние тапочки, модель ввода-вывода была прям ощутимо эффективнее вендовой.
Все эти IRP-ы и MDL-и, ну, они ребята недешевые.
Сейчас, конечно, появились всякие там более навороченные механизмы, про которые я не очень в курсе, потому, что давно драйверами не занимался. Но они факультативные, простому советскому драйверу про них знать не обязательно. Они становятся важными, когда у тебя железо ввода-вывода умеет прямой доступ прямо в пользовательскую память, чтобы совсем уже избежать копирования. Но это совсем не всякое железо так умеет.
Здравствуйте, aik, Вы писали:
aik>Процесс же может замапить память из драйвера и писать/читать оттуда/туда.
Это используется прежде всего для асинхронного обмена. При синхронном обычно удобнее использовать обычную память.
aik>Или драйвер подпишется на page fault
Вопрос о том и был, может ли ядерный модуль в линуксе подписаться на обработку исключений.
Здравствуйте, Pzz, Вы писали:
Pzz>В UNIX на контексте условного write() все то же самое.
Каким образом контекст, как таковой, может обеспечить такое? В нем непременно есть точка входа по исключению?
Pzz>Или хочется прям по структурам в пользовательской памяти указателем полазить?
Бывает, что и так. Например, процесс передает массив описателей, каждый из которых содержит адреса других описателей, все это нужно разобрать и обработать.
Pzz>IMHO, это — стрёмная практика.
Почему вдруг?
Pzz>Ты не можешь просто так взять и зафиксировать нужную область памяти, при переключении контекстов она просто выйдет из области видимости. Pzz>Но пока ты на контексте системного вызова, ты остаешься на контексте вызвавшего процесса.
Ну вот в это время, пока драйвер обрабатывает вызов, и не выполнил явно возврата/переключения, система может принудительно переключить контекст?
Pzz>Все равно заголовки всякие там разбирать. Дело небыстрое.
Какие там сложные заголовки в самом-то видеопотоке? Это в файле может лежать много дополнительной информации, а в самом потоке — только то, что нужно для расшифровки кадров. Если расшифровка делается программно, то дополнительное копирование действительно не критично. Если же аппаратно, то вполне может ощущаться, особенно вкупе с динамическим выделением/освобождением памяти.
Pzz>на заре UNIX-ов вообще (а так же Windows NT вообще), UNIX-овская простая и уютная, как домашние тапочки, модель ввода-вывода была прям ощутимо эффективнее вендовой.
Только на простых и линейных моделях, работавших в доверенном окружении, где все свои. А так, чтоб много параллельных процессов работало с нетривиальными моделями ввода/вывода, и при этом никто не хотел доверять другим? А если при этом нужно было встроить дополнительную обработку куда-нибудь в середину стека, а не банально поверх него?
Pzz>Все эти IRP-ы и MDL-и, ну, они ребята недешевые.
Так они все изначально про параллелизм, управляемые очереди, разделение памяти, разделение функций, безопасность, ортогональность и прочее, а родная модель ввода/вывода Unix — только про примитивный последовательный/блочный обмен.
ЕМ>Когда процесс обменивается с модулем ядра относительно небольшими порциями данных, нет проблем использовать промежуточную буферизацию. А когда идет какой-нибудь аудио/видеопоток, уже разбитый на достаточно большие порции, и нужно его не просто копировать, а разбирать/формировать на ходу, то промежуточное порционное копирование может заметно усложнить обработку. Возможность перехватить исключение процессора тут довольно удобна.