简体   繁体   中英

Conditional compilation based on functionality in Linux kernel headers

Consider the case where I'm using some functionality from the Linux headers exported to user space, such as perf_event_open from <linux/perf_event.h> .

The functionality offered by this API has changed over time, as members have been added to the perf_event_attr , such as perf_event_attr.cap_user_time .

How can I write source that compiles and uses these new functionalities if they are available locally, but falls back gracefully if they aren't and doesn't use them?

In particular, how can I detect in the pre-processor whether this stuff is available?

I've used this perf_event_attr as an example, but my question is a general one because structure members, new structures, definitions and functions are added all the time.

Note that here I'm only considering the case where a process is compiled on the same system that it will run on: if you want to compile on one host and run on another you need a different set of tricks.

Use the macros from /usr/include/linux/version.h :

#include <linux/version.h>

int main() {
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
                                      // ^^^^^^ change for the proper version when `perf_event_attr.cap_user_time` was introduced
   // use old interface
#else
   // use new interface
   // use  perf_event_attr.cap_user_time
#endif
}

You might go into this with the following assumptions

  1. The features available in the header files correspond to those documented for the specific Linux version.

  2. The kernel running during execution corresponds to <linux/version.h> during compilation

Ideally, I suggest not to rely on these two assumptions at all.

The first assumption fails primarily due to backports, eg in enterprise Linux versions based on ancient kernels. If you care about different versions, you probably care about them.

Instead, I recommend utilizing the methods for checking for struct members and include files in build system, eg for CMake:

CHECK_STRUCT_HAS_MEMBER("struct perf_event_attr" cap_user_time linux/perf_event.h HAVE_PERF_CAP_USER_TIME)

CHECK_INCLUDE_FILES can also be useful.

The second assumption can fail for many reasons, even if the binary is not moved between systems; Eg updating the kernel but not recompiling the binary or simply booting another kernel. Specifically perf_event_open fails with EINVAL if a reserved bit is set. This allows you to retry with an alternative implementation not using the requested feature.

In short, statically check for the feature instead of the version. Dynamically, try and retry the legacy implementation if it failed.

Just in addition to other answers.

If you're aiming for supporting both cross-version and cross-distro code, you should also keep in mind that there are distros (Centos/RHEL) which pull some recent changes from new kernels to old. So you may encounter a situation in which you'll have LINUX_VERSION_CODE equal to some old kernel version, but there will be some changes (new fields in data structures, new functions, etc.) from recent kernel. In such case this macro is insufficient.

You can add something like (to avoid preprocessor errors in case it is not a Centos distro):

#ifndef RHEL_RELEASE_CODE
#define RHEL_RELEASE_CODE 0
#endif
#ifndef RHEL_RELEASE_VERSION
#define RHEL_RELEASE_VERSION(x,y) 1
#endif

And use it with > or >= where you need:

#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,3,0) || RHEL_RELEASE_CODE > RHEL_RELEASE_VERSION(7,2)
...

for Centos/RHEL custom kernels support.

PS of course it's necessary to examine an appropriate versions of Centos/RHEL, and understand when and what exactly has changed in the code sections that affect you.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM