Date: Mon, 29 Nov 1999 09:05:55 -0800 (PST) From: Matthew Dillon <dillon@apollo.backplane.com> To: Nate Williams <nate@mt.sri.com>, Julian Elischer <julian@whistle.com>, Jason Evans <jasone@canonware.com>, "Daniel M. Eischen" <eischen@vigrid.com>, freebsd-arch@freebsd.org Subject: Re: Threads Message-ID: <199911291705.JAA06592@apollo.backplane.com> References: <19991124220406.X301@sturm.canonware.com> <Pine.BSF.4.10.9911250109290.12692-100000@current1.whistle.com> <199911291611.JAA19058@mt.sri.com> <199911291621.IAA06301@apollo.backplane.com> <199911291629.JAA19154@mt.sri.com>
next in thread | previous in thread | raw e-mail | index | archive | help
:> The terminology I have been using, which I thought was the same as :> Julian's but may not be, is: :> :> Thread :> :> Two entities. A kernel structure 'Thread' and also a similarly :> named but independant user structure within the UTS. : :So far so good. However, we need to differentiate between a 'Userland' :thread, and a 'kernel thread' somehow. Also, how does a Userland thread :'become' a Kernel thread? (We need a hybrid of Userland/Kernel threads :in order for this to be a viable/effecient solution.) In my scheme the kernel controls the ultimate scheduling of threads so 'Thread' is a kernel thread. The UTS may implement its own thread structure for userland in order to tie the kernel Thread into the userland libraries, but there would have to be a 1:1 correspondance (since userland needs to make a system call to schedule runnability of a thread). The idea here is that the kernel Thread structure is very small, holding only the userland %esp, a reference to the governing struct proc, and a reference to the KSE (which need only be present in certain situations, see below). The kernel will store the userland register state on the userland stack instead of in the Thread structure, keeping the Thread structure *very* small. If we take a stack fault and no memory is available to allocate a new page, the situation is simply treated the same as any other time the kernel blocks while in the supervisor (see below). No special case or extra code is required at all, really. Since the case only occurs under extreme conditions we don't have to worry about it except to ensure that we do not enter a low-memory deadlock when the case occurs. It is precisely the ability to block in supervisor mode that prevents the low memory deadlock from occuring. :> KSE :> :> A kernel scheduleable entity. I was using this to mean the :> contextual information (such as a kernel stack) required for :> the kernel to be able to run a thread. Not required for :> runnability, only required to actually run the thread and :> also held over of the thread blocks while in the kernel. : ^^ :if? Can you expound on this more? Is this transferrable to another :'thread' in the kernel? If so, what is left? If not, what is the :'thing' that we are transferring across? Yes, the KSE is transferable. There are two situations in my scheme where a KSE is required: * The thread is currently running on a physical cpu (not just runnable, but actually *running* ... just like you might have dozens of processes in a 'R'un state (runnable) but on a UP system only one can actually be *running* at a time. * The thread has gone to sleep while in the kernel. i.e. the thread has blocked while in the kernel. Taking them one at a time: (1) The thread is currently running - a KSE is required in case the thread wishes to make a system call, an interrupt occurs, or a fault occurs. The thread is not actually *using* the KSE except for those occurances so if the thread is switched out without the above occuring, the KSE can be reused for the next runnable thread that is switched in by the kernel scheduler. We CANNOT allocate a KSE for a thread upon entering the supervisor without getting into potential deadlock situations, thus the KSE must already exist so entry into the supervisor can occur without having to allocate memory. (2) The thread blocks while in the kernel. For example, the thread makes a synchronous system call such as a read() which blocks. The thread utilizes the KSE that was previously idle and now blocks. The kernel cannot migrate that KSE to another thread because it is needed to hold state for the current thread. Another example, the kernel decides to switch away from a thread currently running in usermode (not a system call, or a special synchronous give-up-the-cpu call) The kernel will attempt to save the register & FP state onto the thread's own user stack. If this succeeds the kernel can switch away from the thread and reuse the KSE for the next thread (assuming it doesn't already have one). On the otherhand, if the act of saving the registers onto the userstack blocks due to a fault and a low-memory condition, swap-in, or other state, the kernel is forced to use the existing KSE to block in the supervisor and saves the register state there (just like normal). When memory becomes available the kernel can complete the switchout for the userstack and destroy the KSE. As you might have gathered, in the case where the thread being switched in by the kernel has no KSE, the kernel simply resumes the user thread with an iret. The previous state that was pushed onto the user thread's stack is restored by a restore vector that was also pushed onto the user thread's stack. The kernel can use the per-cpu supervisor stack to temporarily hold the interrupt return stack to properly restore the user ring. This may sound complex, but it is very close to what our kernel already does currently and would not require significant programming to implement. It would allow us to reduce the kernel memory footprint required to manage threads to a size that is so small that we could scale to hundreds of thousands of threads if we needed to. :> Process :> :> Our good old process. : :I think this is probably the *only* thing we all agree upon the :definition of. :) Kinda hard to screw this one up, yup! :... :> into the kernel's scheduler but if you have a two-cpu system, only 2 of :> those 10 will actually be running at any given moment and require KSE's. : :So far so good. : :> With my system we change the kernel scheduling entity from a 'Process' :> to a 'Thread' and a Thread can then optionally (dynamically) be assigned :> a KSE as required to actually run. : :I think the term you are using for 'Thread' would be an SA, but I'm not :sure everyone else would agree. : :> The KSE is a kernel abstraction and :> essentially *invisible* to user mode. The Thread is a kernel abstraction :> that is visible to user mode. : :I see KSE as being 'kernel context', and that *everytime* a 'thread' :(userland) makes a system call, it gets assigned (created, whatever) a :KSE. However, in order to do proper thread priorities, who determines :which threads get a 'SA' in this context? There could be lots of :threads vying for a SA (or kernel 'Thread') in this context, and the :only entity with enough context to decide correctly is the UTS. : :Nate I see a KSE as being a 'kernel context' as well. The only difference between your description and mine is that the threads currently running on a cpu (not runnable, just running) requires a KSE to be assigned to it so the kernel context is available to switch into when the system call is made. The KSE *cannot* be allocate after the fact without getting into low-memory deadlock situations. A runnable thread (or a stopped thread) which has *no* kernel context does not need a KSE while not being run by a cpu. Thread priorities are controlled by the UTS. The UTS schedules and deschedules kernel Threads. For example, if you have 10 user mode threads which are all runnable, and the UTS is implementing a serialized FIFO scheduling class, the UTS simply schedules one of those threads at a time with the kernel. If the UTS wants to run all of them simultaniously, the UTS schedules all of the threads with the kernel. The kernel's 'run' and 'runnability' state for a thread is entirely separate from the UTS's 'run' and 'runnability' state. This difference is how the UTS imposes the scheduling class on the thread. The kernel aggregates the cpu use for all running kernel threads simply by having the cpu use counter be in the governing process structure. In otherwords, you could have 50 threads running in the kernel associated with a single process but they will only get the same aggregate cpu as that single process would get. This is trivial to do - essentially two lines of code. The purpose of this is not to impose a scheduling class on the threads, that is what the UTS does when the UTS schedules and deschedules the threads with the kernel. The purpose is to place all threads related to an umbrella under the scheduling class and priority of the governing process. Did I explain that well enough? I know its confusing. We are talking about two totally unassociated pieces of the scheduling puzzle here. The kernel only deals with the piece that governs cpu resource sharing between processes (even though the scheduling entity is a 'Thread'), the UTS deals with the scheduling of threads within the slice of cpu(es) (not the plural) the kernel gives the process. The UTS does not have to know how many real cpu's exist in the system. It simulates N virtual cpu's simply by scheduling N threads with the kernel at any given moment. -Matt Matthew Dillon <dillon@backplane.com> To Unsubscribe: send mail to majordomo@FreeBSD.org with "unsubscribe freebsd-arch" in the body of the message
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?199911291705.JAA06592>