Jon said:
[...]
If _all_ you really want to do specifically is block, then I'd say that
Sleep() would be the function that does that. So in that respect,
sure...that's the "special API routine" that blocks. But there's many
many other ways for a thread to block.
Well, sleep would be a perfect solution for me if I could do it for less
than 1ms. 1ms means that if I interlaced every output with sleep(1) that I
could run at a maximum of 1khz. This is extremely slow for my application.
if I could get 10us resolution then it would be much better. I know that
this would be counter productive because it would require a task switch
every 10us which is very costly.
Indeed. The task switch itself could use a significant amount of time
in that case.
I should point out that even though Sleep() takes 1 millisecond units,
you can't actually get 1 millisecond resolution from it. I think you've
already discovered this on your own, but it's worth reiterating. Once
you yield via Sleep(), all other threads at the same priority will get a
chance to run, if if no other threads at your same priority are
runnable, then threads of a lower priority will get to run.
The bottom line being that if there's even one thread ready to run, it
will run and potentially use its entire timeslice, which will be a lot
longer than 1 millisecond, and your thread won't get to run again until
at least that thread is done (and if there are multiple threads at the
same priority, then they _all_ get a shot at the CPU before your thread
gets to run again).
That's a long way of saying that when you call Sleep() even with the
minimal value of 1 millisecond, it could be a lot longer than that
before the call returns control to your thread.
It seems there are 3 parts here.
who blocks, who unblocks, and what triggers the unblock.
I suppose the first one doesn't matter because in either case the thread
will get blocked. The second one cannot be the thread because it makes no
sense to unblock itself because then it would have to be running.. So this
must be the controller of the resource because the scheduler has no idea
about the resource.
So really I think the problem is the last one. I imagine that the the
controller of the resource(well, the code that interfaces with it) says
"Hey, The resources is available" and tells the scheduler that it can now
unblock the thread. Just not sure how that works. Maybe the details don't
matter to much though cause its kinda getting off tangent to my original
problem.
I do agree that the specifics may not be important here. Though, you
mentioned the possibility of writing your own embedded OS. If you do
that and it has any sort of thread scheduling, you'll want to understand
this stuff _very_ well.
For what it's worth, I think the way I like to look at it most of the
time is that the threads are moving themselves and other threads between
the "runnable" and "unrunnable" lists that the scheduler maintains. A
thread moves itself to the "unrunnable" list when it does something that
blocks. Then other threads do things that implicitly move unrunnable
threads back to the runnable list.
For example, one thread may call WaitHandle.WaitOne(), which moves that
thread to the unrunnable list. It remains on that list until some other
thread calls WaitHandle.Set() on the same WaitHandle instance.
The WaitHandle object itself includes some OS glue that actually handles
this. So the thread calling Set() doesn't itself really know anything
about what's going on. But it is in fact effectively causing the other
thread to be moved back to the runnable list.
All of this happens independently of the scheduler. All the scheduler
does is wake up when one of two things happens: a thread yields, or the
timeslice expires. It checks to see what thread gets the CPU next based
on the priorities of the threads in the runnable list, manages the
switch to that thread, and then lets that thread continue executing,
itself going back to sleep until it's needed again.
This is necessarily a bit of an oversimplification. There's lots of
other stuff in there to deal with multiple threads waiting on the same
resource, to prevent absolute thread starvation, etc. But that's the
basic idea.
Yeah, I guess I need to read about these in the context of an operation
system. I know why they are used but I guess I'm not clear on exactly how
they are implemented... of course its probably not such a simple topic.
As is the case with many things, the basic idea really is actually
pretty simple. There are complicating factors to ensure that things
work right in all situations, but the fundamentals of the implementation
are fairly straightforward IMHO.
Yes... but ultimately it doesn't matter because I have to spinwait to get
the timing right... So if I write a kernel mode driver then I have to
spinwait in there and sure I end up blocking the calling thread but also
everything else... but I don't seem to have any choice because I have to
time things but pc's don't have the ability to do such high resolution
timing(AFAIK).
PC's can easily deal with the timing. But Windows does not. This isn't
a hardware limitation, it's a basic issue of the implementation of this
particular multi-tasking OS. Granted, the implementation is not a whole
lot different from other multi-tasking OS's, but in each case the
limitation is fundamental to the OS, not the hardware itself.
[...]
So I guess when I call sleep I put a block out but whateve mechanism is
behind sleep(the os I guess) has the code that will unblock my code after
the time has elapsed?
Yes. The OS is keeping track of the time the thread wants to sleep, and
when that's expired it knows to make the thread to be runnable again.
[...]
Define "controller of the resource". In some respects, the OS is the
"controller". That is, ultimately it is the OS managing who gets the
resource at any given time. On the other hand, you could say that
whatever thread currently has acquired the resource is the "controller".
That is, until that thread releases the resource, the resource is
controlled by that thread and unavailable to any other thread.
I mean the lowest level code that works with the resource like a driver.
Well, keep in mind that the "resource" may not be very low-level. For
example, synchronization objects are fairly high-level things, IMHO.
Certainly nothing like hardware interrupts that drivers might have to
deal with, for example.
But yes, in the case of a driver handling i/o, for example, you can
certainly view the driver as the "controller of the resource".
[...]
Ok. I guess I would need to see exactly how its implmented to feel
comfortable with it but I do see how it could work. I guess its just hard
for me to see what kinda code has to be behind such seemingly simple calls
as readline to get all that stuff to work.
The main thing that makes it complicated is that the same mechanisms are
used for a variety of situations. If all you had to do was implement
Console.ReadLine(), that would be easy. But because of the way an OS is
layered, to build specific functionality on top of more basic
functionality, until you get to the highest levels, it winds up seeming
fairly complicated.
Even at that though, you might be surprised at how little actual code
really needs to execute in order to manage a thread's call to
ReadLine().
I guess though its very similar how dos works? If you wanted to output text
you would call an interrupt routine(although you could do write directly to
video memory I suppose)... the act of interrupting is blocking and the
unblocking is when the interrupt returned from the call?
Sounds like this is very similar but on a more complicated level because of
the multitasking environment.
IMHO, because of the multitasking environment, it's completely different
from the way DOS works. And yet, I suppose in another way it's exactly
the same.
First, when referencing DOS I think it's important to differentiate
between interrupts that are basically just an OS API and interrupts that
actually do interrupt things.
The latter is a lot like the thread scheduler preempting one thread and
scheduling a different one to run. The former is not a lot different
from calling a Windows OS API. The main difference being that Windows
being a multi-tasking OS, some calls to the API cause your thread to
block and wait on some other thread.
The way in which it's exactly the same is easiest to understand on a
single-processor PC. That is, in the end the processor can only execute
one instruction at a time. So while Windows has the concept of a thread
scheduler and it does in fact rely on the low-level hardware interrupts
to work correctly, if you have a bunch of threads that never can finish
their timeslice without needing to block, it doesn't really work a lot
different than some DOS application that uses some sort of cooperative
round-robin scheduling.
For example, there were those TSR programs you could use in DOS that all
hooked to the same interrupts, and chained themselves. They all got a
shot at the CPU, by cooperating with each other and doing a little bit
of work at a time and then calling the next one in the chain.
The thread scheduler in Windows does this in a much more formalized way,
but the end result is the same: execution jumps from piece of code to
piece of code, letting each piece do a little bit of work at a time.
[...]
But I can still use the port to do the communications because its just
simply sending bits in a predefined way. Its clocked/synchronous
communications so there is a clock and a data line. I can use two pins on
the port and just output bits in the correct order to do my communications.
Problem is, in some cases the protocol specifies that the slave will need to
take over the communications(such as the acknowledgement part). So maybe I
can do something like
out 1
out 0
out 1
out 1
but then I need to wait for an acknowledgement from the slave.
How can I do this without polling or interrupts?
Is the slave sends the acknowledgement and I'm not listening then I've lost
it. If I can't use interrupts then only other option is to poll. (and when
it sends th acknowledgement it only lasts for a several microseconds)
From your description, perhaps you do need to poll.
However, given that it's likely that your thread will spend at least as
much time not executing as it does executing, it seems like you are
still engaged in risky business.
If you use a technique to ensure that you don't start sending data until
right at the beginning of a timeslice, and you know for sure that you
can complete an entire transaction within a timeslice, then I suppose it
would work fine.
But otherwise, you run the risk of having that acknowledgment sent when
your thread doesn't have the CPU.
[...]
If you have no managed access to the i/o from the hardware, and no
integrated unmanaged access to the i/o (that is, via one of the
higher-level i/o API in Windows), then I suppose it's possible polling is
your only option. However, even there what you should do is use a call to
Sleep(), with a 1 millisecond timeout, any time you poll and don't have
any work to do, to ensure that your thread immediately yields its
timeslice.
To slow ;/ I would love to use sleep as its the easiest method but its just
to slow. 1ms resolution would just kill my performance.
Well, just so I'm clear: I'm not suggesting calling Sleep(1) in between
each byte you sent. I'm suggesting that you do as much processing as
you need to at the moment, and then yield using Sleep(1).
If you can't just execute the calls to output the data to the port as
fast as possible, then you may still have to do the spin-wait thing to
slow your code down. And you may have to send and then wait for the
acknowledgment before yielding. But you can at least do all that stuff,
assuming you know it will happen fast enough to complete in a single
timeslice.
For one of the protocols each command is about 30 bits long and there are
minimum wait times of about 40ns between each bit sent. This means I would
have to wait ~30ms to send just one command.
If you yielded between each bit, it would be worse than that, because
calling Sleep(1) isn't going to block for just 1 millisecond. It can
easily block for tens of milliseconds. A 30 bit command could take a
second or more to complete.
But I'm not suggesting that. I hope the above clarification explains it
better.
[...]
I think the problem is, is that blocking does nothing for my app because its
not a "timed" block. I'll end up blocking the thread anyways but I'll do it
at the command level.
so essentially instead of inserting sleep between every bit, I'll insert
short spinwaits and then unblock after the full command is sent.
I assume you mean "then block after the full command is sent". And yes,
I think that would be a fine way to do it, assuming you really have to
do the spinwait thing.
It would be ideal if you could just use the built-in parallel port
driver, but you're saying you can't and I don't have any knowledge that
would contradict that. If you can't use the built-in i/o API, then I'd
agree there's not a good thread-friendly way to do what you want.
[...]
Fortunately, for practically all i/o that a Windows application might be
asked to do, the OS switches between threads quickly enough that the
end-user never will notice any difference.
Well, thats a problem though because windows has the benefit of
multi-tasking... which makes it much better for dos but falls short for
timed communications. Would be nice if they added some ability for both.
To some extent, this is addressed by most hardware devices. That is,
they provide hardware level buffering and flow control, allowing the
software to be a little more, well...soft about timing.
I mean, what would be really nice is to have a small device dededicated for
doing such a thing. Actually they are all over the pc but they are all
hardware and not programmable.
It seems to me that the device that's the problem here is whatever
you're connecting to the PC. The parallel port on the PC definitely can
do buffering and flow control, but apparently the devices you're
communicating with don't support that. The real issue is the hardware,
not the lack of support on the PC.
Another thing I would explore, assuming you've already verified that the
unmanaged Windows parallel port i/o technique won't work, is whether
there is a hardware device that will conform to whatever protocol these
devices use, but which can on the other end do the necessary buffering
and flow control so that the PC can access it via the higher-level
hardware protocols.
It seems silly to me in this day and age that hardware exists that is so
primitive, and at the very least there ought to be a good hardware
solution to allow more convenient and modern software solutions for
accessing the device.
[...]
Well, IMHO that's known as "DMA".
Well, but its then a "dumb" piece of a hardware. What I mean is something
that sorta runs a sperate piece of code at a specific timed rate... like a
small thread but that is not part of the main cpu... maybe just 1 register
and a little memory for the stack and buffers that are also not part of the
main memory.
I think what you mean is something programmable. A DMA controller isn't
all that "dumb"; it has its own internal execution flow, it's just not
something you can program directly.
My main point is actually just that there are parts of hardware on the
computer that deal with i/o without the handholding your parallel
interface devices apparently need. That's probably not useful to you,
but they do exist.
[...]
You seem to be asking about the other direction; software being too fast
for the hardware. But that begs the question, why is the hardware driver
not taking care of this already. I seem to recall this was a regular
parallel port; am I misremembering? A standard parallel port driver
should handle all of the buffering you need for it to work correctly with
whatever parallel device is attached to the hardware.
Maybe it does? I didn't know the parallel port buffered anything in SPP or
if it does its a vey small buffer, But even if it did then it might run to
fast?
Could be. I don't know. With less primitive hardware, it wouldn't be a
problem. But if your hardware has no way to indicate to the parallel
port hardware on the PC that it's going too fast, I guess you have to
manage this explicitly.
[...]
Those things are out of my control. If windows has to interrupt my thread
when I'm half sending a command then I can't do anything about it... it just
slows down data rate... this is one reason why the speed is "critical". (not
that its critical in the sense that it has to be fast to work but it has to
fast to work well and in this case = more productivity)
I guess one thing I still don't understand is, if it's theoretically
possible for your code to be interrupted, and all that doing so causes
is the data rate to slow down, how can it still be possible that you
have to poll at a high rate to catch acknowledgments. What happens if
you complete sending the command just before your thread gets preempted,
and then while the thread is suspended by the scheduler the
acknowledgment arrives?
[...]
Maybe I should write up specifically what I'm trying to do in another thread
so its more clear?
Probably. I agree that this thread seems to be pretty much wrung out,
as the basic thread blocking questions you were asking seem to have been
answered.
Frankly though, I think your application is pretty far afield for this
newsgroup altogether. The kinds of things you're doing all involve
using various unmanaged techniques. Your program might be a C# .NET
program, but none of the stuff you're asking about is really all that
much related to C# or .NET.
So while starting a new thread more specific to your needs probably
makes more sense than continuing this thread, I think in reality you'd
probably get much better answers if you found a newsgroup where people
are regularly doing things more like what you're doing. You're
certainly outside my main areas of expertise, and I've even done a
little bit of driver, low-level i/o code. There are a handful of other
people who post here regularly that I think have the sort of experience
that you'd find useful (for sure they know a lot more about this stuff
than I do), but there's probably at least one newsgroup where there are
dozens, if not hundreds, of people with that kind of experience.
You'll probably get much better advice on this particular topic in such
a newsgroup.
Pete