( I hAVE READ THE TOP HALF NICELY but the BOTTOM FELT SO FUCKING WEIRD LOL)
Given that processors can be orders of magnitudes faster than the hardware they talk to, it is not ideal for the kernel to issue a request and wait for a response from the significantly slower hardware. Instead, because the hardware is comparatively slow to respond, the kernel must be free to go and handle other work, dealing with the hardware only after that hardware has actually completed its work.
Interrupts
- Interrupts are asynchronous electrical signals generated by hardware devices to capture the processor’s attention without requiring continuous polling.
- Hardware devices route these signals into input pins on an interrupt controller, which multiplexes multiple lines into a single line directly connected to the processor.
- The processor detects this signal and interrupts its current execution to handle the interrupt. The processor can then notify the operating system that an interrupt has occurred, and the operating system can handle the interrupt appropriately.
- Each signal is identified by a unique numeric value known as an Interrupt Request (IRQ) line, allowing the kernel to distinguish between devices (e.g., keyboard versus hard drive).
- Not all interrupt numbers, however, are so rigidly defined. Interrupts associated with devices on the PCI bus, for example, generally are dynamically assigned.
- Exceptions are synchronous interrupts generated by the processor itself during instruction execution.
- Exceptions trigger in response to programming errors (e.g., divide by zero), abnormal conditions (e.g., page faults), or software interrupts (e.g., system calls trapping into the kernel).
To process these asynchronous hardware signals without stalling system execution, the kernel relies on specialized operational functions.
Interrupt Handlers (Top Halves)
- An Interrupt Service Routine (ISR), or interrupt handler, is a standard C function that the kernel executes in response to a specific interrupt.
- The interrupt handler for a device is part of the device’s driver—the kernel code that manages the device.
- ISRs operate in a special operational mode called interrupt context (or atomic context).
- Code executing in interrupt context cannot block or sleep, strictly limiting the functions available for use.
- An ISR must execute as quickly as possible to acknowledge the hardware and resume the interrupted code.
- AND also: On top of responding to the hardware, the interrupt handler needs to copy networking packets from the hardware into memory, process them, and push the packets down to the appropriate protocol stack or application.
- During handler execution, the corresponding interrupt line is masked on all processors to prevent concurrent nested interrupts on the same line.
- As the interrupt line is masked, interrupt handlers in Linux do not need to be reentrant.
Because ISRs must execute quickly but tasks like network or disk I/O require extensive data processing, the kernel splits interrupt handling into two distinct phases.
Top Halves Versus Bottom Halves
These two goals—that an interrupt handler execute quickly and perform a large amount of work—clearly conflict with one another.
- Top Half (Interrupt Handler): Executes immediately upon receipt of the interrupt.
- Performs time-critical, hardware-specific work, such as acknowledging the interrupt receipt to the hardware and copying data from device buffers into main memory.
- Runs with at least the current interrupt line disabled.
- Bottom Half: Executes deferred work at a more convenient time.
- Performs data processing and protocol stack pushes.
- Runs with all interrupts enabled, ensuring system latency remains low.
To deploy a top half that responds to hardware, drivers must formally register their ISRs with the kernel’s interrupt subsystem.
Registering and Freeing Handlers
Interrupt handlers are the responsibility of the driver managing the hardware. Each device has one associated driver and, if that device uses interrupts (and most do), then that driver must register one interrupt handler.
Drivers allocate an interrupt line and register their ISR using request_irq() which is declared in <linux/interrupt.h>
irq: The numeric IRQ line to allocate. Typically hardcoded for example legacy PC devices such as the system timer or keyboard.handler: A function pointer to the ISR.flags: A bitmask dictating handler behavior, utilizing macros such asIRQF_DISABLED: when set, this flag instructs the kernel to disable all interrupts when executing the interrupt handler. When unset, interrupt handler run with al linterrupts except their own enabled. Most interrupts handlers do not set this flag, as diabling all interrupts is bad form.IRQF_SAMPLE_RANDOM: This flag specifes that interrupts are generated by this device should contribute to the kernel entropy pool. The kernel entropy pool provides truly random numbers derived from various random events. If this flag is specified the timing of interrupts from this device are fed to the pool as entropy. Do not set this if your device issues interrupts at a predictable rate.IRQF_TIMERThis flag specifies that this handler processes interrupts for the sys- tem timer.IRQF_SHARED. This flag specifies that the interrupt line can be shared among mul- tiple interrupt handlers. Each handler registered on a given line must specify this flag; otherwise, only one handler can exist per line.
name: An ASCII string identifying the device, utilized by/proc/irqand/proc/interrupts.dev: A unique cookie passed back to the handler, mandatory for shared IRQ lines to distinguish between devices. And unless your device is old and crusty and lives on the ISA bus, there is a good chance it must support sharing.
Note that request_irq() can sleep and therefore cannot be called from interrupt context or other situations where code cannot block. It is a common mistake to call request_irq() when it is unsafe to sleep.
This is partly because of why request_irq() can block: It is indeed unclear. On registration,
- an entry corresponding to the interrupt is created in
/proc/irq. - The function
proc_mkdir()creates new procfs entries. - This function calls proc_create() to set up the new procfs entries, which in turn calls kmalloc() to allocate memory.
- The
request_irq()function can sleep because it callskmalloc()to create/procentries, meaning it must be executed strictly from process context.
When your driver unloads, you need to unregister your interrupt handler and potentially disable the interrupt line.
- To unregister a handler, drivers invoke
free_irq(unsigned int irq, void *dev). - On shared lines,
free_irq()uses the uniquedevcookie to remove the specific handler and disables the IRQ line only when the final handler is removed.
Now you can see why a unique dev is important.With shared interrupt lines, a unique cookie is required to dif- ferentiate between the multiple handlers that can exist on a single line and enable free_irq() to remove only the correct handler.
Once registered, the ISR must adhere to specific architectural signatures and return types to communicate hardware status back to the kernel.
Handler Implementation and Shared Lines
- Function Prototype: Handlers must match the signature
static irqreturn_t intr_handler(int irq, void *dev). Note that this declaration matches the prototype of the handler argument given to request_irq()
The second parameter, dev, is a generic pointer to the same dev that was given to request_irq() when the interrupt handler was registered. If this value is unique (which is required to support sharing), it can act as a cookie to differentiate between multiple devices potentially using the same interrupt handler.
-
Return Values: The
irqreturn_ttype communicates interrupt ownership to the kernel:IRQ_NONE: Returned when the handler detects that its associated hardware did not originate the interrupt.IRQ_HANDLED: Returned when the handler successfully processes an interrupt originated by its device.- The macro
IRQ_RETVAL(val)returnsIRQ_HANDLEDifvalis nonzero, orIRQ_NONEotherwise. - The interrupt handler is normally marked static because it is never called directly from another file. Interrupt handlers in Linux need not be reentrant. When a given interrupt handler is execut- ing, the corresponding interrupt line is masked out on all processors, preventing another interrupt on the same line from being received.
-
Shared Handlers: A shared handler is registered and executed much like a nonshared handler. Following are the three main differences:
- Multiple devices can share a single IRQ line if they adhere to strict rules:
- All registering drivers must specify the
IRQF_SHAREDflag. - The
devcookie must be unique (often a pointer to the device structure) and cannot beNULL. - The handler must check hardware status registers to quickly confirm if its specific device generated the interrupt, exiting with
IRQ_NONEif it did not. - The kernel sequentially invokes each registered handler on a shared line until one acknowledges the interrupt.
When the kernel receives an interrupt, it invokes sequentially each registered handler on the line.Therefore, it is important that the handler be capable of distinguishing whether it generated a given interrupt.The handler must quickly exit if its associated device did not generate the interrupt.This requires the hardware device to have a status register (or similar mechanism) that the handler can check. Most hardware does indeed have such a feature
As these handlers process hardware state, they run in a strict operational environment distinct from standard user processes.
Interrupt Context
When executing an interrupt handler, the kernel is in interrupt context. Recall that process context is the mode of operation the kernel is in while it is executing on behalf of a process for example, executing a system call or running a kernel thread (on behalf of ktreahd ho yo pani haha). In process context, the current macro points to the associated task. Furthermore, because a process is coupled to the kernel in process context, process context can sleep or otherwise invoke the scheduler.
(you dont have current when you are editing the code of interrupt - different ballgame altogether)
Interrupt context, on the other hand, is not associated with a process. The current macro is not relevant (although it points to the interrupted process). Without a backing process, interrupt context cannot sleep—how would it ever reschedule? Therefore, you cannot call certain functions from interrupt context. If a function sleeps, you cannot use it from your interrupt handler—this limits the functions that one can call from an interrupt handler.
Interrupt context is time-critical because the interrupt handler interrupts other code. Code should be quick and simple. Busy looping is possible, but discouraged.This is an important point; always keep in mind that your interrupt handler has interrupted other code (possibly even another interrupt handler on a different line!).
Because of this asynchronous nature, it is imperative that all interrupt handlers be as quick and as simple as possible.As much as possible, work should be pushed out from the interrupt handler and performed in a bottom half, which runs at a more convenient time. (what is the convenient time - we dont fucking know? YEY)
The setup of an interrupt handler’s stacks is a configuration option. Historically, interrupt handlers did not receive their own stacks. Instead, they would share the stack of the process that they interrupted. The kernel stack is two pages in size; typically, that is 8KB on 32-bit architectures and 16KB on 64-bit architectures. Because in this setup interrupt handlers share the stack, they must be exceptionally frugal with what data they allocate there. Of course, the kernel stack is limited to begin with, so all kernel code should be cautious
To cope with the reduced stack size, interrupt handlers were given their own stack, one stack per processor, one page in size.This stack is referred to as the interrupt stack.Although the total size of the interrupt stack is half that of the original shared stack, the average stack space available is greater because interrupt handlers get the full page of memory to themselves.
- Interrupt Context: Unlike process context, interrupt context is not associated with a backing process.
- The
currentmacro points to the interrupted process but holds no relevance for the handler. - Because there is no backing process to reschedule, code in interrupt context absolutely cannot sleep or block.
- The
- Kernel Stacks:
- Historically, interrupt handlers shared the stack of the interrupted process, which is tightly limited (e.g., 8KB on 32-bit architectures).
- To reduce memory pressure, modern configurations employ 1-page kernel stacks and introduce dedicated interrupt stacks.
- These interrupt stacks provide one 1-page stack per processor exclusively for interrupt handlers.
The transition into this restrictive context follows a precise hardware-to-software execution path initiated by the interrupt controller.
The Interrupt Execution Path
Perhaps not surprising, the implementation of the interrupt handling system in Linux is architecture-dependent. .The implementation depends on the processor, the type of inter- rupt controller used, and the design of the architecture and machine.

- When a device generates an interrupt, it sends a signal to the interrupt controller, which triggers a designated pin on the processor.
- The processor interrupts execution, disables the interrupt system, and jumps to an architecture-specific assembly entry point.
- The initial assembly routine saves the IRQ value and the interrupted task’s registers into a
pt_regsstructure on the stack. - The kernel then invokes the C function
do_IRQ(), which extracts the IRQ number, acknowledges the interrupt to the controller, and disables delivery on that line. do_IRQ()ensures a valid handler exists and callshandle_IRQ_event()to process the action chain.- Unless
IRQF_DISABLEDwas set during registration,handle_IRQ_event()re-enables local processor interrupts before looping through and executing all handlers registered on the line. - If
IRQF_SAMPLE_RANDOMwas specified, the kernel invokesadd_interrupt_randomness()to feed the timing into the entropy pool. - Execution returns to the assembly routine
ret_from_intr(), which checks theneed_reschedflag andpreempt_countto determine if it is safe to invokeschedule()before restoring registers and resuming the interrupted code.
On x86, the initial assembly routines are located in arch/x86/kernel/entry_64.S (entry_32.S for 32-bit x86) and the C methods are located in arch/x86/kernel/irq.c. Other supported architectures are similar.
/proc/interrupts
Procfs is a virtual filesystem that exists only in kernel memory and is typically mounted at /proc. Reading or writing files in procfs invokes kernel functions that simulate reading or writing from a real file.
A relevant example is the /proc/interrupts file, which is popu- lated with statistics related to interrupts on the system. For the curious, procfs code is located primarily in fs/proc.The function that provides /proc/interrupts is, not surprisingly, architecture-dependent and named show_interrupts()
To protect data structures modified during this execution path from concurrent access, the kernel provides strict mechanisms to control interrupt delivery.
Interrupt Control and Synchronization
- Local Interrupt Control: Disabling local interrupts prevents preemptive concurrent access from an ISR on the current processor.
local_irq_disable()andlocal_irq_enable()unconditionally clear and set the allow-interrupts flag on the issuing processor (e.g., viacliandstiassembly instructions on x86).local_irq_save(flags)saves the current interrupt state into an opaqueunsigned long, then disables interrupts.local_irq_restore(flags)restores the exact interrupt state previously saved, preventing the erroneous enablement of interrupts that were already disabled prior to the critical section.- The
flagsvariable must be passed by value within the same stack frame due to architecture-specific stack behaviors.
- Global Interrupt Control: The legacy global
cli()lock was deprecated to mandate fine-grained locking (e.g., spin locks) alongside local interrupt control, ensuring better SMP scalability. - Line-Specific Control: The kernel can mask specific IRQ lines across the entire system.
disable_irq(irq)disables the line and blocks until any currently executing handlers on that line complete.disable_irq_nosync(irq)disables the line immediately without waiting for existing handlers to exit.enable_irq(irq)enables the line; calls nest, meaning two disables require two enables to re-activate the line.synchronize_irq(irq)blocks until a specific interrupt handler finishes executing.
- Context Status Macros:
irqs_disabled()returns nonzero if local interrupt delivery is currently disabled.in_interrupt()returns nonzero if the kernel is in interrupt context (executing an ISR or a bottom half).in_irq()returns nonzero strictly if the kernel is executing an ISR.
Mastering these synchronization primitives ensures that the delicate interplay between asynchronous hardware interrupts and kernel processes remains free of race conditions.
Bottom Halves and Deferring Work
Interrupt processing relies on a strict division of labor to manage the inherent hardware constraints of operating systems. The top half, implemented as the interrupt handler, executes asynchronously, immediately acknowledges the hardware, and performs time-critical tasks,,. Because top halves run with local interrupt lines disabled and execute entirely in interrupt context, they block communication with other hardware and cannot sleep,. To minimize system latency and return processor control to interrupted code, all non-time-critical processing must be deferred,. The bottom half executes this deferred work at a later, more convenient time when all hardware interrupts are re-enabled,.
To manage this deferred execution efficiently across various system architectures, the kernel provides multiple bottom-half mechanisms governed by distinct performance and context constraints. he point of a bottom half is not to do work at some specific point in the future, but sim- ply to defer work until any point in the future when the system is less busy and interrupts are again enabled. Often, bottom halves run immediately after the interrupt returns.The key is that they run with all interrupts enabled
Evolution of Bottom-Half Mechanisms
ver the course of Linux’s history, there have been many bottom-half mechanisms. Confusingly, some of these mechanisms have similar or even dumb names. The infrastructure for deferring work has evolved significantly to handle symmetrical multiprocessing (SMP) and scalability requirements:
- Original BH Interface: The initial bottom-half implementation relied on a statically defined list of routines. Handlers were globally synchronized, meaning no two BH handlers could execute concurrently anywhere in the system, creating a severe performance bottleneck on SMP machines,.
- Task Queues: Designed as an aggregate of linked lists of functions, task queues ran at specific points in the kernel,. The interface lacked flexibility and failed to provide the lightweight overhead required by high-performance subsystems like networking,.The kernel defined a family of queues. Each queue contained a linked list of functions to call.The queued functions were run at certain times, depending on which queue they were in. Drivers could register their bot- tom halves in the appropriate queue.
- Modern Implementations: Kernel 2.5 deprecated both BH and task queues, replacing them entirely with softirqs, tasklets, and work queues,.
The foundation of the modern deferred execution model relies on the highly scalable softirq subsystem.
Softirqs
Softirqs are a set of statically defined bottom halves that can run simultaneously on any processor; even two of the same type can run concur- rently.Tasklets, which have an awful and confusing name,2 are flexible, dynamically cre- ated bottom halves built on top of softirqs.Two different tasklets can run concurrently on different processors, but two of the same type of tasklet cannot run simultaneously.Thus, tasklets are a good trade-off between performance and ease of use. For most bottom-half processing, the tasklet is sufficient. Softirqs are useful when performance is critical, such as with networking. Using softirqs requires more care, however, because two of the same softirq can run at the same time. In addition, softirqs must be registered statically at com- pile time. Conversely, code can dynamically register tasklets.
Additionally, the task queue interface was replaced by the work queue interface. work queues are a simple yet useful metho of queueing work to later be performed in process context.
Another mechanism for deferring work is kernel timers. Unlike the mechanisms discussed in the chapter thus far, timers defer work for a specified amount of time. That is, although the tools discussed in this chapter are useful to defer work to any time but now, you use timers to defer work until at least a specific time has elapsed
Softirqs are statically allocated, high-performance bottom halves designed for heavily threaded subsystems,. Softirqs are represented by the softirq_action structure, which is defined in <linux/interrupt.h>.
A 32-entry array of this structure is declared in kernel/softirq.c:
static struct softirq_action softirq_vec[NR_SOFTIRQS];
The Softirq Handler The prototype of a softirq handler, action, looks like void softirq_handler(struct softirq_action *) When the kernel runs a softirq handler, it executes this action function with a pointer to the corresponding softirq_action structure as its lone argument. For example, if my_softirq pointed to an entry in the softirq_vec array, the kernel would invoke the softirq handler function as my_softirq→action(my_softirq); It seems a bit odd that the kernel passes the entire structure to the softirq handler.This trick enables future additions to the structure without requiring a change in every softirq handler. A softirq never preempts another softirq.The only event that can preempt a softirq is an interrupt handler.Another softirq—even the same one—can run on another processor, however.
- Structure and Allocation: Softirqs are represented by
struct softirq_action, which contains a singleactionfunction pointer. They are statically allocated at compile time via an enumeration index, with lower numerical indices (e.g.,HI_SOFTIRQ,NET_TX_SOFTIRQ) dictating higher execution priority,. The system enforces a hard limit of registered softirqs. - Execution Rules: Softirqs execute in interrupt context with hardware interrupts enabled,. They cannot sleep or block. Softirqs never preempt other softirqs; they are only preempted by top-half interrupt handlers.
- Concurrency: Multiple instances of the exact same softirq can execute concurrently on different processors,. This necessitates aggressive, highly tuned locking protocols or the strict use of per-processor data,.
- Triggering: Handlers are registered using
open_softirq(). A softirq is marked for execution (raised) viaraise_softirq(), which saves the interrupt state, disables local interrupts, flags the softirq, and restores the interrupt state,. - Processing: Softirq execution is processed by
__do_softirq(), which retrieves a 32-bit bitmask of pending softirqs vialocal_softirq_pending(),. It clears the active mask and loops through the bits, invoking theactionhandler for every set bit until the mask evaluates to ,,.
While softirqs offer maximum scalability through concurrent execution, managing their strict locking requirements led to the development of a simpler derivative mechanism known as tasklets.
Tasklets
Tasklets are dynamically created bottom halves built directly on top of the softirq infrastructure,. They trade extreme scalability for a simpler concurrency model,.
- Base Mechanism: Tasklets are multiplexed onto two specific softirqs:
HI_SOFTIRQ(high priority) andTASKLET_SOFTIRQ(normal priority),. - Data Structure: Represented by
struct tasklet_struct, which tracks thenextpointer in the list, the tasklet’sstate, an atomiccountfor references, thefunchandler, and itsdataargument. - State Management: The
statefield utilizesTASKLET_STATE_SCHEDto indicate the tasklet is pending, andTASKLET_STATE_RUNto indicate it is actively executing. Thecountfield operates as a reference counter; a tasklet is only eligible to execute ifcountequals ,. - Concurrency Rules: Two identical tasklets strictly cannot execute concurrently on different processors,. However, two entirely different tasklets can run simultaneously on distinct processors,.
- Scheduling (
tasklet_schedule()):- Verifies
TASKLET_STATE_SCHEDis not already set. - Disables local interrupts to protect list manipulation.
- Appends the tasklet to the head of the per-processor
tasklet_vecortasklet_hi_veclinked lists. - Raises the underlying softirq and restores interrupts.
- Verifies
- Execution (
tasklet_action()):- Clears the local processor’s tasklet list.
- Iterates over the pending tasklets, skipping any where
TASKLET_STATE_RUNis already set globally. - Sets
TASKLET_STATE_RUN, executes the handler, and clears the run state upon completion.
- Interfaces: Tasklets are declared statically via
DECLARE_TASKLET()or dynamically viatasklet_init(),. They can be disabled synchronously or asynchronously viatasklet_disable()andtasklet_disable_nosync(), and removed from queues usingtasklet_kill(),.
Because softirqs and tasklets can continuously reactivate themselves during heavy workloads, the kernel employs dedicated threads to prevent these mechanisms from monopolizing processor time.
ksoftirqd
The ksoftirqd subsystem consists of per-processor kernel threads designed to handle high-volume softirq processing.
- Starvation Prevention: During intense periods (e.g., heavy network traffic), softirqs can continuously reactivate themselves. Processing them indefinitely starves user-space applications, while deferring them strictly to the next hardware interrupt severely degrades throughput,.
- Execution Model: The kernel defers reactivated softirqs to the
ksoftirqd/nthreads (where is the processor ID), which run at the lowest possible scheduler priority (nice value ),. This ensures user-space tasks preempt the softirq processing, while guaranteeing that softirqs still execute promptly on idle processors. - Thread Loop: The thread awakens when
do_softirq()detects self-reactivating softirqs. It runs in a tight loop checkingsoftirq_pending(), invokingdo_softirq(), and yielding the processor viaschedule()wheneverneed_resched()is flagged,.
Softirqs, tasklets, and their daemon threads operate strictly in interrupt context; workloads requiring sleep or blocking I/O must shift to process-context mechanisms.
Work Queues
Work queues defer execution to dedicated kernel threads (worker threads), enabling the bottom half to run entirely in process context.
- Context Capabilities: Because they operate in process context, work queues can block, sleep, allocate heavy memory, and perform synchronous disk I/O,.
- Thread Topologies: By default, the subsystem utilizes the generic
events/nworker threads (one per processor). Drivers with intensive, specialized processing demands can instantiate dedicated custom worker threads viacreate_workqueue(),. - Data Structures:
struct workqueue_struct: Represents all worker threads of a specific type across the global system.struct cpu_workqueue_struct: Represents a single worker thread bound to a specific physical processor. Contains theworklistand the wait queue.struct work_struct: Represents the specific deferred task. It contains thefunchandler pointer and thedatapayload.
- Worker Thread Loop (
worker_thread()):- The thread adds itself to a wait queue and enters a
TASK_INTERRUPTIBLEstate. - If the
worklistis empty, it invokesschedule()and sleeps. - If work is queued, it updates to
TASK_RUNNING, iterates over the linked list ofwork_structobjects, clears their pending bits, and executes each associatedfunc,.
- The thread adds itself to a wait queue and enters a
- Interfaces: Tasks are defined via
DECLARE_WORK()orINIT_WORK(). They are dispatched usingschedule_work()orschedule_delayed_work(),. Subsystems can force synchronous completion of all queued tasks usingflush_scheduled_work().
Choosing the correct mechanism and synchronization schema depends entirely on the context constraints and scalability requirements of the deferred work.
Mechanism Selection and Synchronization
Designing a bottom-half architecture requires mapping subsystem constraints to the appropriate deferral interface and implementing robust concurrency locks.
- Selection Guidelines:
- Work Queues: Mandatory if the deferred work must sleep, block, or allocate significant memory,.
- Tasklets: The default choice for most hardware device drivers. They eliminate the need for complex intra-handler locking by guaranteeing identical tasklets never execute concurrently,.
- Softirqs: Reserved strictly for highly scalable, timing-critical subsystems (like networking) where instances of the same handler must run concurrently across multiple processors, necessitating careful per-processor data management,.
- Concurrency and Locking:
- Intra-Tasklet Safety: Tasklets are inherently serialized against themselves, eliminating intra-tasklet race conditions. However, accessing data shared between two different tasklets requires standard spin locks.
- Softirq Locking: Because softirqs execute concurrently without serialization, any shared global data requires rigorous locking,.
- Process Context Sharing: When process context code shares data with a bottom half, the process code must obtain a lock and explicitly disable bottom-half processing to prevent preemption and deadlocks.
- Interrupt Context Sharing: When an interrupt handler shares data with a bottom half, the handler must obtain a lock and disable local interrupts entirely.
- Disabling Bottom Halves:
local_bh_disable(): Disables all softirq and tasklet processing on the local processor by incrementing the task’spreempt_count,.local_bh_enable(): Decrements thepreempt_count. If the count evaluates to zero, it actively checks for and executes any bottom halves that became pending during the disabled period,. Work queues are unaffected by these calls as they operate asynchronously via the standard process scheduler.