Редактирование: FreeBSD, 02 лекция (от 09 октября)

Материал из eSyr's wiki.

Перейти к: навигация, поиск

Внимание: Вы не представились системе. Ваш IP-адрес будет записан в историю изменений этой страницы.

Правка может быть отменена. Пожалуйста, просмотрите сравнение версий, чтобы убедиться, что это именно те изменения, которые вас интересуют, и нажмите «Записать страницу», чтобы изменения вступили в силу.

Текущая версия Ваш текст
Строка 1: Строка 1:
-
== 02.entering_kernel ==
 
Сегодня попробуем создать модуль с системным вызовом.
Сегодня попробуем создать модуль с системным вызовом.
Начнем очень издалека. Что такое процессор? Это своего рода машина Тьюринга. Совокупность всех регистров есть внутреннее состояние процессора.
Начнем очень издалека. Что такое процессор? Это своего рода машина Тьюринга. Совокупность всех регистров есть внутреннее состояние процессора.
-
Первые компьютеры называли вычислителями. Они шелестели перфокартами, ни на что не реагировали, и, посчитав, выдавали результат. В изначальной формулировке машины Тьюринга останов -- это очень хорошо, это значит, мы получили результат. А мы хотим, чтобы машина реагировала на события. Это значит, мы хотим прерывать работу машины Тьюринга. Так возникло понятие аппаратного прерывания. Работа машины прерывается, выполняется обработчик прерывания, потом работа продолжается.
+
Первые компьютеры называли вычислителями. Они шелестели перфокартами, ни на что не реагировали, и, посчитав, выдавали результат. В изначальной формулировке машины Тьюринга останов -- это очень хорошо, это
 +
значит мы получили результат. А мы хотим, чтобы машина реагировала на события. Это значит, мы хотим прерывать работу машины Тьюринга. Так возникло понятие аппаратного прерывания. Работа машины прерывается,
 +
выполняется обработчик прерывания, потом работа продолжается.
Как это происходит?
Как это происходит?
Строка 10: Строка 11:
Что будет, если обработчик прерывания настолько сложен, что вызывает другие функции и даже не знает, какие регистры будет менять? В таких случаях можно либо сохранить все регистры на стеке, или использовать
Что будет, если обработчик прерывания настолько сложен, что вызывает другие функции и даже не знает, какие регистры будет менять? В таких случаях можно либо сохранить все регистры на стеке, или использовать
-
специальные аппаратные функции сохранения контекста. Такая процедура называется context switch. Почитать /usr/src/sys/i386/i386/switch.c /usr/src/sys/amd64/amd64/cpu_switch.S
+
специальные аппаратные функции сохранения контекста. Такая процедура называется context switch. Почитать user/src/sys/i386/i386/switch.c (для amd64 см. слайды).
-
Что будет если мы, заходя в прерывание, сохраним контекст, а выходя -- загрузим другой? Тогда у нас будет работать другой процесс. Мы сэмулировали многозадачность. Получается, если в машине есть хотя бы прерывание таймера, то мы можем реализовать многозадачную ОС.
+
Что будет если мы, заходя в прерывание, сохраним контекст, а выходя -- загрузим другой? Тогда у нас будет работать другой процесс. Мы сэмулировали многозадачность. Получается, если в машине есть хотя бы прерывание
 +
таймера, то мы можем реализовать многозадачную ОС.
Стало понятно, что концепция прерывания очень важна. Какие бывают прерывания?
Стало понятно, что концепция прерывания очень важна. Какие бывают прерывания?
-
# аппаратные -- с клавиатуры, видеокарты, i/o ready, i/o done. timer tick . Они абсолютно асинхронны, никак не связаны с тем, что машина делала. Произошло что-то внешне и прервало процесс. Они involuntary, то есть не по желанию процесса. Процесс может их ждать, но не может их вызвать.
+
1. аппаратные -- с клавиатуры, видеокарты, i/o ready, i/o done. timer tick . Они абсолютно асинхронны, никак не связаны с тем, что машина делала. Произошло что-то внешне и прервало процесс. Они involuntary, то
-
# trap (sync, involuntary). Невыполнимая инструкция. Попытка выполнить привелиированную инструкцию, деление на ноль, обращение к недоступной памяти. Связано с тем, что машина только что делала. Обработчик может посмотреть в стек и понять, что вызвало проблему. Если приложение обращается к несуществующей странице, мы попадаем в ядро, и ядро может что-нибудь с этим сделать, чтобы приложению стало лучше.
+
есть не по желанию процесса. Процесс может их ждать, но не может их вызвать.
-
# software interrupt -- запрошенное приложением прерывание. В принципе, называть его прерыванием не совсем верно -- никто не прерывал программу, она сама попросила вызвать внешний код. Фактически это вызов привелигированной процедуры.
+
2. trap(sync, involuntary). Невыполнимая инструкция. Попытка выполнить привелиированную инструкцию, деление на ноль, обращение к недоступной памяти. Связано с тем, что машина только что делала. Обработчик
 +
может посмотреть в стек и понять, что вызвало проблему. Если приложение обращается к несуществующей странице, мы попадаем в ядро, и ядро может что-нибудь с этим сделать, чтобы приложению стало лучше.
 +
3. software interrupt -- запрошенное приложением прерывание. В принципе, называть его прерыванием не совсем верно -- никто не прерывал программу, она сама попросила вызвать внешний код. Фактически это вызов
 +
привелигированной процедуры.
=== Режимы ===
=== Режимы ===
-
Изначально было 4, но все ОС используют сейчас только самый нижний и самый верхний -- ring 3 и ring 0. Когда 386 процессор загружается, он сначала эмулирует 86, переводит его в protected mode и оказывается в ring 0. Официально шлюз в ring 0 -- это прерывание и возврат из прерывания. Как же нам вернуться? Подготавливается хитрый контекст, что как будто бы мы в нем выполнялись, и давайте в него вернемся. В ring 0 выполняется ядро, в ring 3 приложение. Юзерланд может прийти в ядро через trap или int, обратно через iret.
+
Изначально было 4, но все ос используют сейчас только самый нижний и самый верхний -- ring 3 и ring 0. Когда 386 процессор загружается, он сначала эмулирует 86, переводит его в протектед мод и оказывается в
 +
ринг0. Официально шлюз в ринг0 это прерывание и возврат из прерывания. Как же нам вернуться? Подготавливается хитрый контекст, что как будто бы мы в нем выполнялись, и давайте в него вернемся. В ринг0
 +
выполняется ядро, в ринг 3 приложение. Юзерланд может прийти в ядро через трап или инт, обратно через айрет.
-
Современные ОС практически не пользуются софтварными прерываниями, используется 0x80 прерывание (i386) / syscall (amd64) для входа в ядро.
+
Современные ос практически не пользуются софтварными прерываниями, используются 80 прерывание для входа в ядро.
-
Сохранение контекста и прочее в таком случае делалось вручную, поэтому были придуманы новые способы. callgates -- из любого ринга в любой. потом появилось syscall, которая делает львиную долю работы и вход через прерывания ушел в прошлое и современные ос инструкцию int практически не выполняют. syscall можно рассматривать как софтварное прерывание, но с кучей дополнительной функциональности.
+
Сохранение контекста и прочее в таком случчае делалось вручную, поэтому были придуманы новые способы. callgates -- из любого ринга в любогой. потом появилось syscall которая длеает львиную долю работы и вход
 +
через прерывания ушел в прошлое и современные ос инструкцию инт практически не выполняют. Сискалл можно рассматривать как софтварное прерывание но с кучей дополнительной функциональности.
-
Системный вызов это договоренность между приложением и ядром, причем очень строгая. Приложение кладет в регистры номер системного вызова и аргументы и вызывает сисколл.
+
Системный вызов это договоренность между приложением и ядром, причем очень строгая. Приложение кладет в регистры номер системного вызова и аргументы и вызывает сискалл.
-
Ядро читает это все из регистра, делает процессинг, возможно большой. Когда сискол закончен, ядро кладет в регистр возвращаемое значение, в память приложения результат и возвращает выполнение приложению. С этого места приложение начинает выполняться в непривелигированном режиме. Для програмиста все выглядело как вызов функции. Посмотреть на это со стороны юзерланда можно в libc, со стороны ядра - /usr/src/sys/i386/i386/trap.c , /usr/src/sys/amd64/amd64/trap.c
+
Ядро читает это все из регистра, делает процессинг, возможно большой. Когда сискал закончен ядро кладет в регистр возвращаемое значение, в память приложения результат и возвращает выполнение приложениюю. С
 +
этого места приложение начинает выполняться в непривелигированном режиме. Для програмиста все выглядело как вызов функции. Посмотреть на это сос стороны юзерланда можно в либц, со стороны ядра sys/syscall.S
-
Сделаем иллюстрацию. Выполним ping www.ru. И с помощью gdb посмотрим бэктрейс. Сначала идет gethostbyname2 , потом берется dtrace и просим оттрейсить ether_output от ping.
+
Сделаем иллюстрацию. Выполним ping www.ru. И с помощью гдб посмотрим бэктрейс. Сначала идет gethostbyname2 , потом берется dtrace и просим оттрейсить ether_output от ping.
-
Важно здесь увидеть то, что у нас есть стек приложения и стек в ядре. ассоциированный с этим приложением. Сисколл выполняется в контексте софтварного прерывания, хотя кроме умозрительной никакой связи между этими стеками нет, потому что один в юзерланд памяти, другой в ядерной.
+
Важно здесь увидеть то, что у нас есть стек приложения и стек в ядре. ассоциированный с этим приложением. Сисколл выполняется в контексте софтварного прерывания, хотя кроме умозрительной никакой связи между
 +
этими стеками нет, потому что один в юзерланд памяти, другой в ядерной.
-
У каждого треда в юзерланд процессе должен быть свой ядерный стек, и эти страницы нельзя положить в своп (как вообще ядерную память), поэтому мы не можем дать треду более двух страниц, потому что иначе было бы слишком много ядерной памяти, которая фактически бы очень редко использовалась.
+
У каждого треда в юзерланд процессе должен быть свой ядерный стек, и эти страницы нельзя положить в своп (как вообще ядерную память), поэтому мы не можем дать треду более двух страниц, потому что иначе было бы
 +
слишком много ядерной памяти, которая фактически бы очень редко использовалась.
-
Есть утилита, которая позволяет трейсить системные вызовы, которые делало приложение. Мы ее запускаем для определенного приложения, в ядре на него ставится флажок и ядро начинает писать в файл всю его активность. Можно удивиться, сколько простая программа делает системных вызовов.. Поскольку много кто слинкован динамически, делается куча ммапов на старте приложения.
+
Есть утилита, которая позволяет трейсить системные вызовы, которые делало приложение. Мы ее запускаем для определенного приложения, в ядре на него ставится флажок и ядро начинает писать в файл всю его
 +
активность. Можно удивиться, сколько простая программа делает системных вызовов.. Поскольку много кто слинкован динамически, делается куча ммапов на страте приложения.
-
Иногда для того чтобы было понятно ядро умозрительно разделяют на нижнюю и верхнюю часть, но в фрибсд вы этого практически не встретите. Есть последовательности вызова процедур от драйвера к юзерланду и пути в обратную сторону, из юзерленда вниз.
+
Иногда для того чтобы было понятно ядро умозрительно разделяют на нижнюю и верхнюю часть, но в фрибсд вы этого практически не встретите, разделение умозрительное. Есть последовательности вызова процедур от
 +
драйвера к юзерланду и пути в обратную сторону, из юзерланда вниз.
Есть некоторые структуры в ядре, разные для разных подсистем (сокетов, дисковой подсистемы, итд) и есть структуры которые доступны как тредам из софтварными интерраптов, так и из хардварных интерраптов.
Есть некоторые структуры в ядре, разные для разных подсистем (сокетов, дисковой подсистемы, итд) и есть структуры которые доступны как тредам из софтварными интерраптов, так и из хардварных интерраптов.
 +
Типичный процесс получения данных -- приходит пакет, ядро запускает обработчик прерывания. оно может обработать его в контексте прерывания, а может пометить как требующий обработки тред и вернуться, чтобы
 +
планировщик решал когда обработать, выйдет из прерывания, пойдет по тцпип стеку, дойдет до приложения, поставит ему флажок что его надо разбудить ему пришли данные и закончитсяю. Разбуженное приложение пойдет
 +
снова вниз в ядро и достанет данные. Для оптимизации может случаться что вниз вызов сразу идет в самый низкий уровень, а обратно так невозможно. Нельзя из ядра подняться на самый верхний уровень. Мы пишем мейн
 +
и не можем допустить, чтобы он вдруг стал выполняться с совершенно другого места. Кроме случая обработки сигналов. Когда ядро посылает сигнал приложение внезапно выполняет обработчик сигнала.
-
Типичный процесс получения данных -- приходит пакет, ядро запускает обработчик прерывания. Оно может обработать его в контексте прерывания, а может пометить как требующий обработки тред и вернуться, чтобы планировщик решал когда обработать, выйдет из прерывания, пойдет по tcp/ip стеку, дойдет до приложения, поставит ему флажок что его надо разбудить, ему пришли данные, и закончится. Разбуженное приложение пойдет снова вниз в ядро и достанет данные. Для оптимизации может случаться, что вниз вызов сразу идет в самый низкий уровень, а обратно так невозможно. Нельзя из ядра подняться на самый верхний уровень. Мы пишем main
+
Никто не говорит что обработчик прерывания должен как-тоотвечать тому, что хочет юзерланд, он может заниматься чисто ядерными вещами (например, таймер управляет шедулерами и ничего приложению не пердает).
-
и не можем допустить, чтобы он вдруг стал выполняться с совершенно другого места. Кроме случая обработки сигналов. Когда ядро посылает сигнал, приложение внезапно выполняет обработчик сигнала.
+
-
 
+
-
Никто не говорит, что обработчик прерывания должен как-то отвечать тому, что хочет юзерланд, он может заниматься чисто ядерными вещами (например, таймер управляет шедулерами и ничего приложению не пердает).
+
Как добавляется сисколл во фрибсд?
Как добавляется сисколл во фрибсд?
-
cd /usr/src/sys/kern/
+
cd /usr/src/sys/kern/
-
vi syscalls.master
+
 
 +
vi syscalls.master
std -- входит в стандарт есть с этим номером всегда
std -- входит в стандарт есть с этим номером всегда
 +
nostd -- не входит в стандарт
nostd -- не входит в стандарт
-
make sysent
+
make sysent
-
cd ../..
+
-
make buildkernel installkernel
+
-
Что делает make sysent? Генерирует /usr/include/sys/syscall.h (для юзерленда) , /usr/include/sys/sysproto.h (для ядра),
+
cd ../..
-
и генерирует syscall.master для различных систем. Работает линуксовая совместимость -- роутинг сисколлов из одной таблицы в другую, это именно байнари совместимость, а не эмуляция.
+
 
 +
make buildkernel installkernel
 +
 
 +
Что делает make sysent?
 +
генерирует /usr/include/sys/syscall.h
 +
 
 +
/usr/include/sys/sysproto.h
 +
 
 +
генерирует syscall.master для различных систем. Работает линуксовая совместимость -- роутинг сисколлов из одной таблицы в другую, это именно байнари совместимость, а не эмуляция.
Переходим к практике.
Переходим к практике.
-
Динамическое добавление сисколла.
 
-
* SYSCALL_MODULE(9)
 
-
* module(9)
 
-
* modstat(2)
 
-
* syscall(2)
 
-
svn co http://svn.freebsd.org/base/user/glebius/course
+
man SYSCALL_MODULE
-
vi course/02.entering_kernel/syscall/module/foo_syscall.c
+
 
 +
man module
 +
 
 +
man modstat
 +
 
 +
man syscall
 +
 
 +
 
 +
Параметры сисколла должны быть выравнены по длине машинного слова. Соответственно если я делаю сисколл в котором будет два парметра,
 +
это значит что ему будут переданы два машинных слова и для нашего сисколла первый парметр был инт, а второй указатель на войд, и нам надо еще учесть литлендиан или бигендиан. Вэто все можно не вдаваться, потому
 +
что все генерируется.
 +
 
 +
Сначала используем макрос SYSCALL_MODULE/. Он генерирует sysent в которой есть число агрументов и указател и на них.
 +
 
 +
Мы можем обратиться из сисколла к памяти процесса, но если окажется, что этот кусок памят с свопе, то будет очень печально. Правильный способ man 9 copy -- copy-in и copy-out.
-
Параметры сисколла должны быть выравнены по длине машинного слова. Соответственно если я делаю сисколл в котором будет два парметра, это значит что ему будут переданы два машинных слова и для нашего сисколла первый парметр был инт, а второй указатель на войд, и нам надо еще учесть литлендиан или бигендиан. В это все можно не вдаваться, потому что все генерируется.
+
При этом в ядре нам тоже может потребоваться память. Как она аллоциируется? Похоже на юзерлэнд но с некоторыми тонкостями. malloc(9). Принимает три аргумента, а не один, как в юзерланде. Во-первых у памяти
 +
есть задаваемый макросом тип, который нужен для дебага и ситемного администрирования. Можно у ядра запросить сколько памяти какого типа аллоцировано.
-
Сначала используем макрос SYSCALL_MODULE. Он генерирует struct sysent в которой есть число агрументов обрабатывающей функции и указатель на нее.
+
С помощью этого также можно понять какой модуль виноват в утечке памяти.
-
Мы можем обратиться из сисколла к памяти процесса, но если окажется, что этот кусок памяти в свопе, то будет очень печально. Правильный способ - copy(9) -- copyin и copyout.
+
Флаги же показывают принципиальное олтчие от юзерлэнда. M_NOWAIT и M_WAITOK. в юзерлэнде маллок никогда не вовращает нулл, он блокируется и ждет, пока память появится. В ядре такой подход допстим далеко не
 +
всегда, поэтому если вы пишите новайт, надо всегда проверять, что маллок вернул не ноль.
-
При этом в ядре нам тоже может потребоваться память. Как она аллоциируется? Похоже на юзерлэнд, но с некоторыми тонкостями. malloc(9). Принимает три аргумента, а не один, как в юзерланде. Во-первых, у памяти есть задаваемый макросом тип, который нужен для дебага и ситемного администрирования. Можно у ядра запросить, сколько памяти какого типа аллоцировано. С помощью этого также можно понять какой модуль виноват в утечке памяти. MALLOC_DEFINE - создание собственного типа.
+
Возьмем наш модуль и сделаем так, чтобы он аллоцировал память.
-
Флаги же показывают принципиальное отличие от юзерлэнда. M_NOWAIT и M_WAITOK. в юзерлэнде malloc никогда не вовращает NULL, он блокируется и ждет, пока память появится. В ядре такой подход допустим далеко не всегда, поэтому если вы пишите M_NOWAIT, надо всегда проверять, что маллок вернул не ноль.
+
MALLOC_DEFINE создание собственного типа.
vmstat -m выдает таблицу выделения памяти.
vmstat -m выдает таблицу выделения памяти.

Пожалуйста, обратите внимание, что все ваши добавления могут быть отредактированы или удалены другими участниками. Если вы не хотите, чтобы кто-либо изменял ваши тексты, не помещайте их сюда.
Вы также подтверждаете, что являетесь автором вносимых дополнений, или скопировали их из источника, допускающего свободное распространение и изменение своего содержимого (см. eSyr's_wiki:Авторское право).
НЕ РАЗМЕЩАЙТЕ БЕЗ РАЗРЕШЕНИЯ ОХРАНЯЕМЫЕ АВТОРСКИМ ПРАВОМ МАТЕРИАЛЫ!

Шаблоны, использованные на этой странице:

Разделы