Как создать функцию сна в 16-битной MASM-сборке x86?
Я пытаюсь создать процедуру сна/задержки в 16-битной MASM-сборке x86, которая будет, скажем, печатать символ на экране каждые 500 мс.
Из исследования, которое я сделал, кажется, что для этого есть три метода - я хотел бы использовать тот, который использует тики ЦП.
Обратите внимание: я запускаю Windows XP через VMWare Fusion на Mac OS X Snow Leopard - я не уверен, что это повлияло на что-либо.
Может ли кто-нибудь указать мне в правильном направлении или предоставить рабочий код, который я могу настроить? Спасибо!
Код, который я нашел, должен печатать "A" на экране каждую секунду, но не работает (я хотел бы использовать миллисекунды в любом случае).
TOP:
MOV AH,2C
INT 21
MOV BH,DH ; DH has current second
GETSEC: ; Loops until the current second is not equal to the last, in BH
MOV AH,2C
INT 21
CMP BH,DH ; Here is the comparison to exit the loop and print 'A'
JNE PRINTA
JMP GETSEC
PRINTA:
MOV AH,02
MOV DL,41
INT 21
JMP TOP
РЕДАКТИРОВАТЬ: Следуя рекомендациям GJ, выполните рабочую процедуру. Просто назовите его
DELAY PROC
TIMER:
MOV AH, 00H
INT 1AH
CMP DX,WAIT_TIME
JB TIMER
ADD DX,3 ;1-18, where smaller is faster and 18 is close to 1 second
MOV WAIT_TIME,DX
RET
DELAY ENDP
Ответы
Ответ 1
На самом деле вы можете использовать функцию прерывания BIOS ROM 1Ah 00h, 'Read Current Clock Count'. Или вы можете прочитать dword по адресу $40: $6C, но вы должны обеспечить чтение атома. Он увеличивается на MS-DOS примерно на 18,2 Гц.
Для получения дополнительной информации читайте: DOS Clock
Ответ 2
Это невозможно сделать в чистом MASM. Все старые трюки для установки фиксированной задержки работают в предположении, что вы полностью контролируете машину и являетесь единственным потоком, работающим на процессоре, так что, если вы подождете 500 миллионов циклов, прошло бы ровно 500,000,000/f
секунд (для CPU с частотой f
); это будет 500 мс для процессора 1 ГГц.
Поскольку вы работаете в современной операционной системе, вы делитесь процессором со многими другими потоками (в том числе с ядром - независимо от того, что вы делаете, вы не можете перенести приоритет над ядром!), так что ждать 500 миллионов циклы только в вашей нити означают, что в реальном мире происходит более 500 миллионов циклов. Эта проблема не может быть решена только кодом пользователя; вам понадобится сотрудничество с ядром.
Правильный способ решить эту проблему - посмотреть, какая функция Win32 API приостановит ваш поток за указанное количество миллисекунд, а затем просто вызовет эту функцию. Вы должны иметь возможность делать это непосредственно из сборки, возможно, с дополнительными аргументами для вашего компоновщика. Или, может быть, системный вызов NT для выполнения этой функции (у меня очень мало опыта с системными вызовами NT, и, честно говоря, я понятия не имею, как выглядит таблица вызовов NT, но функция сна - это то, что я могу ожидаем увидеть). Если системный вызов доступен, то выдача прямого системного вызова из сборки, вероятно, является самым быстрым способом сделать то, что вы хотите; это также наименее портативный (но тогда вы пишете сборку!).
Изменить. Глядя на таблицу системных вызовов NT,, похоже, не связаны с вызовами спать или получать дату и время (например, ваш исходный код использует), но есть несколько системных вызовов для настройки таймеров и запросов. Спиннинг, пока вы ожидаете, что таймер достигнет желаемой задержки, является одним эффективным, если неэлегантным, решением.
Ответ 3
использовать INT 15h, функция 86h:
Вызов с:
AH = 86h
CX: DX = интервал в uS
Ответ 4
Ну, тогда. Старинный, не постоянный, энергозатратный цикл задержки, который замедлит работу других потоков, выглядит следующим образом:
delay equ 5000
top: mov ax, delay
loopa: mov bx, delay
loopb: dec bx
jnc loopb
dec ax
jnc loopa
mov ah,2
mov dl,'A'
int 21
jmp top
Задержка квадратична по отношению к константе. Но если вы воспользуетесь этой петлей задержки, где-нибудь в мире умрет молодой невинный котенок.
Ответ 5
Я не тестировал этот код, но концепция должна работать...
Сохранить/восстановить es register необязательно!
Проверьте код!
DelayProcedure:
push es //Save es and load new es
mov ax, 0040h
mov es, ax
//Pseudo atomic read of 32 bit DOS time tick variable
PseudoAtomicRead1:
mov ax, es:[006ch]
mov dx, es:[006eh]
cmp ax, es:[006ch]
jne PseudoAtomicRead1
//Add time delay to dx,ax where smaller is faster and 18 is close to 1 second
add ax, 3
adc dx, 0
//1800AFh is last DOS time tick value so check day overflow
mov cx, ax
mov bx, dx
//Do 32 bit subtract/compare
sub cx, 00AFh
sbb dx, 0018h
jbe DayOverflow
//Pseudo atomic read of 32 bit DOS time tick variable
PseudoAtomicRead2:
mov cx, es:[006ch]
mov bx, es:[006eh]
cmp cx, es:[006ch]
jne PseudoAtomicRead2
NotZero:
//At last do 32 bit compare
sub cx, ax
sbb bx, dx
jae Exit
//Check again day overflow because task scheduler can overjumps last time ticks
inc bx //If no Day Overflow then bx = 0FFh
jz PseudoAtomicRead2
jmp Exit
DayOverflow:
//Pseudo atomic read of 32 bit DOS time tick variable
PseudoAtomicRead3:
mov ax, es:[006ch]
mov dx, es:[006eh]
cmp dx, es:[006ch]
jne PseudoAtomicRead3
//At last do 32 bit compare
sub ax, cx
sbb dx, bx
jb PseudoAtomicRead3
Exit:
pop es //Restore es
ret
Ответ 6
. Проблема со всеми приведенными выше примерами кода заключается в том, что они используют неблокирующие операции. Если вы изучите использование ЦП в течение относительно длительного периода ожидания, вы увидите, что он работает примерно на 50%. Мы хотим использовать некоторые функции DOS или BIOS, которые блокируют выполнение, так что загрузка процессора составляет около 0%.
.. Оффман, функция BIOS INT 16h, AH = 1 приходит на ум. Возможно, вы сможете разработать процедуру, которая вызывает эту функцию, а затем вставляет нажатие клавиши в буфер клавиатуры, когда время истекло. Есть много проблем с этой идеей;), но это может быть пищей для размышлений. Вероятно, вы будете писать какой-то обработчик прерываний.
.. В 32-битном API-интерфейсе Windows есть функция "Сон". Полагаю, вы могли бы поднять это.