|
|
Интерфейс системы UNIX
8.7. Пример - распределитель памяти
В "лекции №5" мы написали бесхитростный вариант функции alloc. Вариант, который мы напишем теперь, не содержит ограничений: обращения к функциям alloc и free могут перемежаться в любом порядке; когда это необходимо, функция alloc обращается к операционной системе за дополнительной памятью. Кроме того, что эти процедуры полезны сами по себе, они также иллюстрируют некоторые соображения, связанные с написанием машинно-зависимых программ относительно машинно-независимым образом, и показывают практическое применение структур, объединений и конструкций typedef.
Вместо того, чтобы выделять память из скомпилированного внутри массива фиксированного размера, функция alloc будет по мере необходимости обращаться за памятью к операционной системе. Поскольку различные события в программе могут требовать асинхронного выделения памяти, то память, управляемая alloc, не может быть непрерывной. В силу этого свободная память хранится в виде цепочки свободных блоков. Каждый блок включает размер, указатель следующего блока и саму свободную память. Блоки упорядочиваются в порядке возрастания адресов памяти, причем последний блок (с наибольшим адресом ) указывает на первый, так что цепочка фактически оказывается кольцом.
При поступлении запроса список свободных блоков просматривается до тех пор, пока не будет найден достаточно большой блок. Если этот блок имеет в точности требуемый размер, то он отцепляется от списка и передается пользователю. Если же этот блок слишком велик, то он разделяется, нужное количество передается пользователю, а остаток возвращается в свободный список. Если достаточно большого блока найти не удается, то операционной системой выделяется новый блок, который включается в список свободных блоков; затем поиск возобновляется.
Освобождение памяти также влечет за собой просмотр свободного списка в поиске подходящего места для введения освобожденного блока. Если этот освободившийся блок с какой-либо стороны примыкает к блоку из списка свободных блоков, то они объединяются в один блок большего размера, так что память не становится слишком раздробленной. Обнаружить смежные блоки просто, потому что свободный список содержится в порядке возрастания адресов.
Одна из проблем, о которой мы упоминали в "лекции №5" , заключается в обеспечении того, чтобы возвращаемая функцией alloc память была выровнена подходящим образом для тех объектов, которые будут в ней храниться. Хотя машины и различаются, для каждой машины существует тип, требующий наибольших ограничений по размещению памяти, если данные самого ограничительного типа можно поместить в некоторый определенный адрес, то это же возможно и для всех остальных типов. Например, на IBM 360/370,HONEYWELL 6000 и многих других машинах любой объект может храниться в границах, соответствующим переменным типа double ; на PDP-11 будут достаточны переменные типа int.
Свободный блок содержит указатель следующего блока в цепочке, запись о размере блока и само свободное пространство; управляющая информация в начале называется заголовком. Для упрощения выравнивания все блоки кратны размеру заголовка, а сам заголовок выровнен надлежащим образом. Это достигается с помощью объединения, которое содержит желаемую структуру заголовка и образец наиболее ограничительного по выравниванию типа:
typedef int align; /*forces alignment on pdp-11*/
union header { /*free block header*/
struct {
union header *ptr; /*next free block*/
unsigned size; /*size of this free block*/
} s;
align x; /*force alignment of blocks*/
};
typedef union header header;Функция alloc округляет требуемый размер в символах до нужного числа единиц размера заголовка; фактический блок, который будет выделен, содержит на одну единицу больше, предназначаемую для самого заголовка, и это и есть значение, которое записывается в поле size заголовка. указатель, возвращаемый функцией alloc, указывает на свободное пространство, а не на сам заголовок.
static header base; /*empty list to get started*/
static header *allocp=null; /*last allocated block*/
char *alloc(nbytes)/*general-purpose storage allocator*/
unsigned nbytes;
{
header *morecore();
register header *p, *g;
register int nunits;
nunits=1+(nbytes+sizeof(header)-1)/sizeof(header);
if ((g=allocp)==null) \( /*no free list yet*/
base.s ptr=allocp=g=&base;
base.s.size=0;
}
for (p=g>s.ptr; ; g=p, p=p->s.ptr) {
if (p->s.size>=nunits) { /*big enough*/
if (p->s.size==nunits) /*exactly*/
g->s.ptr=p->s.ptr;
else { /*allocate tail end*/
p->s.size-=nunits;
p+=p->s.size;
p->s.size=nunits;
}
allocp=g;
return((char *)(p+1));
}
if(p==allocp) /*wrapped around free list*/
if((p=morecore(nunits))==null)
return(null); /*none left*/
}
}
