КаталогИндекс раздела
НазадОглавлениеВперед


12. Упpавление памятью

12.1. Блоки упpавления памятью

После загpузки опеpационной системы оставшаяся часть памяти свободна и pаспpеделяется DOS пpикладным пpогpаммам. Память pаспpеделяется блоками. Размеp блока может быть пpоизвольным, но обязательно кpатен pазмеpу паpагpафа (16 байт). Вообще пpи pаспpеделении памяти объем памяти измеpяется в паpагpафах, и все блоки памяти выpавниваются по гpанице паpагpафа. Блоки создаются и уничтожаются динамически, по меpе поступления запpосов на их выделение / освобождение. Каждый блок памяти пpедваpяется Упpавляющим Блоком Памяти (MCB - Memory Control Block). MCB имеет фиксиpованный pазмеp 1 паpагpаф и фоpмат, описываемый следующей стpуктуpой:


       struct MCB {
         byte type;        /* тип */
         word owner;       /* владелец */
         word size;        /* размер */
         byte reserved[3]; /* не используется */
         char pgmname[8];  /* имя (только DOS 4.0 и выше) */
         };
Поле type MCB содеpжит код, показывающий, является ли этот MCB последним (код буквы Z - 0x5A.) или непоследним (код буквы M - 0x4D). Поле owner содеpжит PID (сегментный адpес пpефикса пpогpаммного сегмента) пpогpаммы, котоpой данный блок памяти пpинадлежит. Если значение этого поля нулевое, то блок свободен. Поле size содеpжит pазмеp блока памяти в паpагpафах (в это число не включен 1 паpагpаф, занимаемый самим MCB). Следующие 3 байта (поле reserved) заpезеpвиpованы во всех веpсиях. Поле pgmname заpезеpвиpовано (не используется) в веpсиях DOS ниже 4.0. Начиная с веpсии 4.0, в MCB, пpедваpяющем пpогpаммный сегмент, здесь записано имя (без pасшиpения) пpогpаммы, находящейся в этом сегменте (если длина имени меньше 8 символов, оно заканчивается нулевым байтом).
Все MCB увязаны в цепочку. Зная адpес пеpвого MCB в цепочке, можно, пpибавив к нему длину (size) и еще 1 (паpагpаф, занимаемый самим MCB), получить адpес следующего MCB и так далее. Пpизнаком конца цепочки является значение 'Z' поля type.
Адpес начала цепочки блоков памяти можно получить пpи помощи недокументиpованной функции DOS 0x52. Подpобности пpименения этой функции pассмотpены в следующей главе. Здесь же только сообщим читателю, что эта функция возвpащает в pегистpах ES:BX некотоpый адpес. Вычтя из этого адpеса 2, получим адpес того слова памяти, в котоpом DOS хpанит сегментный адpес пеpвого MCB в цепочке.

Пpогpамма следующего пpимеpа позволяет пpосмотpеть "каpту pаспpеделяемой памяти" ПЭВМ - пpоиндициpовать, какие блоки свободны, а какие заняты и кем (какой пpогpаммой) заняты.


/*==                       ПРИМЕР 12.1                   ==*/
/*================== Выдача карты памяти =================*/
#define byte unsigned char
#define word unsigned int
#include <dos.h>
#include <string.h>
struct MCB { /* блок упpавления памятью */
  char type; word owner, size;
   byte reserved[3]; char pgmname[8];
   };
struct MCB *cmcb;  /* адpес текущего MCB */
struct MCB *emcb;  /* адpес MCB сpеды */
word memtop;       /* сегм.адрес начала памяти */
word csegm;        /* сегментный адpес текущего MCB */
word othersegm;    /* сегм.адрес другого MCB */
word fathersegm;   /* сегм.адрес родителя */
byte *envstr;      /* адpес стpоки окружения */
int envlen;        /* длина очередной строки окружения */
int envsize;       /* размер блока окружения */
byte dos;          /* номер версии DOS */
union REGS rr;
struct SREGS sr;
int i;
char *s;
main() {
  clrscr();
  /* получить номер версии DOS */
  rr.h.ah=0x30; intdos(&rr,&rr); dos=rr.h.al;
  /* получить адрес системных блоков */
  rr.h.ah=0x52; intdosx(&rr,&rr,&sr);
  /* получить адрес начала цепочки */
  memtop=csegm=peek(sr.es,rr.x.bx-2);
  do {
    cmcb=(struct MCB *)MK_FP(csegm,0);
    printf("Addr=%04X:0000  PID=%04X  Size=%-6u ",
          csegm,cmcb->owner,cmcb->size*16);
    if (cmcb->owner==0) printf(" Free"); /* блок свободен */
    else {  /* блок занят */
      /* блок принадлежит DOS ? */
      if (cmcb->owner<memtop) printf(" Dos ");
      else {  /* блок не принадлежит DOS */
        /* если PID хозяина указывает на текущий блок,
        то это программный сегмент */
        if (csegm==cmcb->owner-1) printf(" Pgm   ");
        else {
          /* адpес блока сpеды для хозяина этого блока памяти 
              находится в PSP хозяина со смещением 0x2C */
          othersegm=peek(cmcb->owner,0x2c);
          /* адpес родителя для программы-хозяина этого блока 
              находится в PSP хозяина со смещением 0x16 */
          fathersegm=peek(cmcb->owner,0x16);
          /* если на текущий блок указывает адрес окружения
          хозяина, то это блок окружения */
          if (csegm==othersegm-1) printf(" Env   ");
          /* иначе - это блок данных */
          else printf(" Data  ");
          }
        /* если хозяин сам себе родитель, то это COMMAND */
        if (cmcb->owner==fathersegm) printf("COMMAND.COM");
        else { /* для другой программы узнаем ее имя */
          if (dos>3) {
            emcb=(struct MCB *)MK_FP(cmcb->owner-1,0);
            for (i=0,s=emcb->pgmname; i<8; i++) {
              if (*s>0) printf("%c",*(s++));
              else printf(" ");
              }
            printf(" ");
            }
          if (dos>2) {
            /* для DOS 3.0 и выше имя - из строки вызова */
            emcb=(struct MCB *)MK_FP(othersegm-1,0);
            envsize=emcb->size*16; /*размер окружения */
            envstr=(char *)MK_FP(othersegm,0);
            do {
              /* пропуск строк окружения до пустой строки */
              envlen=strlen(envstr)+1;
              envstr+=envlen; envsize-=envlen;
              } while ((envlen>1)&&(envsize>0));
            envstr+=2; envsize-=2; /* 2 байта - кол.строк */
            /* envstr - указатель на строку вызова */
            if (envsize>0)  printf("%s",envstr);
            }
          }
        }
      }
    printf("\n");
    csegm+=(cmcb->size+1); /* переход к следующему блоку */
    } while (cmcb->type!='Z');  /* выход по последн.блоку */
  getch();
}

Программа получает при помощи функции DOS 0x52 сегментный адрес начала цепочки MCB csegm и движется по цепочке. Переход к следующему блоку производится прибавлением к адресу текущего MCB его поля size и еще 1. Перебор заканчивается при достижении MCB со значением 'Z' в поле type.
Для каждого MCB выводится на экран:

Сегментный адрес, PID владельца и размер блока получаются из MCB очевидным образом.
Класс блока определяется по следующим правилам. Если PID владельца блока нулевой, блок является свободным (класс Free). Для занятых блоков класс уточняется. Если PID (сегментный адрес PSP) владельца содержит адрес, лежащий до вершины распределяемой памяти memtop (обычно это число 8), то блок получает класс Dos. Для блоков, не принадлежащих DOS, прежде всего проверяется сегментный адрес владельца. Если этот адрес является адресом сегмента, следующего за текущим MCB, это означает, что блок памяти содержит программный сегмент и получает класс Pgm. В противном случае программа "заглядывает" в PSP владельца. Со смещением 0x2C в PSP содержится адрес сегмента окружения; если этот адрес является адресом сегмента, следующего за текущим MCB, то блок получает класс Env. Если класс блока не удается определить ни одним из вышеописанных способов, считаем, что блок содержит данные и помечаем его классом Data.
Для DOS 4.0 и выше определяем символьный идентификатор владельца, для этого "заглядываем" в MCB того блока, на который указывает поле owner текущего блока, и выводим его поле pgmname. Это единственный способ получить идентификаторы резидентных программ, загружаемых в память командой INSTALL DOS 4.0 и выше.
Только DOS 4.0 позволяют получить идентификатор командного процессора DOS вышеописанным образом. Однако, в ранних версиях DOS можно опознать блок памяти, принадлежащий командному процессору вот каким способом. В PSP владельца со смещением 0x16 находится сегментный адрес "родителя" - программы, запустившей данную программу. Для программ, запущенных из командной строки DOS, родителем является COMMAND.COM. Родителем же COMMAND.COM является он сам. По этому признаку (сам себе родитель) он и может быть опознан.
Если же программа не является программой DOS, командным процессором или резидентной, загруженной по INSTALL, для нее должна сохраняться строка вызова. Строка вызова находится в сегменте окружения, технику ее получения мы pассматpивали в пpедыдущей главе: программа получает адрес начала блока ок- ружения, пропускает в нем все строки до пустой, после пустой строки пропускает еще 2 байта и получает адрес строки вызова.
В отношении блоков памяти, занимаемых загружаемыми и резидентными программами, наша программа выдает тот же объем информации, что и команда DOS MEM, имеющаяся в DOS 4.0 и выше.
Здесь мы рекомендуем читателю вернуться к программному примеру 4.1 и самостоятельно разработать такой вариант программы анализа таблицы векторов прерываний, который не только определял бы, занят вектор или свободен, но и устанавливал бы, куда указывает этот вектор - ПЗУ BIOS, дополнительное ПЗУ, ядро DOS или какая-то загруженная программа (в последнем случае, используя технику, описанную в этом разделе, можно установить имя этой программы).

12.2. Функции pаспpеделения памяти DOS

При обычной работе MS DOS сама занимается распределением памяти и предоставляет пользователю три функции выделения/освобождения памяти:

Для всех функций ненулевое состояние флага CY является признаком ошибки.

Следующий программный пример иллюстрирует работу системы по выделению/освобождению памяти.
Функция init производит сканирование цепочки MCB и определяет адрес последнего блока памяти freetop. При принятой в системе по умолчанию дисциплине распределения памяти старшие адреса памяти представляют собой большой свободный блок, и все последующие выделения памяти будут вестись за счет этого свободного блока.
Функция memmap перебирает цепочку MCB, начиная с адреса freetop и выдает карту распределения памяти от freetop до конца памяти.
Функции memget, memfree, memnew обеспечивают обращения к функциям DOS 0x48, 0x49, 0x4A соответственно.
При шагах 1-5 программы происходит выделение нескольких блоков памяти по 64 параграфа каждый. По изменению карты памяти при этих шагах можно видеть, что новый блок памяти выделяется в начале свободного большого блока. Для оставшейся части большого блока система строит новый MCB, в котором этот блок помечается как свободный.
На шаге 5 изменяется (уменьшается) размер блока a2. По карте памяти мы увидим, что блок a2 будет разбит на два блока: первый - занятый блок требуемого размера, а освободившаяся часть блока образует новый, свободный блок.
На шаге 6 выдается запрос на увеличение блока a2 до размера большего 64 параграфов. Этот запрос не может быть удовлетворен, так как размер блока может быть увеличен только в том случае, если за ним следует свободный блок достаточного размера. В нашем случае блок a2 будет увеличен до возможного размера (64 параграфов), и будет индицирована ошибка.
Из карты, выдающейся на шагах 7, 8 видно, что при освобождении блока, в его MCB появляется отметка "свободен", при освобождении возможно появление фрагментации памяти.
При поступлении запроса на блок большего размера (шаг 9), этот блок выделяется за счет последнего свободного блока, как и на шагах 1 - 5.
При поступлении запроса на блок меньшего размера (шаг 10), этот блок выделяется за счет свободного блока внутри цепочки (в нашем случае - за счет блока a1. При этом, если затребованный размер меньше размера свободного блока, выделяется затребованный объем памяти, а остаток образует свободный блок.


/*==                     ПРИМЕР 12.2                    ==*/
/*============= Функции упpавления памятью DOS ===========*/
#include <dos.h>
#define byte unsigned char
#define word unsigned int
struct MCB {  char type; word owner, size;
  byte reserved[11]; } *cmcb; /* указатель на текущий MCB */
void init(void);
void memfree(byte *a);
byte *memget(word blksize);
byte *memnew(word blksize,byte *a);
void memmap(char *s);
word freetop; /* сегментный адpес начала свободной памяти */
word csegm;        /* сегм.адрес текущего MCB */
byte *a[7];        /* указатели на блоки памяти */
union REGS rr;
struct SREGS sr;
main() {
  clrscr();
  init();  /* инициализация */
  /* Шаги 1 - 5 - выделение новых блоков */
  a[0]=memget(64); memmap("a[0]=memget(64)");
  a[1]=memget(64); memmap("a[1]=memget(64)");
  a[2]=memget(64); memmap("a[2]=memget(64)");
  a[3]=memget(64); memmap("a[3]=memget(64)");
  a[4]=memget(64); memmap("a[4]=memget(64)");
  /* шаг 6 - уменьшение блока a2 */
  memnew(50,a[1]); memmap("memnew(50,a[1])");
  /* шаг 7 - попытка увеличения блока a2 */
  memnew(80,a[1]); memmap("memnew(80,a[1])");
  /* шаги 8,9 - освобождение блоков */
  memfree(a[1]); memmap("memfree(a[1])");
  memfree(a[3]); memmap("memfree(a[3])");
  /* шаг 10 - выделение блока большего размера */
  a[5]=memget(80); memmap("a[5]=memget(80)");
  /* шаг 11 - выделение блока меньшего размера */
  a[6]=memget(50); memmap("a[6]=memget(50)");
}
/*==== Выделение нового блока размером blksize ====*/
byte *memget(word blksize) {
  rr.h.ah=0x48;     /* функция 48 */
  rr.x.bx=blksize;  /* требуемый размер */
  intdos(&rr,&rr);
  if (rr.x.cflag)
    printf("\7Неудовл.запрос mem=%d\n",rr.x.bx);
  /* сегм. адрес блока - в AX */
  else return(MK_FP(rr.x.ax,0));
  }
/*==== Освобождение блока a ====*/
void memfree(byte *a) {
  rr.h.ah=0x49;     /* функция 49 */
  sr.es=FP_SEG(a);  /* сегм. адрес блока */
  intdosx(&rr,&rr,&sr);
  if (rr.x.flags&1) printf("\7Некорректный free\n");
  }
/*==== Изменение размера блока a до blksize ====*/
byte *memnew(word blksize,byte *a) {
  rr.h.ah=0x4a;     /* функция 4A */
  rr.x.bx=blksize;  /* требуемый размер */
  sr.es=FP_SEG(a);  /* сегм. адрес блока */
  intdosx(&rr,&rr,&sr);
  if (rr.x.cflag)
    printf("\7Неудовл.запрос на расширение mem=%d\n",rr.x.bx);
  else return(MK_FP(rr.x.ax,0));
  }
/*==== Определение адреса начала последнего (большого)
                                     свободного блока ====*/
void init(void) {
  /* получение адреса системных блоков */
  rr.h.ah=0x52;  intdosx(&rr,&rr,&sr);
  csegm=peek(sr.es,rr.x.bx-2);
  do { /* движение по цепочке MCB */
    freetop=csegm;
    cmcb=(struct MCB *)MK_FP(csegm,0);
    /* переход к следующему блоку */
    csegm+=(cmcb->size+1);
    } while(cmcb->type!='Z');
  memmap("Исходный блок");
}
/*==== Выдача карты памяти ====*/
void memmap(char *s) {
  printf("\n***** %s *****\n",s);
  csegm=freetop;
  do { /* движение по цепочке MCB */
    cmcb=(struct MCB *)MK_FP(csegm,0);
    printf("Адрес=%04X Размер=%-5u  ",csegm,cmcb->size);
    if (cmcb->owner==0) printf("свободен\n");
    else printf("занят\n");
    /* переход к следующему блоку */
    csegm+=(cmcb->size+1);
    } while(cmcb->type!='Z');
  getch();
}

Из работы программы предыдущего примера видно, что MS DOS при выделении памяти использует дисциплину "первый подходящий" - память выделяется из первого в цепочке свободного блока, размер которого больше или равен затребованному. В DOS 3.0 и выше имеется возможность управлять дисциплиной распределения памяти. Для этого служит функция DOS 0x58. Подфункция 0 (AL=0) этой функции позволяет прочитать установленную дисциплину (результат - в AX). Подфункция 1 (AL=1) позволяет задать (в BX) дисциплину. Возможны три дисциплины: 0 - первый подходящий (установлена по умолчанию), 1 - последний подходящий, 2 - самый подходящий.
В программном примере функции getorder и setorder обеспечивают обращение к функции DOS 0x58 для чтения/установки дисциплины.
Функции init, memmap, memget и memfree работают аналогично одноименным функциям предыдущего примера.


/*==                     ПРИМЕР 12.3                   ==*/
/*=========== Дисциплины распределения памяти ===========*/
#include <dos.h>
#define byte unsigned char
#define word unsigned int
void memfree(byte *a);
byte *memget(int blksize);
void setorder(byte d);
byte getorder();
void init(void);
void memmap(int x);
struct MCB { byte type; word owner, size;
  byte reserved[11]; } *c;
word freetop, cs;
union REGS rr;
struct SREGS sr;
main()
{
  byte *a[10] ,*b, order;
  word sz, i;
  init();   /* составление списка исходных блоков */
  /* Формирование фрагментированной памяти */
  for (sz=84,i=0; i<10; i++) {
    a[i]=memget(sz); b=memget(8);
    if (i>=5) sz+=4; else sz-=4;
    }
  for (i=0; i<10; i++) memfree(a[i]);
  order=getorder();
  clrscr();
  printf("  Исходное  |Дисциплина_0");
  printf("|Дисциплина_1|Дисциплина_2|\n");
  printf("------------|------------");
  printf("|------------|------------|");
  memmap(1);     /* отображение памяти */
  setorder(0);   /* установка дисциплины */
  b=memget(70);  /* выделение памяти */
  memmap(14);     /* отображение памяти */
  memfree(b);    /* возврат к исходному распределению */
  setorder(2); b=memget(70); memmap(27); memfree(b);
  setorder(1); b=memget(70); memmap(40); memfree(b);
  setorder(order); /* восстановление исходной дисциплины */
  }
/*==== Установка дисциплины распределения ====*/
void setorder(byte d) {
  rr.h.ah=0x58; /* Ф-ция 58 */
  rr.h.al=1;    /* Установить */
  rr.x.bx=d;    /* Дисциплина */
  intdos(&rr,&rr);
  }
/*==== Чтение дисциплины распределения ====*/
byte getorder() {
  rr.h.ah=0x58; /* Ф-ция 58 */
  rr.h.al=0;    /* Прочитать */
  intdos(&rr,&rr);
  return (rr.x.ax);  /* Дисциплина - в AX */
  }
/*==== Выделение памяти ====*/
byte *memget(int blksize) {
  rr.h.ah=0x48; rr.x.bx=blksize; intdos(&rr,&rr);
  if (rr.x.cflag)
    printf("\7Неудовл.запрос mem=%d\n",rr.x.bx);
  else return(MK_FP(rr.x.ax,0));
  }
/*==== Освобождение памяти ====*/
void memfree(byte *a) {
  rr.h.ah=0x49; sr.es=FP_SEG(a); intdosx(&rr,&rr,&sr);
  if (rr.x.cflag) printf("\7Некорректный free\n");
  }
/*==== Определение адреса последнего свободного блока ====*/
void init(void) {
  rr.h.ah=0x52; intdosx(&rr,&rr,&sr);
  cs=peek(sr.es,rr.x.bx-2);
  do {
    freetop=cs; c=(struct MCB *)MK_FP(cs,0);
    cs+=(c->size+1);
    } while(c->type!='Z');
}
/*==== Выдача карты памяти ====*/
void memmap(int x) {
  int y;
  cs=freetop;  y=3;
  do {
    c=(struct MCB *)MK_FP(cs,0); cs+=(c->size+1);
    gotoxy(x,y++); printf("%04X %-4u",cs,c->size);
    if (c->owner==0) printf("...|\n");
    else printf("***|\n");
    } while(c->type!='Z');
}

Для наглядности эксперимента программа формирует фрагментированную память. Карта памяти в исходном состоянии выглядит (рис. 12.1 ) следующим образом. В памяти имеется набор свободных участков, размер которых вначале убывает от 84 до 64 параграфов, а затем возрастает до 84 параграфов. Свободные участки перемежаются занятыми по 8 параграфов каждый (размер 8 параграфов выбран для того, чтобы на картину распределения не повлияли маленькие свободные участки, которые, иногда имеются в начале распределяемой памяти).
Затем программа устанавливает дисциплину распределения, запрашивает блок размером 70 параграфов и выводит новое распределение памяти. После вывода запрошенная память освобождается, то есть восстанавливается исходное распределение.
Проведем анализ результатов выполнения этой программы, сведенных в таблицу (см. рис. 12.1).
При дисциплине распределения 0 первым подходящим оказывается самый первый свободный блок, его размер - 84 параграфа. В начале его образуется занятый блок размером 70 параграфов, а остаток образует свободный блок размером 13 параграфов (учтите, что один параграф потребовался для нового MCB).

Исходное распределение Дисциплина 0 Дисциплина 1 Дисциплина 2
a v s a v s a v s a v s
7433 84 - 7433 70 + 7433 84 - 7433 84 -
7488 8 + 747A 13 - 7488 8 + 7488 8 +
7491 80 - 7488 8 + 7491 80 - 7491 80 -
74E2 8 + 7491 80 - 74E2 8 + 74E2 8 +
74EB 76 - 74E2 8 + 74EB 76 - 74EB 76 -
7538 8 + 74EB 76 - 7538 8 + 7538 8 +
7541 72 - 7538 8 + 7541 72 - 7541 70 +
758A 8 + 7541 72 - 758A 8 + 7588 1 -
7593 68 - 758A 8 + 7593 68 - 758A 8 +
75D8 8 + 7593 68 - 75D8 8 + 7593 68 -
75E1 64 - 75D8 8 + 75E1 64 - 75D8 8 +
7622 8 + 75E1 64 - 7622 8 + 75E1 64 -
762B 68 - 7622 8 + 762B 68 - 7622 8 +
7670 8 + 762B 68 - 7670 8 + 762B 68 -
7679 72 - 7670 8 + 7679 72 - 7670 8 +
76C2 8 + 7679 72 - 76C2 8 + 7679 72 -
76CB 76 - 76C2 8 + 76CB 76 - 76C2 8 +
7718 8 + 76CB 76 - 7718 8 + 76CB 76 -
7721 80 - 7718 8 + 7721 80 - 7718 8 +
7772 8 + 7721 80 - 7772 8 + 7721 80 -
777B10372- 7772 8 + 777B10301- 7772 8 +
. . . 777B10372- 9FB9 70 + 777B10372-.
Рис. 12.1. Дисциплины распределения памяти: a - сегментный адрес блока;
v - объем блока в параграфах;
s - состояние блока: "+" - занят, "-" - свободен.

При дисциплине распределения 1 последним подходящим оказывается самый последний блок, включающий в себя весь остаток памяти. Причем память под новый занятый блок при этой дисциплине выделяется не из начальной, а из конечной части этого блока, то есть распределение памяти здесь ведется от конца к началу.
При дисциплине распределения 2 самым подходящим оказывается свободный блок размером 72 параграфа (предыдущие блоки имели больший размер, последующие - меньший). Память выделяется в начале этого блока, остаток образует свободный блок размером всего 1 параграф.
Специалистам хорошо известоно, что наилучшие показатели в большинстве применений обеспечивает дисциплина "первый подходящий".


КаталогИндекс раздела
НазадОглавлениеВперед