Linux Kernel Programming - Second Edition

Book description

Gain both a firm practical understanding and sufficient theoretical insight into the inner workings of Linux kernel internals, learn to write high-quality kernel module code, understand the complexities of kernel synchronization Purchase of the print or Kindle book includes a free eBook in PDF format.

Key Features

  • Discover how to write Linux kernel and module code for real-world products
  • Implement industry-grade techniques in real-world scenarios for fast, efficient memory allocation and data synchronization
  • Understand and exploit kernel architecture, CPU scheduling, and kernel synchronization techniques

Book Description

The 2nd Edition of Linux Kernel Programming is an updated, comprehensive guide for new programmers to the Linux kernel. This book uses the recent 6.1 Long-Term Support (LTS) Linux kernel series, which will be maintained until Dec 2026, and also delves into its many new features. Further, the Civil Infrastructure Project has pledged to maintain and support this 6.1 Super LTS (SLTS) kernel right until August 2033, keeping this book valid for years to come!

You’ll begin this exciting journey by learning how to build the kernel from source. In a step by step manner, you will then learn how to write your first kernel module by leveraging the kernel’s powerful Loadable Kernel Module (LKM) framework. With this foundation, you will delve into key kernel internals topics including Linux kernel architecture, memory management, and CPU (task) scheduling. You’ll finish with understanding the deep issues of concurrency, and gain insight into how they can be addressed with various synchronization/locking technologies (e.g., mutexes, spinlocks, atomic/refcount operators, rw-spinlocks and even lock-free technologies such as per-CPU and RCU).

By the end of this book, you’ll have a much better understanding of the fundamentals of writing the Linux kernel and kernel module code that can straight away be used in real-world projects and products.

What you will learn

  • Configure and build the 6.1 LTS kernel from source
  • Write high-quality modular kernel code (LKM framework) for 6.x kernels
  • Explore modern Linux kernel architecture
  • Get to grips with key internals details regarding memory management within the kernel
  • Understand and work with various dynamic kernel memory alloc/dealloc APIs
  • Discover key internals aspects regarding CPU scheduling within the kernel, including cgroups v2
  • Gain a deeper understanding of kernel concurrency issues
  • Learn how to work with key kernel synchronization primitives

Who this book is for

This book is for beginner Linux programmers and developers looking to get started with the Linux kernel, providing a knowledge base to understand required kernel internal topics and overcome frequent and common development issues. A basic understanding of Linux CLI and C programming is assumed.

Table of contents

  1. Preface
    1. Who this book is for
    2. What’s been added in the second edition?
    3. What this book covers
    4. Get in touch
  2. Linux Kernel Programming – A Quick Introduction
    1. Kernel workspace setup
    2. Technical requirements
    3. Cloning this book’s code repository
  3. Building the 6.x Linux Kernel from Source – Part 1
    1. Technical requirements
    2. Preliminaries for the kernel build
      1. Understanding the Linux kernel release nomenclature
        1. Fingers-and-toes releases
      2. Kernel development workflow – understanding the basics
        1. Viewing the kernel’s Git log via the command line
        2. Viewing the kernel’s Git log via its GitHub page
        3. The kernel dev workflow in a nutshell
        4. Exercise
      3. Exploring the types of kernel source trees
        1. LTS kernels – the new mandate
        2. Which kernel should I run?
    3. Steps to build the kernel from source
    4. Step 1 – Obtaining a Linux kernel source tree
      1. Downloading a specific kernel tree
      2. Cloning a Git tree
    5. Step 2 – Extracting the kernel source tree
      1. A brief tour of the kernel source tree
    6. Step 3 – Configuring the Linux kernel
      1. Minimally understanding the Kconfig/Kbuild build system
        1. How the Kconfig+Kbuild system works – a minimal take
      2. Arriving at a default configuration
        1. Obtaining a good starting point for kernel configuration
        2. Kernel config using distribution config as a starting point
        3. Tuned kernel config via the localmodconfig approach
        4. Kernel config for typical embedded Linux systems
        5. Seeing all available config options
      3. Getting going with the localmodconfig approach
      4. Tuning our kernel configuration via the make menuconfig UI
        1. Sample usage of the make menuconfig UI
      5. Kernel config – exploring a bit more
        1. Searching within the menuconfig UI
        2. Looking up the differences in configuration
        3. Using the kernel’s config script to view/edit the kernel config
        4. Configuring the kernel for security
        5. Miscellaneous tips – kernel config
    7. Customizing the kernel menu, Kconfig, and adding our own menu item
      1. Understanding the Kconfig* files
      2. Creating a new menu item within the General Setup menu
      3. A few details on the Kconfig language
    8. Summary
    9. Exercise
    10. Questions
    11. Further reading
  4. Building the 6.x Linux Kernel from Source – Part 2
    1. Technical requirements
    2. Step 4 – building the kernel image and modules
      1. Getting over a cert config issue on Ubuntu
    3. Step 5 – installing the kernel modules
      1. Locating the kernel modules within the kernel source
      2. Getting the kernel modules installed
        1. Overriding the default module installation location
    4. Step 6 – generating the initramfs image and bootloader setup
      1. Generating the initramfs image – under the hood
    5. Understanding the initramfs framework
      1. Why the initramfs framework?
      2. Understanding the basics of the boot process on the x86
      3. More on the initramfs framework
        1. Peeking into the initramfs image
    6. Step 7 – customizing the GRUB bootloader
      1. Customizing GRUB – the basics
      2. Selecting the default kernel to boot into
      3. Booting our VM via the GNU GRUB bootloader
      4. Experimenting with the GRUB prompt
    7. Verifying our new kernel’s configuration
    8. Kernel build for the Raspberry Pi
      1. Step 1 – cloning the Raspberry Pi kernel source tree
      2. Step 2 – installing an x86_64-to-AArch64 cross-toolchain
      3. Step 3 – configuring and building the Raspberry Pi AArch64 kernel
    9. Miscellaneous tips on the kernel build
      1. Minimum version requirements
      2. Building a kernel for another site
      3. Watching the kernel build run
      4. A shortcut shell syntax to the build procedure
      5. Dealing with missing OpenSSL development headers
      6. How can I check which distro kernels are installed?
    10. Summary
    11. Questions
    12. Further reading
  5. Writing Your First Kernel Module – Part 1
    1. Technical requirements
    2. Understanding the kernel architecture – part 1
      1. User space and kernel space
      2. Library and system call APIs
      3. Kernel space components
    3. Exploring LKMs
      1. The LKM framework
      2. Kernel modules within the kernel source tree
    4. Writing our very first kernel module
      1. Introducing our Hello, world LKM C code
      2. Breaking it down
        1. Kernel headers
        2. Module macros
        3. Entry and exit points
        4. Return values
    5. Common operations on kernel modules
      1. Building the kernel module
      2. Running the kernel module
      3. A quick first look at the kernel printk()
      4. Listing the live kernel modules
      5. Unloading the module from kernel memory
      6. Our lkm convenience script
    6. Understanding kernel logging and printk
      1. Using the kernel memory ring buffer
      2. Kernel logging and systemd’s journalctl
      3. Using printk log levels
        1. The pr_<foo> convenience macros
        2. Writing to the console
        3. Writing output to the Raspberry Pi console
        4. Turning on debug-level kernel messages
      4. Rate limiting the printk instances
        1. Rate-limiting macros to use
      5. Generating kernel messages from user space
      6. Standardizing printk output via the pr_fmt macro
      7. Portability and the printk format specifiers
      8. Understanding the new printk indexing feature
    7. Understanding the basics of a kernel module Makefile
    8. Summary
    9. Questions
    10. Further reading
  6. Writing Your First Kernel Module – Part 2
    1. Technical requirements
    2. A “better” Makefile template for your kernel modules
      1. Configuring a “debug” kernel
    3. Cross-compiling a kernel module
      1. Setting up the system for cross-compilation
      2. Attempt 1 – setting the ARCH and CROSS_COMPILE environment variables
      3. Attempt 2 – pointing the Makefile to the correct kernel source tree for the target
      4. Attempt 3 – cross-compiling our kernel module
        1. Examining Linux kernel ABI compatibility issues
      5. Attempt 4 – cross-compiling our kernel module
      6. Summarizing what went wrong with the module cross-buildd/load and how it was fixed
    4. Gathering minimal system information
      1. Being a bit more security-aware
    5. Licensing kernel modules
      1. Licensing of inline kernel code
      2. Licensing of out-of-tree kernel modules
    6. Emulating “library-like” features for kernel modules
      1. Performing library emulation via linking multiple source files
      2. Understanding function and variable scope in a kernel module
      3. Understanding module stacking
        1. Trying out module stacking
      4. Emulating ‘library-like’ features – summary and conclusions
    7. Passing parameters to a kernel module
      1. Declaring and using module parameters
      2. Getting/setting module parameters after insertion
      3. Learning module parameter data types and validation
        1. Validating kernel module parameters
        2. Overriding the module parameter’s name
        3. Hardware-related kernel parameters
    8. Floating point not allowed in the kernel
      1. Forcing FP in the kernel
    9. Auto-loading modules on system boot
      1. Module auto-loading – additional details
    10. Kernel modules and security – an overview
      1. Proc filesystem tunables affecting the system log
        1. A quick word on the dmesg_restrict sysctl
        2. A quick word on the kptr_restrict sysctl
      2. Understanding the cryptographic signing of kernel modules
        1. The two module-signing modes
      3. Disabling kernel modules altogether
      4. The kernel lockdown LSM – an introduction
    11. Coding style guidelines for kernel developers
    12. Contributing to the mainline kernel
      1. Getting started with contributing to the kernel
    13. Summary
    14. Questions
    15. Further reading
  7. Kernel Internals Essentials – Processes and Threads
    1. Technical requirements
    2. Understanding process and interrupt contexts
    3. Understanding the basics of the process Virtual Address Space (VAS)
    4. Organizing processes, threads, and their stacks – user and kernel space
      1. Running a small script to see the number of processes and threads alive
      2. User space organization
      3. Kernel space organization
        1. Summarizing the kernel with respect to threads, task structures, and stacks
      4. Viewing the user and kernel stacks
        1. Traditional approach to viewing the stacks
        2. eBPF – the modern approach to viewing both stacks
      5. The 10,000-foot view of the process VAS
    5. Understanding and accessing the kernel task structure
      1. Looking into the task structure
      2. Accessing the task structure with current
      3. Determining the context
    6. Working with the task structure via ‘current’
      1. Built-in kernel helper methods and optimizations
      2. Trying out the kernel module to print process context info
        1. Seeing that the Linux OS is monolithic
        2. Coding for security with printk
    7. Iterating over the kernel’s task lists
      1. Iterating over the task list I – displaying all processes
      2. Iterating over the task list II – displaying all threads
        1. Differentiating between the process and thread – the TGID and the PID
      3. Iterating over the task list III – the code
    8. Summary
    9. Questions
    10. Further reading
  8. Memory Management Internals – Essentials
    1. Technical requirements
    2. Understanding the VM split
      1. Looking under the hood – the Hello, world C program
        1. Going beyond the printf() API
      2. Virtual addressing and address translation
      3. VM split on 64-bit Linux systems
        1. Common VM splits
      4. Understanding the process VAS – the full view
    3. Examining the process VAS
      1. Examining the user VAS in detail
        1. Directly viewing the process memory map using procfs
        2. Frontends to view the process memory map
      2. Understanding VMA basics
    4. Examining the kernel VAS
      1. High memory on 32-bit systems
      2. Writing a kernel module to show information about the kernel VAS
        1. Macros and variables describing the kernel VAS layout
        2. Trying it out – viewing kernel VAS details
        3. The kernel VAS via procmap
        4. Trying it out – the user segment
        5. Viewing kernel documentation on the memory layout
    5. Randomizing the memory layout – KASLR
      1. User memory randomization with ASLR
      2. Kernel memory layout randomization with KASLR
      3. Querying/setting KASLR status with a script
    6. Understanding physical memory organization
      1. Physical RAM organization
        1. Nodes and NUMA
        2. Zones within a node
      2. Direct-mapped RAM and address translation
      3. An introduction to physical memory models
        1. Understanding the sparsemem[-vmemmap] memory model in brief
    7. Summary
    8. Questions
    9. Further reading
  9. Kernel Memory Allocation for Module Authors – Part 1
    1. Technical requirements
    2. Introducing kernel memory allocators
    3. Understanding and using the kernel page allocator (or BSA)
      1. The fundamental workings of the page allocator
        1. Understanding the organization of the page allocator freelist
        2. The workings of the page allocator
        3. Working through a few scenarios
        4. Page allocator internals – a few more details
      2. Learning how to use the page allocator APIs
        1. Dealing with the GFP flags
        2. Freeing pages with the page allocator
        3. A few guidelines to observe when (de)allocating kernel memory
        4. Writing a kernel module to demo using the page allocator APIs
        5. Deploying our lowlevel_mem_lkm kernel module
        6. The page allocator and internal fragmentation (wastage)
      3. The GFP flags – digging deeper
        1. Never sleep in interrupt or atomic contexts
        2. Page allocator – pros and cons
    4. Understanding and using the kernel slab allocator
      1. The object caching idea
        1. A few FAQs regarding (slab) memory usage and their answers
      2. Learning how to use the slab allocator APIs
        1. Allocating slab memory
        2. Freeing slab memory
        3. Data structures – a few design tips
        4. The actual slab caches in use for kmalloc
        5. Writing a kernel module to use the basic slab APIs
    5. Size limitations of the kmalloc API
      1. Testing the limits – memory allocation with a single call
        1. Checking via the /proc/buddyinfo pseudofile
    6. Slab allocator – a few additional details
      1. Using the kernel’s resource-managed memory allocation APIs
      2. Additional slab helper APIs
      3. Control groups and memory
    7. Caveats when using the slab allocator
      1. Background details and conclusions
      2. Testing slab allocation with ksize() – case 1
      3. Testing slab allocation with ksize() – case 2
        1. Interpreting the output from case 2
        2. Graphing it
      4. Finding internal fragmentation (wastage) within the kernel
        1. The easy way with slabinfo
        2. More details with alloc_traces and a custom script
      5. Slab layer - pros and cons
      6. Slab layer – a word on its implementations within the kernel
    8. Summary
    9. Questions
    10. Further reading
  10. Kernel Memory Allocation for Module Authors – Part 2
    1. Technical requirements
    2. Creating a custom slab cache
      1. Creating and using a custom slab cache within a kernel module
        1. Step 1 – creating a custom slab cache
        2. Step 2 – using our custom slab cache’s memory
        3. Step 3 – destroying our custom cache
      2. Custom slab – a demo kernel module
        1. Extracting useful information regarding slab caches
      3. Understanding slab shrinkers
      4. Summarizing the slab allocator pros and cons
    3. Debugging kernel memory issues – a quick mention
    4. Understanding and using the kernel vmalloc() API
      1. Learning to use the vmalloc family of APIs
      2. Trying out vmalloc()
      3. A brief note on user-mode memory allocations and demand paging
      4. Friends of vmalloc()
        1. Is this vmalloc-ed (or module region) memory?
        2. Unsure which API to use? Try kvmalloc()
        3. Miscellaneous helpers – vmalloc_exec() and vmalloc_user()
        4. Specifying memory protections
      5. The kmalloc() and vmalloc() APIs – a quick comparison
    5. Memory allocation in the kernel – which APIs to use when
      1. Visualizing the kernel memory allocation API set
      2. Selecting an appropriate API for kernel memory allocation
      3. A word on DMA and CMA
    6. Memory reclaim – a key kernel housekeeping task
      1. Zone watermarks and kswapd
      2. The new multi-generational LRU (MGLRU) lists feature
        1. Trying it out – seeing histogram data from MGLRU
      3. A quick introduction to DAMON – the Data Access Monitoring feature
        1. Running a memory workload and visualizing it with DAMON’s damo front-end
    7. Stayin’ alive – the OOM killer
      1. Deliberately invoking the OOM killer
        1. Invoking the OOM killer via Magic SysRq
        2. Invoking the OOM killer with a crazy allocator program
      2. Understanding the three VM overcommit_memory policies
        1. VM overcommit from the viewpoint of the __vm_enough_memory() code
        2. Case 1: vm.overcommit_memory == 0 (the default, OVERCOMMIT_GUESS)
        3. Case 2: vm.overcommit_memory == 2 (VM overcommit turned off, OVERCOMMIT_NEVER) and vm.overcommit_ratio == 50
      3. Demand paging and OOM
        1. The optimized (unmapped) read
      4. Understanding the OOM score
      5. Closing thoughts on the OOM killer and cgroups
        1. Cgroups and memory bandwidth – a note
    8. Summary
    9. Questions
    10. Further reading
  11. The CPU Scheduler – Part 1
    1. Technical requirements
    2. Learning about the CPU scheduling internals – part 1 – essential background
      1. What is the KSE on Linux?
      2. The Linux process state machine
      3. The POSIX scheduling policies
        1. Thread priorities
    3. Visualizing the flow
      1. Using the gnome-system-monitor GUI to visualize the flow
      2. Using perf to visualize the flow
        1. Trying it out – the command-line approach
        2. Trying it out – the graphical approach
      3. Visualizing the flow via alternate approaches
    4. Learning about the CPU scheduling internals – part 2
      1. Understanding modular scheduling classes
        1. A conceptual example to help understand scheduling classes
        2. Asking the scheduling class
        3. The workings of the Completely Fair Scheduling (CFS) class in brief
        4. Scheduling statistics
    5. Querying a given thread’s scheduling policy and priority
    6. Learning about the CPU scheduling internals – part 3
      1. Preemptible kernel
        1. The dynamic preemptible kernel feature
      2. Who runs the scheduler code?
      3. When does schedule() run?
        1. Minimally understanding the thread_info structure
        2. The timer interrupt housekeeping – setting TIF_NEED_RESCHED
        3. The process context part – checking TIF_NEED_RESCHED
        4. CPU scheduler entry points – a summary
        5. The core scheduler code in brief
    7. Summary
    8. Questions
    9. Further reading
  12. The CPU Scheduler – Part 2
    1. Technical requirements
    2. Understanding, querying, and setting the CPU affinity mask
      1. Querying and setting a thread’s CPU affinity mask
        1. Using taskset to perform CPU affinity
        2. Setting the CPU affinity mask on a kernel thread
    3. Querying and setting a thread’s scheduling policy and priority
      1. Setting the policy and priority within the kernel – on a kernel thread
        1. A real-world example – threaded interrupt handlers
    4. An introduction to cgroups
      1. Cgroup controllers
      2. Exploring the cgroups v2 hierarchy
        1. Enabling or disabling controllers
        2. The cgroups within the hierarchy
        3. Systemd and cgroups
        4. Our cgroups v2 explorer script
      3. Trying it out – constraining the CPU resource via cgroups v2
        1. Leveraging systemd to set up CPU resource constraints on a service
        2. The manual way – a cgroups v2 CPU controller
    5. Running Linux as an RTOS – an introduction
      1. Pointers to building RTL for the mainline 6.x kernel (on x86_64)
    6. Miscellaneous scheduling related topics
      1. A few small (kernel space) routines to check out
      2. The ghOSt OS
    7. Summary
    8. Questions
    9. Further reading
  13. Kernel Synchronization – Part 1
    1. Technical requirements
    2. Critical sections, exclusive execution, and atomicity
      1. What is a critical section?
      2. A classic case – the global i ++
      3. Concepts – the lock
        1. Critical sections – a summary of key points
      4. Data races – a more formal definition
    3. Concurrency concerns within the Linux kernel
      1. Multicore SMP systems and data races
      2. Preemptible kernels, blocking I/O, and data races
      3. Hardware interrupts and data races
      4. Locking guidelines and deadlock
    4. Mutex or spinlock? Which to use when
      1. Determining which lock to use – in theory
      2. Determining which lock to use – in practice
    5. Using the mutex lock
      1. Initializing the mutex lock
      2. Correctly using the mutex lock
      3. Mutex lock and unlock APIs and their usage
        1. Mutex lock – via [un]interruptible sleep?
      4. Mutex locking – an example driver
      5. The mutex lock – a few remaining points
        1. Mutex lock API variants
        2. The semaphore and the mutex
        3. Priority inversion and the RT-mutex
        4. Internal design
    6. Using the spinlock
      1. Spinlock – simple usage
      2. Spinlock – an example driver
      3. Test – sleep in an atomic context
        1. Testing the buggy module on a 6.1 debug kernel
    7. Locking and interrupts
      1. Scenario 1 – driver method and hardware interrupt handler run serialized, sequentially
      2. Scenario 2 – driver method and hardware interrupt handler run interleaved
        1. Scenario 2 on a single-core (UP) system
        2. Scenario 2 on a multicore (SMP) system
        3. Solving the issue on UP and SMP with the spin_[un]lock_irq() API variant
      3. Scenario 3 – some interrupts masked, driver method and hardware interrupt handler run interleaved
      4. Interrupt handling, bottom halves, and locking
        1. Interrupt handling on Linux – a summary of key points
        2. Bottom halves and locking
      5. Using spinlocks – a quick summary
    8. Locking – common mistakes and guidelines
      1. Common mistakes
      2. Locking guidelines
    9. Solutions
    10. Summary
    11. Questions
    12. Further reading
  14. Kernel Synchronization – Part 2
    1. Technical requirements
    2. Using the atomic_t and refcount_t interfaces
      1. The newer refcount_t versus older atomic_t interfaces
      2. The simpler atomic_t and refcount_t interfaces
        1. Examples of using refcount_t within the kernel codebase
      3. 64-bit atomic integer operators
      4. A note on internal implementation
    3. Using the RMW atomic operators
      1. RMW atomic operations – operating on device registers
        1. Using the RMW bitwise operators
        2. Using bitwise atomic operators – an example
        3. Efficiently searching a bitmask
    4. Using the reader-writer spinlock
      1. Reader-writer spinlock interfaces
      2. Trying out the reader-writer spinlock
      3. Performance issues with reader-writer spinlocks
      4. The reader-writer semaphore
    5. Understanding CPU caching basics, cache effects, and false sharing
      1. An introduction to CPU caches
      2. The risks – cache coherency, performance issues, and false sharing
        1. What is the cache coherency problem?
        2. The false sharing issue
    6. Lock-free programming with per-CPU and RCU
      1. Per-CPU variables
        1. Working with per-CPU variables
        2. Per-CPU – an example kernel module
        3. Per-CPU usage within the kernel
      2. Understanding and using the RCU (Read-Copy-Update) lock-free technology – a primer
        1. How does RCU work?
        2. Trying out RCU
        3. RCU: detailed documentation
        4. RCU usage within the kernel
    7. Lock debugging within the kernel
      1. Configuring a debug kernel for lock debugging
      2. The lock validator lockdep – catching locking issues early
      3. Catching deadlock bugs with lockdep – a few examples
        1. Example 1 – catching a self deadlock bug with lockdep
        2. Example 2 – catching an AB-BA deadlock with lockdep
      4. Brief notes on lockdep – annotations and issues
        1. A note on lockdep annotations
        2. A note on lockdep – known issues
      5. Kernel lock statistics
        1. Viewing and interpreting the kernel lock statistics
    8. Introducing memory barriers
      1. An example of using memory barriers in a device driver
      2. A note on marked accesses
    9. Summary
    10. Questions
    11. Further reading
  15. Other Books You May Enjoy
  16. Index

Product information

  • Title: Linux Kernel Programming - Second Edition
  • Author(s): Kaiwan N. Billimoria
  • Release date: February 2024
  • Publisher(s): Packt Publishing
  • ISBN: 9781803232225