Linux Kernel Programming Part 2 - Char Device Drivers and Kernel Synchronization: Create user-kernel interfaces, work with peripheral I/O, and handle hardware interrupts
()
About this ebook
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.
Read more from Kaiwan N. Billimoria
Linux Kernel Programming: A comprehensive guide to kernel internals, writing kernel modules, and kernel synchronization Rating: 0 out of 5 stars0 ratingsLinux Kernel Programming: A comprehensive and practical guide to kernel internals, writing modules, and kernel synchronization Rating: 0 out of 5 stars0 ratingsHands-On System Programming with Linux: Explore Linux system programming interfaces, theory, and practice Rating: 0 out of 5 stars0 ratingsLinux Kernel Debugging: Leverage proven tools and advanced techniques to effectively debug Linux kernels and kernel modules Rating: 0 out of 5 stars0 ratings
Related to Linux Kernel Programming Part 2 - Char Device Drivers and Kernel Synchronization
Related ebooks
Mastering Embedded Linux Programming: Create fast and reliable embedded solutions with Linux 5.4 and the Yocto Project 3.1 (Dunfell) Rating: 0 out of 5 stars0 ratingsKali Linux 2018: Windows Penetration Testing Rating: 0 out of 5 stars0 ratingsMastering Linux Device Driver Development: Write custom device drivers to support computer peripherals in Linux operating systems Rating: 0 out of 5 stars0 ratingsLinux Kernel Programming: A comprehensive and practical guide to kernel internals, writing modules, and kernel synchronization Rating: 0 out of 5 stars0 ratingsLinux Device Driver Development: Everything you need to start with device driver development for Linux kernel and embedded Linux Rating: 0 out of 5 stars0 ratingsLinux Device Driver Development Cookbook: Develop custom drivers for your embedded Linux applications Rating: 0 out of 5 stars0 ratingsMastering Embedded Linux Programming - Second Edition Rating: 4 out of 5 stars4/5Embedded Programming with Modern C++ Cookbook: Practical recipes to help you build robust and secure embedded applications on Linux Rating: 0 out of 5 stars0 ratingsProgramming Basics: Getting Started with Java, C#, and Python Rating: 0 out of 5 stars0 ratingsDevOps with Kubernetes: Accelerating software delivery with container orchestrators, 2nd Edition Rating: 0 out of 5 stars0 ratingsBeagleBone Essentials Rating: 0 out of 5 stars0 ratingsLinux Administration Cookbook: Insightful recipes to work with system administration tasks on Linux Rating: 0 out of 5 stars0 ratingsContinuous Delivery with Docker and Jenkins: Create secure applications by building complete CI/CD pipelines, 2nd Edition Rating: 0 out of 5 stars0 ratingsUsing Yocto Project with BeagleBone Black Rating: 0 out of 5 stars0 ratingsHands-On Embedded Programming with C++17: Create versatile and robust embedded solutions for MCUs and RTOSes with modern C++ Rating: 0 out of 5 stars0 ratingsLinux Mint Essentials Rating: 3 out of 5 stars3/5Hands-On Embedded Programming with Qt: Develop high performance applications for embedded systems with C++ and Qt 5 Rating: 0 out of 5 stars0 ratingsProgramming with 64-Bit ARM Assembly Language: Single Board Computer Development for Raspberry Pi and Mobile Devices Rating: 0 out of 5 stars0 ratingsHands-On Microservices with Kubernetes: Build, deploy, and manage scalable microservices on Kubernetes Rating: 0 out of 5 stars0 ratingsLearn Bosque Programming: Boost your productivity and software reliability with Microsoft's new open-source programming language Rating: 0 out of 5 stars0 ratingsWorking with Linux – Quick Hacks for the Command Line Rating: 5 out of 5 stars5/5Hands-On Internet of Things with MQTT: Build connected IoT devices with Arduino and MQ Telemetry Transport (MQTT) Rating: 0 out of 5 stars0 ratingsCompTIA Linux+ Certification Guide: A comprehensive guide to achieving LX0-103 and LX0-104 certifications with mock exams Rating: 0 out of 5 stars0 ratings
System Administration For You
Learn PowerShell in a Month of Lunches, Fourth Edition: Covers Windows, Linux, and macOS Rating: 5 out of 5 stars5/5Simply SQL: The Fun and Easy Way to Learn Best-Practice SQL Rating: 4 out of 5 stars4/5Linux: Learn in 24 Hours Rating: 5 out of 5 stars5/5PowerShell: A Beginner's Guide to Windows PowerShell Rating: 4 out of 5 stars4/5Learn PowerShell Scripting in a Month of Lunches Rating: 0 out of 5 stars0 ratingsCybersecurity: The Beginner's Guide: A comprehensive guide to getting started in cybersecurity Rating: 5 out of 5 stars5/5Instant Ubuntu Rating: 4 out of 5 stars4/5Ethical Hacking Rating: 4 out of 5 stars4/5Linux Bible Rating: 0 out of 5 stars0 ratingsLearning Microsoft Endpoint Manager: Unified Endpoint Management with Intune and the Enterprise Mobility + Security Suite Rating: 0 out of 5 stars0 ratingsOperating Systems DeMYSTiFieD Rating: 0 out of 5 stars0 ratingsLinux for Beginners: Linux Command Line, Linux Programming and Linux Operating System Rating: 4 out of 5 stars4/5CompTIA A+ Complete Review Guide: Core 1 Exam 220-1101 and Core 2 Exam 220-1102 Rating: 5 out of 5 stars5/5The Complete Powershell Training for Beginners Rating: 0 out of 5 stars0 ratingsLearn Windows PowerShell in a Month of Lunches Rating: 0 out of 5 stars0 ratingsWindows 11 Manual For Seniors: A Beginners Guide to Navigate Your Computer with Step-by-Step Instructions Rating: 3 out of 5 stars3/5Bash Command Line Pro Tips Rating: 5 out of 5 stars5/5Building a Plex Server with Raspberry Pi Rating: 0 out of 5 stars0 ratingsMastering Windows PowerShell Scripting Rating: 4 out of 5 stars4/5Windows Security Basics: User Accounts Rating: 0 out of 5 stars0 ratingsLinux Commands By Example Rating: 5 out of 5 stars5/5How To Speed Up Computer: Your Step-By-Step Guide To Speeding Up Computer Rating: 0 out of 5 stars0 ratingsImprove your skills with Google Sheets: Professional training Rating: 0 out of 5 stars0 ratingsUbuntu 20.04 Essentials: A Guide to Ubuntu 20.04 Desktop and Server Editions Rating: 0 out of 5 stars0 ratingsPractical Data Analysis Rating: 4 out of 5 stars4/5PowerShell: A Comprehensive Guide to Windows PowerShell Rating: 4 out of 5 stars4/5Microsoft 365 Fundamentals Guide: Over 100 tips and tricks to help you get up and running with M365 quickly Rating: 0 out of 5 stars0 ratingsMastering Salesforce CRM Administration Rating: 5 out of 5 stars5/5
Reviews for Linux Kernel Programming Part 2 - Char Device Drivers and Kernel Synchronization
0 ratings0 reviews
Book preview
Linux Kernel Programming Part 2 - Char Device Drivers and Kernel Synchronization - Kaiwan N. Billimoria
Linux Kernel Programming Part 2 - Char Device Drivers and Kernel Synchronization
Create user-kernel interfaces, work with peripheral I/O, and handle hardware interrupts
Kaiwan N Billimoria
BIRMINGHAM - MUMBAI
Linux Kernel Programming Part 2 - Char Device Drivers and Kernel Synchronization
Copyright © 2021 Packt Publishing
All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, without the prior written permission of the publisher, except in the case of brief quotations embedded in critical articles or reviews.
Every effort has been made in the preparation of this book to ensure the accuracy of the information presented. However, the information contained in this book is sold without warranty, either express or implied. Neither the author, nor Packt Publishing or its dealers and distributors, will be held liable for any damages caused or alleged to have been caused directly or indirectly by this book.
Packt Publishing has endeavored to provide trademark information about all of the companies and products mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot guarantee the accuracy of this information.
Group Product Manager: Wilson D'souza
Publishing Product Manager: Vijin Boricha
Content Development Editor: Romy Dias
Senior Editor: Rahul D'souza
Technical Editor: Shruthi Shetty
Copy Editor: Safis Editing
Project Coordinator: Neil Dmello
Proofreader: Safis Editing
Indexer: Pratik Shirodkar
Production Designer: Shankar Kalbhor
First published: March 2021
Production reference: 1190321
Published by Packt Publishing Ltd.
Livery Place
35 Livery Street
Birmingham
B3 2PB, UK.
ISBN 978-1-80107-951-8
www.packt.com
First, to my dear parents, Diana and Nadir Nads
, for showing me how to live a happy and productive life. To my dear wife, Dilshad (an accomplished financial advisor herself), and our amazing kids, Sheroy and Danesh – thanks for all your love and patience.
– Kaiwan N Billimoria
Packt.com
Subscribe to our online digital library for full access to over 7,000 books and videos, as well as industry leading tools to help you plan your personal development and advance your career. For more information, please visit our website.
Why subscribe?
Spend less time learning and more time coding with practical eBooks and Videos from over 4,000 industry professionals
Improve your learning with Skill Plans built especially for you
Get a free eBook or video every month
Fully searchable for easy access to vital information
Copy and paste, print, and bookmark content
Did you know that Packt offers eBook versions of every book published, with PDF and ePub files available? You can upgrade to the eBook version at www.packt.com and as a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at [email protected] for more details.
At www.packt.com, you can also read a collection of free technical articles, sign up for a range of free newsletters, and receive exclusive discounts and offers on Packt books and eBooks.
Contributors
About the author
Kaiwan N Billimoria taught himself BASIC programming on his dad's IBM PC back in 1983. He was programming in C and Assembly on DOS until he discovered the joys of Unix, and by around 1997, Linux!
Kaiwan has worked on many aspects of the Linux system programming stack, including Bash scripting, system programming in C, kernel internals, device drivers, and embedded Linux work. He has actively worked on several commercial/FOSS projects. His contributions include drivers to the mainline Linux OS and many smaller projects hosted on GitHub. His Linux passion feeds well into his passion for teaching these topics to engineers, which he has done for well over two decades now. He's also the author of Hands-On System Programming with Linux. It doesn't hurt that he is a recreational ultrarunner too.
Writing this book took a long while; I'd like to thank the team from Packt for their patience and skill! Carlton Borges, Romy Dias, Vijin Boricha, Rohit Rajkumar, Vivek Anantharaman, Nithin Varghese, Hemangi Lotlikar, and all the others. It was indeed a pleasure working with you.
I owe a debt of gratitude to the very able technical reviewers – Donald Donnie
Tevault and Anil Kumar. They caught a lot of my mistakes and omissions and greatly helped make this book better.
About the reviewers
Donald A. Tevault, but you can call him Donnie, got involved with Linux way back in 2006 and has been working with it ever since. He holds the Linux Professional Institute Level 3 Security certification, and the GIAC Incident Handler certification. Donnie is a professional Linux trainer, and thanks to the magic of the internet, teaches Linux classes literally the world over from the comfort of his living room. He's also a Linux security researcher for an IoT security company.
Anil Kumar is a Linux BSP and firmware developer at Intel. He has over 12 years of software development experience across many verticals, including IoT, mobile chipsets, laptops/Chromebooks, media encoders, and transcoders. He has a master's degree in electronics design from the Indian Institute of Science and a bachelor's degree in electronics and communication from BMS College of Engineering, India. He is an electronics enthusiast and blogger and loves tinkering to create fun DIY projects.
Table of Contents
Title Page
Copyright and Credits
Linux Kernel Programming Part 2 - Char Device Drivers and Kernel Synchronization
Dedication
About Packt
Why subscribe?
Contributors
About the author
About the reviewers
Preface
Who this book is for
What this book covers
To get the most out of this book
Download the example code files
Download the color images
Conventions used
Get in touch
Reviews
Section 1: Character Device Driver Basics
Writing a Simple misc Character Device Driver
Technical requirements
Getting started with writing a simple misc character device driver
Understanding the device basics
A quick note on the Linux Device Model
Writing the misc driver code – part 1
Understanding the connection between the process, the driver, and the kernel
Handling unsupported methods
Writing the misc driver code – part 2
Writing the misc driver code – part 3
Testing our simple misc driver
Copying data from kernel to user space and vice versa
Leveraging kernel APIs to perform the data transfer
A misc driver with a secret
Writing the 'secret' misc device driver's code
Our secret driver – the init code
Our secret driver – the read method
Our secret driver – the write method
Our secret driver – cleanup
Our secret driver – the user space test app
Issues and security concerns
Hacking the secret driver
Bad driver – buggy read()
Bad driver – buggy write() – a privesc!
User space test app modifications
Device driver modifications
Let's get root now
Summary
Questions
Further reading
User-Kernel Communication Pathways
Technical requirements
Approaches to communicating/interfacing a kernel driver with a user space C app
Interfacing via the proc filesystem (procfs)
Understanding the proc filesystem
Directories under /proc
The purpose behind the proc filesystem
procfs is off-bounds to driver authors
Using procfs to interface with the user space
Basic procfs APIs
The four procfs files we will create
Trying out the dynamic debug_level procfs control
Dynamically controlling debug_level via procfs
A few misc procfs APIs
Interfacing via the sys filesystem (sysfs)
Creating a sysfs (pseudo) file in code
Creating a simple platform device
Platform devices
Tying it all together – setting up the device attributes and creating the sysfs file
The code for implementing our sysfs file and its callbacks
The one value per sysfs file
rule
Interfacing via the debug filesystem (debugfs)
Checking for the presence of debugfs
Looking up the debugfs API documentation
An interfacing example with debugfs
Creating and using the first debugfs file
Creating and using the second debugfs file
Helper debugfs APIs for working on numeric globals
Removing the debugfs pseudo file(s)
Seeing a kernel bug – an Oops!
Debugfs – actual users
Interfacing via netlink sockets
Advantages using sockets
Understanding what a netlink socket is
Writing the user space netlink socket application
Writing the kernel-space netlink socket code as a kernel module
Trying out our netlink interfacing project
Interfacing via the ioctl system call
Using ioctl in the user and kernel space
User space – using the ioctl system call
Kernel space – using the ioctl system call
ioctl as a debug interface
Comparing the interfacing methods – a table
Summary
Questions
Further reading
Working with Hardware I/O Memory
Technical requirements
Accessing hardware I/O memory from the kernel
Understanding the issue with direct access
The solution – mapping via I/O memory or I/O port
Asking the kernel's permission
Understanding and using memory-mapped I/O
Using the ioremap*() APIs
The newer breed – the devm_* managed APIs
Obtaining the device resources
All in one with the devm_ioremap_resource() API
Looking up the new mapping via /proc/iomem
MMIO – performing the actual I/O
Performing 1- to 8-byte reads and writes on MMIO memory regions
Performing repeating I/O on MMIO memory regions
Setting and copying on MMIO memory regions
Understanding and using port-mapped I/O
PMIO – performing the actual I/O
A PIO example – the i8042
Looking up the port(s) via /proc/ioports
Port I/O – a few remaining points to note
Summary
Questions
Further reading
Handling Hardware Interrupts
Technical requirements
Hardware interrupts and how the kernel handles them
Allocating the hardware IRQ
Allocating your interrupt handler with request_irq()
Freeing the IRQ line
Setting interrupt flags
Understanding level- and edge-triggered interrupts – a brief note
Code view 1 – the IXGB network driver
Implementing the interrupt handler routine
Interrupt context guidelines – what to do and what not to do
Don't block – spotting possibly blocking code paths
Interrupt masking – the defaults and controlling it
Keep it fast
Writing the interrupt handler routine itself
Code view 2 – the i8042 driver's interrupt handler
Code view 3 – the IXGB network driver's interrupt handler
IRQ allocation – the modern way – the managed interrupt facility
Working with the threaded interrupts model
Employing the threaded interrupt model – the API
Employing the managed threaded interrupt model – the recommended way
Code view 4 – the STM32 F7 microcontroller's threaded interrupt handler
Internally implementing the threaded interrupt
Why use threaded interrupts?
Threaded interrupts – to really make it real time
Constraints when using a threaded handler
Working with either hardirq or threaded handlers
Enabling and disabling IRQs
The NMI
Viewing all allocated interrupt (IRQ) lines
Understanding and using top and bottom halves
Specifying and using a tasklet
Initializing the tasklet
Running the tasklet
Understanding the kernel softirq mechanism
Available softirqs and what they are for
Understanding how the kernel runs softirqs
Running tasklets
Employing the ksoftirqd kernel threads
Softirqs and concurrency
Hardirqs, tasklets, and threaded handlers – what to use when
Fully figuring out the context
Viewing the context – examples
How Linux prioritizes activities
A few remaining FAQs answered
Load balancing interrupts and IRQ affinity
Does the kernel maintain separate IRQ stacks?
Measuring metrics and latency
Measuring interrupts with [e]BPF
Measuring time servicing individual hardirqs
Measuring time servicing individual softirqs
Using Ftrace to get a handle on system latencies
Finding the interrupts disabled worst-case time latency with Ftrace
Other tools
Summary
Questions
Further reading
Working with Kernel Timers, Threads, and Workqueues
Technical requirements
Delaying for a given time in the kernel
Understanding how to use the *delay() atomic APIs
Understanding how to use the *sleep() blocking APIs
Taking timestamps within kernel code
Let's try it – how long do delays and sleeps really take?
The sed
drivers – to demo kernel timers, kthreads, and workqueues
Setting up and using kernel timers
Using kernel timers
Our simple kernel timer module – code view 1
Our simple kernel timer module – code view 2
Our simple kernel timer module – running it
sed1 – implementing timeouts with our demo sed1 driver
Deliberately missing the bus
Creating and working with kernel threads
A simple demo – creating a kernel thread
Running the kthread_simple kernel thread demo
The sed2 driver – design and implementation
sed2 – the design
sed2 driver – code implementation
sed2 – trying it out
Querying and setting the scheduling policy/priority of a kernel thread
Using kernel workqueues
The bare minimum workqueue internals
Using the kernel-global workqueue
Initializing the kernel-global workqueue for your task – INIT_WORK()
Having your work task execute – schedule_work()
Variations of scheduling your work task
Cleaning up – canceling or flushing your work task
A quick summary of the workflow
Our simple work queue kernel module – code view
Our simple work queue kernel module – running it
The sed3 mini project – a very brief look
Summary
Questions
Further reading
Section 2: Delving Deeper
Kernel Synchronization - Part 1
Critical sections, exclusive execution, and atomicity
What is a critical section?
A classic case – the global i ++
Concepts – the lock
A summary of key points
Concurrency concerns within the Linux kernel
Multicore SMP systems and data races
Preemptible kernels, blocking I/O, and data races
Hardware interrupts and data races
Locking guidelines and deadlocks
Mutex or spinlock? Which to use when
Determining which lock to use – in theory
Determining which lock to use – in practice
Using the mutex lock
Initializing the mutex lock
Correctly using the mutex lock
Mutex lock and unlock APIs and their usage
Mutex lock – via [un]interruptible sleep?
Mutex locking – an example driver
The mutex lock – a few remaining points
Mutex lock API variants
The mutex trylock variant
The mutex interruptible and killable variants
The mutex io variant
The semaphore and the mutex
Priority inversion and the RT-mutex
Internal design
Using the spinlock
Spinlock – simple usage
Spinlock – an example driver
Test – sleep in an atomic context
Testing on a 5.4 debug kernel
Testing on a 5.4 non-debug distro kernel
Locking and interrupts
Using spinlocks – a quick summary
Summary
Questions
Further reading
Kernel Synchronization - Part 2
Using the atomic_t and refcount_t interfaces
The newer refcount_t versus older atomic_t interfaces
The simpler atomic_t and refcount_t interfaces
Examples of using refcount_t within the kernel code base
64-bit atomic integer operators
Using the RMW atomic operators
RMW atomic operations – operating on device registers
Using the RMW bitwise operators
Using bitwise atomic operators – an example
Efficiently searching a bitmask
Using the reader-writer spinlock
Reader-writer spinlock interfaces
A word of caution
The reader-writer semaphore
Cache effects and false sharing
Lock-free programming with per-CPU variables
Per-CPU variables
Working with per-CPU
Allocating, initialization, and freeing per-CPU variables
Performing I/O (reads and writes) on per-CPU variables
Per-CPU – an example kernel module
Per-CPU usage within the kernel
Lock debugging within the kernel
Configuring a debug kernel for lock debugging
The lock validator lockdep – catching locking issues early
Examples – catching deadlock bugs with lockdep
Example 1 – catching a self deadlock bug with lockdep
Fixing it
Example 2 – catching an AB-BA deadlock with lockdep
lockdep – annotations and issues
lockdep annotations
lockdep issues
Lock statistics
Viewing lock stats
Memory barriers – an introduction
An example of using memory barriers in a device driver
Summary
Questions
Further reading
Other Books You May Enjoy
Leave a review - let other readers know what you think
Preface
This book has been written with a view to helping you learn the fundamentals of Linux character device driver development in a practical, hands-on fashion, along with the necessary theoretical background to give you a well-rounded view of this vast and interesting topic area. To do the topics justice, that book's scope is deliberately kept limited to (mostly) learning how to write misc class character device drivers on the Linux OS. This way, you will be able to deeply imbibe the fundamental and necessary driver author skills to then be able to tackle different kinds of Linux driver projects with relative ease.
The focus is on hands-on driver development via the powerful Loadable Kernel Module (LKM) framework; the majority of kernel driver development is done in this manner. The focus is kept on working hands-on with driver code, understanding at a sufficiently deep level the internals wherever required, and keeping security in mind.
A recommendation we can't make strongly enough: to really learn and understand the details well, it's really best that you first read and understand this book's companion, Linux Kernel Programming. It covers various key areas – building the kernel from source, writing kernel modules via the LKM framework, kernel internals including kernel architecture, the memory system, memory alloc/dealloc APIs, CPU scheduling, and more. The combination of the two books will give you a sure and deep edge.
This book wastes no time – the first chapter has you learning the details of the Linux driver framework and how to write a simple yet complete misc class character device driver. Next, you learn how to do something very necessary: efficiently interfacing your driver with user space processes using various technologies (some of which help as debug/diagnostic aids as well!). Understanding, and working with, hardware (peripheral chip) I/O memory is then covered. Detailed coverage of handling hardware interrupts follows. This includes learning and using several modern driver techniques – using threaded IRQs, leveraging resource-managed APIs for drivers, I/O resource allocation, and so on. It covers what top/bottom halves are, working with tasklets and softirqs, and measuring interrupt latencies. Kernel mechanisms you will typically work with – using kernel timers, setting up delays, creating and managing kernel threads and workqueues – are covered next.
The remaining two chapters of this book delve into a relatively complex yet critical-to-understand topic for the modern pro-level driver or kernel developer: understanding and working with kernel synchronization.
The book uses the latest, at the time of writing, 5.4 Long Term Support (LTS) Linux kernel. It's a kernel that will be maintained (both bug and security fixes) from November 2019 right through December 2025! This is a key point, ensuring that this book's content remains current and valid for years to come!
We very much believe in a hands-on empirical approach: over 20 kernel modules (besides a few user apps and shell scripts) on this book's GitHub repository make the learning come alive, making it fun, interesting, and useful.
We really hope you learn from and enjoy this book. Happy reading!
Who this book is for
This book is primarily 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 understanding and learning to perform common driver tasks – the modern Linux Device Model (LDM) framework, user-kernel interfaces, performing peripheral I/O, handling hardware interrupts, dealing with concurrency, and more – will benefit from this book. A basic understanding of Linux kernel internals (and common APIs), kernel module development, and C programming is required.
What this book covers
Chapter 1, Writing a Simple misc Character Device Driver, first goes through the very basics – what a driver is supposed to do, the device namespace, the sysfs, and basic tenets of the LDM. We then delve into the details of writing a simple character device driver; along the way, you will learn about the framework – in effect, the internal implementation of the if it's not a process, it's a file
philosophy/architecture! You'll learn how to implement a misc class character device driver with various methods; several code examples help harden the concepts. Basic copying of data between the user-kernel space and vice versa is covered. Also covered are key security concerns and how to address them (in this context); a bad
driver giving rise to a privilege escalation issue is actually demonstrated!
Chapter 2, User-Kernel Communication Pathways, covers how to communicate between the kernel and the user space, which is critical to you, as a kernel module/driver author. Here, you'll learn about various communication interfaces, or pathways. This is an important aspect of writing kernel/driver code. Several techniques are employed: communication via traditional procfs, the better way for drivers via sysfs, and several others, via debugfs, netlink sockets, and the ioctl(2) system call.
Chapter 3, Working with Hardware I/O Memory, covers a key aspect of driver writing – the issue with (and the solution to) accessing hardware memory (mapped memory I/O) from a peripheral device or chip. We cover using the common memory-mapped I/O (MMIO) technique as well as the (typically on x86) port I/O (PIO) techniques for hardware I/O memory access and manipulation. Several examples from existing kernel drivers are shown as well.
Chapter 4, Handling Hardware Interrupts, shows how to handle and work with hardware interrupts in great detail. We start with a brief on how the kernel works with hardware interrupts, then move on to how you're expected to allocate
an IRQ line (covering modern resource-managed APIs), and how to correctly implement the interrupt handler routine. The modern approach of using threaded handlers (and the why of it), the Non-Maskable Interrupt (NMI), and more, are then covered. The reasons for and using both top half
and bottom half
interrupt mechanisms (hardirq, tasklet, and softirqs) in code, as well as key information regarding the dos and don'ts of hardware interrupt handling are covered. Measuring interrupt latencies with the modern [e]BPF toolset, as well as with Ftrace, concludes this key chapter.
Chapter 5, Working with Kernel Timers, Threads, and Workqueues, covers how to use some useful (and often employed by drivers) kernel mechanisms – delays, timers, kernel threads, and workqueues. They come in handy in many real-world situations. How to perform both blocking and non-blocking delays (as the situation warrants), setting up and using kernel timers, creating and working with kernel threads, and understanding and using kernel workqueues are all covered here. Several example modules, including three versions of a simple encrypt decrypt (sed) example driver, serve to illustrate the concepts learned in code.
Chapter 6, Kernel Synchronization – Part 1, first covers the key concepts regarding critical sections, atomicity, what a lock conceptually achieves, and, very importantly, the why of all this. We then cover concurrency concerns when working within the Linux kernel; this moves us naturally on to important locking guidelines, what deadlock means, and key approaches to preventing deadlock. Two of the most popular kernel locking technologies – the mutex lock and the spinlock – are then discussed in depth, along with several (driver) code examples.
Chapter 7, Kernel Synchronization – Part 2, continues the journey on kernel synchronization. Here, you'll learn about key locking optimizations – using lightweight atomic and (the more recent) refcount operators to safely operate on integers, using RMW bit operators to safely perform bit ops, and using the reader-writer spinlock over the regular one. Inherent risks, such as cache false sharing,
are discussed as well. An overview of lock-free programming techniques (with an emphasis on per-CPU variables and their usage, along with examples) is then covered. A critical topic, lock debugging techniques, including the usage of the kernel's powerful lockdep lock validator, is then covered. The chapter is rounded off with a brief look at memory barriers (along with an existing kernel network driver's usage of memory barriers).
We again stress that this book is for kernel programmers who are new to writing device drivers; several Linux driver topics are beyond this book's scope and are not covered. This includes other types of device drivers (besides character), working with the device tree, and so on. Packt offers other valuable guides to help you gain traction on these topic areas. This book would be an excellent start.
To get the most out of this book
To get the most out of this book, we expect you to have knowledge and experience of the following:
Know your way around a Linux system, on the command line (the shell).
The C programming language.
Know how to write simple kernel modules via the Loadable Kernel Module (LKM) framework
Understand (at least the basics) of key Linux kernel internals concepts: kernel architecture, memory management (plus common dynamic memory alloc/de-alloc APIs), and CPU scheduling.
It's not mandatory, but experience with Linux kernel programming concepts and technologies will help greatly.
Ideally, we highly recommend reading this book's companion, Linux Kernel Programming, first.
The details on hardware and software requirements for this book, as well as their installation, are shown here:
Detailed installation steps (software-wise):
Install Linux as a VM on a host Windows system; follow one of these tutorials:
Install Linux Inside Windows Using VirtualBox, Abhishek Prakash (It's FOSS!, August 2019): https://itsfoss.com/install-linux-in-virtualbox/
Alternately, here's another tutorial to help you do the same: Install Ubuntu on Oracle VirtualBox: https://brb.nci.nih.gov/seqtools/installUbuntu.html
Install the required software packages on the Linux VM:
Log in to your Linux guest VM and first run the following commands within a Terminal window (on a shell):
sudo apt update
sudo apt install gcc make perl
Install the Oracle VirtualBox Guest Additions now. Reference:How to Install VirtualBox Guest Additions in Ubuntu: https://www.tecmint.com/install-virtualbox-guest-additions-in-ubuntu/
(This step only applies if you are running Ubuntu as a VM using Oracle VirtualBox as the hypervisor app.)
To install the packages, take the following steps:
Within the Ubuntu VM, first run the sudo apt update command
Now, run the sudo apt install git fakeroot build-essential tar ncurses-dev tar xz-utils libssl-dev bc stress python3-distutils libelf-dev linux-headers-$(uname -r) bison flex libncurses5-dev util-linux net-tools linux-tools-$(uname -r) exuberant-ctags cscope sysfsutils curl perf-tools-unstable gnuplot rt-tests indent tree pstree smem hwloc bpfcc-tools sparse flawfinder cppcheck tuna hexdump trace-cmd virt-what command in a single line.
Useful resources:
The Linux kernel official online documentation:https://www.kernel.org/doc/html/latest/.
The Linux Driver Verification (LDV) project, particularly the Online Linux Driver Verification Servicepage: http://linuxtesting.org/ldv/online?action=rules.
SEALS - Simple Embedded ARM Linux System: https://github.com/kaiwan/seals/.
Every chapter of this book has a very useful Further reading section as well, detailing more resources.
Detailed instructions, as well as additional useful projects, installing a cross-toolchain for ARM, and more, are described inChapter 1, Kernel Workspace Setup, of this book's companion guide, Linux Kernel Programming, Kaiwan N Billimoria, Packt Publishing.
We have tested all the code in this book (it has its own GitHub repository as well) on these platforms:
x86_64 Ubuntu 18.04 LTS guest OS (running on Oracle VirtualBox 6.1)
x86_64 Ubuntu 20.04.1 LTS guest OS (running on Oracle VirtualBox 6.1)
x86_64 Ubuntu 20.04.1 LTS native OS
ARM Raspberry Pi 3B+ (running both its distro kernel as well as our custom 5.4 kernel); lightly tested.
If you are using the digital version of this book, we advise you to type the code yourself or, better, access the code via the GitHub repository (link available in the next section). Doing so will help you avoid any potential errors related to the copying and pasting of code.
For this book, we'll log in as the user named llkd. I strongly recommend that you follow the empirical approach: not taking anyone's word on anything at all, but trying it out and experiencing it for yourself. Hence, this book gives you many hands-on experiments and kernel driver code examples that you can and must try out yourself; this will greatly aid you in making real progress and deeply learning and understanding various aspects of Linux driver/kernel development.
Download the example code files
You can download the example code files for this book from GitHub at https://github.com/PacktPublishing/Linux-Kernel-Programming-Part-2. In case there's an update to the code, it will be updated on the existing GitHub repository.
We also have other code bundles from our rich catalog of books and videos available at https://github.com/PacktPublishing/. Check them out!
Download the color images
We also provide a PDF file that has color images of the screenshots/diagrams used in this book. You can download it here: http://www.packtpub.com/sites/default/files/downloads/9781801079518_ColorImages.pdf.
Conventions used
There are a number of text conventions used throughout this book.
CodeInText: Indicates code words in text, database table names, folder names, filenames, file extensions, pathnames, dummy URLs, user input, and Twitter handles. Here is an example: The ioremap() API returns a KVA of the void * type (since it's an address location).
A block of code is set as follows:
static int __init miscdrv_init(void)
{
int ret;
struct device *dev;
When we wish to draw your attention to a particular part of a code block, the relevant lines or items are set in bold:
#define pr_fmt(fmt) %s:%s():
fmt, KBUILD_MODNAME, __func__
[...]
#include
#include
[...]
Any command-line input or output is written as follows:
pi@raspberrypi:~ $ sudo cat /proc/iomem
Bold: Indicates a new term, an important word, or words that you see onscreen. For example, words in menus or dialog boxes appear in the text like this. Here is an example: Select System info from the Administration panel.
Warnings or important notes appear like this.
Tips and tricks appear like this.
Get in touch
Feedback from our readers is always welcome.
General feedback: If you have questions about any aspect of this book, mention the book title in the subject of your message and email us at [email protected].
Errata: Although we have taken every care to ensure the accuracy of our content, mistakes do happen. If you have found a mistake in this book, we would be grateful if you would report this to us. Please visit www.packtpub.com/support/errata, selecting your book, clicking on the Errata Submission Form link, and entering the details.
Piracy: If you come across any illegal copies of our works in any form on the Internet, we would be grateful if you would provide us with the location address or website name. Please contact us at [email protected] with a link to the material.
If you are interested in becoming an author: If there is a topic that you have expertise in and you are interested in either writing or contributing to a book, please visit authors.packtpub.com.
Reviews
Please leave a review. Once you have read and used this book, why not leave a review on the site that you purchased it from? Potential readers can then see and use your unbiased opinion to make purchase decisions, we at Packt can understand what you think about our products, and our authors can see your feedback on their book. Thank you!
For more information about Packt, please visit packt.com.
Section 1: Character Device Driver Basics
Here, we'll cover what a device driver is, namespaces, Linux Device Model (LDM) basics, and the character device driver framework. We'll implement simple misc drivers (leveraging the kernel's misc framework). We'll set up communication between the user and kernel spaces (via various interfaces, such as debugfs, sysfs, netlink sockets, and ioctl). You will learn how to work with hardware I/O memory on a peripheral chip, as well as understanding and working with hardware interrupts. You'll also learn how to use kernel features such as kernel-level timers, create kernel threads, and use workqueues.
This section comprises the following chapters:
Chapter 1, Writing a Simple misc Character Device Driver
Chapter 2, User-Kernel Communication Pathways
Chapter 3, Working with Hardware I/O Memory
Chapter 4, Handling Hardware Interrupts
Chapter 5, Working with Kernel Timers, Threads, and Workqueues
Writing a Simple misc Character Device Driver
No doubt, device drivers are a vast and interesting topic. Not only that, they are perhaps the most common use of the Loadable Kernel Module (LKM) framework that we have been using. Here, we shall introduce you to writing a few simple yet complete Linux character device drivers, within a class called misc; yes, that's short for miscellaneous. We wish to emphasize that this chapter is limited in its scope and coverage - here, we do not attempt to delve into the deep details regarding the Linux driver model and its many frameworks; instead, we refer you to several excellent books and tutorials on this topic via the Further reading section for this chapter. Our aim here is to quickly get you familiar with the overall concepts behind writing a simple character device driver.
Having said that, this book indeed has several chapters that are dedicated to what a driver author needs to know. Besides this introductory chapter, we cover (in detail) how a driver author works with hardware I/O memory, hardware interrupt handling (and its many sub-topics), and kernel mechanisms such as delays, timers, kernel threads, and work queues. Use of various user-kernel communication pathways or interfaces is covered in detail as well. The final two chapters of this book then focus on something very important for any kernel development, including drivers – kernel synchronization.
The other reasons we'd prefer to write a simple Linux character device driver and not just our usual
kernel module are as follows:
Until now, our kernel modules have been quite simplistic, having only init and cleanup functions, nothing more. A device driver providesseveralentry points into the kernel; these are the file-related system calls, known as the driver's methods. So, we can have anopen()method, a read()method, awrite()method, anllseek() method, an[unlocked|compat]_ioctl()method, arelease()method, and so on.
FYI, all possible methods
(functions) the driver author can hook into are in this key kernel data structure: include/linux/fs.h:file_operations (more on this in the Understanding the connection between the process, the driver, and the kernel section).
This situation is simply more realistic, and more interesting.
In this chapter, we will cover the following topics:
Getting started with writing a simple misc character device driver
Copying data from kernel to user space and vice versa
A misc driver with a secret
Issues and security concerns
Technical requirements
I assume that you have gone through the Preface section To get the most out of this book, and have appropriately prepared a guest VM running Ubuntu 18.04 LTS (or a later stable release) and installed all the required packages. If not, I highly recommend you do this first. To get the most out of this book, I strongly recommend you first set up the workspace environment, including cloning this book's GitHub repository for the code, and work on it in a hands-on fashion. The repository can be found here: https://github.com/PacktPublishing/Linux-Kernel-Programming-Part-2.
Getting started with writing a simple misc character device driver
In this section, you will first learn the required background material – understanding the basics of the device file (or node) and its hierarchy. After that, you will learn – by actually writing the code of a very simple misc character driver – the kernel framework behind the raw character device driver. Along the way, we shall cover how to create the device node(s) and test the driver via a user space app. Let's get started!
Understanding the device basics
Some quick background is in order.
A device driver is the interface between the OS and a peripheral hardware device. It can be written inline – that is, compiled within the kernel image file – or, more commonly, written outside of the kernel source tree as a kernel module (we covered the LKM framework in detail in the companion guide Linux Kernel Programming, Chapter 4, Writing Your First Kernel Module – LKMs Part 1, and Chapter 5, Writing Your First Kernel Module – LKMs Part 2). Either way, the driver code certainly runs at OS privilege, in kernel space (user space device drivers do exist, but can suffer performance issues; while useful in many circumstances, we don't cover them here. Take a look at the Further reading section).
In order for a user space application to gain access to the underlying device driver within the kernel, some I/O mechanism is required. The Unix (and thus Linux) design is to have the process open a special type of file – a device file, or device node. These files typically live in the /dev directory, and on modern systems are dynamic and auto-populated. The device node serves as an entry point into the device driver.
In order for the kernel to distinguish between device files, it uses two attributes within their inode data structure:
The type of file – either character (char) or block
The major and minor number
You will see that the namespace – the device type and the {major#, minor#} pair – form a hierarchy. Devices (and thus their drivers) are organized within a tree-like hierarchy within the kernel (the driver core code within the kernel takes care of this). The hierarchy is first divided based on device type – block or char. Within that, we have some n major numbers for each type, and each major number is further classified via some m minor numbers; Figure 1.1 shows this hierarchy.
Now, the key difference between block and character devices is that block devices have the (kernel-level) capability to be mounted and thus become part of the user-accessible filesystem. Character devices cannot be mounted; thus, storage devices tend to be block-based. Think of it this way (a bit simplistic but useful): if the (hardware) device is not storage, nor a network device, then it's a character device. A huge number of devices