2

It seems that lots of Linux Kernel components provide macros to help the programmer easily declare a variable statically at runtime, which is allocated on the stack (doesn't mean a static keyword); for example, DECLARE_WORK(name, void (*func)(void *), void *data) is provided to declare work_struct which is initialized with the provided func and data arguments.

Also, sometimes it provides a macro to initialize a dynamically created variable such as INIT_WORK(struct work_struct *work, void (*func)(void *), void *data), which assigns func and data argument to the member field of work pointer.

It seems that these macros are usually defined by the core kernel components, and cannot be seen frequently in the driver code. Is there any rule or custom that define when to provide those #defined macros for declaring or initializing data structures in the Linux Kernel such as providing this interface for frequently used data structure?

And why they are provided? Is it only because to provide a way to write clean code by giving a clean interface to declare and initialize complex data structures to kernel programmers?

Is it because some data structures are internally used, but some data structures can be utilized by multiple kernel components? In other words, to hide the implementation of the data structures?

melpomene
  • 84,125
  • 8
  • 85
  • 148
ruach
  • 1,369
  • 11
  • 21
  • 1
    Are you asking how to maintain the Linux kernel coding style or how to write proper, readable, maintainable C? Those two are conflicting interests. – Lundin Aug 19 '19 at 06:53
  • @Lundin Does the macro-defined initialization and declaration are used in other projects also? I've never seen that from other projects before(might be because of the lack of my experience). Is it a common C coding technique to provide a proper, readable, maintainable interface for complex data structure's initialization and declartion? – ruach Aug 19 '19 at 07:17
  • 1
    No, hiding declarations behind macros is bad practice. If you peek at that macro, it does a whole lot of things. Seems that the main reason to use a macro instead of a function is some compile-time string concatenation involving the work parameter. Overall ugly code. – Lundin Aug 19 '19 at 07:37
  • @Lundin I understand your point, but INIT_WORK macro doesn't concatenate any string to the name passed through the argument and just declare a variable. Then, I am curious declaring a variable through the macro itself is not a good custom? But by hiding the details of the complex data structure such as member field name, it can make a cood look much simpler than writing down multiple lines of initialization code. If it is a bad custom, then why Linux kernel developers still follow this coding practice? – ruach Aug 19 '19 at 08:02
  • And another benefit that I can guess is because the kernel source code is too big to understand them all, concealing the details of the data structure implementation may allow other programmers working on other parts of the kernel to have easy access to the data structures. This is my pure guess though. – ruach Aug 19 '19 at 08:08
  • 1
    It would have been proper to use a function instead. I'm guessing the reason for the macro is the string concatenation but also performance - lots of the Linux code is pre-C99, when compilers still had shaky inlining support. Overall, the Linux kernel is not a place to go when searching for best code practices. – Lundin Aug 19 '19 at 08:10
  • @Lundin I think you are talking about declaring a variable with the ## operator in the CPP, and I agree with you on introducing variable which is string concatenated with some prefix or postfix is not a good custom. However, I was curious is there any advantages that we can find on the variable declaring macro. I also agree on your comment about speed because it is inlined and might be "slightly" faster than invoking function. – ruach Aug 19 '19 at 08:14
  • And the final question. Then where can I get the best code practice for C usually? – ruach Aug 19 '19 at 08:16
  • You'll find it after 10+ years of experience :) But well, there are safe subsets of C that can be used to weed out dangerous practices: MISRA-C:2012 or CERT-C. These play in an entirely different division than the garage hacker "Linux kernal coding style" document. – Lundin Aug 19 '19 at 08:20
  • Sometimes the "initialized variable on stack" macros that the kernel headers provide are more convoluted than they need to be to do their job, in order to avoid violating other arbitrary rules in the kernel code, such as "no intermixing of declarations and statements in a block". – Ian Abbott Aug 19 '19 at 12:15
  • @IanAbbott Could you provide an example? Does it seem that the macro that I've mentioned is related to preventing intermingle declarations and statements? – ruach Aug 19 '19 at 14:52
  • @JaehyukLee An example that came up recently is `DECLARE_COMPLETION_ONSTACK(work)` defined in ``. See [this question](https://stackoverflow.com/questions/57487415) for details.It seems that kernel debugging features make these macros more convoluted. – Ian Abbott Aug 19 '19 at 15:01
  • @JaehyukLee For the macro you mentioned, there is a `DECLARE_WORK(work)` corresponding to `INIT_WORK(work)`, but there isn't a `DECLARE_WORK_ONSTACK(work)` corresponding to `INIT_WORK_ONSTACK(work)`, so perhaps the latter turned out to be impossible (I'm not sure). Or it might be because `INIT_WORK_ONSTACK(&work);` should be paired with a `destroy_work_on_stack(&work);` and it breaks the symmetry. – Ian Abbott Aug 19 '19 at 15:09

1 Answers1

1

I think it is a bit of both.

#define __WORK_INITIALIZER(n, f) {                  \
    .data = WORK_DATA_STATIC_INIT(),                \
    .entry  = { &(n).entry, &(n).entry },               \
    .func = (f),                            \
    __WORK_INIT_LOCKDEP_MAP(#n, &(n))               \
    }

#define DECLARE_WORK(n, f)                      \
    struct work_struct n = __WORK_INITIALIZER(n, f)

First of all, in this case, it is most probably due to the simple reason that initializing work_struct is at least 4-5 lines of code and since this is something that happens quite a lot in the kernel, it would add unnecessary verbosity to the code, so it has been abstracted into a macro. It could have been a function, but that is also less efficient.

Also, it is difficult for kernel developers and maintainers, when reviewing the code, to ensure that every time a work_struct is declared, it is also initialized properly. In this case, since the initialization code is at a single place, they can safely assume that any code that uses this macro will have initialized the struct properly.

Thirdly, you may be a developer working on some specific driver inside a specific subsystem and may not want to know the internals of the kernel work_queue mechanism. You just want to be able to queue your work using the api provided. This macro also saves you from being bothered about the internals. Even if a new field is added to the struct tomorrow for internal use, your driver code does not need to change.

th33lf
  • 2,177
  • 11
  • 15