C language skills commonly used in the Linux kernel (1)

foreword

The Linux kernel is written based on the C language, and proficiency in the C language is a basic requirement for in-depth study of the Linux kernel.

Extensions to the GNU C Language

In addition to supporting the ANSI C standard, GCC's C compiler has also made many extensions to the C language.

These extensions provide strong support for code optimization, object code layout, and security checks. Therefore, the C language that supports GNU extensions is called GNU C language .

The Linux kernel uses the GCC compiler, so the code of the Linux kernel naturally uses many new extension features of GCC.

This chapter introduces some new features of GCC C language expansion, and I hope readers pay special attention when learning the Linux kernel.

(1) Statement expression

In the GNU C language, a compound statement in parentheses can be regarded as an expression, called a statement expression.

In a statement expression, loops, jumps, local variables, etc. can be used. This feature is usually used in macro definitions, which can make macro definitions safer, such as comparing the size of two values.


#define max(a,b) ((a) > (b) ? (a) : (b))

The above code will cause security problems, a and b may be calculated twice, for example, a is passed to i++, and b is passed to j++. In the GNU C language, if you know the types of a and b, you can write this macro like this.

#define maxint(a,b) \
					({int _a = (a), _b = (b); _a > _b ? _a : _b; })

If you don't know the types of a and b, you can also use the typeof class conversion macro.

<include/linux/kernel.h>#define min(x, y) ({        \
typeof(x) _min1 = (x);      \
typeof(y) _min2 = (y);      \
(void) (&_min1 == &_min2);    \
_min1 < _min2 ? _min1 : _min2; })

typeof is also an extended usage of the GNU C language, which can be used to construct new types, and is usually used together with statement expressions.

Below are some examples.

typeof (*x) y;
typeof (*x) z[4];
typeof (typeof (char *)[4]) m;
  • The first sentence declares that y is the type pointed to by the pointer x.
  • The second sentence declares that z is an array, where the type of the array is the type pointed to by the x pointer.
  • The third sentence declares that m is an array of pointers, which is the same as the declaration of char*m[4].

(2) Zero-length array

The GNU C language allows the use of variable-length arrays, which is very useful when defining data structures.

<mm/percpu.c>
struct pcpu_chunk {
struct list_head  list;
unsigned long    populated[];  /* 变长数组 */};

The last element of the data structure is defined as a zero-length array, which does not occupy the space of the structure .

This way we can dynamically allocate the size of the structure based on the object size.

struct line {
int length;
char contents[0];
};
struct line *thisline = malloc(sizeof(struct line) +this_length);
thisline->length = this_length;

As shown in the above example, the struct line data structure defines an int length variable and a variable-length array contents[0]. The size of this struct line data structure only includes the size of the int type, and does not include the size of the contents, that is, sizeof (struct line) = sizeof (int).

When creating a structure object, you can specify the length of the variable-length array according to actual needs, and allocate the corresponding space. For example, the above example code allocates this_length bytes of memory, and you can access the data at the index-th address through contents[index] .

(3) case scope

The GNU C language supports specifying a case range as a label, such as:

case low ...high:

case 'A' ...'Z':

Here low to high represents an interval range, which is also very useful in ASCII character codes. Below is a code example in the Linux kernel.

<arch/x86/platform/uv/tlb_uv.c>
static int local_atoi(const char *name)
{
int val = 0;
for (;; name++) {
switch (*name) {
case '0' ...'9':
val = 10*val+(*name-'0');
break;
default:
return val;
}
}
}

In addition, integer numbers can also be used to represent the range, but here you need to pay attention to the spaces on both sides of "...", otherwise compilation errors will occur.

<drivers/usb/gadget/udc/at91_udc.c>
static int at91sam9261_udc_init(struct at91_udc *udc)
{
for (i = 0; i < NUM_ENDPOINTS; i++) {
ep = &udc->ep[i];
switch (i) {
case 0:
ep->maxpacket = 8;
break;
case 1 ...3:
ep->maxpacket = 64;
break;
case 4 ...5:
ep->maxpacket = 256;
break;
}
}
}

(4) Label elements

The standard C language requires that array or structure initialization values ​​must appear in a fixed order. However, the GNU C language can be initialized by specifying the index or structure member name, and it does not have to be initialized in the original fixed order.

The initialization of structure members is often used in the Linux kernel, such as initializing the file_operations data structure in a device driver. Below is a code example in the Linux kernel.

<drivers/char/mem.c>
static const struct file_operations zero_fops = {
.llseek      = zero_lseek,
.read        = new_sync_read,
.write       = write_zero,
.read_iter     = read_iter_zero,
.aio_write     = aio_write_zero,
.mmap        = mmap_zero,
};

For example, the zero_fops member llseek in the above code is initialized to the zero_lseek function, the read member is initialized to the new_sync_read function, and so on. When the definition of the file_operations data structure changes, this initialization method can still guarantee the correctness of known elements, and the value of uninitialized members is 0 or NULL.

(5) variable parameter macro

In the GNU C language, macros can accept a variable number of parameters, which is mainly used in output functions.

<include/linux/printk.h>
#define pr_debug(fmt, ...) \
dynamic_pr_debug(fmt, ##__VA_ARGS__)

"..." represents a parameter table that can be changed, and " VA_ARGS " is a field reserved by the compiler, and the parameters are passed to the macro during preprocessing. When the call to the macro is expanded, the actual arguments are passed to the dynamic_pr_debug function.

(6) Function attributes

The GNU C language allows the statement

  • Function Attribute,
  • Variable Attribute and
  • Type Attribute,

This allows the compiler to perform certain aspects of optimization and more careful code inspection. The special attribute syntax format is:

__attribute__ ((attribute-list))

There are many function attributes defined in the GNU C language, such as noreturn, format, and const.

In addition, some function attributes related to the processor architecture can also be defined. For example, interrupt, isr and other attributes can be defined in the ARM architecture. Interested readers can read the relevant documents of GCC.

Below is an example of using the format attribute in the Linux kernel.

<drivers/staging/lustru/include/linux/libcfs/>

int libcfs_debug_msg(struct libcfs_debug_msg_data *msgdata,const char *format1, ...)

__attribute__ ((format (printf, 2, 3)));

A format function attribute is declared in the libcfs_debug_msg() function, which tells the compiler to check the function parameters according to the format rules of the printf parameter table.

  • The number 2 indicates that the second parameter is a formatted string,

  • The number 3 indicates the number of the first parameter in the parameter "..." in the total number of function parameters.

The noreturn attribute informs the compiler that the function never returns a value, which allows the compiler to eliminate unnecessary warning messages . Such as the die function, the function will not return.

void __attribute__((noreturn)) die(void);

The const attribute will allow the compiler to call the function only once, and only need to return the first result when calling it later, thereby improving efficiency.

static inline u32 __attribute_const__read_cpuid_cachetype(void)
{
return read_cpuid(CTR_EL0);
}

Linux also has some other function attributes, which are defined in the compiler-gcc.h file.

#define __pure           
__attribute__((pure))
#define __aligned(x)       
__attribute__((aligned(x)))
#define __printf(a, b)      
__attribute__((format(printf, a, b)))
#define __scanf(a, b)      
__attribute__((format(scanf, a, b)))
#define noinline         
__attribute__((noinline))
#define __attribute_const__   
__attribute__((__const__))
#define __maybe_unused      
__attribute__((unused))
#define __always_unused     
__attribute__((unused))

(7) Variable attributes and type attributes

Variable attributes can be used to set attributes for variables or structure members. Common attributes of type attributes include alignment, packed, and sections.

The alignment attribute specifies the minimum alignment format of a variable or structure member, in bytes.

struct qib_user_info {
__u32 spu_userversion;
__u64 spu_base_info;
} __aligned(8);

In this example, the compiler allocates the qib_user_info data structure in an 8-byte aligned manner.

The packed attribute enables variables or structure members to use the smallest alignment, which is byte-aligned for variables and bit-aligned for fields.

struct test{
char a;
int x[2] __attribute__ ((packed));
};

The x member uses the packed attribute, which will be stored behind the variable a, so this structure occupies a total of 9 bytes.

(8) Built-in functions

The GNU C language provides a series of built-in functions for optimization, and these built-in functions are prefixed with " builtin " as the function name.

The following introduces some built-in functions commonly used in the Linux kernel.

  • __builtin_constant_p(x): Determine whether x can be determined as a constant at compile time. The function returns 1 if x is a constant, and 0 otherwise.
#define __swab16(x)        \
(__builtin_constant_p((__u16)(x)) ?  \
___constant_swab16(x) :      \
__fswab16(x))
  • __builtin_expect(exp, c): This means that the probability of exp==c is very high, which is used to guide the GCC compiler to perform conditional branch prediction. Developers know which branch is most likely to be executed, and tell the compiler the most likely branch to execute, so that the compiler can optimize the instruction sequence so that the instructions are executed as sequentially as possible, thereby improving the correct rate of CPU prefetch instructions.
#define LIKELY(x) __builtin_expect(!!(x), 1) //x很可能为真
#define UNLIKELY(x) __builtin_expect(!!(x), 0) //x很可能为假
  • __builtin_prefetch(const void *addr, int rw, int locality): Actively perform data prefetch, load the value of address addr into the cache before using it, reduce the delay of reading, and improve performance.
    The function can accept 3 parameters:
    • The first parameter addr indicates the address of the data to be prefetched;
    • The second parameter rw indicates the read-write attribute, 1 indicates writable, and 0 indicates read-only;
    • The third parameter locality indicates the temporal locality of the data in the cache, where 0 indicates that the addr is not kept in the cache after reading it, and 1~3 indicates that the temporal locality is gradually enhanced.

Such as the implementation of the prefetch() and prefetchw() functions below.

<include/linux/prefetch.h>
#define prefetch(x) __builtin_prefetch(x)
#define prefetchw(x) __builtin_prefetch(x,1)

Below is an example of optimization using the prefetch() function.

<mm/page_alloc.c>
void __init __free_pages_bootmem(struct page *page,unsigned int order)
{
unsigned int nr_pages = 1 << order;
struct page *p = page;
unsigned int loop;
prefetchw(p);
for (loop = 0; loop < (nr_pages - 1); loop++, p++) 
{
prefetchw(p + 1);
__ClearPageReserved(p);
set_page_count(p, 0);
}
}

Before processing the struct page data, prefetch it into the cache through prefetchw() to improve performance.

(9)asmlinkage

In the standard C language, when the formal parameters of the function are actually passed in, the problem of parameter storage will be involved. For the x86 architecture, function parameters and local variables are allocated together on the function's local stack.

<arch/x86/include/asm/linkage.h>
#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))

attribute ((regparm(0))): Tell the compiler that the function does not need to pass parameters through any registers, only through the stack.

For ARM, there is a set of ATPCS standards for passing function parameters, that is, passing through registers . The R0-R4 registers in the ARM store incoming parameters. When there are more than 5 parameters, the redundant parameters are stored in the local stack. Therefore, the ARM platform does not define asmlinkage .

<include/linux/linkage.h>
#define asmlinkage CPP_ASMLINKAGE
#define asmlinkage CPP_ASMLINKAGE

(10)UL

In the Linux kernel code, we often see the definition of some numbers using the UL suffix modification.

Numerical constants are implicitly defined as int types. The result of adding two int types may overflow. Therefore, UL is used to force the data of int type to be converted to unsigned long type.

  • 1: Indicates signed integer number 1
  • 1UL: Represents the unsigned long integer number 1

References

"Run Linux Kernel"

Guess you like

Origin blog.csdn.net/weixin_45264425/article/details/131863382
Recommended