rommel@lan.informatik.tu-muenchen.dbp.de (Kai-Uwe Rommel) (01/05/90)
Some time ago I posted my version of MicroEMACS 3.10 to this news group. Of course it's not the best way to do the input from keyboard and mouse with busy waiting. Now I tried to use two separate threads which wait for keyboard and mouse input and write the characters to a global buffer from which the main thread now reads his input. For testing, I extracted all code related to this problem to a stand alone program. The source is appended to this article. I do not have much experience with multiple threads. Can someone please tell me if something is wrong ? The test program works but I have the following problems: 1. In checkmouse() you will find a call to MouReadEventQue() with MOUSE_NOWAIT! When I use MOUSE_WAIT instead, I expect the thread to sleep inside the call when no mouse input is available. But actually THE OTHER (keyboard) thread is blocked and does not accept any input or the main thread is blocked and cannot write to the screen. Why ? As a workaround I use MOUSE_NOWAIT but only 10 times per second (the DosSleep(100L) call !). 2. The KbdGetStatus() call (commented out) blocks the mouse thread when the keyboard thread is waiting for input. Why ? I need the shift state to create the mouse event characters correctly. Except these two problems the serialization with the two semaphores works well. But when I include the code into MicroEMACS other problems arise which do no occur with the stand alone model (yes, the correct compiling and linking options and libraries were used): 3. The whole editor blocks until I insert a DosSleepCall(10L) into the keyboard thread loop too. When I trace the program with the debugger, everything works well even without the DosSleep(10L). 4. When the mouse thread got an event he is blocked before he could insert the event into the global buffer. Again, when I trace the threads with the debugger, everything works well. I already tried DosSetPrty(PRTYS_THREAD, PRTYC_IDLETIME, 0, threadid) calls to reduce the priority of the waiting threads - no change. What am I doing wrong ? Chris Adie suggested another solution using a semaphore for the mouse thread and another for the keyboard thread and a DosMuxSemWait() in the main thread. But I want the kbd and mouse threads to run independently and writing their input to the global buffer because this buffer then serves as a typeahead buffer. Kai Uwe Rommel Munich rommel@lan.informatik.tu-muenchen.dbp.de ------------------------------------------------------------ #include <stdio.h> #include <process.h> #include <stdlib.h> #define INCL_BASE #define INCL_NOPM #include <os2.h> #include <conio.h> #define KEYBUFSIZE 64 #define THREADSTACK 4096 static int mouseflag = 0; static HMOU hmouse; static USHORT mousemask; static int buttonmask[3] = {MOUSE_BN1_DOWN, MOUSE_BN3_DOWN, MOUSE_BN2_DOWN}; static USHORT in_buf[KEYBUFSIZE], in_next, in_last; static void *kbd_stack, *mouse_stack; static ULONG semAccess = 0L, semEmpty = 0L; void in_init(void) /* initialize the input buffer */ { DosSemRequest(&semAccess, SEM_INDEFINITE_WAIT); DosSemSet(&semEmpty); in_next = in_last = 0; DosSemClear(&semAccess); } int in_check(void) /* is the input buffer non-empty? */ { int empty; DosSemRequest(&semAccess, SEM_INDEFINITE_WAIT); empty = (in_next == in_last); DosSemClear(&semAccess); return !empty; } void in_put(int event) { DosSemRequest(&semAccess, SEM_INDEFINITE_WAIT); in_buf[in_last++] = event; in_last &= (KEYBUFSIZE - 1); DosSemClear(&semEmpty); DosSemClear(&semAccess); } int in_get(void) /* get an event from the input buffer */ { int event; /* event to return */ DosSemWait(&semEmpty, SEM_INDEFINITE_WAIT); DosSemRequest(&semAccess, SEM_INDEFINITE_WAIT); event = in_buf[in_next++]; in_next &= (KEYBUFSIZE - 1); if (in_next == in_last) DosSemSet(&semEmpty); DosSemClear(&semAccess); return(event); } void checkmouse(void *arg) { MOUEVENTINFO mouInfo; KBDINFO kbdInfo; USHORT wait, old, new, cnt, chr; while (1) { wait = MOU_NOWAIT; MouReadEventQue(&mouInfo, &wait, hmouse); if ( mouInfo.time != 0L ) { kbdInfo.cb = sizeof(kbdInfo); /* KbdGetStatus(&kbdInfo, 0); */ chr = 'a'; if ( kbdInfo.fsState & (LEFTSHIFT | RIGHTSHIFT) ) chr = 'A'; if ( kbdInfo.fsState & CONTROL ) chr = 'A' - '@'; for ( cnt = 0; cnt < 3; cnt++ ) { old = mousemask & buttonmask[cnt]; new = mouInfo.fs & buttonmask[cnt]; if ( old != new ) { in_put(0); in_put(0x8000); in_put(mouInfo.col); in_put(mouInfo.row); in_put(new ? chr + 2 * cnt : chr + 2 * cnt + 1); } } mousemask = mouInfo.fs; } DosSleep(100L); } } void checkkbd(void *arg) { KBDKEYINFO keyInfo; USHORT nextc; while (1) { KbdCharIn(&keyInfo, IO_WAIT, 0); if (keyInfo.chChar == 0 || keyInfo.chChar == 0xE0) { nextc = keyInfo.chScan; in_put(0); in_put(nextc >> 8); /* prefix byte */ in_put(nextc & 255); /* event code byte */ } else in_put(keyInfo.chChar); /* DosSleep(10L); */ } } int get(void) { NOPTRRECT rect; USHORT res; if ( in_check() ) return in_get(); if ( mouseflag ) MouDrawPtr(hmouse); res = in_get(); if ( mouseflag ) { rect.row = rect.col = 0; rect.cRow = 24; rect.cCol = 79; MouRemovePtr(&rect, hmouse); } return res; } void main(void) { USHORT mask, key; in_init(); if ( (kbd_stack = malloc(THREADSTACK)) == NULL || (mouse_stack = malloc(THREADSTACK)) == NULL ) { puts("Cannot allocate thread stacks."); exit(-1); } MouOpen(NULL, &hmouse); mask = MOUSE_BN1_DOWN | MOUSE_BN2_DOWN; MouSetEventMask(&mask, hmouse); mask = 0; MouSetDevStatus(&mask, hmouse); _beginthread(checkmouse, mouse_stack, THREADSTACK, NULL); _beginthread(checkkbd, kbd_stack, THREADSTACK, NULL); do { key = get(); printf("Empty=%08lx Access=%08lx in_next=%d in_last=%d %04x\n", semEmpty, semAccess, in_next, in_last, key); } while (key != 27); exit(0); }
kulokari@cc.helsinki.fi (01/07/90)
In article <1028@tuminfo1.lan.informatik.tu-muenchen.dbp.de>, rommel@lan.informatik.tu-muenchen.dbp.de (Kai-Uwe Rommel) writes: > Some time ago I posted my version of MicroEMACS 3.10 to this news group. > Of course it's not the best way to do the input from keyboard and mouse > with busy waiting. Now I tried to use two separate threads which wait > for keyboard and mouse input and write the characters to a global buffer > from which the main thread now reads his input. > > For testing, I extracted all code related to this problem to a stand > alone program. The source is appended to this article. I do not have > much experience with multiple threads. Can someone please tell me if > something is wrong ? A corrected version of your program is appended. My patches can be identified by the defined variable HRK (my initials). Some notes: 1) When I first tried to compile your program, it did not compile. You use some symbols (like MOU_WAIT) which are not defined in the standard header files of the IBM toolkit v. 1.1. Which toolkit do you use? 2) When the program did compile (after I had checked from manuals what those undefined symbols probably did mean), it did not link. _beginthread was not found in the standard C library. Closer inspection revealed, that you have been using the multithreaded version of the MSC 5.1 library. This is usually (and in this case) quite unnecessary, and to be avoided of at any cost. 3) Then I found that you have installed your C header files in a nonstandard fashion. The header files of the multithreaded library are supposed to be in the MT subdirectory of the default INCLUDE directory, and the #include statement for process.h is usually #include <mt\process.h> 4) A make file with the C flags and libraries documented would also have been helpful. FLAME ON: When you post your code here for others to scrutiny, please make sure that all deviations from standard assumptions are documented. Thank you. FLAME OFF. 5) I replaced the call to _beginthread() with DosCreateThread() and declared far all that should be far. (The thread functions at least.) The corrected code is not dependent of memory model (I used small), and it compiles, links and runs in standard environment. 6) Your code was mostly okay, although I did quite a lot of cleanup here and there. The basic fault, and probably the cause for all your sorrows, was quite simple: Mou and Kbd subsystems are not re-entrant. Only one thread/process at a time can have a call pending. If other processes need to do something to keyboard or mouse (note: keyboard != Kbd, mouse != Mou), they have to use device control calls. I have substituted those where needed. 7) If you need to de-activate the keyboard/mouse handler temporarily, as you will if you are doing uEmacs, then you have additional trouble as documented in my note posted here a couple of days back. The non-re-entrancy of the Mou and Kbd subsystems is not well documented, if at all. It can be read from between the lines, however, when you remember, that all the processes in a screen group have ONE keyboard and ONE mouse, and only ONE process at a time has the input focus (not necessarily the same process for keyboard and mouse). That process can use the Mou a/or Kbd calls. Virtual keyboards may complicate the picture, but that is a subject with which I am not familiar enough yet. --- This is the day and time of the week when all the industrious Finns take a break and go to sauna. So will I. Hope the rest of the world will find something to amuse itself with in the meantime. Hannu Kulokari CC, U of Helsinki kulokari@cc.helsinki.fi (Internet) kulokari@finuh (Bitnet) ------------------------------------------------------------------------ #define HRK 1 #include <stdio.h> #if HRK==0 #include <process.h> #endif #include <stdlib.h> #define INCL_BASE #define INCL_NOPM #include <os2.h> #include <conio.h> #define KEYBUFSIZE 64 #define THREADSTACK 4096 #if HRK /* these constants are NOT defined in the IBM toolkit! */ #define MOU_WAIT 1 #define MOU_NOWAIT 0 #define MOUSE_BN1_DOWN 4 #define MOUSE_BN2_DOWN 16 #define MOUSE_BN3_DOWN 64 #define LEFTSHIFT 2 #define RIGHTSHIFT 1 #define CONTROL 4 #endif static int mouseflag = 0; static HMOU hmouse; static USHORT mousemask; static int buttonmask[3] = {MOUSE_BN1_DOWN, MOUSE_BN3_DOWN, MOUSE_BN2_DOWN}; static USHORT in_buf[KEYBUFSIZE], in_next, in_last; #if HRK /* These pointers must have type, if we are to increment them in the call of DosCreateThread */ static char *kbd_stack, *mouse_stack; #else static void *kbd_stack, *mouse_stack; #endif static ULONG semAccess = 0L, semEmpty = 0L; void in_init(void) /* initialize the input buffer */ { #if HRK==0 /* not needed here */ DosSemRequest(&semAccess, SEM_INDEFINITE_WAIT); DosSemSet(&semEmpty); #endif in_next = in_last = 0; DosSemClear(&semAccess); } #if HRK==0 /* see get() */ int in_check(void) /* is the input buffer non-empty? */ { int empty; DosSemRequest(&semAccess, SEM_INDEFINITE_WAIT); empty = (in_next == in_last); DosSemClear(&semAccess); return !empty; } #endif void in_put(int event) { #if HRK==0 DosSemRequest(&semAccess, SEM_INDEFINITE_WAIT); #endif in_buf[in_last++] = event; in_last &= (KEYBUFSIZE - 1); #if HRK==0 DosSemClear(&semEmpty); DosSemClear(&semAccess); #endif } #if HRK==0 /* see get() */ int in_get(void) /* get an event from the input buffer */ { int event; /* event to return */ DosSemWait(&semEmpty, SEM_INDEFINITE_WAIT); DosSemRequest(&semAccess, SEM_INDEFINITE_WAIT); event = in_buf[in_next++]; in_next &= (KEYBUFSIZE - 1); if (in_next == in_last) DosSemSet(&semEmpty); DosSemClear(&semAccess); return(event); } #endif #if HRK /* must be explicitly declared far when using small model */ void far checkmouse() #else void checkmouse(void *arg) #endif { MOUEVENTINFO mouInfo; #if HRK struct { USHORT fsState; BYTE fNLS; } kbdInfo; #else KBDINFO kbdInfo; #endif USHORT wait, old, new, cnt, chr; while (1) { #if HRK wait = MOU_WAIT; #else wait = MOU_NOWAIT; #endif MouReadEventQue(&mouInfo, &wait, hmouse); if ( mouInfo.time != 0L ) { #if HRK /* Get keyboard shift status. must use device control call, because Kbd subsystem is not re-entrant. Only one thread can have a call pending. */ DosDevIOCtl(&kbdInfo,0L,0x0073,0x0004,0); #else kbdInfo.cb = sizeof(kbdInfo); /* KbdGetStatus(&kbdInfo, 0); */ #endif chr = 'a'; if ( kbdInfo.fsState & (LEFTSHIFT | RIGHTSHIFT) ) chr = 'A'; if ( kbdInfo.fsState & CONTROL ) chr = 'A' - '@'; #if HRK /* You want this to be saved in unbroken sequence */ DosSemRequest(&semAccess,-1L); #endif for ( cnt = 0; cnt < 3; cnt++ ) { old = mousemask & buttonmask[cnt]; new = mouInfo.fs & buttonmask[cnt]; if ( old != new ) { in_put(0); in_put(0x8000); in_put(mouInfo.col); in_put(mouInfo.row); in_put(new ? chr + 2 * cnt : chr + 2 * cnt + 1); } } #if HRK DosSemClear(&semAccess); if (in_next != in_last)DosSemClear(&semEmpty); #endif mousemask = mouInfo.fs; } #if HRK==0 DosSleep(100L); #endif } } #if HRK /* must be explicitly declared far when using small model */ void far checkkbd() #else void checkkbd(void *arg) #endif { KBDKEYINFO keyInfo; USHORT nextc; while (1) { KbdCharIn(&keyInfo, IO_WAIT, 0); #if HRK DosSemRequest(&semAccess,-1L); #endif if (keyInfo.chChar == 0 || keyInfo.chChar == 0xE0) { nextc = keyInfo.chScan; in_put(0); in_put(nextc >> 8); /* prefix byte */ in_put(nextc & 255); /* event code byte */ } else in_put(keyInfo.chChar); #if HRK DosSemClear(&semAccess); DosSemClear(&semEmpty); #endif /* DosSleep(10L); */ } } int get(void) { USHORT res; NOPTRRECT rect; #if HRK if (in_next == in_last) { /* queue empty. */ /* Show mouse while waiting. Must use device control call, because Mou subsystem is not re-entrant (only one thread can have a call pending). MouseDrawPtr() would hang. */ if (mouseflag) DosDevIOCtl(0L,0L,0x0057,0x0007,hmouse); DosSemSetWait(&semEmpty,-1L); } res = in_buf[in_next++]; in_next &= (KEYBUFSIZE - 1); #else if ( in_check() ) return in_get(); if ( mouseflag ) MouDrawPtr(hmouse); res = in_get(); #endif if ( mouseflag ) { rect.row = rect.col = 0; rect.cRow = 24; rect.cCol = 79; #if HRK /* must use device control call */ DosDevIOCtl(0L,&rect,0x0058,0x0007,hmouse); #else MouRemovePtr(&rect, hmouse); #endif } return res; } #if HRK TID kbdid, mouid; #endif void main(void) { USHORT mask, key; in_init(); if ( (kbd_stack = malloc(THREADSTACK)) == NULL || (mouse_stack = malloc(THREADSTACK)) == NULL ) { puts("Cannot allocate thread stacks."); exit(-1); } #if HRK mouseflag = 0 == MouOpen(NULL,&hmouse); #else MouOpen(NULL, &hmouse); #endif #if HRK if (mouseflag) { #endif mask = MOUSE_BN1_DOWN | MOUSE_BN2_DOWN; MouSetEventMask(&mask, hmouse); mask = 0; MouSetDevStatus(&mask, hmouse); #if HRK MouFlushQue(hmouse); } /* _beginthread is not in the *standard* MSC 5.1 library! */ if (mouseflag) DosCreateThread(checkmouse,&mouid,mouse_stack+THREADSTACK); DosCreateThread(checkkbd,&kbdid,kbd_stack+THREADSTACK); #else _beginthread(checkmouse, mouse_stack, THREADSTACK, NULL); _beginthread(checkkbd, kbd_stack, THREADSTACK, NULL); #endif do { key = get(); printf("Empty=%08lx Access=%08lx in_next=%d in_last=%d %04x\n", semEmpty, semAccess, in_next, in_last, key); } while (key != 27); exit(0); }
jack@csccat.UUCP (Jack Hudler) (01/09/90)
In article <1702.25a6586a@cc.helsinki.fi> kulokari@cc.helsinki.fi writes: >2) When the program did compile (after I had checked from manuals >what those undefined symbols probably did mean), it did not link. >_beginthread was not found in the standard C library. Closer >inspection revealed, that you have been using the multithreaded >version of the MSC 5.1 library. This is usually (and in this >case) quite unnecessary, and to be avoided of at any cost. > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ > This statement is quite inaccurate... you must use the multithread library whenever using stdio in any threads, howelse is 'errno' going to know which thread to return the proper error number too, and other like functions. This was clearly stated in the OS/2 Design Workshops I have attended in Redmond. -- Jack Computer Support Corportion Dallas,Texas Hudler UUCP: {texsun,texbell,attctc}!csccat!jack
leefi@microsoft.UUCP (Lee Fisher) (01/10/90)
[regarding the discussion of _beginthread() being unnecessary] Yes, if you use the MS C 5.1 runtime functions inside a newly created thread, you should be using _beginthread() to create it. If you're using "pure" OS/2 APIs inside this thread (and no C RTL functions), you can use DosCreateThread(). The _beginthread() function sets up things in the C runtime so it can run properly. For more information on _beginthread() (and multi-threaded programming in general) with the MS C 5.1 compiler, see the file MTDYNA.DOC, included in the C 5.1 disks. Also, I don't have the issue number handy, but about six months ago there was an article in Microsoft Systems Journal that discussed pros and cons of using these two functions. -- lee fisher, leefi@microsoft.uu.net, leefi%microsoft@uw-beaver.mil {uw-beaver,decvax,decwrl,fluke,intelca,sco,sun,uunet}!microsoft!leefi disclaimer: my opinions are not necessarily those of my employer.
kulokari@cc.helsinki.fi (01/10/90)
In article <10198@microsoft.UUCP>, leefi@microsoft.UUCP (Lee Fisher) writes: > [regarding the discussion of _beginthread() being unnecessary] > > Yes, if you use the MS C 5.1 runtime functions inside a newly created > thread, you should be using _beginthread() to create it. If you're > using "pure" OS/2 APIs inside this thread (and no C RTL functions), you > can use DosCreateThread(). The _beginthread() function sets up things in > the C runtime so it can run properly. Also, on page 26 of the MSC 5.1 Update of the MSC User's guide there is a list of functions in the *standard* run-time library, which *are* re-entrant and thus *can* be used even in multi-threaded programs. With proper care, you can do just about anything with these functions. I stand by my earlier statement that using the special multi-threaded library should be avoided at any cost. The cost is not so high if you limit your use of multiple threads to special cases, and use OS/2 API functions when necessary instead of the C runtime library (for I/O at least). The concept of threads is non-portable anyway. Hannu Kulokari CC, U of Helsinki kulokari@cc.helsinki.fi (Internet) kulokari@finuh (Bitnet)