What Is a Reentrant Kernel

what is a reentrant kernel

Much simpler answer:

Kernel Re-Entrance

If the kernel is not re-entrant, a process can only be suspended while it is in user mode. Although it could be suspended in kernel mode, that would still block kernel mode execution on all other processes. The reason for this is that all kernel threads share the same memory. If execution would jump between them arbitrarily, corruption might occur.

A re-entrant kernel enables processes (or, to be more precise, their corresponding kernel threads) to give away the CPU while in kernel mode. They do not hinder other processes from also entering kernel mode. A typical use case is IO wait. The process wants to read a file. It calls a kernel function for this. Inside the kernel function, the disk controller is asked for the data. Getting the data will take some time and the function is blocked during that time.
With a re-entrant kernel, the scheduler will assign the CPU to another process (kernel thread) until an interrupt from the disk controller indicates that the data is available and our thread can be resumed. This process can still access IO (which needs kernel functions), like user input. The system stays responsive and CPU time waste due to IO wait is reduced.

This is pretty much standard for today's desktop operating systems.

Kernel pre-emption

Kernel pre-emption does not help in the overall throughput of the system. Instead, it seeks for better responsiveness.

The idea here is that normally kernel functions are only interrupted by hardware causes: Either external interrupts, or IO wait cases, where it voluntarily gives away control to the scheduler. A pre-emptive kernel instead also interrupts and suspends kernel functions just like it would interrupt processes in user mode. The system is more responsive, as processes e.g. handling mouse input, are woken up even while heavy work is done inside the kernel.

Pre-emption on kernel level makes things harder for the kernel developer: The kernel function cannot be suspended only voluntarily or by interrupt handlers (which are somewhat a controlled environment), but also by any other process due to the scheduler. Care has to be taken to e.g. avoid deadlocks: A thread locks resource A but needing resource B is interrupted by another thread which locks resource B, but then needs resource A.

Take my explanation of pre-emption with a grain of salt. I'm happy for any corrections.

How does the Linux kernel realize reentrancy?

[Edit - the term "reentrant" gets used in a couple of different senses. This answer uses the basic "multiple contexts can be executing the same code at the same time." This usually applies to a single routine, but can be extended to apply to a set of cooperating routines, generally routines which share data. An extreme case of this is when applied to a complete program - a web server, or an operating system. A web-server might be considered non-reentrant if it could only deal with one client at a time. (Ugh!) An operating system kernel might be called non-reentrant if only one process/thread/processor could be executing kernel code at a time.

Operating systems like that occurred during the transition to multi-processor systems. Many went through a slow transition from written-for-uniprocessors to one-single-lock-protects-everything (i.e. non-reentrant) through various stages of finer and finer grained locking. IIRC, linux finally got rid of the "big kernel lock" at approx. version 2.6.37 - but it was mostly gone long before that, just protecting remnants not yet converted to a multiprocessing implementation.

The rest of this answer is written in terms of individual routines, rather than complete programs.]

If you are in user space, you don't need to do anything. You call whatever system calls you want, and the right thing happens.

So I'm going to presume you are asking about code in the kernel.

Conceptually, it's fairly simple. It's also pretty much identical to what happens in a multi-threaded program in user space, when multiple threads call the same subroutine. (Let's assume it's a C program - other languages may have differently named mechanisms.)

When the system call implementation is using automatic (stack) variables, it has its own copy - no problem with re-entrancy. When it needs to use global data, it generally needs to use some kind of locking - the specific locking required depends on the specific data it's using, and what it's doing with that data.

This is all pretty generic, so perhaps an example might help.

Let's say the system call want to modify some attribute of a process. The process is represented by a struct task_struct which is a member of various linked lists. Those linked lists are protected by the tasklist_lock. Your system call gets the tasklist_lock, finds the right process, possibly gets a per-process lock controlling the field it cares about, modifies the field, and drops both locks.

One more detail, which is the case of processes executing different system calls, which don't share data with each other. With a reasonable implementation, there are no conflicts at all. One process can get itself into the kernel to handle its system call without affecting the other processes. I don't remember looking specifically at the linux implementation, but I imagine it's "reasonable". Something like a trap into an exception handler, which looks in a table to find the subroutine to handle the specific system call requested. The table is effectively const, so no locks required.

Reentrancy and Reentrant in C?

Just to add what Mr. @Joachim Pileborg already mentioned in his answer, as per the wiki entry for Reentrancy, the basic rules for a function being re-entrant are

  1. Reentrant code may not hold any static (or global) non-constant data.
  2. Reentrant code may not modify its own code.
  3. Reentrant code may not call non-reentrant computer programs or routines.

To elaborate, the function, if reentrant, will not have any issue with its own implementation (inducing the internal data structures it uses for itself) whether being called from different context.

A parameter, (such as a file descriptor) which is supplied to the function does not affect it's reentrancy.

So, for write(), the function itself is Reentrant, but if called with same file descriptor from different thread, it will obviously produce erroneous result. Again, that does not mean, the Reentrancy of write() is gone. It is Reentrant, but not thread-safe, and these two are different aspects.

Does an interrupt handler have to be reentrant?

The short answer is that Interrupt Service Routines are not inherently required to be reentrant. Reentrancy is only required in the case of nested interrupts. If the Operating System you use does not support nested interrupts, then you do not need to worry about reentrancy at all. If it does, you may have control over resetting the interrupt you are servicing so that you should never get a nested interrupt.

EDIT: Now that I know you're using Linux, you might find this link helpful: Can an interrupt handler be preempted by the same interrupt handler?

Essentially the answer to your question is that Linux masks an interrupt when it is asserted so that it won't preempt itself unless a specific flag is passed when registering the ISR.

Here's a relevant quote:

Interrupt handlers in Linux need not be reentrant. When a given
interrupt handler is executing, the corresponding interrupt line is
masked out on all processors, preventing another interrupt on the same
line from being received. Normally all other interrupts are enabled,
so other interrupts are serviced, but the current line is always
disabled. Consequently, the same interrupt handler is never invoked
concurrently to service a nested interrupt. This greatly simplifies
writing your interrupt handler.

How to convince my co-worker the linux kernel code is re-entrant?

Search the kernel mailing list archive for "BKL". That stands for "Big Kernel Lock", which is what used to be used to prevent problems. A lot of work has been put into breaking it up into pieces, to allow reentry as long different parts of the kernel are used by different processes. Most recent mentions of "BKL" (at least that I've noticed) have basically referred to somebody trying to make his own life easy by locking more than somebody else approved of, at which point they frequently say something about "returning to the days of the BKL", or something on that order.

Reentrant code confusion

Re-entrant safety means that the function can successfully handle an "out of context" call to itself while it is executing. In non-kernel code in modern operating systems, this usually means that another thread will be calling it at the same time so in this case it is equivalent to being thread-safe.

However, e.g. in MS-DOS or in kernel code, an interrupt could occur at any time while the function is executing, possibly invoking the function on its own; this could have strange side-effects unless the function is written specifically to avoid this.

See also: http://en.wikipedia.org/wiki/Reentrancy_%28computing%29

linux kernel character drivers

In the open method of your driver:
use a global flag to say whether the device is in use. Unset when closed (in the release method).
Pseudo-code:

am_open=False

open() method:
if (!am_open)
am_open=True
...proceed...
else
return -EAGAIN; // try again later
-or-
return -EBUSY;

release() method:
am_open=False

Having said that, drivers (usually) should be a 100% reentrant :)



Related Topics



Leave a reply



Submit