LinuxKernelProgramming — различия между версиями
(не показано 50 промежуточных версий 5 участников) | |||
Строка 1: | Строка 1: | ||
Лектор - Кринкин Кирилл Владимирович | Лектор - Кринкин Кирилл Владимирович | ||
− | = | + | Посещаемость - [https://docs.google.com/spreadsheet/ccc?key=0Ai0vg3CTwO9wdFBxLVcxZ21BamVmaUhhT2x1aUhsUEE linux-kernel-se-1H2012] |
− | == | + | == Подготовка к работе == |
− | == Установка OpenSSH-Server == | + | Все работы рекомендуется проводить в виртуальном окружении, для этого можно использовать [https://www.virtualbox.org/wiki/Downloads VirtualBox]. |
+ | |||
+ | Для сдачи работ надо использовать git на гуглкоде http://code.google.com/p/linux-kernel-course/ | ||
+ | |||
+ | Для этого скачиваем ядро, | ||
+ | <source lang="bash"> | ||
+ | git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git | ||
+ | </source> | ||
+ | и откатываемся к коммиту '''c579bc7e316e7e3f3b56df5e17f623325caa9783''' | ||
+ | <source lang="bash"> | ||
+ | git reset --hard c579bc7e316e7e3f3b56df5e17f623325caa9783 | ||
+ | </source> | ||
+ | а в конце отсылаем результаты домашней работы в формате, полученном командой | ||
+ | <source lang="bash"> | ||
+ | git format-patch c579bc7e316e7e3f3b56df5e17f623325caa9783 | ||
+ | </source> | ||
+ | |||
+ | Либо, не откатываемся к коммиту, а делаем от него ветку | ||
+ | <source lang="bash"> | ||
+ | git checkout c579bc7e316e7e3f3b56df5e17f623325caa9783 -b spbau-lkp | ||
+ | </source> | ||
+ | и делаем патч | ||
+ | <source lang="bash"> | ||
+ | git format-patch master | ||
+ | </source> | ||
+ | |||
+ | Патчи с выполненными заданиями необходимо сдавать через GoogleCode ([[Медиа:LKP-HW-HowTo.pdf|Инструкция]]). | ||
+ | |||
+ | === Установка OpenSSH-Server в VirtualBox === | ||
Для того, чтобы получить возможность пользоваться буфером обмена (вставлять текст в консоль) и не ставить VirtualBox Guest Additions, можно установить в виртуальную машину OpenSSH-Server, и подключаться к нему любым SSH клиентом. | Для того, чтобы получить возможность пользоваться буфером обмена (вставлять текст в консоль) и не ставить VirtualBox Guest Additions, можно установить в виртуальную машину OpenSSH-Server, и подключаться к нему любым SSH клиентом. | ||
Для этого необходимо добавить новую сетевую карту в виртуальную машину. Это делается в настройках, ДО ВКЛЮЧЕНИЯ виртуальной машины: в настройках нужно перейти на страницу с настройками сети, перейти на вкладку со вторым адаптером (первый адаптер используется виртуальной машиной для доступа к интернет через NAT) и выбрать подключение "Host-only adapter" (смотри пример на картинке). | Для этого необходимо добавить новую сетевую карту в виртуальную машину. Это делается в настройках, ДО ВКЛЮЧЕНИЯ виртуальной машины: в настройках нужно перейти на страницу с настройками сети, перейти на вкладку со вторым адаптером (первый адаптер используется виртуальной машиной для доступа к интернет через NAT) и выбрать подключение "Host-only adapter" (смотри пример на картинке). | ||
[[Файл:HostOnlyAdapter.png|Настройка дополнительной сетевой карты в VirtualBox]] | [[Файл:HostOnlyAdapter.png|Настройка дополнительной сетевой карты в VirtualBox]] | ||
+ | |||
+ | Если VirtualBox выдает ошибку при выборе "Host-only Adapter", то вероятнее всего это связано с тем, что не установлен виртуальный адаптер. Добавить его можно в сетевых настройках VirtualBox (File->Preferences->Networks). | ||
+ | |||
+ | [[Файл:VBoxSettings-Network-2012.png|Добавление виртуального адаптера]] | ||
Теперь необходимо включить на этой сетевой карте возможность получить IP адрес по DHCP. Для этого нужно выполнить следующие две команды (предполагается, что в системе до этого была только одна сетевая карта): | Теперь необходимо включить на этой сетевой карте возможность получить IP адрес по DHCP. Для этого нужно выполнить следующие две команды (предполагается, что в системе до этого была только одна сетевая карта): | ||
− | + | <source lang="bash"> | |
− | + | echo 'allow-hotplug eth1' >> /etc/network/interfaces | |
+ | echo 'iface eth1 inet dhcp' >> /etc/network/interfaces | ||
+ | </source> | ||
и установить OpenSSH-Server | и установить OpenSSH-Server | ||
− | + | <source lang="bash"> | |
− | + | apt-get install openssh-server | |
+ | </source> | ||
Теперь можно из основной системы произвести подключение к виртуальной. Для этого нужно использовать SSH-клиент, такой как [http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html putty] которому нужно сообщить IP адрес виртуальной машины (его можно узнать командой ifconfig, и, скорее всего, это будет 192.168.56.101). | Теперь можно из основной системы произвести подключение к виртуальной. Для этого нужно использовать SSH-клиент, такой как [http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html putty] которому нужно сообщить IP адрес виртуальной машины (его можно узнать командой ifconfig, и, скорее всего, это будет 192.168.56.101). | ||
Строка 25: | Строка 60: | ||
Так же рекомендуется в подпункте Translation пункта Window указывать UTF-8 в качестве Character set translation on received data - в частности, это необходимо для правильного отображения Midnight Commander. | Так же рекомендуется в подпункте Translation пункта Window указывать UTF-8 в качестве Character set translation on received data - в частности, это необходимо для правильного отображения Midnight Commander. | ||
− | == Сборка ядра 3.3.0-rc2 == | + | === Screen === |
+ | |||
+ | Screen позволяет в одном терминале запускать несколько интерактивных шелл-процессов и переключаться между ними. | ||
+ | |||
+ | На скриншоне показано разделение одного терминала на два окна, работающих одновременно: в одном запущен Midnight Commander, в другом вывод команды top. | ||
+ | [[Файл:Screen.png|Пример использования screen]] | ||
+ | |||
+ | Установка | ||
+ | <source lang="bash"> | ||
+ | su -c "apt-get install screen" | ||
+ | </source> | ||
+ | |||
+ | ==== Ключи запуска screen ==== | ||
+ | |||
+ | *-a Включает все возможности | ||
+ | *-A Не хранить размер экрана терминала (по умолчанию, screen старается восстановить исходный размер терминала). | ||
+ | *-r Восстановить подключение к сессии. | ||
+ | *-h num задаёт рамер буфера в количестве линий | ||
+ | *-ls список запущенных сессий | ||
+ | *-s задаёт shell по умолчанию. | ||
+ | *-S sessionname задаёт имя сессии, которое может потом использоваться для её идентификации при подключении. | ||
+ | *-U включает UTF-8. | ||
+ | *-v версия программы | ||
+ | *-wipe [match] убивает мёртвые сессии, и показывает список живых. | ||
+ | *-x позволяет нескольким людям иметь подключение к одному терминалу. | ||
+ | |||
+ | ==== Команды для screen ==== | ||
+ | |||
+ | *<ctrl>+<a> ' - запросит номер окна, на который можно переключиться | ||
+ | *<ctrl>+<a> " - выбаст список окон | ||
+ | *<ctrl>+<a> num - переключится на окно с номером num | ||
+ | *<ctrl>+<a> <ctrl>+<a> - переход к предыдущему окну | ||
+ | *<ctrl>+<a> A - предложет ввести имя окна | ||
+ | *<ctrl>+<a> c - создаёт новое окно шела, и делает его активным | ||
+ | *<ctrl>+<a> d - отсоедининяется от текущей сессии, но потом к ней можно вернутся черех screen -r | ||
+ | *<ctrl>+<a> D D - отсоединяется, и завершает сеанс | ||
+ | *<ctrl>+<a> F - перестраивает размер окна | ||
+ | *<ctrl>+<a> H - включает логирование окна в "screenlog.n" | ||
+ | *<ctrl>+<a> i - вывод информации о активном окне | ||
+ | *<ctrl>+<a> k - убивает активное окно | ||
+ | *<ctrl>+<a> l - обновить | ||
+ | *<ctrl>+<a> space - переход к следующему окну | ||
+ | *<ctrl>+<a> N - выводит номер и заголовок активного окна | ||
+ | *<ctrl>+<a> backspace - переход к предыдущему окну | ||
+ | *<ctrl>+<a> q - послать control-q активному окну | ||
+ | *<ctrl>+<a> s - послать control-s активному окну | ||
+ | *<ctrl>+<a> S - делит окно на 2 части по горизонтале. Для переключения можно использовать <ctrl>+<a> tab. В нижнем окне можно создать новое окно (удобно сравнивать конфиги), или подклчиться к другой сессии. | ||
+ | *<ctrl>+<a> w - выводит список всех окон | ||
+ | *<ctrl>+<a> x - блокировка | ||
+ | *<ctrl>+<a> ? - справочник по комбинациям кнопок | ||
+ | *<ctrl>+<a> C-\ - закрыть всё | ||
+ | *<ctrl>+<a> esc - переключение в "copy/scrollback mode" | ||
+ | *<ctrl>+<a> ] - вставить буфер | ||
+ | *<ctrl>+<a> > - записать буфер в файл | ||
+ | *<ctrl>+<a> * - список всех подсоединенных экранов | ||
+ | |||
+ | === Сборка ядра 3.3.0-rc2 === | ||
Сборка ядра 3.3.0-rc2 (в соответствии с рекомендациями readme) | Сборка ядра 3.3.0-rc2 (в соответствии с рекомендациями readme) | ||
− | + | <source lang="bash"> | |
− | + | wget https://www.kernel.org/pub/linux/kernel/v3.x/testing/linux-3.3-rc2.tar.bz2 | |
− | + | su -c "apt-get install bzip2" | |
− | + | tar -xjf linux-3.3-rc2.tar.bz2 | |
− | + | cd linux-3.3-rc2/ | |
− | + | mkdir -p ~/build/linux-3.3-rc2 | |
− | + | su -c "apt-get install libncurses5-dev" | |
− | + | make O=~/build/linux-3.3-rc2 nconfig | |
− | + | make O=~/build/linux-3.3-rc2 | |
− | + | su -c "make O=~/build/linux-3.3-rc2 modules_install install" | |
− | + | su -c "update-initramfs -c -v -k 3.3.0-rc2" | |
− | + | su -c "update-grub2" | |
+ | shutdown -r now | ||
+ | </source> | ||
+ | |||
+ | Для ускорения сборки, можно отказаться от лишних модулей. Для этого используем конфиг по умолчанию, и добавим в него необходимые (используемые на данный момент) модули. | ||
+ | <source lang="bash"> | ||
+ | make O=~/build/linux-3.3-rc2 defconfig | ||
+ | make O=~/build/linux-3.3-rc2 localmodconfig | ||
+ | make O=~/build/linux-3.3-rc2 nconfig | ||
+ | </source> | ||
− | == | + | == Создание модулей для ядра Linux == |
=== Введение === | === Введение === | ||
Некоторые отличия Ядра от программ, выполняемых в пространстве пользователя: | Некоторые отличия Ядра от программ, выполняемых в пространстве пользователя: | ||
− | * Ядро LINUX пишется с расчётом на компилятор GNU C (разработчики ориентируются на стандарт ISO C99). В коде можно использовать ассемблерные вставки (директива ''asm()''), аннотацию ветвления (''likely()'' - более вероятная ветвь, ''unlikely()'' - менее вероятная) и весьма странный кодстайл. | + | * Ядро LINUX пишется с расчётом на компилятор GNU C (разработчики ориентируются на стандарт ISO C99). В коде можно использовать ассемблерные вставки (директива ''asm()''), аннотацию ветвления (''likely()'' - более вероятная ветвь, ''unlikely()'' - менее вероятная) и весьма странный [http://www.kernel.org/doc/Documentation/CodingStyle кодстайл]. |
* Ядро не имеет доступа к стандартным библиотекам языка программирования C. Это сделано из соображений увеличения скорости выполнения и уменьшения объёма кода. | * Ядро не имеет доступа к стандартным библиотекам языка программирования C. Это сделано из соображений увеличения скорости выполнения и уменьшения объёма кода. | ||
* Отсутствует защита памяти. Если обычная программа предпримет попытку некорректного обращения с памятью, то ядро сможет выгрузить такую программу, но если само ядро предпримет такую же попытку, то его будет некому проконтролировать. Так же важно помнить об отсутствии замещения страниц, т.е. каждому байту, используемому ядром, соответствует байт реальной физической памяти. | * Отсутствует защита памяти. Если обычная программа предпримет попытку некорректного обращения с памятью, то ядро сможет выгрузить такую программу, но если само ядро предпримет такую же попытку, то его будет некому проконтролировать. Так же важно помнить об отсутствии замещения страниц, т.е. каждому байту, используемому ядром, соответствует байт реальной физической памяти. | ||
Строка 54: | Строка 154: | ||
Загружаемый объект ядра называется '''модулем'''. | Загружаемый объект ядра называется '''модулем'''. | ||
Динамическая загрузка и выгрузка модулей по мере необходимости появилась благодаря Питеру Мак-Дональду и впервые была представлена в версии ядра 0.99.<br> | Динамическая загрузка и выгрузка модулей по мере необходимости появилась благодаря Питеру Мак-Дональду и впервые была представлена в версии ядра 0.99.<br> | ||
− | По своей структуре, модуль похож на обычную прогамму (так же имеется точка входа, и | + | По своей структуре, модуль похож на обычную прогамму (так же имеется точка входа, и необходима компиляция в бинарный вид) но имеет прямой доступ к структурам и функциям ядра, в то время как обычные программы такой доступ могут получить только через обёртки. |
− | === Код модуля === | + | === Сборка модуля как отдельного объекта (Kernel object) === |
+ | |||
+ | ==== Код модуля ==== | ||
<source lang="c"> | <source lang="c"> | ||
#include <linux/module.h> // Этот файл подключается в любом модуле по соглашению | #include <linux/module.h> // Этот файл подключается в любом модуле по соглашению | ||
Строка 79: | Строка 181: | ||
MODULE_LICENSE("GPL"); // Указывает на лицензию, под которой распространяется данный модуль | MODULE_LICENSE("GPL"); // Указывает на лицензию, под которой распространяется данный модуль | ||
</source> | </source> | ||
− | === Сборка модуля === | + | ==== Сборка модуля (make) ==== |
Файл с кодом модуля (myModule.c) должен находиться в одной папке с make-фалом, в котом должно быть написано | Файл с кодом модуля (myModule.c) должен находиться в одной папке с make-фалом, в котом должно быть написано | ||
obj-m += myModule.o | obj-m += myModule.o | ||
Тогда сборку модуля можно запустить командой | Тогда сборку модуля можно запустить командой | ||
− | + | <source lang="bash"> | |
+ | make -C ./linux-3.3-rc2 SUBDIRS=$PWD modules | ||
+ | </source> | ||
Внимание, замените путь к исходным кодам ядра на тот, куда вы извлекли содержимое архива linux-3.3-rc2.tar.bz2 | Внимание, замените путь к исходным кодам ядра на тот, куда вы извлекли содержимое архива linux-3.3-rc2.tar.bz2 | ||
В итоге должен получиться файл модуля myModule.ko | В итоге должен получиться файл модуля myModule.ko | ||
− | === | + | ==== Загрузка модуля ==== |
− | Теперь полученный модуль можно загрузить (эта | + | Теперь полученный модуль можно загрузить (эта команда требует прав суперпользователя). Для этого используется команда |
− | + | <source lang="bash"> | |
− | Список загруженных модулей хранится в /proc/modules | + | insmod <имя модуля> |
− | + | </source> | |
− | Выгружать модуль можно командой | + | Список загруженных модулей хранится в /proc/modules (так что можно просмотреть этот файл cat /proc/modules) либо воспользоваться командой |
− | + | <source lang="bash"> | |
+ | lsmod | ||
+ | </source> | ||
+ | Выгружать модуль можно командой (также требует прав суперпользователя) | ||
+ | <source lang="bash"> | ||
+ | rmmod <имя модуля> | ||
+ | </source> | ||
− | === | + | ==== Примечания ==== |
− | == Программа | + | Результат работы модуля выводится в /var/log/syslog, для его просмотра рекомендуется на отдельной консоли использовать |
+ | <source lang="bash"> | ||
+ | tail -f /var/log/syslog | ||
+ | </source> | ||
+ | |||
+ | === Сборка модуля вместе с ядром (Kbuild) === | ||
+ | |||
+ | Модули для ядра можно собирать при помщи системы Kbuild. инструкции для сборки должны находиться в файле Kconfig. | ||
+ | |||
+ | Что бы подключить наш Kconfig, нужно в файл linux-3.3-rc2/arch/x86/Kconfig.debug добавить строчку (рекомендуется сделать это где-нибудь в начле файла, например 6-й строкой) | ||
+ | source "SPbAU/Kconfig" | ||
+ | Это позволит включать и отключать сборку наших модулей в меню '''Kernel hacking''', что в общем-то логично. | ||
+ | |||
+ | Важно заметить, что если в makefile-файлах относительный путь задавался от места размещения самого файла, то в Kconfig - относительные пути строятся от корневого каталога дерева исходных кодов (в данном случае, от linux-3.3-rc2). | ||
+ | |||
+ | Теперь, в дереве исходных кодов создаём каталог SPbAU, переносим туда (из предыдущего раздела) myModule.c и Makefile, а так же создаём файл Kconfig. | ||
+ | |||
+ | ==== Файл Kconfig ==== | ||
+ | Файл Kconfig должен содержать следующий код | ||
+ | |||
+ | <source lang="text"> | ||
+ | config SPbAU_KERNEL | ||
+ | bool "Modules of students of the St. Petersburg Academic University of the Russian Academy of Sciences" | ||
+ | default y | ||
+ | ---help--- | ||
+ | SPbAU module list | ||
+ | |||
+ | config SPbAU_KERNEL_PRINTHW | ||
+ | tristate "The first module =)" | ||
+ | depends on SPbAU_KERNEL | ||
+ | default y | ||
+ | ---help--- | ||
+ | Function prints the text "Hello, world" to the syslog file. | ||
+ | </source> | ||
+ | |||
+ | В первой строке мы объявляем о том, что хотим сделать раздел SPbAU_KERNEL (очень важно следить, что бы имена не перекрывались, по этому рационально использовать свой префикс, типа SPbAU). Потом мы указываем что пункт является bool, т.е. способен принимать значения Y или N (подробнее о значениях будет сказано чуть ниже), и определено имя этого раздела в меню. По умолчанию, раздел отмечен как ИСТИНА. Далее идёт справачная информация, которая может занимать несколько строк (необходимо соблюдать отступ в 1 пробел от основной линии). | ||
+ | |||
+ | Потом мы описываем свой модуль. Ключевое слово ''tristate'' говорит о том, что этот пункт меню будет принимать одно из трёх возможных значения Y/M/N. Строчка depends on SPbAU_KERNEL показывает, что этот модуль не может быть установлен (и даже пункт не будет отображаться в меню), если в прерыщем пункте (SPbAU_KERNEL) стоит значение, отличное от истины. Это позволяет группировать различные элементы меню, уменьшая общую длинну списка. | ||
+ | |||
+ | Возможные значения изначального состояния пунктов меню: | ||
+ | *Y - модуль вкомпилируется в ядро (это произайдёт успешно, если не допускать перекрытия имён точек входа) | ||
+ | *M - модуль компилируется отдельным файлом (kernel object) | ||
+ | *N - модуль не вкомпилируется | ||
+ | |||
+ | Более подробно о языке формирования Kconfig файлов можно прочитать [http://kernel.org/doc/Documentation/kbuild/kconfig-language.txt здесь]. | ||
+ | |||
+ | ==== Файл Makefile ==== | ||
+ | |||
+ | В Makefile необходимо добавить строчку | ||
+ | obj-$(SPbAU_KERNEL_PRINTHW) += SPbAU/ | ||
+ | |||
+ | В файле Kconfig для мы указали tristate, т.е. пользователь может выбрать M и собрать отдельно модуль. Тогда команда ''make modules_install'' разместит .ko-файл в указанной директории (SPbAU), которая будет размещена по адресу /lib/modules/3.3.0-rc2/kernel/, т.е. общий путь к модулю будет /lib/modules/3.3.0-rc2/kernel/SPbAU/myModule.ko | ||
+ | |||
+ | Makefile тоже необходимо подключить | ||
+ | |||
+ | ==== Сборка ядра с модулем ==== | ||
+ | Вернувшись в корень дерева исходных кодов, вызываем меню | ||
+ | <source lang="bash"> | ||
+ | make menuconfig | ||
+ | </source> | ||
+ | |||
+ | Это приведёт к выводу на экран графического меню | ||
+ | |||
+ | [[Файл:Menuconfig.png|Вывод menuconfig]] | ||
+ | |||
+ | Нас интересует раздел Kernel hacking, куда мы положили свои команды сборки модуля | ||
+ | |||
+ | [[Файл:Menuconfig_Kernel_hacking.png|Вывод раздела Kernel hacking]] | ||
+ | |||
+ | Отметка на пункте "Modules of students of the St. Petersburg Academic University of the Russian Academy of Sciences" позволяет скрывать все наши модули (в данном случае модуль всего 1), а отметка на пункте "The first module =)" может принимать как значение, соответствующее сборке отдального модуля, так и сборки внутри ядра. | ||
+ | |||
+ | == Процессы и их дескрипторы == | ||
+ | |||
+ | === Процесс === | ||
+ | Процесс - это исполняющаяся программа. | ||
+ | |||
+ | Он состоит из: | ||
+ | * исполняемого программного кода; | ||
+ | * набора ресурсов (к примеру, открытые файлы); | ||
+ | * внутренние данные ядра; | ||
+ | * адресное пространство потоков исполнения (threads of execution); | ||
+ | * секции данных (с глобальными переменными). | ||
+ | |||
+ | === Описатель (дескриптор) процесса === | ||
+ | С каждым процессом, ассоциирован описатель (дескриптор) процесса, который используется для отслеживания процесса в памяти. | ||
+ | |||
+ | Дескриптор процесса содержит: | ||
+ | * идентификатор процесса (PID); | ||
+ | * состояние процесса, которое определено в переменной state и может принимать одно из следующих значений: | ||
+ | ** 0 - TASK_RUNNING | ||
+ | ** 1 - TASK_INTERRUPTIBLE | ||
+ | ** 2 - TASK_UNINTERRUPTIBLE | ||
+ | ** 4 - TASK_STOPPED | ||
+ | ** 8 - TASK_TRACED | ||
+ | ** 16 - EXIT_ZOMBIE | ||
+ | ** 32 - EXIT_DEAD | ||
+ | ** 64 - TASK_NONINTERACTIVE | ||
+ | * ссылки на родительский и дочерние процессы; | ||
+ | * регистры процессора; | ||
+ | * список открытых файлов; | ||
+ | * информацию об адресном пространстве; | ||
+ | |||
+ | |||
+ | Дескриптор процесса определён структурой [http://lxr.linux.no/#linux+v3.3.1/include/linux/sched.h#L1237 struct task_struct] (объявлено в файле [http://lxr.linux.no/#linux+v3.3.1/include/linux/sched.h linux/sched]). | ||
+ | |||
+ | Для доступа к процессам, все дескрипторы организованы в циклический замкнутый двусвязный список struct list_head, который объявлен в linux/list.h и имеет следующий вид. | ||
+ | <source lang="c"> | ||
+ | struct list_head | ||
+ | { | ||
+ | struct list_head *next, *prev; | ||
+ | }; | ||
+ | </source> | ||
+ | |||
+ | === Макросы для работы с процессами === | ||
+ | |||
+ | Дескриптор процесса init задаётся статично | ||
+ | <source lang="c"> | ||
+ | extern struct task_struct init_task; | ||
+ | </source> | ||
+ | Двигаясь по указателям в какую-либо сторону от этого дескриптора, можно обойти все дескрипторы, и вернуться в исходный. | ||
+ | |||
+ | Для удобного обхода существует несколько макросов. | ||
+ | |||
+ | ==== for_each_process() ==== | ||
+ | Макрос определен в linux/sched.h и используется для обхода всего списка задач, пока не достигнута исходная. | ||
+ | <source lang="c"> | ||
+ | #define for_each_process(p) for (p = &init_task ; (p = next_task(p)) != &init_task ; ) | ||
+ | </source> | ||
+ | |||
+ | ==== next_task() ==== | ||
+ | Макрос определен в linux/sched.h и возвращает следующую задачу из списка. | ||
+ | <source lang="c"> | ||
+ | #define next_task(p) list_entry((p)->tasks.next, struct task_struct, tasks) | ||
+ | </source> | ||
+ | |||
+ | ==== list_entry() ==== | ||
+ | Определен в linux/list.h | ||
+ | <source lang="c"> | ||
+ | /* | ||
+ | * list_entry - get the struct for this entry | ||
+ | * @ptr: the &struct list_head pointer. | ||
+ | * @type: the type of the struct this is embedded in. | ||
+ | * @member: the name of the list_struct within the struct. | ||
+ | */ | ||
+ | #define list_entry(ptr, type, member) container_of(ptr, type, member) | ||
+ | </source> | ||
+ | |||
+ | ==== container_of() ==== | ||
+ | <source lang="c"> | ||
+ | #define container_of(ptr, type, member) \ | ||
+ | ({ \ | ||
+ | const typeof( ((type *)0)->member ) *__mptr = (ptr); \ | ||
+ | (type *)( (char *)__mptr - offsetof(type,member) ); \ | ||
+ | }) \ | ||
+ | </source> | ||
+ | |||
+ | ==== current ==== | ||
+ | Этот макрос является указателем на task_struct текущего исполняющегося процесса, и работает при помощи current_thread_info(), определенной в asm/thread_info.h | ||
+ | <source lang="c"> | ||
+ | /* how to get the thread information struct from C */ | ||
+ | static inline struct thread_info *current_thread_info(void) | ||
+ | { | ||
+ | struct thread_info *ti; | ||
+ | __asm__("andl %%esp,%0; ":"=r" (ti) : "0" (~(THREAD_SIZE - 1))); | ||
+ | return ti; | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | ==== do_each_thread ==== | ||
+ | <source lang="c"> | ||
+ | /* | ||
+ | * Careful: do_each_thread/while_each_thread is a double loop so | ||
+ | * 'break' will not work as expected - use goto instead. | ||
+ | */ | ||
+ | #define do_each_thread(g, t) \ | ||
+ | for (g = t = &init_task ; (g = t = next_task(g)) != &init_task ; ) do | ||
+ | |||
+ | |||
+ | #define while_each_thread(g, t) \ | ||
+ | while ((t = next_thread(t)) != g) | ||
+ | </source> | ||
+ | |||
+ | |||
+ | ==== next_thread ==== | ||
+ | <source lang="c"> | ||
+ | static inline struct task_struct *next_thread(const struct task_struct *p) | ||
+ | { | ||
+ | return list_entry_rcu(p->thread_group.next, struct task_struct, thread_group); | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | === Пример реализации модуля === | ||
+ | Используя макросы, мы можем написать модуль, который обходит все существующий в системе процессы, и выводит их список в var/log/syslog | ||
+ | <source lang="c"> | ||
+ | #include <linux/module.h> // Этот файл подключается в любом модуле по соглашению | ||
+ | #include <linux/kernel.h> // Содержит макросы для функции printk() | ||
+ | #include <linux/init.h> // Содержит определения макросов __init и __exit | ||
+ | #include <linux/sched.h> | ||
+ | void show_proc_list(void) // Функция для вывода всех имеющихся процессов | ||
+ | { | ||
+ | struct task_struct *task; | ||
+ | for_each_process(task) | ||
+ | { | ||
+ | printk("%s [%d]\n",task->comm , task->pid); | ||
+ | } | ||
+ | } | ||
+ | static int __init start(void) // Точка входа в модуль | ||
+ | { | ||
+ | show_proc_list(); // Вызов функции | ||
+ | return 0; // в случае успешной загрузки возвращать нулевое значение | ||
+ | } | ||
+ | static void __exit stop(void) // Точка выхода | ||
+ | { | ||
+ | //printk("Module unloaded\n"); | ||
+ | } | ||
+ | module_init(start); | ||
+ | module_exit(stop); | ||
+ | MODULE_LICENSE("GPL"); // Указывает на лицензию, под которой распространяется данный модуль | ||
+ | </source> | ||
+ | |||
+ | == Netlink == | ||
+ | |||
+ | Пример использования протокола Netlink из пространства пользователя | ||
+ | Программа, получающая список сетевых интерфейсов через RtNetlink: | ||
+ | |||
+ | <source lang="c"> | ||
+ | #include <stdio.h> | ||
+ | #include <string.h> | ||
+ | #include <errno.h> | ||
+ | |||
+ | #include <unistd.h> | ||
+ | |||
+ | #include <sys/socket.h> | ||
+ | #include <linux/netlink.h> | ||
+ | #include <linux/rtnetlink.h> | ||
+ | |||
+ | // ================================================================================ | ||
+ | |||
+ | static char buf[10240]; | ||
+ | |||
+ | int main(int argc, char** argv) { | ||
+ | int nlsock; | ||
+ | struct sockaddr_nl nla; | ||
+ | |||
+ | nlsock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE); | ||
+ | if (nlsock < 0) { | ||
+ | printf("Failed to create a Netlink socket: %s\n", strerror(errno)); | ||
+ | return 1; | ||
+ | } | ||
+ | |||
+ | nla.nl_family = AF_NETLINK; | ||
+ | nla.nl_pad = 0; | ||
+ | nla.nl_pid = 0; | ||
+ | nla.nl_groups = 0; | ||
+ | if (connect(nlsock, (const struct sockaddr*)&nla, sizeof(nla)) < 0) { | ||
+ | printf("Failed to connect to the kernel rtnetlink: %s\n", strerror(errno)); | ||
+ | goto close_sock; | ||
+ | } | ||
+ | |||
+ | struct nlmsghdr* nlh = (struct nlmsghdr*)buf; | ||
+ | nlh->nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); | ||
+ | nlh->nlmsg_type = RTM_GETLINK; | ||
+ | nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; | ||
+ | nlh->nlmsg_seq = 0; | ||
+ | nlh->nlmsg_pid = getpid(); | ||
+ | |||
+ | struct ifinfomsg* msg = (struct ifinfomsg*)NLMSG_DATA(nlh); | ||
+ | msg->ifi_family = AF_UNSPEC; | ||
+ | msg->ifi_index = 1; | ||
+ | msg->ifi_change = 0xFFFFFFFF; | ||
+ | |||
+ | if (send(nlsock, buf, nlh->nlmsg_len, 0) < 0) { | ||
+ | printf("Failed to send a Netlink message: %s\n", strerror(errno)); | ||
+ | goto close_sock; | ||
+ | } | ||
+ | |||
+ | int sz = recv(nlsock, buf, sizeof(buf), 0); | ||
+ | |||
+ | while (NLMSG_OK(nlh, sz) && nlh->nlmsg_type != NLMSG_DONE) { | ||
+ | if (nlh->nlmsg_type & NLMSG_ERROR) { | ||
+ | struct nlmsgerr* e = (struct nlmsgerr*)NLMSG_DATA(nlh); | ||
+ | printf("Failed to get a link: %d (%s)\n", e->error, strerror(-e->error)); | ||
+ | goto close_sock; | ||
+ | } | ||
+ | |||
+ | if (nlh->nlmsg_type & RTM_NEWLINK) { | ||
+ | struct rtattr* pattr; | ||
+ | struct ifinfomsg* msg = (struct ifinfomsg*)NLMSG_DATA(nlh); | ||
+ | |||
+ | pattr = (struct rtattr*)(msg + 1); | ||
+ | int alen = (char*)(nlh + nlh->nlmsg_len) - (char*)pattr; | ||
+ | |||
+ | while (RTA_OK(pattr, alen)) { | ||
+ | if (pattr->rta_type == IFLA_IFNAME) { | ||
+ | printf("Interface %d, name %.*s\n", msg->ifi_index, RTA_PAYLOAD(pattr), RTA_DATA(pattr)); | ||
+ | } | ||
+ | |||
+ | pattr = RTA_NEXT(pattr, alen); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | nlh = NLMSG_NEXT(nlh, sz); | ||
+ | } | ||
+ | |||
+ | close_sock: | ||
+ | close(nlsock); | ||
+ | |||
+ | return 0; | ||
+ | } | ||
+ | </source> | ||
== Список литературы == | == Список литературы == | ||
Строка 105: | Строка 524: | ||
* Разработка ядра Linux. Роберт Лав (http://www.ozon.ru/context/detail/id/2918313/) | * Разработка ядра Linux. Роберт Лав (http://www.ozon.ru/context/detail/id/2918313/) | ||
* Ядро Linux. Д. Бовет, М. Чезати (http://www.ozon.ru/context/detail/id/3589107/) | * Ядро Linux. Д. Бовет, М. Чезати (http://www.ozon.ru/context/detail/id/3589107/) | ||
− | * Linux Device Drivers, Джонатан Корбет, Алесандро Рубини, Грег Кроа-Хартман (http://lwn.net/Kernel/LDD3/) | + | * Linux Device Drivers, Джонатан Корбет, Алесандро Рубини, Грег Кроа-Хартман (http://lwn.net/Kernel/LDD3/) ''officiel!'' |
+ | |||
+ | == Further Reading == | ||
+ | * [http://lxr.free-electrons.com/ Linux Cross Reference] |
Текущая версия на 22:56, 31 мая 2012
Лектор - Кринкин Кирилл Владимирович
Посещаемость - linux-kernel-se-1H2012
Содержание
Подготовка к работе
Все работы рекомендуется проводить в виртуальном окружении, для этого можно использовать VirtualBox.
Для сдачи работ надо использовать git на гуглкоде http://code.google.com/p/linux-kernel-course/
Для этого скачиваем ядро,
git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
и откатываемся к коммиту c579bc7e316e7e3f3b56df5e17f623325caa9783
git reset --hard c579bc7e316e7e3f3b56df5e17f623325caa9783
а в конце отсылаем результаты домашней работы в формате, полученном командой
git format-patch c579bc7e316e7e3f3b56df5e17f623325caa9783
Либо, не откатываемся к коммиту, а делаем от него ветку
git checkout c579bc7e316e7e3f3b56df5e17f623325caa9783 -b spbau-lkp
и делаем патч
git format-patch master
Патчи с выполненными заданиями необходимо сдавать через GoogleCode (Инструкция).
Установка OpenSSH-Server в VirtualBox
Для того, чтобы получить возможность пользоваться буфером обмена (вставлять текст в консоль) и не ставить VirtualBox Guest Additions, можно установить в виртуальную машину OpenSSH-Server, и подключаться к нему любым SSH клиентом. Для этого необходимо добавить новую сетевую карту в виртуальную машину. Это делается в настройках, ДО ВКЛЮЧЕНИЯ виртуальной машины: в настройках нужно перейти на страницу с настройками сети, перейти на вкладку со вторым адаптером (первый адаптер используется виртуальной машиной для доступа к интернет через NAT) и выбрать подключение "Host-only adapter" (смотри пример на картинке).
Если VirtualBox выдает ошибку при выборе "Host-only Adapter", то вероятнее всего это связано с тем, что не установлен виртуальный адаптер. Добавить его можно в сетевых настройках VirtualBox (File->Preferences->Networks).
Теперь необходимо включить на этой сетевой карте возможность получить IP адрес по DHCP. Для этого нужно выполнить следующие две команды (предполагается, что в системе до этого была только одна сетевая карта):
echo 'allow-hotplug eth1' >> /etc/network/interfaces
echo 'iface eth1 inet dhcp' >> /etc/network/interfaces
и установить OpenSSH-Server
apt-get install openssh-server
Теперь можно из основной системы произвести подключение к виртуальной. Для этого нужно использовать SSH-клиент, такой как putty которому нужно сообщить IP адрес виртуальной машины (его можно узнать командой ifconfig, и, скорее всего, это будет 192.168.56.101).
Важно помнить, что по умолчанию SSH не позволяет подключаться, используя учётную запись root. Если необходимы права суперпользователя, в начале подключитесь используя свою учётную запись на виртуальной машине (приняв перед этим сертификат безопасности), а потом используйте su.
Так же рекомендуется в подпункте Translation пункта Window указывать UTF-8 в качестве Character set translation on received data - в частности, это необходимо для правильного отображения Midnight Commander.
Screen
Screen позволяет в одном терминале запускать несколько интерактивных шелл-процессов и переключаться между ними.
На скриншоне показано разделение одного терминала на два окна, работающих одновременно: в одном запущен Midnight Commander, в другом вывод команды top.
Установка
su -c "apt-get install screen"
Ключи запуска screen
- -a Включает все возможности
- -A Не хранить размер экрана терминала (по умолчанию, screen старается восстановить исходный размер терминала).
- -r Восстановить подключение к сессии.
- -h num задаёт рамер буфера в количестве линий
- -ls список запущенных сессий
- -s задаёт shell по умолчанию.
- -S sessionname задаёт имя сессии, которое может потом использоваться для её идентификации при подключении.
- -U включает UTF-8.
- -v версия программы
- -wipe [match] убивает мёртвые сессии, и показывает список живых.
- -x позволяет нескольким людям иметь подключение к одному терминалу.
Команды для screen
- <ctrl>+<a> ' - запросит номер окна, на который можно переключиться
- <ctrl>+<a> " - выбаст список окон
- <ctrl>+<a> num - переключится на окно с номером num
- <ctrl>+<a> <ctrl>+<a> - переход к предыдущему окну
- <ctrl>+<a> A - предложет ввести имя окна
- <ctrl>+<a> c - создаёт новое окно шела, и делает его активным
- <ctrl>+<a> d - отсоедининяется от текущей сессии, но потом к ней можно вернутся черех screen -r
- <ctrl>+<a> D D - отсоединяется, и завершает сеанс
- <ctrl>+<a> F - перестраивает размер окна
- <ctrl>+<a> H - включает логирование окна в "screenlog.n"
- <ctrl>+<a> i - вывод информации о активном окне
- <ctrl>+<a> k - убивает активное окно
- <ctrl>+<a> l - обновить
- <ctrl>+<a> space - переход к следующему окну
- <ctrl>+<a> N - выводит номер и заголовок активного окна
- <ctrl>+<a> backspace - переход к предыдущему окну
- <ctrl>+<a> q - послать control-q активному окну
- <ctrl>+<a> s - послать control-s активному окну
- <ctrl>+<a> S - делит окно на 2 части по горизонтале. Для переключения можно использовать <ctrl>+<a> tab. В нижнем окне можно создать новое окно (удобно сравнивать конфиги), или подклчиться к другой сессии.
- <ctrl>+<a> w - выводит список всех окон
- <ctrl>+<a> x - блокировка
- <ctrl>+<a> ? - справочник по комбинациям кнопок
- <ctrl>+<a> C-\ - закрыть всё
- <ctrl>+<a> esc - переключение в "copy/scrollback mode"
- <ctrl>+<a> ] - вставить буфер
- <ctrl>+<a> > - записать буфер в файл
- <ctrl>+<a> * - список всех подсоединенных экранов
Сборка ядра 3.3.0-rc2
Сборка ядра 3.3.0-rc2 (в соответствии с рекомендациями readme)
wget https://www.kernel.org/pub/linux/kernel/v3.x/testing/linux-3.3-rc2.tar.bz2
su -c "apt-get install bzip2"
tar -xjf linux-3.3-rc2.tar.bz2
cd linux-3.3-rc2/
mkdir -p ~/build/linux-3.3-rc2
su -c "apt-get install libncurses5-dev"
make O=~/build/linux-3.3-rc2 nconfig
make O=~/build/linux-3.3-rc2
su -c "make O=~/build/linux-3.3-rc2 modules_install install"
su -c "update-initramfs -c -v -k 3.3.0-rc2"
su -c "update-grub2"
shutdown -r now
Для ускорения сборки, можно отказаться от лишних модулей. Для этого используем конфиг по умолчанию, и добавим в него необходимые (используемые на данный момент) модули.
make O=~/build/linux-3.3-rc2 defconfig
make O=~/build/linux-3.3-rc2 localmodconfig
make O=~/build/linux-3.3-rc2 nconfig
Создание модулей для ядра Linux
Введение
Некоторые отличия Ядра от программ, выполняемых в пространстве пользователя:
- Ядро LINUX пишется с расчётом на компилятор GNU C (разработчики ориентируются на стандарт ISO C99). В коде можно использовать ассемблерные вставки (директива asm()), аннотацию ветвления (likely() - более вероятная ветвь, unlikely() - менее вероятная) и весьма странный кодстайл.
- Ядро не имеет доступа к стандартным библиотекам языка программирования C. Это сделано из соображений увеличения скорости выполнения и уменьшения объёма кода.
- Отсутствует защита памяти. Если обычная программа предпримет попытку некорректного обращения с памятью, то ядро сможет выгрузить такую программу, но если само ядро предпримет такую же попытку, то его будет некому проконтролировать. Так же важно помнить об отсутствии замещения страниц, т.е. каждому байту, используемому ядром, соответствует байт реальной физической памяти.
- В ядре используются только целочисленные вычисления. Это тоже сделано для ускорения работы, т.к. операции с плавающей точкой значительно более ресурсоёмки (в частности, активнее используются регистры CPU).
- Объём стека фиксирован, и обычно равен двум страницам памяти (8 Кбайт для x86, и 16 Кбайт для x64). По этой причине не рекомендуется использовать рекурсию.
- Важным требованием является переносимость - код должен компилироваться на максимально большом количестве систем.
Загружаемый объект ядра называется модулем.
Динамическая загрузка и выгрузка модулей по мере необходимости появилась благодаря Питеру Мак-Дональду и впервые была представлена в версии ядра 0.99.
По своей структуре, модуль похож на обычную прогамму (так же имеется точка входа, и необходима компиляция в бинарный вид) но имеет прямой доступ к структурам и функциям ядра, в то время как обычные программы такой доступ могут получить только через обёртки.
Сборка модуля как отдельного объекта (Kernel object)
Код модуля
#include <linux/module.h> // Этот файл подключается в любом модуле по соглашению
#include <linux/kernel.h> // Содержит макросы для функции printk()
#include <linux/init.h> // Содержит определения макросов __init и __exit
void printHW(void) // Функция для вывода приветствия
{
printk("Hello, world\n"); // выводит сообщение на экран и в лог messages
}
EXPORT_SYMBOL(printHW); // Экспорт функций ядра - предоставляет доступ к функции другим модулям ядра
static int __init start(void) // Точка входа в модуль
{
printHW(); // Вызов функции
return 0; // в случае успешной загрузки возвращать нулевое значение
}
static void __exit stop(void) // Точка выхода
{
//printk("Module unloaded\n");
}
module_init(start);
module_exit(stop);
MODULE_LICENSE("GPL"); // Указывает на лицензию, под которой распространяется данный модуль
Сборка модуля (make)
Файл с кодом модуля (myModule.c) должен находиться в одной папке с make-фалом, в котом должно быть написано
obj-m += myModule.o
Тогда сборку модуля можно запустить командой
make -C ./linux-3.3-rc2 SUBDIRS=$PWD modules
Внимание, замените путь к исходным кодам ядра на тот, куда вы извлекли содержимое архива linux-3.3-rc2.tar.bz2 В итоге должен получиться файл модуля myModule.ko
Загрузка модуля
Теперь полученный модуль можно загрузить (эта команда требует прав суперпользователя). Для этого используется команда
insmod <имя модуля>
Список загруженных модулей хранится в /proc/modules (так что можно просмотреть этот файл cat /proc/modules) либо воспользоваться командой
lsmod
Выгружать модуль можно командой (также требует прав суперпользователя)
rmmod <имя модуля>
Примечания
Результат работы модуля выводится в /var/log/syslog, для его просмотра рекомендуется на отдельной консоли использовать
tail -f /var/log/syslog
Сборка модуля вместе с ядром (Kbuild)
Модули для ядра можно собирать при помщи системы Kbuild. инструкции для сборки должны находиться в файле Kconfig.
Что бы подключить наш Kconfig, нужно в файл linux-3.3-rc2/arch/x86/Kconfig.debug добавить строчку (рекомендуется сделать это где-нибудь в начле файла, например 6-й строкой)
source "SPbAU/Kconfig"
Это позволит включать и отключать сборку наших модулей в меню Kernel hacking, что в общем-то логично.
Важно заметить, что если в makefile-файлах относительный путь задавался от места размещения самого файла, то в Kconfig - относительные пути строятся от корневого каталога дерева исходных кодов (в данном случае, от linux-3.3-rc2).
Теперь, в дереве исходных кодов создаём каталог SPbAU, переносим туда (из предыдущего раздела) myModule.c и Makefile, а так же создаём файл Kconfig.
Файл Kconfig
Файл Kconfig должен содержать следующий код
config SPbAU_KERNEL
bool "Modules of students of the St. Petersburg Academic University of the Russian Academy of Sciences"
default y
---help---
SPbAU module list
config SPbAU_KERNEL_PRINTHW
tristate "The first module =)"
depends on SPbAU_KERNEL
default y
---help---
Function prints the text "Hello, world" to the syslog file.
В первой строке мы объявляем о том, что хотим сделать раздел SPbAU_KERNEL (очень важно следить, что бы имена не перекрывались, по этому рационально использовать свой префикс, типа SPbAU). Потом мы указываем что пункт является bool, т.е. способен принимать значения Y или N (подробнее о значениях будет сказано чуть ниже), и определено имя этого раздела в меню. По умолчанию, раздел отмечен как ИСТИНА. Далее идёт справачная информация, которая может занимать несколько строк (необходимо соблюдать отступ в 1 пробел от основной линии).
Потом мы описываем свой модуль. Ключевое слово tristate говорит о том, что этот пункт меню будет принимать одно из трёх возможных значения Y/M/N. Строчка depends on SPbAU_KERNEL показывает, что этот модуль не может быть установлен (и даже пункт не будет отображаться в меню), если в прерыщем пункте (SPbAU_KERNEL) стоит значение, отличное от истины. Это позволяет группировать различные элементы меню, уменьшая общую длинну списка.
Возможные значения изначального состояния пунктов меню:
- Y - модуль вкомпилируется в ядро (это произайдёт успешно, если не допускать перекрытия имён точек входа)
- M - модуль компилируется отдельным файлом (kernel object)
- N - модуль не вкомпилируется
Более подробно о языке формирования Kconfig файлов можно прочитать здесь.
Файл Makefile
В Makefile необходимо добавить строчку
obj-$(SPbAU_KERNEL_PRINTHW) += SPbAU/
В файле Kconfig для мы указали tristate, т.е. пользователь может выбрать M и собрать отдельно модуль. Тогда команда make modules_install разместит .ko-файл в указанной директории (SPbAU), которая будет размещена по адресу /lib/modules/3.3.0-rc2/kernel/, т.е. общий путь к модулю будет /lib/modules/3.3.0-rc2/kernel/SPbAU/myModule.ko
Makefile тоже необходимо подключить
Сборка ядра с модулем
Вернувшись в корень дерева исходных кодов, вызываем меню
make menuconfig
Это приведёт к выводу на экран графического меню
Нас интересует раздел Kernel hacking, куда мы положили свои команды сборки модуля
Отметка на пункте "Modules of students of the St. Petersburg Academic University of the Russian Academy of Sciences" позволяет скрывать все наши модули (в данном случае модуль всего 1), а отметка на пункте "The first module =)" может принимать как значение, соответствующее сборке отдального модуля, так и сборки внутри ядра.
Процессы и их дескрипторы
Процесс
Процесс - это исполняющаяся программа.
Он состоит из:
- исполняемого программного кода;
- набора ресурсов (к примеру, открытые файлы);
- внутренние данные ядра;
- адресное пространство потоков исполнения (threads of execution);
- секции данных (с глобальными переменными).
Описатель (дескриптор) процесса
С каждым процессом, ассоциирован описатель (дескриптор) процесса, который используется для отслеживания процесса в памяти.
Дескриптор процесса содержит:
- идентификатор процесса (PID);
- состояние процесса, которое определено в переменной state и может принимать одно из следующих значений:
- 0 - TASK_RUNNING
- 1 - TASK_INTERRUPTIBLE
- 2 - TASK_UNINTERRUPTIBLE
- 4 - TASK_STOPPED
- 8 - TASK_TRACED
- 16 - EXIT_ZOMBIE
- 32 - EXIT_DEAD
- 64 - TASK_NONINTERACTIVE
- ссылки на родительский и дочерние процессы;
- регистры процессора;
- список открытых файлов;
- информацию об адресном пространстве;
Дескриптор процесса определён структурой struct task_struct (объявлено в файле linux/sched).
Для доступа к процессам, все дескрипторы организованы в циклический замкнутый двусвязный список struct list_head, который объявлен в linux/list.h и имеет следующий вид.
struct list_head
{
struct list_head *next, *prev;
};
Макросы для работы с процессами
Дескриптор процесса init задаётся статично
extern struct task_struct init_task;
Двигаясь по указателям в какую-либо сторону от этого дескриптора, можно обойти все дескрипторы, и вернуться в исходный.
Для удобного обхода существует несколько макросов.
for_each_process()
Макрос определен в linux/sched.h и используется для обхода всего списка задач, пока не достигнута исходная.
#define for_each_process(p) for (p = &init_task ; (p = next_task(p)) != &init_task ; )
next_task()
Макрос определен в linux/sched.h и возвращает следующую задачу из списка.
#define next_task(p) list_entry((p)->tasks.next, struct task_struct, tasks)
list_entry()
Определен в linux/list.h
/*
* list_entry - get the struct for this entry
* @ptr: the &struct list_head pointer.
* @type: the type of the struct this is embedded in.
* @member: the name of the list_struct within the struct.
*/
#define list_entry(ptr, type, member) container_of(ptr, type, member)
container_of()
#define container_of(ptr, type, member) \
({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) ); \
}) \
current
Этот макрос является указателем на task_struct текущего исполняющегося процесса, и работает при помощи current_thread_info(), определенной в asm/thread_info.h
/* how to get the thread information struct from C */
static inline struct thread_info *current_thread_info(void)
{
struct thread_info *ti;
__asm__("andl %%esp,%0; ":"=r" (ti) : "0" (~(THREAD_SIZE - 1)));
return ti;
}
do_each_thread
/*
* Careful: do_each_thread/while_each_thread is a double loop so
* 'break' will not work as expected - use goto instead.
*/
#define do_each_thread(g, t) \
for (g = t = &init_task ; (g = t = next_task(g)) != &init_task ; ) do
#define while_each_thread(g, t) \
while ((t = next_thread(t)) != g)
next_thread
static inline struct task_struct *next_thread(const struct task_struct *p)
{
return list_entry_rcu(p->thread_group.next, struct task_struct, thread_group);
}
Пример реализации модуля
Используя макросы, мы можем написать модуль, который обходит все существующий в системе процессы, и выводит их список в var/log/syslog
#include <linux/module.h> // Этот файл подключается в любом модуле по соглашению
#include <linux/kernel.h> // Содержит макросы для функции printk()
#include <linux/init.h> // Содержит определения макросов __init и __exit
#include <linux/sched.h>
void show_proc_list(void) // Функция для вывода всех имеющихся процессов
{
struct task_struct *task;
for_each_process(task)
{
printk("%s [%d]\n",task->comm , task->pid);
}
}
static int __init start(void) // Точка входа в модуль
{
show_proc_list(); // Вызов функции
return 0; // в случае успешной загрузки возвращать нулевое значение
}
static void __exit stop(void) // Точка выхода
{
//printk("Module unloaded\n");
}
module_init(start);
module_exit(stop);
MODULE_LICENSE("GPL"); // Указывает на лицензию, под которой распространяется данный модуль
Netlink
Пример использования протокола Netlink из пространства пользователя Программа, получающая список сетевых интерфейсов через RtNetlink:
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
// ================================================================================
static char buf[10240];
int main(int argc, char** argv) {
int nlsock;
struct sockaddr_nl nla;
nlsock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
if (nlsock < 0) {
printf("Failed to create a Netlink socket: %s\n", strerror(errno));
return 1;
}
nla.nl_family = AF_NETLINK;
nla.nl_pad = 0;
nla.nl_pid = 0;
nla.nl_groups = 0;
if (connect(nlsock, (const struct sockaddr*)&nla, sizeof(nla)) < 0) {
printf("Failed to connect to the kernel rtnetlink: %s\n", strerror(errno));
goto close_sock;
}
struct nlmsghdr* nlh = (struct nlmsghdr*)buf;
nlh->nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
nlh->nlmsg_type = RTM_GETLINK;
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
nlh->nlmsg_seq = 0;
nlh->nlmsg_pid = getpid();
struct ifinfomsg* msg = (struct ifinfomsg*)NLMSG_DATA(nlh);
msg->ifi_family = AF_UNSPEC;
msg->ifi_index = 1;
msg->ifi_change = 0xFFFFFFFF;
if (send(nlsock, buf, nlh->nlmsg_len, 0) < 0) {
printf("Failed to send a Netlink message: %s\n", strerror(errno));
goto close_sock;
}
int sz = recv(nlsock, buf, sizeof(buf), 0);
while (NLMSG_OK(nlh, sz) && nlh->nlmsg_type != NLMSG_DONE) {
if (nlh->nlmsg_type & NLMSG_ERROR) {
struct nlmsgerr* e = (struct nlmsgerr*)NLMSG_DATA(nlh);
printf("Failed to get a link: %d (%s)\n", e->error, strerror(-e->error));
goto close_sock;
}
if (nlh->nlmsg_type & RTM_NEWLINK) {
struct rtattr* pattr;
struct ifinfomsg* msg = (struct ifinfomsg*)NLMSG_DATA(nlh);
pattr = (struct rtattr*)(msg + 1);
int alen = (char*)(nlh + nlh->nlmsg_len) - (char*)pattr;
while (RTA_OK(pattr, alen)) {
if (pattr->rta_type == IFLA_IFNAME) {
printf("Interface %d, name %.*s\n", msg->ifi_index, RTA_PAYLOAD(pattr), RTA_DATA(pattr));
}
pattr = RTA_NEXT(pattr, alen);
}
}
nlh = NLMSG_NEXT(nlh, sz);
}
close_sock:
close(nlsock);
return 0;
}
Список литературы
- Разработка ядра Linux. Роберт Лав (http://www.ozon.ru/context/detail/id/2918313/)
- Ядро Linux. Д. Бовет, М. Чезати (http://www.ozon.ru/context/detail/id/3589107/)
- Linux Device Drivers, Джонатан Корбет, Алесандро Рубини, Грег Кроа-Хартман (http://lwn.net/Kernel/LDD3/) officiel!