Linux core source code

Linux/Reading the Linux Kernel Sources

Contents

Reading the Linux Kernel Sources [ edit | edit source ]

Part of an ongoing Linux Kernel exploration by SVLUG — the Silicon Valley Linux Users Group

Good places to follow along:

Look out for this: sometimes code gets moved from one file or one directory to another. or converted from raw assembler (in the arch area) into C and categorized. Therefore the LXR site letting you surf the older source trees can be very interesting. A specific example of this is the eventual combining of several closely related architectures into one arch with sub-architectures.

  • Wikipedia handy words
    • Linux kernel
    • Extended memory
    • computer interrupt
    • computer pointers
    • the C programming language
  • Potentially useful books
    • Assembly Language Step by Step by Jeff Duntemann (Wiley 2000) ISBN0471375233 — section on the gas assembly language syntax (Chapter 12), INTs used in Linux (Chapter 13), etc.
    • Linux Device Drivers, 3rd edition, Jonathan Corbet, Alessandro Rubini, and Greg Kroah-Hartman (O’Reilly 2005)
    • ISBN0-596-00590-3 — covers device drivers for kernel 2.6.10. Full text available for free at LWN
  • See also Ralf Brown’s x86 interrupt list

Introduction to the Linux Kernel Source [ edit | edit source ]

Traditional computer science programming courseware, for the most part, uses source code examples that are over-simplified and academic, giving little insight into how things work in the real world, and into the forces that change source code over time.

The Linux kernel is one of the most widely used pieces of production source code in the world today. It’s been under continuous development for over two decades, and has seen ever-growing popularity and usage. In that respect, it’s one of the more seminal works of source code for any aspiring engineer to study.

Studying the Linux kernel sources poses a number of challenges. The kernel is comprised of over 15 million lines of code. In addition, the kernel is a unique «program» — one which provides the most fundamental support for the system’s hardware, libraries and the applications which run under it. So studying a kernel is considerably more complicated than studying a typical user space C program. We can’t just start at int main (int argc, char **argv) . ‘ and proceed from there.

The Linux kernel has a number of different distinct entry points. It also contains a small amount of assembly code required for firmware loads and kernel boot sequences. Naturally the Linux kernel cannot link with the standard C libraries (since, at their core the C libraries depend on systems calls which are provided by the kernel — so that would present a classic chicken-and-egg problem).

Here we’ll present a number of tips for studying the kernel sources. We’ll then provide an outline for one sequence of topics which can be studied in a progressive and productive way. The outline will be primarily presented as links via Cross-referencing Linux

We’ll also rely heavily on other online resources such as the LDP Kernel Hacker’s Guide (which, although quite dated can give us some historical perspective on the code from a time when the kernel was somewhat smaller and simpler).

Get Linux first release 0.01

Where the CPU Starts Executing [ edit | edit source ]

One approach would be to start where the CPU starts executing a freshly loaded kernel image.

Naturally we could ask ourselves: «what is the first part of the compiled kernel that will be executed?» Another way of asking this is: «what is the kernel’s ‘entry point?'» (Unfortunately search on the obvious Google terms: «Linux kernel ‘entry point'» leads to discussions of how system calls transition from user space into kernel space — which is a different sort of «entry point» (though one well worth studying)).

In order to answer that question we have to consider how a kernel gets loaded into memory and started on a system. When a computer starts up the system memory (RAM) is empty; the only available operating instructions are in ROM (read-only memory) — also called the firmware.

Naturally the firmware is different for each system architecture. Thus PCs, PowerPC (older Machintosh systems, and IBM POWER, RS/6000 workstations, etc), DEC Alpha, MIPS, SPARC, and other systems each have their own low-level code — and their own loading conventions. Any given compilation of the kernel will have at least one such entry point.

It’s even more complicated than that, however, because there are, in some cases such as the PC, a number of different ways that the kernel can be loaded. For example from hard disk, floppy, CD-ROM or over a network (e.g. via PXE). Older Linux kernels, (before circa 2003) could be dumped onto a raw floppy diskette (using the dd command) and booted from there. We see where that code used to be here:

As the error message here indicates this raw boot model is no longer supported. So we have to use some program like syslinux, lilo, or grub to load the kernel and jump into the code.

Another complication is that the normal PC Linux kernel image is compressed. There is a small header of code which does some minimal memory management and decompresses the rest of the image into RAM. Then that bit of code jumps into the kernel. The PC (x86) code for decompressing a kernel image can be found in . /arch/i386/boot/compressed/head.S, with other architectures’ decompression code found by simply swapping out i386 for your architecture of choice.

Of course, this is so tied to the architecture that it is in assembly language making it somewhat more difficult for C programmers to read. There are a few comments at the beginning of this file which reveal a little about the expectations/assumptions that the code must make about how controls has been passed to it. These become requirements for how the bootloader (syslinux, lilo, grub) must prepare things before the processor jumps to the instruction after the startup_32 label. It uses these arguments to find and decompresses the kernel into the correct place and then jumps to the «correct place,» which the code only refers to using a register.

According to the comments, the point that the decompression code jumps to is found in arch/$YOUR_ARCH/kernel/head.S and is marked with the macro ENTRY(stext). Again, the trail fades out. Calculated values are jumped to and macros are invoked. The next hint can be found in the Linux Kernel 2.4 Internals page: after the initial assembly, execution jumps to start_kernel() in init/main.c. From there the code becomes much easier to read.

Читайте также:  Ноутбук asus драйвер wifi windows 10

Is this correct? Is there any point in the Linux kernel sources called before this point when loading a compressed kernel? The filename head.S is the convention used for this meaning. startup_32 is the code called by setup.S which does the transition into protected mode. setup.S also #includes video.S and seems to call into the subroutines defined there. Is there currently any way to load an uncompressed Linux kernel on a 32-bit PC? How about under x86_64? Is it possible to step through the code with a JTAG based source level debugger in our weekly meeting? Even though this may affect timing, it can tell us what code is actually executed, the calling sequence and the values of variables. It is possible to step through the code with a JTAG based source level debugger on some embedded Linux device that boots Linux directly from Flash? (That keeps us from getting bogged down in the details of the MBR/bootloader, which is unnecessarily complicated on many x86 machines). Perhaps b: Tomato_(firmware) ?

Where to Therefrom? [ edit | edit source ]

Even if we choose to pursue this path of exploring the kernel . where does the chain of execution go from there? At the end of head.S we see the code clear the EBX register and jump to the address which is contained at that point in the EBP (base pointer) register. The preceding code was calculating possible offsets between the kernel’s compiled in load/start address and the location at which it was actually loaded (if it was built as «relocatable»).

Notice that the jump is not to a symbolic label, and the comments in the sources don’t tell us where to read next.

It should be obvious that starting our reading where the CPU starts executing might present some challenges that we’re not quite ready to tackle.

Where «User Space» is Started [ edit | edit source ]

Every good Linux systems administrator should know that the classic UNIX kernel starts exactly one normal user space program. So, perhaps this is a useful place to start reading the kernel sources. We know that several things have to happen before init is started (the root filesystem must be located and mounted, the initial console must be opened and connected to file descriptors 0, 1, and 2 (stdin, stdout, and stderr respectively) and the initial environment must be created.

Each pre-requisites has it’s own pre-requisites: the block device on which the rootfs is hosted must be detected and initialized, the memory limits must be scanned (or otherwise auto-detected), any memory management unit (MMU) and programmable interrupt controllers (PICs, APICs, IOAPICs) must be detected, enumerated and programmed; etc.

So we could find where the init process is created and trace backwards from there to learn more about how the system is prepared for its ultimate mission (running our programs, one would think).

We can also ask ourselves what happens after the init process has been started. Of course any competent sysadmin knows what happens out in user space: init reads /etc/inittab and executes all of the processes described there.

From that we can intuit at least some of what the kernel must be doing.

Clearly the scheduler must be running, giving the init process and each of its descendants time to execute code in user space.

Any good UNIX programmer knows that code in user space can only do some very basic operations — basically computation, arithmetic and string operations, on memory that’s already allocated to the process. Everything else involves access to files, devices, or other system calls (requests to the kernel to do provide a service to the program).

So we can also look forward, finding out how the kernel handles system calls, and how it provides filesystems.

We can see here that the kernel attempts to start /sbin/init, /etc/init, /bin/init and finally falls back to trying /bin/sh. If we read backwards a little bit we see where the kernel tries to execute whatever program was based via the init= kernel argument by the boot loader (and we can follow the hyperlinks to find where the command line was parsed for such an option). Going back a little further we can see where the kernel tries to start the /init (formerly the /linuxrc) if there was one in an initial RAM disk.

We can compare the current version of this file to the oldest one available (from the 1.0.9 kernel version) and see how much is recognizable, and then consider the changes that have accrued over the years, and ponder why those changes where made.

After init Has Been Started [ edit | edit source ]

A traditional UNIX kernel only starts one normal user space process, init and thereafter it assumes its roles as the mediator between user space and the system hardware resources.

Show how the kernel spawns modprobe and hotplug utilities as a counterpoint to the traditional UNIX model

Primarily the kernel schedules CPU time to processes, dispatches signals to them, and handles system calls for them. That’s the view of the kernel from the perspective if the applications programmer.

Show how the kernel handles system calls via entry.S et all Compare this to the sysenter VDSO technique Other memory mapping tricks?

From another perspective the kernel services interrupts — hardware events.

The first and most obvious would the periodic events from the system clock — on a PC those from from a PIT (programmable interrupt timer). The system clock interrupt becomes the heartbeat of the system. During this interrupt the kernel updates the «jiffies» value, possibly updates the kernel real-time clock values, and schedules user space processes and kernels tasks.

(Most interrupts for most I/O devices are divided into a couple of parts, traditionally called the «bottom half» and the «top half» — the bottom half is normally the minimal amount of work that saves enough state and status that will allow the rest of the work to be deferred until the top half can be scheduled to run — so these are one source of kernel tasks that can require scheduling; additionally the Linux kernel maintains a number of kernel threads which appear in normal ps listings as processes (with funny names that are enclosed in square brackets. those exist in that form so that the kernel can schedule them using the same mechanisms as for any user space processes).

Читайте также:  Windows 10 выпуск 10 ноября

The above top half/bottom half explanation is the reverse of what the Linux Device Drivers book says. 🙁 That is, the top half is normally the minimal amount of work that saves enough state and status that will allow the rest of the work to be deferred until the bottom half can be scheduled to run. I see that w: Talk:Interrupt_handler#Wrong also mentions that some people use top/bottom with a swapped meaning of other people. Perhaps, here at Wikiversity, we should use the unambiguous «first level interrupt handler» and «second level interrupt handler» terminology. Find the code responsible for handling the system clock interrupt! Find one of the simple kernel thread tasks, such as krecoveryd and show how it’s initialized and what it does during it’s time slices Discuss the idle task

End of the Road [ edit | edit source ]

While it’s useful to start, in some sense, where the user space interactions with the kernel «start» — it’s also useful to have some idea how it all will end. Once the kernel has started and settles into its role, handling system calls, dispatching signals, servicing interrupts, it will do so until .

Источник

Linux kernel development для самых маленьких

Любой программист знает, что теоретически он может внести свой посильный вклад в развитие Linux ядра. С другой стороны, подавляющее большинство уверено, что занимаются этим исключительно небожители, а процесс контрибьюта в ядро настолько сложен и запутан, что обычному человеку разобраться в нём нет никакой возможности. А значит, и надобности.
Сегодня мы попробуем развеять эту легенду и покажем, как абсолютно любой инженер при наличии достойной идеи, воплощённой в коде, может предложить ее на рассмотрение Linux community для включения в ядро.

0. Подготовка

Как и перед любой инженерной операцией, всё начинается с подготовки своего рабочего места. И первейшее здесь действие — это завести себе аккаунт с адекватным именем. В идеальном мире это будет просто транскрипция имени и фамилии. Если за учётку вроде MamkinC0d$r или Developer31337 в других местах пальцем в вас тыкать не будут, то правила LKC (Linux kernel community) такое прямо запрещают — инкогнито контрибьютить в ядро не принято.

Далее вам понадобится место на локальной машине. Сама папка Linux со скачанными исходниками весит чуть меньше 3-х гигов. Но если ядро пробовать собирать, то вместе с модулями займёт все 30 GB.

Захотелось собрать несколько веток? Умножаем 30 на число веток.
И помним — скорость сборки прямо связана с количеством доступных ядер! Больше ядер — быстрее соберётся. Так что не стесняйтесь выделять под это самую мощную машину.

1. Mail

Самый спорный и поэтому регулярно вызывающий споры момент — это канал коммуникации с LKC. Он безальтернативно один. Почта. Причём сообщения отправляются по классике через smtp.

Вокруг необходимости делать всё через плейнтекст в почте есть масса споров. Недавно в сети была очередная громкая статья на эту тему. Суть материала: письма — это, конечно, здорово, но пихать туда всё, включая куски кода — это вам (т.е. LKC) популярности не добавляет и даже наоборот, отпугивает новичков. С одной стороны вроде и да, если ты не можешь спокойно и структурировано изложить свои мысли в голом тексте, то в низкоуровневой разработке ловить будет особо нечего. С другой стороны, слать в письмах сорсы патчей — это даже архаизмом назвать уже сложно.

Но, как принято в уютном мирке ядра, Линус хлопнул кулаком по столу — и все пишут письма. Возможно, буквально завтра это изменится, но на момент выхода статьи это письма и только письма.

Какой email-client выбрать — есть рекомендации. Самым рекомендуемым почтовым агентом для LKC остаётся mutt. Да, тот самый текстовый почтовый клиент, от которого сводит олдскулы. Для начала mutt нужно поставить (я думаю, со своим пакетным менеджером вы и сами справитесь), а потом задать параметры в файле

Но почты недостаточно. Без Git никуда.

2. Git

Прежде чем что-то делать с исходниками ядра, нужно настроить Git. Можно конфигурировать файлы напрямую, но есть упрощающая жизнь утилита git config, через которую можно регулировать все аспекты работы Git’a.

Внутри есть три уровня настроек: общие для всех пользователей системы и для всех репозиториев (git config —system), общие для всех репозиториев конкретного пользователя (git config —global), отдельные для каждого репозитория (git config —local).

Глобальные настройки хранятся в /etc/gitconfig, настройки пользователя в

/.config/git/config, а настройки отдельных репозиториев хранятся в файле config в каталоге .git/config.

В общем случае будет достаточно законфигурировать файл для пользователя

/.gitconfig . Основная идея: при отправке коммитов должно отображаться ваше корректное имя.

Примечательно, что параметр sslVerify = true препятствует работе с Git напрямую через git://git.kernel.org и форсит доступ только через https://git.kernel.org . Так, наверное, секьюрнее, хотя какого-то смысла я в этом не вижу. Но, может, чего-то не знаю?

signOff обязателен, чтоб в коммитах была информация об авторе. По идее, надо бы, чтобы коммиты подписывались. Была тут недавно статья на эту тему.

Отправка патча выполняется командой git send-email. У git send-email есть несколько параметров с участием smtp, которые можно (и нужно) переопределить.

Полезный параметр —smtp-debug=1. Осуществляет логирование SMTP запросов и ответов, что помогает при разборе проблем с настройками почты. Например, я столкнулся с тем, что почтовый сервер, на котором есть у меня почтовый ящик, не поддерживает TLS. Возможны проблемы с аутентификацией, а сообщения об ошибке git send-email выдаёт не особо разнообразные и адекватные.

Можно задавать пароль к почте через параметр —smtp-pass=p4ssw0rd или вообще захардкорить в конфиге, но это это для тех, кому терять нечего. Но если каждый раз вводить пароль лень, то есть некий хак: если username был задан (через —smtp-user или sendmail.smtpUser), а пароль не указан, тогда пароль получается через git-credential.

Итак, окно в большой мир прорубили. Можно переходить к воплощению своей грандиозной идеи в коде.

3. Coding

Итак, мы готовы сделать первый шаг непосредственно в разработке — склонировать к себе репозиторий. Советую делать это сразу с указанием ветки, которая будет создана. Также отмечу, что работать лучше не над текущим состоянием в master, а над стабильной версией или кандидатом на релиз. Так вы будете более уверены, что ядро соберётся и будет как-то работать, не вызовет неожиданных конфликтов из-за изменений в других подсистемах. Поэтому всегда внимательно смотрите тэги.

И — опять же — имя своей ветке постарайтесь дать чёткое и лаконичное. В идеальном мире оно даже может отображать суть вносимых изменений. Если вы исправляете известный баг, то хорошим тоном считается включить в название ветки номер issue.

Операция довольно долгая, так что смело можно идти за кофе или на обед. А если попробовать ускорить процесс, отказавшись от истории, то работать с «этим» будет невозможно.

Итак, мы получили ветку, в которой можно начинать свою разработку. Здесь всё очевидно: пишем код, собираем, тестируем, исправляем баги — и так до получения нужного результата. О том, как собирать ядро и проводить отладку, информации в сети море, так что подробно описывать весь процесс я не буду. Лишь вкратце пробежимся по нему чуть позже. Единственный нюанс, который добавлю от себя прямо сейчас: перед сборкой проверьте наличие всех необходимых программ из этого списка и их минимальные версии.

Читайте также:  Intel pcie controller x16 драйвер windows 10 что это

Если бродить вслепую по гуглу не хочется, то вся максимально полезная информация по ядру сконцентрирована тут. Прочитать стоит действительно всё. Особенно правильно будет начать с How To о том, как правильно коммуницировать. Потому что мейнтейнеры, как правило, люди весьма занятые, и вникать в невнятно составленные письма им никакого интереса. Да и вам будет обидно, если из-за плохого описание ваше детище не примут в апстрим.

И вот свой небольшой и эффективный код вы написали, отладили, всё протестировали и готовы отправлять на рассмотрение. Но не спешите этого делать. Для начала обязательно проверьтесь на code style. В этом вам поможет ./script/checkpatch.pl . Для этого сделаем патч и отправим его на проверку.

После того, как пройдёт первое удивление и вы доустановите необходимые компоненты для python2 типа ply и git (который у меня так и не установился), наступит чудесное время исправления ошибок и ворнингов. По результатам которых вы а) поймёте, что красивый код писать вы не умеете б) потеряете всякое желание что-то куда-то отправлять. Ведь даже если отбросить все шутки, ещё можно как-то смириться с тем, что длина строк ограничена 100 символами (это начиная с версии 5.7, раньше так было вообще 80). Но вот такие места оставляют неизгладимое впечатление:

Для .h файлов строка с информацией о лицензии должна быть в ремарках / * */, а для *.c файлов должна быть в ремарках //. Такое запросто выбьет кого угодно из душевного равновесия. Вопрос: «Зачем?!» до сих пор болтается в моей голове, хотя есть вера в то, что это не просто ошибка в скриптах.

Кстати, чтобы просто проверить один файл достаточно вызвать

Можно прикрутить этот вызов к git, чтобы автоматически запускался этот скрипт при попытке что-то зачекинить.

Ещё важное замечание: clang-format добрался и до ядра Linux. Файл .clang-format расположился в корне ядра в кучке с другими конфигами. Но я не советую добавлять его в хук для git. Лучше всего корректно настроить среду разработки и запомнить code style. Лично мне не понравилось как он делает переносы длинных строк. Если вдруг строка оказалась длиннее допустимой, то скорее всего функция, в которой эта строка расположилась, является кандидатом на рефакторинг, и лучше его не откладывать. С другой стороны, если у вас много уже готового кода который нужно адаптировать для ядра — clang-format может сильно облегчить вам задачу.

4. Kernel build

Несмотря на то, что процесс описан в других статьях тут и тут, я все же повторюсь.

По шагам процесс сборки ядра довольно прост, если не вдаваться в детали. Для начала ставим необходимые пакеты (использовался Debian 10):

Это без компилятора и обычного для С/С++ разработчика набора программ.
Запускаем конфигурацию:

Тут есть интересный аспект: в качестве шаблона будет браться config ядра от вашего боевого ядра, которое, скорее всего, подготовлено дистрибьютером. Для Debian 10 сборка проходит успешно, если в конфиге потереть информацию о встраиваемых в ядро сертификатах.

Перед попыткой собрать проверьте, что нужные программы уже установлены. Список тут. Чтобы собрать само ядро:

Этого достаточно для проверки собираемости, но недостаточно для запуска ядра в системе, так как без модулей ядро на реальной системе практически беспомощно.

Если какой-то модуль не собирается, просто вырубите его в ближайшем Makefile-е (если 100% уверены, что не пытались в нём что-то улучшить). Наверняка он вам не пригодится, и тратить время на исправления смысла нет.

Теперь можно деплоить то, что получилось, на эту же систему.

Хотя, конечно, экспериментировать с ядром на той же машине, где ведётся разработка — дело рискованное.

Поэтому как минимум нужно снять снапшот, сделать резервную копию или лучше вообще выполнять отладку на другой (лучше виртуальной) машине. Но как это делать, я пока не разбирался. Если у кого есть такой опыт — пишите в комменты, добавлю в статью.

На моей системе загрузчик после установки ядра автоматически обновился. Если у вас этого не произошло, то это делается это на Debian-подобных системах командой:

Update: Как верно заметил gavk, ядро давно уже умеет собирать пакеты, причём как для deb, так и для rpm.
Команда

выводит весь ассортимент. Так что команда

должна собрать пакет с ядром.

5. Patches

Вот теперь мы действительно подготовили код для отправки. Лучше всего, чтобы это был единственный коммит. Так проще делать ревью и так быстрее вам ответят. Всё проверив, наконец-то делаем коммит.

Ещё можно комментарии к коммиту дополнить в человеческом текстовом редакторе.

И теперь его можно оформить в виде того самого письма. Правила хорошего тона, или best practice, если угодно — это 75 символов на строку.

В результате получите два файла. В первом 000-cover-letter.patch нужно указать заголовок письма «Subject» и основное описание патча. В описании патча пишем, для чего он создавался, кому он сделает жизнь на нашей планете лучше и каким образом. Только не словоблудим про космические корабли в Большом театре, а пишем лаконично и по делу. И не в коем случае не пишите корпоративную лабуду а-ля «Без этого патча мой бизнес встанет, меня уволят, а дети мои умрут от голода». Нет, строго по существу: «Увидел вот такую проблему вот тут, починить решил вот таким образом, исходники патча прилагаю». Всё, вы восхитительны! А если не превысили 75 символов на строку, то восхитительны в квадрате.

А ещё один волшебный скриптик ./scripts/getmaintainers.pl

позволит узнать, кому письмо отправлять.

И вот он, момент отправления письма, ради которого всё и затевалось:

Дальше остаётся только сидеть и ждать ответного письма, где вам скорее всего скажут, что всё здорово, но надо ещё сделать вот это и поправить вот здесь. Возможно, придётся доказывать, что ваш функционал действительно новый и не создаёт дублирования. Хотя могут и сразу всё принять, сказав большое спасибо (шутка :-).

После того, как ваш патч был принят, крайне желательно удалить свою ветку.

6. Debuging

И чуть-чуть про отладку. Бонус «на сладкое» для начинающих разработчиков ядра, так сказать.

Как правило, при ошибке вы получаете лог с calltrace-ом. Там указываются имена функций и смещения. Примерно вот так:

Так вот, чтобы понять, в каком месте функции произошла ошибка, достаточно запустить дебагер с подгруженным в него модулем:

Важно, чтобы в модуле сохранились символы (stripped модуль вам тут не поможет).

Выполнив команду list

вы увидите строку кода, приведшую к ошибке. В случае передачи управления по неверному адресу, вы увидите следующую за ошибкой строку.

И на этом позвольте откланяться.

Буду рад вопросам и замечаниям в комментариях.

Источник

Оцените статью