Войти
ФлеймФорумЖелезо

ОБРАБОТКА ПРЕРЫВАНИЯ ЗВУКОВОЙ КАРТЫ НА ВЕДОМОМ КОНТРОЛЛЕРЕ (IRQ10)

Страницы: 1 2 Следующая »
#0
5:44, 15 июля 2019

Занимаюсь программированием на ПК(и не только) на низком уровне.
Довольно часто приходится писать программы или кидать порты под DOS + DPMI.

Многие сейчас усмехнутся, сказав, что ДОС умер...
Может быть это и так, но DOS по крайней мере живёт на моём компьютере и в голове :)

Ближе к делу...

Дано:

1) Звуковая карта PCI Sound Blaster Live (насколько я понял, от саундбластера там осталось лишь одно название). Чип EMU10K.
2) Для ДОС были найдены драйверы, которые эмулируют режим SB16 через эту карту (с помощью VCPI EMM386). Тоесть - это софтовая эмуляция SB16, а не Legacy, как это сделано, к примеру, на картах Cmedia CM8738.
3) С помощью этих драйверов можно конфигурировать SB16: назначить порт, прерывание и номер DMA-канала.
4) DOS-Extender DOS4G (коммерческая версия самого популярного расширителя ДОС: DOS4GW. Почему используется именно он - см. ниже).

Надо:

Реализовать непрерывное воспроизведение аудио-потока с помощью кольцевого буфера с подгрузкой новых данных по прерыванию, вызывающегося после проигрывания очередной половины буфера.

Проблема:

Когда эмуляция SB16 настроена на одно из прерываний ведомого контроллера прерываний (IRQ8 - IRQ15), к примеру - IRQ10, то обработчик прерывания НЕ вызывается!

Если же настроить карту на одно из прерываний ведущего контроллера (IRQ0 - IRQ7), к примеру - IRQ5, то прерывание происходит и всё работает как надо.

Данное поведение, честно говоря, ввело меня в ступор... Попробовал тоже самое сделать в реальном режиме на Turbo Pascal 7.0 - алгоритм работает на IRQ5 и на IRQ10 !!!

Использую компилятор Open Watcom ASM/C/C++ последней официальной версии. Платформа - DOS32(sys dos4g).

Поиск в интернете:

... привел меня к товарищам по несчастью (несколько лет назад):

1) http://forum.codenet.ru/q8115/ - точечное попадание

2) https://groups.google.com/forum/#!topic/comp.os.msdos.programmer/9cpAytOYydw - здесь аналогичная проблема: не могут обработать IRQ15 от жёсткого диска.

Был найден сайтик с ценной информацией, на котором объяснены причины такого поведения: http://rgmroman.narod.ru/Dos4g.htm

DOS/4(W) has auto passup range for interrupts 08h-2Eh, i.e. if any of that interrupt occured in real mode, extender reflects it to protected mode.
For interrupts occured in protected mode externder searches interrupt handler in protected and if none found pass interrupt down to real mode.

Если же вспомнить, что Master-контроллер прерываний по умолчанию настроен на INT 0x8..INT 0xF (IRQ0..IRQ7), а Slave-контроллер на INT 0x70..INT 0x77 (IRQ8..IRQ15),
то становится очевидным, что DOS4GW не может работать с IRQ8..IRQ15, если просто установить обработчик прерывания стандартной C-функцией:

_dos_setvect(0x72,SB16_New); // INT 0x72 - IRQ10

или с помощью DPMI функции 0x205 (установка обработчика прерывания защищённого режима).

Решение номер 1:

Не совсем красивое с точки зрения технической эстетики.

Решение возникло на основе знаний о том, что прерывание IRQ10 от звуковой карты всё-же обрабатывалось, если оно было установлено программой РЕАЛЬНОГО режима.

На Turbo Pascal, а позже на Flat Assembler, был написан небольшой резидентик, устанавливающий обработчик прерывания в реальном режиме,
сигнализирующий о том, что карта завершила чтение буфера:

ORG        0x100

jmp        int0x72

IRQSlave:
push        ax
push        dx
mov        al,1                ;флаг SB_RM_FLAG
mov        BYTE [cs:SBFlag],al ;узнаём порт (заполняется 32-битной программой, см. ниже)
mov        dx,WORD [cs:SBPort]
add        dx,0x00F
in        al,dx
mov        al,0x20
out        0x20,al
out        0xA0,al
pop        dx
pop        ax
iret

SBPort        dw 0x220
SBFlag        db 0

int0x72:
mov        ah,0x25
mov        al,0x72
mov        dx,IRQSlave
int        21h                 ;установка вектора прерывания в реальном режиме
mov        dx,int0x72          ;завершить работу программы, оставив её часть резидентной (от IRQSlave до int0x72)
int        27h

Как видно из программы, ничего особенного - обработчик просто подтверждает прерывание чтением из порта и
записывает 1 в ячейку памяти со смещением SBFlag от начала кода обработчика (это байт со смещением 0x1C).

Далее собственно, мы проверяем этот флаг из нашей программы защищённого режима в другом обработчике прерываний, с которым проблем не возникает.
Это прерывания таймера IRQ0:

volatile u8 *SB_RM_FLAG; //флаг завершения чтения данных от обработчика прерывания реального режима

SB_RM_FLAG=(u8*)(((*(u16*)((0x72UL*4)+2))<<4)+(*(u16*)(0x72UL*4))); //указатель указывает на физический адрес вектора прерывания INT 0x72 реального режима

void interrupt far TimerNew(void)
{
 if(SB_RM_FLAG)  //если флаг установлен
 {
  SB_RM_FLAG=0;  //то сбрасываем его
  MIXER();       //подгружаем новые данные в свобродную половину буфера
 }

 out8(0x20,0x20); //шлём EOI ведущему и ведомому контроллеру прерываний
 out8(0xA0,0x20);
}

Решение блестяще работает как на ведущих, так и на ведомых прерываниях.

Недостатки решения:

1) Требуется постоянно запускать программу-резидент перед запуском основной программы

2) Постоянный опрос флага по прерыванию таймера

Решение номер 2:

Избавляемся от программы-резидента.

В стандарте DPMI (dpmispec1.0.pdf) есть интересная функция: 0x201 - Set Real Mode Interrupt Vector.
Она требует на входе указания номера вектора прерывания, а также адрес обработчика реального режима в формате сегмент:смещение.

Попутно понадобилась ещё одна функция - выделить блок памяти ниже 1 МБ (для размещения кода обработчика прерывания РЕАЛЬНОГО режима).
Это DPMI функция 0x100 - Allocate DOS Memory Block.

Обработчик прерывания был взят с программы-резидента и закодирован в C-массив констант:

/*
Real Mode Interrupt Handler Code for SoundBlaster16
*/
const u8 SB_New_RM_Handler[0x1D]=
{
 0x50,                        //push        ax
 0x52,                        //push        dx
 0xB0,0x01,                   //mov        al,1
 0x2E,0xA2,0x1C,0x00,         //mov        BYTE [cs:SBFlag],al
 0x2E,0x8B,0x16,0x1A,0x00,    //mov        dx,WORD [cs:SBPort]
 0x83,0xC2,0x0F,              //add        dx,0x00F
 0xEC,                        //in        al,dx
 0xB0,0x20,                   //mov        al,0x20
 0xE6,0x20,                   //out        0x20,al
 0xE6,0xA0,                   //out        0xA0,al
 0x5A,                        //pop        dx
 0x58,                        //pop        ax
 0xCF,                        //iret
 0x20,0x02,                   //SBPort        dw 0x220
 0x00,                        //SBFlag        db 0
};

Но так как данный массив скорее всего будет размещён линковщиком по адресу выше первого мегабайта,
то его необходимо скопировать в заранее выделенную область памяти с помощью вышеназванной функции 0x100.

Целиком набор функций инжектора кода обработчика прерывания реального режима выглядит так:

s8 DOS_Allocate(u16 paragraph,u16 *segment,u16 *selector) //выделяет память в адресном пространстве ниже 1 МБ
{
 s8 r=0;
 u16 _seg;
 u16 _sel;
 _asm
 {
  mov ax,0x100
  mov bx,paragraph
  int 0x31
  jnc OK
  mov r,-1
  OK:
  mov _seg,ax
  mov _sel,dx
 }
 *segment=_seg;
 *selector=_sel;
 return r;
}

s8 Set_RM_Handler(u8 i,u16 s,u16 o) //установка обработчика прерывания реального режима (по номеру прерывания и по адресу сегмент:смещение)
{
 s8 r=0;
 _asm
 {
  mov ax,0x201
  mov bl,i
  mov cx,s
  mov dx,o
  int 0x31
  jnc OK
  mov r,-1
  OK:
 }
 return r;
}

u16 SB_RM_SELECTOR; //селектор на участок памяти ниже 1 МБ (нужен для освобождения памяти по завершению работы программы)

s8 SB_Set_RM_Handler(u8 i,u8 *handler,u16 size)
{
 u16 segment; //сегмент участка памяти ниже 1 МБ, полученный функцияе 0x100

 if(DOS_Allocate((size+15)>>4,&segment,&SB_RM_SELECTOR))return -1; //просим необходимое число параграфов

 u32 physical=((u32)segment)<<4; //вычисляем физический адрес (необходим для считывания флага, установленного обработчиком прерывания)

 SB_RM_FLAG=(u8*)(physical+0x1C); //находим физический адрес нашего флага

 *(u16*)&handler[0x1A]=SB_IO; //set SB I/O Port - записываем номер порта подтверждения прерывания (помогаем обработчику)

 memcpy((void*)physical,handler,size); //копируем код обработчика в выделенную память ниже 1 МБ

 if(Set_RM_Handler(i,segment,0))return -1; //устанавливаем обработчик прерывания РЕАЛЬНОГО режима из-под ЗАЩИЩЁННОГО.  Смещение = 0.

 return 0;
}

Вызов:

SB_DisableIRQ(); //запрещаем прерывание от SB16 на время установки нового обработчика

if(SB_IRQ<8) //настройка прерывания от ведущего контроллера(Master PIC, IRQ0..7)
{
 if(SB_Set_RM_Handler(SB_IRQ+0x08,(u8*)SB_New_RM_Handler,sizeof(SB_New_RM_Handler)))DPMI_Error();
}
else  //настройка прерывания от ведомого контроллера(Slave PIC, IRQ 8..15)
{
 if(SB_Set_RM_Handler(SB_IRQ-8+0x70,(u8*)SB_New_RM_Handler,sizeof(SB_New_RM_Handler)))DPMI_Error();
}

SB_EnableIRQ(); //разрешаем прерывания Sound Blaster 16

Труды увенчались успехом!!! Программа работает с любым IRQ от любого контроллера - ведущего или ведомого.
Проверял: в MS DOS 7.1, Free DOS, Win98, WinXP - всё работает.

Следует участь одну важную деталь: в DOS4GW база всегда =0, поэтому 32-битное смещение(ближний указатель) равен физическому адресу.
Поэтому мы просто можем обращатся к ячейкам памяти, умножив сегмент на 16 и сложив со смещением (для реального режима).
В других расширителях ДОС база может быть ненулевой, поэтому код прийдётся скорректировать!

Важное замечание:

Здесь и далее опущены моменты освобождения памяти и восстановления прежних обработчиков, адреса/селекторы которых были заранее получены специальными функциями DPMI.
Сделано это с целью упростить объём и изложение материала, сфокусировавшись на главном.

И наконец -

Решение 3.

Обрабатываем всё в прерывании от звуковой карты! Освобождаем обработчик таймера от постоянного опроса флага.

Полезная функция DPMI: 0x303 - Allocate Real Mode Callback Address.
Функция даёт адрес реального режима сегмент:смещение - точка входа в РЕАЛЬНОМ режиме, в которой будет вызов функции ЗАЩИЩЁННОГО режима.
Коллбэк позволяет вызвать процедуру ЗАЩИЩЁННОГО режима, если процессор находится РЕАЛЬНОМ (или V86) режиме!

Объединив это с предыдущим решением, мы получим обработку прерывания в реальном режиме с заходом в функцию защищённого режима.

Следует отметить, что в процедуре колбэка нам нужно будет восстановить виртуальные регистры (V86 mode) Flags, CS:IP обработчика реального режима.

Обработчик коллбэка написан на Watcom Assembler:

.CODE

EXTRN  __GETDS:PROC        ;это ваткомовский прикол, без него вызов функций внутри обработчика приводит к аварийному вылету программы
EXTRN  UserCallBack_:PROC  ;это пользовательская функция коллбэка

PUBLIC  SystemCallBack_

SystemCallBack_:
  pushad
  push    ds
  push    es
  push    fs
  push    gs
  cld
  lodsw
  mov    es:[edi+0x2A],ax ;IP
  lodsw
  mov    es:[edi+0x2C],ax ;CS
  lodsw
  mov    es:[edi+0x20],ax ;FLAGS
  call    __GETDS
  push    ds
  pop    es
  call    UserCallBack_
  pop    gs
  pop    fs
  pop    es
  pop    ds
  popad
  iretd

END

Отказ от написания обработчика на C/C++ вызван здесь тем, что компилятор C/C++ затирает регистры ds,es,esi,edi, используя их для своих нужд.
А они нам нужны для получения виртуального контекста.
Подробнее см. в спецификации DPMI (функция 0x303; передача и приём виртуального контекста регистров V86).

Пользовательская функция коллбэка:

void UserCallBack(void)
{
 MIXER(); //загружаем новые данные

 IO u8 ack=in8(SB_IO+0xF); //подтверждаем прерывание
 
 out8(0x20,0x20); //EOI для Master и Slave PIC
 out8(0xA0,0x20);
}

Функция выдачи адреса коллбэка (РЕАЛЬНЫЙ режим сегмент:смещение) и установки его на обработчик прерывания РЕАЛЬНОГО режима:

struct RMA //структура адреса реального режима
{ 
 u16 s; //сегмент
 u16 o; //смещение
};

RMA SB_RM_New;

s8 Set_RM_HandlerCallBack(u8 i,void far *handler)
{
 if(CallBack_Allocate(handler,&SB_RM_New))return -1; //выделяем адрес коллбэка RM, при вызове которого будет происходить вызов PM функции handler
 if(Set_RM_Handler(i,SB_RM_New))return -1;           //устанавливаем этот коллбэк в качестве вектора для обработчика RM прерывания!
 return 0;
}

Теперь установка прерываний выглядит так:

SB_DisableIRQ(); //запрещаем прерывания звуковой платы

if(SB_IRQ<8) //если IRQ от ведущего контроллера прерываний
{
 if(Set_RM_HandlerCallBack(SB_IRQ+0x08,SystemCallBack))DPMI_Error();
}
else //если IRQ от ведомого
{
 if(Set_RM_HandlerCallBack(SB_IRQ-8+0x70,SystemCallBack))DPMI_Error();
}

SB_EnableIRQ(); //разрешаем прерывания SoundBlaster

Функция выдачи адреса коллбэка на RM:

#pragma pack(1) //запрещаем выравнивание членов структуры, чтобы не было дырок
struct RMI      //виртуальные регистры для RM V86 (контекст для реального обработчика прерывания)
{ 
 u32 EDI,ESI,EBP,ReservedByRMI,EBX,EDX,ECX,EAX;
 u16 flags,ES,DS,FS,GS,IP,CS,SP,SS;
};

s8 CallBack_Allocate(void far *c,RMA *a) //32-битный указатель на SystemCallBack, получаем сегмент:смещения для вызова PM коллбэка в RM
{
 union REGS r;
 struct SREGS sr;
 static RMI rmi;

 memset(&sr,0,sizeof(sr));
 memset(&rmi,0,sizeof(rmi)); //здесь контекст виртуальных регистров (в обработчике коллбэка восстанавливаем Flags, CS, IP - см. выше)

 r.w.ax=0x303;
 sr.ds=FP_SEG(c);
 r.x.esi=FP_OFF(c);
 sr.es=FP_SEG(&rmi);
 r.x.edi=FP_OFF(&rmi);
 int386x(0x31,&r,&r,&sr);

 if(r.w.cflag)return -1;

 a->s=r.w.cx;
 a->o=r.w.dx;

 return 0;
}

Функция установки обработчика прерывания RM (изменён формат передачи данных - через структуру):

s8 Set_RM_Handler(u8 i,RMA a)
{
 s8 r=0;
 u16 s=a.s;
 u16 o=a.o;
 _asm
 {
  mov ax,0x201
  mov bl,i
  mov cx,s
  mov dx,o
  int 0x31
  jnc OK
  mov r,-1
  OK:
 }
 return r;
}

Решение успешно работает на ведущих и ведомых IRQ, проверял на: MS DOS 7.1, Free DOS, Win98, WinXP, эмуляторе DOSBox.

Необходима КОММЕРЧЕСКАЯ версия ДОС-экстендера - DOS/4G.
Так как некоммерческая версия НЕ поддерживает DPMI-функцию установки RM коллбэка(0x303) и имеет ограничение на максимально используемую память!

Следует отметить, что данный приём оказался совместимым и с другими DOS-extender'ами:

1) causeway
2) pmodew (для обработки прерываний он не использует V86, а переходит в настоящий RM)
3) dos32

И что самое интересное, они в СВЕЖИХ версиях - поддерживают обработку всех аппаратных прерываний (IRQ: ведущие и ведомые).

И можно просто сделать:

_dos_setvect(0x72,SB16_New); // INT 0x72 - IRQ10

или с помощью DPMI функции 0x205 (установка обработчика прерывания PM).

И оно будет работать, в отличие от DOS4G, DOS4GW.

Так же следует отметить, что версии расширителей ДОС, входящие в состав Open Watcom - СТАРОЙ версии, поэтому описаный здесь трюк будет как нельзя кстати!

Надеюсь, выложенная здесь информация будет кому-то полезной/интересной.

Всем, кто дочитал - СПАСИБО! - за проявленный интерес! :)

Данный приём успешно работает в игре Gradius III Total Terror (порт для *DOS): https://gamedev.ru/projects/forum/?id=244656

Ну и на последок, ссылка на FAQ по Watcom C/C++: http://www.codenet.ru/progr/cpp/wfaq.php


#1
6:59, 15 июля 2019

Круто. :) Обожаю такие штуки.

#2
(Правка: 13:39) 13:38, 15 июля 2019

Нужно 3Д рендер под ДОС на шейдерах повесить на обработчик прерывания по кадровому синхроимпульсу.

#3
17:18, 18 июля 2019

увы, ПК со времён VGA дисплеев не имеет внешнего прерывания по кадровому синхроимпульсу (не говоря уже о строчном).

Только порт 0x3DA

#4
(Правка: 21:49) 21:49, 19 июля 2019

Gradius
Номер прерывания звуковой карты задаётся номером pci порта и ты не можешь програмно перенести номер прерывания под досом на другой номер. Почему ты акцентируешь внимание на ведомом контроллере и конкретно 10 том прерывании?. Ведь под досом без разницы под каким номером и на контроллере обрабатывать прерывание.

Я предпочитаю обрабатывать прерывание от dma звуковой карты, чтобы подгрузить следующий чанк музыкального буфера из файла в память.

С dos4gw ты можешь себе позволить загрузить все необходимые музыки одним непрерывным буфером и если тебе не нужно миксовать их, то просто ставишь указатель дма на начало буфера и пусть он себе спокойно проигрыаается, тогда можно не парится о прерываниях

#5
13:56, 20 июля 2019

Fedor1995
> Номер прерывания звуковой карты задаётся номером pci порта и ты не можешь
> програмно перенести номер прерывания под досом на другой номер. Почему ты
> акцентируешь внимание на ведомом контроллере и конкретно 10 том прерывании?.
> Ведь под досом без разницы под каким номером и на контроллере обрабатывать
> прерывание.
>
> Я предпочитаю обрабатывать прерывание от dma звуковой карты, чтобы подгрузить
> следующий чанк музыкального буфера из файла в память.
>
> С dos4gw ты можешь себе позволить загрузить все необходимые музыки одним
> непрерывным буфером и если тебе не нужно миксовать их, то просто ставишь
> указатель дма на начало буфера и пусть он себе спокойно проигрыаается, тогда
> можно не парится о прерываниях

Так я и задаю в настройках BIOS через PCI номер прерывания на PCI слот где карта.
Отключив дисковод, LPT и COM порты стали свободны прерывания: 3,4,5,7,10,11.
Все варианты работают.

Какой IRQ у ДМА ?  Его ставит звуковая карта.

Про непрерывность я знаю, у меня был прицел в портирование на игровую консоль, где памяти всего 32 МБ :)

Подробнее тут, последний пост:

https://gamedev.ru/projects/forum/?id=244656

#6
14:28, 20 июля 2019

Gradius
> Про непрерывность я знаю, у меня был прицел в портирование на игровую консоль, где памяти всего 32 МБ :)
Игровая консоль с досом? о_O

#7
21:33, 20 июля 2019

Gradius
> Какой IRQ у ДМА ?  Его ставит звуковая карта.
Да ну нет же. Это материнка ставит в зависимости от того какой дма контроллер выделила звуковухе под данный конкретный pci порт

#8
17:41, 21 июля 2019

Gradius
Sorry за оффтоп, ностальгнуло...
Я когда-то всё это делал на голом ДОС без экстендеров, без каких-либо драйверов, только QuickBasic и железный SB16, работали запись и воспроизведение.

#9
(Правка: 2:11) 2:10, 22 июля 2019

Mikle
> Я когда-то всё это делал на голом ДОС без экстендеров
внезапно без экстендера это тривиально

#10
12:59, 24 июля 2019

*Lain*
> Игровая консоль с досом? о_O

Игровая консоль со своим загрузчиком.  Свобода как в ДОС-е и может даже ещё лучше! Монопольное владение ресурсов! )

#11
13:03, 24 июля 2019

Mikle
> Я когда-то всё это делал на голом ДОС без экстендеров, без каких-либо
> драйверов, только QuickBasic и железный SB16, работали запись и
> воспроизведение.

Ну я тоже писал переход в защищённый режим и делал обработку прерываний в защищённом режиме - клавиатура, таймер.  Но проблема голого защищённого режима в том, что мы не можем вызывать функции BIOS и Video BIOS, так как они написаны для реального режима.  Прийдётся писать самому на уровне портов, регистров.  А тут уже несовместимость по аппаратной части!

Поэтому тут только прыгать временно в реальный режим (привет, PMODE/W:) ) или в виртуальный V86 (остальные ДОС-экстендеры).

Попутно, заметил, что эмуляция SB 16 на SBLive требует EMM386, а он иногда конфликтует с ДОС-экстендерами.  В итоге, игры Duke3D, Tube, HI-Octane  - идут недолго, до первого Exception в ДОС-экстендере.  Часто это General Fault 0xD.

#12
(Правка: 13:14) 13:10, 24 июля 2019

*Lain*
> внезапно без экстендера это тривиально

Это, скажем, - сложно.  без документации.  Особенно с видеокартой.  Где программная модель на разных картах очень отличается.

Например, VESA функция переключения окна видеопамяти - 0x4F05. На разных картах по-разному. Дизассемблировал Video-BIOS для nVidia, ATI, S3.  У всех разный подход.  регистр переключения банка видеопамяти сидит на портах 0x3D4/0x3D5.  Там же сидят и регистры расширения палитры с 6:6:6 на 8:8:8  и многое другое интересное.  Но беда одна - на разных картах это всё по-разному.  А VESA всех производятлов карт нагибает раком!  ))) Так что сдандарт!  Жаль что Sound Blaster так не нагибает производятлов звуковых карт ((( На звук нет стандарта!  В идеале - PCI Bus Master + общие стандартные порты решили бы проблему!

Можно использовать LFB и отказаться от переключения видеостраниц.  В чистом DOS и под "Чикагой" (Win9x) это прокатит (функция DPMI Map physical to linear 0x800 in 0x31,  в ДОСе вообще Linear=Physical )))  А вот в XP такое уже не работает, жаль!  Это к вопросу - "зачем переключать банки?"  В XP VESA 0x4F05 работает.


Есть ещё такая звучка CrystalCS42xx.  Так у неё вообще портов нет.  Регистры лежат в вeрхних адресах памяти.  Драйвер ДОС переводит процессор во FLAT Unreal,устанавливает лимит сегментного регистра GS в 4 ГБ и спокойно забирает, пишет данные туда )))  Не совсем удобно,с  портами не надо переходить ни в какие 32-битные режимы ))

 

#13
13:28, 24 июля 2019

Gradius
> я тоже писал переход в защищённый режим и делал обработку прерываний в защищённом режиме
Так я же делал это в реальном режиме, SB16 был настоящий, железный.
Gradius
> эмуляция SB 16 на SBLive требует EMM386, а он иногда конфликтует с
> ДОС-экстендерами
А другие драйверы EMS, типа QEMM, не могут помочь?

#14
(Правка: 1:36) 1:35, 25 июля 2019

Mikle
> Так я же делал это в реальном режиме, SB16 был настоящий, железный.
Так вот писать с расширителем часто сложнее. Приходится заниматься заполнением дискрипторов защиты. Или свои дрова писать. Или писать переходы из режима в режим. В рилмоде все именно что тривиальнее

Страницы: 1 2 Следующая »
ФлеймФорумЖелезо