Skip to content

Commit 148fb01

Browse files
authored
Avoid unexpected concurrent access (#94)
In file {chardev,chardev2,sleep}.c, the variable to determine the exclusive access was of integer type, which led to race condition. This patch rewrote the above with atomic CAS respectively to eliminate the race. Close #93
1 parent 9289bfe commit 148fb01

File tree

4 files changed

+33
-20
lines changed

4 files changed

+33
-20
lines changed

examples/chardev.c

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ static ssize_t device_write(struct file *, const char __user *, size_t,
2727
/* Global variables are declared as static, so are global within the file. */
2828

2929
static int major; /* major number assigned to our device driver */
30-
static int open_device_cnt = 0; /* Is device open?
31-
* Used to prevent multiple access to device */
30+
/* Is device open? Used to prevent multiple access to device */
31+
static atomic_t already_open = ATOMIC_INIT(0);
3232
static char msg[BUF_LEN]; /* The msg the device will give when asked */
3333
static char *msg_ptr;
3434

@@ -78,10 +78,9 @@ static int device_open(struct inode *inode, struct file *file)
7878
{
7979
static int counter = 0;
8080

81-
if (open_device_cnt)
81+
if (atomic_cmpxchg(&already_open, 0, 1))
8282
return -EBUSY;
8383

84-
open_device_cnt++;
8584
sprintf(msg, "I already told you %d times Hello world!\n", counter++);
8685
msg_ptr = msg;
8786
try_module_get(THIS_MODULE);
@@ -92,7 +91,7 @@ static int device_open(struct inode *inode, struct file *file)
9291
/* Called when a process closes the device file. */
9392
static int device_release(struct inode *inode, struct file *file)
9493
{
95-
open_device_cnt--; /* We're now ready for our next caller */
94+
atomic_set(&already_open, 0); /* We're now ready for our next caller */
9695

9796
/* Decrement the usage count, or else once you opened the file, you will
9897
* never get get rid of the module.

examples/chardev2.c

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
/* Is the device open right now? Used to prevent concurrent access into
2121
* the same device
2222
*/
23-
static int open_device_cnt = 0;
23+
static atomic_t already_open = ATOMIC_INIT(0);
2424

2525
/* The message the device will give when asked */
2626
static char message[BUF_LEN];
@@ -38,10 +38,9 @@ static int device_open(struct inode *inode, struct file *file)
3838
pr_info("device_open(%p)\n", file);
3939

4040
/* We don't want to talk to two processes at the same time. */
41-
if (open_device_cnt)
41+
if (atomic_cmpxchg(&already_open, 0, 1))
4242
return -EBUSY;
4343

44-
open_device_cnt++;
4544
/* Initialize the message */
4645
message_ptr = message;
4746
try_module_get(THIS_MODULE);
@@ -53,7 +52,7 @@ static int device_release(struct inode *inode, struct file *file)
5352
pr_info("device_release(%p,%p)\n", inode, file);
5453

5554
/* We're now ready for our next caller */
56-
open_device_cnt--;
55+
atomic_set(&already_open, 0);
5756

5857
module_put(THIS_MODULE);
5958
return SUCCESS;

examples/sleep.c

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ static ssize_t module_input(struct file *file, /* The file itself */
7777
}
7878

7979
/* 1 if the file is currently open by somebody */
80-
static int already_open = 0;
80+
static atomic_t already_open = ATOMIC_INIT(0);
8181

8282
/* Queue of processes who want our file */
8383
static DECLARE_WAIT_QUEUE_HEAD(waitq);
@@ -90,7 +90,7 @@ static int module_open(struct inode *inode, struct file *file)
9090
* we should fail with -EAGAIN, meaning "you will have to try again",
9191
* instead of blocking a process which would rather stay awake.
9292
*/
93-
if ((file->f_flags & O_NONBLOCK) && already_open)
93+
if ((file->f_flags & O_NONBLOCK) && atomic_read(&already_open))
9494
return -EAGAIN;
9595

9696
/* This is the correct place for try_module_get(THIS_MODULE) because if
@@ -99,8 +99,7 @@ static int module_open(struct inode *inode, struct file *file)
9999
*/
100100
try_module_get(THIS_MODULE);
101101

102-
/* If the file is already open, wait until it is not. */
103-
while (already_open) {
102+
while (atomic_cmpxchg(&already_open, 0, 1)) {
104103
int i, is_sig = 0;
105104

106105
/* This function puts the current process, including any system
@@ -110,7 +109,7 @@ static int module_open(struct inode *inode, struct file *file)
110109
* is closed) or when a signal, such as Ctrl-C, is sent
111110
* to the process
112111
*/
113-
wait_event_interruptible(waitq, !already_open);
112+
wait_event_interruptible(waitq, !atomic_read(&already_open));
114113

115114
/* If we woke up because we got a signal we're not blocking,
116115
* return -EINTR (fail the system call). This allows processes
@@ -133,10 +132,6 @@ static int module_open(struct inode *inode, struct file *file)
133132
}
134133
}
135134

136-
/* If we got here, already_open must be zero. */
137-
138-
/* Open the file */
139-
already_open = 1;
140135
return 0; /* Allow the access */
141136
}
142137

@@ -148,7 +143,7 @@ static int module_close(struct inode *inode, struct file *file)
148143
* the other processes will be called when already_open is back to one,
149144
* so they'll go back to sleep.
150145
*/
151-
already_open = 0;
146+
atomic_set(&already_open, 0);
152147

153148
/* Wake up all the processes in waitq, so if anybody is waiting for the
154149
* file, they can have it.

lkmpg.tex

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -912,7 +912,7 @@ \subsection{Unregistering A Device}
912912
This is bound to happen to you sooner or later during a module's development.
913913

914914
\subsection{chardev.c}
915-
\label{sec:org7ce767e}
915+
\label{sec:chardev_c}
916916
The next code sample creates a char driver named \verb|chardev|.
917917
You can cat its device file.
918918

@@ -925,6 +925,13 @@ \subsection{chardev.c}
925925
Don't worry if you don't see what we do with the data we read into the buffer; we don't do much with it.
926926
We simply read in the data and print a message acknowledging that we received it.
927927

928+
In the multiple-threaded environment, without any protection, concurrent access to the same memory may lead to the race condition, and will not preserve the performance.
929+
In the kernel module, this problem may happen due to multiple instances accessing the shared resources.
930+
Therefore, a solution is to enforce the exclusive access.
931+
We use atomic Compare-And-Swap (CAS), the single atomic operation, to determine whether the file is currently open by someone.
932+
CAS compares the contents of a memory loaction with the expected value and, only if they are the same, modifies the contents of that memory location to the desired value.
933+
See more concurrency details in the \ref{sec:synchronization} section.
934+
928935
\samplec{examples/chardev.c}
929936

930937
\subsection{Writing Modules for Multiple Kernel Versions}
@@ -1158,6 +1165,9 @@ \section{Talking To Device Files}
11581165
If you want to use ioctls in your own kernel modules, it is best to receive an official ioctl assignment, so if you accidentally get somebody else's ioctls, or if they get yours, you'll know something is wrong.
11591166
For more information, consult the kernel source tree at \src{Documentation/userspace-api/ioctl/ioctl-number.rst}.
11601167

1168+
Also, we need to be careful that concurrent access to the shared resources will lead to the race condition.
1169+
The solution is using atomic Compare-And-Swap (CAS), which we mentioned at \ref{sec:chardev_c} section, to enforce the exclusive access.
1170+
11611171
\samplec{examples/chardev2.c}
11621172

11631173
\samplec{examples/chardev.h}
@@ -1460,6 +1470,16 @@ \subsection{Atomic operations}
14601470

14611471
\samplec{examples/example_atomic.c}
14621472

1473+
Before the C11 standard adopts the built-in atomic types, the kernel already provided a small set of atomic types by using a bunch of tricky architecture-specific codes.
1474+
Implementing the atomic types by C11 atomics may allow the kernel to throw away the architecture-specific codes and letting the kernel code be more friendly to the people who understand the standard.
1475+
But there are some problems, such as the memory model of the kernel doesn't match the model formed by the C11 atomics.
1476+
For further details, see:
1477+
\begin{itemize}
1478+
\item \href{https://www.kernel.org/doc/Documentation/atomic_t.txt}{kernel documentation of atomic types}
1479+
\item \href{https://lwn.net/Articles/691128/}{Time to move to C11 atomics?}
1480+
\item \href{https://lwn.net/Articles/698315/}{Atomic usage patterns in the kernel}
1481+
\end{itemize}
1482+
14631483
% FIXME: we should rewrite this section
14641484
\section{Replacing Print Macros}
14651485
\label{sec:print_macros}

0 commit comments

Comments
 (0)