Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

Embedded C language self-cultivation 04:Linux Kernel first Macro: cont

2025-01-15 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Servers >

Share

Shulou(Shulou.com)06/02 Report--

4.1 typeof keyword

ANSI C defines the sizeof keyword, which is used to get the number of bytes of memory occupied by a variable or data type. GNU C extends the keyword typeof to get the type of a variable or expression. It may not be appropriate to use keywords here because, after all, typeof has not been written into the C standard and is a keyword for GCC extensions. For convenience, let's call it a keyword.

By using typeof, we can get the type of a variable or expression. So there are two forms of parameters for typeof: expressions or types.

Int I; typeof (I) j = 20 typeof (int *) a politics int f (); typeof (f ()) k

In the above code, because the variable I is of type int, typeof (I) equals int,typeof (I) j = 20 is equivalent to int j = 20 int typeof (int) a; equivalent to int a, the function is typed, and the type of the function is its return value type, so typeof (f ()) k; is equivalent to int k;.

4.2 examples of typeof usage

According to the use of typeof above, let's write a program to learn the use of typeof.

Int main (void) {int I = 2; typeof (I) k = 6; int * p = & k; typeof (p) Q = & I; printf ("k =% d\ n", k); printf ("* p =% d\ n", * p); printf ("I =% d\ n", I); printf ("* Q =% d\ n", * Q); return 0;}

The running result is:

K = 6p = 6i = 2q = 2

As you can see from the running results, after you get the type int of a variable through typeof, you can use that type to define another variable. This is the same as defining a variable directly using int, and the effect is the same.

4.3 other uses of typeof

In addition to using typeof to get basic data types, there are other advanced uses:

Typeof (int *) y; / defines y as a pointer to the int type, equivalent to int * y of (int) * y; / defines a pointer variable ytypeof (* x) y that executes the int type; / / defines a pointer variable ytypeof (int) y [4] of the type to which the pointer x points; / / defines a pointer y [4] typeof (* x) y [4] / / an array typeof (typeof (char *) [4]) that defines y as the data type pointed to by the pointer x / equivalent to defining an array of character pointers: char * y [4]; typeof (int x [4]) ybank / equivalent to definition: int y [4] 4.4 continue to improve the MAX (Azob) macro

In the previous section, we defined a macro MAX (XMagol y) to find the larger of the two numbers and to support different types of data:

# define MAX (type,x,y) ({\ type _ x = x;\ type _ y = y;\ _ x > _ y? _ x: _ y;\})

Although this macro can support any data type, it still has a flaw: we must pass the data type to the macro as a separate parameter. Next, let's go on to optimize the macro: instead of passing this parameter separately, we use the typeof keyword to get the data type of the parameter directly.

# define MAX (xpeny) ({\ typeof (x) _ x = x;\ typeof (x) _ y = y;\ _ x > _ y? _ x: _ y;\}) int main (void) {int I = 2; int j = 6; printf ("max:% d\ n", MAX (I, j)) Printf ("max:% f\ n", MAX (3.14,3.15); return 0;}

Get the parameter type of the macro directly through typeof, so that we no longer have to pass the parameter type to the macro separately. The improved macro also supports any type of data comparison size. In the main function, we use this macro to compare int data with float data and find that both work! Isn't that cool? When you write this macro to the interviewer during the interview, do you think the interviewer will be willing to let you go back and wait for the news?

With this idea, we can also rewrite some previously defined macros in this way, so that SWAP macros can also support many types of data.

# define swap (a, b)\ do {\ typeof (a) _ tmp = (a);\ (a) = (b);\ (b) = _ _ tmp;\} while (0) 4.5 Application of typeof in the kernel

The keyword typeof is widely used in the Linux kernel, mainly in macro definitions, to obtain macro parameter types. For example, in the kernel, the definition of min/max macros:

# define min (x, y) ({\ typeof (x) _ min1 = (x);\ typeof (y) _ min2 = (y);\ (void) (& _ min1 = = & _ min2);\ _ min1

< _min2 ? _min1 : _min2; })#define max(x, y) ({ \ typeof(x) _max1 = (x); \ typeof(y) _max2 = (y); \ (void) (&_max1 == &_max2); \ _max1 >

_ max2? _ max1: _ max2;})

In the min\ max macro definition, you don't have to pass the parameter type to the macro by using typeof to get the parameter type directly. The macros defined in the kernel are a little different from the example we gave above, with an extra line of code:

(void) (& _ max1 = = & _ max2)

This sentence is very interesting: it looks like nonsense, but it is actually used very cleverly! It is mainly used to detect whether the data types of the two parameters x and y of the macro are the same. If it is different, the compiler will give a warning message to remind the program developer.

Warning:comparison of distinct pointer types lacks a cast

Let's analyze how it is implemented: the statement & _ max1 = = & _ max2 is used to determine whether the addresses of the two variables _ max1 and _ max2 are equal, that is, to compare whether the two pointers are equal. How can & _ max1 and & _ max2 represent the addresses of two different variables, respectively? Since we all know that the addresses of two different variables in memory must not be equal, why bother here? The beauty is that when two variable types are not the same, the corresponding address, that is, the pointer type is also different. For example, an int variable, a char variable, the corresponding pointer types are char and int, respectively, and compared with the two pointers, they must be pointers of the same type, otherwise the compiler will have a warning. Therefore, through this "curve to save the nation" way, this line of program statement to achieve such a function: when the two parameter types of macros are not the same, the compiler will give us a warning message in time to remind developers.

After watching the implementation of this macro, I have to sigh the breadth and profundity of the kernel! Every detail, every casual sentence, detailed taste, can learn a lot of knowledge, so that your C language skills are more profound. Don't go, let's move on to another more interesting macro in the Linux kernel.

Introduction to the container_of macro container _ of macro in the Linux kernel

With the basics of the above statement expressions and typeof, we can analyze the first macro of the Linux kernel: container_of. This macro is widely used in the Linux kernel. Whether you can use this macro, and whether you can understand it, has gradually become an unwritten standard for examining the C language skills of a kernel driver developer. Cut the crap, let's get a glimpse of your face first.

# define offsetof (TYPE, MEMBER) ((size_t) & (TYPE *) 0)-> MEMBER) # define container_of (ptr, type,member) ({\ const typeof (type *) 0)-> member) * _ mptr = (ptr);\ (type *) ((char *) _ mptr-offsetof (type,member));})

As the first macro in the kernel, it is definitely not covered: look at this figure, this curve, high-end atmosphere, low-key luxury has connotation, do not go out and do a hair, it is simply insult. With the comprehensive use of the high-end extension features of GNU C, there are macros in macros, so I have to admire the genius of the kernel developers. So what on earth is this macro for? Its main function is to obtain the first address of the structure according to the address of a member of the structure. According to the macro definition, we can see that this macro has three parameters, which are:

Type: structure type member: member in the structure ptr: address of the member member in the structure

In other words, if we know the type of a structure and the address of a member in the structure, we can directly get the first address of the structure. The container_of macro returns the first address of this structure.

Example of using container_of Macro

For example, now we define a struct type student:

Struct student {int age; int num; int math;}; int main (void) {struct student stu; struct student * p; p = container_of (& stu.num, struct student, num); return 0;}

In this program, we define a structure type student, and then define a structure variable stu. Now that we know the address of the structure member variable stu.num, we can get the first address of the structure variable stu through the container_of macro.

This macro is very important in the kernel. As we know, in the Linux kernel driver, the data structure is encapsulated many times for abstraction, and multi-layer structures are often embedded in one structure. In other words, different levels of subsystems or modules in the kernel driver use structures with different degrees of encapsulation, which is also the object-oriented idea of the C language. Layering, abstraction and encapsulation can make our programs more compatible and adapt to more devices, but it also increases the complexity of the code.

We often encounter this situation in the kernel: what do we do when we pass parameters to a function that are member variables of a structure, and then other member variables of that structure may be used in this function? This is what container_of does, through which we can first find the first address of the structure, and then access other member variables through the access of the members of the structure.

Struct student {int age; int num; int math;}; int main (void) {struct student stu = {20, 1001, 99}; int * p = & stu.math; struct student * stup = NULL; stup = container_of (p, struct student, math); printf ("% p\ n", stup); printf ("age:% d\ n", stup- > age); printf ("num:% d\ n", stup- > num) Return 0;}

In this program, we define a structure variable stu, know the address of its member variable math & stu.math, we can directly obtain the first address of the stu structure variable through the container_of macro, and then we can directly access other members of the stu structure stup- > age and stup- > num.

4.7 Analysis of container_of Macro implementation

Now that we know how to use the container_of macro, we go on to analyze the implementation of the macro. As a Linux kernel driver developer, in addition to facing various manuals and bottom registers, sometimes we have to deal with building wheels at the bottom. For the stability and performance of the system, sometimes we have to go deep into the bottom and knock on a module for analysis and optimization. While the work at the bottom is challenging, it is sometimes boring and not as interesting as application development. Therefore, in order to enhance their interest in work, although on the surface, people do not say that they are awesome, but deep down, they must establish their own sense of job superiority. We must not be arrogant, but we must be arrogant: unlike application development, we know the API interface, read the document, and complete the function OK. As a low-level developer, you should always remember to blend in with registers, memory, hardware circuits, and other ethnic groups. Only in this way can we build a stable and harmonious embedded system: stable and efficient, smooth up and down, and running 365 sunrises without collapse.

The implementation of container_of macros mainly uses what we have learned in the last two sections: statement expressions and typeof, plus the basics of structure storage. To help you better understand this macro, let's review the basics of structural storage.

Storage of structure in memory

We know that a structure, as a compound type data, can have multiple members. When we define a structure variable, the compiler allocates storage space for the variable in memory. In addition to considering data types and byte alignment, the compiler allocates a contiguous piece of space in memory to store them in the order of the members of the structure.

Struct student {int age; int num; int math;}; int main (void) {struct student stu = {20, 1001, 99}; printf (& stu =% p\ n ", & stu); printf (& stu.age =% p\ n", & stu.age); printf ("& stu.num =% p\ n", & stu.num); printf ("& stu.math =% p\ n", & stu.math) Return 0;}

In this program, we define a structure with three int data members, define a variable, and then print the address of the structure and the address of each member variable. The running result is as follows:

& stu = 0028FF30&stu.age = 0028FF30&stu.num = 0028FF34&stu.math = 0028FF38

From the running results, we can see that each member variable in the structure, starting from the first address of the structure, is stored in turn. Each member variable has a fixed offset from the header address of the structure. For example, num is offset by 4 bytes relative to the header address of the structure. The storage address of the math is offset by 8 bytes from the header address of the structure.

Calculate the offset of member variables in the structure

For a structure data type, the offset of each member relative to the header address of the structure is fixed in the same compilation environment. We can modify the above program. When the first address of the structure is 0, the address of each member in the structure is numerically equal to the offset of each member of the structure relative to the first address of the structure.

Struct student {int age; int num; int math;}; int main (void) {printf ("& age =% p\ n", & (struct student*) 0)-> age); printf ("& num =% p\ n", & (struct student*) 0)-> num); printf ("& math=% p\ n", & (struct student*) 0)-> math); return 0;}

In the above program, instead of defining the structure variable directly, we convert the number 0 to a constant pointer to the structure type student through forced type conversion, and then print the addresses of the members of the structure that the constant pointer points to. The running results are as follows:

& age = 00000000&num = 00000004millimath= 00000008

Because the constant pointer is 0, the header address of the structure can be regarded as 0, so the address of each member variable in the structure is the offset of the member from the header address of the structure. The implementation of container_of macros is implemented using this technique.

Implementation of container_of Macro

With the above foundation, it is relatively simple for us to analyze the implementation of container_of macros. Knowing the address of the member of the structure, how to get the first address of the structure? Simply take the address of the member of the structure, subtract the offset of the member in the body of the structure, and you can get the first address of the structure.

# define offsetof (TYPE, MEMBER) ((size_t) & (TYPE *) 0)-> MEMBER) # define container_of (ptr, type,member) ({\ const typeof (type *) 0)-> member) * _ mptr = (ptr);\ (type *) ((char *) _ mptr-offsetof (type,member));})

From a syntax point of view, we can see that the implementation of a container_of macro consists of a statement expression. The value of the statement expression is the value of the last expression:

(type *) ((char *) _ mptr-offsetof (type,member))

The meaning of the last sentence is to take the address of a member of the structure member, minus the offset of that member in the structure type, and the result is the first address of the structure type. Because the value of the statement expression is equal to the value of the last expression, this result is also the value of the entire statement expression, and container_of finally returns this address value to the caller of the macro.

So how to calculate the offset of a member of the structure in the structure? Offset macros are defined in the kernel to implement this function. Let's look at its definition:

# define offsetof (TYPE, MEMBER) ((size_t) & ((TYPE *) 0)-> MEMBER)

This macro has two parameters, one is the structure type TYPE, the other is the structure member MEMBER, it uses the same technique as we calculated the offset of the 0 address constant pointer above: cast 0 into a structure constant pointer pointing to TYPE, and then access the member through this constant pointer to obtain the address of the member MEMBER, whose size is numerically equal to the offset of MEMBER in the structure TYPE.

Because the member data type of a structure can be any data type, in order to make this macro compatible with various data types. We define a temporary pointer variable _ _ mptr, which is used to store the address of the structure member MEMBER, that is, the value of ptr. So how do you get the ptr pointer type in the following ways:

Typeof ((type *) 0)-> member) * _ mptr = (ptr)

We know that the parameter ptr of the macro represents the address of a structure member variable MEMBER, so the type of ptr is a pointer to the MEMBER data type. When we use the temporary pointer variable mptr to store the value of ptr, we must make sure that the pointer type of mptr is a pointer variable that points to the MEMBER type. The typeof (type) 0)-> member) expression uses the typeof keyword to get the data type of the structure member member, and then using that type, you can define a pointer variable pointing to that type using the program statement typeof (type) 0)-> member) * _ mptr.

Another detail to note is that at the end of the statement expression, because the first address of the structure is returned, the data type must also be cast to TYPE, that is, a pointer to the TYPE structure type is returned, so you will see a cast (TYPE) in the last expression.

Summary

Well, at this point, our analysis of container_of macros is drawing to a close. Any complex thing, we can decompose it and use the basic knowledge we have learned to analyze it bit by bit: first reduce the dimension analysis, and then synthesize it. For example, the definition of container_of macros uses the storage of structures, sentence expressions, typeof and other knowledge points. Mastered these basic knowledge, with the analysis method, later in the kernel to encounter such a similar macro, no longer need Baidu, Google, what if can not be found? At such a critical moment to examine the technical ability of engineers, we can analyze ourselves confidently and calmly. This is your core competence and your opportunity to surpass other engineers and stand out.

This tutorial is adapted from the C language embedded Linux Advanced programming Video tutorial No. 05, the electronic version of the book can join the QQ group: 475504428 download, more embedded video tutorials, you can follow:

Official account of Wechat: Otaku tribe (armlinuxfun)

51CTO College-Mr. Wang Litao: http://edu.51cto.com/sd/d344f

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Servers

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report