BpfRingbuf: Add support for 32 bit kernels
This change adds support for 32 bit kernels by capping mProducerPos to 4 bytes. mConsumerPos (which is written to by userspace) continues to use 8 bytes. (This can be done because an entire page is allocated for mConsumerPos to control access permissions). In a 32 bit kernel, that means that the top order bits are just ignored. In addition, comparisons in userspace only use the bottom 4 bytes to be bitness agnostic. Test: atest BpfRingbufTest Change-Id: I7fe6d9000a151512785f1aa2a53fa97d31967d19
This commit is contained in:
@@ -50,10 +50,6 @@ class BpfRingbufTest : public ::testing::Test {
|
|||||||
GTEST_SKIP() << "BPF ring buffers not supported below 5.8";
|
GTEST_SKIP() << "BPF ring buffers not supported below 5.8";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isKernel64Bit()) {
|
|
||||||
GTEST_SKIP() << "BPF ring buffers not supported on 32 bit kernel";
|
|
||||||
}
|
|
||||||
|
|
||||||
errno = 0;
|
errno = 0;
|
||||||
mProgram.reset(retrieveProgram(mProgPath.c_str()));
|
mProgram.reset(retrieveProgram(mProgPath.c_str()));
|
||||||
EXPECT_EQ(errno, 0);
|
EXPECT_EQ(errno, 0);
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
#include <utils/Log.h>
|
#include <utils/Log.h>
|
||||||
|
|
||||||
#include "bpf/BpfUtils.h"
|
#include "bpf/BpfUtils.h"
|
||||||
#include "bpf/KernelUtils.h"
|
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
|
||||||
@@ -83,13 +82,17 @@ class BpfRingbufBase {
|
|||||||
android::base::unique_fd mRingFd;
|
android::base::unique_fd mRingFd;
|
||||||
|
|
||||||
void* mDataPos = nullptr;
|
void* mDataPos = nullptr;
|
||||||
|
// The kernel uses an "unsigned long" type for both consumer and producer position.
|
||||||
|
// Unsigned long is a 4 byte value on a 32-bit kernel, and an 8 byte value on a 64-bit kernel.
|
||||||
|
// To support 32-bit kernels, producer pos is capped at 4 bytes (despite it being 8 bytes on
|
||||||
|
// 64-bit kernels) and all comparisons of consumer and producer pos only compare the low-order 4
|
||||||
|
// bytes (an inequality comparison is performed to support overflow).
|
||||||
|
// This solution is bitness agnostic. The consumer only increments the 8 byte consumer pos, which,
|
||||||
|
// in a little-endian architecture, is safe since the entire page is mapped into memory and a
|
||||||
|
// 32-bit kernel will just ignore the high-order bits.
|
||||||
std::atomic_uint64_t* mConsumerPos = nullptr;
|
std::atomic_uint64_t* mConsumerPos = nullptr;
|
||||||
std::atomic_uint64_t* mProducerPos = nullptr;
|
std::atomic_uint32_t* mProducerPos = nullptr;
|
||||||
|
|
||||||
// The kernel uses an "unsigned long" type for both consumer and producer position. Unsigned long
|
|
||||||
// is a 4 byte value on a 32 bit kernel, and an 8 byte value on a 64 bit kernel. While 32 bit
|
|
||||||
// kernels are slowly going away (and therefore will not be supported), we *must* still support 32
|
|
||||||
// bit userspace on a 64 bit kernel.
|
|
||||||
// In order to guarantee atomic access in a 32 bit userspace environment, atomic_uint64_t is used
|
// In order to guarantee atomic access in a 32 bit userspace environment, atomic_uint64_t is used
|
||||||
// in addition to std::atomic<T>::is_always_lock_free that guarantees that read / write operations
|
// in addition to std::atomic<T>::is_always_lock_free that guarantees that read / write operations
|
||||||
// are indeed atomic.
|
// are indeed atomic.
|
||||||
@@ -98,7 +101,9 @@ class BpfRingbufBase {
|
|||||||
// to its atomic version is safe (is_always_lock_free being true should provide additional
|
// to its atomic version is safe (is_always_lock_free being true should provide additional
|
||||||
// confidence).
|
// confidence).
|
||||||
static_assert(std::atomic_uint64_t::is_always_lock_free);
|
static_assert(std::atomic_uint64_t::is_always_lock_free);
|
||||||
|
static_assert(std::atomic_uint32_t::is_always_lock_free);
|
||||||
static_assert(sizeof(std::atomic_uint64_t) == sizeof(uint64_t));
|
static_assert(sizeof(std::atomic_uint64_t) == sizeof(uint64_t));
|
||||||
|
static_assert(sizeof(std::atomic_uint32_t) == sizeof(uint32_t));
|
||||||
};
|
};
|
||||||
|
|
||||||
// This is a class wrapper for eBPF ring buffers. An eBPF ring buffer is a
|
// This is a class wrapper for eBPF ring buffers. An eBPF ring buffer is a
|
||||||
@@ -140,10 +145,6 @@ class BpfRingbuf : public BpfRingbufBase {
|
|||||||
|
|
||||||
|
|
||||||
inline base::Result<void> BpfRingbufBase::Init(const char* path) {
|
inline base::Result<void> BpfRingbufBase::Init(const char* path) {
|
||||||
if (!isKernel64Bit()) {
|
|
||||||
return android::base::Error()
|
|
||||||
<< "BpfRingbuf does not support 32 bit kernels.";
|
|
||||||
}
|
|
||||||
mRingFd.reset(mapRetrieveRW(path));
|
mRingFd.reset(mapRetrieveRW(path));
|
||||||
if (!mRingFd.ok()) {
|
if (!mRingFd.ok()) {
|
||||||
return android::base::ErrnoError()
|
return android::base::ErrnoError()
|
||||||
@@ -199,9 +200,9 @@ inline base::Result<void> BpfRingbufBase::Init(const char* path) {
|
|||||||
inline base::Result<int> BpfRingbufBase::ConsumeAll(
|
inline base::Result<int> BpfRingbufBase::ConsumeAll(
|
||||||
const std::function<void(const void*)>& callback) {
|
const std::function<void(const void*)>& callback) {
|
||||||
int64_t count = 0;
|
int64_t count = 0;
|
||||||
auto cons_pos = mConsumerPos->load(std::memory_order_acquire);
|
uint64_t cons_pos = mConsumerPos->load(std::memory_order_acquire);
|
||||||
auto prod_pos = mProducerPos->load(std::memory_order_acquire);
|
uint32_t prod_pos = mProducerPos->load(std::memory_order_acquire);
|
||||||
while (cons_pos < prod_pos) {
|
while ((cons_pos & 0xFFFFFFFF) != prod_pos) {
|
||||||
// Find the start of the entry for this read (wrapping is done here).
|
// Find the start of the entry for this read (wrapping is done here).
|
||||||
void* start_ptr = pointerAddBytes<void*>(mDataPos, cons_pos & mPosMask);
|
void* start_ptr = pointerAddBytes<void*>(mDataPos, cons_pos & mPosMask);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user