Введение:
ядро linux написано преимущественно на языке С. В отличие от многих других языков программирования С не имеет хороших встроенных средств для работы с коллекциями структурированных данных. Такие средства отсутствуют и в стандартной библиотеке С. Т.о., Вы, вероятно будете рады узнать, что можно позаимствовать хорошую реализацию циклического двусвязного списка, написанную на языке С из кода ядра linux.
Ясная, лёгкая в использовании реализация циклического двусвязного списка на С находится в файле include/linux/list.h. Код этой реализации эффективен и переносим - в противном случае он не был бы в ядре. Если при программировании ядра необходим список для работы с множеством данных любой структуры, то используют двусвязные списки ядра linux. Путём минимальной правки (удаление аппаратно-зависимого ускорения предвыборки записей списка) списки могут быть адаптированы к использованию в приложениях пользовательского режима. Здесь находится версия файла, пригодная для такого использования.
Преимущества использования таких списков следующие:
- Независимость от типа:
Вы можете использовать любую структуру данных, какую заблагорассудится. - Переносимость:
Хотя я не пробовал использовать списки на всех платформах, но можно предположить, что данная реализация весьма переносима. В противногм случае она бы не попала в дерево исходного кода ядра. - Простота использования:
В силу независимости списков от типа данных записей для инициализации, доступа к элементам списка, прохождения по списку используются одни и те же функции. - Простота восприятия:
Макросы и подставляемые (inline) функции делают код очень элегантным и простым для понимания. - Экономия времени:
Вам не нужно изобретать колесо. Использование списков позволяет существенно сэкономить время на отладку и повторное создание списков для каждой структуры данных, которая используется в программе.
struct my_list { void *myitem; struct my_list *next; struct my_list *prev; };Реализации списков в ядре создаёт иллюзию, что это данные содержат в себе список! Например, если у Вас есть список данных со структурой struct my_cool_list, то список будет выглядеть следующим образом:
struct my_cool_list { struct list_head list; /* kernel's list structure */ int my_cool_data; void* my_cool_void; };Следует отметить следующее:
- Список содержится в структурах, которые необходимо связать.
- Структуру struct list_head можно поместить везде в объявлении Вашей структуры.
- struct list_head может иметь любое имя.
- У вас в Вашей структуре может быть несколько полей типа struct list_head!
struct todo_tasks{ char *task_name; unsigned int name_len; short int status; int sub_tasks; int subtasks_completed; struct list_head completed_subtasks; /* структура списка */ int subtasks_waiting; struct list_head waiting_subtasks; /* ещё один список таких же или других элементов! */ struct list_head todo_list; /* список дел - todo_tasks */ };Вот несколько примеров использования списокв в самом ядре: Раз уж мы вплотную подошли к спискам, то посмотрим на объявление типа struct list_head в include/linux/list.h:
struct list_head{ struct list_head *next; struct list_head *prev; };Пожалуй, самое время перейти к деталям. Для начала, давайте посмотрим, как можно использовать этот тип в нашей программе, а затем рассмотрим, как эта структура работает.
Использование списка:
Думаю, лучший способ ближе познакомиться с функциями для работы со списками, это посмотреть на содержимое файла исходного кода. Код хорошо откомментирован и его понимание не должно вызвать трудностей.
Ниже приведён пример создания, добавления, удаления и прохождения списка.
#include <stdio.h>
#include <stdlib.h>
#include "list.h"
struct kool_list{
int to;
struct list_head list;
int from;
};
int main(int argc, char **argv)
{
struct kool_list *tmp;
struct list_head *pos, *q;
unsigned int i;
struct kool_list mylist;
INIT_LIST_HEAD(&mylist.list);
/* вместо строки выше можно было бы использовать макрос
* LIST_HEAD(mylist) при объявлении списка; данный макрос объявляет
* переменную типа struct list_head с указанным именем и инициализирует её.
*/
/* добавление элементов в mylist */
for (i = 5; i != 0; --i) {
tmp = (struct kool_list *)malloc (sizeof (struct kool_list));
/* INIT_LIST_HEAD(&tmp->list);
*
* здесь производится инициализация поля list динамически созданной структуры типа struct
* kool_list. Эту инициализацию можно пропустить, если следуим будет вызов add_list() или
* любая другая функция подобного рода, которая выполняет инициализацию полей next, prev
*/
printf ("enter to and from:");
scanf ("%d %d", &tmp->to, &tmp->from);
/* добавить новый элемент 'tmp' в список элементов mylist */
list_add(&(tmp->list), &(mylist.list));
/* можно также использовать list_add_tail(), которая добавляет новые элементы
* в хвост списка
*/
}
printf ("\n");
/* теперь у нас есть циклически связанный список элементов типа struct kool_list.
* теперь распечатаем весь список
*/
/* list_for_each() - это макрос, реализующий цикл прохождения по элементам списка.
* первый аргумент макроса используется как счётчик, или иными словами в цикле он
* используется для указания на поле типа list_head текущего элемента списка.
* второй аргумент - указатель на список. Макрос не манипулирует этим указателем.
*/
printf ("traversing the list using list_for_each()\n");
list_for_each(pos, &mylist.list) {
/* в этой строке: pos->next указывает на поле 'list' следующего элемента списка, а
* pos->prev - на поле 'list' предыдущего элемента. Здесь элемент - это запись типа
* struct kool_list. Но нам нужен сам элемент, а не его поле 'list'! list_entry() позволяет
* получить указатель на сам элемент. Как это делается, см. ниже в части "Как
* это работает?".
*/
tmp = list_entry (pos, struct kool_list, list);
/* в качестве аргументов макрос принимает указатель на struct list_head, в котором
* хранится текущая позиция списка, второй аргумент - тип пользовательской структуры,
* членом которой является поле, на которое указывает, первый аргумент и третий
* аргумент - имя этого поля (член типа struct list_head в пользовательской структуре).
* Макрос возвращает указатель на структуру, членом которой является первый аргумент.
* Или точнее говоря, на который указывает первый аргумент - pos.
* Например, выше list_entry() возвратит указатель на элемент типа struct kool_list,
* частью которого является pos
*/
printf("to = %d from= %d\n", tmp->to, tmp->from);
}
printf ("\n");
/* т.к. наш список цикличен, то его можно обойти и в обратном порядке.
* всё, что для этого надо - заменить макрос 'list_for_each' на 'list_for_each_prev',
* а всё остальное останется без изменений.
*
* Также можно использовать list_for_each_entry(), чтобы пройти по элементам списка
* данного типа. Например:
*/
printf ("traversing the list using list_for_each_entry()\n");
list_for_each_entry(tmp, &mylist.list, list)
printf ("to = %d from = %d\n", tmp->to, tmp->from);
/* Как видно, здесь мы не используем макрос list_entry(), чтобы получить указатель
* на элемент списка типа struct kool_list. Этот указатель помещается в переменную tmp
* на "полуавтомате" самим макросом list_for_each_entry() :)
*/
printf ("\n");
/* теперь будем вежливыми и освободим память, захваченную под записи kool_list items.
* т.к. мы будем удалять записи из списка с помощью list_del(), нам нужна защищённая
* версия макроса list_for_each().
* такая версия названа list_for_each_safe(). Этот макрос НЕОБХОДИМО использовать
* для организации цикла, который предусматривает удаление или перемещение записей
* из одного списка в другой.
*/
printf ("deleting the list using list_for_each_safe()\n");
list_for_each_safe(pos, q, &mylist.list){
tmp = list_entry(pos, struct kool_list, list);
printf ("freeing item to = %d from = %d\n", tmp->to, tmp->from);
list_del(pos);
free (tmp);
}
return 0;
}
Как это работает?
В общем, большая часть реализации довольна тривиальна, но тонко она выполнена. Тонкость заключается в том, чтобы получить адрес целой структуры по адресу члена, являющегося частью этой структуры (т.е., получить указатель на структуру, содержащую в себе элемент типа struct list_head). Этот трюк проделывает макрос list_entry(), как мы увидели ранее. Теперь попытаемся понять, как он это делает.
#define list_entry(ptr, type, member) \
((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))
Если опираться на ранее приведённый пример, то макрос будет развёрнут следующим образом:
((struct kool_list *)((char *)(pos) - (unsigned long)(&((struct kool_list *)0)->list)))
Это смущает многих, однако приём достаточно прост и является широко известным (см. вопрос 2.14 - англ.). Используя указатель на поле типа struct list_head в структуре данных, макрос list_entry() просто высчитывает адрес самой структуры. Чтобы выполнить такой расчёт, необходимо выяснить, где в структуре данных находится член типа list_head (смещение list_head). Затем просто вычисляется смещение члена типа list_head по отношению к указателю, который был передан макросу в качестве первого аргумента.
Следующий вопрос, как нам вычислить смещение элемента внутри структуры данных? Допустим, у нас есть структура struct foo_bar и нам надо найти смещение элемента boo, который является членом структуры. Сделаем это следующим образом:
(unsigned long)(&((struct foo_bar *)0)->boo)
Возьмём нулевой адрес памяти и приведём его к указателю на нашу структуру - в данной случае, к указателю на struct foo_bar. Затем возьмём адрес поля, которое нас интересует. Это и будет смещение данного поля внутри структуры. Так как мы узнали абсолютное положение данного элемента в памяти, мы теперь можем с помощью этого элемента получить указатель на конкретный экземпляр целой структуры, опираясь на некоторые данные (например, аргумент pos в случае с макросом list_entry). Вот и всё. Чтобы лучше понять, что происходит, советую вам погонять этот код.
#include <stdio.h>
#include <stdlib.h>
struct foobar {
unsigned int foo;
char bar;
char boo;
};
int main (int argc, char** argv)
{
struct foobar tmp;
printf ("address of &tmp is= %p\n\n", &tmp);
printf ("address of tmp->foo= %p \t offset of tmp->foo= %lu\n", &tmp.foo,
(unsigned long) &((struct foobar *)0)->foo);
printf ("address of tmp->bar= %p \t offset of tmp->bar= %lu\n", &tmp.bar,
(unsigned long) &((struct foobar *)0)->bar);
printf ("address of tmp->boo= %p \t offset of tmp->boo= %lu\n\n", &tmp.boo,
(unsigned long) &((struct foobar *)0)->boo);
printf ("computed address of &tmp using:\n");
printf ("\taddress and offset of tmp->foo= %p\n",
(struct foobar *) (((char *) &tmp.foo) - ((unsigned long) &((struct foobar *)0)->foo)));
printf ("\taddress and offset of tmp->bar= %p\n",
(struct foobar *) (((char *) &tmp.bar) - ((unsigned long) &((struct foobar *)0)->bar)));
printf ("\taddress and offset of tmp->boo= %p\n",
(struct foobar *) (((char *) &tmp.boo) - ((unsigned long) &((struct foobar *)0)->boo)));
return 0;
}
Вывод приведённого куска кода:
address of &tmp is = 0xbfffed00
address of tmp->foo = 0xbfffed00 offset of tmp->foo= 0
address of tmp->bar = 0xbfffed04 offset of tmp->bar= 4
address of tmp->boo = 0xbfffed05 offset of tmp->boo= 5
computed address of &tmp using:
address and offset of tmp->foo = 0xbfffed00
address and offset of tmp->bar = 0xbfffed00
address and offset of tmp->boo = 0xbfffed00
См. также:
- Код хэш-таблиц, организованных в виде списков.
- Ещё код /sutff/src/
Оригинал статьи на английском лежит здесь.
No comments:
Post a Comment