queue(3)

QUEUE(3) Library Functions Manual QUEUE(3)

ИМЯ

SLIST_EMPTY, SLIST_ENTRY, SLIST_FIRST, SLIST_FOREACH, SLIST_HEAD, SLIST_HEAD_INITIALIZER, SLIST_INIT, SLIST_INSERT_AFTER, SLIST_INSERT_HEAD, SLIST_NEXT, SLIST_REMOVE_HEAD, SLIST_REMOVE, STAILQ_CONCAT, STAILQ_EMPTY, STAILQ_ENTRY, STAILQ_FIRST, STAILQ_FOREACH, STAILQ_HEAD, STAILQ_HEAD_INITIALIZER, STAILQ_INIT, STAILQ_INSERT_AFTER, STAILQ_INSERT_HEAD, STAILQ_INSERT_TAIL, STAILQ_NEXT, STAILQ_REMOVE_HEAD, STAILQ_REMOVE, LIST_EMPTY, LIST_ENTRY, LIST_FIRST, LIST_FOREACH, LIST_HEAD, LIST_HEAD_INITIALIZER, LIST_INIT, LIST_INSERT_AFTER, LIST_INSERT_BEFORE, LIST_INSERT_HEAD, LIST_NEXT, LIST_REMOVE, TAILQ_CONCAT, TAILQ_EMPTY, TAILQ_ENTRY, TAILQ_FIRST, TAILQ_FOREACH, TAILQ_FOREACH_REVERSE, TAILQ_HEAD, TAILQ_HEAD_INITIALIZER, TAILQ_INIT, TAILQ_INSERT_AFTER, TAILQ_INSERT_BEFORE, TAILQ_INSERT_HEAD, TAILQ_INSERT_TAIL, TAILQ_LAST, TAILQ_NEXT, TAILQ_PREV, TAILQ_REMOVE, TAILQ_SWAPреализации односвязных списков, односвязных хвостовых очередей, списков и хвостовых очередей

ОБЗОР

<sys/queue.h> SLIST_EMPTY(SLIST_HEAD *head) SLIST_ENTRY(TYPE) SLIST_FIRST(SLIST_HEAD *head) SLIST_FOREACH(TYPE *var, SLIST_HEAD *head, SLIST_ENTRY NAME) SLIST_HEAD(HEADNAME, TYPE) SLIST_HEAD_INITIALIZER(SLIST_HEAD head) SLIST_INIT(SLIST_HEAD *head) SLIST_INSERT_AFTER(TYPE *listelm, TYPE *elm, SLIST_ENTRY NAME) SLIST_INSERT_HEAD(SLIST_HEAD *head, TYPE *elm, SLIST_ENTRY NAME) SLIST_NEXT(TYPE *elm, SLIST_ENTRY NAME) SLIST_REMOVE_HEAD(SLIST_HEAD *head, SLIST_ENTRY NAME) SLIST_REMOVE(SLIST_HEAD *head, TYPE *elm, TYPE, SLIST_ENTRY NAME) STAILQ_CONCAT(STAILQ_HEAD *head1, STAILQ_HEAD *head2) STAILQ_EMPTY(STAILQ_HEAD *head) STAILQ_ENTRY(TYPE) STAILQ_FIRST(STAILQ_HEAD *head) STAILQ_FOREACH(TYPE *var, STAILQ_HEAD *head, STAILQ_ENTRY NAME) STAILQ_HEAD(HEADNAME, TYPE) STAILQ_HEAD_INITIALIZER(STAILQ_HEAD head) STAILQ_INIT(STAILQ_HEAD *head) STAILQ_INSERT_AFTER(STAILQ_HEAD *head, TYPE *listelm, TYPE *elm, STAILQ_ENTRY NAME) STAILQ_INSERT_HEAD(STAILQ_HEAD *head, TYPE *elm, STAILQ_ENTRY NAME) STAILQ_INSERT_TAIL(STAILQ_HEAD *head, TYPE *elm, STAILQ_ENTRY NAME) STAILQ_NEXT(TYPE *elm, STAILQ_ENTRY NAME) STAILQ_REMOVE_HEAD(STAILQ_HEAD *head, STAILQ_ENTRY NAME) STAILQ_REMOVE(STAILQ_HEAD *head, TYPE *elm, TYPE, STAILQ_ENTRY NAME) LIST_EMPTY(LIST_HEAD *head) LIST_ENTRY(TYPE) LIST_FIRST(LIST_HEAD *head) LIST_FOREACH(TYPE *var, LIST_HEAD *head, LIST_ENTRY NAME) LIST_HEAD(HEADNAME, TYPE) LIST_HEAD_INITIALIZER(LIST_HEAD head) LIST_INIT(LIST_HEAD *head) LIST_INSERT_AFTER(TYPE *listelm, TYPE *elm, LIST_ENTRY NAME) LIST_INSERT_BEFORE(TYPE *listelm, TYPE *elm, LIST_ENTRY NAME) LIST_INSERT_HEAD(LIST_HEAD *head, TYPE *elm, LIST_ENTRY NAME) LIST_NEXT(TYPE *elm, LIST_ENTRY NAME) LIST_REMOVE(TYPE *elm, LIST_ENTRY NAME) LIST_SWAP(LIST_HEAD *head1, LIST_HEAD *head2, TYPE, LIST_ENTRY NAME) TAILQ_CONCAT(TAILQ_HEAD *head1, TAILQ_HEAD *head2, TAILQ_ENTRY NAME) TAILQ_EMPTY(TAILQ_HEAD *head) TAILQ_ENTRY(TYPE) TAILQ_FIRST(TAILQ_HEAD *head) TAILQ_FOREACH(TYPE *var, TAILQ_HEAD *head, TAILQ_ENTRY NAME) TAILQ_FOREACH_REVERSE(TYPE *var, TAILQ_HEAD *head, HEADNAME, TAILQ_ENTRY NAME) TAILQ_HEAD(HEADNAME, TYPE) TAILQ_HEAD_INITIALIZER(TAILQ_HEAD head) TAILQ_INIT(TAILQ_HEAD *head) TAILQ_INSERT_AFTER(TAILQ_HEAD *head, TYPE *listelm, TYPE *elm, TAILQ_ENTRY NAME) TAILQ_INSERT_BEFORE(TYPE *listelm, TYPE *elm, TAILQ_ENTRY NAME) TAILQ_INSERT_HEAD(TAILQ_HEAD *head, TYPE *elm, TAILQ_ENTRY NAME) TAILQ_INSERT_TAIL(TAILQ_HEAD *head, TYPE *elm, TAILQ_ENTRY NAME) TAILQ_LAST(TAILQ_HEAD *head, HEADNAME) TAILQ_NEXT(TYPE *elm, TAILQ_ENTRY NAME) TAILQ_PREV(TYPE *elm, HEADNAME, TAILQ_ENTRY NAME) TAILQ_REMOVE(TAILQ_HEAD *head, TYPE *elm, TAILQ_ENTRY NAME) TAILQ_SWAP(TAILQ_HEAD *head1, TAILQ_HEAD *head2, TYPE, TAILQ_ENTRY NAME)

ОПИСАНИЕ

Данные макросы определяют и управляют четырьмя типами структур данных: односвязными списками, односвязными хвостовыми очередями, списками и хвостовыми очередями. Все четыре структуры поддерживают следующие возможности:

  1. Вставка нового элемента в начало списка.
  2. Вставка нового элемента после любого элемента списка.
  3. Удаление элемента из начала списка за время O(1).
  4. Обход списка из начала в конец.
  5. Перестановка содержимого двух списков.

Односвязные списки — самая простая из этих четырёх структур данных и поддерживает только указанные выше возможности. Односвязные списки идеальны для приложений с большими наборами данных, из которых производится очень мало удалений, или для реализации очереди LIFO (последним пришёл — первым ушёл). У односвязных списков есть дополнительное свойство:

  1. Удаление любого элемента списка за время O(n).

У односвязных хвостовых очередей есть дополнительные свойства:

  1. Добавление элементов в конец списка.
  2. Удаление любого элемента списка за время O(n).
  3. Возможность объединения.

Однако:

  1. При вставке элементов нужно указывать начало списка.
  2. Каждый начальный элемент требует двух указателей вместо одного.
  3. Код примерно на 15% больше и на 20% медленнее, чем для односвязных списков.

Односвязные хвостовые очереди идеальны для приложений с большими наборами данных из которых производится очень мало удалений, или для реализации очереди FIFO (первым пришёл — первым ушёл).

Все двусвязные типы структур данных (списки и хвостовые очереди) дополнительно позволяют:

  1. Вставку нового элемента перед любым элементом списка.
  2. Удаление любого элемента списка за время O(1).

Однако:

  1. Для каждого элемента требуется два указателя вместо одного.
  2. Размер кода и время выполнения операций (кроме удаления) удваивается, по сравнению с односвязными структурами данных.

Связные списки — самая простая из двусвязных структур данных. К указанным выше возможностям для них возможно:

  1. Обход в обратном направлении.

Однако:

  1. Для обхода в обратном направлении требуется указывать начало обхода и сам список.

У хвостовых очередей есть дополнительные свойства:

  1. Добавление элементов в конец списка.
  2. Обход может идти в обратном направлении, от конца к началу.
  3. Возможность объединения.

Однако:

  1. При вставке и удалении элементов нужно указывать начало списка.
  2. Каждый начальный элемент требует двух указателей вместо одного.
  3. Код примерно на 15% больше и на 20% медленнее, чем для односвязных списков.

В определениях макросов TYPE — это имя определяемое пользователем структуры, которая должна содержать поле типа SLIST_ENTRY, STAILQ_ENTRY, LIST_ENTRY или TAILQ_ENTRY, названное NAME. Аргумент HEADNAME — это имя определяемое пользователем структуры, которая должна быть объявлена с помощью макроса SLIST_HEAD, STAILQ_HEAD, LIST_HEAD или TAILQ_HEAD. Ниже показаны примеры использования этих макросов.

Односвязные списки

Односвязный список начинается со структуры, определённой макросом SLIST_HEAD. В этой структуре содержится одиночный указатель на первый элемент списка. Элементы имеют по одной связи для минимизации занимаемого пространства, а дополнительный расход на операции с указателями равен O(n) при удалении произвольного элемента. Новые элементы можно добавлять в список после существующего элемента или в начало списка. Структура SLIST_HEAD объявляется следующим образом:

SLIST_HEAD(HEADNAME, TYPE) head;

где HEADNAME — имя определяемой структуры, а TYPE — тип элементов, объединяемых в список. Указатель на начало списка может в дальнейшем объявляться так:

struct HEADNAME *headp;

(Имена head и headp могут выбираться пользователем.)

Макрос SLIST_HEAD_INITIALIZER запускает инициализатор для head списка.

Макрос SLIST_EMPTY возвращает true, если в списке нет элементов.

Макрос SLIST_ENTRY объявляет структуру, которая добавляет элементы в список.

Макрос SLIST_FIRST возвращает первый элемент списка или NULL, если список пуст.

Макрос SLIST_FOREACH обходит список, на который ссылается head, от начало в конец, назначая var каждый элемент.

Макрос SLIST_INIT инициализирует список, на который ссылается head.

Макрос SLIST_INSERT_HEAD вставляет новый элемент elm в начало списка.

Макрос SLIST_INSERT_AFTER вставляет новый элемент elm за элементом listelm.

Макрос SLIST_NEXT возвращает следующий элемент списка.

Макрос SLIST_REMOVE_HEAD удаляет элемент elm из начала списка. В целях эффективности удаления элемента из начала списка нужно использовать именно этот макрос вместо обычного SLIST_REMOVE.

Макрос SLIST_REMOVE удаляет элемент elm из списка.

Пример односвязного списка

SLIST_HEAD(slisthead, entry) head =
    SLIST_HEAD_INITIALIZER(head);
struct slisthead *headp;		/* начало односвязного
                                           списка */
struct entry {
	...
	SLIST_ENTRY(entry) entries;	/* односвязный список */
	...
} *n1, *n2, *n3, *np;

SLIST_INIT(&head);			/* инициализация списка */

n1 = malloc(sizeof(struct entry));	/* вставка начального элемента */
SLIST_INSERT_HEAD(&head, n1, entries);

n2 = malloc(sizeof(struct entry));	/* вставка последующих */
SLIST_INSERT_AFTER(n1, n2, entries);

SLIST_REMOVE(&head, n2, entry, entries);/* удаление */
free(n2);

n3 = SLIST_FIRST(&head);
SLIST_REMOVE_HEAD(&head, entries);	/* удаление начального элемента */
free(n3);
					/* обход из начала в конец */
SLIST_FOREACH(np, &head, entries)
	np-> ...

while (!SLIST_EMPTY(&head)) {		/* удаление списка */
	n1 = SLIST_FIRST(&head);
	SLIST_REMOVE_HEAD(&head, entries);
	free(n1);
}

Односвязные хвостовые очереди

Односвязная хвостовая очередь начинается со структуры, определяемой макросом STAILQ_HEAD. В этой структуре содержится пара указателей, один на первый элемент хвостовой очереди, а другой на последний элемент. Элементы имеют по одной связи для минимизации занимаемого пространства, а дополнительный расход на операции с указателями равен O(n) при удалении произвольного элемента. Новые элементы можно добавлять в хвостовую очередь после существующего элемента, в начало или конец хвостовой очереди, Структура STAILQ_HEAD объявляется следующим образом:

STAILQ_HEAD(HEADNAME, TYPE) head;

где HEADNAME — имя определяемой структуры, а TYPE — тип связанных элементов в хвостовой очереди. Указатель на начало хвостовой очереди может в дальнейшем объявляться так:

struct HEADNAME *headp;

(Имена head и headp могут выбираться пользователем.)

Макрос STAILQ_HEAD_INITIALIZER запускает инициализатор для head хвостовой очереди.

Макрос STAILQ_CONCAT добавляет хвостовую очередь с началом head2 в конец очереди с началом head1, удаляя все элементы из первой.

Макрос STAILQ_EMPTY возвращает true, если в хвостовой очереди нет элементов.

Макрос STAILQ_ENTRY объявляет структуру, которая подключает элементы в хвостовую очередь.

Макрос STAILQ_FIRST возвращает первый элемент из хвостовой очереди или NULL, если очередь пуста.

Макрос STAILQ_FOREACH обходит хвостовую очередь, на которую ссылается head, из начала в конец, назначая var каждый элемент.

Макрос STAILQ_INIT инициализирует хвостовую очередь, на которую ссылается head.

Макрос STAILQ_INSERT_HEAD вставляет новый элемент I elm в начало хвостовой очереди.

Макрос STAILQ_INSERT_TAIL вставляет новый элемент elm в конец хвостовой очереди.

Макрос STAILQ_INSERT_AFTER вставляет новый элемент elm за элементом listelm.

Макрос STAILQ_NEXT возвращает следующий элемент из хвостовой очереди или NULL, если элемент последний.

Макрос STAILQ_REMOVE_HEAD удаляет элемент из начала хвостовой очереди. В целях эффективности удаления элемента из начала хвостовой очереди нужно использовать именно этот макрос вместо обычного STAILQ_REMOVE.

Макрос STAILQ_REMOVE удаляет элемент elm из хвостовой очереди.

Пример односвязной хвостовой очереди

STAILQ_HEAD(stailhead, entry) head =
    STAILQ_HEAD_INITIALIZER(head);
struct stailhead *headp;		/* начало односвязной хвостовой
                               очереди */
struct entry {
	...
	STAILQ_ENTRY(entry) entries;	/* хвостовая очередь */
	...
} *n1, *n2, *n3, *np;

STAILQ_INIT(&head);			/* инициализация очереди */

n1 = malloc(sizeof(struct entry));	/* вставка начального элемента */
STAILQ_INSERT_HEAD(&head, n1, entries);

n1 = malloc(sizeof(struct entry));	/* вставка в очередь */
STAILQ_INSERT_TAIL(&head, n1, entries);

n2 = malloc(sizeof(struct entry));	/* вставка последующего */
STAILQ_INSERT_AFTER(&head, n1, n2, entries);
					/* удаление */
STAILQ_REMOVE(&head, n2, entry, entries);
free(n2);
					/* удаление из начала */
n3 = STAILQ_FIRST(&head);
STAILQ_REMOVE_HEAD(&head, entries);
free(n3);
					/* обход от начала в конец */
STAILQ_FOREACH(np, &head, entries)
	np-> ...
					/* удаление TailQ */
while (!STAILQ_EMPTY(&head)) {
	n1 = STAILQ_FIRST(&head);
	STAILQ_REMOVE_HEAD(&head, entries);
	free(n1);
}
					/* быстрое удаление TailQ */
n1 = STAILQ_FIRST(&head);
while (n1 != NULL) {
	n2 = STAILQ_NEXT(n1, entries);
	free(n1);
	n1 = n2;
}
STAILQ_INIT(&head);

Списки

Список начинается структурой, определённой макросом LIST_HEAD. Эта структура содержит единственный указатель на первый элемент списка. Элементы дважды связаны, поэтому произвольный элемент можно удалить без прохода по всему списку. Новые элементы могут быть добавлены в список перед или после существующего элемента, а также в начало списка. Структура LIST_HEAD объявляется следующим образом:

LIST_HEAD(HEADNAME, TYPE) head;

где HEADNAME — имя определяемой структуры, а TYPE — тип элементов, объединяемых в список. Указатель на начало списка может в дальнейшем объявляться так:

struct HEADNAME *headp;

(Имена head и headp могут выбираться пользователем.)

Макрос LIST_HEAD_INITIALIZER запускает инициализатор для head списка.

Макрос LIST_EMPTY возвращает true, если в списке нет элементов.

Макрос LIST_ENTRY объявляет структуру, которая добавляет элементы в список.

Макрос LIST_FIRST возвращает первый элемент списка или NULL, если список пуст.

Макрос LIST_FOREACH обходит список, на который ссылается head, от начало в конец, назначая var каждый элемент.

Макрос LIST_INIT инициализирует список, на который ссылается head.

Макрос LIST_INSERT_HEAD вставляет новый элемент elm в начало списка.

Макрос LIST_INSERT_AFTER вставляет новый элемент elm за элементом listelm.

Макрос LIST_INSERT_AFTER вставляет новый элемент elm перед элементом listelm.

Макрос LIST_NEXT возвращает следующий элемент списка или NULL, если элемент последний.

Макрос LIST_REMOVE удаляет элемент elm из списка.

Пример списка

LIST_HEAD(listhead, entry) head =
    LIST_HEAD_INITIALIZER(head);
struct listhead *headp;			/* начало списка */
struct entry {
	...
	LIST_ENTRY(entry) entries;	/* список */
	...
} *n1, *n2, *n3, *np, *np_temp;

LIST_INIT(&head);			/* инициализация списка */

n1 = malloc(sizeof(struct entry));	/* вставка в начало */
LIST_INSERT_HEAD(&head, n1, entries);

n2 = malloc(sizeof(struct entry));	/* вставка последующего */
LIST_INSERT_AFTER(n1, n2, entries);

n3 = malloc(sizeof(struct entry));	/* вставка перед */
LIST_INSERT_BEFORE(n2, n3, entries);

LIST_REMOVE(n2, entries);		/* удаление */
free(n2);
					/* обход из начала в конец */
LIST_FOREACH(np, &head, entries)
	np-> ...

while (!LIST_EMPTY(&head)) {		/* удаление списка */
	n1 = LIST_FIRST(&head);
	LIST_REMOVE(n1, entries);
	free(n1);
}

n1 = LIST_FIRST(&head);			/* быстрое удаление списка */
while (n1 != NULL) {
	n2 = LIST_NEXT(n1, entries);
	free(n1);
	n1 = n2;
}
LIST_INIT(&head);

Хвостовые очереди

Хвостовая очередь начинается со структуры, определяемой макросом TAILQ_HEAD. Эта структура содержит пару указателей, один для первого элемента хвостовой очереди, а другой для последнего элемента хвостовой очереди. Элементы связаны дважды так, что любой элемент может быть удалён без прохождения по всей очереди. Новые элементы могут быть добавлены в хвостовую очередь перед и после существующего элемента, в конец или в начало очереди. Структура TAILQ_HEAD объявляется следующим образом:

TAILQ_HEAD(HEADNAME, TYPE) head;

где HEADNAME — имя определяемой структуры, а TYPE — тип связанных элементов в хвостовой очереди. Указатель на начало хвостовой очереди может в дальнейшем объявляться так:

struct HEADNAME *headp;

(Имена head и headp могут выбираться пользователем.)

Макрос TAILQ_HEAD_INITIALIZER запускает инициализатор для head хвостовой очереди.

Макрос TAILQ_CONCAT добавляет хвостовую очередь с началом head2 в конец очереди с началом head1, удаляя все элементы из первой.

Макрос TAILQ_EMPTY возвращает true, если в хвостовой очереди нет элементов.

Макрос TAILQ_ENTRY объявляет структуру, которая подключает элементы в хвостовую очередь.

Макрос TAILQ_FIRST возвращает первый элемент из хвостовой очереди или NULL, если очередь пуста.

Макрос TAILQ_FOREACH обходит хвостовую очередь, на которую ссылается head, из начала в конец, назначая var каждый элемент. Значение var равно NULL, если пройдена вся очередь или в ней нет элементов.

Макрос TAILQ_FOREACH_REVERSE обходит хвостовую очередь, на которую ссылается head, в обратном направлении, назначая var каждый элемент.

Макрос TAILQ_INIT инициализирует хвостовую очередь, на которую ссылается head.

Макрос TAILQ_INSERT_HEAD вставляет новый элемент I elm в начало хвостовой очереди.

Макрос TAILQ_INSERT_TAIL вставляет новый элемент elm в конец хвостовой очереди.

Макрос TAILQ_INSERT_AFTER вставляет новый элемент elm за элементом listelm.

Макрос TAILQ_INSERT_BEFORE вставляет новый элемент elm перед элементом listelm.

Макрос TAILQ_LAST возвращает последний элемент из хвостовой очереди или NULL, если очередь пуста.

Макрос TAILQ_NEXT возвращает следующий элемент из хвостовой очереди или NULL, если элемент последний.

Макрос TAILQ_PREV возвращает предыдущий элемент из хвостовой очереди или NULL, если элемент первый.

Макрос TAILQ_REMOVE удаляет элемент elm из хвостовой очереди.

Макрос TAILQ_SWAP меняет местами содержимое head1 и head2.

Пример хвостовой очереди

TAILQ_HEAD(tailhead, entry) head =
    TAILQ_HEAD_INITIALIZER(head);
struct tailhead *headp;			/* начало хвостовой очереди */
struct entry {
	...
	TAILQ_ENTRY(entry) entries;	/* хвостовая очередь */
	...
} *n1, *n2, *n3, *np;

TAILQ_INIT(&head);			/* инициализация очереди */

n1 = malloc(sizeof(struct entry));	/* вставка в начало */
TAILQ_INSERT_HEAD(&head, n1, entries);

n1 = malloc(sizeof(struct entry));	/* вставка в конец */
TAILQ_INSERT_TAIL(&head, n1, entries);

n2 = malloc(sizeof(struct entry));	/* вставка последующего */
TAILQ_INSERT_AFTER(&head, n1, n2, entries);

n3 = malloc(sizeof(struct entry));	/* вставка перед */
TAILQ_INSERT_BEFORE(n2, n3, entries);

TAILQ_REMOVE(&head, n2, entries);	/* удаление */
free(n2);
					/* обход из начало в конец */
TAILQ_FOREACH(np, &head, entries)
	np-> ...
					/* обход в обратном направлении */
TAILQ_FOREACH_REVERSE(np, &head, tailhead, entries)
	np-> ...
					/* удаление TailQ */
while (!TAILQ_EMPTY(&head)) {
	n1 = TAILQ_FIRST(&head);
	TAILQ_REMOVE(&head, n1, entries);
	free(n1);
}
					/* быстрое удаление TailQ */
n1 = TAILQ_FIRST(&head);
while (n1 != NULL) {
	n2 = TAILQ_NEXT(n1, entries);
	free(n1);
	n1 = n2;
}

TAILQ_INIT(&head);
n2 = malloc(sizeof(struct entry));  /* вставка перед */
CIRCLEQ_INSERT_BEFORE(&head, n1, n2, entries);
                                    /* обход из начала в конец */
for (np = head.cqh_first; np != (void *)&head;
        np = np->entries.cqe_next)
    np-> ...
                                    /* обход в обратном направлении */
for (np = head.cqh_last; np != (void *)&head; np = np->entries.cqe_prev)
    np-> ...
                                    /* удаление */
while (head.cqh_first != (void *)&head)
    CIRCLEQ_REMOVE(&head, head.cqh_first, entries);

СООТВЕТСТВИЕ СТАНДАРТАМ

Нет в POSIX.1, POSIX.1-2001 и POSIX.1-2008. Присутствует в BSD. Функции queue впервые появились в 4.4BSD.

СМОТРИТЕ ТАКЖЕ

insque(3)

7 февраля 2015 г. Linux 6.12.85-6.12-alt1