Редактирование: FreeBSD, 02 лекция (от 09 октября)
Материал из eSyr's wiki.
Внимание: Вы не представились системе. Ваш IP-адрес будет записан в историю изменений этой страницы.
Правка может быть отменена. Пожалуйста, просмотрите сравнение версий, чтобы убедиться, что это именно те изменения, которые вас интересуют, и нажмите «Записать страницу», чтобы изменения вступили в силу.
Текущая версия | Ваш текст | ||
Строка 1: | Строка 1: | ||
- | == 02.entering_kernel == | ||
Сегодня попробуем создать модуль с системным вызовом. | Сегодня попробуем создать модуль с системным вызовом. | ||
Начнем очень издалека. Что такое процессор? Это своего рода машина Тьюринга. Совокупность всех регистров есть внутреннее состояние процессора. | Начнем очень издалека. Что такое процессор? Это своего рода машина Тьюринга. Совокупность всех регистров есть внутреннее состояние процессора. | ||
- | Первые компьютеры называли вычислителями. Они шелестели перфокартами, ни на что не реагировали, и, посчитав, выдавали результат. В изначальной формулировке машины Тьюринга останов -- это очень хорошо, это значит | + | Первые компьютеры называли вычислителями. Они шелестели перфокартами, ни на что не реагировали, и, посчитав, выдавали результат. В изначальной формулировке машины Тьюринга останов -- это очень хорошо, это |
+ | значит мы получили результат. А мы хотим, чтобы машина реагировала на события. Это значит, мы хотим прерывать работу машины Тьюринга. Так возникло понятие аппаратного прерывания. Работа машины прерывается, | ||
+ | выполняется обработчик прерывания, потом работа продолжается. | ||
Как это происходит? | Как это происходит? | ||
Строка 10: | Строка 11: | ||
Что будет, если обработчик прерывания настолько сложен, что вызывает другие функции и даже не знает, какие регистры будет менять? В таких случаях можно либо сохранить все регистры на стеке, или использовать | Что будет, если обработчик прерывания настолько сложен, что вызывает другие функции и даже не знает, какие регистры будет менять? В таких случаях можно либо сохранить все регистры на стеке, или использовать | ||
- | специальные аппаратные функции сохранения контекста. Такая процедура называется context switch. Почитать | + | специальные аппаратные функции сохранения контекста. Такая процедура называется context switch. Почитать user/src/sys/i386/i386/switch.c (для amd64 см. слайды). |
- | Что будет если мы, заходя в прерывание, сохраним контекст, а выходя -- загрузим другой? Тогда у нас будет работать другой процесс. Мы сэмулировали многозадачность. Получается, если в машине есть хотя бы прерывание таймера, то мы можем реализовать многозадачную ОС. | + | Что будет если мы, заходя в прерывание, сохраним контекст, а выходя -- загрузим другой? Тогда у нас будет работать другой процесс. Мы сэмулировали многозадачность. Получается, если в машине есть хотя бы прерывание |
+ | таймера, то мы можем реализовать многозадачную ОС. | ||
Стало понятно, что концепция прерывания очень важна. Какие бывают прерывания? | Стало понятно, что концепция прерывания очень важна. Какие бывают прерывания? | ||
- | + | 1. аппаратные -- с клавиатуры, видеокарты, i/o ready, i/o done. timer tick . Они абсолютно асинхронны, никак не связаны с тем, что машина делала. Произошло что-то внешне и прервало процесс. Они involuntary, то | |
- | + | есть не по желанию процесса. Процесс может их ждать, но не может их вызвать. | |
- | + | 2. trap(sync, involuntary). Невыполнимая инструкция. Попытка выполнить привелиированную инструкцию, деление на ноль, обращение к недоступной памяти. Связано с тем, что машина только что делала. Обработчик | |
+ | может посмотреть в стек и понять, что вызвало проблему. Если приложение обращается к несуществующей странице, мы попадаем в ядро, и ядро может что-нибудь с этим сделать, чтобы приложению стало лучше. | ||
+ | 3. software interrupt -- запрошенное приложением прерывание. В принципе, называть его прерыванием не совсем верно -- никто не прерывал программу, она сама попросила вызвать внешний код. Фактически это вызов | ||
+ | привелигированной процедуры. | ||
=== Режимы === | === Режимы === | ||
- | Изначально было 4, но все | + | Изначально было 4, но все ос используют сейчас только самый нижний и самый верхний -- ring 3 и ring 0. Когда 386 процессор загружается, он сначала эмулирует 86, переводит его в протектед мод и оказывается в |
+ | ринг0. Официально шлюз в ринг0 это прерывание и возврат из прерывания. Как же нам вернуться? Подготавливается хитрый контекст, что как будто бы мы в нем выполнялись, и давайте в него вернемся. В ринг0 | ||
+ | выполняется ядро, в ринг 3 приложение. Юзерланд может прийти в ядро через трап или инт, обратно через айрет. | ||
- | Современные | + | Современные ос практически не пользуются софтварными прерываниями, используются 80 прерывание для входа в ядро. |
- | Сохранение контекста и прочее в таком | + | Сохранение контекста и прочее в таком случчае делалось вручную, поэтому были придуманы новые способы. callgates -- из любого ринга в любогой. потом появилось syscall которая длеает львиную долю работы и вход |
+ | через прерывания ушел в прошлое и современные ос инструкцию инт практически не выполняют. Сискалл можно рассматривать как софтварное прерывание но с кучей дополнительной функциональности. | ||
- | Системный вызов это договоренность между приложением и ядром, причем очень строгая. Приложение кладет в регистры номер системного вызова и аргументы и вызывает | + | Системный вызов это договоренность между приложением и ядром, причем очень строгая. Приложение кладет в регистры номер системного вызова и аргументы и вызывает сискалл. |
- | Ядро читает это все из регистра, делает процессинг, возможно большой. Когда | + | Ядро читает это все из регистра, делает процессинг, возможно большой. Когда сискал закончен ядро кладет в регистр возвращаемое значение, в память приложения результат и возвращает выполнение приложениюю. С |
+ | этого места приложение начинает выполняться в непривелигированном режиме. Для програмиста все выглядело как вызов функции. Посмотреть на это сос стороны юзерланда можно в либц, со стороны ядра sys/syscall.S | ||
- | Сделаем иллюстрацию. Выполним ping www.ru. И с помощью | + | Сделаем иллюстрацию. Выполним ping www.ru. И с помощью гдб посмотрим бэктрейс. Сначала идет gethostbyname2 , потом берется dtrace и просим оттрейсить ether_output от ping. |
- | Важно здесь увидеть то, что у нас есть стек приложения и стек в ядре. ассоциированный с этим приложением. Сисколл выполняется в контексте софтварного прерывания, хотя кроме умозрительной никакой связи между этими стеками нет, потому что один в юзерланд памяти, другой в ядерной. | + | Важно здесь увидеть то, что у нас есть стек приложения и стек в ядре. ассоциированный с этим приложением. Сисколл выполняется в контексте софтварного прерывания, хотя кроме умозрительной никакой связи между |
+ | этими стеками нет, потому что один в юзерланд памяти, другой в ядерной. | ||
- | У каждого треда в юзерланд процессе должен быть свой ядерный стек, и эти страницы нельзя положить в своп (как вообще ядерную память), поэтому мы не можем дать треду более двух страниц, потому что иначе было бы слишком много ядерной памяти, которая фактически бы очень редко использовалась. | + | У каждого треда в юзерланд процессе должен быть свой ядерный стек, и эти страницы нельзя положить в своп (как вообще ядерную память), поэтому мы не можем дать треду более двух страниц, потому что иначе было бы |
+ | слишком много ядерной памяти, которая фактически бы очень редко использовалась. | ||
- | Есть утилита, которая позволяет трейсить системные вызовы, которые делало приложение. Мы ее запускаем для определенного приложения, в ядре на него ставится флажок и ядро начинает писать в файл всю его активность. Можно удивиться, сколько простая программа делает системных вызовов.. Поскольку много кто слинкован динамически, делается куча ммапов на | + | Есть утилита, которая позволяет трейсить системные вызовы, которые делало приложение. Мы ее запускаем для определенного приложения, в ядре на него ставится флажок и ядро начинает писать в файл всю его |
+ | активность. Можно удивиться, сколько простая программа делает системных вызовов.. Поскольку много кто слинкован динамически, делается куча ммапов на страте приложения. | ||
- | Иногда для того чтобы было понятно ядро умозрительно разделяют на нижнюю и верхнюю часть, но в фрибсд вы этого практически не встретите. Есть последовательности вызова процедур от драйвера к юзерланду и пути в обратную сторону, из | + | Иногда для того чтобы было понятно ядро умозрительно разделяют на нижнюю и верхнюю часть, но в фрибсд вы этого практически не встретите, разделение умозрительное. Есть последовательности вызова процедур от |
+ | драйвера к юзерланду и пути в обратную сторону, из юзерланда вниз. | ||
Есть некоторые структуры в ядре, разные для разных подсистем (сокетов, дисковой подсистемы, итд) и есть структуры которые доступны как тредам из софтварными интерраптов, так и из хардварных интерраптов. | Есть некоторые структуры в ядре, разные для разных подсистем (сокетов, дисковой подсистемы, итд) и есть структуры которые доступны как тредам из софтварными интерраптов, так и из хардварных интерраптов. | ||
+ | Типичный процесс получения данных -- приходит пакет, ядро запускает обработчик прерывания. оно может обработать его в контексте прерывания, а может пометить как требующий обработки тред и вернуться, чтобы | ||
+ | планировщик решал когда обработать, выйдет из прерывания, пойдет по тцпип стеку, дойдет до приложения, поставит ему флажок что его надо разбудить ему пришли данные и закончитсяю. Разбуженное приложение пойдет | ||
+ | снова вниз в ядро и достанет данные. Для оптимизации может случаться что вниз вызов сразу идет в самый низкий уровень, а обратно так невозможно. Нельзя из ядра подняться на самый верхний уровень. Мы пишем мейн | ||
+ | и не можем допустить, чтобы он вдруг стал выполняться с совершенно другого места. Кроме случая обработки сигналов. Когда ядро посылает сигнал приложение внезапно выполняет обработчик сигнала. | ||
- | + | Никто не говорит что обработчик прерывания должен как-тоотвечать тому, что хочет юзерланд, он может заниматься чисто ядерными вещами (например, таймер управляет шедулерами и ничего приложению не пердает). | |
- | + | ||
- | + | ||
- | Никто не говорит | + | |
Как добавляется сисколл во фрибсд? | Как добавляется сисколл во фрибсд? | ||
- | + | cd /usr/src/sys/kern/ | |
- | + | ||
+ | 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 | ||
+ | |||
+ | генерирует syscall.master для различных систем. Работает линуксовая совместимость -- роутинг сисколлов из одной таблицы в другую, это именно байнари совместимость, а не эмуляция. | ||
Переходим к практике. | Переходим к практике. | ||
- | Динамическое добавление сисколла. | ||
- | * SYSCALL_MODULE(9) | ||
- | * module(9) | ||
- | * modstat(2) | ||
- | * syscall(2) | ||
- | + | man SYSCALL_MODULE | |
- | + | ||
- | + | man module | |
- | + | man modstat | |
- | + | man syscall | |
- | При этом в ядре нам тоже может потребоваться память. Как она аллоциируется? Похоже на юзерлэнд, но с некоторыми тонкостями. malloc(9). Принимает три аргумента, а не один, как в юзерланде. Во-первых, у памяти есть задаваемый макросом тип, который нужен для дебага и ситемного администрирования. Можно у ядра запросить, сколько памяти какого типа аллоцировано. С помощью этого также можно понять какой модуль виноват в утечке памяти. MALLOC_DEFINE - создание собственного типа. | ||
- | + | Параметры сисколла должны быть выравнены по длине машинного слова. Соответственно если я делаю сисколл в котором будет два парметра, | |
+ | это значит что ему будут переданы два машинных слова и для нашего сисколла первый парметр был инт, а второй указатель на войд, и нам надо еще учесть литлендиан или бигендиан. Вэто все можно не вдаваться, потому | ||
+ | что все генерируется. | ||
- | + | Сначала используем макрос SYSCALL_MODULE/. Он генерирует sysent в которой есть число агрументов и указател и на них. | |
- | + | Мы можем обратиться из сисколла к памяти процесса, но если окажется, что этот кусок памят с свопе, то будет очень печально. Правильный способ man 9 copy -- copy-in и copy-out. | |
- | + | ||
+ | При этом в ядре нам тоже может потребоваться память. Как она аллоциируется? Похоже на юзерлэнд но с некоторыми тонкостями. malloc(9). Принимает три аргумента, а не один, как в юзерланде. Во-первых у памяти | ||
+ | есть задаваемый макросом тип, который нужен для дебага и ситемного администрирования. Можно у ядра запросить сколько памяти какого типа аллоцировано. | ||
+ | |||
+ | С помощью этого также можно понять какой модуль виноват в утечке памяти. | ||
+ | |||
+ | Флаги же показывают принципиальное олтчие от юзерлэнда. M_NOWAIT и M_WAITOK. в юзерлэнде маллок никогда не вовращает нулл, он блокируется и ждет, пока память появится. В ядре такой подход допстим далеко не | ||
+ | всегда, поэтому если вы пишите новайт, надо всегда проверять, что маллок вернул не ноль. | ||
+ | |||
+ | Возьмем наш модуль и сделаем так, чтобы он аллоцировал память. | ||
+ | |||
+ | MALLOC_DEFINE создание собственного типа. | ||
+ | |||
+ | vmstat -m выдает таблицу выделения памяти. |