请问编程语言中阻塞机制在操作系统最底层是如何实现的?

2019-12-26 10:24:51 +08:00
 squancher

首先大致问题如上,有以下两个疑问

  1. 有一种说法是程序阻塞不占用 CPU 资源。根据我所查阅到的资料,JAVA 为例,阻塞机制最后都到了 native 方法,也就是 C 语言实现,调用到了操作系统的方法(这个说法有点不准确),然后是关于 Linux 阻塞队列的原理,有一个 __wait_event 方法,具体如下:
#define __wait_event(wq, condition)
       do {
               DEFINE_WAIT(__wait);

               for (;;) {
                       prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);
                       if (condition)
                               break;
                       schedule();
               }
               finish_wait(&wq, &__wait);
       } while (0)

然后就没有其它资料了,可以看到里面有一个死循环,所以底层是会用循环来判断是否满足条件?(感觉并没有到底层)那么这也是变相消耗系统资源了啊,有点像回调
2. 如果底层是使用循环来判断,那么例如定时器,操作系统是如何管理时间的?如果也是有循环,那么时间粒度是多少,1 毫秒?如果不是,如何判断时间到达?

感觉最底层一定不是我所猜测的这样,应该是有特殊的数据结构和方法。最后,不要说什么操作系统内核完成,我就是想学习一下原理,就算是汇编也想研究下,有懂的大佬能解下惑吗?或者给几个相关技术名词。

7667 次点击
所在节点    程序员
80 条回复
robot1
2019-12-26 10:36:10 +08:00
操作系统调度没有这么蠢 阻塞操作会导致任务调度器挂起进程,接下来当有中断或者信号时,再恢复进程。
简化版的任务调度可以研究一下各语言的协程实现
想要知道的更细可以看操作系统原理啊,大而全,专而深
x1596357
2019-12-26 10:37:51 +08:00
可以看看 Linux 内核调度器是怎么实现的和 yield 系统调用,执行 yield 后调度器选择另一个程序进行运行,并将当前程序加入调度等待队列。阻塞有阻塞队列,失去阻塞条件的时候被入调度等待队列。 至于调度时间粒度,看调度器设置,1ms 也有 100ns 也有,甚至还有 NoHz 调度器。固定时间的调度器由时钟中断驱动。
xiadong1994
2019-12-26 10:40:58 +08:00
schedule() 就是 call 调度器切换到其他进程,本进程就进到某个 event 的等待队列里去不会再被调度。event 发生以后 kernel 可能会唤醒一个或多个在等待的进程,唤醒其实就是把进程移到可执行的进程队列里面等待调度。
squancher
2019-12-26 10:42:10 +08:00
@robot1 主要疑惑点是如何恢复的,就算是通过信号来通知,那么如何判断何时条件满足,判断条件满足这个过程是什么,像 socket 一样,流来了就唤醒,要做到实时,他是怎么判断的,是不断循环吗?
squancher
2019-12-26 10:44:27 +08:00
@x1596357 Java 的 sleep 我测试出来结果是会多一毫秒,所以时间粒度可能是 1 毫秒吗?
xiadong1994
2019-12-26 10:48:08 +08:00
@squancher 对于网络流这种 IO 操作是靠硬件中断来的
Michaelssss
2019-12-26 10:48:41 +08:00
。。。CPU 不会停下来,只会不停的运行,只不过 CPU 的时间分配在各个进程 /线程之间,你可以认为,只要电脑开着,必然 CPU 是不断 FOR 循环
augustheart
2019-12-26 10:50:56 +08:00
粗糙地来说,就是相当于(相当于)用 sleep 一直等待,就是我们自己经常会写的那种。
并不是真正的不消耗资源,而是消耗的资源可以忽略不计。这也是编程中 no magic 的一个例证。
至于时间粒度,基本不需要考虑,在 windows 下我用 GetTickcount 计时,经常做了一大堆的读取操作(而且还大量执行内核操作)后得到的时间间隔为 0,对现代 cpu 来说,1 毫秒是相当之久的,现在 cpu 比我们想象的快很多。
squancher
2019-12-26 10:52:50 +08:00
看了下调度器,感觉只是实现进程的调度,还是不理解判断条件满足这个过程谁来完成,如何判断的,除了循环有其它方法吗
@x1596357
@xiadong1994
@Michaelssss
hehheh
2019-12-26 10:55:43 +08:00
interrupt 啊,原来上微机原理课讲过。
codehz
2019-12-26 10:57:10 +08:00
其实了解一些基本硬件提供的机制就可以理解这个问题了(当然要具体实现那是另一个问题)
首先我们知道硬件有中断,其中包括时钟中断和 IO 中断(和别的)
于是我们可以做什么呢,时钟中断可以用来切换运行中的线程(抢占式调度)
对应的 IO 中断就可以用来唤醒由于特定 IO 请求而睡眠的线程了!
(所以为什么要阻塞队列?因为有可能同时有好多线程有 IO 请求,总得设计一个结构去保存吧,然后请求来了就扫描一下看看谁可以被唤醒做事)
当然具体实现要复杂的多,比如 IO 中断怎么解析就是一个非常复杂的事情,解析好了也不是立即就唤醒的,除非是无阻塞 poll 模式,不然内核还要帮你把收到的数据填充到缓冲区(以 read 为例)
zivyou
2019-12-26 10:59:34 +08:00
阻塞这个词是相对进程的上下文来说的,内核只是将进程挂起,不让它继续参与调度。
lihongjie0209
2019-12-26 11:11:55 +08:00
不给你调度不就行了? 至于说什么时候唤醒可以是硬件中断, 时钟, 或者是软件中断
gamexg
2019-12-26 11:16:31 +08:00
google 搜索关键字 操作系统原理 sleep

sleep 大体还是和进程调度在一起的。

需要可以继续搜索 操作系统原理 进程调度。
nevin47
2019-12-26 11:20:40 +08:00
LZ 有兴趣可以看看 CPU 流水线的概念

你可以比较粗糙的理解,大部分硬件都是电信号的收发器和处理器,电信号的激励是由时钟来整体控制的

对于阻塞任务,在 CPU 上只需要将这部分任务上下文切到挂起的状态,然后任务数据换到内存的某个地方。接下来时钟继续不断刷新电路,可以理解 CPU 上面执行的是其他任务。

当挂起的条件达成的时候,下一个 cycle 刷新了挂起的寄存器,触发了中断,这个时候调度模块会重新切入这段任务,继续执行下去
squancher
2019-12-26 11:26:29 +08:00
@nevin47 我想知道“当挂起的条件达成的时候”这个过程,怎么实时判断达成条件
nevin47
2019-12-26 11:39:11 +08:00
@squancher 举个简单的例子

在某个神奇的单片机上,CPU 和系统共享一个硬时钟,时钟频率是 1Hz,也就是说一秒钟会从时钟刷新一次电信号

然后我们自己实现了一个 sleep(x)函数,这个函数会把 x 写到某个寄存器 reg_sleep 上,这个寄存器的特性是,每一次电信号刷新,寄存器会减 1(某些定时器的实现和这个类似)

然后我们还存在一个调度器,当每次刷新的时候,调度器如果发现 reg_sleep 的值从 0 变成非 0 了(某些中断的硬实现和这个类似),调度器就会把当前的任务挂起,然后引入新的任务

然后过了 x 秒之后,reg_sleep 的值从非零归位成 0 了,这个时候调度器就把之前挂起的任务,重新切入,继续执行

上面就是一个非常粗糙但是比较简略的 sleep 实现
zunceng
2019-12-26 11:44:36 +08:00
看内核太费劲 建议把几种 IO 模型了解下 毕竟现在好多被吹成神的软件( nodejs,nginx 等等) 很大的亮点就是应用了 IO 模型中的最优解
lxk11153
2019-12-26 12:09:50 +08:00
我觉得就是( @Michaelssss #7 )说的循环,其它人说的什么 sleep,调度,中断,信号等等我也整懵了,当然他们是可能出于更加全面理解操作系统这样的目的

@codehz #11 “扫描一下看看谁可以被唤醒做事” 我觉得楼主在问“怎么扫描” 是不是就是循环遍历一下“好多线程”所在的那个结构
@nevin47 #15-“继续不断刷新电路” #17-“调度器如果发现 reg_sleep” 调度器怎么发现?是不是就是循环一遍所有?

或者可以以“暴力密码破解”来理解,就是一个个试,对于系统就是 “循环接着从头循环接着从头循环接着从头循环”。至于循环什么,可能线程就对应线程 list/table(其它类似)
或者可以这样理解: Linux 下“一切皆文件” ,对于操作系统就是循环

ps. 以上我猜的,因为我不了解操作系统
dongyx
2019-12-26 12:21:59 +08:00
我自己的理解,就是循环。其他同学说的等待标记位变为某个值再唤醒,已经是 CPU+OS 封装之后应用层看到的现象了。操作系统会让进程休眠,但是内核会不断循环读取这个标志位,直到满足条件再把数据填入缓冲区并唤醒进程。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/632449

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX