0

背景:我在使用 Beej 的指南,他提到了分叉并确保你“得到僵尸”。我拿的一本操作系统书解释了操作系统如何创建“线程”(我一直认为这是一个更基本的部分),并且通过引用它,我的意思是操作系统几乎决定了一切。基本上它们共享所有外部资源,但它们拆分了寄存器和堆栈空间(我认为是第三件事)。

所以我得到了http://www.qnx.com的开发者文档解释得很好的 waitpid 函数。事实上,我阅读了关于线程的整个部分,减去了进程和线程谷歌之后的所有类型的条件。

我可以拆分代码并将其重新组合在一起这一事实并不会让我感到困惑。怎么能做到这一点令人困惑。

在 C 和 C++ 中,您的程序是一个 Main() 函数,它继续前进,调用其他函数,可能永远循环(等待输入或渲染),然后最终退出或返回。在这个模型中,我认为它没有理由停止超出“我正在等待某事”,在这种情况下它只是循环。

好吧,它似乎可以通过设置某些东西来循环,例如“我正在等待信号量”或“响应”或“中断”。或者它可能在没有等待的情况下被打断。这让我感到困惑。

处理器对进程和线程进行时间片。这一切都很好,花花公子,但它如何决定何时停止?我了解到您使用轮询功能并说“嘿,我在等待输入、时钟滴答或用户做某事”。它以某种方式告诉操作系统?我不确定。但更重要的是:

它似乎能够完全随机中断或插入,即使在单线程应用程序上也是如此。所以你正在运行一个线程,突然 waitpid() 说“嘿,我完成了一个进程,让我打断这个,我们都讨厌僵尸,我必须这样做。” 你还在循环进行一些计算。那么,刚刚发生了什么???我不知道,不知何故,它们都运行并且您的计算没有被弄乱,因为它是单线程的,但这并不意味着它不会停止在同一个线程中运行 waitpid() 所做的事情 WHILE你还在做你的其他应用程序的事情。

同样令人困惑的是如何通知您,例如 iOS 通知,并说“嘿,我进行了一些 UI 更改,让我离开 16 并让我回到 1,这样我就可以更改这个东西了”。但是与上一段相同的问题,它如何中断正在运行的线程?

我想我理解分裂,但这种加入完全令人困惑。就像教科书上有我应该接受的“戴帽子的兔子”步骤。其他 SO 帖子告诉我他们不共享同一个堆栈,但这没有帮助,现在我想象一个紧身(堆栈)靠在另一个紧身身上,但不确定它如何重新组合以更改数据。

感谢您的帮助,我很抱歉这很长,但我知道有人会误解这一点,如果我在这里过于简洁,会给我“他们是不同的堆栈”的答案。

谢谢,

4

1 回答 1

2

好的,我会试一试,虽然它会“经济实惠”:)

有点像这样:

操作系统内核调度程序/调度程序是用于管理线程的状态机。一个线程由一个栈(在线程创建时分配)和一个线程控制块(TCB)组成,内核中的结构保持线程状态并可以存储线程上下文(包括用户寄存器,尤其是堆栈指针) )。线程必须有代码才能运行,但代码不是专用于线程的——许多线程可以运行相同的代码。线程有状态,例如。在 I/O 上阻塞,在线程间信号上阻塞,休眠一段时间,准备好,在内核上运行。

线程属于进程——一个进程必须至少有一个线程来运行它的代码,并且在进程启动时由操作系统加载程序为其创建一个线程。然后,“主线程”可以创建也属于该进程的其他线程。

状态机输入是软件中断——来自那些已经在内核上运行的线程的系统调用,以及来自外围设备/控制器(磁盘、网络、鼠标、KB 等)的硬件中断,它们使用处理器硬件特性来停止处理器/s 运行来自线程的指令并“立即”运行驱动程序代码。

状态机的输出是一组在内核上运行的线程。如果就绪线程数少于内核数,操作系统将停止不可用的内核数。如果就绪线程多于内核(即机器过载),则决定运行线程的“调度算法”会考虑几个因素 - 线程和进程优先级,刚刚就绪的线程的优先级提升I/O 完成或线程间信号、前台进程提升等。

操作系统能够停止任何内核上的任何正在运行的线程。它有一个处理器间硬件中断通道和驱动程序,可以强制任何线程进入操作系统并被阻塞/停止,(可能是因为另一个线程刚刚准备好并且操作系统调度算法决定必须立即抢占正在运行的线程) .

来自运行线程的软件中断可以通过请求 I/O 或通过向其他线程发出信号(事件、互斥体、条件变量和信号量)来更改运行线程集。来自外围设备的硬件中断可以通过发出 I/O 完成信号来更改正在运行的线程集。

当操作系统获得这些输入时,它使用该输入以及线程控制块和进程控制块结构的容器中的内部状态来决定接下来要运行哪组就绪线程。它可以通过将其上下文(包括寄存器,尤其是堆栈指针)保存在其 TCB 中而不从中断中返回来阻止线程运行。它可以通过将其上下文从其 TCB 恢复到核心并执行中断返回来运行被阻塞的线程,从而允许线程从它停止的地方恢复。

好处是等待 I/O 的线程根本没有运行,因此不使用任何 CPU,当 I/O 可用时,等待线程“立即”准备好,如果有核心可用,正在运行。

操作系统状态数据和硬件/软件中断的这种组合有效地匹配了可以向前推进的线程与可运行它们的内核,并且没有 CPU 被浪费在轮询 I/O 或线程间通信标志上。

所有这些复杂性,无论是在操作系统中还是对于必须设计多线程应用程序并忍受锁、同步、互斥锁等的开发人员来说,只有一个重要目标——高性能 I/O。没有它,您可能会忘记视频流、BitTorrent 和浏览器——它们都太慢而无法使用。

诸如“CPU 量子”、“放弃剩余的时间片”和“循环”之类的语句和短语让我想吐。

这是一个状态机。硬件和软件中断进来,一组运行线程出来。硬件定时器中断(可以使系统调用超时,允许线程休眠并在过载的机器上共享 CPU)虽然很有价值,但只是众多中断之一。

所以我在线程 16 上,我需要进入线程 1 来修改 UI。我在任何地方随机停止它,“将堆栈移动到线程 1”然后“获取它的上下文并修改它”?

不,是时候“经济与真理”#2了……

线程 1 正在运行 GUI。为此,它需要来自鼠标、键盘的输入。发生这种情况的经典方式是线程 1 在 GUI 输入队列(一个线程安全的生产者-消费者队列)上等待并阻塞,以获取 KB/鼠标消息。它不使用 CPU - 内核停止运行服务和 BitTorrent 下载。你敲击键盘上的一个键,键盘控制器硬件在中断控制器上产生一条中断线,导致内核在完成当前指令后立即跳转到键盘驱动程序代码。驱动程序读取 KB 控制器,组装 KeyPressed 消息并将其推送到具有焦点的 GUI 线程的输入队列 - 您的线程 1. 驱动程序通过调用调度程序中断入口点退出,以便可以执行调度运行和您的 GUI线程被分配一个核心并在其上运行。

因此,线程 1 正在执行:

void* HandleGui{
  while(true){
    GUImessage message=thread1InputQueue.pop();
    switch(message.type){
      .. // lots of case statements to handle all the possible GUI messages
      ..
      ..
    };
  };
};

如果线程 16 想要与 GUI 交互,它不能直接进行。它所能做的就是将消息排队到线程 1,以类似于 KB/鼠标驱动程序的方式,指示它做一些事情。

这可能看起来有点限制,但是来自线程 16 的消息可以包含的不仅仅是 POD。它可能有一个“RunMyCode”消息类型,并包含一个函数指针,指向线程 16 想要在线程 1 的上下文中运行的代码。当线程 1 开始处理消息时,它的“RunMyCode”case 语句调用函数指针在消息中。请注意,这种“简单”机制是异步的——线程 16 已发出消息并继续运行——它不知道线程 1 何时开始运行它传递的函数。如果函数访问线程 16 中的任何数据,这可能是一个问题 - 线程 16 也可能正在访问它。如果这是一个问题,(可能不是——函数所需的所有数据都可能在消息中,可以在线程 1 调用它时作为参数传递给函数),可以通过使线程 16 等到线程 1 运行该函数来使函数调用同步。一种方法是让函数将 OS 同步对象作为其最后一行 - 线程 16 在对其“RunMyCode”消息进行排队后立即等待的对象:

void* runOnGUI(GUImessage message){
  // do stuff with GUI controls
  message.notifyCompletion->signal();  // tell thread 16 to run again
};

void* thread16run(){
  ..
  ..
  GUImessage message;
  waitEvent OSkernelWaitObject;
  message.type=RunMyCode;
  message.function=runOnGUI;
  message.notifyCompletion=waitEvent;
  thread1InputQueue.push(message);  // ask thread 1 to run my function.
  waitEvent->wait();  // wait, blocked, until the function is done
  ..
  ..
};    

因此,让一个函数在另一个线程的上下文中运行需要合作。线程不能调用其他线程——只能通过操作系统向它们发出信号。任何期望运行这种“外部信号”代码的线程都必须有一个可访问的入口点,可以在其中放置函数,并且必须执行代码以检索函数地址并调用它。

于 2013-11-07T10:42:00.843 回答