Подтвердить что ты не робот

Ядро Linux: почему структуры "подкласса" помещают информацию базового класса в конец?

Я читал главу в Beautiful Code в ядре Linux, и автор обсуждает, как ядро ​​Linux реализует наследование на языке C (среди другие темы). В двух словах определена "базовая" структура, и для того, чтобы наследовать от нее, структура "подкласса" помещает копию базы в конце определения структуры подкласса. Затем автор проводит пару страниц, объясняя умный и сложный макрос, чтобы выяснить, сколько байтов назад, чтобы преобразовать из базовой части объекта в часть подкласса объекта.

Мой вопрос: В структуре подкласса, почему бы не объявить базовую структуру как первую вещь в структуре, а не последнюю вещь?

Основным преимуществом размещения базового элемента структуры в первую очередь является приведение от базы к подклассу, в котором вам не нужно будет перемещать указатель вообще - по существу, выполнение акта просто означает указание компилятору позволить вашему коду использовать "дополнительные" поля, которые структура подкласса разместила после материала, определяемого базой.

Просто, чтобы немного разъяснить мой вопрос, позвольте мне выделить код:

struct device { // this is the 'base class' struct
     int a;
     int b;
     //etc
}
struct usb_device { // this is the 'subclass' struct
    int usb_a;
    int usb_b;
    struct device dev; // This is what confuses me - 
                       // why put this here, rather than before usb_a?
}

Если у человека есть указатель на поле "dev" внутри объекта usb_device, то для того, чтобы вернуть его обратно к этому объекту usb_device, нужно вычесть 8 из этого указателя. Но если "dev" было первым делом в использовании usb_device, то указателю вообще не нужно было бы перемещать указатель.

Любая помощь по этому поводу будет очень признательна. Даже совет о том, где найти ответ, будет оценен по достоинству - я не совсем уверен, как Google за архитектурную причину такого решения. Самый близкий, который я мог найти здесь, в StackOverflow: зачем использовать эту странную структуру вложенности

И, чтобы быть понятным - я понимаю, что многие яркие люди долгое время работали над ядром Linux, так что у него есть веская причина для этого, я просто не могу понять, что это такое.

4b9b3361

Ответ 1

Amiga OS использует этот трюк "общего заголовка" во многих местах, и в то время это выглядело неплохо: подклассирование путем простого ввода типа указателя. Но есть недостатки.

Pro:

  • Вы можете расширить существующие структуры данных
  • Вы можете использовать один и тот же указатель во всех местах, где ожидается базовый тип, не требуется арифметика указателя, экономя драгоценные циклы
  • Он чувствует себя естественно

Con:

  • Различные компиляторы имеют тенденцию выравнивать структуры данных по-разному. Если базовая структура закончилась с char a;, тогда у вас может быть 0, 1 или 3 байта байт после того, как начнется следующее поле подкласса. Это привело к довольно неприятным ошибкам, особенно когда вам приходилось поддерживать обратную совместимость (то есть по какой-то причине у вас должно быть определенное дополнение, потому что у древней версии компилятора была ошибка, и теперь есть много кода, который ожидает отладку багги).
  • Вы не замечаете быстро, когда проходите неправильную структуру. С кодом в вашем вопросе, поля получаются сбой очень быстро, если арифметика указателя неверна. Это хорошо, так как это повышает вероятность того, что ошибка будет обнаружена более рано.
  • Это приводит к тому, что "мой компилятор исправит это для меня" (которого это иногда не будет), и все приведения приводят к "я знаю лучше, чем компилятор". В последнем случае вы автоматически вставляете броски, прежде чем понимать сообщение об ошибке, что приведет к возникновению всех нечетных проблем.

Ядро Linux ставит общую структуру в другом месте; это может быть, но не обязательно должно быть в конце.

Pro:

  • Ошибки покажутся ранним
  • Вам нужно будет сделать некоторую арифметику указателя для каждой структуры, поэтому вы привыкли к ней.
  • Вам не нужны роли

Con:

  • Не очевидно
  • Код более сложный

Ответ 2

Я новичок в коде ядра Linux, поэтому возьмите мои промахи здесь с солью. Насколько я могу судить, нет требования относительно того, где разместить структуру "подкласса" . Это именно то, что предоставляют макросы: вы можете использовать структуру "подкласса" независимо от ее компоновки. Это обеспечивает надежность вашего кода (макет структуры можно изменить, не изменяя свой код. Возможно, существует соглашение о размещении структуры "базового класса" в конце, но я не знаю об этом. Я видел много кода в драйверах, где различные структуры "базового класса" используются для возврата к той же структуре "подкласса" (из разных полей в "подклассе", конечно).

Ответ 3

У меня нет нового опыта от ядра Linux, но из других ядер. Я бы сказал, что это не имеет значения.

Вы не должны отбрасывать от одного к другому. Разрешить подобные броски следует выполнять только в очень специфических ситуациях. В большинстве случаев это снижает надежность и гибкость кода и считается довольно неряшливым. Таким образом, самая глубокая "архитектурная причина", которую вы ищете, может быть просто "потому, что в том порядке, в котором кто-то случайно написал ее". Или, альтернативно, то, что показали показанные тесты, было бы лучшим для производительности некоторого важного кода в этом коде. Или, наоборот, человек, который его написал, думает, что он выглядит красиво (я всегда создаю перевернутые пирамиды в своих объявлениях и структурах переменных, если у меня нет других ограничений). Или кто-то написал это так 20 лет назад, и с тех пор все остальные копировали его.

За этим может быть какой-то более глубокий дизайн, но я сомневаюсь. Там просто нет причин для разработки этих вещей вообще. Если вы хотите узнать из авторитетного источника, почему это так, просто отправьте патч в linux, который изменит его и посмотрит, кто кричит на вас.

Ответ 4

Это для множественного наследования. struct dev - это не единственный интерфейс, который вы можете применить к структуре в ядре linux, и если у вас более одного, просто отбрасывание подкласса в базовый класс не будет работать. Например:

struct device {
     int a;
     int b;
     // etc...
};

struct asdf {
   int asdf_a;
};

struct usb_device {
    int usb_a;
    int usb_b;
    struct device dev;
    struct asdf asdf;
};