Laaca
Czech republic, 14.08.2021, 07:31 |
How to keep resident only a part of the program (Developers) |
I am thinking about very tiny resident utility which hooks the INT16 interrupt. I know that "high-level" languages like pascal or C have something like function Keep (which apparently calls INT21h/AH=31h). But this function keep a whole program in memory.
My problem is that the program must be quite big and complex (it performs some hardware detections and scanning). But after gathering all needed informations is possible to keep in memory only very small module.
All examples about KEEP function look similarly:
{ Keep.PAS }
{$M $800,0,0 } { 2K stack, no heap }
{ This program makes a click after each keypress.}
uses Crt, Dos;
var
KbdIntVec : Procedure;
{$F+}
procedure Keyclick; interrupt;
begin
if Port[$60] < $80 then
{ Clicks after keypress }
begin
Sound(5000);
Delay(1);
Nosound;
end;
inline ($9C); { PUSHF -- Push flags }
{ Call old driver }
KbdIntVec;
end;
{$F-}
begin
{ Insert into interrupt chain }
GetIntVec($9,@KbdIntVec);
SetIntVec($9,Addr(Keyclick));
Keep(0); { Teminate and keep resident }
end.
This program works but it remains all int the memory. I would like to keep only the procedure KeyClick but nothing more.
Is possible solution to allocate a additional memory block via INT21h/AH=48h, copy the code of KeyClick into this block and the program end not with Keep but hith Halt? --- DOS-u-akbar! |
tkchia
14.08.2021, 08:49 (edited by tkchia, 14.08.2021, 09:02)
@ Laaca
|
How to keep resident only a part of the program |
Hello Laaca,
> I am thinking about very tiny resident utility which hooks the INT16
> interrupt. I know that "high-level" languages like pascal or C have
> something like function Keep (which apparently calls INT21h/AH=31h). But
Actually the int 0x21, ah = 0x31 interface does allow one to specify how many paragraphs of memory one wants to keep resident. Turbo C and Watcom C also accept a paragraph count as a parameter to keep(...) and (respectively) _dos_keep(...).
But it might be hard to use this correctly in the context of high-level code. You need to arrange your program binary code in such a way that the resident portions will come before (most of) the transient portions (and you must take library functions into account too...). I am not sure what features Free Pascal (e.g.) has that might facilitate this sort of thing.
> Is possible solution to allocate a additional memory block via
> INT21h/AH=48h, copy the code of KeyClick into this block and the program
> end not with Keep but hith Halt?
That will likely not work --- DOS will free up all memory blocks allocated by your program, including those from int 0x21, ah = 0x48.
Thank you! --- https://gitlab.com/tkchia · https://codeberg.org/tkchia · 😴 "MOV AX,0D500H+CMOS_REG_D+NMI" |
Laaca
Czech republic, 14.08.2021, 10:32
@ tkchia
|
How to keep resident only a part of the program |
> Actually the int 0x21, ah = 0x31 interface does allow one to specify how
> many paragraphs of memory one wants to keep resident. Turbo C and Watcom C
> also accept a paragraph count as a parameter to keep(...) and
> (respectively) _dos_keep(...).
>
> But it might be hard to use this correctly in the context of high-level
> code.
Exactly. I saw in documentatin that INT 21h/AH=31h has a parameter "number of parameters to keep resident" but I don't know how to use it in the pascal code. BTW, in pascal has the Keep function only argument for the return code.
But I know that many assembler programs CAN do it. They use the INT 21h/AH=31h call and determine which portion of code to thow away and which to keep? Or they use some other mechanism? --- DOS-u-akbar! |
tom
Germany (West), 14.08.2021, 11:28
@ tkchia
|
How to keep resident only a part of the program |
> Hello Laaca,
>
> > I am thinking about very tiny resident utility which hooks the INT16
> > interrupt. I know that "high-level" languages like pascal or C have
> > something like function Keep (which apparently calls INT21h/AH=31h). But
>
> Actually the int 0x21, ah = 0x31 interface does allow one to specify how
> many paragraphs of memory one wants to keep resident. Turbo C and Watcom C
> also accept a paragraph count as a parameter to keep(...) and
> (respectively) _dos_keep(...).
>
> But it might be hard to use this correctly in the context of high-level
> code. You need to arrange your program binary code in such a way that the
> resident portions will come before (most of) the transient portions (and
> you must take library functions into account too...). I am not sure what
> features Free Pascal (e.g.) has that might facilitate this sort of thing.
>
> > Is possible solution to allocate a additional memory block via
> > INT21h/AH=48h, copy the code of KeyClick into this block and the program
> > end not with Keep but hith Halt?
>
> That will likely not work --- DOS will free up all memory blocks
> allocated by your program, including those from int 0x21, ah = 0x48.
while true (this wouldn't work), you can make it work by using the undocumented feature that DOS_realloc(para, size) also sets the PSP of the allocated memory, and on exit(), DOS will only free allocated memory with your PSP.
so a sequence like
needed_bytes = 1024;
resident_seg = DOS_alloc(needed_bytes);
my_PSP = DOS_GET_PSP();
DOS_SET_PSP(0x60); program 0x60 will never exit
DOS_realloc(resident_seg, needed_bytes);
DOS_SET_PSP(my_PSP);
memcpy(MK_FP(resident_seg,0), my code, needed_bytes);
point interrupt vector to the copied code
...
exit()
would work in principle (remove the large program from memory, leave needed_bytes in memory).
of course, your code would really quickly implode because you are calling the runtime library (sound(), delay()) and you have little to no control over where the runtime will be loaded.
time to change the language
Pascal is just not the right language for this kind of stuff. |
tom
Germany (West), 14.08.2021, 11:40
@ Laaca
|
How to keep resident only a part of the program |
> But I know that many assembler programs CAN do it. They use the INT
> 21h/AH=31h call and determine which portion of code to thow away and which
> to keep? Or they use some other mechanism?
usually, you arrange your code layout 'manually' like
resident_code
resident_stuff
transient_code
more_stuff
and make sure that resident_stuff never calls anything in more_stuff after going resident. in ASM this easy, in C at least doable.
for a PASCAL example, you might look at the sources of FreeDOS KEYB.EXE hich I think is written in (some strange sort of) Pascal. |
marcov
14.08.2021, 13:18
@ Laaca
|
How to keep resident only a part of the program |
> I am thinking about very tiny resident utility which hooks the INT16
> interrupt. I know that "high-level" languages like pascal or C have
> something like function Keep (which apparently calls INT21h/AH=31h). But
> this function keep a whole program in memory.
> My problem is that the program must be quite big and complex (it performs
> some hardware detections and scanning). But after gathering all needed
> informations is possible to keep in memory only very small module.
> All examples about KEEP function look similarly:
Did you walk through the TSR section of SWAG ? |
RayeR
CZ, 14.08.2021, 15:49
@ tom
|
How to keep resident only a part of the program |
> and make sure that resident_stuff never calls anything in more_stuff after
> going resident. in ASM this easy, in C at least doable.
I see problem there when resident code would call anything from runtime library that would be freed then. To avoid this probably the best to write TSR part in inline ASM or check compiled code if TSR part doesn't have any depencies. --- DOS gives me freedom to unlimited HW access. |
Laaca
Czech republic, 14.08.2021, 20:18
@ RayeR
|
How to keep resident only a part of the program |
> I see problem there when resident code would call anything from runtime
> library that would be freed then. To avoid this probably the best to write
> TSR part in inline ASM or check compiled code if TSR part doesn't have any
> depencies.
It is no problem at all. I plan to write the TSR part in inline assembler.
But I am considering another attitude. At first to load tiny assembler driver hooking the keyboard interrupt. Then to call a heavy pascal program which will locate the TSR module in memory and patch the code. (kind of self-modyfying code) --- DOS-u-akbar! |
ecm
Düsseldorf, Germany, 15.08.2021, 13:47
@ tkchia
|
How to keep resident only a part of the program |
> > Is possible solution to allocate a additional memory block via
> > INT21h/AH=48h, copy the code of KeyClick into this block and the program
> > end not with Keep but hith Halt?
>
> That will likely not work --- DOS will free up all memory blocks
> allocated by your program, including those from int 0x21, ah = 0x48.
You can make it work by hacking the MCB to be owned by someone other than your process. Tom mentions a way to do that but I think it is cleaner to just access the MCB directly instead of setting the current PSP to another, possibly invalid value.
In my tsr example I first relocate the process (to avoid memory fragmentation) then allocate a new block for the resident portion using service 48h, then I hack the MCB's owner and name fields to make the resident block survive the final process termination (using service 4Ch). I described this in some detail in https://retrocomputing.stackexchange.com/questions/20001/how-much-of-the-program-segment-prefix-area-can-be-reused-by-programs-with-impun/20006#20006
By the way, if you do use service 31h you should close all file handles manually, unless you intend to use them while resident. --- l |
RayeR
CZ, 16.08.2021, 03:22
@ Laaca
|
How to keep resident only a part of the program |
> But I am considering another attitude. At first to load tiny assembler
> driver hooking the keyboard interrupt. Then to call a heavy pascal program
> which will locate the TSR module in memory and patch the code. (kind of
> self-modyfying code)
Yes, it could be possible but you will have to fit in preallocated space. Maybe you can allocate more at the beginning and then free memory that is not longer needed but it will cause some fragmentation. --- DOS gives me freedom to unlimited HW access. |
CandyMan
27.08.2021, 17:46
@ Laaca
|
How to keep resident only a part of the program |
If you want to write resident programs then use assembler. Here is my old program, replacement for the DOS keyboard driver, written in fasm. The resident part is between KeyStart and KeybSize.
Here code:
org 100h
mov dx,Information+1
call Print
mov ax,3509h
int 21h
mov word [KeyStart+Old09h+0],bx
mov word [KeyStart+Old09h+2],es
mov ah,$AD ; Disable Controller
call SetStat
in al,21h ;\
or al,00000010b ; > Disable IRQ1
out 21h,al ;/
mov ah,20h ; Read Command Byte
call GetEcho
push ax
mov ah,$AA ; Keyboard Self Test
call GetEcho
jnz NotAnswer
cmp al,55h
jnz NotAnswer
mov ah,$F4 ; Enable Keyboard
call SetOut
mov ah,$F3 ; Set Typematic Rate&Delay
call SetOut
mov ah,0 ; Max Rate&Delay
call SetOut
NotAnswer: pop bx ; Restore Command Byte
or bl,1
mov ah,60h ; Set Command Byte
call SetEcho
mov ah,$AE ; Enable Controller
call SetStat
in al,21h ;\
and al,11111101b ; > Enable IRQ1
out 21h,al ;/
call GetMem
jc Quit
xor di,di
mov cx,KeybSize
mov si,KeyStart
rep movsb
push es
pop ds
mov ax,2509h
mov dx,New09h
int 21h
ret
Quit: mov dx,NoMem
Print: mov ah,9
int 21h
ret
Allocate: mov ah,48h
mov bx,Paragraphs
int 21h
ret
GetMem: mov ax,5802h
int 21h
xor ah,ah
mov si,ax
cmp al,1
je LoadUpper
mov ax,5803h
mov bx,1
int 21h
jc NoUpper
LoadUpper: call Allocate
pushf
push ax
mov ax,5803h
mov bx,si
int 21h
pop ax
popf
jnc IsOK
NoUpper: mov ah,4Ah
mov bx,-1
push ds
pop es
int 21h
sub bx,Paragraphs+1
jc MemEnd
mov ah,4Ah
int 21h
jc MemEnd
call Allocate
jc MemEnd
IsOK: push ax
dec ax
mov es,ax
inc ax
mov di,1
cld
stosw
add di,5
mov si,Information
movsw
movsw
movsw
movsw
pop es
MemEnd: ret
; Wait Not Data in port 60h\64h
WaitNoData: xor cx,cx
.WaitRetry: in al,64h
out $EB,al
test al,00000010b
jz .Ok
loop .WaitRetry
inc cx
.Ok: ret
; Wait No TimeOut (Data transmit completed)
WaitNoTime: xor cx,cx
.WaitTime: in al,64h
test al,00100000b
jz .Ok
loop .WaitTime
inc cx
.Ok: ret
; Wait Data in port 60h
WaitOutput: mov ah,0Ch
.CheckAgain: xor cx,cx
.CheckOutput: in al,64h
out $EB,al
test al,00000001b
jnz .IsOutput ; Ok!
loop .CheckOutput
dec ah ; \ Wait error!
jnz .CheckAgain ; / Wait again
.IsOutput: lahf ; \
xor ah,40h ; > Negate ZF
sahf ; /
ret
; Set Command in AH to Status Register
SetStat: cli
call WaitNoData
jnz .Err
mov al,ah
out 64h,al
call WaitNoData
.Err: sti
ret
; Set Command in AH to Output Register
SetOut: cli
call WaitNoTime
jnz .Err
in al,60h
call WaitNoData
jnz .Err
mov al,ah
out 60h,al
call WaitNoData
call WaitOutput
jnz .Err
in al,60h
cmp al,$FA
.Err: sti
ret
; Set Command in AH to Status Register &
; get echo\command from Output Register
GetEcho: cli
call SetStat
jnz .Err
call WaitOutput
jnz .Err
in al,60h
.Err: sti
ret
; Set Command in AH to Status Register &
; set echo\command in BL from Output Register
SetEcho: cli
call SetStat
jnz .Err
call WaitNoData
jnz .Err
mov al,bl
out 60h,al
call WaitNoData
.Err: sti
ret
ReadInput: cli
mov bx,$FF0F
call WaitNoData
jnz .Err
.Retry: mov ah,$C0
call GetEcho
jnz .Err
cmp al,bh
je .Ok
mov bh,al
xor cx,cx
loop $
dec bl
jnz .Retry
.Err: inc cx
.Ok: sti
ret
Information db '$KEYB-PL v1.1',13,10,36
NoMem db 13,10,'Not enough memory',13,10,36
KeyStart: org 0
KeyTable: db $1E,$2E,$12,$26,$31,$18,$1F,$2C,$2D
db $A5,$86,$A9,$88,$E4,$A2,$98,$BE,$AB
db $A4,$8F,$A8,$9D,$E3,$E0,$97,$BD,$8D
New09h: push ax bx ds si
push 40h
pop ds
mov ax,[1Ch]
mov si,ax
inc ax
inc ax
cmp ax,[82h]
jne @1
mov ax,[80h]
@1: cmp ax,[1Ah]
je Return
xchg bx,ax
mov ax,[17h]
test al,8
je Return
xchg ah,al
test al,2
jne Return
in al,60h
cmp al,31h ; Max z KeyTable
ja Return
push di
push cx
push es
push cs
pop es
cld
mov cx,9
xor di,di ; mov di,KeyTable
repnz scasb
pop es
pop cx
jne Exit
xchg ah,al
test al,40h
je @2
and al,3 ; test al,3
jne @4
jmp @3
@2: and al,3 ; test al,3
je @4
@3: add di,9
@4: cbw ; xor ah,ah
mov al,[cs:di+8]
mov [si],ax
mov [1Ch],bx
mov al,20h
out 20h,al
pop di si ds bx ax
iret
Exit: pop di
Return: pop si ds bx ax
db $EA
Old09h dd ?
KeybSize:
Paragraphs = ($+15)/16 |
bretjohn
Rio Rancho, NM, 27.08.2021, 21:22
@ ecm
|
How to keep resident only a part of the program |
I use ecm's TSR installation method (or at least a slight variation of it) in most of my TSR's nowadays. It works really well. It even allows you to automatically install the program into upper memory without needing to use LOADHI. I'm not sure you could do it (at least not easily) in anything other than ASM, though. ASM is more difficult to use, but can give you complete control over everything, including the relationships of which parts of the program get installed in which parts of memory.
I will say that INT 21.31 is probably the easiest way to install a TSR, but it is not the best option (by far). |
bretjohn
Rio Rancho, NM, 27.08.2021, 21:47
@ Laaca
|
How to keep resident only a part of the program |
From a performance perspective, this would not be a good way to implement a keyclick program. You have the sound being turned on, waiting, and turning off all inside an INT 09 (hardware IRQ) handler, which is not a good idea. The keyboard is a very high priority IRQ (the only one being higher is the clock at INT 08) and the machine will "stall" inside the IRQ 09 handler waiting for the sound to stop. This could cause some serious performance issues in other programs.
There are a few different ways around this. What may be the easiest/best in this situation would be to simply start the sound within the INT 09 handler and then stop it after a delay using one of the timer interrupts (INT 08 or INT 70h). |
Laaca
Czech republic, 08.03.2022, 12:24
@ Laaca
|
How to keep resident only a part of the program |
I succeeded to solve this problem!
It is possible to create a big program in the Turbopascal and keep only a tiny TSR in the memory and unloaded the rest.
The practical implementation I used here.
The principle is to write a small assembler procedure which will be used as a TSR. In this procedure keep a area which will be modified on the fly to store there some data.
Then move this procedure/module just after PSP of the main program. (PSP+256)
Now is it a little bit dangerous because a part of the pascal system library is overwritten so it is not safe to call many RTL functions.
In assembler redirect the calling interrupt vector to PSP+256
Then call the DOS function 31h and into DX store the 16+TSR_size/16.
And it works
The question is: Do I need to keep the PSP? Is it safe to perform the relocation not to PSP+256 but to PSP+128? Or to PSP+0? --- DOS-u-akbar! |
tom
Germany (West), 08.03.2022, 12:51
@ Laaca
|
How to keep resident only a part of the program |
> The question is: Do I need to keep the PSP?
yes, at least part of it is used on exit() 'terminate program'
lime parent PSP, INT 23 address and similar.
> Is it safe to perform the
> relocation not to PSP+256 but to PSP+128? Or to PSP+0?
PSP+80 should be enough as you probably don't use FCB's, and have used your command line (@PSP+128) a long time ago. |
tkchia
08.03.2022, 14:20
@ tom
|
How to keep resident only a part of the program |
Hello Laaca, hello tom,
> > Is it safe to perform the
> > relocation not to PSP+256 but to PSP+128? Or to PSP+0?
> PSP+80 should be enough as you probably don't use FCB's, and have used your
> command line (@PSP+128) a long time ago.
Actually, if the program does not use FCBs, one can probably throw away everything above PSP:0x5c (where the first default FCB starts).
(Or perhaps even everything above PSP:0x50. There is a "int 0x21; retf" sequence at PSP:0x50, but other than that there is nothing between PSP:0x50 and PSP:0x5c.)
Thank you! --- https://gitlab.com/tkchia · https://codeberg.org/tkchia · 😴 "MOV AX,0D500H+CMOS_REG_D+NMI" |
rr
Berlin, Germany, 08.03.2022, 17:32
@ Laaca
|
How to keep resident only a part of the program |
> I succeeded to solve this problem!
> It is possible to create a big program in the Turbopascal and keep only a
> tiny TSR in the memory and unloaded the rest.
> The practical implementation I used here.
>
> The principle is to write a small assembler procedure which will be used as
> a TSR. In this procedure keep a area which will be modified on the fly to
> store there some data.
> Then move this procedure/module just after PSP of the main program.
> (PSP+256)
> Now is it a little bit dangerous because a part of the pascal system
> library is overwritten so it is not safe to call many RTL functions.
> In assembler redirect the calling interrupt vector to PSP+256
> Then call the DOS function 31h and into DX store the 16+TSR_size/16.
> And it works
A bit late, because you already solved it, but https://github.com/FDOS/keyb might be of interest. --- Forum admin |
bretjohn
Rio Rancho, NM, 08.03.2022, 19:19
@ tkchia
|
How to keep resident only a part of the program |
As far as to how much of the PSP you need to keep after the TSR is installed, the answer is usually 0. Except in very rare cases, the PSP is only used while the TSR is being installed but is not used by the TSR itself. So, you can overwrite the PSP with whatever you want. I've used the PSP for various different things in my TSR's, the most useful probably being as a TSR Stack (complicated TSR's should always provide their own stack space).
You do need to make sure you don't overwrite the PSP before the TSR is actually installed, though. That can be a little tricky in high-level languages like Pascal since you don't necessarily know exactly when or how the PSP is used. |