Assembly 迷你操作系统中不同信号量的就绪队列的优缺点

Assembly 迷你操作系统中不同信号量的就绪队列的优缺点,assembly,multithreading,operating-system,semaphore,Assembly,Multithreading,Operating System,Semaphore,在操作系统中,我们最初有一个线程就绪队列,请注意,只有一个队列。为什么对每个信号量使用不同的队列更好。优点和缺点可能与效率、复杂性或其他因素有关。我假设您有一个单处理器系统(有多个处理器可能会影响就绪队列的数量) 您准备好排队的是什么?我见过的大多数就绪队列都包含一个列表,其中列出了所有准备运行的线程。这当然与系统中的所有线程不同。如果此模型与您的设置相匹配,我建议使用单个就绪队列。在安排或选择下一个线程运行时,您只需检查一个队列,而不必担心该队列中挂起的任务 每个信号量有一个等待队列也是值得的

在操作系统中,我们最初有一个线程就绪队列,请注意,只有一个队列。为什么对每个信号量使用不同的队列更好。优点和缺点可能与效率、复杂性或其他因素有关。

我假设您有一个单处理器系统(有多个处理器可能会影响就绪队列的数量)

您准备好排队的是什么?我见过的大多数就绪队列都包含一个列表,其中列出了所有准备运行的线程。这当然与系统中的所有线程不同。如果此模型与您的设置相匹配,我建议使用单个就绪队列。在安排或选择下一个线程运行时,您只需检查一个队列,而不必担心该队列中挂起的任务

每个信号量有一个等待队列也是值得的。pend队列将跟踪等待信号量的所有线程。由于线程正在等待,因此它尚未就绪,并且不在就绪队列中

这样做有助于保持线程处于相似的状态。因此,当您必须搜索这些列表时,您可以将开销保持在最低限度


希望这能有所帮助。

好的,要回答这个问题,请参考我的另一个问题,关于“Linux时分进程还是线程?”的问题,让我们从一个虚拟内核的结构开始,使用x86处理器和C代码……我已经修改了这个结构,以考虑信号量

struct regs{ int eax, ebx, ecx, edx, es, ds, gs, fs, cs, ip, flags; struct tss *task_sel; } struct mem_loc{ int *phys_mem_begin; int *phys_mem_end; int *semaphores; } struct thread{ struct regs *regs; struct mem_loc *memlocTracker; int parent_id; struct thread *next; } struct task{ struct regs *regs; struct mem_loc *task_mem; int *filehandles; int priority; int *num_threads; int quantum; int duration; int start_time, end_time; int parent_id; struct thread *task_thread; /* ... */ struct task *next; } struct queue_ready{ struct task *task_id; struct queue_ready *next; } 内核的调度程序看起来过于复杂,但事实并非如此,我在代码中遗漏的唯一一个地方就是优先级……在讨论OP的问题时可以忽略它

让我们将此调度程序功能分解为
调度程序
功能…但首先快速浏览并解释一下
调度程序
功能中使用了哪些功能:

  • “load_cpu_task_state”和“load_cpu_thread_state”-这会加载cpu寄存器的状态,为简洁起见,它们分别用于task和thread
  • “load_mem_task_state”和“save_mem_task_state”-分别将内存状态加载并保存到所述任务的
    mem_loc
    结构的
    phys_mem_begin
    phys_mem_end
    字段指定的磁盘上的页面。为简洁起见,这将加载所述任务拥有的所有内存,包括线程
  • “jmp_和_exec_cpu_task”和“jmp_和_exec_cpu_task”-这会导致内核发出跳转到所述任务的
    reg
    结构的指定
    cs
    ip
    寄存器的魔法,同样,对于线程也是如此
  • “save_cpu_task_state”和“save_cpu_thread_state”——这会导致内核分别保存任务和线程的cpu寄存器状态
  • “lock_memory”和“unlock_memory”-这是用于使用信号灯锁定内存区域,该信号灯很快就会起作用
现在,
scheduler
将永远运行,并在任务的链接列表上迭代,需要检查任务是否已运行一段时间并超过
quantium
量,例如20毫秒。然后,它保存cpu状态和内存状态(可能保存到磁盘上的分页文件),准备继续执行列表中的下一个任务

如果还有时间,它将加载任务的CPU寄存器并加载内存(可能是从一个页面),然后跳转到最后一个指令指针和代码段所在的位置(分别为ip和cs),然后任务继续

如果所述任务有线程,它将迭代线程的链接列表,加载CPU寄存器,跳入代码以恢复线程执行,然后保存CPU寄存器

现在,这是事情变得有点棘手的地方,特别是在这个玩具操作系统中,如何管理信号量!嗯,看起来操作系统开发人员的头上隐约出现了一个大问题

我可以想象,如果内存部分被锁定,
信号量
将是非空的,并且将由请求信号量的用户代码中的运行时管理器获取,它将锁定内存以防止线程践踏数据,跳转到所述线程的代码中,并在该线程的持续时间结束并保存线程CPU寄存器的状态时解锁它

结论:

这是我基于这一点得出的结论,至于支持我的事实,我不能,因为这个理论已经被详尽地讲授过,在无数的书籍中在显微镜下检查过,从美国的一所大学,一直到新西兰的一所大学,这是从我的观察和我的理解中得出的,正如你从这一代码中可以看到的,开发人员在编写代码时所承受的复杂性和负担,而不仅仅是所施加的约束,设计决策——包括硬件和软件


在这种情况下,管理信号量要复杂得多,要回答,如果有更多的队列可用,就像我努力将其保持在一个队列
queue_ready
,内存使用、磁盘使用以及最重要的时间使用方面的成本因素,当内核必须跟踪那些起作用的缓解因素时,另一个同样重要的因素是将有多少优先级,因此,在我看来,为不同的信号量设置一个就绪队列是不可行的。

就绪队列包含所有准备运行的线程,因此在我的情况下,所有线程都应该在该队列中。谢谢你的帮助。干得好!我们在实验课上使用的操作系统使用了一个包含所有线程信息和堆栈指针的数组,有一个链表会更有效地移动/删除任务,我想大多数系统都会这样做,但我们的玩具操作系统只是为了了解基本知识。 static void scheduler(void){ struct queue_ready *rdy_sched; while(1){ for (rdy_sched = head_queue_ready; rdy_sched != NULL; rdy_sched = rdy_sched->next){ if (time >= rdy_sched->task_id->start_time + rdy_sched->task_id->quantum){ save_cpu_task_state(&task_reg->regs); save_mem_task_state(&rdy_sched->task_id->task_mem); }else{ struct regs *task_reg = rdy_sched->task_id->regs; struct mem_loc *mem_task = rdy_sched->task_id->task_mem; load_cpu_task_state(task_reg); load_mem_task_state(mem_task); jmp_and_exec_cpu_task(task_reg->cs, task_reg->ip); save_cpu_task_state(&rdy_sched->task_id->regs); if (rdy_sched->task_id->num_threads > 0){ struct thread *threads = rdy_sched->task_id->task_thread; while (threads->next != NULL){ struct regs *thread_reg = threads->regs; load_cpu_thread_state(thread_reg); if (threads->memlocTracker->semaphores){ /* House keeping in relation to semaphores */ lock_memory(threads->memlocTracker); jmp_and_exec_cpu_thread(thread_reg->cs, thread_reg->ip); save_cpu_thread_state(&thread->regs); unlock_memory(threads->memlocTracker); }else{ jmp_and_exec_cpu_thread(thread_reg->cs, thread_reg->ip); save_cpu_thread_state(&thread->regs); } } } } } } }