Linux Kernel Programming Part 2 - Char Device Drivers and Kernel Synchronization

Book description

Discover how to write high-quality character driver code, interface with userspace, work with chip memory, and gain an in-depth understanding of working with hardware interrupts and kernel synchronization

Key Features

  • Delve into hardware interrupt handling, threaded IRQs, tasklets, softirqs, and understand which to use when
  • Explore powerful techniques to perform user-kernel interfacing, peripheral I/O and use kernel mechanisms
  • Work with key kernel synchronization primitives to solve kernel concurrency issues

Book Description

Linux Kernel Programming Part 2 - Char Device Drivers and Kernel Synchronization is an ideal companion guide to the Linux Kernel Programming book. This book provides a comprehensive introduction for those new to Linux device driver development and will have you up and running with writing misc class character device driver code (on the 5.4 LTS Linux kernel) in next to no time.

You'll begin by learning how to write a simple and complete misc class character driver before interfacing your driver with user-mode processes via procfs, sysfs, debugfs, netlink sockets, and ioctl. You'll then find out how to work with hardware I/O memory. The book covers working with hardware interrupts in depth and helps you understand interrupt request (IRQ) allocation, threaded IRQ handlers, tasklets, and softirqs. You'll also explore the practical usage of useful kernel mechanisms, setting up delays, timers, kernel threads, and workqueues. Finally, you'll discover how to deal with the complexity of kernel synchronization with locking technologies (mutexes, spinlocks, and atomic/refcount operators), including more advanced topics such as cache effects, a primer on lock-free techniques, deadlock avoidance (with lockdep), and kernel lock debugging techniques.

By the end of this Linux kernel book, you'll have learned the fundamentals of writing Linux character device driver code for real-world projects and products.

What you will learn

  • Get to grips with the basics of the modern Linux Device Model (LDM)
  • Write a simple yet complete misc class character device driver
  • Perform user-kernel interfacing using popular methods
  • Understand and handle hardware interrupts confidently
  • Perform I/O on peripheral hardware chip memory
  • Explore kernel APIs to work with delays, timers, kthreads, and workqueues
  • Understand kernel concurrency issues
  • Work with key kernel synchronization primitives and discover how to detect and avoid deadlock

Who this book is for

An understanding of the topics covered in the Linux Kernel Programming book is highly recommended to make the most of this book. This book is for Linux programmers beginning to find their way with device driver development. Linux device driver developers looking to overcome frequent and common kernel/driver development issues, as well as perform common driver tasks such as user-kernel interfaces, performing peripheral I/O, handling hardware interrupts, and dealing with concurrency will benefit from this book. A basic understanding of Linux kernel internals (and common APIs), kernel module development, and C programming is required.

Publisher resources

Download Example Code

Table of contents

  1. Title Page
  2. Copyright and Credits
    1. Linux Kernel Programming Part 2 - Char Device Drivers and Kernel Synchronization
  3. Dedication
  4. About Packt
    1. Why subscribe?
  5. Contributors
    1. About the author
    2. About the reviewers
  6. Preface
    1. Who this book is for
    2. What this book covers
    3. To get the most out of this book
    4. Download the example code files
    5. Download the color images
    6. Conventions used
    7. Get in touch
    8. Reviews
  7. Section 1: Character Device Driver Basics
  8. Writing a Simple misc Character Device Driver
    1. Technical requirements
    2. Getting started with writing a simple misc character device driver
    3. Understanding the device basics
    4. A quick note on the Linux Device Model
    5. Writing the misc driver code – part 1
    6. Understanding the connection between the process, the driver, and the kernel
    7. Handling unsupported methods
    8. Writing the misc driver code – part 2
    9. Writing the misc driver code – part 3
    10. Testing our simple misc driver
    11. Copying data from kernel to user space and vice versa
    12. Leveraging kernel APIs to perform the data transfer
    13. A misc driver with a secret
    14. Writing the 'secret' misc device driver's code
    15. Our secret driver – the init code
    16. Our secret driver – the read method
    17. Our secret driver – the write method
    18. Our secret driver – cleanup
    19. Our secret driver – the user space test app
    20. Issues and security concerns
    21. Hacking the secret driver
    22. Bad driver – buggy read()
    23. Bad driver – buggy write() – a privesc!
    24. User space test app modifications
    25. Device driver modifications
    26. Let's get root now
    27. Summary
    28. Questions
    29. Further reading
  9. User-Kernel Communication Pathways
    1. Technical requirements
    2. Approaches to communicating/interfacing a kernel driver with a user space C app
    3. Interfacing via the proc filesystem (procfs)
    4. Understanding the proc filesystem
    5. Directories under /proc
    6. The purpose behind the proc filesystem
    7. procfs is off-bounds to driver authors
    8. Using procfs to interface with the user space
    9. Basic procfs APIs
    10. The four procfs files we will create
    11. Trying out the dynamic debug_level procfs control
    12. Dynamically controlling debug_level via procfs
    13. A few misc procfs APIs
    14. Interfacing via the sys filesystem (sysfs)
    15. Creating a sysfs (pseudo) file in code
    16. Creating a simple platform device
    17. Platform devices
    18. Tying it all together – setting up the device attributes and creating the sysfs file
    19. The code for implementing our sysfs file and its callbacks
    20. The "one value per sysfs file" rule
    21. Interfacing via the debug filesystem (debugfs)
    22. Checking for the presence of debugfs
    23. Looking up the debugfs API documentation
    24. An interfacing example with debugfs
    25. Creating and using the first debugfs file
    26. Creating and using the second debugfs file
    27. Helper debugfs APIs for working on numeric globals
    28. Removing the debugfs pseudo file(s)
    29. Seeing a kernel bug – an Oops!
    30. Debugfs – actual users
    31. Interfacing via netlink sockets
    32. Advantages using sockets
    33. Understanding what a netlink socket is
    34. Writing the user space netlink socket application
    35. Writing the kernel-space netlink socket code as a kernel module
    36. Trying out our netlink interfacing project
    37. Interfacing via the ioctl system call
    38. Using ioctl in the user and kernel space
    39. User space – using the ioctl system call
    40. Kernel space – using the ioctl system call
    41. ioctl as a debug interface
    42. Comparing the interfacing methods – a table
    43. Summary
    44. Questions
    45. Further reading
  10. Working with Hardware I/O Memory
    1. Technical requirements
    2. Accessing hardware I/O memory from the kernel
    3. Understanding the issue with direct access
    4. The solution – mapping via I/O memory or I/O port
    5. Asking the kernel's permission
    6. Understanding and using memory-mapped I/O
    7. Using the ioremap*() APIs
    8. The newer breed – the devm_* managed APIs
    9. Obtaining the device resources
    10. All in one with the devm_ioremap_resource() API
    11. Looking up the new mapping via /proc/iomem
    12. MMIO – performing the actual I/O
    13. Performing 1- to 8-byte reads and writes on MMIO memory regions
    14. Performing repeating I/O on MMIO memory regions
    15. Setting and copying on MMIO memory regions
    16. Understanding and using port-mapped I/O
    17. PMIO – performing the actual I/O
    18. A PIO example – the i8042
    19. Looking up the port(s) via /proc/ioports
    20. Port I/O – a few remaining points to note
    21. Summary
    22. Questions
    23. Further reading
  11. Handling Hardware Interrupts
    1. Technical requirements
    2. Hardware interrupts and how the kernel handles them
    3. Allocating the hardware IRQ
    4. Allocating your interrupt handler with request_irq()
    5. Freeing the IRQ line
    6. Setting interrupt flags
    7. Understanding level- and edge-triggered interrupts – a brief note
    8. Code view 1 – the IXGB network driver
    9. Implementing the interrupt handler routine
    10. Interrupt context guidelines – what to do and what not to do
    11. Don't block – spotting possibly blocking code paths
    12. Interrupt masking – the defaults and controlling it
    13. Keep it fast
    14. Writing the interrupt handler routine itself
    15. Code view 2 – the i8042 driver's interrupt handler
    16. Code view 3 – the IXGB network driver's interrupt handler
    17. IRQ allocation – the modern way – the managed interrupt facility
    18. Working with the threaded interrupts model
    19. Employing the threaded interrupt model – the API
    20. Employing the managed threaded interrupt model – the recommended way
    21. Code view 4 – the STM32 F7 microcontroller's threaded interrupt handler
    22. Internally implementing the threaded interrupt
    23. Why use threaded interrupts?
    24. Threaded interrupts – to really make it real time
    25. Constraints when using a threaded handler
    26. Working with either hardirq or threaded handlers
    27. Enabling and disabling IRQs
    28. The NMI
    29. Viewing all allocated interrupt (IRQ) lines
    30. Understanding and using top and bottom halves
    31. Specifying and using a tasklet
    32. Initializing the tasklet
    33. Running the tasklet
    34. Understanding the kernel softirq mechanism
    35. Available softirqs and what they are for
    36. Understanding how the kernel runs softirqs
    37. Running tasklets
    38. Employing the ksoftirqd kernel threads
    39. Softirqs and concurrency
    40. Hardirqs, tasklets, and threaded handlers – what to use when
    41. Fully figuring out the context
    42. Viewing the context – examples
    43. How Linux prioritizes activities
    44. A few remaining FAQs answered
    45. Load balancing interrupts and IRQ affinity
    46. Does the kernel maintain separate IRQ stacks?
    47. Measuring metrics and latency
    48. Measuring interrupts with [e]BPF
    49. Measuring time servicing individual hardirqs
    50. Measuring time servicing individual softirqs
    51. Using Ftrace to get a handle on system latencies
    52. Finding the interrupts disabled worst-case time latency with Ftrace
    53. Other tools
    54. Summary
    55. Questions
    56. Further reading
  12. Working with Kernel Timers, Threads, and Workqueues
    1. Technical requirements
    2. Delaying for a given time in the kernel
    3. Understanding how to use the *delay() atomic APIs
    4. Understanding how to use the *sleep() blocking APIs
    5. Taking timestamps within kernel code
    6. Let's try it – how long do delays and sleeps really take?
    7. The "sed" drivers – to demo kernel timers, kthreads, and workqueues
    8. Setting up and using kernel timers
    9. Using kernel timers
    10. Our simple kernel timer module – code view 1
    11. Our simple kernel timer module – code view 2
    12. Our simple kernel timer module – running it
    13. sed1 – implementing timeouts with our demo sed1 driver
    14. Deliberately missing the bus
    15. Creating and working with kernel threads
    16. A simple demo – creating a kernel thread
    17. Running the kthread_simple kernel thread demo
    18. The sed2 driver – design and implementation
    19. sed2 – the design
    20. sed2 driver – code implementation
    21. sed2 – trying it out
    22. Querying and setting the scheduling policy/priority of a kernel thread
    23. Using kernel workqueues
    24. The bare minimum workqueue internals
    25. Using the kernel-global workqueue
    26. Initializing the kernel-global workqueue for your task – INIT_WORK()
    27. Having your work task execute – schedule_work()
    28. Variations of scheduling your work task
    29. Cleaning up – canceling or flushing your work task
    30. A quick summary of the workflow
    31. Our simple work queue kernel module – code view
    32. Our simple work queue kernel module – running it
    33. The sed3 mini project – a very brief look
    34. Summary
    35. Questions
    36. Further reading
  13. Section 2: Delving Deeper
  14. Kernel Synchronization - Part 1
    1. Critical sections, exclusive execution, and atomicity
    2. What is a critical section?
    3. A classic case – the global i ++
    4. Concepts – the lock
    5. A summary of key points
    6. Concurrency concerns within the Linux kernel
    7. Multicore SMP systems and data races
    8. Preemptible kernels, blocking I/O, and data races
    9. Hardware interrupts and data races
    10. Locking guidelines and deadlocks
    11. Mutex or spinlock? Which to use when
    12. Determining which lock to use – in theory
    13. Determining which lock to use – in practice
    14. Using the mutex lock
    15. Initializing the mutex lock
    16. Correctly using the mutex lock
    17. Mutex lock and unlock APIs and their usage
    18. Mutex lock – via [un]interruptible sleep?
    19. Mutex locking – an example driver
    20. The mutex lock – a few remaining points
    21. Mutex lock API variants
    22. The mutex trylock variant
    23. The mutex interruptible and killable variants
    24. The mutex io variant
    25. The semaphore and the mutex
    26. Priority inversion and the RT-mutex
    27. Internal design
    28. Using the spinlock
    29. Spinlock – simple usage
    30. Spinlock – an example driver
    31. Test – sleep in an atomic context
    32. Testing on a 5.4 debug kernel
    33. Testing on a 5.4 non-debug distro kernel
    34. Locking and interrupts
    35. Using spinlocks – a quick summary
    36. Summary
    37. Questions
    38. Further reading
  15. Kernel Synchronization - Part 2
    1. Using the atomic_t and refcount_t interfaces
    2. The newer refcount_t versus older atomic_t interfaces
    3. The simpler atomic_t and refcount_t interfaces
    4. Examples of using refcount_t within the kernel code base
    5. 64-bit atomic integer operators
    6. Using the RMW atomic operators
    7. RMW atomic operations – operating on device registers
    8. Using the RMW bitwise operators
    9. Using bitwise atomic operators – an example
    10. Efficiently searching a bitmask
    11. Using the reader-writer spinlock
    12. Reader-writer spinlock interfaces
    13. A word of caution
    14. The reader-writer semaphore
    15. Cache effects and false sharing
    16. Lock-free programming with per-CPU variables
    17. Per-CPU variables
    18. Working with per-CPU
    19. Allocating, initialization, and freeing per-CPU variables
    20. Performing I/O (reads and writes) on per-CPU variables
    21. Per-CPU – an example kernel module
    22. Per-CPU usage within the kernel
    23. Lock debugging within the kernel
    24. Configuring a debug kernel for lock debugging
    25. The lock validator lockdep – catching locking issues early
    26. Examples – catching deadlock bugs with lockdep
    27. Example 1 – catching a self deadlock bug with lockdep
    28. Fixing it
    29. Example 2 – catching an AB-BA deadlock with lockdep
    30. lockdep – annotations and issues
    31. lockdep annotations
    32. lockdep issues
    33. Lock statistics
    34. Viewing lock stats
    35. Memory barriers – an introduction
    36. An example of using memory barriers in a device driver
    37. Summary
    38. Questions
    39. Further reading
  16. Other Books You May Enjoy
    1. Leave a review - let other readers know what you think

Product information

  • Title: Linux Kernel Programming Part 2 - Char Device Drivers and Kernel Synchronization
  • Author(s): Kaiwan N Billimoria
  • Release date: March 2021
  • Publisher(s): Packt Publishing
  • ISBN: 9781801079518