Part of the WritingPortableDrivers section

The gcc compiler typically aligns individual fields of a structure on whatever byte boundary it likes in order to provide faster execution. For example, consider this code and resulting output:

   1 #include <stdio.h>
   2 #include <stddef.h>
   4 struct foo {
   5         char  a;
   6         short b;
   7         int   c;
   8 };
  10 #define OFFSET_A  offsetof(struct foo, a)
  11 #define OFFSET_B  offsetof(struct foo, b)
  12 #define OFFSET_C  offsetof(struct foo, c)
  14 int main ()
  15 {
  16         printf ("offset A = %d\n", OFFSET_A);
  17         printf ("offset B = %d\n", OFFSET_B);
  18         printf ("offset C = %d\n", OFFSET_C);
  19         return 0;
  20 }

Running the program gives:

offset A = 0
offset B = 2
offset C = 4

The output shows that the compiler aligned fields b and c in struct foo on even byte boundaries. This is not a good thing when we want to overlay a structure on top of a memory location. Typically driver data structures do not have even byte padding for the individual fields. Because of this, the gcc attribute ((packed)) is used to tell the compiler not to place any "memory holes" within a structure.

If we change the struct foo structure to use the packed attribute like this:

   1 struct foo {
   2         char    a;
   3         short   b;
   4         int     c;
   5 } __attribute__((packed));

Then the output of the program changes to:

offset A = 0
offset B = 1
offset C = 3

Now there are no more memory holes in the structure.

This packed attribute can be used to pack an entire structure, as shown above, or it can be used only to pack a number of specific fields within a structure.

For example, the struct usb_ctrlrequest is defined in include/usb.h as the following:

   1 struct usb_ctrlrequest {
   2         __u8 bRequestType;
   3         __u8 bRequest;
   4         __le16 wValue;
   5         __le16 wIndex;
   6         __le16 wLength;
   7 } __attribute__ ((packed));

This ensures that the entire structure is packed, so that it can be used to write data directly to a USB connection.

But the definition of the struct usb_endpoint_descriptor looks like:

   1 struct usb_endpoint_descriptor {
   2         __u8   bLength           __attribute__((packed));
   3         __u8   bDescriptorType   __attribute__((packed));
   4         __u8   bEndpointAddress  __attribute__((packed));
   5         __u8   bmAttributes      __attribute__((packed));
   6         __le16 wMaxPacketSize    __attribute__((packed));
   7         __u8   bInterval         __attribute__((packed));
   8         __u8   bRefresh          __attribute__((packed));
   9         __u8   bSynchAddress     __attribute__((packed));
  10         unsigned char *extra;   /* Extra descriptors */
  11         int extralen;
  12 };

This ensures that the first part of the structure is packed and can be used to read directly from a USB connection, but the extra and extralen fields of the structure can be aligned to whatever the compiler thinks will be fastest to access.

KernelNewbies: DataAlignment (last edited 2017-12-30 01:30:30 by localhost)