Библиотека сайта rus-linux.net
Драйверы устройств в Linux
Авторы: Alessandro Rubini и Jonathan Corbet
Перевод: Андрей Киселёв (kis_an [at] mail.ru)
Данная книга распространяется на условиях GNU Free Documentation License. Полный текст лицензии вы сможете найти по адресу http://www.oreilly.com/catalog/linuxdrive2/chapter/licenseinfo.html .
- Содержание
- Предисловие
- Предисловие от Алессандро Рубини
- Предисловие от Джона
- Кому адресована эта книга
- Организация материала
- Дополнительные сведения
- Источники дополнительной информации
- Электронная версия книги и лицензионное соглашение
- Типографские соглашения
- Обратная связь
- Благодарности
- 1. Введение.
- 2. Сборка и запуск модулей.
- 3. ---
- 4. ---
- 5. ---
- 6. Bla-bla-bla
Предисловие
Из заглавия книги видно, что она посвящена созданию драйверов для операционной системы Linux. Сама по себе цель безусловно важна, особенно в наше время, когда поток новых устройств расширяется все больше и больше, и кто-то постоянно сталкивается с необходимостью написания драйвера для того или иного устройства. Но эта книга так же и о ядре Linux, о том, как оно работает и как его можно адаптировать под свои нужды. Linux -- это открытая операционная система и с этой книгой мы надеемся сделать её еще более доступной и открытой для большой армии разработчиков.
С момента выхода первой редакции, Linux сильно изменился. Теперь он может работать на многопроцессорных системах и поддерживает намного большее количество устройств. Внутренние программные интерфейсы изменились очень сильно. Всвязи с этим появилась вторая редакция. Она охватывает ядра серии 2.4, со всеми его нововведениями.
Надеемся, что чтение этой книги доставит вам столько же удовольствия, сколько нам доставил процесс работы над ней.
Предисловие от Алессандро Рубини
Я, как инженер-электронщик (подвид человека разумного), получаю массу удовольствия, заставляя компьютер работать с разнообразными "железками". С юных лет, когда я упражнялся еще на отцовском Apple II, я искал возможность подключить свои самосборные устройства к компьютерам с другой аппаратной архитектурой и написать для них драйверы. К сожалению, в 80-х годах прошлого века IBM-совместимые персональные компьютеры были слишком слабы, как в смысле аппаратуры, так и в смысле программной поддержки. Внутренний дизайн PC сильно уступал Apple II, а имеющаяся документация была просто ужасна. Но затем появился Linux и я решился приобрести дорогостоящий, по тем временам, PC на 386 процессоре, не приобретая при этом никакого проприетарного программного обеспечения.
В то время я уже работал с UNIX в университете и не переставал поражаться мощи этой операционной системы. Я постоянно экспериментировал с ядром Linux, установленным на моем PC и мне удалось даже написать несколько драйверов для устройств, собранных самостоятельно. Я продолжаю говорить всем: "Когда я повзрослею, я хотел бы остаться хакером", а GNU/Linux -- лучшая платформа для воплощения моих мечтаний. Правда я не знаю -- повзрослею ли я когда нибудь.
По мере развития Linux, все больше и больше людей начинают интересоваться проблемой написания драйверов. Как справедливо заметил Линус Торвальдс (Linus Torvalds): "We're back to the times when men were men and wrote their own device drivers" ("Мы возвращаемся во времена, когда мужчины были мужчинами и сами писали свои драйверы").
Начиная с 1996 года я, развлечения ради, писал свои драйверы для устройств, которые я брал на время, получал в дар или даже собирал самостоятельно. К тому времени я уже написал несколько страниц для "Kernel Hacker's Guide" Майкла Джонсона (Michael Johnson) и начал писать ряд статей, посвященных ядру, для "Linux Journal", основателем и руководителем которого был Майкл. Он же и познакомил меня с Энди Орамом (Andy Oram) из издательства O'Reilly. Энди предложил мне написать книгу, посвященную драйверам устройств и я принял это предложение.
В 1999 году стало ясно, что мне не под силу одному написать второе издание книги: в моем семействе произошло пополнение и к тому же работы у меня было слишком много, чтобы позволить себе сконцентрироваться исключительно на создании программ под GPL. К тому же ядро разрослось, стало поддерживать большее количество платформ, а API (от англ. Application Programming Interface -- Интерфейс Прикладных Программ, прим. перев.) стал более обширным и зрелым. В этот момент появился Джонатан (Jonathan) и предложил свою помощь. Он был талантлив и полон энтузиазма. Это предложение заставило меня вновь засучить рукава. Джонатан оказался очень ценным помощником, он стал главной движущей силой и вложил в эту работу намного больше чем я. Мне очень нравилось работать с ним как с профессионалом и как с человеком.
Предисловие от Джона
С Linux я начал работать с начала 1994 года. Тогда мне удалось убедить своего работодателя в необходимости приобретения ноутбука, выпускаемого компанией, которая в то время называлась Fintronic Systems. Будучи пользователем Unix с начала 80-х, я уже имел опыт работы с исходными текстами программ, поэтому Linux покорил меня сразу же. Даже тогда, в 1994 году, Linux уже был очень неплохой системой и первой, действительно свободной, среди тех, с которыми мне приходилось работать. С этого момента я потерял всяческий интерес к проприетарным системам.
В действительности я никогда не планировал заняться написанием книги о Linux. Когда начались переговоры с O'Reilly об оказании помощи в подготовке второго издания этой книги, я как раз уволился из фирмы, где проработал 18 лет, чтобы начать свой бизнес в консалтинговой компании. Для того чтобы привлечь к себе внимание, мы запустили новостной сайт Linux Weekly News, на котором, среди всего прочего, размещались материалы посвященные разработке ядра. По мере роста популярности Linux росла и популярность нашего сайта и в конечном итоге консалтинговый бизнес был заброшен.
Изначально мой интерес всегда лежал в области системного программирования. Первое время я занимался доработкой кода подсистемы виртуальной памяти в BSD Unix (это была очень тяжелая работа), писал драйверы для капризных ленточных накопителей под VAX/VMS. По прошествии времени я подошел к написанию драйверов для систем с такими именами, как Alliant, Ardent и Sun. Непосредственно перед тем как приступить к работе над этой книгой, я учавствовал в разработке системы сбора радиолокационных данных в реальном масштабе времени и занимался доработкой кода драйвера НГМД (floppy) в Linux.
Я согласился помочь с подготовкой второго издания этой книги по нескольким причинам. Это был шанс "копнуть" код ядра поглубже и помочь в этом другим. Linux всегда вызывал у меня интерес, но самое интересное в Linux -- это возможность "поиграть" с ядром. Работа с Алессандро доставила мне огромное удовольствие. Я благодарен ему за то, что он доверил мне доработку его замечательной книги. За его долготерпение ко мне. Это было славное время!
Кому адресована эта книга
С технической точки зрения эта книга предлагает практический подход к пониманию внутренней организации ядра и технических решений, выбранных разработчиками. Не смотря на это, основная цель книги состоит в том, чтобы обучить вас, как писать драйверы устройств, а так же дать краткий обзор реализации ядра.
Хотя опытные программисты смогут найти все необходимые сведения в исходных текстах ядра, тем не менее обычная книга может помочь в приобретении навыков программирования. Текст, который вы читаете, является результатом многочасовых поисков по исходным текстам ядра и мы надеемся, что эта книга того стоила.
Эта книга может оказаться источником ценной информации как для тех, кто только начинает набираться опыта, так и для технических программистов, которые ежедневно сталкиваются с аппаратурой, работающей под управлением Linux. Обратите внимание: здесь термин "аппаратура" употребляется в более широком смысле, чем просто "персональный компьютер", поскольку Linux поддерживает большое количество самых разных платформ и программная реализация ядра почти никак не привязана к какой-то конкретной платформе. Мы надеемся, что эта книга окажется полезной для тех, кто желает стать опытным программистом, разбирающимся в ядре, но не знает с чего начать
Энтузиасты найдут в этой книге достаточно пищи для ума, чтобы начать эксперименты с ядром, а возможно, впоследствие присоединиться к команде разработчиков. Эта книга не претендует на полноту охвата всего ядра, но разработчик драйверов для Linux должен знать, как работает большая часть подсистем ядра. Таким образом, она является неплохим введением в программирование ядра. Linux продолжает развиваться и в этом мире всегда найдется место для новых программистов, готовых включиться в игру.
С другой стороны, если вы хотите лишь написать драйвер для своего устройства и не желаете при этом копаться во внутреннем устройстве ядра, то эта книга достаточно хорошо разбита на отдельные части, чтобы удовлетворить ваши запросы. Если вы не желаете глубоко погружаться в детали, то можете просто пропустить сугубо технические разделы и прочитать те части книги, которые посвящены описанию прикладных интерфейсов, используемых драйверами для взаимодействия с ядром.
Основная цель книги -- рассказать о том, как правильно писать модули для ядра Linux, серии 2.4. Модуль -- это файл с объектным кодом, который может быть загружен во время исполнения, чтобы добавить новые функциональные возможности в ядро. Приводимые в книге примеры как правило работают и с ядрами версий 2.0 и 2.2, в противном случае мы будем явно указывать на имеющиеся особенности, характерные для версии 2.4.
Организация материала
Темы, рассматриваемые здесь, следуют в порядке возрастания сложности и в этом смысле книга делится на две большие части. Первая часть (главы с 1 по 10) рассказывает о различных аспектах программирования, которые вам обязательно нужно знать, чтобы написать полноценный драйвер для символьного устройства. Каждая глава обсуждает отдельную тему и в конце содержит краткий справочник по данной теме.
На протяжении всей первой части, излагаемый материал постепенно смещается c программного уровня, на уровень аппаратуры. Такая организация предусмотрена с целью дать вам возможность, насколько это возможно, проверить работу программного кода на вашем компьютере без необходимости подключения к нему дополнительных устройств. Все главы включают исходный код примеров драйверов, которые вы сможете запустить. Однако, в главах 8 и 9, мы попросим вас вставить перемычку в разъем параллельного порта для проведения тестов, но нам кажется, что эта просьба не слишком обременит вас.
Вторая часть книги описывает драйверы блочных устройств и сетевых интерфейсов, и еще глубже погружается в технические подробности. Многим из вас изучение этой части книги может не потребоваться, но мы предлагаем вам все-таки прочитать ее. Большая часть предлагаемого здесь материала, посвящена описанию принципов работы ядра Linux.
Дополнительные сведения
Чтобы усвоить материал этой книги, вы должны достаточно хорошо знать язык C. Опыт работы с операционной системой так же необходим, поскольку мы часто будем обращаться к командам Unix.
Вам не требуется иметь опыт работы с аппаратурой. Все основные понятия будут разъясняться по ходу изложения материала. Мы старались не привязывать материал книги к архитектуре PC, однако, если это будет необходимо, мы будем давать дополнительную информацию, касающуюся конкретных устройств.
Вам потребуется ряд инструментальных программных средств, необходимых для сборки ядра и вам нужно определить их версии, поскольку слишком старые версии программ могут не поддерживать необходимую функциональность, а слишком новые могут привести к сборке "битого" ядра. Как правило, инструментальные средства, входящие в состав современных дистрибутивов, работают без каких либо нареканий. Однако, уточнить требования к версиям программ вы сможете в Documentation/Changes, в дереве каталогов с исходными текстами ядра.
Источники дополнительной информации
Значительная часть сведений, приводимых в данной книге, была взята из исходных текстов ядра
и сопроводительной документации. Обратите внимание на директорию
Documentation
, в дереве каталогов с исходными текстами ядра.
Здесь вы найдете огромное количество полезной информации, включая сведения о прикладных
интерфейсах ядра -- API (в каталоге DocBook
).
В разделе "Библиография" приводится список книг по сопутствующим темам.
Немало полезной информации можно найти в Интернет. Ниже приводится список интернет-сайтов, Конечно, сайты меняются очень динамично, в то время как книги обновляются довольно редко, поэтому данный список следует расценивать как несколько устаревший.
- http://www.kernel.org
- ftp://ftp.kernel.org
Сайт команды разработчиков ядра Linux. Здесь вы всегда найдете самую последнюю версию ядра и документацию к нему. Следует отметить, что сайт ftp://ftp.kernel.org имеет много зеркал по всему миру, так что вы можете найти наиболее удобное для вас зеркало.
- http://www.linuxdoc.org
Сайт The Linux Documentation Project содержит массу интересных HOWTO. Некоторые из них носят сугубо технический характер и охватывают темы относящиеся к ядру.
- http://www.linux-mag.com/depts/gear.html
Раздел "Gearheads only" электронного журнала Linux Magazine, где часто появляются статьи от разработчиков ядра.
- http://www.linux.it/kerneldocs
Здесь вы найдете множество журнальных статей по ядру, написанных Алессандро.
- http://lwn.net
Новостной сайт (который ведет один из авторов книги), где среди всего прочего вы найдете статьи, посвященные ядру.
- http://kt.zork.net
Популярный сайт Kernel Traffic, где еженедельно подводятся итоги дискуссий из списка рассылки разработчиков ядра Linux.
- http://www.atnf.csiro.au/~rgooch/linux/docs/kernel-newsflash.html
Сайт Kernel Newsflash -- центр обмена самыми последними новостями о ядре. В частности, уделяется внимание проблемам несовместимости в текущей версии ядра. Этот ресурс может оказаться полезным для тех, кто пытается выяснить -- почему их драйверы отказываются работать с последней версией ядра.
- http://www.kernelnotes.org
Сайт Kernel Notes, ставший уже классикой, предоставляет сведения о выпусках ядра, неофициальных патчах (заплатах -- patch) и пр.
- http://www.kernelnewbies.org
Этот сайт ориентирован на начинающих разработчиков ядра. Здесь имеется информация начального характера, разного рода ЧАВО (FAQ) и канал IRC для тех, кто нуждается в непосредственной помощи.
- http://lksr.org
The Linux Kernel Source Reference -- web-интерфейс к CVS-архиву невероятных размеров, в котором хранятся предыдущие выпуски ядра Linux. Может оказаться очень полезным, если вы хотите узнать -- когда были внесены в ядро те или иные изменения.
- http://www.linux-mm.org
Эта страничка ориентирована на разработчиков подсистемы управления памятью. Здесь содержится достаточно большой объем информации, а так же внушительный по своим размерам перечень ссылок на ресурсы в Интернет.
- http://www.conecta.it/linux
Итальянский сайт, где содержится постоянно обновляемая информация обо всех выходящих проектах, связанных с Linux.
Электронная версия книги и лицензионное соглашение
Авторы пожелали сделать эту книгу свободно доступной, на основе соглашений GNU Free Documentation License, версии 1.1
Типографские соглашения
Ниже приводится перечень типографских соглашений, принятых в данной книге
Курсив | Используется для выделения имен каталогов, файлов, программ, команд и аргументов командной строки. Ссылки в Интернет и новые термины. |
Моноширинный | Используется для отображения исходных текстов примеров, содержимого файлов или вывод на терминал, полученный в результате исполнения команд. |
Моноширинный курсив | Используется для выделения меняющихся опций, ключевых слов или текста, которые пользователь должен заменить фактическими значениями. |
Моноширинный жирный | Используется для выделения примеров или иного текста, которые пользователь должен набрать на клавиатуре. |
Обратите внимание: отдельные части текста выделяются рисунками
Обратная связь
O Reilly & Associates, Inc. 101 Morris Street Sebastopol, CA 95472 (800) 998-9938 (в США и Канаде) (707) 829-0515 (international/local) (707) 829-0104 (fax)
У этой книги есть своя web-страничка, где перечисляются обнаруженные опечатки, имеются примеры и дополнительная информация. Вы можете обращаться к ней по адресу:
http://www.oreilly.com/catalog/linuxdrive2
Свои комментарии и вопросы по книге вы можете направлять по адресу:
bookquestions [at] oreilly.com
Благодарности
Когда мы работали над книгой, конечно же мы не были одиноки и нам хотелось бы выразить свою благодарность всем тем, кто сделал выход этой книги возможным.
Я (Алессандро) хотел бы поблагодарить всех, с чьей помощью стало возможным написать эту книгу. Прежде всего я хотел бы сказать слова благодарности моей милой Федерике (Federica), которая позволила мне заняться пересмотром первого издания книги во время нашего медового месяца, с ноутбуком в палатке. Моим Джулии (Giulia) и Джорджио (Giorgio), которые во время подготовки второго издания помогли мне не потерять чувство реальности, разрывая страницы, выдергивая провода, и требуя внимания своим криком. Я хотел бы сказать спасибо всем моим бабушкам и дедушкам, которые приходили мне на помощь в самые тяжелые моменты и взваливали себе на плечи мои отцовские обязанности, позволяя сосредоточиться на коде и кофе. Я также должен сказать огромное спасибо Майклу Джонсону (Michael Johnson), который ввел меня в писательский мир. Несмотря на то, что это было несколько лет тому назад, он по прежнему остается тем человеком, который закрутил это колесо. В свое время я оставил университет, лишь бы не писать статьи вместо программ. Будучи независимым консультантом, я не связан какими либо обязательствами перед работодателем, что позволило мне без особого напряжения работать над книгой, с другой стороны, я многим обязан Франческо Маджента (Francesco Magenta) и Родолфо Джиометти (Rodolfo Giometti), которые оказывали мне помощь как "зависимые консультанты". Наконец, я хочу выразить признательность всем авторам свободно-распространяемого программного обеспечения, которые фактически учили меня программированию, даже не будучи знакомы со мной. Это относится и к разработчикам ядра и к прикладным программистам. К сожалению их слишком много, чтобы перечислить всех.
Я (Джон) очень обязан многим людям. Прежде всего я хотел бы поблагодарить свою супругу Лауру (Laura), которая проявляла долготерпение все время, которое потребовалось для создания книги, одновременно занимаясь своим бизнесом. Огромное спасибо моим детям -- Мишель (Michele) и Джулии (Giulia), которые стали для меня неиссякаемым источником радости и вдохновения, многочисленным разработчикам ядра, которые проявили огромное терпение, отвечая на мои вопросы и наставляя меня на путь истинный. Спасибо моим коллегам по LWN.net за их необычайную терпимость к моему отсутствию и нашим читателям странички LWN kernel, которые терпели отсутствие обновлений. Это издание едва ли увидело бы свет, если бы не сотрудники местной радиостанции Boulder, которые передавали удивительную музыку. Выражаю свою благодарность служащим горнолыжного курорта Lake Eldora, которые подавали мне замечательный кофе и позволили полностью отдаться работе над книгой, пока мои дети брали уроки катания на лыжах. Я благодарен Эви Немец (Evi Nemeth) за первую возможность поиграть с исходными текстами ранних версий BSD на её VAX-е, Вильяму Вэйту (William Waite), за его уроки программирования и Рит Карбон (Rit Carbone) из Национального Центра Атмосферных Исследований (National Center for Atmospheric Research -- NCAR), который дал старт моей длинной карьере, во время которой я обучился всему остальному.
Мы оба благодарим нашего редактора, Энди Орама (Andy Oram), эта книга стала намного лучше благодаря его стараниям. И безусловно мы обязаны всем тем, кто создал и продолжает воплощать в жизнь идею свободно-распространяемого программного обеспечения (в первую очередь Ричарду Столлману (Richard Stallman) и всем остальным).
Нам также оказывали помощь с аппаратурой, без которой мы не смогли бы изучить такое множество платформ. Мы выражаем благодарность компании Intel, за то что она предоставила в наше распоряжение систему на IA-64, и Rebel.com -- за подаренный наладонник Netwinder. Prosa Labs, бывшая Linuxcare-Italia -- за роскошный PowerPC. NEC Electronics -- за их интересную систему разработки на процессоре VR4181 (palm), куда мы смогли установить GNU/Linux. Sun-Italia предоставила системы SPARC и SPARC64. Все эти компании помогли Алессандро заняться проблемами переносимости и заставили его выделить отдельную комнату под весь этот "зоопарк" из электронных чудищ.
Техническими рецензентами первого издания были Алан Кокс (Alan Cox), Грег Ханкинс (Greg Hankins), Ханс Лермен (Hans Lermen), Хейко Эйссфельдт (Heiko Eissfeldt) и Мигель де Иказа (Miguel de Icaza) (в алфавитном порядке по именам). Рецензирование второго издания выполнили Алан Круз (Allan B. Cruse), Кристиан Моргнер (Christian Morgner), Джейк Эддж (Jake Edge), Джеф Гарзик (Jeff Garzik), Дженс Аксбо (Jens Axboe), Джерри Куперстейн (Jerry Cooperstein), Джером Питер Линч (Jerome Peter Lynch), Майкл Керриск (Michael Kerrisk), Пол Кинзелман (Paul Kinzelman) и Раф Левин (Raph Levien). Все вместе они приложили немало усилий по поиску ошибок и внося предложения по улучшению книги.
И последнее (но не в последнюю очередь) -- мы благодарим всех разработчиков за их неустанный труд. Это относится как разработчикам ядра, так и к прикладным программистам, о которых мы все так часто забываем. Мы не будем перечислять имён, чтобы не оказаться несправедливыми к тем, кого мы можем пропустить по забывчивости. Единственное исключение из этого правила -- Линус Торвальдс (Linus Torvalds), надеемся, что он не будет возражать.
Глава 1. Введение.
Вместе с ростом популярности Linux, устойчиво продолжает расти и интерес к написанию драйверов для него. Linux в значительной степени не зависит от аппаратной платформы и большинство пользователей могут пребывать в счастливом неведении о проблемах, связанных с аппаратурой. Но, для любой части аппаратного обеспечения, поддерживаемого Linux, кто-нибудь, когда-нибудь писал драйвер. Без драйверов система не сможет функционировать.
Драйверы играют особую роль в ядре Linux. Это настоящие "черные ящики", которые полностью скрывают детали, касающиеся работы обслуживаемого устройства, и предоставляют четкий программный интерфейс для работы с аппаратурой. Действия пользователя обрабатываются набором стандартизированных запросов, которые не зависят от определенного драйвера. Роль драйвера заключается в том, чтобы преобразовать эти запросы в операции, специфичные для заданного устройства, посредством которых и осуществляется взаимодействие с аппаратурой. Программный интерфейс задуман так, что он позволяет собирать драйверы отдельно от ядра и "подключать" их во время работы по мере необходимости. Такая модульная организация значительно облегчает написание драйверов в Linux.
Есть множество причин, чтобы интересоваться проблемой написания драйверов в Linux. Темпы появления новых устройств (и устаревания ранее выпущенных) гарантируют, что разработчики драйверов не останутся без работы в обозримом будущем. Кому-то могут потребоваться эти знания, чтобы получить доступ к конкретному устройству. Производители аппаратного обеспечения, за счет выпуска драйверов под Linux, могут значительно расширить свой потенциальный рынок. А открытый характер Linux гарантирует быстрое распространение драйвера среди миллионов пользователей.
Эта книга расскажет вам, как писать драйверы, а так же раскроет некоторые секреты ядра. Нами предпринят аппаратно-независимый подход -- методика программирования и интерфейсы представлены, насколько это возможно, без привязки к какой-либо конкретной аппаратуре. Каждый драйвер имеет определенные отличия от других и вы, как разработчик драйвера, должны четко знать специфику устройства, для которого создается драйвер. Тем не менее, основные правила и методы разработки одинаковы для большинства случаев. Эта книга не дает сведений о конкретных устройствах, но она даст вам базовые знания, основываясь на которых вы сможете работать с вашим устройством.
В процессе прочтения книги вы многое узнаете о ядре Linux. Эти знания помогут вам понять -- как работает ваш компьютер, почему некоторые действия выполняются не так быстро, как это ожидалось или почему компьютер делает совсем не то, что вам хотелось бы. Новые понятия мы будем давать постепенно, начиная с простейших примеров. Каждое новое понятие будет сопровождаться примером кода, для работы которого не требуется наличие специальных устройств.
В этой главе ничего не говорится о написании кода. Но здесь даются некоторые базовые понятия, относящиеся к ядру Linux, которые пригодятся вам при прочтении последующих глав.
1.1. Назначение драйвера устройства
Как программист, вы постоянно должны будете идти на компромисс, выбирая между временем, затрачиваемым на программирование и универсальностью драйвера. Вас может удивить употребление термина "универсальный" по отношению к драйверу. Но нам нравится этот термин, поскольку он подчеркивает, что основное назначение драйвера устройства заключается в реализации тактики, а не стратегии.
Разделение тактики и стратегии -- одна из замечательных черт дизайна Unix. Значительная часть задач может быть разбита на две части: "какие возможности следует обеспечить" (тактика) и "как эти возможности использовать" (стратегия). Если эти две подзадачи решаются в разных частях программы или даже разными программами, то такой программный пакет намного проще развивать и адаптировать под изменяющиеся потребности.
Например, управление графическим дисплеем в Unix производит X-сервер, который работает с аппаратным окружением и предоставляет обобщенный инерфейс для пользовательских программ. Оконный менеджер осуществляют стратегию, ничего не зная об имеющихся аппаратных средствах. Разные люди могут использовать один и тот же оконный менеджер на различной аппаратуре, а различные пользователи могут использовать разные оконные менеджеры на одной и той же рабочей станции. Даже такие, отличные друг от друга окружения рабочего стола, как KDE и GNOME могут прекрасно сосуществовать вместе. Другой пример -- многоуровневая архитектура стека протоколов TCP/IP. Операционная система предоставляет абстракцию сокетов, которая не реализует никакой стратегии по обработке получаемых или передаваемых данных, в то время как за предоставление конкретных сетевых услуг отвечают конкретные серверы (реализующие свои стратегии).
Более того, такой сервер как ftpd реализует механизм (тактику) передачи файлов, в то время как пользователи могут использовать самые разнообразные клентские программы (и даже написать свою), обеспечивающие удобный интерфейс для передачи файлов (стратегия).
Такое же разделение относится и к драйверам. Драйвер НГМД (floppy) не реализует стратегии. Его назначение -- представить дискету как непрерывный массив блоков данных. Более высокие уровни операционной системы реализуют стратегию работы с дискетой -- обращаться ли к ней напрямую или посредством файловой системы, а также решают вопрос -- могут ли пользователи монтировать файловую систему на дискете. Поскольку разные среды работают с аппаратурой по-разному, очень важно, чтобы драйверы были свободны от принятия стратегических решений настолько, насколько это возможно.
При написании драйвера, программист должен обращать особое внимание на фундаментальный аспект: код ядра, предоставляющий доступ к аппаратуре, не должен заниматься реализацей стратегических решений для пользователей, поскольку пользователи могут иметь совершенно различные потребности. Драйвер должен обеспечивать доступ к аппаратуре, оставляя решение проблемы о том как ее использовать прикладным программам. Универсализм драйвера заключается в том, чтобы обеспечить доступ к возможностям аппаратуры не накладывая дополнительных ограничений. Иногда, однако, приходится выполнять реализацию отдельных стратегических решений. Например, драйвер дискретного ввода-вывода сможет получать-выдавать данные только побайтно, если не добавить дополнительный код, который будет обрабатывать отдельные биты.
Вы можете взглянуть на ваш драйвер под другим углом: это промежуточный уровень между приложением и аппаратурой. Такое привелигированное положение позволяет программисту сделать выбор, как будет представлено данное устройство: различные драйверы могут предоставлять различные возможности даже в случае одного и того же устройства. Фактически, драйвер должен иметь сбалансированный дизайн с самых разных точек зрения. Например, устройство может одновременно использоваться несколькими приложениями и программист абсолютно свободен в выборе тактики обслуживания конкурирующих запросов. Вы можете реализовать представление устройства независимо от его аппаратных возможностей или написать библиотеку примитивов, которые помогут прикладному программисту реализовать свою стратегию и т.д.. Одно важное ограничение -- вам придется выбирать между желанием предоставить в распоряжение пользователя такую широту выбора, какая только возможна, и между временем, которое вы можете затратить на написание драйвера, к тому же необходимо сохранить код достаточно простым, чтобы в него не закрались ошибки.
Драйверы, свободные от стратегических решений, несут в себе множество характерных особенностей. Среди них -- поддержка как синхронных, так и асинхронных операций; возможность работы в многозадачной среде; максимально полное использование возможностей аппаратуры; отсутствие стратегической прослойки, что упрощает код драйвера, оставляя его простым и понятным. Драйверы такого сорта не только лучше работают, но и более просты в разработке и сопровождении.
В действительности, значительная часть драйверов выпускается совместно с прикладными
программами, которые помогают сконфигурировать доступ к конкретному устройству. Эти
программы могут быть и простыми утилитами командной строки и сложными приложениями с
графическим интерфейсом пользователя. В качестве примеров можно привести утилиту
tunelp
, для настройки драйвера параллельного порта принтера
и графическую утилиту cardctl
, которая входит в состав пакета
драйвера PCMCIA. Зачастую клиентские бмблиотеки реализуют дополнительные возможности,
которые не являются составной частью драйвера.
Область, которой ограничивается данная книга -- это ядро и мы не будем обсуждать проблемы стратегии, прикладных программ или вспомогательных библиотек. Иногда повествование будет касаться различных стратегических решений но углубляться в эту тему мы не предполагаем. Однако вы должны понимать, что пользовательские приложения зачастую входят в состав больших программных пакетов, которые распространяются с конфигурационными файлами, предоставляющими настройки по-умолчанию (своего рода стратегическое решение).
1.2. Строение ядра
В операционной системе Unix одновременно работает множество процессов, выполняющих самые разнообразные задачи. Каждый процесс запрашивает у системы различные ресурсы, будь то процессор, память, сеть или нечто иное. Принимает и обрабатывает все эти запросы -- ядро, которое является одним большим испоняемым файлом. Хотя границы различных задач, выполняемых ядром, не всегда могут быть четко проведены, тем не менее функциональность ядра может быть разделена на несколько частей, как показано на рисунке ниже.
- Управление процессами
Ядро отвечает за запуск и остановку процессов, а так же за их взаимодействие с внешним миром (ввод-вывод). Обмен данными между процессами (посредством сигналов, каналов и примитивов межпроцессных взаимодействий -- IPC), как основная и ведущая задача операционной системы, так же возложена на ядро. Кроме того, планировщик, который занимается распределением процессорного времени между задачами, так же является частью подсистемы управления процессами.
- Управление памятью
Память компьютера -- самый важный ресурс, а стратегия распределения памяти очень сильно влияет на общую производительность системы. Ядро обеспечивает всем и каждому из процессов свое виртуальное адресное пространство, скрывая имеющиеся ограничения. Различные части ядра взаимодействуют с подсистемой управления памятью через набор функций, начиная от простых malloc/free и заканчивая весьма экзотическими вариантами.
- Файловая система
Unix очень жестко связан с концепцией файловой системы. Практически все в Unix может рассматриваться как файл. Ядро выстраивает структурированную файловую систему поверх разнородного аппаратного обеспечения, а полученная файловая абстракция интенсивно используется в системе практически повсюду. Кроме того, Linux поддерживает множество разнотипных файловых систем, отличающихся способом организации данных на физическом носителе. Например, на дискете может быть создана, привычная для Linux, файловая система ext2 или не менее широко используемая FAT.
- Управление устройствами
Почти каждая системная операция в конечном счете отображается на физическое устройство. За исключением процессора, памяти, и незначительного количества других устройств, практически все операции управления устройствами выполняются кодом, который является специфичным для заданного устройства. Этот код называется драйвером устройства. Для каждого переферийного устройства в ядро должен быть внедрен свой драйвер, начиная от жесткого диска и заканчивая клавиатурой. Этот аспект ядра и является основным, рассматриваемым в данной книге.
- Сетевая поддержка
Сетевая поддержка осуществляется операционной системой, поскольку большинство сетевых операций не относятся к какому либо конкретному процессу: входящие пакеты поступают асинхронно. Пакеты должны быть собраны и идентифицированы прежде, чем они будут переданы процессам для дальнейшей обработки. Система отвечает за передачу пакетов между сетевыми интерфейсами к приложениями и должна управлять исполнением программ в зависимости от сетевой активности. Кроме того, все задачи, связанные с маршрутизацией и разрешением адресов, решаются внутри ядра.
Одна из замечательных особенностей ядра Linux -- возможность расширять свою функциональность во время исполнения. Это означает, что вы можете добавить дополнительные возможности в ядро прямо во время работы системы.
Код, который добавляется в ядро во время исполнения, называется модуль.
Ядро Linux предоставляет поддержку для разных типов (классов) модулей, для драйверов
устройств в том числе. Каждый модуль представляет из себя файл с объектным кодом
(не связанным в исполняемый модуль, т.е. не прошедший стадию линковки), который
может быть динамически внедрен в работающее ядро с помощью программы
insmod
, а затем удален из ядра программой
rmmod
.
На рисунке выше показаны разные классы модулей, отвечающих за решение различных задач. Говорят, что модуль принадлежит определенному классу согласно предлагаемым функциональным возможностям.
1.3. Классы устройств и модулей
Традиционно, Unix разделяет все устройства на три основных типа. Каждый модуль, как правило, реализует функциональность одного из этих типов и отсюда может классифицироваться как символьный модуль, блочный модуль или сетевой модуль. Это деление не жесткое, программист может создать один большой модуль, в котором будет реализовано несколько различных драйверов. Но обычно хорошие программисты создают отдельные модули для каждого случая, поскольку декомпозиция -- есть ключевой момент масштабируемости и расширяемости.
Ниже приводятся краткие описания этих трех классов:
- Символьные устройства
Символьное устройство -- это такое устройство, которое поставляет данные как поток байт (напоминает файлы). Драйвер символьного устройства отвечает за реализацию поддержки такого поведения. Такие драйверы реализуют по меньшей мере четыре системных вызова:
open
,close
,write
иread
. Типичными примерами таких устройств могут служить текстовая консоль (/dev/console
) и последовательный порт (/dev/ttyS*
). К символьным устройствам можно обращаться посредством элементов файловой системы, таких как/dev/tty1
или/dev/lp0
. Единственное важное отличие обычных файлов от символьных устройств заключается в том, что вы можете перемещаться по файлам взад и вперёд, в то время как символьные устройства -- это лишь каналы передачи данных, доступ к которым осуществляется в заданном порядке. Тем не менее, существуют символьные устройства, которые допускают произвольный доступ к данным в потоке, типичным примером могут служить устройства захвата изображения, где приложения могут обращаться к изображению целиком, используяmmap
илиlseek
.- Блочные устройства
Подобно символьным устройствам, к блочным устройствам так же можно обращаться посредством элементов файловой системы в каталоге
/dev
. Самым известным примером блочного устройства может служить жёсткий диск. Обмен данными с блочным устройством производится порциями байт -- блоками. В большинстве Unix-систем размер одного блока равен 1 килобайту или другому числу, являющемуся степенью числа 2. Linux позволяет приложениям обращаться к блочному устройству так же как к символьному -- он разрешает передачу любого числа байт в блоке. В результате, все различие между блочными и символьными устройствами сводится к внутреннему представлению данных в ядре. Драйвер блочного устройства реализует точно такой же интерфейс с ядром, что и драйвер символьного устройства, но дополнительно реализуется еще и блочно-ориентированный интерфейс, который "невидим" для пользователя или приложения, которые открывают доступ к блочному устройству посредством псевдофайловой системы/dev
. Тем не менее, блочный интерфейс совершенно необходим, чтобы можно было выполнитьmount
файловой системы.- Сетевые интерфейсы
Любой сетевой обмен выполняется через сетевой интерфейс, т.е. устройство, которое способно обмениваться данными с другими узлами сети. Как правило -- это некое аппаратное устройство, но возможна и исключительно программная реализация сетевого устройства, например петлевое устройство
loopback
(локальный сетевой интерфейс). Сетевой интерфейс отвечает за передачу и получение пакетов данных, которыми управляет сетевая подсистема ядра, ничего не зная о том, к каким соединениям эти пакеты принадлежат. Не смотря на то, что соединения по протоколам Telnet и FTP используют один и тот же сетевой интерфейс, само устройство не различает эти соединения, оно "видит" только пакеты данных.Не будучи потоковым устройством, сетевой интерфейс не может быть отображен на файловую систему, как это делается в случае с
/dev/tty1
. Традиционно Unix предоставляет доступ к интерфейсу, назначая ему уникальное имя (например,eth0
), но вы не найдете файл с этим именем в каталоге/dev
. Принципы взаимодействия ядра с драйвером сетевого устройства, в корне отличаются от принципов, применимых к символьным или блочным устройствам. Вместо функцийwrite
иread
, ядро вызывает соответствующие функции, управляющие передачей пакетов.
В Linux существуют и другие классы модулей драйверов, которые добавляют в ядро возможность
взаимодействия с определенными типами устройств. Из таких нестандартных классов устройств
наиболее часто встречается SCSI (Small Computer Systems Interface). Хотя внешние устройства,
связанные с шиной SCSI, и отображаются в каталоге /dev
,
в виде символьных или блочных устройств, тем не менее, как и в случае с сетевыми интерфейсами,
внутренняя программная организация отличается очень сильно.
Так же, как сетевые платы предоставляют свои возможности сетевой подсистеме, контроллер SCSI предоставляет в распоряжение подсистеме SCSI возможность доступа к SCSI-устройствам. SCSI -- это протокол взаимодействия между компьютером и переферийными устройствами. Любое SCSI-устройство откликается на этот протокол, вне зависимости от того, какой контроллер установлен в компьютер. Ядро отображает файловые операции на протокол взаимодействия SCSI. Разработчик SCSI драйвера должен реализовать отображение между абстракцией SCSI и физическим кабелем. Это отображение зависит от типа контроллера, но не зависит от типа устройства, подключенного к контроллеру.
Ряд других классов драйверов устройств были добавлены не так давно. Сюда можно отнести USB, FireWire и I2O. Как и в случае SCSI, разработчики ядра собрали воедино особенности, характерные для всего класса и экспортировали их для разработчиков драйверов, чтобы избежать дублирования работы и уменьшить вероятность появления ошибок, чем упростили процесс написания таких драйверов.
Помимо драйверов устройств, пожалуй самым важным классом модулей в Linux являются файловые системы. Тип файловой системы обусловливает способ организации информации на блочном устройстве. Это -- не драйвер устройства, здесь нет никакого устройства, которое было бы связано со способом размещения информации. Тип файловой системы -- это программный драйвер, потому что он отображает структуры данных нижнего уровня на структуры данных верхнего уровня. Файловая система определяет -- какой длины могут быть имена файлов и какая информация о каждом из файлов должна храниться. Файловая система должна реализовать самый нижний уровень системных вызовов для доступа к каталогам и файлам, путем отображения их имён (и иной информации, такой как права доступа и пр.) в структуры данных, которые записываются в блоки данных. Такой интерфейс полностью независим от физической передачи данных с/на диск (или другой носитель), которая выполняется драйвером блочного устройства.
Если вы задумаетесь над тем, насколько сильно Unix зависит от концепции файловой
системы, вы поймете насколько она важна для работы операционной системы. Функции
обработки информации, хранящейся в файловой системе находятся на самом нижнем уровне
в иерархии ядра и имеют очень важное значение. Даже если вы напишете драйвер для своего
CD-ROM, он окажется совершенно бесполезен, если не будет возможности выполнить
ls
или cp
.
Linux поддерживает модули файловых систем, которые реализуют различные файловые операции.
Однако, вам едва ли придется писать свой модуль файловой системы, поскольку ядро
Linux включает в себя код довольно большого числа наиболее популярных файловых систем.
1.4. Проблемы безопасности
Безопасность в наше время приобретает все более важное значение. Мы будем обсуждать проблемы безопасности на протяжении всей книги. Однако, ряд базовых понятий мы хотим дать прямо сейчас.
Проблема безопасности имеет два основных понятия: случайное нанесение ущерба и умышленное. Первое понятие -- ущерб, причиненный в результате ошибочных действий пользователя или непреднамеренных ошибок в программе. Второе -- ущерб, причиненный в результате предумышленных действий программиста, который мог умышленно написать вредоносный код. Другими словами -- следует остерегаться запускать программы, полученные от незнакомых лиц, да еще под учетной записью root. Доступность компилятора не должна расцениваться как "дыра" в системе защиты. "Дыра" может появиться, когда запускается скомпилированная программа. Вы должны быть очень внимательны при обращении с модулями, потому что модулям ядра доступно практически все, как и суперпользователю.
Любая проверка безопасности производится кодом ядра. Если ядро имеет бреши в системе
безопасности, то и система в целом будет иметь бреши. В официальных версиях ядра
только автороизованный пользователь имеет право загружать модули -- проверка
прав производится системным вызовом create_module
.
Таким образом, на официальных версиях ядра только суперпользователь или злоумышленник,
который завладел привилегиями суперпользователя, сможет исполнить привелигированный код
(в ядрах серии 2.0 только суперпользователю было позволено запускать привелигированный код,
начиная с версии 2.2 ядром выполняются более изощренные проверки).
При создании драйверов вы должны избегать написания кода, отвечающего за политику безопасности. Проблемы безопасности неплохо решаются на более высоких уровнях в ядре, под контролем системного администратора. Однако, любое правило имеет свои исключения. Как разработчик вы должны знать о ситуациях, когда некоторые виды доступа к устройствам могут оказать неблагоприятное воздействие на систему в целом, поэтому вы должны обеспечить адекватный контроль. Например, такие действия с устройствами, которые могут затронуть глобальные ресурсы системы (такие как прерывания) или сказаться на других пользователях (такие как изменение размера блока по-умолчанию для ленточного накопителя), обычно могут выполнять только привелигированные пользователи и соответствующие проверки должны быть реализованы в драйвере.
Вы так же должны стремиться не допускать ошибки в коде, которые могут стать дырами в системе безопасности. Язык программирования C "облегчает" внесение некоторых видов ошибок. Например, немало проблем с безопасностью породила ошибка переполнения буфера. Эта ошибка связана с забывчивостью программиста, который забывает выполнить проверку количества данных, записываемых в буфер. В результате данные могут быть записаны далеко за пределы памяти, отведенной под буфер, что приводит к порче других данных. Такого рода ошибки могут поставить под угрозу работоспособность системы в целом. К счастью, уйти от таких ошибок довольно просто, особенно в контексте драйвера устройства, для которого интерфейс с пользователем четко определен.
Следует постоянно помнить и о некоторых других мерах предосторожности. К любой информации, введенной пользователем, вы должны относиться с большим подозрением -- никогда не доверяйте этой информации и всегда проверяйте ее, если это возможно. Будьте внимательны при работе с неинициализированной памятью -- любой участок памяти, полученной от ядра, должен забиваться нулями или инициализироваться каким-либо другим способом прежде, чем эта память будет передана пользовательскому процессу или устройству. Если ваше устройство выполняет интерпретацию данных, убедитесь, что пользователь не может послать ничего, что могло бы поставить под угрозу работоспособность системы. Наконец, подумайте о возможных побочных эффектах (например, перепрошивка firmware в ПЗУ устройства, форматирование диска и т.п.). Доступность операций, которые могут вызвать такие побочные эффекты, должна ограничиваться узким кругом привелигированных пользователей.
Будьте внимательны при работе с программным обеспечением, полученным от третьих лиц,
особенно это касается ядра, поскольку каждый, кто имеет доступ к исходным текстам,
может внести вредоносный код и скомпилировать его. Собранным ядрам, входящим в состав
официальных дистрибутивов, вы обычно можете доверять, но не доверяйте ядрам, собранным
незнакомыми лицами. Злоумышленник может внести изменения в код вызова
create_module
и получить возможность загружать модули
ядра, открывая таким образом "черный ход" для запуска вредоносных
программ.
Здесь следует упомянуть, что ядро может быть собрано вообще без поддержки загружаемых модулей. В этом случае, вам естесственно придется скомпилировать все необходимые драйверы статически. Начиная с версии 2.2, ядро допускает отключение возможности загрузки модулей сразу после загрузки системы через соответствующий механизм.
1.5. Нумерация версий
Перед тем как углубиться в программирование, нам хотелось бы сказать несколько слов о принципах нумерации версий, используемых в Linux и о версиях, которые охватываются этой книгой.
Прежде всего следует заметить, что каждый программный пакет в Linux имеет свой собственный номер версии, который зачастую учитывается в зависимостях другими пакетами -- это означает, что если вы собрались установить какой-либо пакет, вы должны установить другие пакеты, от которых зависит данная версия пакета. Создатели дистрибутивов обычно учитывают проблему зависимостей пакетов и те, кто устанавливает Linux из официальных дистрибутивов, как правило с ней не сталкиваются. Наоборот те, кто сам собирает или модернизирует операционную систему, вынуждены решать эту проблему самомтоятельно. К счастью, почти все современные дистрибутивы поддерживают возможность обновления отдельных пакетов, выполняя проверку зависимостей -- менеджер пакетов вообще откажется устанавливать новую версию, если зависимости не были удовлетворены.
Для запуска примеров, которые будут встречаться по ходу изложения материала, вам не
потребуется знать версии инструментальных средств, но это замечание не относится к ядру --
наши примеры смогут быть опробованы практически в любом современном дистрибутиве Linux.
Мы не будем развивать дискуссию в этом направлении, поскольку в вашем распоряжении
имеется файл Documentation/Changes
(в дереве каталогов
с исходными текстами ядра) где вы всегда сможете найти исчерпывающую информацию при
возникновении проблем.
Основной интерес для нас представляют ядра, имеющие четные номера версий (т.е. 2.2.x и 2.4.x). Это стабильные версии ядра, предназначенные для включение в состав дистрибутивов и широкого использования. Нечетные версии (такие как 2.3.x) наоборот являются нестабильными и предназначены для разработчиков. Эти версии устаревают очень быстро, порядка нескольких дней или около того.
Эта книга охватывает ядра версий 2.0.x, 2.2.x и 2.4.x. Однако основное внимание будет уделяться ветке 2.4 (текущая стабильная ветка на момент написания книги), всем ее особенностям, доступным для разработчиков драйверов. Мы попробуем показать особенности ветки 2.2 в тех местах, где они имеют расхождения с веткой 2.4. Мы так же будем обращать ваше внимание на возможности недоступные в ветке 2.0. Вообще код, который будет продемонстрирован в книге, опробовался в широком диапазоне версий ядра. Он был полностью протестирован на ядре 2.4.4, где это было возможно -- на ядре 2.2.18 и 2.0.38.
Эта книга не обсуждает ядра нечетных версий. Обычно пользователи не имеют веских причин для установки таких ядер. Разработчики ядра, экспериментируя с новыми возможностями, наоборот предпочитают иметь самую свежую версию. Они постоянно продолжают вносить новые особенности в ядро и исправляют замеченные ошибки. В случае нечетных версий ядра никто не даст вам гарантий, что оно будет достаточно работоспособно (впрочем то же касается и четных версий, если вы не используете коммерческие дистрибутивы, в которых поставщик выдает свою собственную гарантию) и вам едва ли придется рассчитывать на чью-либо помощь в случае возникновения проблем. Те, кто устанавливает нечетные версии ядра, обычно достаточно квалифицированы, чтобы разобраться с кодом без каких-либо учебников. Это является еще одной причиной, по которой мы не будем касаться нестабильных ядер.
Еще одна особенность Linux -- его независимость от аппаратной платформы. Linux -- это не просто "клон UNIX для PC". Он успешно работает на процессорах Alpha и SPARC, на платформах 68000 и PowerPC и на многих других. В этой книге не обсуждается какая-либо конкретная платформа, навколько это было возможно сделать. Все примеры тестировались на различных платформах, таких как PC, Alpha, ARM, IA-64, M68k, PowerPC, SPARC, SPARC64 и VR41xx (MIPS). Поскольку код проверялся как на 32-х битных так и на 64-х битных процессорах, он должен компилироваться и запускаться и на других платформах. Если какой-либо пример будет работать не на всех платформах, то это будет явно указываться нами.
1.6. Лицензионные соглашения
Linux распространяется на основе GNU General Public License (GPL), которая была разработана Free Software Foundation для проекта GNU. Она позволяет любому распространять и даже продавать продукты, выпущенные на основе GPL, при условии, что покупатель, вместе с бинарными файлами, сможет получить и исходные тексты. Кроме того, если программный продукт является производным от продукта, выпущенного на основе GPL, он так же должен распространяться на основе GPL.
Основная цель этой лицензии -- распространение знаний, разрешая каждому изменять программы
по своему усмотрению, вместе с тем допуская распространение программного обеспечения
на коммерческой основе. Несмотря на простые цели лицензии, споры о ней и ее применении
по-прежнему не прекращаются. Если вы пожелаете ознакомиться с текстом лицензии, вы сможете
найти ее в своем дистрибутиве, как одно из мест --
/usr/src/linux
в файле COPYING
.
Модули не являются частью ядра Linux, а посему вы не обязаны
лицензировать их под GPL. Модули обращаются
к ядру, используя
предопределенный интерфейс, но не так как используют ядро пользовательские программы,
обращаясь к нему через системные вызовы. Освобождение от лицензирования под GPL относится
только к модулям ядра, потому что они использую только официально опубликованный интерфейс.
Модули, которые используют более глубинные механизмы ядра, подпадают под определение
"derived work" (производный продукт) лицензии GPL.
Короче говоря, если ваш код входит в состав ядра, то вы должны использовать GPL, как только вы его выпустите. Если вы распространяете ваш код, то вы должны включить в комплект поставки исходные тексты -- всем, кто приобретает ваш пакет необходимо предоставить право пересобрать бинарные файлы. Если вы написали модуль ядра, то вы можете распространять его в виде скомпилированных файлов, но это не совсем практично, поскольку модули, как правило, необходимо пересобирать заново для каждой версии ядра (подробнее этого вопроса мы коснемся в главе 2, в разделе "Зависимость версий" и в главе 11, в разделе " Контроль версий в модулях"). Новые версии ядра, даже стабильные, зачастую отказываются работать с модулями, предкомпилированными с другими версиями ядра, требуя их пересборки. Линус Торвальдс публично заявил, что не видит в этом никакой проблемы, поскольку скомпилированные модули по-прежнему сохраняют свою работоспособность в той версии ядра, под которую они собирались. Как разработчик вы сделаете благое дело, если предоставите свой исходный код в общее пользование.
Почти весь код, который приводится в данной книге, может вами свободно распространяться
как в исходном виде, так и в скомпилированном. И никто -- ни мы, ни O Reilly & Associates
не сохраняют за собой право на любой продукт, производный от данного кода. Все программы
доступны для свободного скачивания на
ftp://ftp.ora.com/pub/examples/linux/drivers/. Точные условия лицензирования указаны
в файле LICENSE
.
Если примеры используют части кода ядра, то они подпадают под условия GPL. Комментарии в примерах напоминают об этом в недвусмысленной форме. Это относится только к паре файлов, которые приводятся во второстепенных разделах данной книги.
1.7. Как присоединиться к сообществу разработчиков ядра
Вступая в мир писателей модулей ядра, вы становитесь частью большого сообщества разработчиков. Здесь вы найдете не только тех, кто делает работу, аналогичную вашей, но также группы чрезвычайно преданных идее инженеров, работающих над улучшением Linux. Эти люди могут стать для вас источником новых идей и окажут посильную помощь.
Центральное место встречи всех разработчиков ядра -- список почтовой рассылки linux-kernel. Все основные разработчики ядра подписаны на эту рассылку, в том числе и Линус Торвальдс. Единственно хотим отметить, что эта рассылка не для слабонервных -- ежедневный объем может доходить до 200 сообщений и больше. Тем не менее эта расслка окажется весьма полезной для тех, кто хочет постоянно оставаться в курсе событий, а кроме того, эта рассылка является источником очень ценной информации.
Чтобы подписаться на рассылку, следуйте инструкциям, приводимым в FAQ, по адресу http://www.tux.org/lkml. Пожалуйста, прочитайте FAQ до конца, здесь содержится много полезных сведений. Разработчики занятые люди и они более склонны помогать тем, кто имеет хотя бы начальные знания.
1.8. Обзор книги
Со следующей главы мы начинаем обсуждать вопросы, касающиеся программирования для ядра. В этой главе будут рассмотрены вопросы модульной архитектуры и показан пример простого модуля ядра. В главе 3 мы погоаорим о драйверах символьных устройств и продемонстрируем полный текст простого драйвера, который может просто читать и писать данные из/в память. Использование памяти вместо аппаратного устройства позволит вам опробовать код без необходимости подключения дополнительной аппаратуры.
Методы отладки и необходимые инструменты программиста будут рассмотрены в главе 4. Затем, получив необходимые знания об отладке, мы двинемся дальше и в главе 5 рассмотрим некоторые дополнительные особенности, характерные для драйверов символьных устройств, такие как блокирующие операции, функции select и ioctl.
Прежде чем приступить к работе с аппаратной частью компьютера, мы опять немного углубимся в ядро. В главе 6 мы расскажем об управлении временем в ядре и в главе 7 -- о способах выделения памяти.
Затем мы сосредоточим наше внимание на аппаратной части. В главе 8 мы рассмотрим управление портами ввода-вывода, буферами памяти и затем в главе 9 перейдем к прерываниям. При изучении этой главы нам потребуется эмулировать наличие дополнительной аппаратуры, чтобы проверить прерывания. Мы постарались свести использование дополнительных аппаратных средств к минимуму, так что вам достаточно будет лишь втсавить в параллельный порт одну единственную перемычку. Надеемся, что для вас это не будет большой проблемой.
В главе 10 мы дадим ряд дополнительных советов по написанию модулей и коснемся проблемы переносимости.
Во второй части книги мы еще глубже погрузимся в ядро и в главе 11 обсудим проблему модульной архитектуры ядра.
В главе 12 опишем блочные драйверы, отдельно останавливаясь
на аспектах, отличающих драйверы блочных устройств от драйверов символьных устройств.
Затем, в главе 13 мы рассмотрим дополнительные способы
управления памятью: mmap
и DMA (Direct Memory Access -- Прямой
Доступ к Памяти). На этом обсуждение драйверов блочных и символьных устройств будет
закончено.
Затем мы представим вам третий основной класс драйверов. В главе 14 мы поговорим о некоторых особенностях сетевых интерфейсов и рассмотрим код типичного драйвера сетевой платы.
Некоторые особенности драйверов напрямую зависят от интерфейсной шины, к которой подключаются устройства, таким образом, глава 15 послужит кратким обзором основных, наиболее часто встречающихся, типов интерфейсных шин. Особое внимание будет уделено поддержке шин PCI и USB.
И наконец глава 16 представляет собой тур по исходным текстам ядра: она предназначена для тех, кто желает понять его архитектуру, но боится затеряться в огромном количестве исходных текстов.
Глава 2. Сборка и запуск модулей.
Итак, мы готовы приступить к программированию. Эта глава затрагивает основные концепции программирования модулей ядра. В ней будет описан процесс создания, сборки и запуска простейшего модуля ядра. Этот опыт пригодится вам впоследствие, при написании модуля любого типа. В этой главе говорится только о базовых концепциях, безотносительно к какому-либо классу устройств.
Все элементы ядра (функции, переменные, заголовочные файлы и макроопределения), которые будут упоминаться здесь, вынесены в небольшой справочник, в конце главы.
Для нетерпеливого читателя ниже приводится код модуля "Hello, World" (который не делает ничего особенного). Этот код может быть скомпилирован и запущен на ядрах версий 2.0, 2.2 и 2.4.
#define MODULE #include <linux/module.h> int init_module(void) { printk("<1>Hello, world\n"); return 0; } void cleanup_module(void) { printk("<1>Goodbye cruel world\n"); }
Функция printk
определена в ядре и по своему поведению
напоминает функцию printf
из стандартной библиотеки
языка C. Зачем же тогда ядру своя собственная функция? Все просто -- ядро, это
самостоятельный код, который собирается без вспомогательных библиотек языка C.
Модуль может вызвать функцию printk
, поскольку после
того как insmod
загрузит его, ему становятся доступны
все глобальные имена ядра (функции и переменные, более детально этот вопрос
затрагивается в следующем разделе). Строка <1> задает приоритет сообщения,
в данном случае мы задали высокий приоритет, поскольку с приоритетом по-умолчанию
сообщение может не выводиться на консоль, в зависимости от версии ядра, версии демона
klogd
и конфигурации. Вы можете пока оставить все вопросы,
связанные с printk
, поскольку более подробно эта функция
будет обсуждаться в главе 4.
Вы можете протестировать работу модуля, вызвав insmod
и
rmmod
, как показано ниже, в следующем параграфе. Обратите
внимание: только суперпользователь может загрузить и выгрузить модуль.
Модуль, исходный текст которого приведен выше, может быть загружен и выгружен только
если в ядре отключен механизм проверки версий модулей. Однако, в большинстве дистрибутивов
ядро собирается с этой поддержкой (механизм контроля версий будет обсуждаться в
разделе "Контроль версий в
модулях", в главе 11). Хотя старые modutils
и допускали загрузку модулей в такое ядро, теперь это стало невозможно. Эта проблема
решается включением нескольких строк в исходный текст модуля, примеры вы сможете
найти в каталоге misc-modules
с примерами кода. Со своей
стороны мы настоятельно рекомендуем вам пересобрать свое ядро, отключив проверку версий
модулей.
root#gcc -c hello.c
root#insmod ./hello.o
Hello, world root#rmmod hello
Goodbye cruel world root#
В зависимости от версии ядра и его конфигурации, выводимое сообщение может несколько
отличаться. Вывод на экран, приведенный выше, был получен в текстовой консоли. Если
вы производите загрузку/выгрузку модуля в окне терминала (например
xterm
), то эти сообщения появляться не будут. Но вы сможете
обнаружить их в системном журнале /var/log/messages
(имя файла системного журнала и путь к нему могут варьировать от системы к системе).
Механизм вывода сообщений ядра описан в разделе "Как журналируются сообщения" в главе 4.
Как видите, в модулях нет ничего сложного. Самое тяжелое, при их написании -- это разобраться с тем, как работает устройство. В этой главе мы постараемся уходить от проблем, связанных с какими-либо особенностями аппаратных устройств, оставляя эту тему для обсуждения в последующих главах.
2.1. Модули ядра и прикладные программы
Прежде чем двинуться дальше, необходимо подчеркнуть различия, иемеющиеся между модулями ядра и прикладными программами.
Приложение выполняется как цельная задача, от начала и до конца. Модуль же просто
регистрирует себя самого в ядре, подготавливая его для обслуживания возможных запросов
и его функция "main
" завершает
свою работу сразу же после вызова. Другими словами, задача функции
init_module
(точка входа) состоит в подготовке функций
модуля для последующих вызовов. Она как бы говорит ядру: "Эй! Я здесь! Вот то, что я могу
делать!". Вторая точка входа в модуль -- cleanup_module
вызывается
непосредственно перед выгрузкой модуля. Она сообщает ядру: "Я ухожу! Больше не проси меня
ни о чем!". Возможность выгружать модули -- это одна из особенностей модульной архитектуры,
которую вы наверняка оцените по достоинству, поскольку она поможет вам значительно сократить
время, затрачиваемое на разработку модуля. С ее помощью вы сможете проверять новые версии
модуля без необходимости тратить свое драгоценное время на перезагрузку системы.
Как программист, вы знаете, что приложение может вызывать функции, которые не определены
в самой программе. На стадии связывания (линковки) разрешаются все внешние ссылки, уходящие
во внешние библиотеки. Функция printf
-- одна из таких
функций, которая определена в библиотеке libc
.
Модули так же проходят стадию связывания, но только с ядром, и могут вызывать только
те функции, которые экспортируются ядром. Функция printk
,
использованная в примере hello.c
, определена в ядре
и импортируется модулем. Она очень похожа на своего сородича из
libc
, с небольшими отличиями, главное из которых
состоит в отсутствии поддержки вывода чисел с плавающей точкой
[2]
На рисунке ниже показана схема вызова функций и как используются указатели на функции в модуле при добавлении его в ядро.
Поскольку модуль не связывается ни с одной из стандартных библиотек, исходные тексты
модуля не должны подключать обычные заголовочные файлы. В модулях ядра могут использоваться
только те функции, которые экспортируются ядром. Все заголовочные файлы, которые
относятся к ядру, расположены в каталогах include/linux
и
include/asm
, внутри дерева каталогов с исходными текстами
ядра (как правило это каталог /usr/src/linux
). Ранние версии
Linux (основанные на libc
версии 5 и более ранних) устанавливали
символические ссылки из /usr/include/linux
и
/usr/include/asm
на фактические каталоги из исходных
текстов ядра, таким образом дерево заголовочных файлов libc
могло ссылаться на заголовочные файлы ядра. Это позволяло подключать заголовочные файлы
ядра в пользовательские приложения тогда, когда в этом возникала необходимость.
Но даже теперь, когда заголовочные файлы ядра отделены от заголовочных файлов, используемых
прикладными программами, все равно иногда возникает необходимость включения их
в программы, работающие в пространстве пользователя, чтобы воспользоваться определениями,
отсутствующими в обычных заголовочных файлах. Однако большая часть определений из
заголовочных файлов ядра относится исключительно к ядру и "невидима" для
обычных приложений, поскольку доступ к этим определениям заключен в блоки
#ifdef __KERNEL__
. Это кстати одна из причин, почему
необходимо определять символ __KERNEL__
при сборке модуля.
Назначение отдельных заголовочных файлов ядра будет поясняться на протяжении всей книги, по мере необходимости.
Разработчики, работающие с большими программными пакетами (такими как ядро), должны избегать "загрязнения" пространства имен (namespace). Под "загрязнением" пространства имен понимается внедрение множества имен функций и переменных, с глобальной областью видимости, значение которых не интутивно и трудноразличимо. Программист, который работает с такими приложениями, тратит огромное количество умственных сил на то, чтобы запомнить "зарезервированные" имена и придумать свои уникальные названия. Коллизии имен могут породить трудноуловимые ошибки, начиная от того, что модуль просто отказывается загружаться, и заканчивая весьма причудливыми сообщениями.
Разработчик не должен допускать появления таких ошибок, поскольку даже маленький
модуль линкуется с целым ядром. Лучший способ избежать "загрязнения"
пространства имен -- это объявлять все имена как
static
и использовать префиксы для придания уникальности
именам с глобальной областью видимости. Проблемы, связанные с управлением областью
видимости, будут обсуждаться в разделе "Таблица имён ядра", в этой же главе.
Так же хорошей практикой считается использование своих предопределенных префиксов для
всех локальных имен в модуле, это поможет вам в процессе отладки. Они позволят вам
экспортировать имена, без "загрязнения" пространства имен, в процессе
тестирования. Префиксы, используемые в исходных текстах ядра, всегда состоят только из
символов нижнего регистра. Мы так же будем придерживаться этого соглагшения
(большая часть версий insmod
экспортирует нестатические имена,
придавая им глобальную область видимости).
И наконец последнее отличие модулей ядра от обычных приложений, на котором
мы хотим остановиться, состоит в том, как обрабатываются ошибки. Например, ошибка
segmentation fault
, возникающая в приложении может
быть отслежена и устранена без особых проблем, а вот в модуле ядра практически
любая ошибка может стать фатальной для всей системы. В разделе "Отладка системных ошибок", главы 4,
мы подробнее остановимся на проблеме отладки.
2.1.1. Пространство пользователя и пространство ядра
Модуль выполняется в так называемом пространстве ядра, в то время, как прикладные задачи -- в пространстве пользователя. Это базовая концепция в теории построения операционных систем.
Роль операционной системы, фактически состоит в предоставлении аппаратных ресурсов в распоряжение прикладных программ. Кроме того, операционная система должна быть изолирована от действий прикладных программ и предотвращать несанкционированный доступ к ресурсам компьютера. Решение этой нетривиальной задачи возможно только в том случае, если процессор предоставляет возможность защиты системного программного обеспечения от прикладных программ.
Любой современный процессор может предложить защиту подобного рода. Она реализуется в виде нескольких уровней. Каждый из уровней играет свою роль и некоторые операции запрещены к исполнению на более низких уровнях. Программный код может переходить с одного уровня на другой ограниченным числом способов. Unix системы спроектированы так, что они в состоянии использовать в своих интересах два таких уровня. Все современные процессоры имеют по меньшей мере два уровня защиты, а в некоторых, например x86 -- их больше. При наличии у процессора более чем двух уровней защиты используются только два -- высший и низший. Ядро Linux исполняется на высшем уровне (он еще называется привелигированный режим), где позволяется выполнение любых действий. Приложения же исполняются на самом нижнем уровне (так называемый непривелигированный режим), где прямой доступ к аппаратуре и памяти регулируется процессором.
Обычно, о режимах исполнения, мы говорим как пространство ядра и пространство пользователя. Эти два понятия охватывают не только два режима исполнения, но так же и то, что каждый из режимов имеет свое собственное отображение памяти -- свое собственное адресное пространство.
Unix производит переключение из пространства пользователя в пространство ядра всякий раз, когда приложение делает системный вызов или приостанавливается аппаратным прерыванием. Код ядра, исполняющий системный вызов, работает в контексте процесса -- от имени вызвавшего процесса и имеет доступ к данным в адресном пространстве процесса. Код, который обрабатывает прерывание, наоборот, являясь асинхронным по своей природе, не относится ни к одному из процессов.
Основное назначение модулей -- расширение функциональности ядра. Код модуля исполняется в пространстве ядра. Обычно модуль реализует обе, рассмотренные выше задачи -- одни функции выполняются как часть системных вызовов, другие -- выполняют обработку прерываний.
2.1.2. Конкуренция в ядре
Еще одно очень важное отличие драйверов устройств от большинства прикладных программ -- возможность конкурирующих вызовов. Если приложение обычно исполняется последовательно, от начала до конца, особо не беспокоясь об изменениях, наступивших в его окружении, то в случае с драйвером все не так просто. Код ядра работает в намного более сложном и запутанном мире и он должен быть написан в предположении одновременного наступления многих событий.
Существует несколько причин появления конкуренции. Естественно, Linux является многозадачной системой, в которой одновременно исполняется множество процессов, из которых два и более могут одновременно обратиться к вашему драйверу. Большинство устройств могут генерировать прерывания. Обработчики прерываний работают асинхронно и могут быть вызваны в тот момент, когда ваш драйвер занят выполнением каких-либо действий. Некоторые программные абстракции (такие как таймеры, описываемые в Гл. 6 ) так же работают асинхронно. Более того, Linux может работать на многопроцессорных системах, в результате конкурирующие запросы к вашему драйверу могут исполняться на нескольких процессорах.
Всвязи с вышеизложенным, код ядра, а значит и код драйвера, должен быть реентерабельным -- он должен предусматривать возможность повторного вхождения в более чем одном контексте одновременно. Структуры данных должны быть тщательно спроектированы так, чтобы имелась возможность многопоточной работы, а код должен обращаться к разделяемым данным таким образом, чтобы не исказить их. Кроме того, вам не следует забывать о возможности взаимоблокировки разных вызовов при неудачном выборе порядка захвата ресурсов. Все примеры драйверов в данной книге написаны в реентерабельном стиле и мы будем пояснять используемые нами методы по мере необходимости.
Очень часто программисты ошибочно полагают, что конкуренция не является проблемой до тех пор, пока исполняемый код не может быть вытеснен (или заблокирован). И действительно, ядро не может быть вытеснено (nonpreemptive), за одним очень важным исключением -- обработка прерываний, когда управление передается обработчику вне зависимости от желаний ядра. На ранних этапах развития ядра, невытесняемость в большинстве случаев обеспечивала отсутствие нежелательной конкуренции. Однако, на многопроцессорных системах, она (вытесняемость) перестала быть гарантией от появления конкурирующих вызовов.
Если ваш код построен на предположении, что он не может быть вытеснен, то он не будет корректно работать на многопроцессорных системах. Даже если в вашем распоряжении нет такой системы, то это не значит, что ее нет у других, которые пользуются вашим модулем. В будущем вполне возможно, что ядро станет вытесняемым, с этого момента даже однопроцессорные системы будут сталкиваться с проблемой конкуренции (некоторые изменения в этом направлении уже присутствуют в ядре). Таким образом, более благоразумным будет рассматривать возможность работы модуля в многопроцессорной среде.
2.1.3. Текущий процесс
Хотя модули ядра исполняются не последовательно, как прикладные задачи, тем не менее
в большинстве случаев их действия связаны с определенным процессом. Код ядра может
определить текущий процесс, обратившийся к модулю, через глобальный элемент
current
-- указатель на struct
task_struct
, который в ядре 2.4 объявляется в файле
<asm/current.h>
. Указатель
current
ссылается на текущий пользовательский процесс.
В процессе выполнения системных вызовов, таких как read
или write
, текущим считается
процесс, сделавший этот вызов. Ядро может воспользоваться информацией о текущем процессе,
используя указатель current
, если возникнет
такая необходимость. Пример будет представлен в разделе Управление доступом к файлам устройств в главе 5.
Фактически current
более не является глобальной переменной
ядра, как это было ранее. Разработчики оптимизировали доступ к структуре, описывающей
текущий процесс, перенеся ее на стек. Реализацию current
вы найдете в файле <asm/current.h>
. Но прежде, чем
вы отправитесь исследовать этот файл, вы должны запомнить, что Linux -- это SMP-совместимая
система (от англ. SMP -- Symmetric Multi-Processing) и потому простая глобальная переменная
здесь просто неприменима. Детали реализации находятся в других подсистемах ядра и
все же, драйвер устройства может подключить заголовочный файл
<linux/sched.h>
и обращаться к указателю
current
.
С точки зрения модуля, current
-- обычная внешняя ссылка,
как например printk
. Модуль может обращаться к
current
всякий раз, когда сочтет это необходимым.
Например, следующий код выведет идентификатор (ID) процесса и имя команды, запустившей
процесс:
printk("The process is \"%s\" (pid %i)\n", current->comm, current->pid);
Имя команды хранится в поле current->comm
и
представляет собой имя файла программы.
2.2. Компиляция и загрузка
Остальная часть главы посвящена созданию законченного модуля. Этот модуль не принадлежит ни одному из классов, описанных в разделе Классы устройств и модулей главы 1. Этот модуль называется skull, сокращенно от "Simple Kernel Utility for Loading Localities".
Ранее мы уже познакомились с функциями init_module
и
cleanup_module
. Теперь мы создадим
Makefile
, с помощью которого будем собирать свой модуль.
Для начала определим символ препроцессора __KERNEL__
,
прежде чем будем поключать какие-либо заголовочные файлы. Как уже говорилось ранее,
большая часть заголовочных файлов ядра недоступна без этого определения.
Еще один очень важный символ -- MODULE
, который
должен быть определен до подключения файла
linux/module.h
(за исключением случаев, когда
драйвер компилируется с ядром статически). Эта книга не рассматривает вопрос
статической компиляции драйверов в ядро, поэтому в наших примерах всегда необходимо
определять символ MODULE
.
Если вы собираете модуль для многопроцессорной системы, то необходимо определить
символ __SMP__
до подключения заголовочных
файлов. Начиная с версии 2.2 выбор между однопроцессорным и многопроцессорным вариантами
можно определить через конфигурационный файл. Таким образом, определение символа
__SMP__
можно оформит так:
#include <linux/config.h> #ifdef CONFIG_SMP #define _ _SMP_ _ #endif
Вам так же надлежить указать флаг компилятора -O
,
потому что некоторые из функций объявлены в заголовочных файлах как
inline
, а gcc
не сможет развернуть эти функции, если оптимизация запрещена. Кроме того, вам
может понадобиться флаг -g
, чтобы иметь возможность
отладки кода, который использует inline
функции.
[3]
Вам так же необходимо убедиться, что версия компилятора соответствует версии ядра. Требования
к версии компилятора вы найдете в файле Documentation/Changes
.
Процессы развития компилятора и ядра протекают одновременно, только разработки ведутся
разными командами, поэтому иногда изменения в компиляторе могут привести к появлению ошибок
в ядре. Некоторые дистрибутивы распространяются с новейшими версиями компилятора, который
пока еще плохо справляется со сборкой ядра. В этом случае в дистрибутив, как правило,
вкладывается отдельный пакет с компилятором (обычно под именем
kgcc
), предназначенным для сборки ядра.
И наконец, чтобы предотвратить появление досадных ошибок, мы предлагаем использовать
флаг Wall
(all warnings -- все предупреждения), который
поможет вам исправить все участки кода, порождающие предупреждения компилятора,
пусть даже они вызваны лишь привычным для вас стилем оформления программ.
При написании кода ядра предпочтительным, безусловно является стиль, присущий
самому Линасу Торвальдсу. Забавные и поучительные заметки по стилю оформления
исходного кода вы найдете в файле Documentation/CodingStyle
.
Все предлагаемые флаги компилятора лучше всего записать в виде переменной окружения
CFLAGS, которую использует утилита make
.
Кроме переменной CFLAGS, Makefile должен содержать правило для сборки объектного файла
модуля из нескольких объектных файлов. Это правило потребуется только в том случае,
если исходные тексты модуля хранятся в нескольких файлах, что нехарактерно для
модулей. Если исходный текст вашего модуль находится в единственном файле, то можете
просто пропустить правило, которое содержит ld -r
.
[4]
# Измените, если это необходимо, путь к исходным текстам ядра # или укажите его в командной строке, при вызове утилиты "make" KERNELDIR = /usr/src/linux include $(KERNELDIR)/.config CFLAGS = -D_ _KERNEL_ _ -DMODULE -I$(KERNELDIR)/include -O -Wall ifdef CONFIG_SMP CFLAGS += -D_ _SMP_ _ -DSMP endif all: skull.o skull.o: skull_init.o skull_clean.o $(LD) -r $^ -o $@ clean: rm -f *.o *~ core
Если вы не знакомы с утилитой make
, у вас наверняка
возникнет вопрос: "А где же правила, управляющие компиляцией .c файлов?"
Дело в том, что явное указание этих правил необязательно, поскольку
make
по-умолчанию формирует .o файлы из .c, без
необходимости явного указания правил. Во время компиляции используются
компилятор $(CC) и флаги компиляции $(CFLAGS).
Примечания
[1] | От переводчика: Сразу же, с первого же примера начинают сказываться отличия между веткой 2.4 и 2.6. Теперь для ядер серии 2.6 собрать модуль стало чуть-чуть сложнее. И данный пример не сработает, если у вас система построена на ядре 2.6. Переработанный вариант примера модуля "HelloWorld" выглядит так: #include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> static int hello_init(void) { printk("<1> Hello, world\n"); return 0; } static void hello_exit(void) { printk("<1> Goodbye, cruel world\n"); } module_init(hello_init); module_exit(hello_exit); Кроме того, для его сборки необходимо будет создать Makefile, в котором содержится всего одна строчка: obj-m := hello.o А процесс сборки, загрузки и выгрузки модуля будет выглядеть так: root# Однако на консоль сообщения не выводятся. Лицезреть вы их сможете, просмотрев системный журнал (лог-файл). Если в системном журнале появилось сообщение "hello: module license 'unspecified' taints kernel." которое выводит механизм контроля версий, то просто добавьте, в файл с исходным текстом модуля, строчку MODULE_LICENSE("Dual BSD/GPL"); |
[2] | Кроме того, в ядрах версий 2.0 и 2.2 нет поддержки спецификаторов формата L и Z, они появились только в ядрах версии 2.4. |
[3] | При этом не следует использовать слишком агрессивную оптимизацию.
Использование уровня оптимизации выше чем |
[4] | От переводчика:
Аналогичный Makefile для ядра серии 2.6:
ifneq ($(KERNELRELEASE),) obj-m := skull.o skull-objs := skull_init.o skull_clean.o else KDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) default: $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules clean: rm -f *.o *~ core endifПроцесс сборки запускается простой командой make .
Если модуль состоит из единственного исходного файла -- уберите строчку
module-objs := skull_init.o skull_clean.o
|