深入拆解Tomcat & Jetty
李号双
eBay技术主管
立即订阅
6067 人已学习
课程目录
已完结 44 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | Java程序员如何快速成长?
免费
模块一 必备基础 (4讲)
01 | Web容器学习路径
02 | HTTP协议必知必会
03 | 你应该知道的Servlet规范和Servlet容器
04 | 实战:纯手工打造和运行一个Servlet
模块二 整体架构 (9讲)
05 | Tomcat系统架构(上): 连接器是如何设计的?
06 | Tomcat系统架构(下):聊聊多层容器的设计
07 | Tomcat如何实现一键式启停?
08 | Tomcat的“高层们”都负责做什么?
09 | 比较:Jetty架构特点之Connector组件
10 | 比较:Jetty架构特点之Handler组件
11 | 总结:从Tomcat和Jetty中提炼组件化设计规范
12 | 实战:优化并提高Tomcat启动速度
13 | 热点问题答疑(1):如何学习源码?
模块三 连接器 (9讲)
14 | NioEndpoint组件:Tomcat如何实现非阻塞I/O?
15 | Nio2Endpoint组件:Tomcat如何实现异步I/O?
16 | AprEndpoint组件:Tomcat APR提高I/O性能的秘密
17 | Executor组件:Tomcat如何扩展Java线程池?
18 | 新特性:Tomcat如何支持WebSocket?
19 | 比较:Jetty的线程策略EatWhatYouKill
20 | 总结:Tomcat和Jetty中的对象池技术
21 | 总结:Tomcat和Jetty的高性能、高并发之道
22 | 热点问题答疑(2):内核如何阻塞与唤醒进程?
模块四 容器 (8讲)
23 | Host容器:Tomcat如何实现热部署和热加载?
24 | Context容器(上):Tomcat如何打破双亲委托机制?
25 | Context容器(中):Tomcat如何隔离Web应用?
26 | Context容器(下):Tomcat如何实现Servlet规范?
27 | 新特性:Tomcat如何支持异步Servlet?
28 | 新特性:Spring Boot如何使用内嵌式的Tomcat和Jetty?
29 | 比较:Jetty如何实现具有上下文信息的责任链?
30 | 热点问题答疑(3):Spring框架中的设计模式
模块五 通用组件 (4讲)
31 | Logger组件:Tomcat的日志框架及实战
32 | Manager组件:Tomcat的Session管理机制解析
33 | Cluster组件:Tomcat的集群通信原理
特别放送 | 如何持续保持对学习的兴趣?
模块六 性能优化 (8讲)
34 | JVM GC原理及调优的基本思路
35 | 如何监控Tomcat的性能?
36 | Tomcat I/O和线程池的并发调优
37 | Tomcat内存溢出的原因分析及调优
38 | Tomcat拒绝连接原因分析及网络优化
39 | Tomcat进程占用CPU过高怎么办?
40 | 谈谈Jetty性能调优的思路
41 | 热点问题答疑(4): Tomcat和Jetty有哪些不同?
结束语 (1讲)
结束语 | 静下心来,品味经典
深入拆解Tomcat & Jetty
登录|注册

22 | 热点问题答疑(2):内核如何阻塞与唤醒进程?

李号双 2019-06-29
在专栏的第三个模块,我们学习了 Tomcat 连接器组件的设计,其中最重要的是各种 I/O 模型及其实现。而 I/O 模型跟操作系统密切相关,要彻底理解这些原理,我们首先需要弄清楚什么是进程和线程,什么是虚拟内存和物理内存,什么是用户空间和内核空间,线程的阻塞到底意味着什么,内核又是如何唤醒用户线程的等等这些问题。可以说掌握这些底层的知识,对于你学习 Tomcat 和 Jetty 的原理,乃至其他各种后端架构都至关重要,这些知识可以说是后端开发的“基石”。
在专栏的留言中我也发现很多同学反馈对这些底层的概念很模糊,那今天作为模块的答疑篇,我就来跟你聊聊这些问题。

进程和线程

我们先从 Linux 的进程谈起,操作系统要运行一个可执行程序,首先要将程序文件加载到内存,然后 CPU 去读取和执行程序指令,而一个进程就是“一次程序的运行过程”,内核会给每一个进程创建一个名为task_struct的数据结构,而内核也是一段程序,系统启动时就被加载到内存中了。
进程在运行过程中要访问内存,而物理内存是有限的,比如 16GB,那怎么把有限的内存分给不同的进程使用呢?跟 CPU 的分时共享一样,内存也是共享的,Linux 给每个进程虚拟出一块很大的地址空间,比如 32 位机器上进程的虚拟内存地址空间是 4GB,从 0x00000000 到 0xFFFFFFFF。但这 4GB 并不是真实的物理内存,而是进程访问到了某个虚拟地址,如果这个地址还没有对应的物理内存页,就会产生缺页中断,分配物理内存,MMU(内存管理单元)会将虚拟地址与物理内存页的映射关系保存在页表中,再次访问这个虚拟地址,就能找到相应的物理内存页。每个进程的这 4GB 虚拟地址空间分布如下图所示:
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《深入拆解Tomcat & Jetty 》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(24)

  • 一塌糊涂
    老师问个问题,用户态切换到内核态,使用的是虚拟空间内核地址?这时用户线程会挂起执行内核线程吗?这是两个线程吗?

    作者回复: cpu处于内核态时使用的是内核地址空间,用户线程的挂起其实是由内核完成的,具体来说就是系统调用发触发软中断,cpu在内核态模式下执行软中断程序,也就是系统调用的具体实现函数(内核代码),内核代码执行过程中发现网络数据未就绪就主动让出cpu。这个时候才会将当前线程阻塞,因此从这个角度看,不是两个线程,而是一个线程在不同cpu模式下的执行过程。

    2019-07-11
    1
    8
  • 佑儿
    虚拟内存是一个联系的地址空间,该地址空间由不一定连续的物理内存组成。 这样理解对吗?

    作者回复: 对的

    2019-07-01
    4
  • 东方奇骥
    答疑干货还挺多的,操作系统一直是自己的弱项,最近也在学习,然后发现极客时间专栏里牛逼的老师操作系统基础都很好。
    2019-06-30
    4
  • brianway
    “线程有自己的task_struct结构体和运行栈区,但是线程的其他资源都是跟父进程共用”,这句话怎么理解。线程的task_struct结构体和运行栈区在图中哪个部分?

    是从进程的内核空间里面分配出来的,还是用户空间里分配出来的?具体哪个部分的内存?

    作者回复: 每个线程都有tast_struct,由内核创建,在内核空间上。

    2019-07-05
    3
  • 飞翔
    用户态和用户空间是啥关系?

    作者回复: 你可以理解为CPU上有个开关,可以设置CPU的工作模式:用户态和内核态。在用户态模式下访问用户空间,也就是低地址的3GB。

    2019-06-29
    1
    3
  • 长脖子树
    看了作者的文章又去看了 copy-on-write 和 MappedByteBuffer 了解又加深了一层 哈哈

    作者回复: 赞

    2019-08-16
    2
  • nightmare
    老师今天讲了线程和进程,进程和线程都是统一在内核空间建立task_truct,根据代码是否有系统调用在用户态和内核态来做上下文切换,然后还讲了read的系统调用过程以及进程的虚拟内存和物理内存的机制,有一点没明白,是每个进程都会有一个虚拟内核空间吗?然后进程的虚拟内核空间映射到系统管理的内核空间上?

    作者回复: 每一个进程的进程空间都包含内核空间,但是内核是各进程共享的,因此可以这里理解,内核代码运行过程中访问的内存空间被映射到各个进程空间的高地址3-4G。

    2019-06-30
    2
  • -W.LI-
    感谢老师,万分感谢。上次有个问题我不明白,老师还帮我查阅源码确认了。李老师,还有http那个老师是最最负责的真的万分感谢。
    向老师,我要把计算机组成原理和操作系统自己看一遍看不懂就看两遍。

    作者回复: 😁

    2019-06-29
    2
  • Liam
    如果是通过mmap读数据,流程是怎样的呢?

    1 如果没有数据,是否会阻塞?
    2 不需要拷贝数据?意思是用户进程可以直接读mmap,不需要拷贝到堆吗?

    作者回复: mmap不支持Socket读写,只支持磁盘文件。

    通过mmap将文件映射到内存后,直接写读写内存,内核会负责将数据刷新到磁盘文件。

    2019-06-29
    2
  • 代码搬运工
    老师,每节课后的思考题什么时侯也答疑下

    作者回复: 嗯,基本上每篇的留言回复里都能找到答案

    2019-06-29
    2
  • Ericens
    李老师,请教个关于协程与线程的疑问。比如,a协程调用socket. read(),此时数据没有准备好,则继续调度b协程。

    把协程换成线程,上面这个过程哪个更加轻量?协程还是线程?
    我理解这个过程涉及的过程如下,都一样。
    1.都有系统调用read(),从用户态切换到了内核态,
    2.都有上下文切换。(不同协程的寄存器,和不同线程的寄存器)
    3. 都要执行任务调度。协程调度或者线程调度。

    那协程到底轻量在哪?
    2019-10-09
    2
    1
  • 帽子丨影
    老师好,既然用户态运行时也会占用cpu,内核态又可以访问整个虚拟空间,为什么不让cpu一直处在内核态呢,这样就没有切换带来损耗了

    作者回复: 如果是这样的话,你写的应用程序直接在内核态运行,权限级别太高,出了问题会导致整个操作系统崩溃,所有才有了用户态核心态,算是一种隔离和容错吧。

    2019-09-24
    1
  • 妥协
    您是不是指的数据区和堆区?数据区和栈区不是连续的呀

    作者回复: 是数据区和栈区的中间,这个中间的部分有本地堆区,JVM内存区,mmap映射都在这里

    2019-07-04
    1
  • 靠人品去赢
    进程和线程:进程的本质就是“一个程序运行的过程”,而线程可以看做是一个小进程因为他也有进程的task_struct这些东西,但是他是跟父进程公用资源的。
    阻塞的本质:就是移出运行队列到等待队列,条件好了内核通知把数据写进去用户态,就又进入到运行队列等待CPU时间片。
    老师这本质也归纳的太好了吧。
    2019-10-16
  • 楊_宵夜
    李老师好,能说说nio下的内核态及用户态的切换情形吗?
    2019-08-26
  • 星辰
    老师 从来没人跟我说过基础要看啥书 我都是东一榔头 西一棒槌 想求老师告知一两本基石的书。 我在网络专栏也提了同样的问题,希望你们有一位老师能回答我的问题。如果重复了我就去重,把要看书单压到枕头底下。 (c方面只考过c++二级)

    作者回复: unix环境高级编程,数据结构和算法,java编程思想

    2019-08-16
  • xiaolin777
    老师,您说的task_struct是指进程控制块吗?(刚才还没打完就点了保存,以为是保存草稿,没想到直接提交了,后台能删除吗)

    作者回复: 对的,进程控制块是个比较抽象的名字,具体实现就是内核的一个数据结构task_struct

    2019-08-13
  • 听雨
    1、文中开始说:“task_struct中保存了打开的文件、创建的socket以及CPU运行的上下文"。后面又说"线程有自己的task_struct“。那最后说的“和父进程共用的资源:创建的socket。打开的文件等”这些是怎么共享呀,socket和打开的文件都是在task_struct中保存的,线程有自己独立的task_struct,还需要共享吗?
    2、task_struct为什么不用数组呀,数组不是更节省空间吗,而且随机访问这个特性也支持CPU随机调度(猜的,从多线程的不确定性感觉应该是随机调度),是考虑到这个数组有可能很大和频繁扩容的问题才采用双向链表吗?
    以上问题,请老师指点一下!

    作者回复: 1.task_struct上保存的是指针,本质是一个内存地址,比如打开的文件是这样的:
    struct files_struct *files;

    一个进程内的所有线程的task_struct的files变量都指向同一个内存地址,也就是同一个files_struct结构体

    2,线程需要动态的创建和销毁,意味task_struct链表需要频繁的添加和删除节点,所以用链表

    2019-08-12
  • aroll
    老师请问一下,sendfile系统调用时,文件内容通过DMA模块被复制到内核缓冲区中。如果此时应用程序还要操作读出来的数据,而不是直接通过socket将数据发送出去,那么是不是还是避免不了数据从内核空间复制到用户空间? 还是说sendfile和mmap可以一起用,此时只需要在共享内存空间中操作即可?
    2019-08-07
  • 梁中华
    文中混合使用“可运行队列”和“运行队列”,这两者应该是同一个队列吧。另外关于线程唤醒部分我有一个理解,老师看看对否:当前线程因IO等待而进入"阻塞状态(Blocked)",同时进入线程进入等待队列。让IO操作ready后,内核唤醒等待中的线程,线程状态变成"可运行状态(Runnable)",同时该线程进入可运行队列,等待CPU调度后进入运行状态(Running)。

    作者回复: 对的

    2019-07-10
收起评论
24
返回
顶部