在 Linux 中、一切都是文件、硬件设备是文件、管道是文件、网络套接字也是文件。
for
https://juejin.cn/post/6844904103437582344
fork 进程的一些问题
fork 函数比较特殊、一次调用会返回两次。在父进程和子进程都会返回。
每个进程在内核中都是一个 taskstruct 结构、fork 的时候、内核把内核中原来的 task_struct 拷贝了一份
还创建了一个全新的地址空间和堆栈
进程的地址空间没有拷贝,子进程和父进程是在共享内存空间的。都是映射到同一个物理内存页上的
内核把这些页面都设置成只读、如果你们是读的话、不会有问题、但是有一方尝试写入、就会触发异常。内核发现异常后再分配一个新的页面让你们分开使用。这个就是写时复制。
线程的栈
线程的栈默认是 8M、实际上一开始分配给栈并不是那么大的空间。会按需自动增大。直到大于 8M 就会栈溢出。
进程间如何通信
信号
信号时 Linux 上的一种软中断通信机制、可以向制定进程发送通知。总共有 64 种信号、不过信号只能作为通知使用。没办法传输数据。
socket 套接字
网络通信使用。可以将连接的地址设置为 127.0.0.1 作为本地计算机通信。这样子的话、不需要经过网卡、因为 127.0.0.1 是本地回环地址,数据在协议栈就转发。 但是抓包可以抓到、因为在虚拟的回环网卡lo上抓到。
匿名管道
单向。满了会阻塞
需要有亲缘关系的进程继承指挥才能通信。
命名管道
只要指明管道名称就能通信。不需要进程间有关系。
消息队列
共享内存
IO 多路复用
自己写了一个 web 服务进程。监听某个端口、如何处理客户端的连接请求。(单线程 while 去接受处理客户端连接请求)
当客户连接成功之后、但是一直不传输数据、线程会一直卡在那里、并且没法再处理其他客户的连接请求了
多线程去应对
每一个连接过来都去创建一个新的线程去处理。
select 模型
越来越多的连接过来了、线程越来越多、终于顶唔住了。。。。
I/O 多路复用
有个叫 select 的函数、你把文件描述符批量传给他,平时他阻塞在那里、只要其中有一个有消息来、它就会返回,你这个时候去检查睡来消息,并去处理就行了。
select 函数是怎么做到的?
它会遍历所有的文件描述符、把你挂入与这些文件描述符相关联的设备等待队列中、如何交出执行权进入阻塞,等后面这些设备来消息、然后通过回调函数通知你。把你唤醒。
但是 select 函数底层时使用位图数组来存储要管理的文件描述符的、容量有上线。最多只能同时处理 1024 个文件描述符。
poll
但是 poll 和 select 一样。慢!!
为啥慢?
1、 每次返回后不告诉程序到底是哪一个文件描述符有消息、需要程序一个个遍历。耽搁了不少时间
2、每次调用他们的时候、都要把所有的文件描述符从用户态地址空间拷贝到内核中,这样子经常拷贝也费时间。
epoll
epoll 这个多路复用模型、不需要每次拷贝全部的数据、只需要增减就行。因为它内部采用红黑树来管理监听的文件描述符,所以查找起来很快。而且它内部还有一个队列、所有就绪的文件描述符都会进入中国队列。程序不再需要遍历所有的文件描述符去找来消息的那个了。
像访问内存一样读写文件
因为磁盘太慢了、所以在内核空间中给每一个要读取的文件建立一个数据结构。里面记录了已经缓存的文件数据块信息。从硬盘读过来的数据就缓存到内存。并记录到这个数据结构中。
以后读取文件的时候、先通过这个数据结构去查询、查到就直接拷贝给应用、查不到才去找磁盘要。
CPU的局部性原理在这里也适用。
在写文件的时候是先写到这个缓存里面、并不会立即同步刷到磁盘的、这时候突然断电、缓存的数据就会丢掉了。
sync 函数、只要你调用它、就会马上进行同步、写入硬盘。
内存映射文件
读取文件时需要进行两次拷贝、第一次从磁盘拷贝到内核的缓存页中、第二次把它从缓存中拷贝到应用程序的缓冲区中。
写的时候也是同理。
把文件的数据缓存页映射到用户态地址空间、这样用户态地址空间的缓冲区和缓存页就能映射到同一个物理内存页。
再进一步的话就是
在进程的地址空间划分一块区域和文件内容简历映射关系、等到应用程序访问这部分区域的时候、会发生缺页中断错误、这时我们把数据从硬盘读取到缓存页中、再把缓存页和进程中缺页中断的页面关联起来。
这样子对应用无感。不用再使用 read、write、fseek 这样麻烦地读写文件。
这个 api 叫 mmap 内存映射文件。
协程
A:Java 线程执行阻塞函数时被操作系统挂起、切换到别的线程。
B:线程切换是否需要成本?如果大量线程频繁切换、成本又当如何?
A:如果担心这个问题、那就不用阻塞函数、通过异步回调进行。
B:异步回调确实不用阻塞、不过它有两点不好、其一就是割裂了原来的业务代码、其二就是回调地狱。
协程
线程可以在执行函数遇到阻塞后,保存执行的上下文、转而执行别处到代码。待阻塞请求完成后、再回去继续执行。
线程是有操作系统统一调度管理的。那么在一个线程中、同样可以抽象出多个执行流、由线程来统一调度管理、这线程智商抽象的执行流就是协程。
线程时操作系统在调度管理、那线程里抽出来的执行流、也就是协程、该怎么调度管理?
OS 通过时钟中断和系统调用进入内核来剥夺线程的执行权、那线程如何剥夺协程的执行权来实现调度管理?
协程:协作式程序。它会主动交出执行权。
Java19 已经支持了虚拟线程(协程)、或者使用第三方协程框架 Quasar
https://zhuanlan.zhihu.com/p/425978232
https://book.douban.com/subject/36428782/