forked from microsoft/Windows-driver-samples
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathringbuffer.h
More file actions
223 lines (196 loc) · 7.56 KB
/
ringbuffer.h
File metadata and controls
223 lines (196 loc) · 7.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
#pragma once
#include "internal.h"
class CRingBuffer
{
private:
//
// Private local variables.
//
//
// The size in bytes of the ring buffer.
//
SIZE_T m_Size;
//
// A pointer to the base of the ring buffer.
//
PBYTE m_Base;
//
// A pointer to the byte beyond the end of the ring buffer. Used for
// quick comparisons when determining if we need to wrap.
//
PBYTE m_End;
//
// A pointer to the current read point in the ring buffer.
//
// Updates to this are not protected by any lock. This is different from
// the write pointer, which is protected by the "pending read pointer"
// lock. The reason for this difference is that in this driver, we do not
// keep write requests pending. If there is not enough space to write all
// the data that was requested, we write as much as we can and drop the
// rest (lossy data transfer).
//
// If we had multiple threads modifying this pointer, then that would
// provide yet another reason for protecting updates to the pointer using a
// lock. However, in this driver, at any given time we have only one thread
// that modifies this pointer (the thread that runs the read callback).
// This is true because we use a sequential queue for read requests. If we
// were to change our read queue to be a parallel queue, this would no
// longer be true.
//
//
PBYTE m_Head;
//
// A pointer to the current write point in the ring buffer.
//
// Updates to this pointer are protected by the "pending read pointer
// lock", because we do not want a consumer thread to mark a read request
// as pending while we are in the process of writing data to the buffer.
// The reason is that the write that we are currently performing might
// actually supply enough data to satisfy the read request, in which case
// it should not be marked pending at all.
// If the read request were to be marked pending in the situation described
// above, then we would need some trigger to later retrieve the request and
// complete it. In our driver, arrival of data is the only event that can
// trigger this. So if no more data arrives, the request will remain
// pending forever, even though there is enough data in the buffer to
// complete it. Hence we do not keep a read request pending in situations
// where the read buffer contains enough data to satisfy it.
//
// If we had multiple threads modifying this pointer, then that would
// provide yet another reason for protecting updates to the pointer using a
// lock. However, in this driver, at any given time we have only one thread
// that modifies this pointer (the thread that runs the write callback).
// This is true because we use a sequential queue for write requests. If we
// were to change our write queue to be a parallel queue, this would no
// longer be true.
//
PBYTE m_Tail;
private:
//
// Private Internal Methods.
//
void
GetAvailableSpace(
_Out_ SIZE_T *AvailableSpace
)
{
WUDF_TEST_DRIVER_ASSERT(AvailableSpace);
PBYTE headSnapshot = NULL;
PBYTE tailSnapshot = NULL;
PBYTE tailPlusOne = NULL;
//
// Take a snapshot of the head and tail pointers. We will compute the
// available space based on this snapshot. This is safe to do in a
// single-producer, single-consumer model, because -
// * A producer will call GetAvailableSpace() to determine whether
// there is enough space to write the data it is trying to write.
// The only other thread that could modify the amount of space
// available is the consumer thread, which can only increase the
// amount of space available. Hence it is safe for the producer
// to write based on this snapshot.
// * A consumer thread will call GetAvailableSpace() to determine
// whether there is enough data in the buffer for it to read.
// (Available data = Buffer size - Available space). The only
// other thread that could modify the amount of space available
// is the producer thread, which can only decrease the amount of
// space available (thereby increasing the amount of data
// available. Hence it is safe for the consumer to read based on
// this snapshot.
//
headSnapshot = m_Head;
tailSnapshot = m_Tail;
//
// In order to distinguish between a full buffer and an empty buffer,
// we always leave the last byte of the buffer unused. So, an empty
// buffer is denoted by -
// tail == head
// ... and a full buffer is denoted by -
// (tail+1) == head
//
tailPlusOne = ((tailSnapshot+1) == m_End) ? m_Base : (tailSnapshot+1);
if (tailPlusOne == headSnapshot)
{
//
// Buffer full
//
*AvailableSpace = 0;
}
else if (tailSnapshot == headSnapshot)
{
//
// Buffer empty
// The -1 in the computation below is to account for the fact that
// we always leave the last byte of the ring buffer unused in order
// to distinguish between an empty buffer and a full buffer.
//
*AvailableSpace = m_Size - 1;
}
else
{
if (tailSnapshot > headSnapshot)
{
//
// Data has not wrapped around the end of the buffer
// The -1 in the computation below is to account for the fact
// that we always leave the last byte of the ring buffer unused
// in order to distinguish between an empty buffer and a full
// buffer.
//
*AvailableSpace = m_Size - (tailSnapshot - headSnapshot) - 1;
}
else
{
//
// Data has wrapped around the end of the buffer
// The -1 in the computation below is to account for the fact
// that we always leave the last byte of the ring buffer unused
// in order to distinguish between an empty buffer and a full
// buffer.
//
*AvailableSpace = (headSnapshot - tailSnapshot) - 1;
}
}
return;
}
public:
//
// Public Internal methods.
//
CRingBuffer(
VOID
);
~CRingBuffer(
VOID
);
HRESULT
Initialize(
_In_ SIZE_T BufferSize
);
HRESULT
Write(
_In_reads_bytes_(DataSize) PBYTE Data,
_In_ SIZE_T DataSize
);
HRESULT
Read(
_Out_writes_bytes_to_(DataSize, *BytesCopied) PBYTE Data,
_In_ SIZE_T DataSize,
_Out_ SIZE_T *BytesCopied
);
void
GetAvailableData(
_Out_ SIZE_T *AvailableData
)
{
SIZE_T availableSpace;
WUDF_TEST_DRIVER_ASSERT(AvailableData);
GetAvailableSpace(&availableSpace);
//
// The -1 in the arithmetic below accounts for the fact that we always
// keep 1 byte of the ring buffer unused in order to distinguish
// between a full buffer and an empty buffer.
//
*AvailableData = m_Size - availableSpace - 1;
return;
}
};