вторник, 23 ноября 2010 г.

Трюк со стековой памятью

Интересный способ оптимизации я недавно обнаружил изучая реализацию системного вызова poll в ядре Linux. Этому системному вызову передаётся массив структур pollfd. Для работы с этим массивом его необходимо скопировать в адресное пространство ядра. Можно выделить для этого память в куче, но доступ к куче относительно медленный. Чаще всего системному вызову poll передаётся не большое количество дескрипторов, поэтому часть массива можно разместить на стеке, а то, что не поместилось - в куче. Для этого используется связный список.
Элемент связного списка описывается структурой:

struct poll_list {
 struct poll_list *next;
 int len;
 struct pollfd entries[0];
};

struct poll_list *next - указатель на следующий элемент списка;
int len - длина массива дескрипторов;
struct pollfd entries[0] - указатель на массив;

Структура pollfd:
struct pollfd {
 int fd;
 short events;
 short revents;
};

Для указателя на массив дескрипторов используется массив нулевой длины. Почему не struct pollfd* entries будет понятно ниже.

Далее имеем вот такой код (несколько упрощённый вариант)

#define POLL_STACK_ALLOC 256

/*Количество элементов массива pollfd, которое поместится на стеке
  Также учитывается размер элемента списка, т.к. он тоже размещается на стеке*/

#define N_STACK_PPS ((sizeof(stack_pps) - sizeof(struct poll_list))  / \
   sizeof(struct pollfd))

#define POLLFD_PER_PAGE  ((PAGE_SIZE-sizeof(struct poll_list)) / sizeof(struct pollfd))


int do_sys_poll(struct pollfd *ufds, unsigned int nfds,
 struct timespec *end_time)
{
 struct poll_wqueues table;
 int err = -EFAULT, fdcount, len, size;

 /*Память под элемент списка и массив pollfd*/
 long stack_pps[POLL_STACK_ALLOC/sizeof(long)];
 /*Указатель на первый элемент списка*/
 struct poll_list *const head = (struct poll_list *)stack_pps;
 /*Указатель для прохода по списку*/
 struct poll_list *walk = head;
 /*Сколько ещё элементов необходимо скопировать*/
 unsigned long todo = nfds;

 /*При перво проходе цикла len содержит количество элементов,
    которые будут размещены на стеке*/
 len = min_t(unsigned int, nfds, N_STACK_PPS);
 for (;;) {
  walk->next = NULL;
  walk->len = len;
  if (!len)
   break;

  /*Копируем данные из пространства пользователя в пространство ядра*/
  if (copy_from_user(walk->entries, ufds + nfds-todo,
     sizeof(struct pollfd) * walk->len))
   goto out_fds;

  /*Сколько ещё осталось скопировать*/
  todo -= walk->len;
  if (!todo)
   break;

  /*Все элементы, которые не поместились на стеке, мы размещаем в куче
     постранично*/

  len = min(todo, POLLFD_PER_PAGE);
  size = sizeof(struct poll_list) + sizeof(struct pollfd) * len;
  walk = walk->next = kmalloc(size, GFP_KERNEL);
  if (!walk) {
   err = -ENOMEM;
   goto out_fds;
  }
 }

 ................................

 return err;
}

Макрос N_STACK_PPS вычисляет количество элементов + размер элемента списка, которые  могут поместиться на стеке. Получается, что в массиве stack_pps первые sizeof(poll_list) байт отводится под элемент списка, а sizeof(stack_pps) - sizeof(poll_list) байт под массив pollfd.
Надо как то ссылаться на массив pollfd из элемента списка. Известно, что массив располагается сразу после структуры poll_list в массиве stack_pps и в выделяемой в куче памяти. Для того, чтобы получить указатель на область сразу за poll_list используется массив нулевой длинны struct pollfd entries[0]. Это эффективней чем указатель struct pollfd* entries, т.к. он съел бы дополнительные байты памяти, и его необходимо было бы инициализировать адресом первого элемента массива.

Комментариев нет:

Отправить комментарий