|
Интерфейс системы 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*/ } }